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:
Jake Bailey
2026-04-17 12:57:03 -07:00
committed by GitHub
parent b792c10680
commit 5ba8766cbc
16 changed files with 853 additions and 438 deletions
+250 -63
View File
@@ -36,6 +36,12 @@ import (
"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
// any method in particular.
type signatureInfo struct {
@@ -276,7 +282,7 @@ func (p *lowerInterfacesPass) run() error {
for _, fn := range interfaceAssertFunctions {
methodsAttr := fn.GetStringAttributeAtIndex(-1, "tinygo-methods")
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
@@ -325,6 +331,49 @@ func (p *lowerInterfacesPass) run() error {
}
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
// optimizations if they are left in place.
zero := llvm.ConstInt(p.ctx.Int32Type(), 0, false)
@@ -332,9 +381,39 @@ func (p *lowerInterfacesPass) run() error {
t := p.types[name]
if !t.methodSet.IsNil() {
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
for i := 1; i < initializer.Type().StructElementTypesCount(); i++ {
newInitializerFields = append(newInitializerFields, p.builder.CreateExtractValue(initializer, i, ""))
for i := 1; i < numFields; 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)
typecodeName := t.typecode.Name()
@@ -428,66 +507,6 @@ func (p *lowerInterfacesPass) getSignature(name string) *signatureInfo {
return p.signatures[name]
}
// defineInterfaceImplementsFunc defines the interface type assert function. It
// checks whether the given interface type (passed as an argument) is one of the
// types it implements.
//
// The type match is implemented using an if/else chain over all possible types.
// This if/else chain is easily converted to a big switch over all possible
// types by the LLVM simplifycfg pass.
func (p *lowerInterfacesPass) defineInterfaceImplementsFunc(fn llvm.Value, itf *interfaceInfo) {
// Create the function and function signature.
fn.Param(0).SetName("actualType")
fn.SetLinkage(llvm.InternalLinkage)
fn.SetUnnamedAddr(true)
AddStandardAttributes(fn, p.config)
// Start the if/else chain at the entry block.
entry := p.ctx.AddBasicBlock(fn, "entry")
thenBlock := p.ctx.AddBasicBlock(fn, "then")
p.builder.SetInsertPointAtEnd(entry)
if p.dibuilder != nil {
difile := p.getDIFile("<Go interface assert>")
diFuncType := p.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
File: difile,
})
difunc := p.dibuilder.CreateFunction(difile, llvm.DIFunction{
Name: "(Go interface assert)",
File: difile,
Line: 0,
Type: diFuncType,
LocalToUnit: true,
IsDefinition: true,
ScopeLine: 0,
Flags: llvm.FlagPrototyped,
Optimized: true,
})
fn.SetSubprogram(difunc)
p.builder.SetCurrentDebugLocation(0, 0, difunc, llvm.Metadata{})
}
// Iterate over all possible types. Each iteration creates a new branch
// either to the 'then' block (success) or the .next block, for the next
// check.
actualType := fn.Param(0)
for _, typ := range itf.types {
nextBlock := p.ctx.AddBasicBlock(fn, typ.name+".next")
cmp := p.builder.CreateICmp(llvm.IntEQ, actualType, typ.typecodeGEP, typ.name+".icmp")
p.builder.CreateCondBr(cmp, thenBlock, nextBlock)
p.builder.SetInsertPointAtEnd(nextBlock)
}
// The builder is now inserting at the last *.next block. Once we reach
// this point, all types have been checked so the type assert will have
// failed.
p.builder.CreateRet(llvm.ConstInt(p.ctx.Int1Type(), 0, false))
// Fill 'then' block (type assert was successful).
p.builder.SetInsertPointAtEnd(thenBlock)
p.builder.CreateRet(llvm.ConstInt(p.ctx.Int1Type(), 1, false))
}
// defineInterfaceMethodFunc defines this thunk by calling the concrete method
// of the type that implements this interface.
//
@@ -592,3 +611,171 @@ func (p *lowerInterfacesPass) getDIFile(file string) llvm.Metadata {
}
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
}
-1
View File
@@ -65,7 +65,6 @@ func Optimize(mod llvm.Module, config *compileopts.Config) []error {
// Run TinyGo-specific optimization passes.
OptimizeStringToBytes(mod)
OptimizeReflectImplements(mod)
maxStackSize := config.MaxStackAlloc()
OptimizeAllocs(mod, nil, maxStackSize, nil)
err = LowerInterfaces(mod, config)
-80
View File
@@ -4,8 +4,6 @@ package transform
// calls.
import (
"strings"
"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()
}
}
-8
View File
@@ -22,11 +22,3 @@ func TestOptimizeStringEqual(t *testing.T) {
transform.OptimizeStringEqual(mod)
})
}
func TestOptimizeReflectImplements(t *testing.T) {
t.Parallel()
testTransform(t, "testdata/reflect-implements", func(mod llvm.Module) {
// Run optimization pass.
transform.OptimizeReflectImplements(mod)
})
}
-29
View File
@@ -7,7 +7,6 @@ target triple = "armv7m-none-eabi"
@"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: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
@"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
@@ -16,10 +15,7 @@ target triple = "armv7m-none-eabi"
declare i1 @runtime.typeAssert(ptr, ptr)
declare void @runtime.printuint8(i8)
declare void @runtime.printint16(i16)
declare void @runtime.printint32(i32)
declare void @runtime.printptr(i32)
declare void @runtime.printnl()
declare void @runtime.nilPanic(ptr)
define void @printInterfaces() {
call void @printInterface(ptr @"reflect/types.type:basic:int", ptr inttoptr (i32 5 to ptr))
@@ -30,25 +26,6 @@ define void @printInterfaces() {
}
define void @printInterface(ptr %typecode, ptr %value) {
%isUnmatched = call i1 @Unmatched$typeassert(ptr %typecode)
br i1 %isUnmatched, label %typeswitch.Unmatched, label %typeswitch.notUnmatched
typeswitch.Unmatched:
%unmatched = ptrtoint ptr %value to i32
call void @runtime.printptr(i32 %unmatched)
call void @runtime.printnl()
ret void
typeswitch.notUnmatched:
%isDoubler = call i1 @Doubler$typeassert(ptr %typecode)
br i1 %isDoubler, label %typeswitch.Doubler, label %typeswitch.notDoubler
typeswitch.Doubler:
%doubler.result = call i32 @"Doubler.Double$invoke"(ptr %value, ptr %typecode, ptr undef)
call void @runtime.printint32(i32 %doubler.result)
ret void
typeswitch.notDoubler:
%isByte = call i1 @runtime.typeAssert(ptr %typecode, ptr nonnull @"reflect/types.typeid:basic:uint8")
br i1 %isByte, label %typeswitch.byte, label %typeswitch.notByte
@@ -86,10 +63,4 @@ define i32 @"(Number).Double$invoke"(ptr %receiverPtr, ptr %context) {
declare i32 @"Doubler.Double$invoke"(ptr %receiver, ptr %typecode, ptr %context) #0
declare i1 @Doubler$typeassert(ptr %typecode) #1
declare i1 @Unmatched$typeassert(ptr %typecode) #2
attributes #0 = { "tinygo-invoke"="reflect/methods.Double() int" "tinygo-methods"="reflect/methods.Double() int" }
attributes #1 = { "tinygo-methods"="reflect/methods.Double() int" }
attributes #2 = { "tinygo-methods"="reflect/methods.NeverImplementedMethod()" }
+2 -65
View File
@@ -12,14 +12,8 @@ declare void @runtime.printuint8(i8)
declare void @runtime.printint16(i16)
declare void @runtime.printint32(i32)
declare void @runtime.printptr(i32)
declare void @runtime.printnl()
declare void @runtime.nilPanic(ptr)
define void @printInterfaces() {
call void @printInterface(ptr @"reflect/types.type:basic:int", ptr inttoptr (i32 5 to ptr))
call void @printInterface(ptr @"reflect/types.type:basic:uint8", ptr inttoptr (i8 120 to ptr))
@@ -28,35 +22,16 @@ define void @printInterfaces() {
}
define void @printInterface(ptr %typecode, ptr %value) {
%isUnmatched = call i1 @"Unmatched$typeassert"(ptr %typecode)
br i1 %isUnmatched, label %typeswitch.Unmatched, label %typeswitch.notUnmatched
typeswitch.Unmatched: ; preds = %0
%unmatched = ptrtoint ptr %value to i32
call void @runtime.printptr(i32 %unmatched)
call void @runtime.printnl()
ret void
typeswitch.notUnmatched: ; preds = %0
%isDoubler = call i1 @"Doubler$typeassert"(ptr %typecode)
br i1 %isDoubler, label %typeswitch.Doubler, label %typeswitch.notDoubler
typeswitch.Doubler: ; preds = %typeswitch.notUnmatched
%doubler.result = call i32 @"Doubler.Double$invoke"(ptr %value, ptr %typecode, ptr undef)
call void @runtime.printint32(i32 %doubler.result)
ret void
typeswitch.notDoubler: ; preds = %typeswitch.notUnmatched
%typeassert.ok = icmp eq ptr @"reflect/types.type:basic:uint8", %typecode
br i1 %typeassert.ok, label %typeswitch.byte, label %typeswitch.notByte
typeswitch.byte: ; preds = %typeswitch.notDoubler
typeswitch.byte: ; preds = %0
%byte = ptrtoint ptr %value to i8
call void @runtime.printuint8(i8 %byte)
call void @runtime.printnl()
ret void
typeswitch.notByte: ; preds = %typeswitch.notDoubler
typeswitch.notByte: ; preds = %0
br i1 false, label %typeswitch.int16, label %typeswitch.notInt16
typeswitch.int16: ; preds = %typeswitch.notByte
@@ -79,41 +54,3 @@ define i32 @"(Number).Double$invoke"(ptr %receiverPtr, ptr %context) {
%ret = call i32 @"(Number).Double"(i32 %receiver, ptr undef)
ret i32 %ret
}
define internal i32 @"Doubler.Double$invoke"(ptr %receiver, ptr %actualType, ptr %context) unnamed_addr #0 {
entry:
%"named:Number.icmp" = icmp eq ptr %actualType, @"reflect/types.type:named:Number"
br i1 %"named:Number.icmp", label %"named:Number", label %"named:Number.next"
"named:Number": ; preds = %entry
%0 = call i32 @"(Number).Double$invoke"(ptr %receiver, ptr undef)
ret i32 %0
"named:Number.next": ; preds = %entry
call void @runtime.nilPanic(ptr undef)
unreachable
}
define internal i1 @"Doubler$typeassert"(ptr %actualType) unnamed_addr #1 {
entry:
%"named:Number.icmp" = icmp eq ptr %actualType, @"reflect/types.type:named:Number"
br i1 %"named:Number.icmp", label %then, label %"named:Number.next"
then: ; preds = %entry
ret i1 true
"named:Number.next": ; preds = %entry
ret i1 false
}
define internal i1 @"Unmatched$typeassert"(ptr %actualType) unnamed_addr #2 {
entry:
ret i1 false
then: ; No predecessors!
ret i1 true
}
attributes #0 = { "tinygo-invoke"="reflect/methods.Double() int" "tinygo-methods"="reflect/methods.Double() int" }
attributes #1 = { "tinygo-methods"="reflect/methods.Double() int" }
attributes #2 = { "tinygo-methods"="reflect/methods.NeverImplementedMethod()" }
-41
View File
@@ -1,41 +0,0 @@
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"
%runtime._interface = type { ptr, ptr }
@"reflect/types.type:named:error" = internal constant { i8, i16, ptr, ptr } { i8 52, i16 0, ptr @"reflect/types.type:pointer:named:error", ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}" }, align 4
@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = internal constant { i8, ptr } { i8 20, ptr @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" }, align 4
@"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = internal constant { i8, ptr } { i8 21, ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}" }, align 4
@"reflect/types.type:pointer:named:error" = internal constant { i8, i16, ptr } { i8 21, i16 0, ptr @"reflect/types.type:named:error" }, align 4
@"reflect/types.type:pointer:named:reflect.rawType" = internal constant { ptr, i8, i16, ptr } { ptr null, i8 21, i16 0, ptr null }, align 4
@"reflect/methods.Implements(reflect.Type) bool" = internal constant i8 0, align 1
; var errorType = reflect.TypeOf((*error)(nil)).Elem()
; func isError(typ reflect.Type) bool {
; return typ.Implements(errorType)
; }
; The type itself is stored in %typ.value, %typ.typecode just refers to the
; type of reflect.Type. This function can be optimized because errorType is
; known at compile time (after the interp pass has run).
define i1 @main.isError(ptr %typ.typecode, ptr %typ.value, ptr %context) {
entry:
%result = call i1 @"reflect.Type.Implements$invoke"(ptr %typ.value, ptr getelementptr inbounds ({ ptr, i8, ptr }, ptr @"reflect/types.type:pointer:named:reflect.rawType", i32 0, i32 1), ptr @"reflect/types.type:named:error", ptr %typ.typecode, ptr undef)
ret i1 %result
}
; This Implements method call can not be optimized because itf is not known at
; compile time.
; func isUnknown(typ, itf reflect.Type) bool {
; return typ.Implements(itf)
; }
define i1 @main.isUnknown(ptr %typ.typecode, ptr %typ.value, ptr %itf.typecode, ptr %itf.value, ptr %context) {
entry:
%result = call i1 @"reflect.Type.Implements$invoke"(ptr %typ.value, ptr %itf.typecode, ptr %itf.value, ptr %typ.typecode, ptr undef)
ret i1 %result
}
declare i1 @"reflect.Type.Implements$invoke"(ptr, ptr, ptr, ptr, ptr) #0
declare i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(ptr %0) #1
attributes #0 = { "tinygo-invoke"="reflect/methods.Implements(reflect.Type) bool" "tinygo-methods"="reflect/methods.Align() int; reflect/methods.Implements(reflect.Type) bool" }
attributes #1 = { "tinygo-methods"="reflect/methods.Error() string" }
-28
View File
@@ -1,28 +0,0 @@
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"
@"reflect/types.type:named:error" = internal constant { i8, i16, ptr, ptr } { i8 52, i16 0, ptr @"reflect/types.type:pointer:named:error", ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}" }, align 4
@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = internal constant { i8, ptr } { i8 20, ptr @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" }, align 4
@"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = internal constant { i8, ptr } { i8 21, ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}" }, align 4
@"reflect/types.type:pointer:named:error" = internal constant { i8, i16, ptr } { i8 21, i16 0, ptr @"reflect/types.type:named:error" }, align 4
@"reflect/types.type:pointer:named:reflect.rawType" = internal constant { ptr, i8, i16, ptr } { ptr null, i8 21, i16 0, ptr null }, align 4
@"reflect/methods.Implements(reflect.Type) bool" = internal constant i8 0, align 1
define i1 @main.isError(ptr %typ.typecode, ptr %typ.value, ptr %context) {
entry:
%0 = call i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(ptr %typ.value)
ret i1 %0
}
define i1 @main.isUnknown(ptr %typ.typecode, ptr %typ.value, ptr %itf.typecode, ptr %itf.value, ptr %context) {
entry:
%result = call i1 @"reflect.Type.Implements$invoke"(ptr %typ.value, ptr %itf.typecode, ptr %itf.value, ptr %typ.typecode, ptr undef)
ret i1 %result
}
declare i1 @"reflect.Type.Implements$invoke"(ptr, ptr, ptr, ptr, ptr) #0
declare i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(ptr) #1
attributes #0 = { "tinygo-invoke"="reflect/methods.Implements(reflect.Type) bool" "tinygo-methods"="reflect/methods.Align() int; reflect/methods.Implements(reflect.Type) bool" }
attributes #1 = { "tinygo-methods"="reflect/methods.Error() string" }