compiler: simplify interface lowering

This commit simplifies the IR a little bit: instead of calling
pseudo-functions runtime.interfaceImplements and
runtime.interfaceMethod, real declared functions are being called that
are then defined in the interface lowering pass. This should simplify
the interaction between various transformation passes. It also reduces
the number of lines of code, which is generally a good thing.
Этот коммит содержится в:
Ayke van Laethem 2021-10-24 14:29:40 +02:00 коммит произвёл Ron Evans
родитель 90076f9401
коммит a4afc3b4b0
14 изменённых файлов: 355 добавлений и 397 удалений

Просмотреть файл

@ -1344,9 +1344,9 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c
// //
// This is also where compiler intrinsics are implemented. // This is also where compiler intrinsics are implemented.
func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) { func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) {
if instr.IsInvoke() { var params []llvm.Value
fnCast, args := b.getInvokeCall(instr) for _, param := range instr.Args {
return b.createCall(fnCast, args, ""), nil params = append(params, b.getValue(param))
} }
// Try to call the function directly for trivially static calls. // Try to call the function directly for trivially static calls.
@ -1417,12 +1417,20 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
} else if call, ok := instr.Value.(*ssa.Builtin); ok { } else if call, ok := instr.Value.(*ssa.Builtin); ok {
// Builtin function (append, close, delete, etc.).) // Builtin function (append, close, delete, etc.).)
var argTypes []types.Type var argTypes []types.Type
var argValues []llvm.Value
for _, arg := range instr.Args { for _, arg := range instr.Args {
argTypes = append(argTypes, arg.Type()) argTypes = append(argTypes, arg.Type())
argValues = append(argValues, b.getValue(arg))
} }
return b.createBuiltin(argTypes, argValues, call.Name(), instr.Pos()) return b.createBuiltin(argTypes, params, call.Name(), instr.Pos())
} else if instr.IsInvoke() {
// Interface method call (aka invoke call).
itf := b.getValue(instr.Value) // interface value (runtime._interface)
typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode")
value := b.CreateExtractValue(itf, 1, "invoke.func.value") // receiver
// Prefix the params with receiver value and suffix with typecode.
params = append([]llvm.Value{value}, params...)
params = append(params, typecode)
callee = b.getInvokeFunction(instr)
context = llvm.Undef(b.i8ptrType)
} else { } else {
// Function pointer. // Function pointer.
value := b.getValue(instr.Value) value := b.getValue(instr.Value)
@ -1432,11 +1440,6 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
b.createNilCheck(instr.Value, callee, "fpcall") b.createNilCheck(instr.Value, callee, "fpcall")
} }
var params []llvm.Value
for _, param := range instr.Args {
params = append(params, b.getValue(param))
}
if !exported { if !exported {
// This function takes a context parameter. // This function takes a context parameter.
// Add it to the end of the parameter list. // Add it to the end of the parameter list.

Просмотреть файл

@ -331,10 +331,10 @@ func (b *builder) createRunDefers() {
//Pass context //Pass context
forwardParams = append(forwardParams, context) forwardParams = append(forwardParams, context)
} else { } else {
// Isolate the typecode. // Move typecode from the start to the end of the list of
typecode := forwardParams[0] // parameters.
forwardParams = forwardParams[1:] forwardParams = append(forwardParams[1:], forwardParams[0])
fnPtr = b.getInvokePtr(callback, typecode) fnPtr = b.getInvokeFunction(callback)
// Add the context parameter. An interface call cannot also be a // Add the context parameter. An interface call cannot also be a
// closure but we have to supply the parameter anyway for platforms // closure but we have to supply the parameter anyway for platforms

Просмотреть файл

@ -47,6 +47,7 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
var length int64 var length int64
var methodSet llvm.Value var methodSet llvm.Value
var ptrTo llvm.Value var ptrTo llvm.Value
var typeAssert llvm.Value
switch typ := typ.(type) { switch typ := typ.(type) {
case *types.Named: case *types.Named:
references = c.getTypeCode(typ.Underlying()) references = c.getTypeCode(typ.Underlying())
@ -69,6 +70,9 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
} }
if _, ok := typ.Underlying().(*types.Interface); !ok { if _, ok := typ.Underlying().(*types.Interface); !ok {
methodSet = c.getTypeMethodSet(typ) methodSet = c.getTypeMethodSet(typ)
} else {
typeAssert = c.getInterfaceImplementsFunc(typ)
typeAssert = llvm.ConstPtrToInt(typeAssert, c.uintptrType)
} }
if _, ok := typ.Underlying().(*types.Pointer); !ok { if _, ok := typ.Underlying().(*types.Pointer); !ok {
ptrTo = c.getTypeCode(types.NewPointer(typ)) ptrTo = c.getTypeCode(types.NewPointer(typ))
@ -87,6 +91,9 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
if !ptrTo.IsNil() { if !ptrTo.IsNil() {
globalValue = llvm.ConstInsertValue(globalValue, ptrTo, []uint32{3}) globalValue = llvm.ConstInsertValue(globalValue, ptrTo, []uint32{3})
} }
if !typeAssert.IsNil() {
globalValue = llvm.ConstInsertValue(globalValue, typeAssert, []uint32{4})
}
global.SetInitializer(globalValue) global.SetInitializer(globalValue)
global.SetLinkage(llvm.LinkOnceODRLinkage) global.SetLinkage(llvm.LinkOnceODRLinkage)
global.SetGlobalConstant(true) global.SetGlobalConstant(true)
@ -193,7 +200,11 @@ func getTypeCodeName(t types.Type) string {
case *types.Interface: case *types.Interface:
methods := make([]string, t.NumMethods()) methods := make([]string, t.NumMethods())
for i := 0; i < t.NumMethods(); i++ { for i := 0; i < t.NumMethods(); i++ {
methods[i] = t.Method(i).Name() + ":" + getTypeCodeName(t.Method(i).Type()) name := t.Method(i).Name()
if !token.IsExported(name) {
name = t.Method(i).Pkg().Path() + "." + name
}
methods[i] = name + ":" + getTypeCodeName(t.Method(i).Type())
} }
return "interface:" + "{" + strings.Join(methods, ",") + "}" return "interface:" + "{" + strings.Join(methods, ",") + "}"
case *types.Map: case *types.Map:
@ -306,10 +317,9 @@ func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value {
return llvm.ConstGEP(global, []llvm.Value{zero, zero}) return llvm.ConstGEP(global, []llvm.Value{zero, zero})
} }
// getMethodSignature returns a global variable which is a reference to an // getMethodSignatureName returns a unique name (that can be used as the name of
// external *i8 indicating the indicating the signature of this method. It is // a global) for the given method.
// used during the interface lowering pass. func (c *compilerContext) getMethodSignatureName(method *types.Func) string {
func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value {
signature := methodSignature(method) signature := methodSignature(method)
var globalName string var globalName string
if token.IsExported(method.Name()) { if token.IsExported(method.Name()) {
@ -317,6 +327,14 @@ func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value {
} else { } else {
globalName = method.Type().(*types.Signature).Recv().Pkg().Path() + ".$methods." + signature globalName = method.Type().(*types.Signature).Recv().Pkg().Path() + ".$methods." + signature
} }
return globalName
}
// getMethodSignature returns a global variable which is a reference to an
// external *i8 indicating the indicating the signature of this method. It is
// used during the interface lowering pass.
func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value {
globalName := c.getMethodSignatureName(method)
signatureGlobal := c.mod.NamedGlobal(globalName) signatureGlobal := c.mod.NamedGlobal(globalName)
if signatureGlobal.IsNil() { if signatureGlobal.IsNil() {
// TODO: put something useful in these globals, such as the method // TODO: put something useful in these globals, such as the method
@ -345,15 +363,16 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
commaOk := llvm.Value{} commaOk := llvm.Value{}
if _, 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 pseudo call will be lowered in the interface lowering pass to a // This is a call to an interface type assert function.
// real call which checks whether the provided typecode is any of the // The interface lowering pass will define this function by filling it
// concrete types that implements this interface. // with a type switch over all concrete types that implement this
// interface, and returning whether it's one of the matched types.
// This is very different from how interface asserts are implemented in // This is very different from how interface asserts are implemented in
// the main Go compiler, where the runtime checks whether the type // the main Go compiler, where the runtime checks whether the type
// implements each method of the interface. See: // implements each method of the interface. See:
// https://research.swtch.com/interfaces // https://research.swtch.com/interfaces
methodSet := b.getInterfaceMethodSet(expr.AssertedType) fn := b.getInterfaceImplementsFunc(expr.AssertedType)
commaOk = b.createRuntimeCall("interfaceImplements", []llvm.Value{actualTypeNum, methodSet}, "") commaOk = b.CreateCall(fn, []llvm.Value{actualTypeNum}, "")
} else { } else {
globalName := "reflect/types.typeid:" + getTypeCodeName(expr.AssertedType) globalName := "reflect/types.typeid:" + getTypeCodeName(expr.AssertedType)
@ -420,39 +439,50 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
} }
} }
// getInvokePtr creates an interface function pointer lookup for the specified invoke instruction, using a specified typecode. // getMethodsString returns a string to be used in the "tinygo-methods" string
func (b *builder) getInvokePtr(instr *ssa.CallCommon, typecode llvm.Value) llvm.Value { // attribute for interface functions.
llvmFnType := b.getRawFuncType(instr.Method.Type().(*types.Signature)) func (c *compilerContext) getMethodsString(itf *types.Interface) string {
values := []llvm.Value{ methods := make([]string, itf.NumMethods())
typecode, for i := range methods {
b.getInterfaceMethodSet(instr.Value.Type()), methods[i] = c.getMethodSignatureName(itf.Method(i))
b.getMethodSignature(instr.Method),
} }
fn := b.createRuntimeCall("interfaceMethod", values, "invoke.func") return strings.Join(methods, "; ")
return b.CreateIntToPtr(fn, llvmFnType, "invoke.func.cast")
} }
// getInvokeCall creates and returns the function pointer and parameters of an // getInterfaceImplementsfunc returns a declared function that works as a type
// interface call. // switch. The interface lowering pass will define this function.
func (b *builder) getInvokeCall(instr *ssa.CallCommon) (llvm.Value, []llvm.Value) { func (c *compilerContext) getInterfaceImplementsFunc(assertedType types.Type) llvm.Value {
// Call an interface method with dynamic dispatch. fnName := getTypeCodeName(assertedType.Underlying()) + ".$typeassert"
itf := b.getValue(instr.Value) // interface llvmFn := c.mod.NamedFunction(fnName)
if llvmFn.IsNil() {
typecode := b.CreateExtractValue(itf, 0, "invoke.typecode") llvmFnType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.uintptrType}, false)
fnCast := b.getInvokePtr(instr, typecode) llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType)
receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver") methods := c.getMethodsString(assertedType.Underlying().(*types.Interface))
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-methods", methods))
args := []llvm.Value{receiverValue}
for _, arg := range instr.Args {
args = append(args, b.getValue(arg))
} }
// Add the context parameter. An interface call never takes a context but we return llvmFn
// have to supply the parameter anyway. }
args = append(args, llvm.Undef(b.i8ptrType))
// Add the parent goroutine handle.
args = append(args, llvm.Undef(b.i8ptrType))
return fnCast, args // getInvokeFunction returns the thunk to call the given interface method. The
// thunk is declared, not defined: it will be defined by the interface lowering
// pass.
func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value {
fnName := getTypeCodeName(instr.Value.Type().Underlying()) + "." + instr.Method.Name() + "$invoke"
llvmFn := c.mod.NamedFunction(fnName)
if llvmFn.IsNil() {
sig := instr.Method.Type().(*types.Signature)
var paramTuple []*types.Var
for i := 0; i < sig.Params().Len(); i++ {
paramTuple = append(paramTuple, sig.Params().At(i))
}
paramTuple = append(paramTuple, types.NewVar(token.NoPos, nil, "$typecode", types.Typ[types.Uintptr]))
llvmFnType := c.getRawFuncType(types.NewSignature(sig.Recv(), types.NewTuple(paramTuple...), sig.Results(), false)).ElementType()
llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType)
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-invoke", c.getMethodSignatureName(instr.Method)))
methods := c.getMethodsString(instr.Value.Type().Underlying().(*types.Interface))
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-methods", methods))
}
return llvmFn
} }
// getInterfaceInvokeWrapper returns a wrapper for the given method so it can be // getInterfaceInvokeWrapper returns a wrapper for the given method so it can be

9
compiler/testdata/interface.go предоставленный
Просмотреть файл

@ -49,6 +49,15 @@ func isStringer(itf interface{}) bool {
return ok return ok
} }
type fooInterface interface {
String() string
foo(int) byte
}
func callFooMethod(itf fooInterface) uint8 {
return itf.foo(3)
}
func callErrorMethod(itf error) string { func callErrorMethod(itf error) string {
return itf.Error() return itf.Error()
} }

48
compiler/testdata/interface.ll предоставленный
Просмотреть файл

@ -3,25 +3,24 @@ source_filename = "interface.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32-unknown-wasi" target triple = "wasm32-unknown-wasi"
%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo*, %runtime.typecodeID* } %runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo*, %runtime.typecodeID*, i32 }
%runtime.interfaceMethodInfo = type { i8*, i32 } %runtime.interfaceMethodInfo = type { i8*, i32 }
%runtime._interface = type { i32, i8* } %runtime._interface = type { i32, i8* }
%runtime._string = type { i8*, i32 } %runtime._string = type { i8*, i32 }
@"reflect/types.type:basic:int" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* null, i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:basic:int" } @"reflect/types.type:basic:int" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* null, i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:basic:int", i32 0 }
@"reflect/types.type:pointer:basic:int" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null } @"reflect/types.type:pointer:basic:int" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 0 }
@"reflect/types.type:pointer:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:named:error", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null } @"reflect/types.type:pointer:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:named:error", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 0 }
@"reflect/types.type:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:named:error" } @"reflect/types.type:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:named:error", i32 ptrtoint (i1 (i32)* @"interface:{Error:func:{}{basic:string}}.$typeassert" to i32) }
@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{Error() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" } @"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{Error() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}", i32 ptrtoint (i1 (i32)* @"interface:{Error:func:{}{basic:string}}.$typeassert" to i32) }
@"reflect/methods.Error() string" = linkonce_odr constant i8 0, align 1 @"reflect/methods.Error() string" = linkonce_odr constant i8 0, align 1
@"reflect/types.interface:interface{Error() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.Error() string"] @"reflect/types.interface:interface{Error() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.Error() string"]
@"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null } @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 0 }
@"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{String:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null } @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{String:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 0 }
@"reflect/types.type:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{String() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" } @"reflect/types.type:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{String() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}", i32 ptrtoint (i1 (i32)* @"interface:{String:func:{}{basic:string}}.$typeassert" to i32) }
@"reflect/methods.String() string" = linkonce_odr constant i8 0, align 1 @"reflect/methods.String() string" = linkonce_odr constant i8 0, align 1
@"reflect/types.interface:interface{String() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.String() string"] @"reflect/types.interface:interface{String() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.String() string"]
@"reflect/types.typeid:basic:int" = external constant i8 @"reflect/types.typeid:basic:int" = external constant i8
@"error$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.Error() string"]
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*) declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
@ -49,12 +48,16 @@ entry:
ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:pointer:named:error" to i32), i8* null } ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:pointer:named:error" to i32), i8* null }
} }
declare i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(i32) #1
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden %runtime._interface @main.anonymousInterfaceType(i8* %context, i8* %parentHandle) unnamed_addr #0 { define hidden %runtime._interface @main.anonymousInterfaceType(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" to i32), i8* null } ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" to i32), i8* null }
} }
declare i1 @"interface:{String:func:{}{basic:string}}.$typeassert"(i32) #2
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden i1 @main.isInt(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr #0 { define hidden i1 @main.isInt(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
@ -73,7 +76,7 @@ declare i1 @runtime.typeAssert(i32, i8* dereferenceable_or_null(1), i8*, i8*)
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden i1 @main.isError(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr #0 { define hidden i1 @main.isError(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%0 = call i1 @runtime.interfaceImplements(i32 %itf.typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"error$interface", i32 0, i32 0), i8* undef, i8* null) #0 %0 = call i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(i32 %itf.typecode) #0
br i1 %0, label %typeassert.ok, label %typeassert.next br i1 %0, label %typeassert.ok, label %typeassert.next
typeassert.ok: ; preds = %entry typeassert.ok: ; preds = %entry
@ -83,12 +86,10 @@ typeassert.next: ; preds = %typeassert.ok, %ent
ret i1 %0 ret i1 %0
} }
declare i1 @runtime.interfaceImplements(i32, i8** dereferenceable_or_null(4), i8*, i8*)
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden i1 @main.isStringer(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr #0 { define hidden i1 @main.isStringer(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%0 = call i1 @runtime.interfaceImplements(i32 %itf.typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"reflect/types.interface:interface{String() string}$interface", i32 0, i32 0), i8* undef, i8* null) #0 %0 = call i1 @"interface:{String:func:{}{basic:string}}.$typeassert"(i32 %itf.typecode) #0
br i1 %0, label %typeassert.ok, label %typeassert.next br i1 %0, label %typeassert.ok, label %typeassert.next
typeassert.ok: ; preds = %entry typeassert.ok: ; preds = %entry
@ -98,15 +99,26 @@ typeassert.next: ; preds = %typeassert.ok, %ent
ret i1 %0 ret i1 %0
} }
; Function Attrs: nounwind
define hidden i8 @main.callFooMethod(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = call i8 @"interface:{String:func:{}{basic:string},main.foo:func:{basic:int}{basic:uint8}}.foo$invoke"(i8* %itf.value, i32 3, i32 %itf.typecode, i8* undef, i8* undef) #0
ret i8 %0
}
declare i8 @"interface:{String:func:{}{basic:string},main.foo:func:{basic:int}{basic:uint8}}.foo$invoke"(i8*, i32, i32, i8*, i8*) #3
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden %runtime._string @main.callErrorMethod(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr #0 { define hidden %runtime._string @main.callErrorMethod(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%invoke.func = call i32 @runtime.interfaceMethod(i32 %itf.typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"error$interface", i32 0, i32 0), i8* nonnull @"reflect/methods.Error() string", i8* undef, i8* null) #0 %0 = call %runtime._string @"interface:{Error:func:{}{basic:string}}.Error$invoke"(i8* %itf.value, i32 %itf.typecode, i8* undef, i8* undef) #0
%invoke.func.cast = inttoptr i32 %invoke.func to %runtime._string (i8*, i8*, i8*)*
%0 = call %runtime._string %invoke.func.cast(i8* %itf.value, i8* undef, i8* undef) #0
ret %runtime._string %0 ret %runtime._string %0
} }
declare i32 @runtime.interfaceMethod(i32, i8** dereferenceable_or_null(4), i8* dereferenceable_or_null(1), i8*, i8*) declare %runtime._string @"interface:{Error:func:{}{basic:string}}.Error$invoke"(i8*, i32, i8*, i8*) #4
attributes #0 = { nounwind } attributes #0 = { nounwind }
attributes #1 = { "tinygo-methods"="reflect/methods.Error() string" }
attributes #2 = { "tinygo-methods"="reflect/methods.String() string" }
attributes #3 = { "tinygo-invoke"="main.$methods.foo(int) byte" "tinygo-methods"="reflect/methods.String() string; main.$methods.foo(int) byte" }
attributes #4 = { "tinygo-invoke"="reflect/methods.Error() string" "tinygo-methods"="reflect/methods.Error() string" }

Просмотреть файл

@ -378,7 +378,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
} else { } else {
locals[inst.localIndex] = literalValue{uint8(0)} locals[inst.localIndex] = literalValue{uint8(0)}
} }
case callFn.name == "runtime.interfaceImplements": case strings.HasSuffix(callFn.name, ".$typeassert"):
if r.debug { if r.debug {
fmt.Fprintln(os.Stderr, indent+"interface assert:", operands[1:]) fmt.Fprintln(os.Stderr, indent+"interface assert:", operands[1:])
} }
@ -393,11 +393,9 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
return nil, mem, r.errorAt(inst, err) return nil, mem, r.errorAt(inst, err)
} }
methodSet := mem.get(methodSetPtr.index()).llvmGlobal.Initializer() methodSet := mem.get(methodSetPtr.index()).llvmGlobal.Initializer()
interfaceMethodSetPtr, err := operands[2].asPointer(r) llvmFn := inst.llvmInst.CalledValue()
if err != nil { methodSetAttr := llvmFn.GetStringAttributeAtIndex(-1, "tinygo-methods")
return nil, mem, r.errorAt(inst, err) methodSetString := methodSetAttr.GetStringValue()
}
interfaceMethodSet := mem.get(interfaceMethodSetPtr.index()).llvmGlobal.Initializer()
// Make a set of all the methods on the concrete type, for // Make a set of all the methods on the concrete type, for
// easier checking in the next step. // easier checking in the next step.
@ -412,8 +410,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// of defined methods calculated above. This is the interface // of defined methods calculated above. This is the interface
// assert itself. // assert itself.
assertOk := uint8(1) // i1 true assertOk := uint8(1) // i1 true
for i := 0; i < interfaceMethodSet.Type().ArrayLength(); i++ { for _, name := range strings.Split(methodSetString, "; ") {
name := llvm.ConstExtractValue(interfaceMethodSet, []uint32{uint32(i)}).Name()
if _, ok := concreteTypeMethods[name]; !ok { if _, ok := concreteTypeMethods[name]; !ok {
// There is a method on the interface that is not // There is a method on the interface that is not
// implemented by the type. The assertion will fail. // implemented by the type. The assertion will fail.
@ -423,20 +420,20 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
} }
// If assertOk is still 1, the assertion succeeded. // If assertOk is still 1, the assertion succeeded.
locals[inst.localIndex] = literalValue{assertOk} locals[inst.localIndex] = literalValue{assertOk}
case callFn.name == "runtime.interfaceMethod": case strings.HasSuffix(callFn.name, "$invoke"):
// This builtin returns the function (which may be a thunk) to // This thunk is the interface method dispatcher: it is called
// invoke a method on an interface. It does not call the method. // with all regular parameters and a type code. It will then
// call the concrete method for it.
if r.debug { if r.debug {
fmt.Fprintln(os.Stderr, indent+"interface method:", operands[1:]) fmt.Fprintln(os.Stderr, indent+"invoke method:", operands[1:])
} }
// Load the first param, which is the type code (ptrtoint of the // Load the type code of the interface value.
// type code global). typecodeIDBitCast, err := operands[len(operands)-3].toLLVMValue(inst.llvmInst.Operand(len(operands)-4).Type(), &mem)
typecodeIDPtrToInt, err := operands[1].toLLVMValue(inst.llvmInst.Operand(0).Type(), &mem)
if err != nil { if err != nil {
return nil, mem, r.errorAt(inst, err) return nil, mem, r.errorAt(inst, err)
} }
typecodeID := typecodeIDPtrToInt.Operand(0).Initializer() typecodeID := typecodeIDBitCast.Operand(0).Initializer()
// Load the method set, which is part of the typecodeID object. // Load the method set, which is part of the typecodeID object.
methodSet := llvm.ConstExtractValue(typecodeID, []uint32{2}).Operand(0).Initializer() methodSet := llvm.ConstExtractValue(typecodeID, []uint32{2}).Operand(0).Initializer()
@ -444,7 +441,10 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// We don't need to load the interface method set. // We don't need to load the interface method set.
// Load the signature of the to-be-called function. // Load the signature of the to-be-called function.
signature := inst.llvmInst.Operand(2) llvmFn := inst.llvmInst.CalledValue()
invokeAttr := llvmFn.GetStringAttributeAtIndex(-1, "tinygo-invoke")
invokeName := invokeAttr.GetStringValue()
signature := r.mod.NamedGlobal(invokeName)
// Iterate through all methods, looking for the one method that // Iterate through all methods, looking for the one method that
// should be returned. // should be returned.
@ -457,9 +457,13 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
} }
} }
if method.IsNil() { if method.IsNil() {
return nil, mem, r.errorAt(inst, errors.New("could not find method: "+signature.Name())) return nil, mem, r.errorAt(inst, errors.New("could not find method: "+invokeName))
} }
locals[inst.localIndex] = r.getValue(method)
// Change the to-be-called function to the underlying method to
// be called and fall through to the default case.
callFn = r.getFunction(method)
fallthrough
default: default:
if len(callFn.blocks) == 0 { if len(callFn.blocks) == 0 {
// Call to a function declaration without a definition // Call to a function declaration without a definition

Просмотреть файл

@ -117,6 +117,10 @@ type typecodeID struct {
// Keeping the type struct alive here is important so that values from // Keeping the type struct alive here is important so that values from
// reflect.New (which uses reflect.PtrTo) can be used in type asserts etc. // reflect.New (which uses reflect.PtrTo) can be used in type asserts etc.
ptrTo *typecodeID ptrTo *typecodeID
// typeAssert is a ptrtoint of a declared interface assert function.
// It only exists to make the rtcalls pass easier.
typeAssert uintptr
} }
// structField is used by the compiler to pass information to the interface // structField is used by the compiler to pass information to the interface
@ -133,11 +137,3 @@ type structField struct {
// asserts. Also, it is replaced with const false if this type assert can never // asserts. Also, it is replaced with const false if this type assert can never
// happen. // happen.
func typeAssert(actualType uintptr, assertedType *uint8) bool 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) uintptr

Просмотреть файл

@ -3,32 +3,27 @@ package transform
// This file provides function to lower interface intrinsics to their final LLVM // This file provides function to lower interface intrinsics to their final LLVM
// form, optimizing them in the process. // form, optimizing them in the process.
// //
// During SSA construction, the following pseudo-calls are created: // During SSA construction, the following pseudo-call is created (see
// src/runtime/interface.go):
// runtime.typeAssert(typecode, assertedType) // runtime.typeAssert(typecode, assertedType)
// runtime.interfaceImplements(typecode, interfaceMethodSet) // Additionally, interface type asserts and interface invoke functions are
// runtime.interfaceMethod(typecode, interfaceMethodSet, signature) // declared but not defined, so the optimizer will leave them alone.
// 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: // This pass lowers these functions to their final form:
// //
// typeAssert: // typeAssert:
// Replaced with an icmp instruction so it can be directly used in a type // 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 // switch. This is very easy to optimize for LLVM: it will often translate a
// type switch into a regular switch statement. // type switch into a regular switch statement.
// //
// interfaceImplements: // interface type assert:
// This call is translated into a call that checks whether the underlying // These functions are defined by creating a big type switch over all the
// type is one of the types implementing this interface. // concrete types implementing this interface.
// //
// interfaceMethod: // interface invoke:
// This call is replaced with a call to a function that calls the // These functions are defined with a similar type switch, but instead of
// appropriate method depending on the underlying type. // checking for the appropriate type, these functions will call the
// When there is only one type implementing this interface, this call is // underlying method instead.
// 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 // 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 // main Go compiler implements them. For more details on how the main Go
@ -45,29 +40,10 @@ import (
// any method in particular. // any method in particular.
type signatureInfo struct { type signatureInfo struct {
name string name string
global llvm.Value
methods []*methodInfo methods []*methodInfo
interfaces []*interfaceInfo 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 {
var methodName string
if strings.HasPrefix(s.name, "reflect/methods.") {
methodName = s.name[len("reflect/methods."):]
} else if idx := strings.LastIndex(s.name, ".$methods."); idx >= 0 {
methodName = s.name[idx+len(".$methods."):]
} else {
panic("could not find method name")
}
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. // methodInfo describes a single method on a concrete type.
type methodInfo struct { type methodInfo struct {
*signatureInfo *signatureInfo
@ -98,21 +74,9 @@ func (t *typeInfo) getMethod(signature *signatureInfo) *methodInfo {
// interfaceInfo keeps information about a Go interface type, including all // interfaceInfo keeps information about a Go interface type, including all
// methods it has. // methods it has.
type interfaceInfo struct { type interfaceInfo struct {
name string // name with $interface suffix name string // "tinygo-methods" attribute
methodSet llvm.Value // global which this interfaceInfo describes signatures map[string]*signatureInfo // method set
signatures []*signatureInfo // method set types []*typeInfo // types this interface implements
types []*typeInfo // 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 // lowerInterfacesPass keeps state related to the interface lowering pass. The
@ -172,25 +136,26 @@ func (p *lowerInterfacesPass) run() error {
} }
} }
// Find all interface method calls. // Find all interface type asserts and interface method thunks.
interfaceMethod := p.mod.NamedFunction("runtime.interfaceMethod") var interfaceAssertFunctions []llvm.Value
interfaceMethodUses := getUses(interfaceMethod) var interfaceInvokeFunctions []llvm.Value
for _, use := range interfaceMethodUses { for fn := p.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
methodSet := use.Operand(1).Operand(0) methodsAttr := fn.GetStringAttributeAtIndex(-1, "tinygo-methods")
name := methodSet.Name() if methodsAttr.IsNil() {
if _, ok := p.interfaces[name]; !ok { continue
p.addInterface(methodSet)
} }
} if !hasUses(fn) {
// Don't bother defining this function.
// Find all interface type asserts. continue
interfaceImplements := p.mod.NamedFunction("runtime.interfaceImplements") }
interfaceImplementsUses := getUses(interfaceImplements) p.addInterface(methodsAttr.GetStringValue())
for _, use := range interfaceImplementsUses { invokeAttr := fn.GetStringAttributeAtIndex(-1, "tinygo-invoke")
methodSet := use.Operand(1).Operand(0) if invokeAttr.IsNil() {
name := methodSet.Name() // Type assert.
if _, ok := p.interfaces[name]; !ok { interfaceAssertFunctions = append(interfaceAssertFunctions, fn)
p.addInterface(methodSet) } else {
// Interface invoke.
interfaceInvokeFunctions = append(interfaceInvokeFunctions, fn)
} }
} }
@ -254,63 +219,20 @@ func (p *lowerInterfacesPass) run() error {
}) })
} }
// Replace all interface methods with their uses, if possible. // Define all interface invoke thunks.
for _, use := range interfaceMethodUses { for _, fn := range interfaceInvokeFunctions {
typecode := use.Operand(0) methodsAttr := fn.GetStringAttributeAtIndex(-1, "tinygo-methods")
signature := p.signatures[use.Operand(2).Name()] invokeAttr := fn.GetStringAttributeAtIndex(-1, "tinygo-invoke")
itf := p.interfaces[methodsAttr.GetStringValue()]
methodSet := use.Operand(1).Operand(0) // global variable signature := itf.signatures[invokeAttr.GetStringValue()]
itf := p.interfaces[methodSet.Name()] p.defineInterfaceMethodFunc(fn, itf, signature)
// Delegate calling the right function to a special wrapper function.
inttoptrs := getUses(use)
if len(inttoptrs) != 1 || inttoptrs[0].IsAIntToPtrInst().IsNil() {
return errorAt(use, "internal error: expected exactly one inttoptr use of runtime.interfaceMethod")
}
inttoptr := inttoptrs[0]
calls := getUses(inttoptr)
for _, call := range calls {
// 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/inttoptr/call with the new call.
p.builder.SetInsertPointBefore(call)
retval := p.builder.CreateCall(redirector, append(params, llvm.ConstNull(llvm.PointerType(p.ctx.Int8Type(), 0))), "")
if retval.Type().TypeKind() != llvm.VoidTypeKind {
call.ReplaceAllUsesWith(retval)
}
call.EraseFromParentAsInstruction()
}
inttoptr.EraseFromParentAsInstruction()
use.EraseFromParentAsInstruction()
} }
// Replace all typeasserts on interface types with matches on their concrete // Define all interface type assert functions.
// types, if possible. for _, fn := range interfaceAssertFunctions {
for _, use := range interfaceImplementsUses { methodsAttr := fn.GetStringAttributeAtIndex(-1, "tinygo-methods")
actualType := use.Operand(0) itf := p.interfaces[methodsAttr.GetStringValue()]
p.defineInterfaceImplementsFunc(fn, itf)
methodSet := use.Operand(1).Operand(0) // global variable
itf := p.interfaces[methodSet.Name()]
// 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()
} }
// Replace each type assert with an actual type comparison or (if the type // Replace each type assert with an actual type comparison or (if the type
@ -337,11 +259,14 @@ func (p *lowerInterfacesPass) run() error {
} }
// Remove all method sets, which are now unnecessary and inhibit later // Remove all method sets, which are now unnecessary and inhibit later
// optimizations if they are left in place. // optimizations if they are left in place. Also remove references to the
// interface type assert functions just to be sure.
zeroUintptr := llvm.ConstNull(p.uintptrType)
for _, t := range p.types { for _, t := range p.types {
initializer := t.typecode.Initializer() initializer := t.typecode.Initializer()
methodSet := llvm.ConstExtractValue(initializer, []uint32{2}) methodSet := llvm.ConstExtractValue(initializer, []uint32{2})
initializer = llvm.ConstInsertValue(initializer, llvm.ConstNull(methodSet.Type()), []uint32{2}) initializer = llvm.ConstInsertValue(initializer, llvm.ConstNull(methodSet.Type()), []uint32{2})
initializer = llvm.ConstInsertValue(initializer, zeroUintptr, []uint32{4})
t.typecode.SetInitializer(initializer) t.typecode.SetInitializer(initializer)
} }
@ -366,7 +291,7 @@ func (p *lowerInterfacesPass) addTypeMethods(t *typeInfo, methodSet llvm.Value)
signatureGlobal := llvm.ConstExtractValue(methodData, []uint32{0}) signatureGlobal := llvm.ConstExtractValue(methodData, []uint32{0})
signatureName := signatureGlobal.Name() signatureName := signatureGlobal.Name()
function := llvm.ConstExtractValue(methodData, []uint32{1}).Operand(0) function := llvm.ConstExtractValue(methodData, []uint32{1}).Operand(0)
signature := p.getSignature(signatureName, signatureGlobal) signature := p.getSignature(signatureName)
method := &methodInfo{ method := &methodInfo{
function: function, function: function,
signatureInfo: signature, signatureInfo: signature,
@ -378,53 +303,43 @@ func (p *lowerInterfacesPass) addTypeMethods(t *typeInfo, methodSet llvm.Value)
// addInterface reads information about an interface, which is the // addInterface reads information about an interface, which is the
// fully-qualified name and the signatures of all methods it has. // fully-qualified name and the signatures of all methods it has.
func (p *lowerInterfacesPass) addInterface(methodSet llvm.Value) { func (p *lowerInterfacesPass) addInterface(methodsString string) {
name := methodSet.Name() if _, ok := p.interfaces[methodsString]; ok {
t := &interfaceInfo{ return
name: name,
methodSet: methodSet,
} }
p.interfaces[name] = t t := &interfaceInfo{
methodSet = methodSet.Initializer() // get global value from getelementptr name: methodsString,
for i := 0; i < methodSet.Type().ArrayLength(); i++ { signatures: make(map[string]*signatureInfo),
signatureGlobal := llvm.ConstExtractValue(methodSet, []uint32{uint32(i)}) }
signatureName := signatureGlobal.Name() p.interfaces[methodsString] = t
signature := p.getSignature(signatureName, signatureGlobal) for _, method := range strings.Split(methodsString, "; ") {
signature := p.getSignature(method)
signature.interfaces = append(signature.interfaces, t) signature.interfaces = append(signature.interfaces, t)
t.signatures = append(t.signatures, signature) t.signatures[method] = signature
} }
} }
// getSignature returns a new *signatureInfo, creating it if it doesn't already // getSignature returns a new *signatureInfo, creating it if it doesn't already
// exist. // exist.
func (p *lowerInterfacesPass) getSignature(name string, global llvm.Value) *signatureInfo { func (p *lowerInterfacesPass) getSignature(name string) *signatureInfo {
if _, ok := p.signatures[name]; !ok { if _, ok := p.signatures[name]; !ok {
p.signatures[name] = &signatureInfo{ p.signatures[name] = &signatureInfo{
name: name, name: name,
global: global,
} }
} }
return p.signatures[name] return p.signatures[name]
} }
// getInterfaceImplementsFunc returns a function that checks whether a given // defineInterfaceImplementsFunc defines the interface type assert function. It
// interface type implements a given interface, by checking all possible types // checks whether the given interface type (passed as an argument) is one of the
// that implement this interface. // types it implements.
// //
// The type match is implemented using an if/else chain over all possible types. // The type match is implemented using an if/else chain over all possible types.
// This if/else chain is easily converted to a big switch over all possible // This if/else chain is easily converted to a big switch over all possible
// types by the LLVM simplifycfg pass. // types by the LLVM simplifycfg pass.
func (p *lowerInterfacesPass) getInterfaceImplementsFunc(itf *interfaceInfo) llvm.Value { func (p *lowerInterfacesPass) defineInterfaceImplementsFunc(fn llvm.Value, itf *interfaceInfo) {
if !itf.assertFunc.IsNil() {
return itf.assertFunc
}
// Create the function and function signature. // Create the function and function signature.
// TODO: debug info // TODO: debug info
fnName := itf.id() + "$typeassert"
fnType := llvm.FunctionType(p.ctx.Int1Type(), []llvm.Type{p.uintptrType}, false)
fn := llvm.AddFunction(p.mod, fnName, fnType)
itf.assertFunc = fn
fn.Param(0).SetName("actualType") fn.Param(0).SetName("actualType")
fn.SetLinkage(llvm.InternalLinkage) fn.SetLinkage(llvm.InternalLinkage)
fn.SetUnnamedAddr(true) fn.SetUnnamedAddr(true)
@ -456,33 +371,21 @@ func (p *lowerInterfacesPass) getInterfaceImplementsFunc(itf *interfaceInfo) llv
// Fill 'then' block (type assert was successful). // Fill 'then' block (type assert was successful).
p.builder.SetInsertPointAtEnd(thenBlock) p.builder.SetInsertPointAtEnd(thenBlock)
p.builder.CreateRet(llvm.ConstInt(p.ctx.Int1Type(), 1, false)) p.builder.CreateRet(llvm.ConstInt(p.ctx.Int1Type(), 1, false))
return itf.assertFunc
} }
// getInterfaceMethodFunc returns a thunk for calling a method on an interface. // defineInterfaceMethodFunc defines this thunk by calling the concrete method
// of the type that implements this interface.
// //
// Matching the actual type is implemented using an if/else chain over all // Matching the actual type is implemented using an if/else chain over all
// possible types. This is later converted to a switch statement by the LLVM // possible types. This is later converted to a switch statement by the LLVM
// simplifycfg pass. // simplifycfg pass.
func (p *lowerInterfacesPass) getInterfaceMethodFunc(itf *interfaceInfo, signature *signatureInfo, returnType llvm.Type, paramTypes []llvm.Type) llvm.Value { func (p *lowerInterfacesPass) defineInterfaceMethodFunc(fn llvm.Value, itf *interfaceInfo, signature *signatureInfo) {
if fn, ok := itf.methodFuncs[signature]; ok { parentHandle := fn.LastParam()
// This function has already been created. context := llvm.PrevParam(parentHandle)
return fn actualType := llvm.PrevParam(context)
} context.SetName("context")
if itf.methodFuncs == nil { actualType.SetName("actualType")
// initialize the above map parentHandle.SetName("parentHandle")
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, append(paramTypes, llvm.PointerType(p.ctx.Int8Type(), 0)), false)
fn := llvm.AddFunction(p.mod, fnName, fnType)
llvm.PrevParam(fn.LastParam()).SetName("actualType")
fn.LastParam().SetName("parentHandle")
itf.methodFuncs[signature] = fn
fn.SetLinkage(llvm.InternalLinkage) fn.SetLinkage(llvm.InternalLinkage)
fn.SetUnnamedAddr(true) fn.SetUnnamedAddr(true)
if p.sizeLevel >= 2 { if p.sizeLevel >= 2 {
@ -494,17 +397,20 @@ func (p *lowerInterfacesPass) getInterfaceMethodFunc(itf *interfaceInfo, signatu
// Collect the params that will be passed to the functions to call. // Collect the params that will be passed to the functions to call.
// These params exclude the receiver (which may actually consist of multiple // These params exclude the receiver (which may actually consist of multiple
// parts). // parts).
params := make([]llvm.Value, fn.ParamsCount()-3) params := make([]llvm.Value, fn.ParamsCount()-4)
for i := range params { for i := range params {
params[i] = fn.Param(i + 1) params[i] = fn.Param(i + 1)
} }
params = append(params,
llvm.Undef(llvm.PointerType(p.ctx.Int8Type(), 0)),
llvm.Undef(llvm.PointerType(p.ctx.Int8Type(), 0)),
)
// Start chain in the entry block. // Start chain in the entry block.
entry := p.ctx.AddBasicBlock(fn, "entry") entry := p.ctx.AddBasicBlock(fn, "entry")
p.builder.SetInsertPointAtEnd(entry) p.builder.SetInsertPointAtEnd(entry)
// Define all possible functions that can be called. // Define all possible functions that can be called.
actualType := llvm.PrevParam(fn.LastParam())
for _, typ := range itf.types { for _, typ := range itf.types {
// Create type check (if/else). // Create type check (if/else).
bb := p.ctx.AddBasicBlock(fn, typ.name) bb := p.ctx.AddBasicBlock(fn, typ.name)
@ -562,6 +468,4 @@ func (p *lowerInterfacesPass) getInterfaceMethodFunc(itf *interfaceInfo, signatu
llvm.Undef(llvm.PointerType(p.ctx.Int8Type(), 0)), llvm.Undef(llvm.PointerType(p.ctx.Int8Type(), 0)),
}, "") }, "")
p.builder.CreateUnreachable() p.builder.CreateUnreachable()
return fn
} }

Просмотреть файл

@ -109,77 +109,64 @@ func OptimizeReflectImplements(mod llvm.Module) {
if implementsSignature.IsNil() { if implementsSignature.IsNil() {
return return
} }
interfaceMethod := mod.NamedFunction("runtime.interfaceMethod")
if interfaceMethod.IsNil() {
return
}
interfaceImplements := mod.NamedFunction("runtime.interfaceImplements")
if interfaceImplements.IsNil() {
return
}
builder := mod.Context().NewBuilder() builder := mod.Context().NewBuilder()
defer builder.Dispose() defer builder.Dispose()
// Get a few useful object for use later. // Get a few useful object for use later.
zero := llvm.ConstInt(mod.Context().Int32Type(), 0, false)
uintptrType := mod.Context().IntType(llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8) uintptrType := mod.Context().IntType(llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8)
defer llvm.VerifyModule(mod, llvm.PrintMessageAction) // Look up the (reflect.Value).Implements() method.
var implementsFunc llvm.Value
for _, use := range getUses(implementsSignature) { for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
if use.IsACallInst().IsNil() { attr := fn.GetStringAttributeAtIndex(-1, "tinygo-invoke")
if attr.IsNil() {
continue continue
} }
if use.CalledValue() != interfaceMethod { if attr.GetStringValue() == "reflect/methods.Implements(reflect.Type) bool" {
continue implementsFunc = fn
} break
for _, bitcast := range getUses(use) {
if !bitcast.IsABitCastInst().IsNil() {
continue
}
for _, call := range getUses(bitcast) {
// Try to get the interface method set.
interfaceTypeBitCast := call.Operand(2)
if interfaceTypeBitCast.IsAConstantExpr().IsNil() || interfaceTypeBitCast.Opcode() != llvm.BitCast {
continue
}
interfaceType := interfaceTypeBitCast.Operand(0)
if strings.HasPrefix(interfaceType.Name(), "reflect/types.type:named:") {
// Get the underlying type.
interfaceType = llvm.ConstExtractValue(interfaceType.Initializer(), []uint32{0})
}
if !strings.HasPrefix(interfaceType.Name(), "reflect/types.type:interface:") {
// This is an error. The Type passed to Implements should be
// of interface type. Ignore it here (don't report it), it
// will be reported at runtime.
continue
}
if interfaceType.IsAGlobalVariable().IsNil() {
// Interface is unknown at compile time. This can't be
// optimized.
continue
}
// Get the 'references' field of the runtime.typecodeID, which
// is a bitcast of an interface method set.
interfaceMethodSet := llvm.ConstExtractValue(interfaceType.Initializer(), []uint32{0}).Operand(0)
builder.SetInsertPointBefore(call)
implements := builder.CreateCall(interfaceImplements, []llvm.Value{
builder.CreatePtrToInt(call.Operand(0), uintptrType, ""), // typecode to check
llvm.ConstGEP(interfaceMethodSet, []llvm.Value{zero, zero}), // method set to check against
llvm.Undef(llvm.PointerType(mod.Context().Int8Type(), 0)),
llvm.Undef(llvm.PointerType(mod.Context().Int8Type(), 0)),
}, "")
call.ReplaceAllUsesWith(implements)
call.EraseFromParentAsInstruction()
}
if !hasUses(bitcast) {
bitcast.EraseFromParentAsInstruction()
}
}
if !hasUses(use) {
use.EraseFromParentAsInstruction()
} }
} }
if implementsFunc.IsNil() {
// Doesn't exist in the program, so nothing to do.
return
}
for _, call := range getUses(implementsFunc) {
if call.IsACallInst().IsNil() {
continue
}
interfaceTypeBitCast := call.Operand(2)
if interfaceTypeBitCast.IsAConstantExpr().IsNil() || interfaceTypeBitCast.Opcode() != llvm.BitCast {
// The asserted interface is not constant, so can't optimize this
// code.
continue
}
interfaceType := interfaceTypeBitCast.Operand(0)
if strings.HasPrefix(interfaceType.Name(), "reflect/types.type:named:") {
// Get the underlying type.
interfaceType = llvm.ConstExtractValue(interfaceType.Initializer(), []uint32{0})
}
if !strings.HasPrefix(interfaceType.Name(), "reflect/types.type:interface:") {
// This is an error. The Type passed to Implements should be of
// interface type. Ignore it here (don't report it), it will be
// reported at runtime.
continue
}
if interfaceType.IsAGlobalVariable().IsNil() {
// Interface is unknown at compile time. This can't be optimized.
continue
}
typeAssertFunction := llvm.ConstExtractValue(interfaceType.Initializer(), []uint32{4}).Operand(0)
// Replace Implements call with the type assert call.
builder.SetInsertPointBefore(call)
implements := builder.CreateCall(typeAssertFunction, []llvm.Value{
builder.CreatePtrToInt(call.Operand(0), uintptrType, ""), // typecode to check
}, "")
call.ReplaceAllUsesWith(implements)
call.EraseFromParentAsInstruction()
}
} }

34
transform/testdata/interface.ll предоставленный
Просмотреть файл

@ -1,7 +1,7 @@
target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64" target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7m-none-eabi" target triple = "armv7m-none-eabi"
%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo* } %runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo*, %runtime.typecodeID*, i32 }
%runtime.interfaceMethodInfo = type { i8*, i32 } %runtime.interfaceMethodInfo = type { i8*, i32 }
@"reflect/types.type:basic:uint8" = private constant %runtime.typecodeID zeroinitializer @"reflect/types.type:basic:uint8" = private constant %runtime.typecodeID zeroinitializer
@ -9,15 +9,11 @@ target triple = "armv7m-none-eabi"
@"reflect/types.typeid:basic:int16" = external constant i8 @"reflect/types.typeid:basic:int16" = external constant i8
@"reflect/types.type:basic:int" = private constant %runtime.typecodeID zeroinitializer @"reflect/types.type:basic:int" = private constant %runtime.typecodeID zeroinitializer
@"reflect/methods.NeverImplementedMethod()" = linkonce_odr constant i8 0 @"reflect/methods.NeverImplementedMethod()" = linkonce_odr constant i8 0
@"Unmatched$interface" = private constant [1 x i8*] [i8* @"reflect/methods.NeverImplementedMethod()"]
@"reflect/methods.Double() int" = linkonce_odr constant i8 0 @"reflect/methods.Double() int" = linkonce_odr constant i8 0
@"Doubler$interface" = private constant [1 x i8*] [i8* @"reflect/methods.Double() int"] @"Number$methodset" = private constant [1 x %runtime.interfaceMethodInfo] [%runtime.interfaceMethodInfo { i8* @"reflect/methods.Double() int", i32 ptrtoint (i32 (i8*, i8*, i8*)* @"(Number).Double$invoke" to i32) }]
@"Number$methodset" = private constant [1 x %runtime.interfaceMethodInfo] [%runtime.interfaceMethodInfo { i8* @"reflect/methods.Double() int", i32 ptrtoint (i32 (i8*, i8*)* @"(Number).Double$invoke" to i32) }] @"reflect/types.type:named:Number" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0, %runtime.interfaceMethodInfo* getelementptr inbounds ([1 x %runtime.interfaceMethodInfo], [1 x %runtime.interfaceMethodInfo]* @"Number$methodset", i32 0, i32 0), %runtime.typecodeID* null, i32 0 }
@"reflect/types.type:named:Number" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0, %runtime.interfaceMethodInfo* getelementptr inbounds ([1 x %runtime.interfaceMethodInfo], [1 x %runtime.interfaceMethodInfo]* @"Number$methodset", i32 0, i32 0) }
declare i1 @runtime.interfaceImplements(i32, i8**)
declare i1 @runtime.typeAssert(i32, i8*) declare i1 @runtime.typeAssert(i32, i8*)
declare i32 @runtime.interfaceMethod(i32, i8**, i8*)
declare void @runtime.printuint8(i8) declare void @runtime.printuint8(i8)
declare void @runtime.printint16(i16) declare void @runtime.printint16(i16)
declare void @runtime.printint32(i32) declare void @runtime.printint32(i32)
@ -34,7 +30,7 @@ define void @printInterfaces() {
} }
define void @printInterface(i32 %typecode, i8* %value) { define void @printInterface(i32 %typecode, i8* %value) {
%isUnmatched = call i1 @runtime.interfaceImplements(i32 %typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"Unmatched$interface", i32 0, i32 0)) %isUnmatched = call i1 @Unmatched$typeassert(i32 %typecode)
br i1 %isUnmatched, label %typeswitch.Unmatched, label %typeswitch.notUnmatched br i1 %isUnmatched, label %typeswitch.Unmatched, label %typeswitch.notUnmatched
typeswitch.Unmatched: typeswitch.Unmatched:
@ -44,13 +40,11 @@ typeswitch.Unmatched:
ret void ret void
typeswitch.notUnmatched: typeswitch.notUnmatched:
%isDoubler = call i1 @runtime.interfaceImplements(i32 %typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"Doubler$interface", i32 0, i32 0)) %isDoubler = call i1 @Doubler$typeassert(i32 %typecode)
br i1 %isDoubler, label %typeswitch.Doubler, label %typeswitch.notDoubler br i1 %isDoubler, label %typeswitch.Doubler, label %typeswitch.notDoubler
typeswitch.Doubler: typeswitch.Doubler:
%doubler.func = call i32 @runtime.interfaceMethod(i32 %typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"Doubler$interface", i32 0, i32 0), i8* nonnull @"reflect/methods.Double() int") %doubler.result = call i32 @"Doubler.Double$invoke"(i8* %value, i32 %typecode, i8* undef, i8* undef)
%doubler.func.cast = inttoptr i32 %doubler.func to i32 (i8*, i8*)*
%doubler.result = call i32 %doubler.func.cast(i8* %value, i8* null)
call void @runtime.printint32(i32 %doubler.result) call void @runtime.printint32(i32 %doubler.result)
ret void ret void
@ -79,13 +73,23 @@ typeswitch.notInt16:
ret void ret void
} }
define i32 @"(Number).Double"(i32 %receiver, i8* %parentHandle) { define i32 @"(Number).Double"(i32 %receiver, i8* %context, i8* %parentHandle) {
%ret = mul i32 %receiver, 2 %ret = mul i32 %receiver, 2
ret i32 %ret ret i32 %ret
} }
define i32 @"(Number).Double$invoke"(i8* %receiverPtr, i8* %parentHandle) { define i32 @"(Number).Double$invoke"(i8* %receiverPtr, i8* %context, i8* %parentHandle) {
%receiver = ptrtoint i8* %receiverPtr to i32 %receiver = ptrtoint i8* %receiverPtr to i32
%ret = call i32 @"(Number).Double"(i32 %receiver, i8* null) %ret = call i32 @"(Number).Double"(i32 %receiver, i8* undef, i8* null)
ret i32 %ret ret i32 %ret
} }
declare i32 @"Doubler.Double$invoke"(i8* %receiver, i32 %typecode, i8* %context, i8* %parentHandle) #0
declare i1 @Doubler$typeassert(i32 %typecode) #1
declare i1 @Unmatched$typeassert(i32 %typecode) #2
attributes #0 = { "tinygo-invoke"="reflect/methods.Double() int" "tinygo-methods"="reflect/methods.Double() int" }
attributes #1 = { "tinygo-methods"="reflect/methods.Double() int" }
attributes #2 = { "tinygo-methods"="reflect/methods.NeverImplementedMethod()" }

40
transform/testdata/interface.out.ll предоставленный
Просмотреть файл

@ -1,12 +1,12 @@
target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64" target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7m-none-eabi" target triple = "armv7m-none-eabi"
%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo* } %runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo*, %runtime.typecodeID*, i32 }
%runtime.interfaceMethodInfo = type { i8*, i32 } %runtime.interfaceMethodInfo = type { i8*, i32 }
@"reflect/types.type:basic:uint8" = private constant %runtime.typecodeID zeroinitializer @"reflect/types.type:basic:uint8" = private constant %runtime.typecodeID zeroinitializer
@"reflect/types.type:basic:int" = private constant %runtime.typecodeID zeroinitializer @"reflect/types.type:basic:int" = private constant %runtime.typecodeID zeroinitializer
@"reflect/types.type:named:Number" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0, %runtime.interfaceMethodInfo* null } @"reflect/types.type:named:Number" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 0 }
declare void @runtime.printuint8(i8) declare void @runtime.printuint8(i8)
@ -28,8 +28,8 @@ define void @printInterfaces() {
} }
define void @printInterface(i32 %typecode, i8* %value) { define void @printInterface(i32 %typecode, i8* %value) {
%typeassert.ok1 = call i1 @"Unmatched$typeassert"(i32 %typecode) %isUnmatched = call i1 @"Unmatched$typeassert"(i32 %typecode)
br i1 %typeassert.ok1, label %typeswitch.Unmatched, label %typeswitch.notUnmatched br i1 %isUnmatched, label %typeswitch.Unmatched, label %typeswitch.notUnmatched
typeswitch.Unmatched: ; preds = %0 typeswitch.Unmatched: ; preds = %0
%unmatched = ptrtoint i8* %value to i32 %unmatched = ptrtoint i8* %value to i32
@ -38,17 +38,17 @@ typeswitch.Unmatched: ; preds = %0
ret void ret void
typeswitch.notUnmatched: ; preds = %0 typeswitch.notUnmatched: ; preds = %0
%typeassert.ok = call i1 @"Doubler$typeassert"(i32 %typecode) %isDoubler = call i1 @"Doubler$typeassert"(i32 %typecode)
br i1 %typeassert.ok, label %typeswitch.Doubler, label %typeswitch.notDoubler br i1 %isDoubler, label %typeswitch.Doubler, label %typeswitch.notDoubler
typeswitch.Doubler: ; preds = %typeswitch.notUnmatched typeswitch.Doubler: ; preds = %typeswitch.notUnmatched
%1 = call i32 @"(Doubler).Double"(i8* %value, i8* null, i32 %typecode, i8* null) %doubler.result = call i32 @"Doubler.Double$invoke"(i8* %value, i32 %typecode, i8* undef, i8* undef)
call void @runtime.printint32(i32 %1) call void @runtime.printint32(i32 %doubler.result)
ret void ret void
typeswitch.notDoubler: ; preds = %typeswitch.notUnmatched typeswitch.notDoubler: ; preds = %typeswitch.notUnmatched
%typeassert.ok2 = icmp eq i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:uint8" to i32), %typecode %typeassert.ok = icmp eq i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:uint8" to i32), %typecode
br i1 %typeassert.ok2, label %typeswitch.byte, label %typeswitch.notByte br i1 %typeassert.ok, label %typeswitch.byte, label %typeswitch.notByte
typeswitch.byte: ; preds = %typeswitch.notDoubler typeswitch.byte: ; preds = %typeswitch.notDoubler
%byte = ptrtoint i8* %value to i8 %byte = ptrtoint i8* %value to i8
@ -69,32 +69,32 @@ typeswitch.notInt16: ; preds = %typeswitch.notByte
ret void ret void
} }
define i32 @"(Number).Double"(i32 %receiver, i8* %parentHandle) { define i32 @"(Number).Double"(i32 %receiver, i8* %context, i8* %parentHandle) {
%ret = mul i32 %receiver, 2 %ret = mul i32 %receiver, 2
ret i32 %ret ret i32 %ret
} }
define i32 @"(Number).Double$invoke"(i8* %receiverPtr, i8* %parentHandle) { define i32 @"(Number).Double$invoke"(i8* %receiverPtr, i8* %context, i8* %parentHandle) {
%receiver = ptrtoint i8* %receiverPtr to i32 %receiver = ptrtoint i8* %receiverPtr to i32
%ret = call i32 @"(Number).Double"(i32 %receiver, i8* null) %ret = call i32 @"(Number).Double"(i32 %receiver, i8* undef, i8* null)
ret i32 %ret ret i32 %ret
} }
define internal i32 @"(Doubler).Double"(i8* %0, i8* %1, i32 %actualType, i8* %parentHandle) unnamed_addr { define internal i32 @"Doubler.Double$invoke"(i8* %receiver, i32 %actualType, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%"named:Number.icmp" = icmp eq i32 %actualType, ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:Number" to i32) %"named:Number.icmp" = icmp eq i32 %actualType, ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:Number" to i32)
br i1 %"named:Number.icmp", label %"named:Number", label %"named:Number.next" br i1 %"named:Number.icmp", label %"named:Number", label %"named:Number.next"
"named:Number": ; preds = %entry "named:Number": ; preds = %entry
%2 = call i32 @"(Number).Double$invoke"(i8* %0, i8* %1) %0 = call i32 @"(Number).Double$invoke"(i8* %receiver, i8* undef, i8* undef)
ret i32 %2 ret i32 %0
"named:Number.next": ; preds = %entry "named:Number.next": ; preds = %entry
call void @runtime.nilPanic(i8* undef, i8* undef) call void @runtime.nilPanic(i8* undef, i8* undef)
unreachable unreachable
} }
define internal i1 @"Doubler$typeassert"(i32 %actualType) unnamed_addr { define internal i1 @"Doubler$typeassert"(i32 %actualType) unnamed_addr #1 {
entry: entry:
%"named:Number.icmp" = icmp eq i32 %actualType, ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:Number" to i32) %"named:Number.icmp" = icmp eq i32 %actualType, ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:Number" to i32)
br i1 %"named:Number.icmp", label %then, label %"named:Number.next" br i1 %"named:Number.icmp", label %then, label %"named:Number.next"
@ -106,10 +106,14 @@ then: ; preds = %entry
ret i1 false ret i1 false
} }
define internal i1 @"Unmatched$typeassert"(i32 %actualType) unnamed_addr { define internal i1 @"Unmatched$typeassert"(i32 %actualType) unnamed_addr #2 {
entry: entry:
ret i1 false ret i1 false
then: ; No predecessors! then: ; No predecessors!
ret i1 true ret i1 true
} }
attributes #0 = { "tinygo-invoke"="reflect/methods.Double() int" "tinygo-methods"="reflect/methods.Double() int" }
attributes #1 = { "tinygo-methods"="reflect/methods.Double() int" }
attributes #2 = { "tinygo-methods"="reflect/methods.NeverImplementedMethod()" }

26
transform/testdata/reflect-implements.ll предоставленный
Просмотреть файл

@ -1,24 +1,20 @@
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128" target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux" target triple = "i686--linux"
%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo* } %runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo*, %runtime.typecodeID*, i32 }
%runtime.interfaceMethodInfo = type { i8*, i32 } %runtime.interfaceMethodInfo = type { i8*, i32 }
@"reflect/types.type:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null } @"reflect/types.type:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 ptrtoint (i1 (i32)* @"error.$typeassert" to i32) }
@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{Error() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null } @"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{Error() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 ptrtoint (i1 (i32)* @"error.$typeassert" to i32) }
@"reflect/methods.Error() string" = linkonce_odr constant i8 0 @"reflect/methods.Error() string" = linkonce_odr constant i8 0
@"reflect/types.interface:interface{Error() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.Error() string"] @"reflect/types.interface:interface{Error() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.Error() string"]
@"reflect/methods.Align() int" = linkonce_odr constant i8 0 @"reflect/methods.Align() int" = linkonce_odr constant i8 0
@"reflect/methods.Implements(reflect.Type) bool" = linkonce_odr constant i8 0 @"reflect/methods.Implements(reflect.Type) bool" = linkonce_odr constant i8 0
@"reflect.Type$interface" = linkonce_odr constant [2 x i8*] [i8* @"reflect/methods.Align() int", i8* @"reflect/methods.Implements(reflect.Type) bool"] @"reflect.Type$interface" = linkonce_odr constant [2 x i8*] [i8* @"reflect/methods.Align() int", i8* @"reflect/methods.Implements(reflect.Type) bool"]
@"reflect/types.type:named:reflect.rawType" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:uintptr", i32 0, %runtime.interfaceMethodInfo* getelementptr inbounds ([20 x %runtime.interfaceMethodInfo], [20 x %runtime.interfaceMethodInfo]* @"reflect.rawType$methodset", i32 0, i32 0) } @"reflect/types.type:named:reflect.rawType" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:uintptr", i32 0, %runtime.interfaceMethodInfo* getelementptr inbounds ([20 x %runtime.interfaceMethodInfo], [20 x %runtime.interfaceMethodInfo]* @"reflect.rawType$methodset", i32 0, i32 0), %runtime.typecodeID* null, i32 0 }
@"reflect.rawType$methodset" = linkonce_odr constant [20 x %runtime.interfaceMethodInfo] zeroinitializer @"reflect.rawType$methodset" = linkonce_odr constant [20 x %runtime.interfaceMethodInfo] zeroinitializer
@"reflect/types.type:basic:uintptr" = linkonce_odr constant %runtime.typecodeID zeroinitializer @"reflect/types.type:basic:uintptr" = linkonce_odr constant %runtime.typecodeID zeroinitializer
declare i1 @runtime.interfaceImplements(i32, i8**, i8*, i8*)
declare i32 @runtime.interfaceMethod(i32, i8**, i8*, i8*, i8*)
; var errorType = reflect.TypeOf((*error)(nil)).Elem() ; var errorType = reflect.TypeOf((*error)(nil)).Elem()
; func isError(typ reflect.Type) bool { ; func isError(typ reflect.Type) bool {
; return typ.Implements(errorType) ; return typ.Implements(errorType)
@ -28,9 +24,7 @@ declare i32 @runtime.interfaceMethod(i32, i8**, i8*, i8*, i8*)
; known at compile time (after the interp pass has run). ; known at compile time (after the interp pass has run).
define i1 @main.isError(i32 %typ.typecode, i8* %typ.value, i8* %context, i8* %parentHandle) { define i1 @main.isError(i32 %typ.typecode, i8* %typ.value, i8* %context, i8* %parentHandle) {
entry: entry:
%invoke.func = call i32 @runtime.interfaceMethod(i32 %typ.typecode, i8** getelementptr inbounds ([2 x i8*], [2 x i8*]* @"reflect.Type$interface", i32 0, i32 0), i8* nonnull @"reflect/methods.Implements(reflect.Type) bool", i8* undef, i8* null) %result = call i1 @"reflect.Type.Implements$invoke"(i8* %typ.value, i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:reflect.rawType" to i32), i8* bitcast (%runtime.typecodeID* @"reflect/types.type:named:error" to i8*), i32 %typ.typecode, i8* undef, i8* undef)
%invoke.func.cast = inttoptr i32 %invoke.func to i1 (i8*, i32, i8*, i8*, i8*)*
%result = call i1 %invoke.func.cast(i8* %typ.value, i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:reflect.rawType" to i32), i8* bitcast (%runtime.typecodeID* @"reflect/types.type:named:error" to i8*), i8* undef, i8* undef)
ret i1 %result ret i1 %result
} }
@ -41,8 +35,12 @@ entry:
; } ; }
define i1 @main.isUnknown(i32 %typ.typecode, i8* %typ.value, i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) { define i1 @main.isUnknown(i32 %typ.typecode, i8* %typ.value, i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) {
entry: entry:
%invoke.func = call i32 @runtime.interfaceMethod(i32 %typ.typecode, i8** getelementptr inbounds ([2 x i8*], [2 x i8*]* @"reflect.Type$interface", i32 0, i32 0), i8* nonnull @"reflect/methods.Implements(reflect.Type) bool", i8* undef, i8* null) %result = call i1 @"reflect.Type.Implements$invoke"(i8* %typ.value, i32 %itf.typecode, i8* %itf.value, i32 %typ.typecode, i8* undef, i8* undef)
%invoke.func.cast = inttoptr i32 %invoke.func to i1 (i8*, i32, i8*, i8*, i8*)*
%result = call i1 %invoke.func.cast(i8* %typ.value, i32 %itf.typecode, i8* %itf.value, i8* undef, i8* undef)
ret i1 %result ret i1 %result
} }
declare i1 @"reflect.Type.Implements$invoke"(i8*, i32, i8*, i32, i8*, i8*) #0
declare i1 @"error.$typeassert"(i32) #1
attributes #0 = { "tinygo-invoke"="reflect/methods.Implements(reflect.Type) bool" "tinygo-methods"="reflect/methods.Align() int; reflect/methods.Implements(reflect.Type) bool" }
attributes #1 = { "tinygo-methods"="reflect/methods.Error() string" }

25
transform/testdata/reflect-implements.out.ll предоставленный
Просмотреть файл

@ -1,35 +1,36 @@
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128" target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux" target triple = "i686--linux"
%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo* } %runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo*, %runtime.typecodeID*, i32 }
%runtime.interfaceMethodInfo = type { i8*, i32 } %runtime.interfaceMethodInfo = type { i8*, i32 }
@"reflect/types.type:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null } @"reflect/types.type:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 ptrtoint (i1 (i32)* @"error.$typeassert" to i32) }
@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{Error() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null } @"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{Error() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 ptrtoint (i1 (i32)* @"error.$typeassert" to i32) }
@"reflect/methods.Error() string" = linkonce_odr constant i8 0 @"reflect/methods.Error() string" = linkonce_odr constant i8 0
@"reflect/types.interface:interface{Error() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.Error() string"] @"reflect/types.interface:interface{Error() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.Error() string"]
@"reflect/methods.Align() int" = linkonce_odr constant i8 0 @"reflect/methods.Align() int" = linkonce_odr constant i8 0
@"reflect/methods.Implements(reflect.Type) bool" = linkonce_odr constant i8 0 @"reflect/methods.Implements(reflect.Type) bool" = linkonce_odr constant i8 0
@"reflect.Type$interface" = linkonce_odr constant [2 x i8*] [i8* @"reflect/methods.Align() int", i8* @"reflect/methods.Implements(reflect.Type) bool"] @"reflect.Type$interface" = linkonce_odr constant [2 x i8*] [i8* @"reflect/methods.Align() int", i8* @"reflect/methods.Implements(reflect.Type) bool"]
@"reflect/types.type:named:reflect.rawType" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:uintptr", i32 0, %runtime.interfaceMethodInfo* getelementptr inbounds ([20 x %runtime.interfaceMethodInfo], [20 x %runtime.interfaceMethodInfo]* @"reflect.rawType$methodset", i32 0, i32 0) } @"reflect/types.type:named:reflect.rawType" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:uintptr", i32 0, %runtime.interfaceMethodInfo* getelementptr inbounds ([20 x %runtime.interfaceMethodInfo], [20 x %runtime.interfaceMethodInfo]* @"reflect.rawType$methodset", i32 0, i32 0), %runtime.typecodeID* null, i32 0 }
@"reflect.rawType$methodset" = linkonce_odr constant [20 x %runtime.interfaceMethodInfo] zeroinitializer @"reflect.rawType$methodset" = linkonce_odr constant [20 x %runtime.interfaceMethodInfo] zeroinitializer
@"reflect/types.type:basic:uintptr" = linkonce_odr constant %runtime.typecodeID zeroinitializer @"reflect/types.type:basic:uintptr" = linkonce_odr constant %runtime.typecodeID zeroinitializer
declare i1 @runtime.interfaceImplements(i32, i8**, i8*, i8*)
declare i32 @runtime.interfaceMethod(i32, i8**, i8*, i8*, i8*)
define i1 @main.isError(i32 %typ.typecode, i8* %typ.value, i8* %context, i8* %parentHandle) { define i1 @main.isError(i32 %typ.typecode, i8* %typ.value, i8* %context, i8* %parentHandle) {
entry: entry:
%0 = ptrtoint i8* %typ.value to i32 %0 = ptrtoint i8* %typ.value to i32
%1 = call i1 @runtime.interfaceImplements(i32 %0, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"reflect/types.interface:interface{Error() string}$interface", i32 0, i32 0), i8* undef, i8* undef) %1 = call i1 @"error.$typeassert"(i32 %0)
ret i1 %1 ret i1 %1
} }
define i1 @main.isUnknown(i32 %typ.typecode, i8* %typ.value, i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) { define i1 @main.isUnknown(i32 %typ.typecode, i8* %typ.value, i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) {
entry: entry:
%invoke.func = call i32 @runtime.interfaceMethod(i32 %typ.typecode, i8** getelementptr inbounds ([2 x i8*], [2 x i8*]* @"reflect.Type$interface", i32 0, i32 0), i8* nonnull @"reflect/methods.Implements(reflect.Type) bool", i8* undef, i8* null) %result = call i1 @"reflect.Type.Implements$invoke"(i8* %typ.value, i32 %itf.typecode, i8* %itf.value, i32 %typ.typecode, i8* undef, i8* undef)
%invoke.func.cast = inttoptr i32 %invoke.func to i1 (i8*, i32, i8*, i8*, i8*)*
%result = call i1 %invoke.func.cast(i8* %typ.value, i32 %itf.typecode, i8* %itf.value, i8* undef, i8* undef)
ret i1 %result ret i1 %result
} }
declare i1 @"reflect.Type.Implements$invoke"(i8*, i32, i8*, i32, i8*, i8*) #0
declare i1 @"error.$typeassert"(i32) #1
attributes #0 = { "tinygo-invoke"="reflect/methods.Implements(reflect.Type) bool" "tinygo-methods"="reflect/methods.Align() int; reflect/methods.Implements(reflect.Type) bool" }
attributes #1 = { "tinygo-methods"="reflect/methods.Error() string" }

Просмотреть файл

@ -38,6 +38,12 @@ func ExternalInt64AsPtr(mod llvm.Module) error {
// coroutine lowering. // coroutine lowering.
continue continue
} }
if !fn.GetStringAttributeAtIndex(-1, "tinygo-methods").IsNil() {
// These are internal functions (interface method call, interface
// type assert) that will be lowered by the interface lowering pass.
// Don't transform them.
continue
}
hasInt64 := false hasInt64 := false
paramTypes := []llvm.Type{} paramTypes := []llvm.Type{}