compiler: lower interfaces in a separate pass
This commit changes many things: * Most interface-related operations are moved into an optimization pass for more modularity. IR construction creates pseudo-calls which are lowered in this pass. * Type codes are assigned in this interface lowering pass, after DCE. * Type codes are sorted by usage: types more often used in type asserts are assigned lower numbers to ease jump table construction during machine code generation. * Interface assertions are optimized: they are replaced by constant false, comparison against a constant, or a typeswitch with only concrete types in the general case. * Interface calls are replaced with unreachable, direct calls, or a concrete type switch with direct calls depending on the number of implementing types. This hopefully makes some interface patterns zero-cost. These changes lead to a ~0.5K reduction in code size on Cortex-M for testdata/interface.go. It appears that a major cause for this is the replacement of function pointers with direct calls, which are far more susceptible to optimization. Also, not having a fixed global array of function pointers greatly helps dead code elimination. This change also makes future optimizations easier, like optimizations on interface value comparisons.
Этот коммит содержится в:
родитель
e45c4ac182
коммит
b4c90f3677
13 изменённых файлов: 1039 добавлений и 535 удалений
|
@ -43,29 +43,30 @@ type Config struct {
|
||||||
|
|
||||||
type Compiler struct {
|
type Compiler struct {
|
||||||
Config
|
Config
|
||||||
mod llvm.Module
|
mod llvm.Module
|
||||||
ctx llvm.Context
|
ctx llvm.Context
|
||||||
builder llvm.Builder
|
builder llvm.Builder
|
||||||
dibuilder *llvm.DIBuilder
|
dibuilder *llvm.DIBuilder
|
||||||
cu llvm.Metadata
|
cu llvm.Metadata
|
||||||
difiles map[string]llvm.Metadata
|
difiles map[string]llvm.Metadata
|
||||||
ditypes map[string]llvm.Metadata
|
ditypes map[string]llvm.Metadata
|
||||||
machine llvm.TargetMachine
|
machine llvm.TargetMachine
|
||||||
targetData llvm.TargetData
|
targetData llvm.TargetData
|
||||||
intType llvm.Type
|
intType llvm.Type
|
||||||
i8ptrType llvm.Type // for convenience
|
i8ptrType llvm.Type // for convenience
|
||||||
uintptrType llvm.Type
|
uintptrType llvm.Type
|
||||||
coroIdFunc llvm.Value
|
coroIdFunc llvm.Value
|
||||||
coroSizeFunc llvm.Value
|
coroSizeFunc llvm.Value
|
||||||
coroBeginFunc llvm.Value
|
coroBeginFunc llvm.Value
|
||||||
coroSuspendFunc llvm.Value
|
coroSuspendFunc llvm.Value
|
||||||
coroEndFunc llvm.Value
|
coroEndFunc llvm.Value
|
||||||
coroFreeFunc llvm.Value
|
coroFreeFunc llvm.Value
|
||||||
initFuncs []llvm.Value
|
initFuncs []llvm.Value
|
||||||
deferFuncs []*ir.Function
|
deferFuncs []*ir.Function
|
||||||
deferInvokeFuncs []InvokeDeferFunction
|
deferInvokeFuncs []InvokeDeferFunction
|
||||||
ctxDeferFuncs []ContextDeferFunction
|
ctxDeferFuncs []ContextDeferFunction
|
||||||
ir *ir.Program
|
interfaceInvokeWrappers []interfaceInvokeWrapper
|
||||||
|
ir *ir.Program
|
||||||
}
|
}
|
||||||
|
|
||||||
type Frame struct {
|
type Frame struct {
|
||||||
|
@ -489,6 +490,15 @@ func (c *Compiler) Compile(mainPath string) error {
|
||||||
c.builder.CreateRetVoid()
|
c.builder.CreateRetVoid()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Define the already declared functions that wrap methods for use in
|
||||||
|
// interfaces.
|
||||||
|
for _, state := range c.interfaceInvokeWrappers {
|
||||||
|
err = c.createInterfaceInvokeWrapper(state)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// After all packages are imported, add a synthetic initializer function
|
// After all packages are imported, add a synthetic initializer function
|
||||||
// that calls the initializer of each package.
|
// that calls the initializer of each package.
|
||||||
initFn := c.ir.GetFunction(c.ir.Program.ImportedPackage("runtime").Members["initAll"].(*ssa.Function))
|
initFn := c.ir.GetFunction(c.ir.Program.ImportedPackage("runtime").Members["initAll"].(*ssa.Function))
|
||||||
|
@ -534,13 +544,6 @@ func (c *Compiler) Compile(mainPath string) error {
|
||||||
}
|
}
|
||||||
c.builder.CreateRetVoid()
|
c.builder.CreateRetVoid()
|
||||||
|
|
||||||
// Add runtime type information for interfaces: interface calls and type
|
|
||||||
// asserts.
|
|
||||||
err = c.createInterfaceRTTI()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// see: https://reviews.llvm.org/D18355
|
// see: https://reviews.llvm.org/D18355
|
||||||
c.mod.AddNamedMetadataOperand("llvm.module.flags",
|
c.mod.AddNamedMetadataOperand("llvm.module.flags",
|
||||||
c.ctx.MDNode([]llvm.Metadata{
|
c.ctx.MDNode([]llvm.Metadata{
|
||||||
|
@ -995,17 +998,6 @@ func (c *Compiler) getInterpretedValue(prefix string, value ir.Value) (llvm.Valu
|
||||||
ptr := llvm.ConstInBoundsGEP(value.Global.LLVMGlobal, []llvm.Value{zero})
|
ptr := llvm.ConstInBoundsGEP(value.Global.LLVMGlobal, []llvm.Value{zero})
|
||||||
return ptr, nil
|
return ptr, nil
|
||||||
|
|
||||||
case *ir.InterfaceValue:
|
|
||||||
underlying := llvm.ConstPointerNull(c.i8ptrType) // could be any 0 value
|
|
||||||
if value.Elem != nil {
|
|
||||||
elem, err := c.getInterpretedValue(prefix, value.Elem)
|
|
||||||
if err != nil {
|
|
||||||
return llvm.Value{}, err
|
|
||||||
}
|
|
||||||
underlying = elem
|
|
||||||
}
|
|
||||||
return c.parseMakeInterface(underlying, value.Type, prefix)
|
|
||||||
|
|
||||||
case *ir.MapValue:
|
case *ir.MapValue:
|
||||||
// Create initial bucket.
|
// Create initial bucket.
|
||||||
firstBucketGlobal, keySize, valueSize, err := c.initMapNewBucket(prefix, value.Type)
|
firstBucketGlobal, keySize, valueSize, err := c.initMapNewBucket(prefix, value.Type)
|
||||||
|
|
715
compiler/interface-lowering.go
Обычный файл
715
compiler/interface-lowering.go
Обычный файл
|
@ -0,0 +1,715 @@
|
||||||
|
package compiler
|
||||||
|
|
||||||
|
// This file provides function to lower interface intrinsics to their final LLVM
|
||||||
|
// form, optimizing them in the process.
|
||||||
|
//
|
||||||
|
// During SSA construction, the following pseudo-calls are created:
|
||||||
|
// runtime.makeInterface(typecode, methodSet)
|
||||||
|
// runtime.typeAssert(typecode, assertedType)
|
||||||
|
// runtime.interfaceImplements(typecode, interfaceMethodSet)
|
||||||
|
// runtime.interfaceMethod(typecode, interfaceMethodSet, signature)
|
||||||
|
// See src/runtime/interface.go for details.
|
||||||
|
// These calls are to declared but not defined functions, so the optimizer will
|
||||||
|
// leave them alone.
|
||||||
|
//
|
||||||
|
// This pass lowers the above functions to their final form:
|
||||||
|
//
|
||||||
|
// makeInterface:
|
||||||
|
// Replaced with a constant typecode.
|
||||||
|
//
|
||||||
|
// typeAssert:
|
||||||
|
// Replaced with an icmp instruction so it can be directly used in a type
|
||||||
|
// switch. This is very easy to optimize for LLVM: it will often translate a
|
||||||
|
// type switch into a regular switch statement.
|
||||||
|
// When this type assert is not possible (the type is never used in an
|
||||||
|
// interface with makeInterface), this call is replaced with a constant
|
||||||
|
// false to optimize the type assert away completely.
|
||||||
|
//
|
||||||
|
// interfaceImplements:
|
||||||
|
// This call is translated into a call that checks whether the underlying
|
||||||
|
// type is one of the types implementing this interface.
|
||||||
|
// When there is only one type implementing this interface, the check is
|
||||||
|
// replaced with a simple icmp instruction, just like a type assert.
|
||||||
|
// When there is no type at all that implements this interface, it is
|
||||||
|
// replaced with a constant false to optimize it completely.
|
||||||
|
//
|
||||||
|
// interfaceMethod:
|
||||||
|
// This call is replaced with a call to a function that calls the
|
||||||
|
// appropriate method depending on the underlying type.
|
||||||
|
// When there is only one type implementing this interface, this call is
|
||||||
|
// translated into a direct call of that method.
|
||||||
|
// When there is no type implementing this interface, this code is marked
|
||||||
|
// unreachable as there is no way such an interface could be constructed.
|
||||||
|
//
|
||||||
|
// Note that this way of implementing interfaces is very different from how the
|
||||||
|
// main Go compiler implements them. For more details on how the main Go
|
||||||
|
// compiler does it: https://research.swtch.com/interfaces
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/aykevl/go-llvm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// signatureInfo is a Go signature of an interface method. It does not represent
|
||||||
|
// any method in particular.
|
||||||
|
type signatureInfo struct {
|
||||||
|
name string
|
||||||
|
methods []*methodInfo
|
||||||
|
interfaces []*interfaceInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// methodName takes a method name like "func String()" and returns only the
|
||||||
|
// name, which is "String" in this case.
|
||||||
|
func (s *signatureInfo) methodName() string {
|
||||||
|
if !strings.HasPrefix(s.name, "func ") {
|
||||||
|
panic("signature must start with \"func \"")
|
||||||
|
}
|
||||||
|
methodName := s.name[len("func "):]
|
||||||
|
if openingParen := strings.IndexByte(methodName, '('); openingParen < 0 {
|
||||||
|
panic("no opening paren in signature name")
|
||||||
|
} else {
|
||||||
|
return methodName[:openingParen]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// methodInfo describes a single method on a concrete type.
|
||||||
|
type methodInfo struct {
|
||||||
|
*signatureInfo
|
||||||
|
function llvm.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// typeInfo describes a single concrete Go type, which can be a basic or a named
|
||||||
|
// type. If it is a named type, it may have methods.
|
||||||
|
type typeInfo struct {
|
||||||
|
name string
|
||||||
|
typecode llvm.Value
|
||||||
|
methodSet llvm.Value
|
||||||
|
num uint64 // the type number after lowering
|
||||||
|
countMakeInterfaces int // how often this type is used in an interface
|
||||||
|
countTypeAsserts int // how often a type assert happens on this method
|
||||||
|
methods []*methodInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// getMethod looks up the method on this type with the given signature and
|
||||||
|
// returns it. The method must exist on this type, otherwise getMethod will
|
||||||
|
// panic.
|
||||||
|
func (t *typeInfo) getMethod(signature *signatureInfo) *methodInfo {
|
||||||
|
for _, method := range t.methods {
|
||||||
|
if method.signatureInfo == signature {
|
||||||
|
return method
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic("could not find method")
|
||||||
|
}
|
||||||
|
|
||||||
|
// id returns the fully-qualified type name including import path, removing the
|
||||||
|
// $type suffix.
|
||||||
|
func (t *typeInfo) id() string {
|
||||||
|
if !strings.HasSuffix(t.name, "$type") {
|
||||||
|
panic("concrete type does not have $type suffix: " + t.name)
|
||||||
|
}
|
||||||
|
return t.name[:len(t.name)-len("$type")]
|
||||||
|
}
|
||||||
|
|
||||||
|
// typeInfoSlice implements sort.Slice, sorting the most commonly used types
|
||||||
|
// first.
|
||||||
|
type typeInfoSlice []*typeInfo
|
||||||
|
|
||||||
|
func (t typeInfoSlice) Len() int { return len(t) }
|
||||||
|
func (t typeInfoSlice) Less(i, j int) bool {
|
||||||
|
// Try to sort the most commonly used types first.
|
||||||
|
if t[i].countTypeAsserts != t[j].countTypeAsserts {
|
||||||
|
return t[i].countTypeAsserts < t[j].countTypeAsserts
|
||||||
|
}
|
||||||
|
if t[i].countMakeInterfaces != t[j].countMakeInterfaces {
|
||||||
|
return t[i].countMakeInterfaces < t[j].countMakeInterfaces
|
||||||
|
}
|
||||||
|
return t[i].name < t[j].name
|
||||||
|
}
|
||||||
|
func (t typeInfoSlice) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
|
||||||
|
|
||||||
|
// interfaceInfo keeps information about a Go interface type, including all
|
||||||
|
// methods it has.
|
||||||
|
type interfaceInfo struct {
|
||||||
|
name string // name with $interface suffix
|
||||||
|
signatures []*signatureInfo // method set
|
||||||
|
types typeInfoSlice // types this interface implements
|
||||||
|
assertFunc llvm.Value // runtime.interfaceImplements replacement
|
||||||
|
methodFuncs map[*signatureInfo]llvm.Value // runtime.interfaceMethod replacements for each signature
|
||||||
|
}
|
||||||
|
|
||||||
|
// id removes the $interface suffix from the name and returns the clean
|
||||||
|
// interface name including import path.
|
||||||
|
func (itf *interfaceInfo) id() string {
|
||||||
|
if !strings.HasSuffix(itf.name, "$interface") {
|
||||||
|
panic("interface type does not have $interface suffix: " + itf.name)
|
||||||
|
}
|
||||||
|
return itf.name[:len(itf.name)-len("$interface")]
|
||||||
|
}
|
||||||
|
|
||||||
|
// lowerInterfacesPass keeps state related to the interface lowering pass. The
|
||||||
|
// pass has been implemented as an object type because of its complexity, but
|
||||||
|
// should be seen as a regular function call (see LowerInterfaces).
|
||||||
|
type lowerInterfacesPass struct {
|
||||||
|
*Compiler
|
||||||
|
types map[string]*typeInfo
|
||||||
|
signatures map[string]*signatureInfo
|
||||||
|
interfaces map[string]*interfaceInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lower all interface functions. They are emitted by the compiler as
|
||||||
|
// higher-level intrinsics that need some lowering before LLVM can work on them.
|
||||||
|
// This is done so that a few cleanup passes can run before assigning the final
|
||||||
|
// type codes.
|
||||||
|
func (c *Compiler) LowerInterfaces() {
|
||||||
|
p := &lowerInterfacesPass{
|
||||||
|
Compiler: c,
|
||||||
|
types: make(map[string]*typeInfo),
|
||||||
|
signatures: make(map[string]*signatureInfo),
|
||||||
|
interfaces: make(map[string]*interfaceInfo),
|
||||||
|
}
|
||||||
|
p.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// run runs the pass itself.
|
||||||
|
func (p *lowerInterfacesPass) run() {
|
||||||
|
// Count per type how often it is put in an interface. Also, collect all
|
||||||
|
// methods this type has (if it is named).
|
||||||
|
makeInterface := p.mod.NamedFunction("runtime.makeInterface")
|
||||||
|
makeInterfaceUses := getUses(makeInterface)
|
||||||
|
for _, use := range makeInterfaceUses {
|
||||||
|
typecode := use.Operand(0)
|
||||||
|
name := typecode.Name()
|
||||||
|
if t, ok := p.types[name]; !ok {
|
||||||
|
// This is the first time this type has been seen, add it to the
|
||||||
|
// list of types.
|
||||||
|
t = p.addType(typecode)
|
||||||
|
p.addTypeMethods(t, use.Operand(1))
|
||||||
|
} else {
|
||||||
|
p.addTypeMethods(t, use.Operand(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count the number of MakeInterface instructions, for sorting the
|
||||||
|
// typecodes later.
|
||||||
|
p.types[name].countMakeInterfaces++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count per type how often it is type asserted on (e.g. in a switch
|
||||||
|
// statement).
|
||||||
|
typeAssert := p.mod.NamedFunction("runtime.typeAssert")
|
||||||
|
typeAssertUses := getUses(typeAssert)
|
||||||
|
for _, use := range typeAssertUses {
|
||||||
|
typecode := use.Operand(1)
|
||||||
|
name := typecode.Name()
|
||||||
|
if _, ok := p.types[name]; !ok {
|
||||||
|
p.addType(typecode)
|
||||||
|
}
|
||||||
|
p.types[name].countTypeAsserts++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all interface method calls.
|
||||||
|
interfaceMethod := p.mod.NamedFunction("runtime.interfaceMethod")
|
||||||
|
interfaceMethodUses := getUses(interfaceMethod)
|
||||||
|
for _, use := range interfaceMethodUses {
|
||||||
|
methodSet := use.Operand(1).Operand(0)
|
||||||
|
name := methodSet.Name()
|
||||||
|
if _, ok := p.interfaces[name]; !ok {
|
||||||
|
p.addInterface(methodSet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all interface type asserts.
|
||||||
|
interfaceImplements := p.mod.NamedFunction("runtime.interfaceImplements")
|
||||||
|
interfaceImplementsUses := getUses(interfaceImplements)
|
||||||
|
for _, use := range interfaceImplementsUses {
|
||||||
|
methodSet := use.Operand(1).Operand(0)
|
||||||
|
name := methodSet.Name()
|
||||||
|
if _, ok := p.interfaces[name]; !ok {
|
||||||
|
p.addInterface(methodSet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all the interfaces that are implemented per type.
|
||||||
|
for _, t := range p.types {
|
||||||
|
// This type has no methods, so don't spend time calculating them.
|
||||||
|
if len(t.methods) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre-calculate a set of signatures that this type has, for easy
|
||||||
|
// lookup/check.
|
||||||
|
typeSignatureSet := make(map[*signatureInfo]struct{})
|
||||||
|
for _, method := range t.methods {
|
||||||
|
typeSignatureSet[method.signatureInfo] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A set of interfaces, mapped from the name to the info.
|
||||||
|
// When the name maps to a nil pointer, one of the methods of this type
|
||||||
|
// exists in the given interface but not all of them so this type
|
||||||
|
// doesn't implement the interface.
|
||||||
|
satisfiesInterfaces := make(map[string]*interfaceInfo)
|
||||||
|
|
||||||
|
for _, method := range t.methods {
|
||||||
|
for _, itf := range method.interfaces {
|
||||||
|
if _, ok := satisfiesInterfaces[itf.name]; ok {
|
||||||
|
// interface already checked with a different method
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// check whether this interface satisfies this type
|
||||||
|
satisfies := true
|
||||||
|
for _, itfSignature := range itf.signatures {
|
||||||
|
if _, ok := typeSignatureSet[itfSignature]; !ok {
|
||||||
|
satisfiesInterfaces[itf.name] = nil // does not satisfy
|
||||||
|
satisfies = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !satisfies {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
satisfiesInterfaces[itf.name] = itf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add this type to all interfaces that satisfy this type.
|
||||||
|
for _, itf := range satisfiesInterfaces {
|
||||||
|
if itf == nil {
|
||||||
|
// Interface does not implement this type, but one of the
|
||||||
|
// methods on this type also exists on the interface.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
itf.types = append(itf.types, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort all types added to the interfaces, to check for more common types
|
||||||
|
// first.
|
||||||
|
for _, itf := range p.interfaces {
|
||||||
|
sort.Sort(itf.types)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace all interface methods with their uses, if possible.
|
||||||
|
for _, use := range interfaceMethodUses {
|
||||||
|
typecode := use.Operand(0)
|
||||||
|
signature := p.signatures[use.Operand(2).Name()]
|
||||||
|
|
||||||
|
// If the interface was created in the same function, we can insert a
|
||||||
|
// direct call. This may not happen often but it is an easy
|
||||||
|
// optimization so let's do it anyway.
|
||||||
|
if !typecode.IsACallInst().IsNil() && typecode.CalledValue() == makeInterface {
|
||||||
|
name := typecode.Operand(0).Name()
|
||||||
|
typ := p.types[name]
|
||||||
|
p.replaceInvokeWithCall(use, typ, signature)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
methodSet := use.Operand(1).Operand(0) // global variable
|
||||||
|
itf := p.interfaces[methodSet.Name()]
|
||||||
|
if len(itf.types) == 0 {
|
||||||
|
// This method call is impossible: no type implements this
|
||||||
|
// interface. In fact, the previous type assert that got this
|
||||||
|
// interface value should already have returned false.
|
||||||
|
// Replace the function pointer with undef (which will then be
|
||||||
|
// called), indicating to the optimizer this code is unreachable.
|
||||||
|
use.ReplaceAllUsesWith(llvm.Undef(p.i8ptrType))
|
||||||
|
use.EraseFromParentAsInstruction()
|
||||||
|
} else if len(itf.types) == 1 {
|
||||||
|
// There is only one implementation of the given type.
|
||||||
|
// Call that function directly.
|
||||||
|
p.replaceInvokeWithCall(use, itf.types[0], signature)
|
||||||
|
} else {
|
||||||
|
// There are multiple types implementing this interface, thus there
|
||||||
|
// are multiple possible functions to call. Delegate calling the
|
||||||
|
// right function to a special wrapper function.
|
||||||
|
bitcasts := getUses(use)
|
||||||
|
if len(bitcasts) != 1 || bitcasts[0].IsABitCastInst().IsNil() {
|
||||||
|
panic("expected exactly one bitcast use of runtime.interfaceMethod")
|
||||||
|
}
|
||||||
|
bitcast := bitcasts[0]
|
||||||
|
calls := getUses(bitcast)
|
||||||
|
if len(calls) != 1 || calls[0].IsACallInst().IsNil() {
|
||||||
|
panic("expected exactly one call use of runtime.interfaceMethod")
|
||||||
|
}
|
||||||
|
call := calls[0]
|
||||||
|
|
||||||
|
// Set up parameters for the call. First copy the regular params...
|
||||||
|
params := make([]llvm.Value, call.OperandsCount())
|
||||||
|
paramTypes := make([]llvm.Type, len(params))
|
||||||
|
for i := 0; i < len(params)-1; i++ {
|
||||||
|
params[i] = call.Operand(i)
|
||||||
|
paramTypes[i] = params[i].Type()
|
||||||
|
}
|
||||||
|
// then add the typecode to the end of the list.
|
||||||
|
params[len(params)-1] = typecode
|
||||||
|
paramTypes[len(params)-1] = p.uintptrType
|
||||||
|
|
||||||
|
// Create a function that redirects the call to the destination
|
||||||
|
// call, after selecting the right concrete type.
|
||||||
|
redirector := p.getInterfaceMethodFunc(itf, signature, call.Type(), paramTypes)
|
||||||
|
|
||||||
|
// Replace the old lookup/bitcast/call with the new call.
|
||||||
|
p.builder.SetInsertPointBefore(call)
|
||||||
|
retval := p.builder.CreateCall(redirector, params, "")
|
||||||
|
if retval.Type().TypeKind() != llvm.VoidTypeKind {
|
||||||
|
call.ReplaceAllUsesWith(retval)
|
||||||
|
}
|
||||||
|
call.EraseFromParentAsInstruction()
|
||||||
|
bitcast.EraseFromParentAsInstruction()
|
||||||
|
use.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace all typeasserts on interface types with matches on their concrete
|
||||||
|
// types, if possible.
|
||||||
|
for _, use := range interfaceImplementsUses {
|
||||||
|
actualType := use.Operand(0)
|
||||||
|
if !actualType.IsACallInst().IsNil() && actualType.CalledValue() == makeInterface {
|
||||||
|
// Type assert is in the same function that creates the interface
|
||||||
|
// value. This means the underlying type is already known so match
|
||||||
|
// on that.
|
||||||
|
// This may not happen often but it is an easy optimization.
|
||||||
|
name := actualType.Operand(0).Name()
|
||||||
|
typ := p.types[name]
|
||||||
|
p.builder.SetInsertPointBefore(use)
|
||||||
|
assertedType := p.builder.CreatePtrToInt(typ.typecode, p.uintptrType, "typeassert.typecode")
|
||||||
|
commaOk := p.builder.CreateICmp(llvm.IntEQ, assertedType, actualType, "typeassert.ok")
|
||||||
|
use.ReplaceAllUsesWith(commaOk)
|
||||||
|
use.EraseFromParentAsInstruction()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
methodSet := use.Operand(1).Operand(0) // global variable
|
||||||
|
itf := p.interfaces[methodSet.Name()]
|
||||||
|
if len(itf.types) == 0 {
|
||||||
|
// There are no types implementing this interface, so this assert
|
||||||
|
// can never succeed.
|
||||||
|
// Signal this to the optimizer by branching on constant false. It
|
||||||
|
// should remove the "then" block.
|
||||||
|
use.ReplaceAllUsesWith(llvm.ConstInt(p.ctx.Int1Type(), 0, false))
|
||||||
|
use.EraseFromParentAsInstruction()
|
||||||
|
} else if len(itf.types) == 1 {
|
||||||
|
// There is only one type implementing this interface.
|
||||||
|
// Transform this interface assert into comparison against a
|
||||||
|
// constant.
|
||||||
|
p.builder.SetInsertPointBefore(use)
|
||||||
|
assertedType := p.builder.CreatePtrToInt(itf.types[0].typecode, p.uintptrType, "typeassert.typecode")
|
||||||
|
commaOk := p.builder.CreateICmp(llvm.IntEQ, assertedType, actualType, "typeassert.ok")
|
||||||
|
use.ReplaceAllUsesWith(commaOk)
|
||||||
|
use.EraseFromParentAsInstruction()
|
||||||
|
} else {
|
||||||
|
// There are multiple possible types implementing this interface.
|
||||||
|
// Create a function that does a type switch on all available types
|
||||||
|
// that implement this interface.
|
||||||
|
fn := p.getInterfaceImplementsFunc(itf)
|
||||||
|
p.builder.SetInsertPointBefore(use)
|
||||||
|
commaOk := p.builder.CreateCall(fn, []llvm.Value{actualType}, "typeassert.ok")
|
||||||
|
use.ReplaceAllUsesWith(commaOk)
|
||||||
|
use.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a slice of types sorted by frequency of use.
|
||||||
|
typeSlice := make(typeInfoSlice, 0, len(p.types))
|
||||||
|
for _, t := range p.types {
|
||||||
|
typeSlice = append(typeSlice, t)
|
||||||
|
}
|
||||||
|
sort.Sort(typeSlice)
|
||||||
|
|
||||||
|
// A type code must fit in 16 bits.
|
||||||
|
if len(typeSlice) >= 1<<16 {
|
||||||
|
panic("typecode does not fit in a uint16: too many types in this program")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign a type code for each type.
|
||||||
|
for i, t := range typeSlice {
|
||||||
|
t.num = uint64(i + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace each call to runtime.makeInterface with the constant type code.
|
||||||
|
for _, use := range makeInterfaceUses {
|
||||||
|
global := use.Operand(0)
|
||||||
|
t := p.types[global.Name()]
|
||||||
|
use.ReplaceAllUsesWith(llvm.ConstPtrToInt(t.typecode, p.uintptrType))
|
||||||
|
use.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace each type assert with an actual type comparison or (if the type
|
||||||
|
// assert is impossible) the constant false.
|
||||||
|
for _, use := range typeAssertUses {
|
||||||
|
actualType := use.Operand(0)
|
||||||
|
assertedTypeGlobal := use.Operand(1)
|
||||||
|
t := p.types[assertedTypeGlobal.Name()]
|
||||||
|
var commaOk llvm.Value
|
||||||
|
if t.countMakeInterfaces == 0 {
|
||||||
|
// impossible type assert: optimize accordingly
|
||||||
|
commaOk = llvm.ConstInt(llvm.Int1Type(), 0, false)
|
||||||
|
} else {
|
||||||
|
// regular type assert
|
||||||
|
p.builder.SetInsertPointBefore(use)
|
||||||
|
commaOk = p.builder.CreateICmp(llvm.IntEQ, llvm.ConstPtrToInt(assertedTypeGlobal, p.uintptrType), actualType, "typeassert.ok")
|
||||||
|
}
|
||||||
|
use.ReplaceAllUsesWith(commaOk)
|
||||||
|
use.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill in each helper function for type asserts on interfaces
|
||||||
|
// (interface-to-interface matches).
|
||||||
|
for _, itf := range p.interfaces {
|
||||||
|
if !itf.assertFunc.IsNil() {
|
||||||
|
p.createInterfaceImplementsFunc(itf)
|
||||||
|
}
|
||||||
|
for signature := range itf.methodFuncs {
|
||||||
|
p.createInterfaceMethodFunc(itf, signature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace all ptrtoint typecode placeholders with their final type code
|
||||||
|
// numbers.
|
||||||
|
for _, typ := range p.types {
|
||||||
|
for _, use := range getUses(typ.typecode) {
|
||||||
|
if use.IsConstant() && use.Opcode() == llvm.PtrToInt {
|
||||||
|
use.ReplaceAllUsesWith(llvm.ConstInt(p.uintptrType, typ.num, false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove method sets of types. Unnecessary, but cleans up the IR for
|
||||||
|
// inspection.
|
||||||
|
for _, typ := range p.types {
|
||||||
|
if !typ.methodSet.IsNil() {
|
||||||
|
typ.methodSet.EraseFromParentAsGlobal()
|
||||||
|
typ.methodSet = llvm.Value{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// addType retrieves Go type information based on a i16 global variable.
|
||||||
|
// Only the name of the i16 is relevant, the object itself is const-propagated
|
||||||
|
// and discared afterwards.
|
||||||
|
func (p *lowerInterfacesPass) addType(typecode llvm.Value) *typeInfo {
|
||||||
|
name := typecode.Name()
|
||||||
|
t := &typeInfo{
|
||||||
|
name: name,
|
||||||
|
typecode: typecode,
|
||||||
|
}
|
||||||
|
p.types[name] = t
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// addTypeMethods reads the method set of the given type info struct. It
|
||||||
|
// retrieves the signatures and the references to the method functions
|
||||||
|
// themselves for later type<->interface matching.
|
||||||
|
func (p *lowerInterfacesPass) addTypeMethods(t *typeInfo, methodSet llvm.Value) {
|
||||||
|
if !t.methodSet.IsNil() || methodSet.IsNull() {
|
||||||
|
// no methods or methods already read
|
||||||
|
return
|
||||||
|
}
|
||||||
|
methodSet = methodSet.Operand(0) // get global from GEP
|
||||||
|
|
||||||
|
// This type has methods, collect all methods of this type.
|
||||||
|
t.methodSet = methodSet
|
||||||
|
set := methodSet.Initializer() // get value from global
|
||||||
|
for i := 0; i < set.Type().ArrayLength(); i++ {
|
||||||
|
methodData := llvm.ConstExtractValue(set, []uint32{uint32(i)})
|
||||||
|
signatureName := llvm.ConstExtractValue(methodData, []uint32{0}).Name()
|
||||||
|
function := llvm.ConstExtractValue(methodData, []uint32{1}).Operand(0)
|
||||||
|
signature := p.getSignature(signatureName)
|
||||||
|
method := &methodInfo{
|
||||||
|
function: function,
|
||||||
|
signatureInfo: signature,
|
||||||
|
}
|
||||||
|
signature.methods = append(signature.methods, method)
|
||||||
|
t.methods = append(t.methods, method)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// addInterface reads information about an interface, which is the
|
||||||
|
// fully-qualified name and the signatures of all methods it has.
|
||||||
|
func (p *lowerInterfacesPass) addInterface(methodSet llvm.Value) {
|
||||||
|
name := methodSet.Name()
|
||||||
|
t := &interfaceInfo{
|
||||||
|
name: name,
|
||||||
|
}
|
||||||
|
p.interfaces[name] = t
|
||||||
|
methodSet = methodSet.Initializer() // get global value from getelementptr
|
||||||
|
for i := 0; i < methodSet.Type().ArrayLength(); i++ {
|
||||||
|
signatureName := llvm.ConstExtractValue(methodSet, []uint32{uint32(i)}).Name()
|
||||||
|
signature := p.getSignature(signatureName)
|
||||||
|
signature.interfaces = append(signature.interfaces, t)
|
||||||
|
t.signatures = append(t.signatures, signature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSignature returns a new *signatureInfo, creating it if it doesn't already
|
||||||
|
// exist.
|
||||||
|
func (p *lowerInterfacesPass) getSignature(name string) *signatureInfo {
|
||||||
|
if _, ok := p.signatures[name]; !ok {
|
||||||
|
p.signatures[name] = &signatureInfo{
|
||||||
|
name: name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p.signatures[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
// replaceInvokeWithCall replaces a runtime.interfaceMethod + bitcast with a
|
||||||
|
// concrete method. This can be done when only one type implements the
|
||||||
|
// interface.
|
||||||
|
func (p *lowerInterfacesPass) replaceInvokeWithCall(use llvm.Value, typ *typeInfo, signature *signatureInfo) {
|
||||||
|
bitcasts := getUses(use)
|
||||||
|
if len(bitcasts) != 1 || bitcasts[0].IsABitCastInst().IsNil() {
|
||||||
|
panic("expected exactly one bitcast use of runtime.interfaceMethod")
|
||||||
|
}
|
||||||
|
bitcast := bitcasts[0]
|
||||||
|
function := typ.getMethod(signature).function
|
||||||
|
if bitcast.Type() != function.Type() {
|
||||||
|
p.builder.SetInsertPointBefore(use)
|
||||||
|
function = p.builder.CreateBitCast(function, bitcast.Type(), "")
|
||||||
|
}
|
||||||
|
bitcast.ReplaceAllUsesWith(function)
|
||||||
|
bitcast.EraseFromParentAsInstruction()
|
||||||
|
use.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
|
||||||
|
// getInterfaceImplementsFunc returns a function that checks whether a given
|
||||||
|
// interface type implements a given interface, by checking all possible types
|
||||||
|
// that implement this interface.
|
||||||
|
func (p *lowerInterfacesPass) getInterfaceImplementsFunc(itf *interfaceInfo) llvm.Value {
|
||||||
|
if !itf.assertFunc.IsNil() {
|
||||||
|
return itf.assertFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the function and function signature.
|
||||||
|
// TODO: debug info
|
||||||
|
fnName := itf.id() + "$typeassert"
|
||||||
|
fnType := llvm.FunctionType(p.ctx.Int1Type(), []llvm.Type{p.uintptrType}, false)
|
||||||
|
itf.assertFunc = llvm.AddFunction(p.mod, fnName, fnType)
|
||||||
|
itf.assertFunc.Param(0).SetName("actualType")
|
||||||
|
|
||||||
|
// Type asserts will be made for each type, so increment the counter for
|
||||||
|
// those.
|
||||||
|
for _, typ := range itf.types {
|
||||||
|
typ.countTypeAsserts++
|
||||||
|
}
|
||||||
|
|
||||||
|
return itf.assertFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// createInterfaceImplementsFunc finishes the work of
|
||||||
|
// getInterfaceImplementsFunc, because it needs to run after types have a type
|
||||||
|
// code assigned.
|
||||||
|
//
|
||||||
|
// The type match is implemented using a big type switch over all possible
|
||||||
|
// types.
|
||||||
|
func (p *lowerInterfacesPass) createInterfaceImplementsFunc(itf *interfaceInfo) {
|
||||||
|
fn := itf.assertFunc
|
||||||
|
fn.SetLinkage(llvm.InternalLinkage)
|
||||||
|
fn.SetUnnamedAddr(true)
|
||||||
|
|
||||||
|
// TODO: debug info
|
||||||
|
|
||||||
|
// Create all used basic blocks.
|
||||||
|
entry := llvm.AddBasicBlock(fn, "entry")
|
||||||
|
thenBlock := llvm.AddBasicBlock(fn, "then")
|
||||||
|
elseBlock := llvm.AddBasicBlock(fn, "else")
|
||||||
|
|
||||||
|
// Add all possible types as cases.
|
||||||
|
p.builder.SetInsertPointAtEnd(entry)
|
||||||
|
actualType := fn.Param(0)
|
||||||
|
sw := p.builder.CreateSwitch(actualType, elseBlock, len(itf.types))
|
||||||
|
for _, typ := range itf.types {
|
||||||
|
sw.AddCase(llvm.ConstInt(p.uintptrType, typ.num, false), thenBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill 'then' block (type assert was successful).
|
||||||
|
p.builder.SetInsertPointAtEnd(thenBlock)
|
||||||
|
p.builder.CreateRet(llvm.ConstInt(p.ctx.Int1Type(), 1, false))
|
||||||
|
|
||||||
|
// Fill 'else' block (type asserted failed).
|
||||||
|
p.builder.SetInsertPointAtEnd(elseBlock)
|
||||||
|
p.builder.CreateRet(llvm.ConstInt(p.ctx.Int1Type(), 0, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
// getInterfaceMethodFunc return a function that returns a function pointer for
|
||||||
|
// calling a method on an interface. It only declares the function,
|
||||||
|
// createInterfaceMethodFunc actually defines the function.
|
||||||
|
func (p *lowerInterfacesPass) getInterfaceMethodFunc(itf *interfaceInfo, signature *signatureInfo, returnType llvm.Type, params []llvm.Type) llvm.Value {
|
||||||
|
if fn, ok := itf.methodFuncs[signature]; ok {
|
||||||
|
// This function has already been created.
|
||||||
|
return fn
|
||||||
|
}
|
||||||
|
if itf.methodFuncs == nil {
|
||||||
|
// initialize the above map
|
||||||
|
itf.methodFuncs = make(map[*signatureInfo]llvm.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct the function name, which is of the form:
|
||||||
|
// (main.Stringer).String
|
||||||
|
fnName := "(" + itf.id() + ")." + signature.methodName()
|
||||||
|
fnType := llvm.FunctionType(returnType, params, false)
|
||||||
|
fn := llvm.AddFunction(p.mod, fnName, fnType)
|
||||||
|
fn.LastParam().SetName("actualType")
|
||||||
|
itf.methodFuncs[signature] = fn
|
||||||
|
return fn
|
||||||
|
}
|
||||||
|
|
||||||
|
// createInterfaceMethodFunc finishes the work of getInterfaceMethodFunc,
|
||||||
|
// because it needs to run after type codes have been assigned to concrete
|
||||||
|
// types.
|
||||||
|
//
|
||||||
|
// Matching the actual type is implemented using a big type switch over all
|
||||||
|
// possible types.
|
||||||
|
func (p *lowerInterfacesPass) createInterfaceMethodFunc(itf *interfaceInfo, signature *signatureInfo) {
|
||||||
|
fn := itf.methodFuncs[signature]
|
||||||
|
fn.SetLinkage(llvm.InternalLinkage)
|
||||||
|
fn.SetUnnamedAddr(true)
|
||||||
|
|
||||||
|
// TODO: debug info
|
||||||
|
|
||||||
|
// Create entry block.
|
||||||
|
entry := llvm.AddBasicBlock(fn, "entry")
|
||||||
|
|
||||||
|
// Create default block and make it unreachable (which it is, because all
|
||||||
|
// possible types are checked).
|
||||||
|
defaultBlock := llvm.AddBasicBlock(fn, "default")
|
||||||
|
p.builder.SetInsertPointAtEnd(defaultBlock)
|
||||||
|
p.builder.CreateUnreachable()
|
||||||
|
|
||||||
|
// Create type switch in entry block.
|
||||||
|
p.builder.SetInsertPointAtEnd(entry)
|
||||||
|
actualType := fn.LastParam()
|
||||||
|
sw := p.builder.CreateSwitch(actualType, defaultBlock, len(itf.types))
|
||||||
|
|
||||||
|
// Collect the params that will be passed to the functions to call.
|
||||||
|
// These params exclude the receiver (which may actually consist of multiple
|
||||||
|
// parts).
|
||||||
|
params := make([]llvm.Value, fn.ParamsCount()-2)
|
||||||
|
for i := range params {
|
||||||
|
params[i] = fn.Param(i + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define all possible functions that can be called.
|
||||||
|
for _, typ := range itf.types {
|
||||||
|
bb := llvm.AddBasicBlock(fn, typ.id())
|
||||||
|
sw.AddCase(llvm.ConstInt(p.uintptrType, typ.num, false), bb)
|
||||||
|
|
||||||
|
// The function we will redirect to when the interface has this type.
|
||||||
|
function := typ.getMethod(signature).function
|
||||||
|
|
||||||
|
p.builder.SetInsertPointAtEnd(bb)
|
||||||
|
receiver := fn.FirstParam()
|
||||||
|
if receiver.Type() != function.FirstParam().Type() {
|
||||||
|
// When the receiver is a pointer, it is not wrapped. This means the
|
||||||
|
// i8* has to be cast to the correct pointer type of the target
|
||||||
|
// function.
|
||||||
|
receiver = p.builder.CreateBitCast(receiver, function.FirstParam().Type(), "")
|
||||||
|
}
|
||||||
|
retval := p.builder.CreateCall(function, append([]llvm.Value{receiver}, params...), "")
|
||||||
|
if retval.Type().TypeKind() == llvm.VoidTypeKind {
|
||||||
|
p.builder.CreateRetVoid()
|
||||||
|
} else {
|
||||||
|
p.builder.CreateRet(retval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,10 @@
|
||||||
package compiler
|
package compiler
|
||||||
|
|
||||||
|
// This file transforms interface-related instructions (*ssa.MakeInterface,
|
||||||
|
// *ssa.TypeAssert, calls on interface types) to an intermediate IR form, to be
|
||||||
|
// lowered to the final form by the interface lowering pass. See
|
||||||
|
// interface-lowering.go for more details.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"go/types"
|
"go/types"
|
||||||
|
@ -32,10 +37,10 @@ func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, global str
|
||||||
// Allocate on the heap and put a pointer in the interface.
|
// Allocate on the heap and put a pointer in the interface.
|
||||||
// TODO: escape analysis.
|
// TODO: escape analysis.
|
||||||
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
||||||
alloc := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "")
|
alloc := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "makeinterface.alloc")
|
||||||
itfValueCast := c.builder.CreateBitCast(alloc, llvm.PointerType(val.Type(), 0), "")
|
itfValueCast := c.builder.CreateBitCast(alloc, llvm.PointerType(val.Type(), 0), "makeinterface.cast.value")
|
||||||
c.builder.CreateStore(val, itfValueCast)
|
c.builder.CreateStore(val, itfValueCast)
|
||||||
itfValue = c.builder.CreateBitCast(itfValueCast, c.i8ptrType, "")
|
itfValue = c.builder.CreateBitCast(itfValueCast, c.i8ptrType, "makeinterface.cast.i8ptr")
|
||||||
}
|
}
|
||||||
} else if size == 0 {
|
} else if size == 0 {
|
||||||
itfValue = llvm.ConstPointerNull(c.i8ptrType)
|
itfValue = llvm.ConstPointerNull(c.i8ptrType)
|
||||||
|
@ -43,38 +48,136 @@ func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, global str
|
||||||
// Directly place the value in the interface.
|
// Directly place the value in the interface.
|
||||||
switch val.Type().TypeKind() {
|
switch val.Type().TypeKind() {
|
||||||
case llvm.IntegerTypeKind:
|
case llvm.IntegerTypeKind:
|
||||||
itfValue = c.builder.CreateIntToPtr(val, c.i8ptrType, "")
|
itfValue = c.builder.CreateIntToPtr(val, c.i8ptrType, "makeinterface.cast.int")
|
||||||
case llvm.PointerTypeKind:
|
case llvm.PointerTypeKind:
|
||||||
itfValue = c.builder.CreateBitCast(val, c.i8ptrType, "")
|
itfValue = c.builder.CreateBitCast(val, c.i8ptrType, "makeinterface.cast.ptr")
|
||||||
case llvm.StructTypeKind:
|
case llvm.StructTypeKind:
|
||||||
// A bitcast would be useful here, but bitcast doesn't allow
|
// A bitcast would be useful here, but bitcast doesn't allow
|
||||||
// aggregate types. So we'll bitcast it using an alloca.
|
// aggregate types. So we'll bitcast it using an alloca.
|
||||||
// Hopefully this will get optimized away.
|
// Hopefully this will get optimized away.
|
||||||
mem := c.builder.CreateAlloca(c.i8ptrType, "")
|
mem := c.builder.CreateAlloca(c.i8ptrType, "makeinterface.cast.struct")
|
||||||
memStructPtr := c.builder.CreateBitCast(mem, llvm.PointerType(val.Type(), 0), "")
|
memStructPtr := c.builder.CreateBitCast(mem, llvm.PointerType(val.Type(), 0), "makeinterface.cast.struct.cast")
|
||||||
c.builder.CreateStore(val, memStructPtr)
|
c.builder.CreateStore(val, memStructPtr)
|
||||||
itfValue = c.builder.CreateLoad(mem, "")
|
itfValue = c.builder.CreateLoad(mem, "makeinterface.cast.load")
|
||||||
default:
|
default:
|
||||||
return llvm.Value{}, errors.New("todo: makeinterface: cast small type to i8*")
|
return llvm.Value{}, errors.New("todo: makeinterface: cast small type to i8*")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
itfTypeNum, _ := c.ir.TypeNum(typ)
|
itfTypeCodeGlobal := c.getTypeCode(typ)
|
||||||
if itfTypeNum >= 1<<16 {
|
itfMethodSetGlobal, err := c.getTypeMethodSet(typ)
|
||||||
return llvm.Value{}, errors.New("interface typecodes do not fit in a 16-bit integer")
|
if err != nil {
|
||||||
|
return llvm.Value{}, nil
|
||||||
}
|
}
|
||||||
itf := llvm.ConstNamedStruct(c.mod.GetTypeByName("runtime._interface"), []llvm.Value{llvm.ConstInt(c.ctx.Int16Type(), uint64(itfTypeNum), false), llvm.Undef(c.i8ptrType)})
|
itfTypeCode := c.createRuntimeCall("makeInterface", []llvm.Value{itfTypeCodeGlobal, itfMethodSetGlobal}, "makeinterface.typecode")
|
||||||
|
itf := llvm.Undef(c.mod.GetTypeByName("runtime._interface"))
|
||||||
|
itf = c.builder.CreateInsertValue(itf, itfTypeCode, 0, "")
|
||||||
itf = c.builder.CreateInsertValue(itf, itfValue, 1, "")
|
itf = c.builder.CreateInsertValue(itf, itfValue, 1, "")
|
||||||
return itf, nil
|
return itf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getTypeCode returns a reference to a type code.
|
||||||
|
// It returns a pointer to an external global which should be replaced with the
|
||||||
|
// real type in the interface lowering pass.
|
||||||
|
func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
|
||||||
|
global := c.mod.NamedGlobal(typ.String() + "$type")
|
||||||
|
if global.IsNil() {
|
||||||
|
global = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), typ.String()+"$type")
|
||||||
|
global.SetGlobalConstant(true)
|
||||||
|
}
|
||||||
|
return global
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTypeMethodSet returns a reference (GEP) to a global method set. This
|
||||||
|
// method set should be unreferenced after the interface lowering pass.
|
||||||
|
func (c *Compiler) getTypeMethodSet(typ types.Type) (llvm.Value, error) {
|
||||||
|
global := c.mod.NamedGlobal(typ.String() + "$methodset")
|
||||||
|
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||||
|
if !global.IsNil() {
|
||||||
|
// the method set already exists
|
||||||
|
return llvm.ConstGEP(global, []llvm.Value{zero, zero}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ms := c.ir.Program.MethodSets.MethodSet(typ)
|
||||||
|
if ms.Len() == 0 {
|
||||||
|
// no methods, so can leave that one out
|
||||||
|
return llvm.ConstPointerNull(llvm.PointerType(c.mod.GetTypeByName("runtime.interfaceMethodInfo"), 0)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
methods := make([]llvm.Value, ms.Len())
|
||||||
|
interfaceMethodInfoType := c.mod.GetTypeByName("runtime.interfaceMethodInfo")
|
||||||
|
for i := 0; i < ms.Len(); i++ {
|
||||||
|
method := ms.At(i)
|
||||||
|
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
|
||||||
|
f := c.ir.GetFunction(c.ir.Program.MethodValue(method))
|
||||||
|
if f.LLVMFn.IsNil() {
|
||||||
|
// compiler error, so panic
|
||||||
|
panic("cannot find function: " + f.LinkName())
|
||||||
|
}
|
||||||
|
fn, err := c.getInterfaceInvokeWrapper(f)
|
||||||
|
if err != nil {
|
||||||
|
return llvm.Value{}, err
|
||||||
|
}
|
||||||
|
methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{
|
||||||
|
signatureGlobal,
|
||||||
|
llvm.ConstBitCast(fn, c.i8ptrType),
|
||||||
|
})
|
||||||
|
methods[i] = methodInfo
|
||||||
|
}
|
||||||
|
arrayType := llvm.ArrayType(interfaceMethodInfoType, len(methods))
|
||||||
|
value := llvm.ConstArray(interfaceMethodInfoType, methods)
|
||||||
|
global = llvm.AddGlobal(c.mod, arrayType, typ.String()+"$methodset")
|
||||||
|
global.SetInitializer(value)
|
||||||
|
global.SetGlobalConstant(true)
|
||||||
|
global.SetLinkage(llvm.PrivateLinkage)
|
||||||
|
return llvm.ConstGEP(global, []llvm.Value{zero, zero}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getInterfaceMethodSet returns a global variable with the method set of the
|
||||||
|
// given named interface type. This method set is used by the interface lowering
|
||||||
|
// pass.
|
||||||
|
func (c *Compiler) getInterfaceMethodSet(typ *types.Named) llvm.Value {
|
||||||
|
global := c.mod.NamedGlobal(typ.String() + "$interface")
|
||||||
|
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||||
|
if !global.IsNil() {
|
||||||
|
// method set already exist, return it
|
||||||
|
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every method is a *i16 reference indicating the signature of this method.
|
||||||
|
methods := make([]llvm.Value, typ.Underlying().(*types.Interface).NumMethods())
|
||||||
|
for i := range methods {
|
||||||
|
method := typ.Underlying().(*types.Interface).Method(i)
|
||||||
|
methods[i] = c.getMethodSignature(method)
|
||||||
|
}
|
||||||
|
|
||||||
|
value := llvm.ConstArray(methods[0].Type(), methods)
|
||||||
|
global = llvm.AddGlobal(c.mod, value.Type(), typ.String()+"$interface")
|
||||||
|
global.SetInitializer(value)
|
||||||
|
global.SetGlobalConstant(true)
|
||||||
|
global.SetLinkage(llvm.PrivateLinkage)
|
||||||
|
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
|
||||||
|
}
|
||||||
|
|
||||||
|
// getMethodSignature returns a global variable which is a reference to an
|
||||||
|
// external *i16 indicating the indicating the signature of this method. It is
|
||||||
|
// used during the interface lowering pass.
|
||||||
|
func (c *Compiler) getMethodSignature(method *types.Func) llvm.Value {
|
||||||
|
signature := ir.MethodSignature(method)
|
||||||
|
signatureGlobal := c.mod.NamedGlobal("func " + signature)
|
||||||
|
if signatureGlobal.IsNil() {
|
||||||
|
signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), "func "+signature)
|
||||||
|
signatureGlobal.SetGlobalConstant(true)
|
||||||
|
}
|
||||||
|
return signatureGlobal
|
||||||
|
}
|
||||||
|
|
||||||
// parseTypeAssert will emit the code for a typeassert, used in if statements
|
// parseTypeAssert will emit the code for a typeassert, used in if statements
|
||||||
// and in switch statements (Go SSA does not have type switches, only if/else
|
// and in type switches (Go SSA does not have type switches, only if/else
|
||||||
// chains). Note that even though the Go SSA does not contain type switches,
|
// chains). Note that even though the Go SSA does not contain type switches,
|
||||||
// LLVM will recognize the pattern and make it a real switch in many cases.
|
// LLVM will recognize the pattern and make it a real switch in many cases.
|
||||||
//
|
//
|
||||||
// Type asserts on concrete types are trivial: just compare type numbers. Type
|
// Type asserts on concrete types are trivial: just compare type numbers. Type
|
||||||
// asserts on interfaces are more difficult to implement and so are delegated to
|
// asserts on interfaces are more difficult, see the comments in the function.
|
||||||
// a runtime library function.
|
|
||||||
func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) (llvm.Value, error) {
|
func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) (llvm.Value, error) {
|
||||||
itf, err := c.parseExpr(frame, expr.X)
|
itf, err := c.parseExpr(frame, expr.X)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -91,38 +194,24 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) (llvm.Val
|
||||||
|
|
||||||
actualTypeNum := c.builder.CreateExtractValue(itf, 0, "interface.type")
|
actualTypeNum := c.builder.CreateExtractValue(itf, 0, "interface.type")
|
||||||
commaOk := llvm.Value{}
|
commaOk := llvm.Value{}
|
||||||
if itf, ok := expr.AssertedType.Underlying().(*types.Interface); ok {
|
if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok {
|
||||||
// Type assert on interface type.
|
// Type assert on interface type.
|
||||||
// This is slightly non-trivial: at runtime the list of methods
|
// This pseudo call will be lowered in the interface lowering pass to a
|
||||||
// needs to be checked to see whether it implements the interface.
|
// real call which checks whether the provided typecode is any of the
|
||||||
// At the same time, the interface value itself is unchanged.
|
// concrete types that implements this interface.
|
||||||
itfTypeNum := c.ir.InterfaceNum(itf)
|
// This is very different from how interface asserts are implemented in
|
||||||
itfTypeNumValue := llvm.ConstInt(c.ctx.Int16Type(), uint64(itfTypeNum), false)
|
// the main Go compiler, where the runtime checks whether the type
|
||||||
commaOk = c.createRuntimeCall("interfaceImplements", []llvm.Value{actualTypeNum, itfTypeNumValue}, "")
|
// implements each method of the interface. See:
|
||||||
|
// https://research.swtch.com/interfaces
|
||||||
|
methodSet := c.getInterfaceMethodSet(expr.AssertedType.(*types.Named))
|
||||||
|
commaOk = c.createRuntimeCall("interfaceImplements", []llvm.Value{actualTypeNum, methodSet}, "")
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Type assert on concrete type.
|
// Type assert on concrete type.
|
||||||
// This is easy: just compare the type number.
|
// Call runtime.typeAssert, which will be lowered to a simple icmp or
|
||||||
assertedTypeNum, typeExists := c.ir.TypeNum(expr.AssertedType)
|
// const false in the interface lowering pass.
|
||||||
if !typeExists {
|
assertedTypeCodeGlobal := c.getTypeCode(expr.AssertedType)
|
||||||
// Static analysis has determined this type assert will never apply.
|
commaOk = c.createRuntimeCall("typeAssert", []llvm.Value{actualTypeNum, assertedTypeCodeGlobal}, "typecode")
|
||||||
// Using undef here so that LLVM knows we'll never get here and
|
|
||||||
// can optimize accordingly.
|
|
||||||
undef := llvm.Undef(assertedType)
|
|
||||||
commaOk := llvm.ConstInt(c.ctx.Int1Type(), 0, false)
|
|
||||||
if expr.CommaOk {
|
|
||||||
return c.ctx.ConstStruct([]llvm.Value{undef, commaOk}, false), nil
|
|
||||||
} else {
|
|
||||||
c.createRuntimeCall("interfaceTypeAssert", []llvm.Value{commaOk}, "")
|
|
||||||
return undef, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if assertedTypeNum >= 1<<16 {
|
|
||||||
return llvm.Value{}, errors.New("interface typecodes do not fit in a 16-bit integer")
|
|
||||||
}
|
|
||||||
|
|
||||||
assertedTypeNumValue := llvm.ConstInt(c.ctx.Int16Type(), uint64(assertedTypeNum), false)
|
|
||||||
commaOk = c.builder.CreateICmp(llvm.IntEQ, assertedTypeNumValue, actualTypeNum, "")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add 2 new basic blocks (that should get optimized away): one for the
|
// Add 2 new basic blocks (that should get optimized away): one for the
|
||||||
|
@ -171,16 +260,14 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) (llvm.Val
|
||||||
valueOk = c.builder.CreatePtrToInt(valuePtr, assertedType, "typeassert.value.ok")
|
valueOk = c.builder.CreatePtrToInt(valuePtr, assertedType, "typeassert.value.ok")
|
||||||
case llvm.PointerTypeKind:
|
case llvm.PointerTypeKind:
|
||||||
valueOk = c.builder.CreateBitCast(valuePtr, assertedType, "typeassert.value.ok")
|
valueOk = c.builder.CreateBitCast(valuePtr, assertedType, "typeassert.value.ok")
|
||||||
case llvm.StructTypeKind:
|
default: // struct, float, etc.
|
||||||
// A bitcast would be useful here, but bitcast doesn't allow
|
// A bitcast would be useful here, but bitcast doesn't allow
|
||||||
// aggregate types. So we'll bitcast it using an alloca.
|
// aggregate types. So we'll bitcast it using an alloca.
|
||||||
// Hopefully this will get optimized away.
|
// Hopefully this will get optimized away.
|
||||||
mem := c.builder.CreateAlloca(c.i8ptrType, "")
|
mem := c.builder.CreateAlloca(c.i8ptrType, "")
|
||||||
c.builder.CreateStore(valuePtr, mem)
|
c.builder.CreateStore(valuePtr, mem)
|
||||||
memStructPtr := c.builder.CreateBitCast(mem, llvm.PointerType(assertedType, 0), "")
|
memCast := c.builder.CreateBitCast(mem, llvm.PointerType(assertedType, 0), "")
|
||||||
valueOk = c.builder.CreateLoad(memStructPtr, "typeassert.value.ok")
|
valueOk = c.builder.CreateLoad(memCast, "typeassert.value.ok")
|
||||||
default:
|
|
||||||
return llvm.Value{}, errors.New("todo: typeassert: bitcast small types")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -231,7 +318,8 @@ func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Valu
|
||||||
typecode := c.builder.CreateExtractValue(itf, 0, "invoke.typecode")
|
typecode := c.builder.CreateExtractValue(itf, 0, "invoke.typecode")
|
||||||
values := []llvm.Value{
|
values := []llvm.Value{
|
||||||
typecode,
|
typecode,
|
||||||
llvm.ConstInt(c.ctx.Int16Type(), uint64(c.ir.MethodNum(instr.Method)), false),
|
c.getInterfaceMethodSet(instr.Value.Type().(*types.Named)),
|
||||||
|
c.getMethodSignature(instr.Method),
|
||||||
}
|
}
|
||||||
fn := c.createRuntimeCall("interfaceMethod", values, "invoke.func")
|
fn := c.createRuntimeCall("interfaceMethod", values, "invoke.func")
|
||||||
fnCast := c.builder.CreateBitCast(fn, llvmFnType, "invoke.func.cast")
|
fnCast := c.builder.CreateBitCast(fn, llvmFnType, "invoke.func.cast")
|
||||||
|
@ -255,142 +343,40 @@ func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Valu
|
||||||
return fnCast, args, nil
|
return fnCast, args, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize runtime type information, for interfaces.
|
// interfaceInvokeWrapper keeps some state between getInterfaceInvokeWrapper and
|
||||||
// See src/runtime/interface.go for more details.
|
// createInterfaceInvokeWrapper. The former is called during IR construction
|
||||||
func (c *Compiler) createInterfaceRTTI() error {
|
// itself and the latter is called when finishing up the IR.
|
||||||
dynamicTypes := c.ir.AllDynamicTypes()
|
type interfaceInvokeWrapper struct {
|
||||||
numDynamicTypes := 0
|
fn *ir.Function
|
||||||
for _, meta := range dynamicTypes {
|
wrapper llvm.Value
|
||||||
numDynamicTypes += len(meta.Methods)
|
receiverType llvm.Type
|
||||||
}
|
|
||||||
ranges := make([]llvm.Value, 0, len(dynamicTypes))
|
|
||||||
funcPointers := make([]llvm.Value, 0, numDynamicTypes)
|
|
||||||
signatures := make([]llvm.Value, 0, numDynamicTypes)
|
|
||||||
startIndex := 0
|
|
||||||
rangeType := c.mod.GetTypeByName("runtime.methodSetRange")
|
|
||||||
for _, meta := range dynamicTypes {
|
|
||||||
rangeValues := []llvm.Value{
|
|
||||||
llvm.ConstInt(c.ctx.Int16Type(), uint64(startIndex), false),
|
|
||||||
llvm.ConstInt(c.ctx.Int16Type(), uint64(len(meta.Methods)), false),
|
|
||||||
}
|
|
||||||
rangeValue := llvm.ConstNamedStruct(rangeType, rangeValues)
|
|
||||||
ranges = append(ranges, rangeValue)
|
|
||||||
methods := make([]*types.Selection, 0, len(meta.Methods))
|
|
||||||
for _, method := range meta.Methods {
|
|
||||||
methods = append(methods, method)
|
|
||||||
}
|
|
||||||
c.ir.SortMethods(methods)
|
|
||||||
for _, method := range methods {
|
|
||||||
f := c.ir.GetFunction(c.ir.Program.MethodValue(method))
|
|
||||||
if f.LLVMFn.IsNil() {
|
|
||||||
return errors.New("cannot find function: " + f.LinkName())
|
|
||||||
}
|
|
||||||
fn, err := c.wrapInterfaceInvoke(f)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fnPtr := llvm.ConstBitCast(fn, c.i8ptrType)
|
|
||||||
funcPointers = append(funcPointers, fnPtr)
|
|
||||||
signatureNum := c.ir.MethodNum(method.Obj().(*types.Func))
|
|
||||||
signature := llvm.ConstInt(c.ctx.Int16Type(), uint64(signatureNum), false)
|
|
||||||
signatures = append(signatures, signature)
|
|
||||||
}
|
|
||||||
startIndex += len(meta.Methods)
|
|
||||||
}
|
|
||||||
|
|
||||||
interfaceTypes := c.ir.AllInterfaces()
|
|
||||||
interfaceIndex := make([]llvm.Value, len(interfaceTypes))
|
|
||||||
interfaceLengths := make([]llvm.Value, len(interfaceTypes))
|
|
||||||
interfaceMethods := make([]llvm.Value, 0)
|
|
||||||
for i, itfType := range interfaceTypes {
|
|
||||||
if itfType.Type.NumMethods() > 0xff {
|
|
||||||
return errors.New("too many methods for interface " + itfType.Type.String())
|
|
||||||
}
|
|
||||||
interfaceIndex[i] = llvm.ConstInt(c.ctx.Int16Type(), uint64(i), false)
|
|
||||||
interfaceLengths[i] = llvm.ConstInt(c.ctx.Int8Type(), uint64(itfType.Type.NumMethods()), false)
|
|
||||||
funcs := make([]*types.Func, itfType.Type.NumMethods())
|
|
||||||
for i := range funcs {
|
|
||||||
funcs[i] = itfType.Type.Method(i)
|
|
||||||
}
|
|
||||||
c.ir.SortFuncs(funcs)
|
|
||||||
for _, f := range funcs {
|
|
||||||
id := llvm.ConstInt(c.ctx.Int16Type(), uint64(c.ir.MethodNum(f)), false)
|
|
||||||
interfaceMethods = append(interfaceMethods, id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(ranges) >= 1<<16 {
|
|
||||||
return errors.New("method call numbers do not fit in a 16-bit integer")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace the pre-created arrays with the generated arrays.
|
|
||||||
rangeArray := llvm.ConstArray(rangeType, ranges)
|
|
||||||
rangeArrayNewGlobal := llvm.AddGlobal(c.mod, rangeArray.Type(), "runtime.methodSetRanges.tmp")
|
|
||||||
rangeArrayNewGlobal.SetInitializer(rangeArray)
|
|
||||||
rangeArrayNewGlobal.SetLinkage(llvm.InternalLinkage)
|
|
||||||
rangeArrayOldGlobal := c.mod.NamedGlobal("runtime.methodSetRanges")
|
|
||||||
rangeArrayOldGlobal.ReplaceAllUsesWith(llvm.ConstBitCast(rangeArrayNewGlobal, rangeArrayOldGlobal.Type()))
|
|
||||||
rangeArrayOldGlobal.EraseFromParentAsGlobal()
|
|
||||||
rangeArrayNewGlobal.SetName("runtime.methodSetRanges")
|
|
||||||
funcArray := llvm.ConstArray(c.i8ptrType, funcPointers)
|
|
||||||
funcArrayNewGlobal := llvm.AddGlobal(c.mod, funcArray.Type(), "runtime.methodSetFunctions.tmp")
|
|
||||||
funcArrayNewGlobal.SetInitializer(funcArray)
|
|
||||||
funcArrayNewGlobal.SetLinkage(llvm.InternalLinkage)
|
|
||||||
funcArrayOldGlobal := c.mod.NamedGlobal("runtime.methodSetFunctions")
|
|
||||||
funcArrayOldGlobal.ReplaceAllUsesWith(llvm.ConstBitCast(funcArrayNewGlobal, funcArrayOldGlobal.Type()))
|
|
||||||
funcArrayOldGlobal.EraseFromParentAsGlobal()
|
|
||||||
funcArrayNewGlobal.SetName("runtime.methodSetFunctions")
|
|
||||||
signatureArray := llvm.ConstArray(c.ctx.Int16Type(), signatures)
|
|
||||||
signatureArrayNewGlobal := llvm.AddGlobal(c.mod, signatureArray.Type(), "runtime.methodSetSignatures.tmp")
|
|
||||||
signatureArrayNewGlobal.SetInitializer(signatureArray)
|
|
||||||
signatureArrayNewGlobal.SetLinkage(llvm.InternalLinkage)
|
|
||||||
signatureArrayOldGlobal := c.mod.NamedGlobal("runtime.methodSetSignatures")
|
|
||||||
signatureArrayOldGlobal.ReplaceAllUsesWith(llvm.ConstBitCast(signatureArrayNewGlobal, signatureArrayOldGlobal.Type()))
|
|
||||||
signatureArrayOldGlobal.EraseFromParentAsGlobal()
|
|
||||||
signatureArrayNewGlobal.SetName("runtime.methodSetSignatures")
|
|
||||||
interfaceIndexArray := llvm.ConstArray(c.ctx.Int16Type(), interfaceIndex)
|
|
||||||
interfaceIndexArrayNewGlobal := llvm.AddGlobal(c.mod, interfaceIndexArray.Type(), "runtime.interfaceIndex.tmp")
|
|
||||||
interfaceIndexArrayNewGlobal.SetInitializer(interfaceIndexArray)
|
|
||||||
interfaceIndexArrayNewGlobal.SetLinkage(llvm.InternalLinkage)
|
|
||||||
interfaceIndexArrayOldGlobal := c.mod.NamedGlobal("runtime.interfaceIndex")
|
|
||||||
interfaceIndexArrayOldGlobal.ReplaceAllUsesWith(llvm.ConstBitCast(interfaceIndexArrayNewGlobal, interfaceIndexArrayOldGlobal.Type()))
|
|
||||||
interfaceIndexArrayOldGlobal.EraseFromParentAsGlobal()
|
|
||||||
interfaceIndexArrayNewGlobal.SetName("runtime.interfaceIndex")
|
|
||||||
interfaceLengthsArray := llvm.ConstArray(c.ctx.Int8Type(), interfaceLengths)
|
|
||||||
interfaceLengthsArrayNewGlobal := llvm.AddGlobal(c.mod, interfaceLengthsArray.Type(), "runtime.interfaceLengths.tmp")
|
|
||||||
interfaceLengthsArrayNewGlobal.SetInitializer(interfaceLengthsArray)
|
|
||||||
interfaceLengthsArrayNewGlobal.SetLinkage(llvm.InternalLinkage)
|
|
||||||
interfaceLengthsArrayOldGlobal := c.mod.NamedGlobal("runtime.interfaceLengths")
|
|
||||||
interfaceLengthsArrayOldGlobal.ReplaceAllUsesWith(llvm.ConstBitCast(interfaceLengthsArrayNewGlobal, interfaceLengthsArrayOldGlobal.Type()))
|
|
||||||
interfaceLengthsArrayOldGlobal.EraseFromParentAsGlobal()
|
|
||||||
interfaceLengthsArrayNewGlobal.SetName("runtime.interfaceLengths")
|
|
||||||
interfaceMethodsArray := llvm.ConstArray(c.ctx.Int16Type(), interfaceMethods)
|
|
||||||
interfaceMethodsArrayNewGlobal := llvm.AddGlobal(c.mod, interfaceMethodsArray.Type(), "runtime.interfaceMethods.tmp")
|
|
||||||
interfaceMethodsArrayNewGlobal.SetInitializer(interfaceMethodsArray)
|
|
||||||
interfaceMethodsArrayNewGlobal.SetLinkage(llvm.InternalLinkage)
|
|
||||||
interfaceMethodsArrayOldGlobal := c.mod.NamedGlobal("runtime.interfaceMethods")
|
|
||||||
interfaceMethodsArrayOldGlobal.ReplaceAllUsesWith(llvm.ConstBitCast(interfaceMethodsArrayNewGlobal, interfaceMethodsArrayOldGlobal.Type()))
|
|
||||||
interfaceMethodsArrayOldGlobal.EraseFromParentAsGlobal()
|
|
||||||
interfaceMethodsArrayNewGlobal.SetName("runtime.interfaceMethods")
|
|
||||||
|
|
||||||
c.mod.NamedGlobal("runtime.firstTypeWithMethods").SetInitializer(llvm.ConstInt(c.ctx.Int16Type(), uint64(c.ir.FirstDynamicType()), false))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrap an interface method function pointer. The wrapper takes in a pointer to
|
// Wrap an interface method function pointer. The wrapper takes in a pointer to
|
||||||
// the underlying value, dereferences it, and calls the real method. This
|
// the underlying value, dereferences it, and calls the real method. This
|
||||||
// wrapper is only needed when the interface value actually doesn't fit in a
|
// wrapper is only needed when the interface value actually doesn't fit in a
|
||||||
// pointer and a pointer to the value must be created.
|
// pointer and a pointer to the value must be created.
|
||||||
func (c *Compiler) wrapInterfaceInvoke(f *ir.Function) (llvm.Value, error) {
|
func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) (llvm.Value, error) {
|
||||||
|
wrapperName := f.LinkName() + "$invoke"
|
||||||
|
wrapper := c.mod.NamedFunction(wrapperName)
|
||||||
|
if !wrapper.IsNil() {
|
||||||
|
// Wrapper already created. Return it directly.
|
||||||
|
return wrapper, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the expanded receiver type.
|
||||||
receiverType, err := c.getLLVMType(f.Params[0].Type())
|
receiverType, err := c.getLLVMType(f.Params[0].Type())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return llvm.Value{}, err
|
return llvm.Value{}, err
|
||||||
}
|
}
|
||||||
expandedReceiverType := c.expandFormalParamType(receiverType)
|
expandedReceiverType := c.expandFormalParamType(receiverType)
|
||||||
|
|
||||||
if c.targetData.TypeAllocSize(receiverType) <= c.targetData.TypeAllocSize(c.i8ptrType) && len(expandedReceiverType) == 1 {
|
// Does this method even need any wrapping?
|
||||||
// nothing to wrap
|
if len(expandedReceiverType) == 1 && receiverType.TypeKind() == llvm.PointerTypeKind {
|
||||||
|
// Nothing to wrap.
|
||||||
|
// Casting a function signature to a different signature and calling it
|
||||||
|
// with a receiver pointer bitcasted to *i8 (as done in calls on an
|
||||||
|
// interface) is hopefully a safe (defined) operation.
|
||||||
return f.LLVMFn, nil
|
return f.LLVMFn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -398,16 +384,30 @@ func (c *Compiler) wrapInterfaceInvoke(f *ir.Function) (llvm.Value, error) {
|
||||||
fnType := f.LLVMFn.Type().ElementType()
|
fnType := f.LLVMFn.Type().ElementType()
|
||||||
paramTypes := append([]llvm.Type{c.i8ptrType}, fnType.ParamTypes()[len(expandedReceiverType):]...)
|
paramTypes := append([]llvm.Type{c.i8ptrType}, fnType.ParamTypes()[len(expandedReceiverType):]...)
|
||||||
wrapFnType := llvm.FunctionType(fnType.ReturnType(), paramTypes, false)
|
wrapFnType := llvm.FunctionType(fnType.ReturnType(), paramTypes, false)
|
||||||
wrapper := llvm.AddFunction(c.mod, f.LinkName()+"$invoke", wrapFnType)
|
wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType)
|
||||||
|
c.interfaceInvokeWrappers = append(c.interfaceInvokeWrappers, interfaceInvokeWrapper{
|
||||||
|
fn: f,
|
||||||
|
wrapper: wrapper,
|
||||||
|
receiverType: receiverType,
|
||||||
|
})
|
||||||
|
return wrapper, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// createInterfaceInvokeWrapper finishes the work of getInterfaceInvokeWrapper,
|
||||||
|
// see that function for details.
|
||||||
|
func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) error {
|
||||||
|
wrapper := state.wrapper
|
||||||
|
fn := state.fn
|
||||||
|
receiverType := state.receiverType
|
||||||
wrapper.SetLinkage(llvm.InternalLinkage)
|
wrapper.SetLinkage(llvm.InternalLinkage)
|
||||||
wrapper.SetUnnamedAddr(true)
|
wrapper.SetUnnamedAddr(true)
|
||||||
|
|
||||||
// add debug info
|
// add debug info if needed
|
||||||
if c.Debug {
|
if c.Debug {
|
||||||
pos := c.ir.Program.Fset.Position(f.Pos())
|
pos := c.ir.Program.Fset.Position(fn.Pos())
|
||||||
difunc, err := c.attachDebugInfoRaw(f, wrapper, "$invoke", pos.Filename, pos.Line)
|
difunc, err := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return llvm.Value{}, err
|
return err
|
||||||
}
|
}
|
||||||
c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
|
c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
|
||||||
}
|
}
|
||||||
|
@ -424,7 +424,7 @@ func (c *Compiler) wrapInterfaceInvoke(f *ir.Function) (llvm.Value, error) {
|
||||||
// Load the underlying value.
|
// Load the underlying value.
|
||||||
receiverPtrType := llvm.PointerType(receiverType, 0)
|
receiverPtrType := llvm.PointerType(receiverType, 0)
|
||||||
receiverPtr = c.builder.CreateBitCast(wrapper.Param(0), receiverPtrType, "receiver.ptr")
|
receiverPtr = c.builder.CreateBitCast(wrapper.Param(0), receiverPtrType, "receiver.ptr")
|
||||||
} else if len(expandedReceiverType) != 1 {
|
} else {
|
||||||
// The value is stored in the interface, but it is of type struct which
|
// The value is stored in the interface, but it is of type struct which
|
||||||
// is expanded to multiple parameters (e.g. {i8, i8}). So we have to
|
// is expanded to multiple parameters (e.g. {i8, i8}). So we have to
|
||||||
// receive the struct as parameter, expand it, and pass it on to the
|
// receive the struct as parameter, expand it, and pass it on to the
|
||||||
|
@ -435,19 +435,17 @@ func (c *Compiler) wrapInterfaceInvoke(f *ir.Function) (llvm.Value, error) {
|
||||||
alloca := c.builder.CreateAlloca(c.i8ptrType, "receiver.alloca")
|
alloca := c.builder.CreateAlloca(c.i8ptrType, "receiver.alloca")
|
||||||
c.builder.CreateStore(wrapper.Param(0), alloca)
|
c.builder.CreateStore(wrapper.Param(0), alloca)
|
||||||
receiverPtr = c.builder.CreateBitCast(alloca, llvm.PointerType(receiverType, 0), "receiver.ptr")
|
receiverPtr = c.builder.CreateBitCast(alloca, llvm.PointerType(receiverType, 0), "receiver.ptr")
|
||||||
} else {
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
receiverValue := c.builder.CreateLoad(receiverPtr, "receiver")
|
receiverValue := c.builder.CreateLoad(receiverPtr, "receiver")
|
||||||
params := append(c.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
|
params := append(c.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
|
||||||
if fnType.ReturnType().TypeKind() == llvm.VoidTypeKind {
|
if fn.LLVMFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
|
||||||
c.builder.CreateCall(f.LLVMFn, params, "")
|
c.builder.CreateCall(fn.LLVMFn, params, "")
|
||||||
c.builder.CreateRetVoid()
|
c.builder.CreateRetVoid()
|
||||||
} else {
|
} else {
|
||||||
ret := c.builder.CreateCall(f.LLVMFn, params, "ret")
|
ret := c.builder.CreateCall(fn.LLVMFn, params, "ret")
|
||||||
c.builder.CreateRet(ret)
|
c.builder.CreateRet(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
return wrapper, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
package compiler
|
package compiler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/aykevl/go-llvm"
|
"github.com/aykevl/go-llvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Run the LLVM optimizer over the module.
|
// Run the LLVM optimizer over the module.
|
||||||
// The inliner can be disabled (if necessary) by passing 0 to the inlinerThreshold.
|
// The inliner can be disabled (if necessary) by passing 0 to the inlinerThreshold.
|
||||||
func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) {
|
func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) error {
|
||||||
builder := llvm.NewPassManagerBuilder()
|
builder := llvm.NewPassManagerBuilder()
|
||||||
defer builder.Dispose()
|
defer builder.Dispose()
|
||||||
builder.SetOptLevel(optLevel)
|
builder.SetOptLevel(optLevel)
|
||||||
|
@ -40,7 +42,13 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) {
|
||||||
c.OptimizeMaps()
|
c.OptimizeMaps()
|
||||||
c.OptimizeStringToBytes()
|
c.OptimizeStringToBytes()
|
||||||
c.OptimizeAllocs()
|
c.OptimizeAllocs()
|
||||||
c.Verify()
|
c.LowerInterfaces()
|
||||||
|
} else {
|
||||||
|
// Must be run at any optimization level.
|
||||||
|
c.LowerInterfaces()
|
||||||
|
}
|
||||||
|
if err := c.Verify(); err != nil {
|
||||||
|
return errors.New("optimizations caused a verification failure")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run module passes.
|
// Run module passes.
|
||||||
|
@ -48,6 +56,8 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) {
|
||||||
defer modPasses.Dispose()
|
defer modPasses.Dispose()
|
||||||
builder.Populate(modPasses)
|
builder.Populate(modPasses)
|
||||||
modPasses.Run(c.mod)
|
modPasses.Run(c.mod)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Eliminate created but not used maps.
|
// Eliminate created but not used maps.
|
||||||
|
@ -299,6 +309,9 @@ func (c *Compiler) hasFlag(call, param llvm.Value, kind string) bool {
|
||||||
// Return a list of values (actually, instructions) where this value is used as
|
// Return a list of values (actually, instructions) where this value is used as
|
||||||
// an operand.
|
// an operand.
|
||||||
func getUses(value llvm.Value) []llvm.Value {
|
func getUses(value llvm.Value) []llvm.Value {
|
||||||
|
if value.IsNil() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
var uses []llvm.Value
|
var uses []llvm.Value
|
||||||
use := value.FirstUse()
|
use := value.FirstUse()
|
||||||
for !use.IsNil() {
|
for !use.IsNil() {
|
||||||
|
|
|
@ -300,6 +300,8 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||||
ret = llvm.ConstInsertValue(ret, retLen, []uint32{1}) // len
|
ret = llvm.ConstInsertValue(ret, retLen, []uint32{1}) // len
|
||||||
ret = llvm.ConstInsertValue(ret, retLen, []uint32{2}) // cap
|
ret = llvm.ConstInsertValue(ret, retLen, []uint32{2}) // cap
|
||||||
fr.locals[inst] = &LocalValue{fr.Eval, ret}
|
fr.locals[inst] = &LocalValue{fr.Eval, ret}
|
||||||
|
case callee.Name() == "runtime.makeInterface":
|
||||||
|
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstPtrToInt(inst.Operand(0), fr.TargetData.IntPtrType())}
|
||||||
case strings.HasPrefix(callee.Name(), "runtime.print") || callee.Name() == "runtime._panic":
|
case strings.HasPrefix(callee.Name(), "runtime.print") || callee.Name() == "runtime._panic":
|
||||||
// all print instructions, which necessarily have side
|
// all print instructions, which necessarily have side
|
||||||
// effects but no results
|
// effects but no results
|
||||||
|
|
|
@ -73,7 +73,12 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
||||||
result.updateSeverity(sideEffectAll)
|
result.updateSeverity(sideEffectAll)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
name := child.Name()
|
||||||
if child.IsDeclaration() {
|
if child.IsDeclaration() {
|
||||||
|
if name == "runtime.makeInterface" {
|
||||||
|
// Can be interpreted so does not have side effects.
|
||||||
|
continue
|
||||||
|
}
|
||||||
// External function call. Assume only limited side effects
|
// External function call. Assume only limited side effects
|
||||||
// (no affected globals, etc.).
|
// (no affected globals, etc.).
|
||||||
if result.hasLocalSideEffects(dirtyLocals, inst) {
|
if result.hasLocalSideEffects(dirtyLocals, inst) {
|
||||||
|
|
|
@ -278,8 +278,6 @@ func (p *Program) interpret(instrs []ssa.Instruction, paramKeys []*ssa.Parameter
|
||||||
} else {
|
} else {
|
||||||
return i, errors.New("todo: init IndexAddr index: " + instr.Index.String())
|
return i, errors.New("todo: init IndexAddr index: " + instr.Index.String())
|
||||||
}
|
}
|
||||||
case *ssa.MakeInterface:
|
|
||||||
locals[instr] = &InterfaceValue{instr.X.Type(), locals[instr.X]}
|
|
||||||
case *ssa.MakeMap:
|
case *ssa.MakeMap:
|
||||||
locals[instr] = &MapValue{instr.Type().Underlying().(*types.Map), nil, nil}
|
locals[instr] = &MapValue{instr.Type().Underlying().(*types.Map), nil, nil}
|
||||||
case *ssa.MapUpdate:
|
case *ssa.MapUpdate:
|
||||||
|
@ -388,7 +386,6 @@ func canInterpret(callee *ssa.Function) bool {
|
||||||
case *ssa.Extract:
|
case *ssa.Extract:
|
||||||
case *ssa.FieldAddr:
|
case *ssa.FieldAddr:
|
||||||
case *ssa.IndexAddr:
|
case *ssa.IndexAddr:
|
||||||
case *ssa.MakeInterface:
|
|
||||||
case *ssa.MakeMap:
|
case *ssa.MakeMap:
|
||||||
case *ssa.MapUpdate:
|
case *ssa.MapUpdate:
|
||||||
case *ssa.Return:
|
case *ssa.Return:
|
||||||
|
@ -447,8 +444,6 @@ func (p *Program) getZeroValue(t types.Type) (Value, error) {
|
||||||
return &ZeroBasicValue{typ}, nil
|
return &ZeroBasicValue{typ}, nil
|
||||||
case *types.Signature:
|
case *types.Signature:
|
||||||
return &FunctionValue{typ, nil}, nil
|
return &FunctionValue{typ, nil}, nil
|
||||||
case *types.Interface:
|
|
||||||
return &InterfaceValue{typ, nil}, nil
|
|
||||||
case *types.Map:
|
case *types.Map:
|
||||||
return &MapValue{typ, nil, nil}, nil
|
return &MapValue{typ, nil, nil}, nil
|
||||||
case *types.Pointer:
|
case *types.Pointer:
|
||||||
|
@ -492,11 +487,6 @@ type FunctionValue struct {
|
||||||
Elem *ssa.Function
|
Elem *ssa.Function
|
||||||
}
|
}
|
||||||
|
|
||||||
type InterfaceValue struct {
|
|
||||||
Type types.Type
|
|
||||||
Elem Value
|
|
||||||
}
|
|
||||||
|
|
||||||
type PointerBitCastValue struct {
|
type PointerBitCastValue struct {
|
||||||
Type types.Type
|
Type types.Type
|
||||||
Elem Value
|
Elem Value
|
||||||
|
|
91
ir/ir.go
91
ir/ir.go
|
@ -19,21 +19,18 @@ import (
|
||||||
// View on all functions, types, and globals in a program, with analysis
|
// View on all functions, types, and globals in a program, with analysis
|
||||||
// results.
|
// results.
|
||||||
type Program struct {
|
type Program struct {
|
||||||
Program *ssa.Program
|
Program *ssa.Program
|
||||||
mainPkg *ssa.Package
|
mainPkg *ssa.Package
|
||||||
Functions []*Function
|
Functions []*Function
|
||||||
functionMap map[*ssa.Function]*Function
|
functionMap map[*ssa.Function]*Function
|
||||||
Globals []*Global
|
Globals []*Global
|
||||||
globalMap map[*ssa.Global]*Global
|
globalMap map[*ssa.Global]*Global
|
||||||
comments map[string]*ast.CommentGroup
|
comments map[string]*ast.CommentGroup
|
||||||
NamedTypes []*NamedType
|
NamedTypes []*NamedType
|
||||||
needsScheduler bool
|
needsScheduler bool
|
||||||
goCalls []*ssa.Go
|
goCalls []*ssa.Go
|
||||||
typesWithMethods map[string]*TypeWithMethods // see AnalyseInterfaceConversions
|
typesInInterfaces map[string]struct{} // see AnalyseInterfaceConversions
|
||||||
typesWithoutMethods map[string]int // see AnalyseInterfaceConversions
|
fpWithContext map[string]struct{} // see AnalyseFunctionPointers
|
||||||
methodSignatureNames map[string]int // see MethodNum
|
|
||||||
interfaces map[string]*Interface // see AnalyseInterfaceConversions
|
|
||||||
fpWithContext map[string]struct{} // see AnalyseFunctionPointers
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function or method.
|
// Function or method.
|
||||||
|
@ -179,13 +176,11 @@ func NewProgram(lprogram *loader.Program, mainPath string) *Program {
|
||||||
}
|
}
|
||||||
|
|
||||||
p := &Program{
|
p := &Program{
|
||||||
Program: program,
|
Program: program,
|
||||||
mainPkg: mainPkg,
|
mainPkg: mainPkg,
|
||||||
functionMap: make(map[*ssa.Function]*Function),
|
functionMap: make(map[*ssa.Function]*Function),
|
||||||
globalMap: make(map[*ssa.Global]*Global),
|
globalMap: make(map[*ssa.Global]*Global),
|
||||||
methodSignatureNames: make(map[string]int),
|
comments: comments,
|
||||||
interfaces: make(map[string]*Interface),
|
|
||||||
comments: comments,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pkg := range packageList {
|
for _, pkg := range packageList {
|
||||||
|
@ -270,18 +265,6 @@ func (p *Program) GetGlobal(ssaGlobal *ssa.Global) *Global {
|
||||||
return p.globalMap[ssaGlobal]
|
return p.globalMap[ssaGlobal]
|
||||||
}
|
}
|
||||||
|
|
||||||
// SortMethods sorts the list of methods by method ID.
|
|
||||||
func (p *Program) SortMethods(methods []*types.Selection) {
|
|
||||||
m := &methodList{methods: methods, program: p}
|
|
||||||
sort.Sort(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SortFuncs sorts the list of functions by method ID.
|
|
||||||
func (p *Program) SortFuncs(funcs []*types.Func) {
|
|
||||||
m := &funcList{funcs: funcs, program: p}
|
|
||||||
sort.Sort(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Program) MainPkg() *ssa.Package {
|
func (p *Program) MainPkg() *ssa.Package {
|
||||||
return p.mainPkg
|
return p.mainPkg
|
||||||
}
|
}
|
||||||
|
@ -442,46 +425,6 @@ func (p *Program) IsVolatile(t types.Type) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrapper type to implement sort.Interface for []*types.Selection.
|
|
||||||
type methodList struct {
|
|
||||||
methods []*types.Selection
|
|
||||||
program *Program
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *methodList) Len() int {
|
|
||||||
return len(m.methods)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *methodList) Less(i, j int) bool {
|
|
||||||
iid := m.program.MethodNum(m.methods[i].Obj().(*types.Func))
|
|
||||||
jid := m.program.MethodNum(m.methods[j].Obj().(*types.Func))
|
|
||||||
return iid < jid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *methodList) Swap(i, j int) {
|
|
||||||
m.methods[i], m.methods[j] = m.methods[j], m.methods[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrapper type to implement sort.Interface for []*types.Func.
|
|
||||||
type funcList struct {
|
|
||||||
funcs []*types.Func
|
|
||||||
program *Program
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fl *funcList) Len() int {
|
|
||||||
return len(fl.funcs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fl *funcList) Less(i, j int) bool {
|
|
||||||
iid := fl.program.MethodNum(fl.funcs[i])
|
|
||||||
jid := fl.program.MethodNum(fl.funcs[j])
|
|
||||||
return iid < jid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fl *funcList) Swap(i, j int) {
|
|
||||||
fl.funcs[i], fl.funcs[j] = fl.funcs[j], fl.funcs[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return true if this is a CGo-internal function that can be ignored.
|
// Return true if this is a CGo-internal function that can be ignored.
|
||||||
func isCGoInternal(name string) bool {
|
func isCGoInternal(name string) bool {
|
||||||
if strings.HasPrefix(name, "_Cgo_") || strings.HasPrefix(name, "_cgo") {
|
if strings.HasPrefix(name, "_Cgo_") || strings.HasPrefix(name, "_cgo") {
|
||||||
|
|
102
ir/passes.go
102
ir/passes.go
|
@ -2,8 +2,6 @@ package ir
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go/types"
|
"go/types"
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/ssa"
|
"golang.org/x/tools/go/ssa"
|
||||||
)
|
)
|
||||||
|
@ -59,18 +57,6 @@ func Signature(sig *types.Signature) string {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert an interface type to a string of all method strings, separated by
|
|
||||||
// "; ". For example: "Read([]byte) (int, error); Close() error"
|
|
||||||
func InterfaceKey(itf *types.Interface) string {
|
|
||||||
methodStrings := []string{}
|
|
||||||
for i := 0; i < itf.NumMethods(); i++ {
|
|
||||||
method := itf.Method(i)
|
|
||||||
methodStrings = append(methodStrings, MethodSignature(method))
|
|
||||||
}
|
|
||||||
sort.Strings(methodStrings)
|
|
||||||
return strings.Join(methodStrings, ";")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill in parents of all functions.
|
// Fill in parents of all functions.
|
||||||
//
|
//
|
||||||
// All packages need to be added before this pass can run, or it will produce
|
// All packages need to be added before this pass can run, or it will produce
|
||||||
|
@ -117,30 +103,17 @@ func (p *Program) AnalyseCallgraph() {
|
||||||
|
|
||||||
// Find all types that are put in an interface.
|
// Find all types that are put in an interface.
|
||||||
func (p *Program) AnalyseInterfaceConversions() {
|
func (p *Program) AnalyseInterfaceConversions() {
|
||||||
// Clear, if AnalyseTypes has been called before.
|
// Clear, if AnalyseInterfaceConversions has been called before.
|
||||||
p.typesWithoutMethods = map[string]int{"nil": 0}
|
p.typesInInterfaces = map[string]struct{}{}
|
||||||
p.typesWithMethods = map[string]*TypeWithMethods{}
|
|
||||||
|
|
||||||
for _, f := range p.Functions {
|
for _, f := range p.Functions {
|
||||||
for _, block := range f.Blocks {
|
for _, block := range f.Blocks {
|
||||||
for _, instr := range block.Instrs {
|
for _, instr := range block.Instrs {
|
||||||
switch instr := instr.(type) {
|
switch instr := instr.(type) {
|
||||||
case *ssa.MakeInterface:
|
case *ssa.MakeInterface:
|
||||||
methods := getAllMethods(f.Prog, instr.X.Type())
|
|
||||||
name := instr.X.Type().String()
|
name := instr.X.Type().String()
|
||||||
if _, ok := p.typesWithMethods[name]; !ok && len(methods) > 0 {
|
if _, ok := p.typesInInterfaces[name]; !ok {
|
||||||
t := &TypeWithMethods{
|
p.typesInInterfaces[name] = struct{}{}
|
||||||
t: instr.X.Type(),
|
|
||||||
Num: len(p.typesWithMethods),
|
|
||||||
Methods: make(map[string]*types.Selection),
|
|
||||||
}
|
|
||||||
for _, sel := range methods {
|
|
||||||
name := MethodSignature(sel.Obj().(*types.Func))
|
|
||||||
t.Methods[name] = sel
|
|
||||||
}
|
|
||||||
p.typesWithMethods[name] = t
|
|
||||||
} else if _, ok := p.typesWithoutMethods[name]; !ok && len(methods) == 0 {
|
|
||||||
p.typesWithoutMethods[name] = len(p.typesWithoutMethods)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -349,75 +322,10 @@ func (p *Program) IsBlocking(f *Function) bool {
|
||||||
return f.blocking
|
return f.blocking
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the type number and whether this type is actually used. Used in
|
|
||||||
// interface conversions (type is always used) and type asserts (type may not be
|
|
||||||
// used, meaning assert is always false in this program).
|
|
||||||
//
|
|
||||||
// May only be used after all packages have been added to the analyser.
|
|
||||||
func (p *Program) TypeNum(typ types.Type) (int, bool) {
|
|
||||||
if n, ok := p.typesWithoutMethods[typ.String()]; ok {
|
|
||||||
return n, true
|
|
||||||
} else if meta, ok := p.typesWithMethods[typ.String()]; ok {
|
|
||||||
return len(p.typesWithoutMethods) + meta.Num, true
|
|
||||||
} else {
|
|
||||||
return -1, false // type is never put in an interface
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// InterfaceNum returns the numeric interface ID of this type, for use in type
|
|
||||||
// asserts.
|
|
||||||
func (p *Program) InterfaceNum(itfType *types.Interface) int {
|
|
||||||
key := InterfaceKey(itfType)
|
|
||||||
if itf, ok := p.interfaces[key]; !ok {
|
|
||||||
num := len(p.interfaces)
|
|
||||||
p.interfaces[key] = &Interface{Num: num, Type: itfType}
|
|
||||||
return num
|
|
||||||
} else {
|
|
||||||
return itf.Num
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MethodNum returns the numeric ID of this method, to be used in method lookups
|
|
||||||
// on interfaces for example.
|
|
||||||
func (p *Program) MethodNum(method *types.Func) int {
|
|
||||||
name := MethodSignature(method)
|
|
||||||
if _, ok := p.methodSignatureNames[name]; !ok {
|
|
||||||
p.methodSignatureNames[name] = len(p.methodSignatureNames)
|
|
||||||
}
|
|
||||||
return p.methodSignatureNames[MethodSignature(method)]
|
|
||||||
}
|
|
||||||
|
|
||||||
// The start index of the first dynamic type that has methods.
|
|
||||||
// Types without methods always have a lower ID and types with methods have this
|
|
||||||
// or a higher ID.
|
|
||||||
//
|
|
||||||
// May only be used after all packages have been added to the analyser.
|
|
||||||
func (p *Program) FirstDynamicType() int {
|
|
||||||
return len(p.typesWithoutMethods)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return all types with methods, sorted by type ID.
|
|
||||||
func (p *Program) AllDynamicTypes() []*TypeWithMethods {
|
|
||||||
l := make([]*TypeWithMethods, len(p.typesWithMethods))
|
|
||||||
for _, m := range p.typesWithMethods {
|
|
||||||
l[m.Num] = m
|
|
||||||
}
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return all interface types, sorted by interface ID.
|
|
||||||
func (p *Program) AllInterfaces() []*Interface {
|
|
||||||
l := make([]*Interface, len(p.interfaces))
|
|
||||||
for _, itf := range p.interfaces {
|
|
||||||
l[itf.Num] = itf
|
|
||||||
}
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Program) FunctionNeedsContext(f *Function) bool {
|
func (p *Program) FunctionNeedsContext(f *Function) bool {
|
||||||
if !f.addressTaken {
|
if !f.addressTaken {
|
||||||
if f.Signature.Recv() != nil {
|
if f.Signature.Recv() != nil {
|
||||||
_, hasInterfaceConversion := p.TypeNum(f.Signature.Recv().Type())
|
_, hasInterfaceConversion := p.typesInInterfaces[f.Signature.Recv().Type().String()]
|
||||||
if hasInterfaceConversion && p.SignatureNeedsContext(f.Signature) {
|
if hasInterfaceConversion && p.SignatureNeedsContext(f.Signature) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
32
main.go
32
main.go
|
@ -64,7 +64,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||||
fmt.Println(c.IR())
|
fmt.Println(c.IR())
|
||||||
}
|
}
|
||||||
if err := c.Verify(); err != nil {
|
if err := c.Verify(); err != nil {
|
||||||
return err
|
return errors.New("verification error after IR construction")
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.initInterp {
|
if config.initInterp {
|
||||||
|
@ -73,13 +73,13 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := c.Verify(); err != nil {
|
if err := c.Verify(); err != nil {
|
||||||
return err
|
return errors.New("verification error after interpreting runtime.initAll")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ApplyFunctionSections() // -ffunction-sections
|
c.ApplyFunctionSections() // -ffunction-sections
|
||||||
if err := c.Verify(); err != nil {
|
if err := c.Verify(); err != nil {
|
||||||
return err
|
return errors.New("verification error after applying function sections")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Browsers cannot handle external functions that have type i64 because it
|
// Browsers cannot handle external functions that have type i64 because it
|
||||||
|
@ -92,7 +92,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := c.Verify(); err != nil {
|
if err := c.Verify(); err != nil {
|
||||||
return err
|
return errors.New("verification error after running the wasm i64 hack")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,20 +100,23 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||||
// exactly.
|
// exactly.
|
||||||
switch config.opt {
|
switch config.opt {
|
||||||
case "none:", "0":
|
case "none:", "0":
|
||||||
c.Optimize(0, 0, 0) // -O0
|
err = c.Optimize(0, 0, 0) // -O0
|
||||||
case "1":
|
case "1":
|
||||||
c.Optimize(1, 0, 0) // -O1
|
err = c.Optimize(1, 0, 0) // -O1
|
||||||
case "2":
|
case "2":
|
||||||
c.Optimize(2, 0, 225) // -O2
|
err = c.Optimize(2, 0, 225) // -O2
|
||||||
case "s":
|
case "s":
|
||||||
c.Optimize(2, 1, 225) // -Os
|
err = c.Optimize(2, 1, 225) // -Os
|
||||||
case "z":
|
case "z":
|
||||||
c.Optimize(2, 2, 5) // -Oz, default
|
err = c.Optimize(2, 2, 5) // -Oz, default
|
||||||
default:
|
default:
|
||||||
return errors.New("unknown optimization level: -opt=" + config.opt)
|
err = errors.New("unknown optimization level: -opt=" + config.opt)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
if err := c.Verify(); err != nil {
|
if err := c.Verify(); err != nil {
|
||||||
return err
|
return errors.New("verification failure after LLVM optimization passes")
|
||||||
}
|
}
|
||||||
|
|
||||||
// On the AVR, pointers can point either to flash or to RAM, but we don't
|
// On the AVR, pointers can point either to flash or to RAM, but we don't
|
||||||
|
@ -124,7 +127,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||||
if strings.HasPrefix(spec.Triple, "avr") {
|
if strings.HasPrefix(spec.Triple, "avr") {
|
||||||
c.NonConstGlobals()
|
c.NonConstGlobals()
|
||||||
if err := c.Verify(); err != nil {
|
if err := c.Verify(); err != nil {
|
||||||
return err
|
return errors.New("verification error after making all globals non-constant on AVR")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -382,7 +385,10 @@ func Run(pkgName string) error {
|
||||||
// -Oz, which is the fastest optimization level (faster than -O0, -O1, -O2
|
// -Oz, which is the fastest optimization level (faster than -O0, -O1, -O2
|
||||||
// and -Os). Turn off the inliner, as the inliner increases optimization
|
// and -Os). Turn off the inliner, as the inliner increases optimization
|
||||||
// time.
|
// time.
|
||||||
c.Optimize(2, 2, 0)
|
err = c.Optimize(2, 2, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
engine, err := llvm.NewExecutionEngine(c.Module())
|
engine, err := llvm.NewExecutionEngine(c.Module())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -4,64 +4,12 @@ package runtime
|
||||||
//
|
//
|
||||||
// Interfaces are represented as a pair of {typecode, value}, where value can be
|
// Interfaces are represented as a pair of {typecode, value}, where value can be
|
||||||
// anything (including non-pointers).
|
// anything (including non-pointers).
|
||||||
//
|
|
||||||
// Signatures itself are not matched on strings, but on uniqued numbers that
|
|
||||||
// contain the name and the signature of the function (to save space), think of
|
|
||||||
// signatures as interned strings at compile time.
|
|
||||||
//
|
|
||||||
// The typecode is a small number unique for the Go type. All typecodes <
|
|
||||||
// firstTypeWithMethods do not have any methods and typecodes >=
|
|
||||||
// firstTypeWithMethods all have at least one method. This means that
|
|
||||||
// methodSetRanges does not need to contain types without methods and is thus
|
|
||||||
// indexed starting at a typecode with number firstTypeWithMethods.
|
|
||||||
//
|
|
||||||
// To further conserve some space, the methodSetRange (as the name indicates)
|
|
||||||
// doesn't contain a list of methods and function pointers directly, but instead
|
|
||||||
// just indexes into methodSetSignatures and methodSetFunctions which contains
|
|
||||||
// the mapping from uniqued signature to function pointer.
|
|
||||||
|
|
||||||
type _interface struct {
|
type _interface struct {
|
||||||
typecode uint16
|
typecode uintptr
|
||||||
value *uint8
|
value *uint8
|
||||||
}
|
}
|
||||||
|
|
||||||
// This struct indicates the range of methods in the methodSetSignatures and
|
|
||||||
// methodSetFunctions arrays that belong to this named type.
|
|
||||||
type methodSetRange struct {
|
|
||||||
index uint16 // start index into interfaceSignatures and interfaceFunctions
|
|
||||||
length uint16 // number of methods
|
|
||||||
}
|
|
||||||
|
|
||||||
// Global constants that will be set by the compiler. The arrays are of size 0,
|
|
||||||
// which is a dummy value, but will be bigger after the compiler has filled them
|
|
||||||
// in.
|
|
||||||
var (
|
|
||||||
firstTypeWithMethods uint16 // the lowest typecode that has at least one method
|
|
||||||
methodSetRanges [0]methodSetRange // indices into methodSetSignatures and methodSetFunctions
|
|
||||||
methodSetSignatures [0]uint16 // uniqued method ID
|
|
||||||
methodSetFunctions [0]*uint8 // function pointer of method
|
|
||||||
interfaceIndex [0]uint16 // mapping from interface ID to an index in interfaceMethods
|
|
||||||
interfaceLengths [0]uint8 // mapping from interface ID to the number of methods it has
|
|
||||||
interfaceMethods [0]uint16 // the method an interface implements (list of method IDs)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Get the function pointer for the method on the interface.
|
|
||||||
// This is a compiler intrinsic.
|
|
||||||
//go:nobounds
|
|
||||||
func interfaceMethod(typecode uint16, method uint16) *uint8 {
|
|
||||||
// This function doesn't do bounds checking as the supplied method must be
|
|
||||||
// in the list of signatures. The compiler will only emit
|
|
||||||
// runtime.interfaceMethod calls when the method actually exists on this
|
|
||||||
// interface (proven by the typechecker).
|
|
||||||
i := methodSetRanges[typecode-firstTypeWithMethods].index
|
|
||||||
for {
|
|
||||||
if methodSetSignatures[i] == method {
|
|
||||||
return methodSetFunctions[i]
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return true iff both interfaces are equal.
|
// Return true iff both interfaces are equal.
|
||||||
func interfaceEqual(x, y _interface) bool {
|
func interfaceEqual(x, y _interface) bool {
|
||||||
if x.typecode != y.typecode {
|
if x.typecode != y.typecode {
|
||||||
|
@ -76,67 +24,37 @@ func interfaceEqual(x, y _interface) bool {
|
||||||
panic("unimplemented: interface equality")
|
panic("unimplemented: interface equality")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return true iff the type implements all methods needed by the interface. This
|
// interfaceTypeAssert is called when a type assert without comma-ok still
|
||||||
// means the type satisfies the interface.
|
// returns false.
|
||||||
// This is a compiler intrinsic.
|
|
||||||
//go:nobounds
|
|
||||||
func interfaceImplements(typecode, interfaceNum uint16) bool {
|
|
||||||
// method set indices of the interface
|
|
||||||
itfIndex := interfaceIndex[interfaceNum]
|
|
||||||
itfIndexEnd := itfIndex + uint16(interfaceLengths[interfaceNum])
|
|
||||||
|
|
||||||
if itfIndex == itfIndexEnd {
|
|
||||||
// This interface has no methods, so it satisfies all types.
|
|
||||||
// TODO: this should be figured out at compile time (as it is known at
|
|
||||||
// compile time), so that this check is unnecessary at runtime.
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if typecode < firstTypeWithMethods {
|
|
||||||
// Type has no methods while the interface has (checked above), so this
|
|
||||||
// type does not satisfy this interface.
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// method set indices of the concrete type
|
|
||||||
methodSet := methodSetRanges[typecode-firstTypeWithMethods]
|
|
||||||
methodIndex := methodSet.index
|
|
||||||
methodIndexEnd := methodSet.index + methodSet.length
|
|
||||||
|
|
||||||
// Iterate over all methods of the interface:
|
|
||||||
for itfIndex < itfIndexEnd {
|
|
||||||
methodId := interfaceMethods[itfIndex]
|
|
||||||
if methodIndex >= methodIndexEnd {
|
|
||||||
// Reached the end of the list of methods, so interface doesn't
|
|
||||||
// implement this type.
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if methodId == methodSetSignatures[methodIndex] {
|
|
||||||
// Found a matching method, continue to the next method.
|
|
||||||
itfIndex++
|
|
||||||
methodIndex++
|
|
||||||
continue
|
|
||||||
} else if methodId > methodSetSignatures[methodIndex] {
|
|
||||||
// The method didn't match, but method ID of the concrete type was
|
|
||||||
// lower than that of the interface, so probably it has a method the
|
|
||||||
// interface doesn't implement.
|
|
||||||
// Move on to the next method of the concrete type.
|
|
||||||
methodIndex++
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
// The concrete type is missing a method. This means the type assert
|
|
||||||
// fails.
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Found a method for each expected method in the interface. This type
|
|
||||||
// assert is successful.
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func interfaceTypeAssert(ok bool) {
|
func interfaceTypeAssert(ok bool) {
|
||||||
if !ok {
|
if !ok {
|
||||||
runtimePanic("type assert failed")
|
runtimePanic("type assert failed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The following declarations are only used during IR construction. They are
|
||||||
|
// lowered to inline IR in the interface lowering pass.
|
||||||
|
// See compiler/interface-lowering.go for details.
|
||||||
|
|
||||||
|
type interfaceMethodInfo struct {
|
||||||
|
signature *uint8 // external *i8 with a name identifying the Go function signature
|
||||||
|
funcptr *uint8 // bitcast from the actual function pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pseudo function call used while putting a concrete value in an interface,
|
||||||
|
// that must be lowered to a constant uintptr.
|
||||||
|
func makeInterface(typecode *uint8, methodSet *interfaceMethodInfo) uintptr
|
||||||
|
|
||||||
|
// Pseudo function call used during a type assert. It is used during interface
|
||||||
|
// lowering, to assign the lowest type numbers to the types with the most type
|
||||||
|
// asserts. Also, it is replaced with const false if this type assert can never
|
||||||
|
// happen.
|
||||||
|
func typeAssert(actualType uintptr, assertedType *uint8) bool
|
||||||
|
|
||||||
|
// Pseudo function call that returns whether a given type implements all methods
|
||||||
|
// of the given interface.
|
||||||
|
func interfaceImplements(typecode uintptr, interfaceMethodSet **uint8) bool
|
||||||
|
|
||||||
|
// Pseudo function that returns a function pointer to the method to call.
|
||||||
|
// See the interface lowering pass for how this is lowered to a real call.
|
||||||
|
func interfaceMethod(typecode uintptr, interfaceMethodSet **uint8, signature *uint8) *uint8
|
||||||
|
|
|
@ -205,7 +205,14 @@ func printitf(msg interface{}) {
|
||||||
// cast to underlying type
|
// cast to underlying type
|
||||||
itf := *(*_interface)(unsafe.Pointer(&msg))
|
itf := *(*_interface)(unsafe.Pointer(&msg))
|
||||||
putchar('(')
|
putchar('(')
|
||||||
print(itf.typecode)
|
switch unsafe.Sizeof(itf.typecode) {
|
||||||
|
case 2:
|
||||||
|
printuint16(uint16(itf.typecode))
|
||||||
|
case 4:
|
||||||
|
printuint32(uint32(itf.typecode))
|
||||||
|
case 8:
|
||||||
|
printuint64(uint64(itf.typecode))
|
||||||
|
}
|
||||||
putchar(':')
|
putchar(':')
|
||||||
print(itf.value)
|
print(itf.value)
|
||||||
putchar(')')
|
putchar(')')
|
||||||
|
|
7
testdata/interface.go
предоставленный
7
testdata/interface.go
предоставленный
|
@ -26,6 +26,8 @@ func main() {
|
||||||
|
|
||||||
func printItf(val interface{}) {
|
func printItf(val interface{}) {
|
||||||
switch val := val.(type) {
|
switch val := val.(type) {
|
||||||
|
case Unmatched:
|
||||||
|
panic("matched the unmatchable")
|
||||||
case Doubler:
|
case Doubler:
|
||||||
println("is Doubler:", val.Double())
|
println("is Doubler:", val.Double())
|
||||||
case Tuple:
|
case Tuple:
|
||||||
|
@ -127,3 +129,8 @@ func (p SmallPair) Nth(n int) uint32 {
|
||||||
func (p SmallPair) Print() {
|
func (p SmallPair) Print() {
|
||||||
println("SmallPair.Print:", p.a, p.b)
|
println("SmallPair.Print:", p.a, p.b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// There is no type that matches this method.
|
||||||
|
type Unmatched interface {
|
||||||
|
NeverImplementedMethod()
|
||||||
|
}
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче