diff --git a/compiler/llvm.go b/compiler/llvm.go index 3bd9eeee..07895665 100644 --- a/compiler/llvm.go +++ b/compiler/llvm.go @@ -1,8 +1,6 @@ package compiler import ( - "reflect" - "github.com/tinygo-org/tinygo/compiler/llvmutil" "tinygo.org/x/go-llvm" ) @@ -130,43 +128,14 @@ func (c *Compiler) splitBasicBlock(afterInst llvm.Value, insertAfter llvm.BasicB // contents, and returns the global. // Note that it is left with the default linkage etc., you should set // linkage/constant/etc properties yourself. -func (c *Compiler) makeGlobalArray(bufItf interface{}, name string, elementType llvm.Type) llvm.Value { - buf := reflect.ValueOf(bufItf) - globalType := llvm.ArrayType(elementType, buf.Len()) +func (c *Compiler) makeGlobalArray(buf []byte, name string, elementType llvm.Type) llvm.Value { + globalType := llvm.ArrayType(elementType, len(buf)) global := llvm.AddGlobal(c.mod, globalType, name) value := llvm.Undef(globalType) - for i := 0; i < buf.Len(); i++ { - ch := buf.Index(i).Uint() + for i := 0; i < len(buf); i++ { + ch := uint64(buf[i]) value = llvm.ConstInsertValue(value, llvm.ConstInt(elementType, ch, false), []uint32{uint32(i)}) } global.SetInitializer(value) return global } - -// getGlobalBytes returns the slice contained in the array of the provided -// global. It can recover the bytes originally created using makeGlobalArray, if -// makeGlobalArray was given a byte slice. -func getGlobalBytes(global llvm.Value) []byte { - value := global.Initializer() - buf := make([]byte, value.Type().ArrayLength()) - for i := range buf { - buf[i] = byte(llvm.ConstExtractValue(value, []uint32{uint32(i)}).ZExtValue()) - } - return buf -} - -// replaceGlobalByteWithArray replaces a global integer type in the module with -// an integer array, using a GEP to make the types match. It is a convenience -// function used for creating reflection sidetables, for example. -func (c *Compiler) replaceGlobalIntWithArray(name string, buf interface{}) llvm.Value { - oldGlobal := c.mod.NamedGlobal(name) - global := c.makeGlobalArray(buf, name+".tmp", oldGlobal.Type().ElementType()) - gep := llvm.ConstGEP(global, []llvm.Value{ - llvm.ConstInt(c.ctx.Int32Type(), 0, false), - llvm.ConstInt(c.ctx.Int32Type(), 0, false), - }) - oldGlobal.ReplaceAllUsesWith(gep) - oldGlobal.EraseFromParentAsGlobal() - global.SetName(name) - return global -} diff --git a/compiler/llvmutil/wordpack.go b/compiler/llvmutil/wordpack.go index 1cbada7e..0c1c951f 100644 --- a/compiler/llvmutil/wordpack.go +++ b/compiler/llvmutil/wordpack.go @@ -45,10 +45,18 @@ func EmitPointerPack(builder llvm.Builder, mod llvm.Module, config *compileopts. // Packed data is bigger than a pointer, so allocate it on the heap. sizeValue := llvm.ConstInt(uintptrType, size, false) alloc := mod.NamedFunction("runtime.alloc") - packedHeapAlloc = builder.CreateCall(alloc, []llvm.Value{sizeValue}, "") + packedHeapAlloc = builder.CreateCall(alloc, []llvm.Value{ + sizeValue, + llvm.Undef(i8ptrType), // unused context parameter + llvm.ConstPointerNull(i8ptrType), // coroutine handle + }, "") if config.NeedsStackObjects() { trackPointer := mod.NamedFunction("runtime.trackPointer") - builder.CreateCall(trackPointer, []llvm.Value{packedHeapAlloc}, "") + builder.CreateCall(trackPointer, []llvm.Value{ + packedHeapAlloc, + llvm.Undef(i8ptrType), // unused context parameter + llvm.ConstPointerNull(i8ptrType), // coroutine handle + }, "") } packedAlloc = builder.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "") } diff --git a/compiler/optimizer.go b/compiler/optimizer.go index c7fd0ba3..0b319e3d 100644 --- a/compiler/optimizer.go +++ b/compiler/optimizer.go @@ -55,7 +55,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro transform.OptimizeMaps(c.mod) transform.OptimizeStringToBytes(c.mod) transform.OptimizeAllocs(c.mod) - c.LowerInterfaces() + transform.LowerInterfaces(c.mod) c.LowerFuncValues() // After interfaces are lowered, there are many more opportunities for @@ -89,7 +89,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro } } else { // Must be run at any optimization level. - c.LowerInterfaces() + transform.LowerInterfaces(c.mod) c.LowerFuncValues() err := c.LowerGoroutines() if err != nil { diff --git a/compiler/interface-lowering.go b/transform/interface-lowering.go similarity index 95% rename from compiler/interface-lowering.go rename to transform/interface-lowering.go index 1aa9862f..b75054aa 100644 --- a/compiler/interface-lowering.go +++ b/transform/interface-lowering.go @@ -1,4 +1,4 @@ -package compiler +package transform // This file provides function to lower interface intrinsics to their final LLVM // form, optimizing them in the process. @@ -38,6 +38,7 @@ import ( "sort" "strings" + "github.com/tinygo-org/tinygo/compiler/llvmutil" "tinygo.org/x/go-llvm" ) @@ -133,22 +134,28 @@ func (itf *interfaceInfo) id() string { // pass has been implemented as an object type because of its complexity, but // should be seen as a regular function call (see LowerInterfaces). type lowerInterfacesPass struct { - *Compiler - types map[string]*typeInfo - signatures map[string]*signatureInfo - interfaces map[string]*interfaceInfo + mod llvm.Module + builder llvm.Builder + ctx llvm.Context + uintptrType llvm.Type + types map[string]*typeInfo + signatures map[string]*signatureInfo + interfaces map[string]*interfaceInfo } -// Lower all interface functions. They are emitted by the compiler as -// higher-level intrinsics that need some lowering before LLVM can work on them. -// This is done so that a few cleanup passes can run before assigning the final -// type codes. -func (c *Compiler) LowerInterfaces() { +// LowerInterfaces lowers all intermediate interface calls and globals that are +// emitted by the compiler as higher-level intrinsics. They need some lowering +// before LLVM can work on them. This is done so that a few cleanup passes can +// run before assigning the final type codes. +func LowerInterfaces(mod llvm.Module) { p := &lowerInterfacesPass{ - Compiler: c, - types: make(map[string]*typeInfo), - signatures: make(map[string]*signatureInfo), - interfaces: make(map[string]*interfaceInfo), + mod: mod, + builder: mod.Context().NewBuilder(), + ctx: mod.Context(), + uintptrType: mod.Context().IntType(llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8), + types: make(map[string]*typeInfo), + signatures: make(map[string]*signatureInfo), + interfaces: make(map[string]*interfaceInfo), } p.run() } @@ -156,8 +163,8 @@ func (c *Compiler) LowerInterfaces() { // run runs the pass itself. func (p *lowerInterfacesPass) run() { // Collect all type codes. - typecodeIDPtr := llvm.PointerType(p.getLLVMRuntimeType("typecodeID"), 0) - typeInInterfacePtr := llvm.PointerType(p.getLLVMRuntimeType("typeInInterface"), 0) + typecodeIDPtr := llvm.PointerType(p.mod.GetTypeByName("runtime.typecodeID"), 0) + typeInInterfacePtr := llvm.PointerType(p.mod.GetTypeByName("runtime.typeInInterface"), 0) var typesInInterfaces []llvm.Value for global := p.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) { switch global.Type() { @@ -368,7 +375,7 @@ func (p *lowerInterfacesPass) run() { } // Assign a type code for each type. - p.assignTypeCodes(typeSlice) + assignTypeCodes(p.mod, typeSlice) // Replace each use of a runtime.typeInInterface with the constant type // code. @@ -519,7 +526,7 @@ func (p *lowerInterfacesPass) replaceInvokeWithCall(use llvm.Value, typ *typeInf } } p.builder.SetInsertPointBefore(call) - receiverParams := p.emitPointerUnpack(operands[0], receiverParamTypes) + receiverParams := llvmutil.EmitPointerUnpack(p.builder, p.mod, operands[0], receiverParamTypes) result := p.builder.CreateCall(function, append(receiverParams, operands[1:]...), "") if result.Type().TypeKind() != llvm.VoidTypeKind { call.ReplaceAllUsesWith(result) diff --git a/transform/interface-lowering_test.go b/transform/interface-lowering_test.go new file mode 100644 index 00000000..2bc8ed64 --- /dev/null +++ b/transform/interface-lowering_test.go @@ -0,0 +1,10 @@ +package transform + +import ( + "testing" +) + +func TestInterfaceLowering(t *testing.T) { + t.Parallel() + testTransform(t, "testdata/interface", LowerInterfaces) +} diff --git a/transform/llvm.go b/transform/llvm.go index 54e5aad5..f41d2f9c 100644 --- a/transform/llvm.go +++ b/transform/llvm.go @@ -1,12 +1,17 @@ package transform import ( + "reflect" + "tinygo.org/x/go-llvm" ) // Return a list of values (actually, instructions) where this value is used as // an operand. func getUses(value llvm.Value) []llvm.Value { + if value.IsNil() { + return nil + } var uses []llvm.Value use := value.FirstUse() for !use.IsNil() { @@ -15,3 +20,48 @@ func getUses(value llvm.Value) []llvm.Value { } return uses } + +// makeGlobalArray creates a new LLVM global with the given name and integers as +// contents, and returns the global. +// Note that it is left with the default linkage etc., you should set +// linkage/constant/etc properties yourself. +func makeGlobalArray(mod llvm.Module, bufItf interface{}, name string, elementType llvm.Type) llvm.Value { + buf := reflect.ValueOf(bufItf) + globalType := llvm.ArrayType(elementType, buf.Len()) + global := llvm.AddGlobal(mod, globalType, name) + value := llvm.Undef(globalType) + for i := 0; i < buf.Len(); i++ { + ch := buf.Index(i).Uint() + value = llvm.ConstInsertValue(value, llvm.ConstInt(elementType, ch, false), []uint32{uint32(i)}) + } + global.SetInitializer(value) + return global +} + +// getGlobalBytes returns the slice contained in the array of the provided +// global. It can recover the bytes originally created using makeGlobalArray, if +// makeGlobalArray was given a byte slice. +func getGlobalBytes(global llvm.Value) []byte { + value := global.Initializer() + buf := make([]byte, value.Type().ArrayLength()) + for i := range buf { + buf[i] = byte(llvm.ConstExtractValue(value, []uint32{uint32(i)}).ZExtValue()) + } + return buf +} + +// replaceGlobalByteWithArray replaces a global integer type in the module with +// an integer array, using a GEP to make the types match. It is a convenience +// function used for creating reflection sidetables, for example. +func replaceGlobalIntWithArray(mod llvm.Module, name string, buf interface{}) llvm.Value { + oldGlobal := mod.NamedGlobal(name) + global := makeGlobalArray(mod, buf, name+".tmp", oldGlobal.Type().ElementType()) + gep := llvm.ConstGEP(global, []llvm.Value{ + llvm.ConstInt(mod.Context().Int32Type(), 0, false), + llvm.ConstInt(mod.Context().Int32Type(), 0, false), + }) + oldGlobal.ReplaceAllUsesWith(gep) + oldGlobal.EraseFromParentAsGlobal() + global.SetName(name) + return global +} diff --git a/compiler/reflect.go b/transform/reflect.go similarity index 94% rename from compiler/reflect.go rename to transform/reflect.go index 8c0c82ce..9bfd1690 100644 --- a/compiler/reflect.go +++ b/transform/reflect.go @@ -1,4 +1,4 @@ -package compiler +package transform // This file has some compiler support for run-time reflection using the reflect // package. In particular, it encodes type information in type codes in such a @@ -125,27 +125,27 @@ type typeCodeAssignmentState struct { // assignTypeCodes is used to assign a type code to each type in the program // that is ever stored in an interface. It tries to use the smallest possible // numbers to make the code that works with interfaces as small as possible. -func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) { +func assignTypeCodes(mod llvm.Module, typeSlice typeInfoSlice) { // if reflect were not used, we could skip generating the sidetable // this does not help in practice, and is difficult to do correctly // Assign typecodes the way the reflect package expects. state := typeCodeAssignmentState{ fallbackIndex: 1, - uintptrLen: c.uintptrType.IntTypeWidth(), + uintptrLen: llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8, namedBasicTypes: make(map[string]int), namedNonBasicTypes: make(map[string]int), arrayTypes: make(map[string]int), structTypes: make(map[string]int), structNames: make(map[string]int), - needsNamedNonBasicTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.namedNonBasicTypesSidetable"))) != 0, - needsStructTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.structTypesSidetable"))) != 0, - needsStructNamesSidetable: len(getUses(c.mod.NamedGlobal("reflect.structNamesSidetable"))) != 0, - needsArrayTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.arrayTypesSidetable"))) != 0, + needsNamedNonBasicTypesSidetable: len(getUses(mod.NamedGlobal("reflect.namedNonBasicTypesSidetable"))) != 0, + needsStructTypesSidetable: len(getUses(mod.NamedGlobal("reflect.structTypesSidetable"))) != 0, + needsStructNamesSidetable: len(getUses(mod.NamedGlobal("reflect.structNamesSidetable"))) != 0, + needsArrayTypesSidetable: len(getUses(mod.NamedGlobal("reflect.arrayTypesSidetable"))) != 0, } for _, t := range typeSlice { num := state.getTypeCodeNum(t.typecode) - if num.BitLen() > c.uintptrType.IntTypeWidth() || !num.IsUint64() { + if num.BitLen() > state.uintptrLen || !num.IsUint64() { // TODO: support this in some way, using a side table for example. // That's less efficient but better than not working at all. // Particularly important on systems with 16-bit pointers (e.g. @@ -157,22 +157,22 @@ func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) { // Only create this sidetable when it is necessary. if state.needsNamedNonBasicTypesSidetable { - global := c.replaceGlobalIntWithArray("reflect.namedNonBasicTypesSidetable", state.namedNonBasicTypesSidetable) + global := replaceGlobalIntWithArray(mod, "reflect.namedNonBasicTypesSidetable", state.namedNonBasicTypesSidetable) global.SetLinkage(llvm.InternalLinkage) global.SetUnnamedAddr(true) } if state.needsArrayTypesSidetable { - global := c.replaceGlobalIntWithArray("reflect.arrayTypesSidetable", state.arrayTypesSidetable) + global := replaceGlobalIntWithArray(mod, "reflect.arrayTypesSidetable", state.arrayTypesSidetable) global.SetLinkage(llvm.InternalLinkage) global.SetUnnamedAddr(true) } if state.needsStructTypesSidetable { - global := c.replaceGlobalIntWithArray("reflect.structTypesSidetable", state.structTypesSidetable) + global := replaceGlobalIntWithArray(mod, "reflect.structTypesSidetable", state.structTypesSidetable) global.SetLinkage(llvm.InternalLinkage) global.SetUnnamedAddr(true) } if state.needsStructNamesSidetable { - global := c.replaceGlobalIntWithArray("reflect.structNamesSidetable", state.structNamesSidetable) + global := replaceGlobalIntWithArray(mod, "reflect.structNamesSidetable", state.structNamesSidetable) global.SetLinkage(llvm.InternalLinkage) global.SetUnnamedAddr(true) } diff --git a/transform/testdata/interface.ll b/transform/testdata/interface.ll new file mode 100644 index 00000000..dd00670d --- /dev/null +++ b/transform/testdata/interface.ll @@ -0,0 +1,80 @@ +target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64" +target triple = "armv7m-none-eabi" + +%runtime.typecodeID = type { %runtime.typecodeID*, i32 } +%runtime.typeInInterface = type { %runtime.typecodeID*, %runtime.interfaceMethodInfo* } +%runtime.interfaceMethodInfo = type { i8*, i32 } + +@"reflect/types.type:basic:uint8" = external constant %runtime.typecodeID +@"reflect/types.type:basic:int" = external constant %runtime.typecodeID +@"typeInInterface:reflect/types.type:basic:uint8" = private constant %runtime.typeInInterface { %runtime.typecodeID* @"reflect/types.type:basic:uint8", %runtime.interfaceMethodInfo* null } +@"typeInInterface:reflect/types.type:basic:int" = private constant %runtime.typeInInterface { %runtime.typecodeID* @"reflect/types.type:basic:int", %runtime.interfaceMethodInfo* null } +@"func NeverImplementedMethod()" = external constant i8 +@"Unmatched$interface" = private constant [1 x i8*] [i8* @"func NeverImplementedMethod()"] +@"func Double() int" = external constant i8 +@"Doubler$interface" = private constant [1 x i8*] [i8* @"func Double() int"] +@"Number$methodset" = private constant [1 x %runtime.interfaceMethodInfo] [%runtime.interfaceMethodInfo { i8* @"func Double() int", i32 ptrtoint (i32 (i8*)* @"(Number).Double$invoke" to i32) }] +@"reflect/types.type:named:Number" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0 } +@"typeInInterface:reflect/types.type:named:Number" = private constant %runtime.typeInInterface { %runtime.typecodeID* @"reflect/types.type:named:Number", %runtime.interfaceMethodInfo* getelementptr inbounds ([1 x %runtime.interfaceMethodInfo], [1 x %runtime.interfaceMethodInfo]* @"Number$methodset", i32 0, i32 0) } + +declare i1 @runtime.interfaceImplements(i32, i8**) +declare i1 @runtime.typeAssert(i32, %runtime.typecodeID*) +declare i32 @runtime.interfaceMethod(i32, i8**, i8*) +declare void @runtime.printuint8(i8) +declare void @runtime.printint32(i32) +declare void @runtime.printptr(i32) +declare void @runtime.printnl() + +define void @printInterfaces() { + call void @printInterface(i32 ptrtoint (%runtime.typeInInterface* @"typeInInterface:reflect/types.type:basic:int" to i32), i8* inttoptr (i32 5 to i8*)) + call void @printInterface(i32 ptrtoint (%runtime.typeInInterface* @"typeInInterface:reflect/types.type:basic:uint8" to i32), i8* inttoptr (i8 120 to i8*)) + call void @printInterface(i32 ptrtoint (%runtime.typeInInterface* @"typeInInterface:reflect/types.type:named:Number" to i32), i8* inttoptr (i32 3 to i8*)) + + ret void +} + +define void @printInterface(i32 %typecode, i8* %value) { + %isUnmatched = call i1 @runtime.interfaceImplements(i32 %typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"Unmatched$interface", i32 0, i32 0)) + br i1 %isUnmatched, label %typeswitch.Unmatched, label %typeswitch.notUnmatched + +typeswitch.Unmatched: + %unmatched = ptrtoint i8* %value to i32 + call void @runtime.printptr(i32 %unmatched) + call void @runtime.printnl() + ret void + +typeswitch.notUnmatched: + %isDoubler = call i1 @runtime.interfaceImplements(i32 %typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"Doubler$interface", i32 0, i32 0)) + br i1 %isDoubler, label %typeswitch.Doubler, label %typeswitch.notDoubler + +typeswitch.Doubler: + %doubler.func = call i32 @runtime.interfaceMethod(i32 %typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"Doubler$interface", i32 0, i32 0), i8* nonnull @"func Double() int") + %doubler.func.cast = inttoptr i32 %doubler.func to i32 (i8*)* + %doubler.result = call i32 %doubler.func.cast(i8* %value) + call void @runtime.printint32(i32 %doubler.result) + ret void + +typeswitch.notDoubler: + %isByte = call i1 @runtime.typeAssert(i32 %typecode, %runtime.typecodeID* nonnull @"reflect/types.type:basic:uint8") + br i1 %isByte, label %typeswitch.byte, label %typeswitch.notByte + +typeswitch.byte: + %byte = ptrtoint i8* %value to i8 + call void @runtime.printuint8(i8 %byte) + call void @runtime.printnl() + ret void + +typeswitch.notByte: + ret void +} + +define i32 @"(Number).Double"(i32 %receiver) { + %ret = mul i32 %receiver, 2 + ret i32 %ret +} + +define i32 @"(Number).Double$invoke"(i8* %receiverPtr) { + %receiver = ptrtoint i8* %receiverPtr to i32 + %ret = call i32 @"(Number).Double"(i32 %receiver) + ret i32 %ret +} diff --git a/transform/testdata/interface.out.ll b/transform/testdata/interface.out.ll new file mode 100644 index 00000000..d4828cf5 --- /dev/null +++ b/transform/testdata/interface.out.ll @@ -0,0 +1,102 @@ +target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64" +target triple = "armv7m-none-eabi" + +%runtime.typecodeID = type { %runtime.typecodeID*, i32 } + +@"reflect/types.type:basic:uint8" = external constant %runtime.typecodeID +@"reflect/types.type:basic:int" = external constant %runtime.typecodeID +@"func NeverImplementedMethod()" = external constant i8 +@"Unmatched$interface" = private constant [1 x i8*] [i8* @"func NeverImplementedMethod()"] +@"func Double() int" = external constant i8 +@"Doubler$interface" = private constant [1 x i8*] [i8* @"func Double() int"] +@"reflect/types.type:named:Number" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0 } + +declare i1 @runtime.interfaceImplements(i32, i8**) + +declare i1 @runtime.typeAssert(i32, %runtime.typecodeID*) + +declare i32 @runtime.interfaceMethod(i32, i8**, i8*) + +declare void @runtime.printuint8(i8) + +declare void @runtime.printint32(i32) + +declare void @runtime.printptr(i32) + +declare void @runtime.printnl() + +define void @printInterfaces() { + call void @printInterface(i32 4, i8* inttoptr (i32 5 to i8*)) + call void @printInterface(i32 16, i8* inttoptr (i8 120 to i8*)) + call void @printInterface(i32 68, i8* inttoptr (i32 3 to i8*)) + ret void +} + +define void @printInterface(i32 %typecode, i8* %value) { + %typeassert.ok1 = call i1 @"Unmatched$typeassert"(i32 %typecode) + br i1 %typeassert.ok1, label %typeswitch.Unmatched, label %typeswitch.notUnmatched + +typeswitch.Unmatched: + %unmatched = ptrtoint i8* %value to i32 + call void @runtime.printptr(i32 %unmatched) + call void @runtime.printnl() + ret void + +typeswitch.notUnmatched: + %typeassert.ok = call i1 @"Doubler$typeassert"(i32 %typecode) + br i1 %typeassert.ok, label %typeswitch.Doubler, label %typeswitch.notDoubler + +typeswitch.Doubler: + %doubler.result = call i32 @"(Number).Double$invoke"(i8* %value) + call void @runtime.printint32(i32 %doubler.result) + ret void + +typeswitch.notDoubler: + %typeassert.ok2 = icmp eq i32 16, %typecode + br i1 %typeassert.ok2, label %typeswitch.byte, label %typeswitch.notByte + +typeswitch.byte: + %byte = ptrtoint i8* %value to i8 + call void @runtime.printuint8(i8 %byte) + call void @runtime.printnl() + ret void + +typeswitch.notByte: + ret void +} + +define i32 @"(Number).Double"(i32 %receiver) { + %ret = mul i32 %receiver, 2 + ret i32 %ret +} + +define i32 @"(Number).Double$invoke"(i8* %receiverPtr) { + %receiver = ptrtoint i8* %receiverPtr to i32 + %ret = call i32 @"(Number).Double"(i32 %receiver) + ret i32 %ret +} + +define internal i1 @"Doubler$typeassert"(i32 %actualType) unnamed_addr { +entry: + switch i32 %actualType, label %else [ + i32 68, label %then + ] + +then: + ret i1 true + +else: + ret i1 false +} + +define internal i1 @"Unmatched$typeassert"(i32 %actualType) unnamed_addr { +entry: + switch i32 %actualType, label %else [ + ] + +then: + ret i1 true + +else: + ret i1 false +}