Compare commits

...

1 Commits

Author SHA1 Message Date
Ayke van Laethem 19e2d12517 compiler: reimplement interface type asserts
This is a big reimplementation that simplifies the compiler a lot.
Instead of storing the method set in metadata and lowering the type
asserts as a whole program pass, this change just puts the list of
methods in the type code (and a separate global for the interface type).
2024-07-31 19:55:18 +02:00
18 changed files with 467 additions and 436 deletions
+90 -27
View File
@@ -10,6 +10,7 @@ import (
"fmt"
"go/token"
"go/types"
"sort"
"strconv"
"strings"
@@ -180,6 +181,15 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
typeFieldTypes := []*types.Var{
types.NewVar(token.NoPos, nil, "kind", types.Typ[types.Int8]),
}
var methods []*types.Func
for i := 0; i < ms.Len(); i++ {
methods = append(methods, ms.At(i).Obj().(*types.Func))
}
methodSetType := types.NewStruct([]*types.Var{
types.NewVar(token.NoPos, nil, "length", types.Typ[types.Uintptr]),
types.NewVar(token.NoPos, nil, "methods", types.NewArray(types.Typ[types.UnsafePointer], int64(len(methods)))),
}, nil)
methodSetValue := c.getMethodSetValue(methods)
switch typ := typ.(type) {
case *types.Basic:
typeFieldTypes = append(typeFieldTypes,
@@ -196,6 +206,7 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]),
types.NewVar(token.NoPos, nil, "underlying", types.Typ[types.UnsafePointer]),
types.NewVar(token.NoPos, nil, "pkgpath", types.Typ[types.UnsafePointer]),
types.NewVar(token.NoPos, nil, "methods", methodSetType),
types.NewVar(token.NoPos, nil, "name", types.NewArray(types.Typ[types.Int8], int64(len(pkgname)+1+len(name)+1))),
)
case *types.Chan:
@@ -214,6 +225,7 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
typeFieldTypes = append(typeFieldTypes,
types.NewVar(token.NoPos, nil, "numMethods", types.Typ[types.Uint16]),
types.NewVar(token.NoPos, nil, "elementType", types.Typ[types.UnsafePointer]),
types.NewVar(token.NoPos, nil, "methods", methodSetType),
)
case *types.Array:
typeFieldTypes = append(typeFieldTypes,
@@ -238,12 +250,13 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
types.NewVar(token.NoPos, nil, "size", types.Typ[types.Uint32]),
types.NewVar(token.NoPos, nil, "numFields", types.Typ[types.Uint16]),
types.NewVar(token.NoPos, nil, "fields", types.NewArray(c.getRuntimeType("structField"), int64(typ.NumFields()))),
types.NewVar(token.NoPos, nil, "methods", methodSetType),
)
case *types.Interface:
typeFieldTypes = append(typeFieldTypes,
types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]),
types.NewVar(token.NoPos, nil, "methods", methodSetType),
)
// TODO: methods
case *types.Signature:
typeFieldTypes = append(typeFieldTypes,
types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]),
@@ -294,6 +307,7 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
c.getTypeCode(types.NewPointer(typ)), // ptrTo
c.getTypeCode(typ.Underlying()), // underlying
pkgPathPtr, // pkgpath pointer
methodSetValue, // methods
c.ctx.ConstString(pkgname+"."+name+"\x00", false), // name
}
metabyte |= 1 << 5 // "named" flag
@@ -323,6 +337,7 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
typeFields = []llvm.Value{
llvm.ConstInt(c.ctx.Int16Type(), uint64(numMethods), false), // numMethods
c.getTypeCode(typ.Elem()),
methodSetValue, // methods
}
case *types.Array:
typeFields = []llvm.Value{
@@ -404,9 +419,12 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
}))
}
typeFields = append(typeFields, llvm.ConstArray(structFieldType, fields))
typeFields = append(typeFields, methodSetValue)
case *types.Interface:
typeFields = []llvm.Value{c.getTypeCode(types.NewPointer(typ))}
// TODO: methods
typeFields = []llvm.Value{
c.getTypeCode(types.NewPointer(typ)),
methodSetValue,
}
case *types.Signature:
typeFields = []llvm.Value{c.getTypeCode(types.NewPointer(typ))}
// TODO: params, return values, etc
@@ -685,7 +703,7 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
assertedType := b.getLLVMType(expr.AssertedType)
actualTypeNum := b.CreateExtractValue(itf, 0, "interface.type")
commaOk := llvm.Value{}
var commaOk llvm.Value
if intf, ok := expr.AssertedType.Underlying().(*types.Interface); ok {
if intf.Empty() {
@@ -693,17 +711,16 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
// This type assertion always succeeds, so we can just set commaOk to true.
commaOk = llvm.ConstInt(b.ctx.Int1Type(), 1, true)
} else {
// Type assert on interface type with methods.
// This is a call to an interface type assert function.
// The interface lowering pass will define this function by filling it
// 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
// the main Go compiler, where the runtime checks whether the type
// implements each method of the interface. See:
// Type assert using interface type with methods.
// This is implemented using a runtime call, which checks that the
// type implements each method of the interface.
// For comparison, here is how the Go compiler does this (which is
// very similar):
// https://research.swtch.com/interfaces
fn := b.getInterfaceImplementsFunc(expr.AssertedType)
commaOk = b.CreateCall(fn.GlobalValueType(), fn, []llvm.Value{actualTypeNum}, "")
commaOk = b.createRuntimeCall("typeImplementsMethodSet", []llvm.Value{
actualTypeNum,
b.getInterfaceMethodSet(intf),
}, "")
}
} else {
name, _ := getTypeCodeName(expr.AssertedType)
@@ -780,20 +797,66 @@ func (c *compilerContext) getMethodsString(itf *types.Interface) string {
return strings.Join(methods, "; ")
}
// getInterfaceImplementsFunc returns a declared function that works as a type
// switch. The interface lowering pass will define this function.
func (c *compilerContext) getInterfaceImplementsFunc(assertedType types.Type) llvm.Value {
s, _ := getTypeCodeName(assertedType.Underlying())
fnName := s + ".$typeassert"
llvmFn := c.mod.NamedFunction(fnName)
if llvmFn.IsNil() {
llvmFnType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.dataPtrType}, false)
llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType)
c.addStandardDeclaredAttributes(llvmFn)
methods := c.getMethodsString(assertedType.Underlying().(*types.Interface))
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-methods", methods))
// Get the global that contains an interface method set, creating it if needed.
func (c *compilerContext) getInterfaceMethodSet(t *types.Interface) llvm.Value {
s, _ := getTypeCodeName(t)
methodSetName := s + "$itfmethods"
methodSet := c.mod.NamedFunction(methodSetName)
if methodSet.IsNil() {
var methods []*types.Func
for i := 0; i < t.NumMethods(); i++ {
methods = append(methods, t.Method(i))
}
if len(methods) == 0 {
// This *should* be unreachable: the caller checks whether the
// interface is empty before creating a method set.
panic("unreachable")
}
methodSetValue := c.getMethodSetValue(methods)
methodSet = llvm.AddGlobal(c.mod, methodSetValue.Type(), methodSetName)
methodSet.SetInitializer(methodSetValue)
methodSet.SetGlobalConstant(true)
methodSet.SetLinkage(llvm.LinkOnceODRLinkage)
methodSet.SetAlignment(c.targetData.ABITypeAlignment(methodSetValue.Type()))
methodSet.SetUnnamedAddr(true)
}
return llvmFn
return methodSet
}
// Get the method set value that is used in a number of type structs.
func (c *compilerContext) getMethodSetValue(methods []*types.Func) llvm.Value {
// Create a sorted list of method names.
var methodRefNames []string
for _, method := range methods {
name := method.Name()
if !token.IsExported(name) {
name = method.Pkg().Path() + "." + name
}
s, _ := getTypeCodeName(method.Type())
methodRefNames = append(methodRefNames, "reflect/types.signature:"+name+":"+s)
}
sort.Strings(methodRefNames)
// Turn this slice of strings in a slice of global variables.
var methodRefValues []llvm.Value
for _, name := range methodRefNames {
value := c.mod.NamedGlobal(name)
if value.IsNil() {
value = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), name)
value.SetInitializer(llvm.ConstNull(c.ctx.Int8Type()))
value.SetGlobalConstant(true)
value.SetLinkage(llvm.LinkOnceODRLinkage)
value.SetAlignment(1)
}
methodRefValues = append(methodRefValues, value)
}
return c.ctx.ConstStruct([]llvm.Value{
llvm.ConstInt(c.uintptrType, uint64(len(methodRefValues)), false),
llvm.ConstArray(c.dataPtrType, methodRefValues),
}, false)
}
// getInvokeFunction returns the thunk to call the given interface method. The
+1 -1
View File
@@ -22,7 +22,7 @@ target triple = "wasm32-unknown-wasi"
@"runtime/gc.layout:62-2000000000000001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\01\00\00\00\00\00\00 " }
@"runtime/gc.layout:62-0001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\01\00\00\00\00\00\00\00" }
@"reflect/types.type:basic:complex128" = linkonce_odr constant { i8, ptr } { i8 80, ptr @"reflect/types.type:pointer:basic:complex128" }, align 4
@"reflect/types.type:pointer:basic:complex128" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:basic:complex128" }, align 4
@"reflect/types.type:pointer:basic:complex128" = linkonce_odr constant { i8, i16, ptr, { i32, [0 x ptr] } } { i8 -43, i16 0, ptr @"reflect/types.type:basic:complex128", { i32, [0 x ptr] } zeroinitializer }, align 4
; Function Attrs: allockind("alloc,zeroed") allocsize(0)
declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0
+31 -31
View File
@@ -7,15 +7,19 @@ target triple = "wasm32-unknown-wasi"
%runtime._string = type { ptr, i32 }
@"reflect/types.type:basic:int" = linkonce_odr constant { i8, ptr } { i8 -62, ptr @"reflect/types.type:pointer:basic:int" }, align 4
@"reflect/types.type:pointer:basic:int" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:basic:int" }, align 4
@"reflect/types.type:pointer:named:error" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:named:error" }, align 4
@"reflect/types.type:named:error" = linkonce_odr constant { i8, i16, ptr, ptr, ptr, [7 x i8] } { i8 116, i16 1, ptr @"reflect/types.type:pointer:named:error", ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}", ptr @"reflect/types.type.pkgpath.empty", [7 x i8] c".error\00" }, align 4
@"reflect/types.type:pointer:basic:int" = linkonce_odr constant { i8, i16, ptr, { i32, [0 x ptr] } } { i8 -43, i16 0, ptr @"reflect/types.type:basic:int", { i32, [0 x ptr] } zeroinitializer }, align 4
@"reflect/types.type:pointer:named:error" = linkonce_odr constant { i8, i16, ptr, { i32, [0 x ptr] } } { i8 -43, i16 0, ptr @"reflect/types.type:named:error", { i32, [0 x ptr] } zeroinitializer }, align 4
@"reflect/types.signature:Error:func:{}{basic:string}" = linkonce_odr constant i8 0, align 1
@"reflect/types.type:named:error" = linkonce_odr constant { i8, i16, ptr, ptr, ptr, { i32, [1 x ptr] }, [7 x i8] } { i8 116, i16 1, ptr @"reflect/types.type:pointer:named:error", ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}", ptr @"reflect/types.type.pkgpath.empty", { i32, [1 x ptr] } { i32 1, [1 x ptr] [ptr @"reflect/types.signature:Error:func:{}{basic:string}"] }, [7 x i8] c".error\00" }, align 4
@"reflect/types.type.pkgpath.empty" = linkonce_odr unnamed_addr constant [1 x i8] zeroinitializer, align 1
@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant { i8, ptr } { i8 84, ptr @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" }, align 4
@"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}" }, align 4
@"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:interface:{String:func:{}{basic:string}}" }, align 4
@"reflect/types.type:interface:{String:func:{}{basic:string}}" = linkonce_odr constant { i8, ptr } { i8 84, ptr @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" }, align 4
@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant { i8, ptr, { i32, [1 x ptr] } } { i8 84, ptr @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}", { i32, [1 x ptr] } { i32 1, [1 x ptr] [ptr @"reflect/types.signature:Error:func:{}{basic:string}"] } }, align 4
@"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant { i8, i16, ptr, { i32, [0 x ptr] } } { i8 -43, i16 0, ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}", { i32, [0 x ptr] } zeroinitializer }, align 4
@"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" = linkonce_odr constant { i8, i16, ptr, { i32, [0 x ptr] } } { i8 -43, i16 0, ptr @"reflect/types.type:interface:{String:func:{}{basic:string}}", { i32, [0 x ptr] } zeroinitializer }, align 4
@"reflect/types.signature:String:func:{}{basic:string}" = linkonce_odr constant i8 0, align 1
@"reflect/types.type:interface:{String:func:{}{basic:string}}" = linkonce_odr constant { i8, ptr, { i32, [1 x ptr] } } { i8 84, ptr @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}", { i32, [1 x ptr] } { i32 1, [1 x ptr] [ptr @"reflect/types.signature:String:func:{}{basic:string}"] } }, align 4
@"reflect/types.typeid:basic:int" = external constant i8
@"interface:{Error:func:{}{basic:string}}$itfmethods" = linkonce_odr unnamed_addr constant { i32, [1 x ptr] } { i32 1, [1 x ptr] [ptr @"reflect/types.signature:Error:func:{}{basic:string}"] }, align 4
@"interface:{String:func:{}{basic:string}}$itfmethods" = linkonce_odr unnamed_addr constant { i32, [1 x ptr] } { i32 1, [1 x ptr] [ptr @"reflect/types.signature:String:func:{}{basic:string}"] }, align 4
; Function Attrs: allockind("alloc,zeroed") allocsize(0)
declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0
@@ -32,8 +36,8 @@ entry:
define hidden %runtime._interface @main.simpleType(ptr %context) unnamed_addr #2 {
entry:
%stackalloc = alloca i8, align 1
call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:basic:int", ptr nonnull %stackalloc, ptr undef) #7
call void @runtime.trackPointer(ptr null, ptr nonnull %stackalloc, ptr undef) #7
call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:basic:int", ptr nonnull %stackalloc, ptr undef) #5
call void @runtime.trackPointer(ptr null, ptr nonnull %stackalloc, ptr undef) #5
ret %runtime._interface { ptr @"reflect/types.type:basic:int", ptr null }
}
@@ -41,8 +45,8 @@ entry:
define hidden %runtime._interface @main.pointerType(ptr %context) unnamed_addr #2 {
entry:
%stackalloc = alloca i8, align 1
call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:pointer:basic:int", ptr nonnull %stackalloc, ptr undef) #7
call void @runtime.trackPointer(ptr null, ptr nonnull %stackalloc, ptr undef) #7
call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:pointer:basic:int", ptr nonnull %stackalloc, ptr undef) #5
call void @runtime.trackPointer(ptr null, ptr nonnull %stackalloc, ptr undef) #5
ret %runtime._interface { ptr @"reflect/types.type:pointer:basic:int", ptr null }
}
@@ -50,8 +54,8 @@ entry:
define hidden %runtime._interface @main.interfaceType(ptr %context) unnamed_addr #2 {
entry:
%stackalloc = alloca i8, align 1
call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:pointer:named:error", ptr nonnull %stackalloc, ptr undef) #7
call void @runtime.trackPointer(ptr null, ptr nonnull %stackalloc, ptr undef) #7
call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:pointer:named:error", ptr nonnull %stackalloc, ptr undef) #5
call void @runtime.trackPointer(ptr null, ptr nonnull %stackalloc, ptr undef) #5
ret %runtime._interface { ptr @"reflect/types.type:pointer:named:error", ptr null }
}
@@ -59,15 +63,15 @@ entry:
define hidden %runtime._interface @main.anonymousInterfaceType(ptr %context) unnamed_addr #2 {
entry:
%stackalloc = alloca i8, align 1
call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}", ptr nonnull %stackalloc, ptr undef) #7
call void @runtime.trackPointer(ptr null, ptr nonnull %stackalloc, ptr undef) #7
call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}", ptr nonnull %stackalloc, ptr undef) #5
call void @runtime.trackPointer(ptr null, ptr nonnull %stackalloc, ptr undef) #5
ret %runtime._interface { ptr @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}", ptr null }
}
; Function Attrs: nounwind
define hidden i1 @main.isInt(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #2 {
entry:
%typecode = call i1 @runtime.typeAssert(ptr %itf.typecode, ptr nonnull @"reflect/types.typeid:basic:int", ptr undef) #7
%typecode = call i1 @runtime.typeAssert(ptr %itf.typecode, ptr nonnull @"reflect/types.typeid:basic:int", ptr undef) #5
br i1 %typecode, label %typeassert.ok, label %typeassert.next
typeassert.next: ; preds = %typeassert.ok, %entry
@@ -82,7 +86,7 @@ declare i1 @runtime.typeAssert(ptr, ptr dereferenceable_or_null(1), ptr) #1
; Function Attrs: nounwind
define hidden i1 @main.isError(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #2 {
entry:
%0 = call i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(ptr %itf.typecode) #7
%0 = call i1 @runtime.typeImplementsMethodSet(ptr %itf.typecode, ptr nonnull @"interface:{Error:func:{}{basic:string}}$itfmethods", ptr undef) #5
br i1 %0, label %typeassert.ok, label %typeassert.next
typeassert.next: ; preds = %typeassert.ok, %entry
@@ -92,12 +96,12 @@ typeassert.ok: ; preds = %entry
br label %typeassert.next
}
declare i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(ptr) #3
declare i1 @runtime.typeImplementsMethodSet(ptr, ptr, ptr) #1
; Function Attrs: nounwind
define hidden i1 @main.isStringer(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #2 {
entry:
%0 = call i1 @"interface:{String:func:{}{basic:string}}.$typeassert"(ptr %itf.typecode) #7
%0 = call i1 @runtime.typeImplementsMethodSet(ptr %itf.typecode, ptr nonnull @"interface:{String:func:{}{basic:string}}$itfmethods", ptr undef) #5
br i1 %0, label %typeassert.ok, label %typeassert.next
typeassert.next: ; preds = %typeassert.ok, %entry
@@ -107,34 +111,30 @@ typeassert.ok: ; preds = %entry
br label %typeassert.next
}
declare i1 @"interface:{String:func:{}{basic:string}}.$typeassert"(ptr) #4
; Function Attrs: nounwind
define hidden i8 @main.callFooMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #2 {
entry:
%0 = call i8 @"interface:{String:func:{}{basic:string},main.foo:func:{basic:int}{basic:uint8}}.foo$invoke"(ptr %itf.value, i32 3, ptr %itf.typecode, ptr undef) #7
%0 = call i8 @"interface:{String:func:{}{basic:string},main.foo:func:{basic:int}{basic:uint8}}.foo$invoke"(ptr %itf.value, i32 3, ptr %itf.typecode, ptr undef) #5
ret i8 %0
}
declare i8 @"interface:{String:func:{}{basic:string},main.foo:func:{basic:int}{basic:uint8}}.foo$invoke"(ptr, i32, ptr, ptr) #5
declare i8 @"interface:{String:func:{}{basic:string},main.foo:func:{basic:int}{basic:uint8}}.foo$invoke"(ptr, i32, ptr, ptr) #3
; Function Attrs: nounwind
define hidden %runtime._string @main.callErrorMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #2 {
entry:
%stackalloc = alloca i8, align 1
%0 = call %runtime._string @"interface:{Error:func:{}{basic:string}}.Error$invoke"(ptr %itf.value, ptr %itf.typecode, ptr undef) #7
%0 = call %runtime._string @"interface:{Error:func:{}{basic:string}}.Error$invoke"(ptr %itf.value, ptr %itf.typecode, ptr undef) #5
%1 = extractvalue %runtime._string %0, 0
call void @runtime.trackPointer(ptr %1, ptr nonnull %stackalloc, ptr undef) #7
call void @runtime.trackPointer(ptr %1, ptr nonnull %stackalloc, ptr undef) #5
ret %runtime._string %0
}
declare %runtime._string @"interface:{Error:func:{}{basic:string}}.Error$invoke"(ptr, ptr, ptr) #6
declare %runtime._string @"interface:{Error:func:{}{basic:string}}.Error$invoke"(ptr, ptr, ptr) #4
attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" }
attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" }
attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" }
attributes #3 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "tinygo-methods"="reflect/methods.Error() string" }
attributes #4 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "tinygo-methods"="reflect/methods.String() string" }
attributes #5 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "tinygo-invoke"="main.$methods.foo(int) uint8" "tinygo-methods"="reflect/methods.String() string; main.$methods.foo(int) uint8" }
attributes #6 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "tinygo-invoke"="reflect/methods.Error() string" "tinygo-methods"="reflect/methods.Error() string" }
attributes #7 = { nounwind }
attributes #3 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "tinygo-invoke"="main.$methods.foo(int) uint8" "tinygo-methods"="reflect/methods.String() string; main.$methods.foo(int) uint8" }
attributes #4 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "tinygo-invoke"="reflect/methods.Error() string" "tinygo-methods"="reflect/methods.Error() string" }
attributes #5 = { nounwind }
-58
View File
@@ -421,64 +421,6 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// Special function that will trigger an error.
// This is used to test error reporting.
return nil, mem, r.errorAt(inst, errors.New("test error"))
case strings.HasSuffix(callFn.name, ".$typeassert"):
if r.debug {
fmt.Fprintln(os.Stderr, indent+"interface assert:", operands[1:])
}
// Load various values for the interface implements check below.
typecodePtr, err := operands[1].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
// typecodePtr always point to the numMethod field in the type
// description struct. The methodSet, when present, comes right
// before the numMethod field (the compiler doesn't generate
// method sets for concrete types without methods).
// Considering that the compiler doesn't emit interface type
// asserts for interfaces with no methods (as the always succeed)
// then if the offset is zero, this assert must always fail.
if typecodePtr.offset() == 0 {
locals[inst.localIndex] = literalValue{uint8(0)}
break
}
typecodePtrOffset, err := typecodePtr.addOffset(-int64(r.pointerSize))
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
methodSetPtr, err := mem.load(typecodePtrOffset, r.pointerSize).asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
methodSet := mem.get(methodSetPtr.index()).llvmGlobal.Initializer()
numMethods := int(r.builder.CreateExtractValue(methodSet, 0, "").ZExtValue())
llvmFn := inst.llvmInst.CalledValue()
methodSetAttr := llvmFn.GetStringAttributeAtIndex(-1, "tinygo-methods")
methodSetString := methodSetAttr.GetStringValue()
// Make a set of all the methods on the concrete type, for
// easier checking in the next step.
concreteTypeMethods := map[string]struct{}{}
for i := 0; i < numMethods; i++ {
methodInfo := r.builder.CreateExtractValue(methodSet, 1, "")
name := r.builder.CreateExtractValue(methodInfo, i, "").Name()
concreteTypeMethods[name] = struct{}{}
}
// Check whether all interface methods are also in the list
// of defined methods calculated above. This is the interface
// assert itself.
assertOk := uint8(1) // i1 true
for _, name := range strings.Split(methodSetString, "; ") {
if _, ok := concreteTypeMethods[name]; !ok {
// There is a method on the interface that is not
// implemented by the type. The assertion will fail.
assertOk = 0 // i1 false
break
}
}
// If assertOk is still 1, the assertion succeeded.
locals[inst.localIndex] = literalValue{assertOk}
case strings.HasSuffix(callFn.name, "$invoke"):
// This thunk is the interface method dispatcher: it is called
// with all regular parameters and a type code. It will then
+83
View File
@@ -0,0 +1,83 @@
package reflect
// This file defines functions that logically belong in the runtime but that
// depend on the internals of the reflect package and are therefore better
// implemented directly in the reflect package.
import (
"unsafe"
)
// Return whether the concrete type implements the asserted method set.
// This function is called both from the compiler and from the reflect package.
//
//go:linkname typeImplementsMethodSet runtime.typeImplementsMethodSet
func typeImplementsMethodSet(concreteType, assertedMethodSet unsafe.Pointer) bool {
if concreteType == nil {
// This can happen when doing a type assert on a nil interface value.
// According to the Go language specification, a type assert on a nil
// interface always fails.
return false
}
const ptrSize = unsafe.Sizeof((*byte)(nil))
itfNumMethod := *(*uintptr)(assertedMethodSet)
if itfNumMethod == 0 {
// While the compiler shouldn't emit such type asserts, the reflect
// package may do so.
// This is actually needed for correctness: for example a value of type
// int may be assigned to a variable of type interface{}.
return true
}
// Pull the method set out of the concrete type.
var methods *methodSet
metaByte := *(*uint8)(concreteType)
if metaByte&flagNamed != 0 {
concreteType := (*namedType)(concreteType)
methods = &concreteType.methods
} else if metaByte&kindMask == uint8(Struct) {
concreteType := (*structType)(concreteType)
methods = &concreteType.methods
} else if metaByte&kindMask == uint8(Pointer) {
concreteType := (*ptrType)(concreteType)
methods = &concreteType.methods
} else {
// Other types don't have a method set.
return false
}
concreteTypePtr := unsafe.Pointer(&methods.methods)
concreteTypeEnd := unsafe.Add(concreteTypePtr, uintptr(methods.length)*ptrSize)
// Iterate over each method in the interface method set, and check whether
// the method exists in the method set of the concrete type.
// Both method sets are sorted in the same way, so we can use a simple loop
// to check for a match.
assertedTypePtr := unsafe.Add(assertedMethodSet, ptrSize)
assertedTypeEnd := unsafe.Add(assertedTypePtr, itfNumMethod*ptrSize)
for assertedTypePtr != assertedTypeEnd {
assertedMethod := *(*unsafe.Pointer)(assertedTypePtr)
// Search for the same method in the concrete type.
for {
if concreteTypePtr == concreteTypeEnd {
// Reached the end of the concrete type method set.
// The method wasn't found, so the type assert failed.
return false
}
concreteMethod := *(*unsafe.Pointer)(concreteTypePtr)
concreteTypePtr = unsafe.Add(concreteTypePtr, ptrSize)
if concreteMethod == assertedMethod {
// Found the method in the concrete type. Continue with the
// next in the interface method set.
break
}
}
// The method was found. Continue with the next.
assertedTypePtr = unsafe.Add(assertedTypePtr, ptrSize)
}
// All methods in the interface were found.
return true
}
+229
View File
@@ -0,0 +1,229 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package reflect_test
import (
"bytes"
"go/ast"
"go/token"
"io"
. "reflect"
"testing"
)
//func TestImplicitMapConversion(t *testing.T) {
// // Test implicit conversions in MapIndex and SetMapIndex.
// {
// // direct
// m := make(map[int]int)
// mv := ValueOf(m)
// mv.SetMapIndex(ValueOf(1), ValueOf(2))
// x, ok := m[1]
// if x != 2 {
// t.Errorf("#1 after SetMapIndex(1,2): %d, %t (map=%v)", x, ok, m)
// }
// if n := mv.MapIndex(ValueOf(1)).Interface().(int); n != 2 {
// t.Errorf("#1 MapIndex(1) = %d", n)
// }
// }
// {
// // convert interface key
// m := make(map[any]int)
// mv := ValueOf(m)
// mv.SetMapIndex(ValueOf(1), ValueOf(2))
// x, ok := m[1]
// if x != 2 {
// t.Errorf("#2 after SetMapIndex(1,2): %d, %t (map=%v)", x, ok, m)
// }
// if n := mv.MapIndex(ValueOf(1)).Interface().(int); n != 2 {
// t.Errorf("#2 MapIndex(1) = %d", n)
// }
// }
// {
// // convert interface value
// m := make(map[int]any)
// mv := ValueOf(m)
// mv.SetMapIndex(ValueOf(1), ValueOf(2))
// x, ok := m[1]
// if x != 2 {
// t.Errorf("#3 after SetMapIndex(1,2): %d, %t (map=%v)", x, ok, m)
// }
// if n := mv.MapIndex(ValueOf(1)).Interface().(int); n != 2 {
// t.Errorf("#3 MapIndex(1) = %d", n)
// }
// }
// {
// // convert both interface key and interface value
// m := make(map[any]any)
// mv := ValueOf(m)
// mv.SetMapIndex(ValueOf(1), ValueOf(2))
// x, ok := m[1]
// if x != 2 {
// t.Errorf("#4 after SetMapIndex(1,2): %d, %t (map=%v)", x, ok, m)
// }
// if n := mv.MapIndex(ValueOf(1)).Interface().(int); n != 2 {
// t.Errorf("#4 MapIndex(1) = %d", n)
// }
// }
// {
// // convert both, with non-empty interfaces
// m := make(map[io.Reader]io.Writer)
// mv := ValueOf(m)
// b1 := new(bytes.Buffer)
// b2 := new(bytes.Buffer)
// mv.SetMapIndex(ValueOf(b1), ValueOf(b2))
// x, ok := m[b1]
// if x != b2 {
// t.Errorf("#5 after SetMapIndex(b1, b2): %p (!= %p), %t (map=%v)", x, b2, ok, m)
// }
// if p := mv.MapIndex(ValueOf(b1)).Elem().UnsafePointer(); p != unsafe.Pointer(b2) {
// t.Errorf("#5 MapIndex(b1) = %#x want %p", p, b2)
// }
// }
// {
// // convert channel direction
// m := make(map[<-chan int]chan int)
// mv := ValueOf(m)
// c1 := make(chan int)
// c2 := make(chan int)
// mv.SetMapIndex(ValueOf(c1), ValueOf(c2))
// x, ok := m[c1]
// if x != c2 {
// t.Errorf("#6 after SetMapIndex(c1, c2): %p (!= %p), %t (map=%v)", x, c2, ok, m)
// }
// if p := mv.MapIndex(ValueOf(c1)).UnsafePointer(); p != ValueOf(c2).UnsafePointer() {
// t.Errorf("#6 MapIndex(c1) = %#x want %p", p, c2)
// }
// }
// {
// // convert identical underlying types
// type MyBuffer bytes.Buffer
// m := make(map[*MyBuffer]*bytes.Buffer)
// mv := ValueOf(m)
// b1 := new(MyBuffer)
// b2 := new(bytes.Buffer)
// mv.SetMapIndex(ValueOf(b1), ValueOf(b2))
// x, ok := m[b1]
// if x != b2 {
// t.Errorf("#7 after SetMapIndex(b1, b2): %p (!= %p), %t (map=%v)", x, b2, ok, m)
// }
// if p := mv.MapIndex(ValueOf(b1)).UnsafePointer(); p != unsafe.Pointer(b2) {
// t.Errorf("#7 MapIndex(b1) = %#x want %p", p, b2)
// }
// }
//
//}
//
//func TestImplicitSetConversion(t *testing.T) {
// // Assume TestImplicitMapConversion covered the basics.
// // Just make sure conversions are being applied at all.
// var r io.Reader
// b := new(bytes.Buffer)
// rv := ValueOf(&r).Elem()
// rv.Set(ValueOf(b))
// if r != b {
// t.Errorf("after Set: r=%T(%v)", r, r)
// }
//}
//
//func TestImplicitSendConversion(t *testing.T) {
// c := make(chan io.Reader, 10)
// b := new(bytes.Buffer)
// ValueOf(c).Send(ValueOf(b))
// if bb := <-c; bb != b {
// t.Errorf("Received %p != %p", bb, b)
// }
//}
//
//func TestImplicitCallConversion(t *testing.T) {
// // Arguments must be assignable to parameter types.
// fv := ValueOf(io.WriteString)
// b := new(strings.Builder)
// fv.Call([]Value{ValueOf(b), ValueOf("hello world")})
// if b.String() != "hello world" {
// t.Errorf("After call: string=%q want %q", b.String(), "hello world")
// }
//}
//
//func TestImplicitAppendConversion(t *testing.T) {
// // Arguments must be assignable to the slice's element type.
// s := []io.Reader{}
// sv := ValueOf(&s).Elem()
// b := new(bytes.Buffer)
// sv.Set(Append(sv, ValueOf(b)))
// if len(s) != 1 || s[0] != b {
// t.Errorf("after append: s=%v want [%p]", s, b)
// }
//}
var implementsTests = []struct {
x any
t any
b bool
}{
{new(*bytes.Buffer), new(io.Reader), true},
{new(bytes.Buffer), new(io.Reader), false},
{new(*bytes.Buffer), new(io.ReaderAt), false},
{new(*ast.Ident), new(ast.Expr), true},
{new(*notAnExpr), new(ast.Expr), false},
{new(*ast.Ident), new(notASTExpr), false},
{new(notASTExpr), new(ast.Expr), false},
{new(ast.Expr), new(notASTExpr), false},
{new(*notAnExpr), new(notASTExpr), true},
}
type notAnExpr struct{}
func (notAnExpr) Pos() token.Pos { return token.NoPos }
func (notAnExpr) End() token.Pos { return token.NoPos }
func (notAnExpr) exprNode() {}
type notASTExpr interface {
Pos() token.Pos
End() token.Pos
exprNode()
}
func TestImplements(t *testing.T) {
for _, tt := range implementsTests {
xv := TypeOf(tt.x).Elem()
xt := TypeOf(tt.t).Elem()
if b := xv.Implements(xt); b != tt.b {
t.Errorf("(%s).Implements(%s) = %v, want %v", xv.String(), xt.String(), b, tt.b)
}
}
}
// TODO: AssignableTo should be possible to implement now that Implements()
// mostly works.
//var assignableTests = []struct {
// x any
// t any
// b bool
//}{
// {new(chan int), new(<-chan int), true},
// {new(<-chan int), new(chan int), false},
// {new(*int), new(IntPtr), true},
// {new(IntPtr), new(*int), true},
// {new(IntPtr), new(IntPtr1), false},
// {new(Ch), new(<-chan any), true},
// // test runs implementsTests too
//}
//
//type IntPtr *int
//type IntPtr1 *int
//type Ch <-chan any
//
//func TestAssignableTo(t *testing.T) {
// for _, tt := range append(assignableTests, implementsTests...) {
// xv := TypeOf(tt.x).Elem()
// xt := TypeOf(tt.t).Elem()
// if b := xv.AssignableTo(xt); b != tt.b {
// t.Errorf("(%s).AssignableTo(%s) = %v, want %v", xv.String(), xt.String(), b, tt.b)
// }
// }
//}
//
+24 -3
View File
@@ -23,6 +23,7 @@
// meta uint8
// nmethods uint16
// elementType *typeStruct
// methods methodSet
// - array types (see arrayType)
// meta uint8
// nmethods uint16 (0)
@@ -44,7 +45,8 @@
// pkgpath *byte // package path; null terminated
// numField uint16
// fields [...]structField // the remaining fields are all of type structField
// - interface types (this is missing the interface methods):
// methods methodSet
// - interface types:
// meta uint8
// ptrTo *typeStruct
// - signature types (this is missing input and output parameters):
@@ -56,6 +58,7 @@
// ptrTo *typeStruct
// elem *typeStruct // underlying type
// pkgpath *byte // pkgpath; null terminated
// methods methodSet
// name [1]byte // actual name; null terminated
//
// The type struct is essentially a union of all the above types. Which it is,
@@ -420,6 +423,7 @@ type ptrType struct {
rawType
numMethod uint16
elem *rawType
methods methodSet
}
type arrayType struct {
@@ -445,6 +449,7 @@ type namedType struct {
ptrTo *rawType
elem *rawType
pkg *byte
methods methodSet
name [1]byte
}
@@ -463,6 +468,7 @@ type structType struct {
size uint32
numField uint16
fields [1]structField // the remaining fields are all of type structField
methods methodSet
}
type structField struct {
@@ -470,6 +476,18 @@ type structField struct {
data unsafe.Pointer // various bits of information, packed in a byte array
}
type interfaceType struct {
rawType
ptrTo *rawType
methods methodSet
}
// Method set, as emitted by the compiler.
type methodSet struct {
length uintptr
methods [0]*byte // variable number of methods
}
// Equivalent to (go/types.Type).Underlying(): if this is a named type return
// the underlying type, else just return the type itself.
func (t *rawType) underlying() *rawType {
@@ -972,7 +990,8 @@ func (t *rawType) Implements(u Type) bool {
if u.Kind() != Interface {
panic("reflect: non-interface type passed to Type.Implements")
}
return t.AssignableTo(u)
u_itf := (*interfaceType)(unsafe.Pointer(u.(*rawType).underlying()))
return typeImplementsMethodSet(unsafe.Pointer(t), unsafe.Pointer(&u_itf.methods))
}
// Comparable returns whether values of this type can be compared to each other.
@@ -1046,7 +1065,9 @@ func readStringZ(data unsafe.Pointer) string {
func (t *rawType) name() string {
ntype := (*namedType)(unsafe.Pointer(t))
return readStringZ(unsafe.Pointer(&ntype.name[0]))
ptr := unsafe.Pointer(&ntype.name[0])
ptr = unsafe.Add(ptr, uintptr(ntype.methods.length)*unsafe.Sizeof(unsafe.Pointer(nil)))
return readStringZ(ptr)
}
func (t *rawType) Name() string {
+3
View File
@@ -82,6 +82,9 @@ func reflectValueEqual(x, y reflect.Value) bool {
}
}
// Implemented in the reflect package.
func typeImplementsMethodSet(actualTypeNum, assertedMethodSet unsafe.Pointer) bool
// interfaceTypeAssert is called when a type assert without comma-ok still
// returns false.
func interfaceTypeAssert(ok bool) {
+3
View File
@@ -22,6 +22,7 @@ func main() {
printItf(array)
printItf(ArrayStruct{3, array})
printItf(SmallPair{3, 5})
printItf(nil)
s := Stringer(thing)
println("Stringer.String():", s.String())
var itf interface{} = s
@@ -150,6 +151,8 @@ func printItf(val interface{}) {
println("is struct{n int `foo:\"bar\"`}")
case Foo:
println("is Foo:", val)
case nil:
println("is nil")
default:
println("is ?")
}
+1
View File
@@ -16,6 +16,7 @@ is Tuple: 1 7 11 13
ArrayStruct.Print: 4 3
is Tuple: 0 8 16 24
SmallPair.Print: 3 5
is nil
Stringer.String(): foo
Stringer.(*Thing).String(): foo
s has String() method: foo
-67
View File
@@ -272,13 +272,6 @@ func (p *lowerInterfacesPass) run() error {
p.defineInterfaceMethodFunc(fn, itf, signature)
}
// Define all interface type assert functions.
for _, fn := range interfaceAssertFunctions {
methodsAttr := fn.GetStringAttributeAtIndex(-1, "tinygo-methods")
itf := p.interfaces[methodsAttr.GetStringValue()]
p.defineInterfaceImplementsFunc(fn, itf)
}
// Replace each type assert with an actual type comparison or (if the type
// assert is impossible) the constant false.
llvmFalse := llvm.ConstInt(p.ctx.Int1Type(), 0, false)
@@ -428,66 +421,6 @@ func (p *lowerInterfacesPass) getSignature(name string) *signatureInfo {
return p.signatures[name]
}
// defineInterfaceImplementsFunc defines the interface type assert function. It
// checks whether the given interface type (passed as an argument) is one of the
// types it implements.
//
// 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
// types by the LLVM simplifycfg pass.
func (p *lowerInterfacesPass) defineInterfaceImplementsFunc(fn llvm.Value, itf *interfaceInfo) {
// Create the function and function signature.
fn.Param(0).SetName("actualType")
fn.SetLinkage(llvm.InternalLinkage)
fn.SetUnnamedAddr(true)
AddStandardAttributes(fn, p.config)
// Start the if/else chain at the entry block.
entry := p.ctx.AddBasicBlock(fn, "entry")
thenBlock := p.ctx.AddBasicBlock(fn, "then")
p.builder.SetInsertPointAtEnd(entry)
if p.dibuilder != nil {
difile := p.getDIFile("<Go interface assert>")
diFuncType := p.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
File: difile,
})
difunc := p.dibuilder.CreateFunction(difile, llvm.DIFunction{
Name: "(Go interface assert)",
File: difile,
Line: 0,
Type: diFuncType,
LocalToUnit: true,
IsDefinition: true,
ScopeLine: 0,
Flags: llvm.FlagPrototyped,
Optimized: true,
})
fn.SetSubprogram(difunc)
p.builder.SetCurrentDebugLocation(0, 0, difunc, llvm.Metadata{})
}
// Iterate over all possible types. Each iteration creates a new branch
// either to the 'then' block (success) or the .next block, for the next
// check.
actualType := fn.Param(0)
for _, typ := range itf.types {
nextBlock := p.ctx.AddBasicBlock(fn, typ.name+".next")
cmp := p.builder.CreateICmp(llvm.IntEQ, actualType, typ.typecodeGEP, typ.name+".icmp")
p.builder.CreateCondBr(cmp, thenBlock, nextBlock)
p.builder.SetInsertPointAtEnd(nextBlock)
}
// The builder is now inserting at the last *.next block. Once we reach
// this point, all types have been checked so the type assert will have
// failed.
p.builder.CreateRet(llvm.ConstInt(p.ctx.Int1Type(), 0, false))
// Fill 'then' block (type assert was successful).
p.builder.SetInsertPointAtEnd(thenBlock)
p.builder.CreateRet(llvm.ConstInt(p.ctx.Int1Type(), 1, false))
}
// defineInterfaceMethodFunc defines this thunk by calling the concrete method
// of the type that implements this interface.
//
-1
View File
@@ -65,7 +65,6 @@ func Optimize(mod llvm.Module, config *compileopts.Config) []error {
// Run TinyGo-specific optimization passes.
OptimizeStringToBytes(mod)
OptimizeReflectImplements(mod)
maxStackSize := config.MaxStackAlloc()
OptimizeAllocs(mod, nil, maxStackSize, nil)
err = LowerInterfaces(mod, config)
-78
View File
@@ -4,8 +4,6 @@ package transform
// calls.
import (
"strings"
"tinygo.org/x/go-llvm"
)
@@ -100,79 +98,3 @@ func OptimizeStringEqual(mod llvm.Module) {
}
}
}
// OptimizeReflectImplements optimizes the following code:
//
// implements := someType.Implements(someInterfaceType)
//
// where someType is an arbitrary reflect.Type and someInterfaceType is a
// reflect.Type of interface kind, to the following code:
//
// _, implements := someType.(interfaceType)
//
// if the interface type is known at compile time (that is, someInterfaceType is
// a LLVM constant aggregate). This optimization is especially important for the
// encoding/json package, which uses this method.
//
// As of this writing, the (reflect.Type).Interface method has not yet been
// implemented so this optimization is critical for the encoding/json package.
func OptimizeReflectImplements(mod llvm.Module) {
implementsSignature := mod.NamedGlobal("reflect/methods.Implements(reflect.Type) bool")
if implementsSignature.IsNil() {
return
}
builder := mod.Context().NewBuilder()
defer builder.Dispose()
// Look up the (reflect.Value).Implements() method.
var implementsFunc llvm.Value
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
attr := fn.GetStringAttributeAtIndex(-1, "tinygo-invoke")
if attr.IsNil() {
continue
}
if attr.GetStringValue() == "reflect/methods.Implements(reflect.Type) bool" {
implementsFunc = fn
break
}
}
if implementsFunc.IsNil() {
// Doesn't exist in the program, so nothing to do.
return
}
for _, call := range getUses(implementsFunc) {
if call.IsACallInst().IsNil() {
continue
}
interfaceType := stripPointerCasts(call.Operand(2))
if interfaceType.IsAGlobalVariable().IsNil() {
// Interface is unknown at compile time. This can't be optimized.
continue
}
if strings.HasPrefix(interfaceType.Name(), "reflect/types.type:named:") {
// Get the underlying type.
interfaceType = stripPointerCasts(builder.CreateExtractValue(interfaceType.Initializer(), 3, ""))
}
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
}
typeAssertFunction := mod.NamedFunction(strings.TrimPrefix(interfaceType.Name(), "reflect/types.type:") + ".$typeassert")
if typeAssertFunction.IsNil() {
continue
}
// Replace Implements call with the type assert call.
builder.SetInsertPointBefore(call)
implements := builder.CreateCall(typeAssertFunction.GlobalValueType(), typeAssertFunction, []llvm.Value{
call.Operand(0), // typecode to check
}, "")
call.ReplaceAllUsesWith(implements)
call.EraseFromParentAsInstruction()
}
}
-8
View File
@@ -22,11 +22,3 @@ func TestOptimizeStringEqual(t *testing.T) {
transform.OptimizeStringEqual(mod)
})
}
func TestOptimizeReflectImplements(t *testing.T) {
t.Parallel()
testTransform(t, "testdata/reflect-implements", func(mod llvm.Module) {
// Run optimization pass.
transform.OptimizeReflectImplements(mod)
})
}
-28
View File
@@ -16,10 +16,7 @@ target triple = "armv7m-none-eabi"
declare i1 @runtime.typeAssert(ptr, ptr)
declare void @runtime.printuint8(i8)
declare void @runtime.printint16(i16)
declare void @runtime.printint32(i32)
declare void @runtime.printptr(i32)
declare void @runtime.printnl()
declare void @runtime.nilPanic(ptr)
define void @printInterfaces() {
call void @printInterface(ptr @"reflect/types.type:basic:int", ptr inttoptr (i32 5 to ptr))
@@ -30,25 +27,6 @@ define void @printInterfaces() {
}
define void @printInterface(ptr %typecode, ptr %value) {
%isUnmatched = call i1 @Unmatched$typeassert(ptr %typecode)
br i1 %isUnmatched, label %typeswitch.Unmatched, label %typeswitch.notUnmatched
typeswitch.Unmatched:
%unmatched = ptrtoint ptr %value to i32
call void @runtime.printptr(i32 %unmatched)
call void @runtime.printnl()
ret void
typeswitch.notUnmatched:
%isDoubler = call i1 @Doubler$typeassert(ptr %typecode)
br i1 %isDoubler, label %typeswitch.Doubler, label %typeswitch.notDoubler
typeswitch.Doubler:
%doubler.result = call i32 @"Doubler.Double$invoke"(ptr %value, ptr %typecode, ptr undef)
call void @runtime.printint32(i32 %doubler.result)
ret void
typeswitch.notDoubler:
%isByte = call i1 @runtime.typeAssert(ptr %typecode, ptr nonnull @"reflect/types.typeid:basic:uint8")
br i1 %isByte, label %typeswitch.byte, label %typeswitch.notByte
@@ -86,10 +64,4 @@ define i32 @"(Number).Double$invoke"(ptr %receiverPtr, ptr %context) {
declare i32 @"Doubler.Double$invoke"(ptr %receiver, ptr %typecode, ptr %context) #0
declare i1 @Doubler$typeassert(ptr %typecode) #1
declare i1 @Unmatched$typeassert(ptr %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()" }
+2 -65
View File
@@ -12,14 +12,8 @@ declare void @runtime.printuint8(i8)
declare void @runtime.printint16(i16)
declare void @runtime.printint32(i32)
declare void @runtime.printptr(i32)
declare void @runtime.printnl()
declare void @runtime.nilPanic(ptr)
define void @printInterfaces() {
call void @printInterface(ptr @"reflect/types.type:basic:int", ptr inttoptr (i32 5 to ptr))
call void @printInterface(ptr @"reflect/types.type:basic:uint8", ptr inttoptr (i8 120 to ptr))
@@ -28,35 +22,16 @@ define void @printInterfaces() {
}
define void @printInterface(ptr %typecode, ptr %value) {
%isUnmatched = call i1 @"Unmatched$typeassert"(ptr %typecode)
br i1 %isUnmatched, label %typeswitch.Unmatched, label %typeswitch.notUnmatched
typeswitch.Unmatched: ; preds = %0
%unmatched = ptrtoint ptr %value to i32
call void @runtime.printptr(i32 %unmatched)
call void @runtime.printnl()
ret void
typeswitch.notUnmatched: ; preds = %0
%isDoubler = call i1 @"Doubler$typeassert"(ptr %typecode)
br i1 %isDoubler, label %typeswitch.Doubler, label %typeswitch.notDoubler
typeswitch.Doubler: ; preds = %typeswitch.notUnmatched
%doubler.result = call i32 @"Doubler.Double$invoke"(ptr %value, ptr %typecode, ptr undef)
call void @runtime.printint32(i32 %doubler.result)
ret void
typeswitch.notDoubler: ; preds = %typeswitch.notUnmatched
%typeassert.ok = icmp eq ptr @"reflect/types.type:basic:uint8", %typecode
br i1 %typeassert.ok, label %typeswitch.byte, label %typeswitch.notByte
typeswitch.byte: ; preds = %typeswitch.notDoubler
typeswitch.byte: ; preds = %0
%byte = ptrtoint ptr %value to i8
call void @runtime.printuint8(i8 %byte)
call void @runtime.printnl()
ret void
typeswitch.notByte: ; preds = %typeswitch.notDoubler
typeswitch.notByte: ; preds = %0
br i1 false, label %typeswitch.int16, label %typeswitch.notInt16
typeswitch.int16: ; preds = %typeswitch.notByte
@@ -79,41 +54,3 @@ define i32 @"(Number).Double$invoke"(ptr %receiverPtr, ptr %context) {
%ret = call i32 @"(Number).Double"(i32 %receiver, ptr undef)
ret i32 %ret
}
define internal i32 @"Doubler.Double$invoke"(ptr %receiver, ptr %actualType, ptr %context) unnamed_addr #0 {
entry:
%"named:Number.icmp" = icmp eq ptr %actualType, @"reflect/types.type:named:Number"
br i1 %"named:Number.icmp", label %"named:Number", label %"named:Number.next"
"named:Number": ; preds = %entry
%0 = call i32 @"(Number).Double$invoke"(ptr %receiver, ptr undef)
ret i32 %0
"named:Number.next": ; preds = %entry
call void @runtime.nilPanic(ptr undef)
unreachable
}
define internal i1 @"Doubler$typeassert"(ptr %actualType) unnamed_addr #1 {
entry:
%"named:Number.icmp" = icmp eq ptr %actualType, @"reflect/types.type:named:Number"
br i1 %"named:Number.icmp", label %then, label %"named:Number.next"
then: ; preds = %entry
ret i1 true
"named:Number.next": ; preds = %entry
ret i1 false
}
define internal i1 @"Unmatched$typeassert"(ptr %actualType) unnamed_addr #2 {
entry:
ret i1 false
then: ; No predecessors!
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()" }
-41
View File
@@ -1,41 +0,0 @@
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"
%runtime._interface = type { ptr, ptr }
@"reflect/types.type:named:error" = internal constant { i8, i16, ptr, ptr } { i8 52, i16 0, ptr @"reflect/types.type:pointer:named:error", ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}" }, align 4
@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = internal constant { i8, ptr } { i8 20, ptr @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" }, align 4
@"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = internal constant { i8, ptr } { i8 21, ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}" }, align 4
@"reflect/types.type:pointer:named:error" = internal constant { i8, i16, ptr } { i8 21, i16 0, ptr @"reflect/types.type:named:error" }, align 4
@"reflect/types.type:pointer:named:reflect.rawType" = internal constant { ptr, i8, i16, ptr } { ptr null, i8 21, i16 0, ptr null }, align 4
@"reflect/methods.Implements(reflect.Type) bool" = internal constant i8 0, align 1
; var errorType = reflect.TypeOf((*error)(nil)).Elem()
; func isError(typ reflect.Type) bool {
; return typ.Implements(errorType)
; }
; The type itself is stored in %typ.value, %typ.typecode just refers to the
; type of reflect.Type. This function can be optimized because errorType is
; known at compile time (after the interp pass has run).
define i1 @main.isError(ptr %typ.typecode, ptr %typ.value, ptr %context) {
entry:
%result = call i1 @"reflect.Type.Implements$invoke"(ptr %typ.value, ptr getelementptr inbounds ({ ptr, i8, ptr }, ptr @"reflect/types.type:pointer:named:reflect.rawType", i32 0, i32 1), ptr @"reflect/types.type:named:error", ptr %typ.typecode, ptr undef)
ret i1 %result
}
; This Implements method call can not be optimized because itf is not known at
; compile time.
; func isUnknown(typ, itf reflect.Type) bool {
; return typ.Implements(itf)
; }
define i1 @main.isUnknown(ptr %typ.typecode, ptr %typ.value, ptr %itf.typecode, ptr %itf.value, ptr %context) {
entry:
%result = call i1 @"reflect.Type.Implements$invoke"(ptr %typ.value, ptr %itf.typecode, ptr %itf.value, ptr %typ.typecode, ptr undef)
ret i1 %result
}
declare i1 @"reflect.Type.Implements$invoke"(ptr, ptr, ptr, ptr, ptr) #0
declare i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(ptr %0) #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" }
-28
View File
@@ -1,28 +0,0 @@
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"
@"reflect/types.type:named:error" = internal constant { i8, i16, ptr, ptr } { i8 52, i16 0, ptr @"reflect/types.type:pointer:named:error", ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}" }, align 4
@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = internal constant { i8, ptr } { i8 20, ptr @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" }, align 4
@"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = internal constant { i8, ptr } { i8 21, ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}" }, align 4
@"reflect/types.type:pointer:named:error" = internal constant { i8, i16, ptr } { i8 21, i16 0, ptr @"reflect/types.type:named:error" }, align 4
@"reflect/types.type:pointer:named:reflect.rawType" = internal constant { ptr, i8, i16, ptr } { ptr null, i8 21, i16 0, ptr null }, align 4
@"reflect/methods.Implements(reflect.Type) bool" = internal constant i8 0, align 1
define i1 @main.isError(ptr %typ.typecode, ptr %typ.value, ptr %context) {
entry:
%0 = call i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(ptr %typ.value)
ret i1 %0
}
define i1 @main.isUnknown(ptr %typ.typecode, ptr %typ.value, ptr %itf.typecode, ptr %itf.value, ptr %context) {
entry:
%result = call i1 @"reflect.Type.Implements$invoke"(ptr %typ.value, ptr %itf.typecode, ptr %itf.value, ptr %typ.typecode, ptr undef)
ret i1 %result
}
declare i1 @"reflect.Type.Implements$invoke"(ptr, ptr, ptr, ptr, ptr) #0
declare i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(ptr) #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" }