mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-19 12:04:03 +00:00
compiler: implement method-set based AssignableTo and Implements (#5304)
* reflect: implement method-set based AssignableTo and Implements Based on the design from #4376 by aykevl. Fixes #4277, fixes #3580. Co-authored-by: Ayke van Laethem <aykevanlaethem@gmail.com> * builder: update expected binary sizes for reflect changes * Make interface checks similar to invoke, allowing typeImplementsMethodSet and method info to be dropped when reflect is not present * Add more tests that BigGo reflect tests * Even more pruning * Add go/token and net/url to passing tests * Prune even further, I am less happy with this, though * Update size test now that we are smaller * Skip some tests * elide method lists * format, oops * fix tests * Add a panic, pull out constant to keep in sync * Add debug info * Remove code that was leftover from a previous refactor --------- Co-authored-by: Ayke van Laethem <aykevanlaethem@gmail.com>
This commit is contained in:
+3
-1
@@ -348,6 +348,7 @@ TEST_PACKAGES_FAST = \
|
|||||||
go/ast \
|
go/ast \
|
||||||
go/format \
|
go/format \
|
||||||
go/scanner \
|
go/scanner \
|
||||||
|
go/token \
|
||||||
go/version \
|
go/version \
|
||||||
hash \
|
hash \
|
||||||
hash/adler32 \
|
hash/adler32 \
|
||||||
@@ -360,6 +361,7 @@ TEST_PACKAGES_FAST = \
|
|||||||
math/cmplx \
|
math/cmplx \
|
||||||
net/http/internal/ascii \
|
net/http/internal/ascii \
|
||||||
net/mail \
|
net/mail \
|
||||||
|
net/url \
|
||||||
os \
|
os \
|
||||||
path \
|
path \
|
||||||
reflect \
|
reflect \
|
||||||
@@ -485,7 +487,7 @@ TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_WINDOWS)
|
|||||||
TEST_IOFS := false
|
TEST_IOFS := false
|
||||||
endif
|
endif
|
||||||
|
|
||||||
TEST_SKIP_FLAG := -skip='TestExtraMethods|TestParseAndBytesRoundTrip/P256/Generic'
|
TEST_SKIP_FLAG := -skip='TestExtraMethods|TestParseAndBytesRoundTrip/P256/Generic|TestParseQueryLimits|TestParseStrictIpv6'
|
||||||
TEST_ADDITIONAL_FLAGS ?=
|
TEST_ADDITIONAL_FLAGS ?=
|
||||||
|
|
||||||
# Test known-working standard library packages.
|
# Test known-working standard library packages.
|
||||||
|
|||||||
+153
-37
@@ -10,6 +10,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"go/token"
|
"go/token"
|
||||||
"go/types"
|
"go/types"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -17,6 +18,12 @@ import (
|
|||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// numMethodHasMethodSet is a flag in bit 15 of the numMethod field (uint16) in
|
||||||
|
// Named, Pointer, and Struct type descriptors. When set, an inline method set
|
||||||
|
// is present in the type descriptor. Must match the constant in
|
||||||
|
// src/internal/reflectlite/type.go.
|
||||||
|
const numMethodHasMethodSet = 0x8000
|
||||||
|
|
||||||
// Type kinds for basic types.
|
// Type kinds for basic types.
|
||||||
// They must match the constants for the Kind type in src/reflect/type.go.
|
// They must match the constants for the Kind type in src/reflect/type.go.
|
||||||
var basicTypes = [...]uint8{
|
var basicTypes = [...]uint8{
|
||||||
@@ -183,6 +190,16 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
|
|||||||
typeFieldTypes := []*types.Var{
|
typeFieldTypes := []*types.Var{
|
||||||
types.NewVar(token.NoPos, nil, "kind", types.Typ[types.Int8]),
|
types.NewVar(token.NoPos, nil, "kind", types.Typ[types.Int8]),
|
||||||
}
|
}
|
||||||
|
// Compute the method set value for types that support methods.
|
||||||
|
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) {
|
switch typ := typ.(type) {
|
||||||
case *types.Basic:
|
case *types.Basic:
|
||||||
typeFieldTypes = append(typeFieldTypes,
|
typeFieldTypes = append(typeFieldTypes,
|
||||||
@@ -199,6 +216,13 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
|
|||||||
types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]),
|
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, "underlying", types.Typ[types.UnsafePointer]),
|
||||||
types.NewVar(token.NoPos, nil, "pkgpath", types.Typ[types.UnsafePointer]),
|
types.NewVar(token.NoPos, nil, "pkgpath", types.Typ[types.UnsafePointer]),
|
||||||
|
)
|
||||||
|
if len(methods) > 0 {
|
||||||
|
typeFieldTypes = append(typeFieldTypes,
|
||||||
|
types.NewVar(token.NoPos, nil, "methods", methodSetType),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
typeFieldTypes = append(typeFieldTypes,
|
||||||
types.NewVar(token.NoPos, nil, "name", types.NewArray(types.Typ[types.Int8], int64(len(pkgname)+1+len(name)+1))),
|
types.NewVar(token.NoPos, nil, "name", types.NewArray(types.Typ[types.Int8], int64(len(pkgname)+1+len(name)+1))),
|
||||||
)
|
)
|
||||||
case *types.Chan:
|
case *types.Chan:
|
||||||
@@ -218,6 +242,11 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
|
|||||||
types.NewVar(token.NoPos, nil, "numMethods", types.Typ[types.Uint16]),
|
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, "elementType", types.Typ[types.UnsafePointer]),
|
||||||
)
|
)
|
||||||
|
if len(methods) > 0 {
|
||||||
|
typeFieldTypes = append(typeFieldTypes,
|
||||||
|
types.NewVar(token.NoPos, nil, "methods", methodSetType),
|
||||||
|
)
|
||||||
|
}
|
||||||
case *types.Array:
|
case *types.Array:
|
||||||
typeFieldTypes = append(typeFieldTypes,
|
typeFieldTypes = append(typeFieldTypes,
|
||||||
types.NewVar(token.NoPos, nil, "numMethods", types.Typ[types.Uint16]),
|
types.NewVar(token.NoPos, nil, "numMethods", types.Typ[types.Uint16]),
|
||||||
@@ -242,11 +271,16 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
|
|||||||
types.NewVar(token.NoPos, nil, "numFields", types.Typ[types.Uint16]),
|
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, "fields", types.NewArray(c.getRuntimeType("structField"), int64(typ.NumFields()))),
|
||||||
)
|
)
|
||||||
|
if len(methods) > 0 {
|
||||||
|
typeFieldTypes = append(typeFieldTypes,
|
||||||
|
types.NewVar(token.NoPos, nil, "methods", methodSetType),
|
||||||
|
)
|
||||||
|
}
|
||||||
case *types.Interface:
|
case *types.Interface:
|
||||||
typeFieldTypes = append(typeFieldTypes,
|
typeFieldTypes = append(typeFieldTypes,
|
||||||
types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]),
|
types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]),
|
||||||
|
types.NewVar(token.NoPos, nil, "methods", methodSetType),
|
||||||
)
|
)
|
||||||
// TODO: methods
|
|
||||||
case *types.Signature:
|
case *types.Signature:
|
||||||
typeFieldTypes = append(typeFieldTypes,
|
typeFieldTypes = append(typeFieldTypes,
|
||||||
types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]),
|
types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]),
|
||||||
@@ -292,14 +326,24 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
|
|||||||
pkgname = pkg.Name()
|
pkgname = pkg.Name()
|
||||||
}
|
}
|
||||||
pkgPathPtr := c.pkgPathPtr(pkgpath)
|
pkgPathPtr := c.pkgPathPtr(pkgpath)
|
||||||
typeFields = []llvm.Value{
|
namedNumMethods := uint64(numMethods)
|
||||||
llvm.ConstInt(c.ctx.Int16Type(), uint64(numMethods), false), // numMethods
|
if namedNumMethods&numMethodHasMethodSet != 0 {
|
||||||
c.getTypeCode(types.NewPointer(typ)), // ptrTo
|
panic("numMethods overflow: too many exported methods on named type " + name)
|
||||||
c.getTypeCode(typ.Underlying()), // underlying
|
|
||||||
pkgPathPtr, // pkgpath pointer
|
|
||||||
c.ctx.ConstString(pkgname+"."+name+"\x00", false), // name
|
|
||||||
}
|
}
|
||||||
metabyte |= 1 << 5 // "named" flag
|
if len(methods) > 0 {
|
||||||
|
namedNumMethods |= numMethodHasMethodSet
|
||||||
|
}
|
||||||
|
typeFields = []llvm.Value{
|
||||||
|
llvm.ConstInt(c.ctx.Int16Type(), namedNumMethods, false), // numMethods
|
||||||
|
c.getTypeCode(types.NewPointer(typ)), // ptrTo
|
||||||
|
c.getTypeCode(typ.Underlying()), // underlying
|
||||||
|
pkgPathPtr, // pkgpath pointer
|
||||||
|
}
|
||||||
|
if len(methods) > 0 {
|
||||||
|
typeFields = append(typeFields, methodSetValue) // methods
|
||||||
|
}
|
||||||
|
typeFields = append(typeFields, c.ctx.ConstString(pkgname+"."+name+"\x00", false)) // name
|
||||||
|
metabyte |= 1 << 5 // "named" flag
|
||||||
case *types.Chan:
|
case *types.Chan:
|
||||||
var dir reflectChanDir
|
var dir reflectChanDir
|
||||||
switch typ.Dir() {
|
switch typ.Dir() {
|
||||||
@@ -323,10 +367,20 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
|
|||||||
c.getTypeCode(typ.Elem()), // elementType
|
c.getTypeCode(typ.Elem()), // elementType
|
||||||
}
|
}
|
||||||
case *types.Pointer:
|
case *types.Pointer:
|
||||||
|
ptrNumMethods := uint64(numMethods)
|
||||||
|
if ptrNumMethods&numMethodHasMethodSet != 0 {
|
||||||
|
panic("numMethods overflow: too many exported methods on pointer type")
|
||||||
|
}
|
||||||
|
if len(methods) > 0 {
|
||||||
|
ptrNumMethods |= numMethodHasMethodSet
|
||||||
|
}
|
||||||
typeFields = []llvm.Value{
|
typeFields = []llvm.Value{
|
||||||
llvm.ConstInt(c.ctx.Int16Type(), uint64(numMethods), false), // numMethods
|
llvm.ConstInt(c.ctx.Int16Type(), ptrNumMethods, false), // numMethods
|
||||||
c.getTypeCode(typ.Elem()),
|
c.getTypeCode(typ.Elem()),
|
||||||
}
|
}
|
||||||
|
if len(methods) > 0 {
|
||||||
|
typeFields = append(typeFields, methodSetValue)
|
||||||
|
}
|
||||||
case *types.Array:
|
case *types.Array:
|
||||||
typeFields = []llvm.Value{
|
typeFields = []llvm.Value{
|
||||||
llvm.ConstInt(c.ctx.Int16Type(), 0, false), // numMethods
|
llvm.ConstInt(c.ctx.Int16Type(), 0, false), // numMethods
|
||||||
@@ -353,9 +407,16 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
|
|||||||
|
|
||||||
llvmStructType := c.getLLVMType(typ)
|
llvmStructType := c.getLLVMType(typ)
|
||||||
size := c.targetData.TypeStoreSize(llvmStructType)
|
size := c.targetData.TypeStoreSize(llvmStructType)
|
||||||
|
structNumMethods := uint64(numMethods)
|
||||||
|
if structNumMethods&numMethodHasMethodSet != 0 {
|
||||||
|
panic("numMethods overflow: too many exported methods on struct type")
|
||||||
|
}
|
||||||
|
if len(methods) > 0 {
|
||||||
|
structNumMethods |= numMethodHasMethodSet
|
||||||
|
}
|
||||||
typeFields = []llvm.Value{
|
typeFields = []llvm.Value{
|
||||||
llvm.ConstInt(c.ctx.Int16Type(), uint64(numMethods), false), // numMethods
|
llvm.ConstInt(c.ctx.Int16Type(), structNumMethods, false), // numMethods
|
||||||
c.getTypeCode(types.NewPointer(typ)), // ptrTo
|
c.getTypeCode(types.NewPointer(typ)), // ptrTo
|
||||||
pkgPathPtr,
|
pkgPathPtr,
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(size), false), // size
|
llvm.ConstInt(c.ctx.Int32Type(), uint64(size), false), // size
|
||||||
llvm.ConstInt(c.ctx.Int16Type(), uint64(typ.NumFields()), false), // numFields
|
llvm.ConstInt(c.ctx.Int16Type(), uint64(typ.NumFields()), false), // numFields
|
||||||
@@ -407,9 +468,14 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
typeFields = append(typeFields, llvm.ConstArray(structFieldType, fields))
|
typeFields = append(typeFields, llvm.ConstArray(structFieldType, fields))
|
||||||
|
if len(methods) > 0 {
|
||||||
|
typeFields = append(typeFields, methodSetValue)
|
||||||
|
}
|
||||||
case *types.Interface:
|
case *types.Interface:
|
||||||
typeFields = []llvm.Value{c.getTypeCode(types.NewPointer(typ))}
|
typeFields = []llvm.Value{
|
||||||
// TODO: methods
|
c.getTypeCode(types.NewPointer(typ)),
|
||||||
|
methodSetValue,
|
||||||
|
}
|
||||||
case *types.Signature:
|
case *types.Signature:
|
||||||
typeFields = []llvm.Value{c.getTypeCode(types.NewPointer(typ))}
|
typeFields = []llvm.Value{c.getTypeCode(types.NewPointer(typ))}
|
||||||
// TODO: params, return values, etc
|
// TODO: params, return values, etc
|
||||||
@@ -696,17 +762,11 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
|
|||||||
// This type assertion always succeeds, so we can just set commaOk to true.
|
// This type assertion always succeeds, so we can just set commaOk to true.
|
||||||
commaOk = llvm.ConstInt(b.ctx.Int1Type(), 1, true)
|
commaOk = llvm.ConstInt(b.ctx.Int1Type(), 1, true)
|
||||||
} else {
|
} else {
|
||||||
// Type assert on interface type with methods.
|
// Type assert on an interface type with methods.
|
||||||
// This is a call to an interface type assert function.
|
// Create a call to a declared-but-not-defined function that will
|
||||||
// The interface lowering pass will define this function by filling it
|
// be lowered by the interface lowering pass into a type-ID
|
||||||
// with a type switch over all concrete types that implement this
|
// comparison chain.
|
||||||
// interface, and returning whether it's one of the matched types.
|
commaOk = b.createInterfaceTypeAssert(intf, actualTypeNum)
|
||||||
// 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:
|
|
||||||
// https://research.swtch.com/interfaces
|
|
||||||
fn := b.getInterfaceImplementsFunc(expr.AssertedType)
|
|
||||||
commaOk = b.CreateCall(fn.GlobalValueType(), fn, []llvm.Value{actualTypeNum}, "")
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
name, _ := getTypeCodeName(expr.AssertedType)
|
name, _ := getTypeCodeName(expr.AssertedType)
|
||||||
@@ -783,20 +843,58 @@ func (c *compilerContext) getMethodsString(itf *types.Interface) string {
|
|||||||
return strings.Join(methods, "; ")
|
return strings.Join(methods, "; ")
|
||||||
}
|
}
|
||||||
|
|
||||||
// getInterfaceImplementsFunc returns a declared function that works as a type
|
// getMethodSetValue creates the method set struct value for a list of methods.
|
||||||
// switch. The interface lowering pass will define this function.
|
// The struct contains a length and a sorted array of method signature pointers.
|
||||||
func (c *compilerContext) getInterfaceImplementsFunc(assertedType types.Type) llvm.Value {
|
func (c *compilerContext) getMethodSetValue(methods []*types.Func) llvm.Value {
|
||||||
s, _ := getTypeCodeName(assertedType.Underlying())
|
// Create a sorted list of method signature global names.
|
||||||
fnName := s + ".$typeassert"
|
type methodRef struct {
|
||||||
llvmFn := c.mod.NamedFunction(fnName)
|
name string
|
||||||
if llvmFn.IsNil() {
|
value llvm.Value
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
return llvmFn
|
var refs []methodRef
|
||||||
|
for _, method := range methods {
|
||||||
|
name := method.Name()
|
||||||
|
if !token.IsExported(name) {
|
||||||
|
name = method.Pkg().Path() + "." + name
|
||||||
|
}
|
||||||
|
s, _ := getTypeCodeName(method.Type())
|
||||||
|
globalName := "reflect/types.signature:" + name + ":" + s
|
||||||
|
value := c.mod.NamedGlobal(globalName)
|
||||||
|
if value.IsNil() {
|
||||||
|
value = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), globalName)
|
||||||
|
value.SetInitializer(llvm.ConstNull(c.ctx.Int8Type()))
|
||||||
|
value.SetGlobalConstant(true)
|
||||||
|
value.SetLinkage(llvm.LinkOnceODRLinkage)
|
||||||
|
value.SetAlignment(1)
|
||||||
|
if c.Debug {
|
||||||
|
file := c.getDIFile("<Go type>")
|
||||||
|
diglobal := c.dibuilder.CreateGlobalVariableExpression(file, llvm.DIGlobalVariableExpression{
|
||||||
|
Name: globalName,
|
||||||
|
File: file,
|
||||||
|
Line: 1,
|
||||||
|
Type: c.getDIType(types.Typ[types.Uint8]),
|
||||||
|
LocalToUnit: false,
|
||||||
|
Expr: c.dibuilder.CreateExpression(nil),
|
||||||
|
AlignInBits: 8,
|
||||||
|
})
|
||||||
|
value.AddMetadata(0, diglobal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
refs = append(refs, methodRef{globalName, value})
|
||||||
|
}
|
||||||
|
sort.Slice(refs, func(i, j int) bool {
|
||||||
|
return refs[i].name < refs[j].name
|
||||||
|
})
|
||||||
|
|
||||||
|
var values []llvm.Value
|
||||||
|
for _, ref := range refs {
|
||||||
|
values = append(values, ref.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.ctx.ConstStruct([]llvm.Value{
|
||||||
|
llvm.ConstInt(c.uintptrType, uint64(len(values)), false),
|
||||||
|
llvm.ConstArray(c.dataPtrType, values),
|
||||||
|
}, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getInvokeFunction returns the thunk to call the given interface method. The
|
// getInvokeFunction returns the thunk to call the given interface method. The
|
||||||
@@ -823,6 +921,24 @@ func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value {
|
|||||||
return llvmFn
|
return llvmFn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// createInterfaceTypeAssert creates a call to a declared-but-not-defined
|
||||||
|
// $typeassert function for the given interface. This function will be defined
|
||||||
|
// by the interface lowering pass as a type-ID comparison chain, avoiding the
|
||||||
|
// need for runtime.typeImplementsMethodSet at compile time.
|
||||||
|
func (b *builder) createInterfaceTypeAssert(intf *types.Interface, actualType llvm.Value) llvm.Value {
|
||||||
|
s, _ := getTypeCodeName(intf)
|
||||||
|
fnName := s + ".$typeassert"
|
||||||
|
llvmFn := b.mod.NamedFunction(fnName)
|
||||||
|
if llvmFn.IsNil() {
|
||||||
|
llvmFnType := llvm.FunctionType(b.ctx.Int1Type(), []llvm.Type{b.dataPtrType}, false)
|
||||||
|
llvmFn = llvm.AddFunction(b.mod, fnName, llvmFnType)
|
||||||
|
b.addStandardDeclaredAttributes(llvmFn)
|
||||||
|
methods := b.getMethodsString(intf)
|
||||||
|
llvmFn.AddFunctionAttr(b.ctx.CreateStringAttribute("tinygo-methods", methods))
|
||||||
|
}
|
||||||
|
return b.CreateCall(llvmFn.GlobalValueType(), llvmFn, []llvm.Value{actualType}, "")
|
||||||
|
}
|
||||||
|
|
||||||
// getInterfaceInvokeWrapper returns a wrapper for the given method so it can be
|
// getInterfaceInvokeWrapper returns a wrapper for the given method so it can be
|
||||||
// invoked from an interface. The wrapper takes in a pointer to the underlying
|
// invoked from an interface. The wrapper takes in a pointer to the underlying
|
||||||
// value, dereferences or unpacks it if necessary, and calls the real method.
|
// value, dereferences or unpacks it if necessary, and calls the real method.
|
||||||
|
|||||||
Vendored
+5
-3
@@ -9,12 +9,14 @@ target triple = "wasm32-unknown-wasi"
|
|||||||
@"reflect/types.type:basic:int" = linkonce_odr constant { i8, ptr } { i8 -62, ptr @"reflect/types.type:pointer:basic:int" }, align 4
|
@"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: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: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.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 -32767, 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.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: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 } { i8 -43, i16 0, ptr @"reflect/types.type: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: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.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
|
@"reflect/types.typeid:basic:int" = external constant i8
|
||||||
|
|
||||||
; Function Attrs: allockind("alloc,zeroed") allocsize(0)
|
; Function Attrs: allockind("alloc,zeroed") allocsize(0)
|
||||||
|
|||||||
@@ -413,64 +413,6 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
|
|||||||
// Special function that will trigger an error.
|
// Special function that will trigger an error.
|
||||||
// This is used to test error reporting.
|
// This is used to test error reporting.
|
||||||
return nil, mem, r.errorAt(inst, errors.New("test error"))
|
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"):
|
case strings.HasSuffix(callFn.name, "$invoke"):
|
||||||
// This thunk is the interface method dispatcher: it is called
|
// This thunk is the interface method dispatcher: it is called
|
||||||
// with all regular parameters and a type code. It will then
|
// with all regular parameters and a type code. It will then
|
||||||
|
|||||||
@@ -157,6 +157,10 @@ const (
|
|||||||
flagIsBinary = 128 // flag that is set if this type uses the hashmap binary algorithm
|
flagIsBinary = 128 // flag that is set if this type uses the hashmap binary algorithm
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Flag in the numMethod field (uint16) of Pointer and Struct type descriptors,
|
||||||
|
// indicating that an inline method set is present in the type descriptor.
|
||||||
|
const numMethodHasMethodSet = 0x8000
|
||||||
|
|
||||||
// The base type struct. All type structs start with this.
|
// The base type struct. All type structs start with this.
|
||||||
type RawType struct {
|
type RawType struct {
|
||||||
meta uint8 // metadata byte, contains kind and flags (see constants above)
|
meta uint8 // metadata byte, contains kind and flags (see constants above)
|
||||||
@@ -171,16 +175,22 @@ type elemType struct {
|
|||||||
elem *RawType
|
elem *RawType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ptrType is the type descriptor for pointer types.
|
||||||
|
// The numMethod field stores the number of exported methods in the lower bits,
|
||||||
|
// with bit 15 (numMethodHasMethodSet) indicating whether the methods field is
|
||||||
|
// present. When the flag is clear, the methods field does not exist in the
|
||||||
|
// actual type descriptor and must not be accessed.
|
||||||
type ptrType struct {
|
type ptrType struct {
|
||||||
RawType
|
RawType
|
||||||
numMethod uint16
|
numMethod uint16
|
||||||
elem *RawType
|
elem *RawType
|
||||||
|
methods methodSet // only present when numMethod & numMethodHasMethodSet != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
type interfaceType struct {
|
type interfaceType struct {
|
||||||
RawType
|
RawType
|
||||||
ptrTo *RawType
|
ptrTo *RawType
|
||||||
// TODO: methods
|
methods methodSet
|
||||||
}
|
}
|
||||||
|
|
||||||
type arrayType struct {
|
type arrayType struct {
|
||||||
@@ -200,13 +210,19 @@ type mapType struct {
|
|||||||
key *RawType
|
key *RawType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// namedType is the type descriptor for named types. The numMethod field uses
|
||||||
|
// bit 15 (numMethodHasMethodSet) to indicate whether an inline method set is
|
||||||
|
// present after pkg. When the flag is set, a methodSet follows at
|
||||||
|
// unsafe.Sizeof(namedType{}), and the name string follows after the method
|
||||||
|
// set's entries. When clear, the name string starts directly at that offset.
|
||||||
type namedType struct {
|
type namedType struct {
|
||||||
RawType
|
RawType
|
||||||
numMethod uint16
|
numMethod uint16
|
||||||
ptrTo *RawType
|
ptrTo *RawType
|
||||||
elem *RawType
|
elem *RawType
|
||||||
pkg *byte
|
pkg *byte
|
||||||
name [1]byte
|
// if numMethod & numMethodHasMethodSet != 0: methodSet follows here
|
||||||
|
// name (null-terminated "pkg.Name\0") follows after the method set (or directly here)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type for struct types. The numField value is intentionally put before ptrTo
|
// Type for struct types. The numField value is intentionally put before ptrTo
|
||||||
@@ -216,6 +232,10 @@ type namedType struct {
|
|||||||
// The fields array isn't necessarily 1 structField long, instead it is as long
|
// The fields array isn't necessarily 1 structField long, instead it is as long
|
||||||
// as numFields. The array is given a length of 1 to satisfy the Go type
|
// as numFields. The array is given a length of 1 to satisfy the Go type
|
||||||
// checker.
|
// checker.
|
||||||
|
// The numMethod field stores the number of exported methods in the lower bits,
|
||||||
|
// with bit 15 (numMethodHasMethodSet) indicating whether an inline method set
|
||||||
|
// follows the fields array. When the flag is clear, no method set is present
|
||||||
|
// and the type descriptor ends after the last structField entry.
|
||||||
type structType struct {
|
type structType struct {
|
||||||
RawType
|
RawType
|
||||||
numMethod uint16
|
numMethod uint16
|
||||||
@@ -224,6 +244,7 @@ type structType struct {
|
|||||||
size uint32
|
size uint32
|
||||||
numField uint16
|
numField uint16
|
||||||
fields [1]structField // the remaining fields are all of type structField
|
fields [1]structField // the remaining fields are all of type structField
|
||||||
|
// methods methodSet follows after fields, only when numMethod & numMethodHasMethodSet != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
type structField struct {
|
type structField struct {
|
||||||
@@ -231,6 +252,12 @@ type structField struct {
|
|||||||
data unsafe.Pointer // various bits of information, packed in a byte array
|
data unsafe.Pointer // various bits of information, packed in a byte array
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Method set, as emitted by the compiler.
|
||||||
|
type methodSet struct {
|
||||||
|
length uintptr
|
||||||
|
methods [0]unsafe.Pointer // variable number of method signature pointers
|
||||||
|
}
|
||||||
|
|
||||||
// Equivalent to (go/types.Type).Underlying(): if this is a named type return
|
// Equivalent to (go/types.Type).Underlying(): if this is a named type return
|
||||||
// the underlying type, else just return the type itself.
|
// the underlying type, else just return the type itself.
|
||||||
func (t *RawType) underlying() *RawType {
|
func (t *RawType) underlying() *RawType {
|
||||||
@@ -733,21 +760,38 @@ func (t *RawType) FieldAlign() int {
|
|||||||
// AssignableTo returns whether a value of type t can be assigned to a variable
|
// AssignableTo returns whether a value of type t can be assigned to a variable
|
||||||
// of type u.
|
// of type u.
|
||||||
func (t *RawType) AssignableTo(u Type) bool {
|
func (t *RawType) AssignableTo(u Type) bool {
|
||||||
if t == u.(*RawType) {
|
u_raw := u.(*RawType)
|
||||||
return true
|
if t == u_raw {
|
||||||
}
|
|
||||||
|
|
||||||
if t.underlying() == u.(*RawType).underlying() && (!t.isNamed() || !u.(*RawType).isNamed()) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if u.Kind() == Interface && u.NumMethod() == 0 {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if u.Kind() == Interface {
|
if u.Kind() == Interface {
|
||||||
panic("reflect: unimplemented: AssignableTo with interface")
|
// T is an interface type and x implements T.
|
||||||
|
u_itf := (*interfaceType)(unsafe.Pointer(u_raw.underlying()))
|
||||||
|
return typeImplementsMethodSet(unsafe.Pointer(t), unsafe.Pointer(&u_itf.methods))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t_named := t.isNamed()
|
||||||
|
u_named := u_raw.isNamed()
|
||||||
|
if t_named && u_named {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if t.underlying() == u_raw.underlying() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Kind() == Chan && u_raw.Kind() == Chan {
|
||||||
|
t_chan := (*elemType)(unsafe.Pointer(t.underlying()))
|
||||||
|
u_chan := (*elemType)(unsafe.Pointer(u_raw.underlying()))
|
||||||
|
if t_chan.elem != u_chan.elem {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if t_chan.ChanDir() != BothDir {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -755,7 +799,85 @@ func (t *RawType) Implements(u Type) bool {
|
|||||||
if u.Kind() != Interface {
|
if u.Kind() != Interface {
|
||||||
panic("reflect: non-interface type passed to Type.Implements")
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
// typeImplementsMethodSet checks whether the concrete type (identified by its
|
||||||
|
// typecode pointer) implements the given method set. Both the concrete type's
|
||||||
|
// method set and the asserted method set are sorted arrays of method signature
|
||||||
|
// pointers, so comparison is O(n+m).
|
||||||
|
//
|
||||||
|
//go:linkname typeImplementsMethodSet runtime.typeImplementsMethodSet
|
||||||
|
func typeImplementsMethodSet(concreteType, assertedMethodSet unsafe.Pointer) bool {
|
||||||
|
if concreteType == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const ptrSize = unsafe.Sizeof((*byte)(nil))
|
||||||
|
itfNumMethod := *(*uintptr)(assertedMethodSet)
|
||||||
|
if itfNumMethod == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pull the method set out of the concrete type.
|
||||||
|
var methods *methodSet
|
||||||
|
metaByte := *(*uint8)(concreteType)
|
||||||
|
if metaByte&flagNamed != 0 {
|
||||||
|
ct := (*namedType)(concreteType)
|
||||||
|
if ct.numMethod&numMethodHasMethodSet == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
methods = (*methodSet)(unsafe.Add(unsafe.Pointer(ct), unsafe.Sizeof(*ct)))
|
||||||
|
} else if metaByte&kindMask == uint8(Interface) {
|
||||||
|
ct := (*interfaceType)(concreteType)
|
||||||
|
methods = &ct.methods
|
||||||
|
} else if metaByte&kindMask == uint8(Pointer) {
|
||||||
|
ct := (*ptrType)(concreteType)
|
||||||
|
if ct.numMethod&numMethodHasMethodSet == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
methods = &ct.methods
|
||||||
|
} else if metaByte&kindMask == uint8(Struct) {
|
||||||
|
ct := (*structType)(concreteType)
|
||||||
|
if ct.numMethod&numMethodHasMethodSet == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// For struct types, the method set follows after the variable-length
|
||||||
|
// fields array. We need to compute its offset dynamically.
|
||||||
|
fieldSize := unsafe.Sizeof(structField{})
|
||||||
|
methodsPtr := unsafe.Add(unsafe.Pointer(&ct.fields[0]), uintptr(ct.numField)*fieldSize)
|
||||||
|
methods = (*methodSet)(methodsPtr)
|
||||||
|
} else {
|
||||||
|
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, so we can use a linear scan.
|
||||||
|
assertedTypePtr := unsafe.Add(assertedMethodSet, ptrSize)
|
||||||
|
assertedTypeEnd := unsafe.Add(assertedTypePtr, itfNumMethod*ptrSize)
|
||||||
|
for assertedTypePtr != assertedTypeEnd {
|
||||||
|
assertedMethod := *(*unsafe.Pointer)(assertedTypePtr)
|
||||||
|
|
||||||
|
for {
|
||||||
|
if concreteTypePtr == concreteTypeEnd {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
concreteMethod := *(*unsafe.Pointer)(concreteTypePtr)
|
||||||
|
concreteTypePtr = unsafe.Add(concreteTypePtr, ptrSize)
|
||||||
|
if concreteMethod == assertedMethod {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertedTypePtr = unsafe.Add(assertedTypePtr, ptrSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comparable returns whether values of this type can be compared to each other.
|
// Comparable returns whether values of this type can be compared to each other.
|
||||||
@@ -782,14 +904,14 @@ func (t *RawType) ChanDir() ChanDir {
|
|||||||
func (t *RawType) NumMethod() int {
|
func (t *RawType) NumMethod() int {
|
||||||
|
|
||||||
if t.isNamed() {
|
if t.isNamed() {
|
||||||
return int((*namedType)(unsafe.Pointer(t)).numMethod)
|
return int((*namedType)(unsafe.Pointer(t)).numMethod & ^uint16(numMethodHasMethodSet))
|
||||||
}
|
}
|
||||||
|
|
||||||
switch t.Kind() {
|
switch t.Kind() {
|
||||||
case Pointer:
|
case Pointer:
|
||||||
return int((*ptrType)(unsafe.Pointer(t)).numMethod)
|
return int((*ptrType)(unsafe.Pointer(t)).numMethod & ^uint16(numMethodHasMethodSet))
|
||||||
case Struct:
|
case Struct:
|
||||||
return int((*structType)(unsafe.Pointer(t)).numMethod)
|
return int((*structType)(unsafe.Pointer(t)).numMethod & ^uint16(numMethodHasMethodSet))
|
||||||
case Interface:
|
case Interface:
|
||||||
//FIXME: Use len(methods)
|
//FIXME: Use len(methods)
|
||||||
return (*interfaceType)(unsafe.Pointer(t)).ptrTo.NumMethod()
|
return (*interfaceType)(unsafe.Pointer(t)).ptrTo.NumMethod()
|
||||||
@@ -816,7 +938,14 @@ func readStringZ(data unsafe.Pointer) string {
|
|||||||
|
|
||||||
func (t *RawType) name() string {
|
func (t *RawType) name() string {
|
||||||
ntype := (*namedType)(unsafe.Pointer(t))
|
ntype := (*namedType)(unsafe.Pointer(t))
|
||||||
return readStringZ(unsafe.Pointer(&ntype.name[0]))
|
// The name follows after the fixed fields (and optionally the method set).
|
||||||
|
ptr := unsafe.Add(unsafe.Pointer(ntype), unsafe.Sizeof(*ntype))
|
||||||
|
if ntype.numMethod&numMethodHasMethodSet != 0 {
|
||||||
|
ms := (*methodSet)(ptr)
|
||||||
|
// Skip past the length field and the method pointer entries.
|
||||||
|
ptr = unsafe.Add(ptr, unsafe.Sizeof(uintptr(0))+uintptr(ms.length)*unsafe.Sizeof(unsafe.Pointer(nil)))
|
||||||
|
}
|
||||||
|
return readStringZ(ptr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *RawType) Name() string {
|
func (t *RawType) Name() string {
|
||||||
|
|||||||
@@ -90,6 +90,9 @@ func interfaceTypeAssert(ok bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Implemented in the internal/reflectlite package.
|
||||||
|
func typeImplementsMethodSet(actualTypeNum, assertedMethodSet unsafe.Pointer) bool
|
||||||
|
|
||||||
// The following declarations are only used during IR construction. They are
|
// The following declarations are only used during IR construction. They are
|
||||||
// lowered to inline IR in the interface lowering pass.
|
// lowered to inline IR in the interface lowering pass.
|
||||||
// See compiler/interface-lowering.go for details.
|
// See compiler/interface-lowering.go for details.
|
||||||
|
|||||||
Vendored
+226
-6
@@ -344,12 +344,8 @@ func main() {
|
|||||||
println("PtrTo failed for type myslice")
|
println("PtrTo failed for type myslice")
|
||||||
}
|
}
|
||||||
|
|
||||||
if reflect.TypeOf(errorValue).Implements(errorType) != true {
|
println("\ninterface implements")
|
||||||
println("errorValue.Implements(errorType) was false, expected true")
|
testImplements()
|
||||||
}
|
|
||||||
if reflect.TypeOf(errorValue).Implements(stringerType) != false {
|
|
||||||
println("errorValue.Implements(errorType) was true, expected false")
|
|
||||||
}
|
|
||||||
|
|
||||||
println("\nalignment / offset:")
|
println("\nalignment / offset:")
|
||||||
v2 := struct {
|
v2 := struct {
|
||||||
@@ -573,6 +569,230 @@ func testInterfaceMethod() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Types for interface Implements/AssignableTo tests.
|
||||||
|
|
||||||
|
type Reader interface {
|
||||||
|
Read(p []byte) (n int, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Writer interface {
|
||||||
|
Write(p []byte) (n int, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReadWriter interface {
|
||||||
|
Read(p []byte) (n int, err error)
|
||||||
|
Write(p []byte) (n int, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Closer interface {
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReadCloser interface {
|
||||||
|
Read(p []byte) (n int, err error)
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type myReader struct{}
|
||||||
|
|
||||||
|
func (myReader) Read(p []byte) (int, error) { return 0, nil }
|
||||||
|
|
||||||
|
type myWriter struct{}
|
||||||
|
|
||||||
|
func (*myWriter) Write(p []byte) (int, error) { return 0, nil }
|
||||||
|
|
||||||
|
type myReadWriter struct{}
|
||||||
|
|
||||||
|
func (myReadWriter) Read(p []byte) (int, error) { return 0, nil }
|
||||||
|
func (*myReadWriter) Write(p []byte) (int, error) { return 0, nil }
|
||||||
|
|
||||||
|
type myStringer struct{}
|
||||||
|
|
||||||
|
func (myStringer) String() string { return "mystringer" }
|
||||||
|
|
||||||
|
type myErrorStringer struct{}
|
||||||
|
|
||||||
|
func (myErrorStringer) Error() string { return "err" }
|
||||||
|
func (myErrorStringer) String() string { return "str" }
|
||||||
|
|
||||||
|
// Interface with unexported method (from upstream set_test.go).
|
||||||
|
type exprLike interface {
|
||||||
|
Pos() int
|
||||||
|
End() int
|
||||||
|
exprNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
type notAnExpr struct{}
|
||||||
|
|
||||||
|
func (notAnExpr) Pos() int { return 0 }
|
||||||
|
func (notAnExpr) End() int { return 0 }
|
||||||
|
func (notAnExpr) exprNode() {}
|
||||||
|
|
||||||
|
// Named types for assignability tests (from upstream set_test.go).
|
||||||
|
type IntPtr *int
|
||||||
|
type IntPtr1 *int
|
||||||
|
type Ch <-chan interface{}
|
||||||
|
|
||||||
|
func testImplements() {
|
||||||
|
readerType := reflect.TypeOf((*Reader)(nil)).Elem()
|
||||||
|
writerType := reflect.TypeOf((*Writer)(nil)).Elem()
|
||||||
|
readWriterType := reflect.TypeOf((*ReadWriter)(nil)).Elem()
|
||||||
|
closerType := reflect.TypeOf((*Closer)(nil)).Elem()
|
||||||
|
readCloserType := reflect.TypeOf((*ReadCloser)(nil)).Elem()
|
||||||
|
emptyItf := reflect.TypeOf((*interface{})(nil)).Elem()
|
||||||
|
|
||||||
|
// --- Concrete type implements interface ---
|
||||||
|
println("concrete implements:")
|
||||||
|
|
||||||
|
// myReader has value receiver Read → implements Reader
|
||||||
|
println("myReader → Reader:", reflect.TypeOf(myReader{}).Implements(readerType)) // true
|
||||||
|
println("*myReader → Reader:", reflect.TypeOf(new(myReader)).Elem().Implements(readerType)) // true (value method in pointer set)
|
||||||
|
|
||||||
|
// myWriter has pointer receiver Write → only *myWriter implements Writer
|
||||||
|
println("myWriter → Writer:", reflect.TypeOf(myWriter{}).Implements(writerType)) // false (pointer receiver)
|
||||||
|
println("*myWriter → Writer:", reflect.TypeOf(&myWriter{}).Implements(writerType)) // true
|
||||||
|
|
||||||
|
// myReadWriter: Read on value, Write on pointer
|
||||||
|
println("myReadWriter → Reader:", reflect.TypeOf(myReadWriter{}).Implements(readerType)) // true
|
||||||
|
println("myReadWriter → Writer:", reflect.TypeOf(myReadWriter{}).Implements(writerType)) // false (Write is ptr recv)
|
||||||
|
println("myReadWriter → ReadWriter:", reflect.TypeOf(myReadWriter{}).Implements(readWriterType)) // false
|
||||||
|
println("*myReadWriter → Reader:", reflect.TypeOf(&myReadWriter{}).Implements(readerType)) // true
|
||||||
|
println("*myReadWriter → Writer:", reflect.TypeOf(&myReadWriter{}).Implements(writerType)) // true
|
||||||
|
println("*myReadWriter → ReadWriter:", reflect.TypeOf(&myReadWriter{}).Implements(readWriterType)) // true
|
||||||
|
|
||||||
|
// Nothing implements Closer (none of our types have Close)
|
||||||
|
println("myReader → Closer:", reflect.TypeOf(myReader{}).Implements(closerType)) // false
|
||||||
|
println("*myReadWriter → Closer:", reflect.TypeOf(&myReadWriter{}).Implements(closerType)) // false
|
||||||
|
|
||||||
|
// errorValue (*errors.errorString) implements error but not Stringer
|
||||||
|
println("errorValue → error:", reflect.TypeOf(errorValue).Implements(errorType)) // true
|
||||||
|
println("errorValue → Stringer:", reflect.TypeOf(errorValue).Implements(stringerType)) // false
|
||||||
|
|
||||||
|
// myErrorStringer implements both error and Stringer
|
||||||
|
println("myErrorStringer → error:", reflect.TypeOf(myErrorStringer{}).Implements(errorType)) // true
|
||||||
|
println("myErrorStringer → Stringer:", reflect.TypeOf(myErrorStringer{}).Implements(stringerType)) // true
|
||||||
|
|
||||||
|
// Everything implements empty interface
|
||||||
|
println("myReader → interface{}:", reflect.TypeOf(myReader{}).Implements(emptyItf)) // true
|
||||||
|
println("int → interface{}:", reflect.TypeOf(0).Implements(emptyItf)) // true
|
||||||
|
|
||||||
|
// --- Interface implements interface (superset check, issue #3580) ---
|
||||||
|
println("interface implements interface:")
|
||||||
|
|
||||||
|
// ReadWriter is a superset of Reader and Writer
|
||||||
|
println("ReadWriter → Reader:", readWriterType.Implements(readerType)) // true
|
||||||
|
println("ReadWriter → Writer:", readWriterType.Implements(writerType)) // true
|
||||||
|
println("Reader → ReadWriter:", readerType.Implements(readWriterType)) // false
|
||||||
|
println("Writer → ReadWriter:", writerType.Implements(readWriterType)) // false
|
||||||
|
|
||||||
|
// ReadCloser has Read+Close, Reader has Read
|
||||||
|
println("ReadCloser → Reader:", readCloserType.Implements(readerType)) // true
|
||||||
|
println("ReadCloser → Closer:", readCloserType.Implements(closerType)) // true
|
||||||
|
println("ReadCloser → Writer:", readCloserType.Implements(writerType)) // false
|
||||||
|
println("Reader → ReadCloser:", readerType.Implements(readCloserType)) // false
|
||||||
|
|
||||||
|
// Self-implements
|
||||||
|
println("Reader → Reader:", readerType.Implements(readerType)) // true
|
||||||
|
println("ReadWriter → ReadWriter:", readWriterType.Implements(readWriterType)) // true
|
||||||
|
|
||||||
|
// error and Stringer are unrelated
|
||||||
|
println("error → Stringer:", errorType.Implements(stringerType)) // false
|
||||||
|
println("Stringer → error:", stringerType.Implements(errorType)) // false
|
||||||
|
|
||||||
|
// Everything implements empty interface
|
||||||
|
println("Reader → interface{}:", readerType.Implements(emptyItf)) // true
|
||||||
|
println("ReadWriter → interface{}:", readWriterType.Implements(emptyItf)) // true
|
||||||
|
|
||||||
|
// --- AssignableTo ---
|
||||||
|
println("assignable to:")
|
||||||
|
|
||||||
|
// Identical types
|
||||||
|
println("int → int:", reflect.TypeOf(0).AssignableTo(reflect.TypeOf(0))) // true
|
||||||
|
println("string → string:", reflect.TypeOf("").AssignableTo(reflect.TypeOf(""))) // true
|
||||||
|
|
||||||
|
// Different types
|
||||||
|
println("int → string:", reflect.TypeOf(0).AssignableTo(reflect.TypeOf(""))) // false
|
||||||
|
println("int → int64:", reflect.TypeOf(0).AssignableTo(reflect.TypeOf(int64(0)))) // false
|
||||||
|
|
||||||
|
// Concrete assignable to interface (implements check)
|
||||||
|
println("myReader → Reader:", reflect.TypeOf(myReader{}).AssignableTo(readerType)) // true
|
||||||
|
println("*myWriter → Writer:", reflect.TypeOf(&myWriter{}).AssignableTo(writerType)) // true
|
||||||
|
println("myWriter → Writer:", reflect.TypeOf(myWriter{}).AssignableTo(writerType)) // false
|
||||||
|
println("*myReadWriter → ReadWriter:", reflect.TypeOf(&myReadWriter{}).AssignableTo(readWriterType)) // true
|
||||||
|
|
||||||
|
// Interface assignable to interface
|
||||||
|
println("ReadWriter → Reader:", readWriterType.AssignableTo(readerType)) // true
|
||||||
|
println("Reader → ReadWriter:", readerType.AssignableTo(readWriterType)) // false
|
||||||
|
|
||||||
|
// Everything assignable to empty interface
|
||||||
|
println("int → interface{}:", reflect.TypeOf(0).AssignableTo(emptyItf)) // true
|
||||||
|
println("Reader → interface{}:", readerType.AssignableTo(emptyItf)) // true
|
||||||
|
|
||||||
|
// --- Upstream set_test.go: unexported method interfaces ---
|
||||||
|
println("unexported method interface:")
|
||||||
|
exprType := reflect.TypeOf((*exprLike)(nil)).Elem()
|
||||||
|
println("*notAnExpr → exprLike:", reflect.TypeOf(new(notAnExpr)).Implements(exprType)) // true
|
||||||
|
println("notAnExpr → exprLike:", reflect.TypeOf(notAnExpr{}).Implements(exprType)) // true
|
||||||
|
println("*notAnExpr → exprLike (AssignableTo):", reflect.TypeOf(new(notAnExpr)).AssignableTo(exprType)) // true
|
||||||
|
|
||||||
|
// --- Upstream set_test.go: channel direction assignability ---
|
||||||
|
println("channel direction:")
|
||||||
|
println("chan int → <-chan int:", reflect.TypeOf(make(chan int)).AssignableTo(reflect.TypeOf(make(<-chan int)))) // true
|
||||||
|
println("<-chan int → chan int:", reflect.TypeOf(make(<-chan int)).AssignableTo(reflect.TypeOf(make(chan int)))) // false
|
||||||
|
|
||||||
|
// --- Upstream set_test.go: named type assignability ---
|
||||||
|
println("named types:")
|
||||||
|
println("*int → IntPtr:", reflect.TypeOf(new(int)).AssignableTo(reflect.TypeOf(IntPtr(nil)))) // true
|
||||||
|
println("IntPtr → *int:", reflect.TypeOf(IntPtr(nil)).AssignableTo(reflect.TypeOf(new(int)))) // true
|
||||||
|
println("IntPtr → IntPtr1:", reflect.TypeOf(IntPtr(nil)).AssignableTo(reflect.TypeOf(IntPtr1(nil)))) // false
|
||||||
|
println("Ch → <-chan interface{}:", reflect.TypeOf(Ch(nil)).AssignableTo(reflect.TypeOf(make(<-chan interface{})))) // true
|
||||||
|
|
||||||
|
// --- reflect.Value.Set with interface (issue #4277) ---
|
||||||
|
println("value set interface:")
|
||||||
|
type Node interface{ node() }
|
||||||
|
type FooNode struct{ V int }
|
||||||
|
type BarNode struct{ V int }
|
||||||
|
// Make FooNode and BarNode implement Node with pointer receivers
|
||||||
|
// (can't add methods to local types in function, use a different approach)
|
||||||
|
testValueSetInterface()
|
||||||
|
}
|
||||||
|
|
||||||
|
type IfaceNode interface {
|
||||||
|
ifaceNode()
|
||||||
|
}
|
||||||
|
type FooNode struct{ V int }
|
||||||
|
type BarNode struct{ V int }
|
||||||
|
|
||||||
|
func (*FooNode) ifaceNode() {}
|
||||||
|
func (*BarNode) ifaceNode() {}
|
||||||
|
|
||||||
|
type NodeContainer struct {
|
||||||
|
Nodes []IfaceNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func testValueSetInterface() {
|
||||||
|
c := &NodeContainer{
|
||||||
|
Nodes: []IfaceNode{&FooNode{V: 1}, &FooNode{V: 2}},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use reflect to replace elements
|
||||||
|
v := reflect.ValueOf(c).Elem().FieldByName("Nodes")
|
||||||
|
v.Index(0).Set(reflect.ValueOf(&BarNode{V: 10}))
|
||||||
|
|
||||||
|
switch n := c.Nodes[0].(type) {
|
||||||
|
case *BarNode:
|
||||||
|
println("Set[0] to BarNode:", n.V) // 10
|
||||||
|
default:
|
||||||
|
println("FAIL: expected *BarNode")
|
||||||
|
}
|
||||||
|
switch n := c.Nodes[1].(type) {
|
||||||
|
case *FooNode:
|
||||||
|
println("Set[1] still FooNode:", n.V) // 2
|
||||||
|
default:
|
||||||
|
println("FAIL: expected *FooNode")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var xorshift32State uint32 = 1
|
var xorshift32State uint32 = 1
|
||||||
|
|
||||||
func xorshift32(x uint32) uint32 {
|
func xorshift32(x uint32) uint32 {
|
||||||
|
|||||||
Vendored
+64
@@ -439,6 +439,70 @@ offset for int64 matches: true
|
|||||||
offset for complex128 matches: true
|
offset for complex128 matches: true
|
||||||
type assertion succeeded for unreferenced type
|
type assertion succeeded for unreferenced type
|
||||||
|
|
||||||
|
interface implements
|
||||||
|
concrete implements:
|
||||||
|
myReader → Reader: true
|
||||||
|
*myReader → Reader: true
|
||||||
|
myWriter → Writer: false
|
||||||
|
*myWriter → Writer: true
|
||||||
|
myReadWriter → Reader: true
|
||||||
|
myReadWriter → Writer: false
|
||||||
|
myReadWriter → ReadWriter: false
|
||||||
|
*myReadWriter → Reader: true
|
||||||
|
*myReadWriter → Writer: true
|
||||||
|
*myReadWriter → ReadWriter: true
|
||||||
|
myReader → Closer: false
|
||||||
|
*myReadWriter → Closer: false
|
||||||
|
errorValue → error: true
|
||||||
|
errorValue → Stringer: false
|
||||||
|
myErrorStringer → error: true
|
||||||
|
myErrorStringer → Stringer: true
|
||||||
|
myReader → interface{}: true
|
||||||
|
int → interface{}: true
|
||||||
|
interface implements interface:
|
||||||
|
ReadWriter → Reader: true
|
||||||
|
ReadWriter → Writer: true
|
||||||
|
Reader → ReadWriter: false
|
||||||
|
Writer → ReadWriter: false
|
||||||
|
ReadCloser → Reader: true
|
||||||
|
ReadCloser → Closer: true
|
||||||
|
ReadCloser → Writer: false
|
||||||
|
Reader → ReadCloser: false
|
||||||
|
Reader → Reader: true
|
||||||
|
ReadWriter → ReadWriter: true
|
||||||
|
error → Stringer: false
|
||||||
|
Stringer → error: false
|
||||||
|
Reader → interface{}: true
|
||||||
|
ReadWriter → interface{}: true
|
||||||
|
assignable to:
|
||||||
|
int → int: true
|
||||||
|
string → string: true
|
||||||
|
int → string: false
|
||||||
|
int → int64: false
|
||||||
|
myReader → Reader: true
|
||||||
|
*myWriter → Writer: true
|
||||||
|
myWriter → Writer: false
|
||||||
|
*myReadWriter → ReadWriter: true
|
||||||
|
ReadWriter → Reader: true
|
||||||
|
Reader → ReadWriter: false
|
||||||
|
int → interface{}: true
|
||||||
|
Reader → interface{}: true
|
||||||
|
unexported method interface:
|
||||||
|
*notAnExpr → exprLike: true
|
||||||
|
notAnExpr → exprLike: true
|
||||||
|
*notAnExpr → exprLike (AssignableTo): true
|
||||||
|
channel direction:
|
||||||
|
chan int → <-chan int: true
|
||||||
|
<-chan int → chan int: false
|
||||||
|
named types:
|
||||||
|
*int → IntPtr: true
|
||||||
|
IntPtr → *int: true
|
||||||
|
IntPtr → IntPtr1: false
|
||||||
|
Ch → <-chan interface{}: true
|
||||||
|
value set interface:
|
||||||
|
Set[0] to BarNode: 10
|
||||||
|
Set[1] still FooNode: 2
|
||||||
|
|
||||||
alignment / offset:
|
alignment / offset:
|
||||||
struct{[0]func(); byte}: true
|
struct{[0]func(); byte}: true
|
||||||
|
|
||||||
|
|||||||
+250
-63
@@ -36,6 +36,12 @@ import (
|
|||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// numMethodHasMethodSet is a flag in bit 15 of the numMethod field (uint16) in
|
||||||
|
// Named, Pointer, and Struct type descriptors. When set, an inline method set
|
||||||
|
// is present in the type descriptor. Must match the constant in
|
||||||
|
// src/internal/reflectlite/type.go.
|
||||||
|
const numMethodHasMethodSet = 0x8000
|
||||||
|
|
||||||
// signatureInfo is a Go signature of an interface method. It does not represent
|
// signatureInfo is a Go signature of an interface method. It does not represent
|
||||||
// any method in particular.
|
// any method in particular.
|
||||||
type signatureInfo struct {
|
type signatureInfo struct {
|
||||||
@@ -276,7 +282,7 @@ func (p *lowerInterfacesPass) run() error {
|
|||||||
for _, fn := range interfaceAssertFunctions {
|
for _, fn := range interfaceAssertFunctions {
|
||||||
methodsAttr := fn.GetStringAttributeAtIndex(-1, "tinygo-methods")
|
methodsAttr := fn.GetStringAttributeAtIndex(-1, "tinygo-methods")
|
||||||
itf := p.interfaces[methodsAttr.GetStringValue()]
|
itf := p.interfaces[methodsAttr.GetStringValue()]
|
||||||
p.defineInterfaceImplementsFunc(fn, itf)
|
p.defineInterfaceAssertFunc(fn, itf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
@@ -325,6 +331,49 @@ func (p *lowerInterfacesPass) run() error {
|
|||||||
}
|
}
|
||||||
sort.Strings(typeNames)
|
sort.Strings(typeNames)
|
||||||
|
|
||||||
|
// Check whether runtime.typeImplementsMethodSet still has uses. Now that
|
||||||
|
// interface type assertions have been lowered to type-ID comparison
|
||||||
|
// chains, the only remaining callers would be from reflect
|
||||||
|
// (AssignableTo/Implements). If none remain, we can strip the inline
|
||||||
|
// method-set data from type descriptors to save binary size.
|
||||||
|
stripMethodSets := false
|
||||||
|
typeImplementsFn := p.mod.NamedFunction("runtime.typeImplementsMethodSet")
|
||||||
|
if !typeImplementsFn.IsNil() && !hasUses(typeImplementsFn) {
|
||||||
|
stripMethodSets = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect all method signatures that appear in any interface type
|
||||||
|
// descriptor. When reflect is imported and method sets are kept,
|
||||||
|
// concrete type method sets are pruned: individual methods not in any
|
||||||
|
// interface are removed, and types that can't fully satisfy at least
|
||||||
|
// one interface have their method sets emptied entirely.
|
||||||
|
//
|
||||||
|
// When method sets are stripped entirely (reflect not imported),
|
||||||
|
// methodFilter is nil and filterMethodSet replaces with empty.
|
||||||
|
var methodFilter map[string]struct{}
|
||||||
|
var ifaceMethodSets []map[string]struct{}
|
||||||
|
if !stripMethodSets {
|
||||||
|
methodFilter = make(map[string]struct{})
|
||||||
|
for _, name := range typeNames {
|
||||||
|
if !strings.HasPrefix(name, "interface:") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t := p.types[name]
|
||||||
|
initializer := t.typecode.Initializer()
|
||||||
|
ifaceSet := make(map[string]struct{})
|
||||||
|
for i := 0; i < initializer.Type().StructElementTypesCount(); i++ {
|
||||||
|
field := p.builder.CreateExtractValue(initializer, i, "")
|
||||||
|
for _, sig := range p.extractMethodSigs(field) {
|
||||||
|
methodFilter[sig] = struct{}{}
|
||||||
|
ifaceSet[sig] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(ifaceSet) > 0 {
|
||||||
|
ifaceMethodSets = append(ifaceMethodSets, ifaceSet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
zero := llvm.ConstInt(p.ctx.Int32Type(), 0, false)
|
zero := llvm.ConstInt(p.ctx.Int32Type(), 0, false)
|
||||||
@@ -332,9 +381,39 @@ func (p *lowerInterfacesPass) run() error {
|
|||||||
t := p.types[name]
|
t := p.types[name]
|
||||||
if !t.methodSet.IsNil() {
|
if !t.methodSet.IsNil() {
|
||||||
initializer := t.typecode.Initializer()
|
initializer := t.typecode.Initializer()
|
||||||
|
numFields := initializer.Type().StructElementTypesCount()
|
||||||
|
|
||||||
|
// Read numMethods from the original type descriptor (index 2:
|
||||||
|
// after prefix pointer at 0 and kind byte at 1). For Named,
|
||||||
|
// Pointer, and Struct types, the numMethodHasMethodSet flag
|
||||||
|
// indicates that an inline method set is present.
|
||||||
|
var numMethodsConst uint64
|
||||||
|
var numMethodsIsI16 bool
|
||||||
|
if numFields > 2 {
|
||||||
|
nmField := p.builder.CreateExtractValue(initializer, 2, "")
|
||||||
|
if nmField.Type() == p.ctx.Int16Type() {
|
||||||
|
numMethodsConst = nmField.ZExtValue()
|
||||||
|
numMethodsIsI16 = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var newInitializerFields []llvm.Value
|
var newInitializerFields []llvm.Value
|
||||||
for i := 1; i < initializer.Type().StructElementTypesCount(); i++ {
|
for i := 1; i < numFields; i++ {
|
||||||
newInitializerFields = append(newInitializerFields, p.builder.CreateExtractValue(initializer, i, ""))
|
field := p.builder.CreateExtractValue(initializer, i, "")
|
||||||
|
field = p.filterMethodSet(field, methodFilter, ifaceMethodSets)
|
||||||
|
// Strip empty inline method sets for Named, Pointer, and
|
||||||
|
// Struct types. When the method set is pruned to empty, we
|
||||||
|
// remove it and clear the numMethodHasMethodSet flag (bit 15
|
||||||
|
// of numMethod) so the runtime skips reading it.
|
||||||
|
if numMethodsIsI16 && numMethodsConst&numMethodHasMethodSet != 0 && p.isMethodSetType(field.Type()) {
|
||||||
|
elems := field.Type().StructElementTypes()
|
||||||
|
if elems[1].ArrayLength() == 0 {
|
||||||
|
clearedNumMethods := numMethodsConst & ^uint64(numMethodHasMethodSet)
|
||||||
|
newInitializerFields[1] = llvm.ConstInt(p.ctx.Int16Type(), clearedNumMethods, false)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newInitializerFields = append(newInitializerFields, field)
|
||||||
}
|
}
|
||||||
newInitializer := p.ctx.ConstStruct(newInitializerFields, false)
|
newInitializer := p.ctx.ConstStruct(newInitializerFields, false)
|
||||||
typecodeName := t.typecode.Name()
|
typecodeName := t.typecode.Name()
|
||||||
@@ -428,66 +507,6 @@ func (p *lowerInterfacesPass) getSignature(name string) *signatureInfo {
|
|||||||
return p.signatures[name]
|
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
|
// defineInterfaceMethodFunc defines this thunk by calling the concrete method
|
||||||
// of the type that implements this interface.
|
// of the type that implements this interface.
|
||||||
//
|
//
|
||||||
@@ -592,3 +611,171 @@ func (p *lowerInterfacesPass) getDIFile(file string) llvm.Metadata {
|
|||||||
}
|
}
|
||||||
return difile
|
return difile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// defineInterfaceAssertFunc defines a $typeassert function for the given
|
||||||
|
// interface. The function returns true if the concrete type (passed as a
|
||||||
|
// type-ID pointer) implements the interface, using a chain of type-ID
|
||||||
|
// comparisons. This avoids pulling in runtime.typeImplementsMethodSet for
|
||||||
|
// programs that don't use reflect.
|
||||||
|
func (p *lowerInterfacesPass) defineInterfaceAssertFunc(fn llvm.Value, itf *interfaceInfo) {
|
||||||
|
actualType := fn.FirstParam()
|
||||||
|
actualType.SetName("actualType")
|
||||||
|
fn.SetLinkage(llvm.InternalLinkage)
|
||||||
|
fn.SetUnnamedAddr(true)
|
||||||
|
AddStandardAttributes(fn, p.config)
|
||||||
|
|
||||||
|
entry := p.ctx.AddBasicBlock(fn, "entry")
|
||||||
|
p.builder.SetInsertPointAtEnd(entry)
|
||||||
|
|
||||||
|
if p.dibuilder != nil {
|
||||||
|
difile := p.getDIFile("<Go interface type assert>")
|
||||||
|
diFuncType := p.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
|
||||||
|
File: difile,
|
||||||
|
})
|
||||||
|
difunc := p.dibuilder.CreateFunction(difile, llvm.DIFunction{
|
||||||
|
Name: "(Go interface type 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{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build an OR chain: return (type == T1) || (type == T2) || ...
|
||||||
|
llvmFalse := llvm.ConstInt(p.ctx.Int1Type(), 0, false)
|
||||||
|
result := llvmFalse
|
||||||
|
for _, typ := range itf.types {
|
||||||
|
cmp := p.builder.CreateICmp(llvm.IntEQ, actualType, typ.typecodeGEP, typ.name+".icmp")
|
||||||
|
result = p.builder.CreateOr(result, cmp, "")
|
||||||
|
}
|
||||||
|
p.builder.CreateRet(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isMethodSetType reports whether ty has the shape of a method-set struct:
|
||||||
|
// { uintptr, [N x ptr] }.
|
||||||
|
func (p *lowerInterfacesPass) isMethodSetType(ty llvm.Type) bool {
|
||||||
|
if ty.TypeKind() != llvm.StructTypeKind {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
elems := ty.StructElementTypes()
|
||||||
|
if len(elems) != 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if elems[0] != p.uintptrType {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return elems[1].TypeKind() == llvm.ArrayTypeKind && elems[1].ElementType() == p.ptrType
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractMethodSigs returns the names of method signature globals inside a
|
||||||
|
// method-set field ({ uintptr, [N x ptr] }). Returns nil if field is not a
|
||||||
|
// method set.
|
||||||
|
func (p *lowerInterfacesPass) extractMethodSigs(field llvm.Value) []string {
|
||||||
|
if !p.isMethodSetType(field.Type()) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
methodArray := p.builder.CreateExtractValue(field, 1, "")
|
||||||
|
n := methodArray.Type().ArrayLength()
|
||||||
|
sigs := make([]string, 0, n)
|
||||||
|
for j := 0; j < n; j++ {
|
||||||
|
sig := p.builder.CreateExtractValue(methodArray, j, "")
|
||||||
|
sig = stripPointerCasts(sig)
|
||||||
|
sigs = append(sigs, sig.Name())
|
||||||
|
}
|
||||||
|
return sigs
|
||||||
|
}
|
||||||
|
|
||||||
|
// filterMethodSet processes a type-descriptor field that may be a method set.
|
||||||
|
// Non-method-set fields are returned unchanged.
|
||||||
|
//
|
||||||
|
// If keepSigs is nil, the method set is replaced with an empty one (strip mode,
|
||||||
|
// used when reflect is not imported). If keepSigs is non-nil, the method set is
|
||||||
|
// pruned in two stages: first, methods not in keepSigs (the union of all
|
||||||
|
// interface signatures) are removed; then, if the remaining methods cannot
|
||||||
|
// fully satisfy at least one interface in ifaceSets, the entire method set is
|
||||||
|
// emptied.
|
||||||
|
func (p *lowerInterfacesPass) filterMethodSet(field llvm.Value, keepSigs map[string]struct{}, ifaceSets []map[string]struct{}) llvm.Value {
|
||||||
|
if !p.isMethodSetType(field.Type()) {
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
|
||||||
|
methodArray := p.builder.CreateExtractValue(field, 1, "")
|
||||||
|
numMethods := methodArray.Type().ArrayLength()
|
||||||
|
|
||||||
|
// Strip mode: replace with empty method set.
|
||||||
|
if keepSigs == nil {
|
||||||
|
return p.ctx.ConstStruct([]llvm.Value{
|
||||||
|
llvm.ConstInt(p.uintptrType, 0, false),
|
||||||
|
llvm.ConstArray(p.ptrType, nil),
|
||||||
|
}, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if numMethods == 0 {
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract all methods and their signature names.
|
||||||
|
type methodEntry struct {
|
||||||
|
value llvm.Value
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
entries := make([]methodEntry, numMethods)
|
||||||
|
nameSet := make(map[string]struct{}, numMethods)
|
||||||
|
for j := 0; j < numMethods; j++ {
|
||||||
|
sig := p.builder.CreateExtractValue(methodArray, j, "")
|
||||||
|
stripped := stripPointerCasts(sig)
|
||||||
|
name := stripped.Name()
|
||||||
|
entries[j] = methodEntry{sig, name}
|
||||||
|
nameSet[name] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether this type can fully implement at least one interface.
|
||||||
|
// If not, its method set can never produce a true result from
|
||||||
|
// typeImplementsMethodSet, so we can empty it entirely.
|
||||||
|
implementsAny := false
|
||||||
|
for _, ifaceSet := range ifaceSets {
|
||||||
|
if isSubsetOf(ifaceSet, nameSet) {
|
||||||
|
implementsAny = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !implementsAny {
|
||||||
|
return p.ctx.ConstStruct([]llvm.Value{
|
||||||
|
llvm.ConstInt(p.uintptrType, 0, false),
|
||||||
|
llvm.ConstArray(p.ptrType, nil),
|
||||||
|
}, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prune: keep only methods whose signature appears in keepSigs.
|
||||||
|
var kept []llvm.Value
|
||||||
|
for _, e := range entries {
|
||||||
|
if _, ok := keepSigs[e.name]; ok {
|
||||||
|
kept = append(kept, e.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(kept) == numMethods {
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.ctx.ConstStruct([]llvm.Value{
|
||||||
|
llvm.ConstInt(p.uintptrType, uint64(len(kept)), false),
|
||||||
|
llvm.ConstArray(p.ptrType, kept),
|
||||||
|
}, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isSubsetOf reports whether every key in sub is also in super.
|
||||||
|
func isSubsetOf(sub, super map[string]struct{}) bool {
|
||||||
|
for k := range sub {
|
||||||
|
if _, ok := super[k]; !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|||||||
@@ -65,7 +65,6 @@ func Optimize(mod llvm.Module, config *compileopts.Config) []error {
|
|||||||
|
|
||||||
// Run TinyGo-specific optimization passes.
|
// Run TinyGo-specific optimization passes.
|
||||||
OptimizeStringToBytes(mod)
|
OptimizeStringToBytes(mod)
|
||||||
OptimizeReflectImplements(mod)
|
|
||||||
maxStackSize := config.MaxStackAlloc()
|
maxStackSize := config.MaxStackAlloc()
|
||||||
OptimizeAllocs(mod, nil, maxStackSize, nil)
|
OptimizeAllocs(mod, nil, maxStackSize, nil)
|
||||||
err = LowerInterfaces(mod, config)
|
err = LowerInterfaces(mod, config)
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ package transform
|
|||||||
// calls.
|
// calls.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
|
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -100,81 +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) {
|
|
||||||
implementsSignature1 := mod.NamedGlobal("reflect/methods.Implements(reflect.Type) bool")
|
|
||||||
implementsSignature2 := mod.NamedGlobal("reflect/methods.Implements(internal/reflectlite.Type) bool")
|
|
||||||
if implementsSignature1.IsNil() && implementsSignature2.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
|
|
||||||
}
|
|
||||||
val := attr.GetStringValue()
|
|
||||||
if val == "reflect/methods.Implements(reflect.Type) bool" || val == "reflect/methods.Implements(internal/reflectlite.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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -22,11 +22,3 @@ func TestOptimizeStringEqual(t *testing.T) {
|
|||||||
transform.OptimizeStringEqual(mod)
|
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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
Vendored
-29
@@ -7,7 +7,6 @@ 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" = linkonce_odr constant { i8, ptr } { i8 2, ptr @"reflect/types.type:pointer:basic:int" }, align 4
|
@"reflect/types.type:basic:int" = linkonce_odr constant { i8, ptr } { i8 2, ptr @"reflect/types.type:pointer:basic:int" }, align 4
|
||||||
@"reflect/types.type:pointer:basic:int" = linkonce_odr constant { i8, ptr } { i8 21, ptr @"reflect/types.type:basic:int" }, align 4
|
@"reflect/types.type:pointer:basic:int" = linkonce_odr constant { i8, ptr } { i8 21, ptr @"reflect/types.type:basic:int" }, align 4
|
||||||
@"reflect/methods.NeverImplementedMethod()" = linkonce_odr constant i8 0
|
|
||||||
@"reflect/methods.Double() int" = linkonce_odr constant i8 0
|
@"reflect/methods.Double() int" = linkonce_odr constant i8 0
|
||||||
@"Number$methodset" = linkonce_odr unnamed_addr constant { i32, [1 x ptr], { ptr } } { i32 1, [1 x ptr] [ptr @"reflect/methods.Double() int"], { ptr } { ptr @"(Number).Double$invoke" } }
|
@"Number$methodset" = linkonce_odr unnamed_addr constant { i32, [1 x ptr], { ptr } } { i32 1, [1 x ptr] [ptr @"reflect/methods.Double() int"], { ptr } { ptr @"(Number).Double$invoke" } }
|
||||||
@"reflect/types.type:named:Number" = linkonce_odr constant { ptr, i8, ptr, ptr } { ptr @"Number$methodset", i8 34, ptr @"reflect/types.type:pointer:named:Number", ptr @"reflect/types.type:basic:int" }, align 4
|
@"reflect/types.type:named:Number" = linkonce_odr constant { ptr, i8, ptr, ptr } { ptr @"Number$methodset", i8 34, ptr @"reflect/types.type:pointer:named:Number", ptr @"reflect/types.type:basic:int" }, align 4
|
||||||
@@ -16,10 +15,7 @@ target triple = "armv7m-none-eabi"
|
|||||||
declare i1 @runtime.typeAssert(ptr, ptr)
|
declare i1 @runtime.typeAssert(ptr, ptr)
|
||||||
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.printptr(i32)
|
|
||||||
declare void @runtime.printnl()
|
declare void @runtime.printnl()
|
||||||
declare void @runtime.nilPanic(ptr)
|
|
||||||
|
|
||||||
define void @printInterfaces() {
|
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:int", ptr inttoptr (i32 5 to ptr))
|
||||||
@@ -30,25 +26,6 @@ define void @printInterfaces() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
define void @printInterface(ptr %typecode, ptr %value) {
|
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")
|
%isByte = call i1 @runtime.typeAssert(ptr %typecode, ptr nonnull @"reflect/types.typeid:basic:uint8")
|
||||||
br i1 %isByte, label %typeswitch.byte, label %typeswitch.notByte
|
br i1 %isByte, label %typeswitch.byte, label %typeswitch.notByte
|
||||||
|
|
||||||
@@ -86,10 +63,4 @@ define i32 @"(Number).Double$invoke"(ptr %receiverPtr, ptr %context) {
|
|||||||
|
|
||||||
declare i32 @"Doubler.Double$invoke"(ptr %receiver, ptr %typecode, ptr %context) #0
|
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 #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()" }
|
|
||||||
|
|||||||
Vendored
+2
-65
@@ -12,14 +12,8 @@ declare void @runtime.printuint8(i8)
|
|||||||
|
|
||||||
declare void @runtime.printint16(i16)
|
declare void @runtime.printint16(i16)
|
||||||
|
|
||||||
declare void @runtime.printint32(i32)
|
|
||||||
|
|
||||||
declare void @runtime.printptr(i32)
|
|
||||||
|
|
||||||
declare void @runtime.printnl()
|
declare void @runtime.printnl()
|
||||||
|
|
||||||
declare void @runtime.nilPanic(ptr)
|
|
||||||
|
|
||||||
define void @printInterfaces() {
|
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:int", ptr inttoptr (i32 5 to ptr))
|
||||||
call void @printInterface(ptr @"reflect/types.type:basic:uint8", ptr inttoptr (i8 120 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) {
|
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
|
%typeassert.ok = icmp eq ptr @"reflect/types.type:basic:uint8", %typecode
|
||||||
br i1 %typeassert.ok, label %typeswitch.byte, label %typeswitch.notByte
|
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
|
%byte = ptrtoint ptr %value to i8
|
||||||
call void @runtime.printuint8(i8 %byte)
|
call void @runtime.printuint8(i8 %byte)
|
||||||
call void @runtime.printnl()
|
call void @runtime.printnl()
|
||||||
ret void
|
ret void
|
||||||
|
|
||||||
typeswitch.notByte: ; preds = %typeswitch.notDoubler
|
typeswitch.notByte: ; preds = %0
|
||||||
br i1 false, label %typeswitch.int16, label %typeswitch.notInt16
|
br i1 false, label %typeswitch.int16, label %typeswitch.notInt16
|
||||||
|
|
||||||
typeswitch.int16: ; preds = %typeswitch.notByte
|
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 = call i32 @"(Number).Double"(i32 %receiver, ptr undef)
|
||||||
ret i32 %ret
|
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
@@ -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
@@ -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" }
|
|
||||||
Reference in New Issue
Block a user