diff --git a/compiler/goroutine.go b/compiler/goroutine.go index 790d2426..b5848192 100644 --- a/compiler/goroutine.go +++ b/compiler/goroutine.go @@ -79,7 +79,15 @@ func (b *builder) createGo(instr *ssa.Go) { } b.createBuiltin(argTypes, argValues, builtin.Name(), instr.Pos()) return - } else if !instr.Call.IsInvoke() { + } else if instr.Call.IsInvoke() { + // This is a method call on an interface value. + itf := b.getValue(instr.Call.Value) + itfTypeCode := b.CreateExtractValue(itf, 0, "") + itfValue := b.CreateExtractValue(itf, 1, "") + funcPtr = b.getInvokeFunction(&instr.Call) + params = append([]llvm.Value{itfValue}, params...) // start with receiver + params = append(params, itfTypeCode) // end with typecode + } else { // This is a function pointer. // At the moment, two extra params are passed to the newly started // goroutine: @@ -99,9 +107,6 @@ func (b *builder) createGo(instr *ssa.Go) { panic("unknown scheduler type") } prefix = b.fn.RelString(nil) - } else { - b.addError(instr.Pos(), "todo: go on interface call") - return } paramBundle := b.emitPointerPack(params) diff --git a/compiler/testdata/goroutine-cortex-m-qemu.ll b/compiler/testdata/goroutine-cortex-m-qemu.ll index 8c99dce9..064a5fd8 100644 --- a/compiler/testdata/goroutine-cortex-m-qemu.ll +++ b/compiler/testdata/goroutine-cortex-m-qemu.ll @@ -9,6 +9,8 @@ target triple = "armv7m-unknown-unknown-eabi" %"internal/task.state" = type { i32, i32* } %runtime.chanSelectState = type { %runtime.channel*, i8* } +@"main.startInterfaceMethod$string" = internal unnamed_addr constant [4 x i8] c"test", align 1 + declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*) ; Function Attrs: nounwind @@ -158,8 +160,50 @@ entry: declare void @runtime.chanClose(%runtime.channel* dereferenceable_or_null(32), i8*, i8*) +; Function Attrs: nounwind +define hidden void @main.startInterfaceMethod(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr #0 { +entry: + %0 = call i8* @runtime.alloc(i32 16, i8* undef, i8* null) #0 + %1 = bitcast i8* %0 to i8** + store i8* %itf.value, i8** %1, align 4 + %2 = getelementptr inbounds i8, i8* %0, i32 4 + %.repack = bitcast i8* %2 to i8** + store i8* getelementptr inbounds ([4 x i8], [4 x i8]* @"main.startInterfaceMethod$string", i32 0, i32 0), i8** %.repack, align 4 + %.repack1 = getelementptr inbounds i8, i8* %0, i32 8 + %3 = bitcast i8* %.repack1 to i32* + store i32 4, i32* %3, align 4 + %4 = getelementptr inbounds i8, i8* %0, i32 12 + %5 = bitcast i8* %4 to i32* + store i32 %itf.typecode, i32* %5, align 4 + %stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), i8* undef, i8* undef) #0 + call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), i8* nonnull %0, i32 %stacksize, i8* undef, i8* null) #0 + ret void +} + +declare void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(i8*, i8*, i32, i32, i8*, i8*) #5 + +; Function Attrs: nounwind +define linkonce_odr void @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper"(i8* %0) unnamed_addr #6 { +entry: + %1 = bitcast i8* %0 to i8** + %2 = load i8*, i8** %1, align 4 + %3 = getelementptr inbounds i8, i8* %0, i32 4 + %4 = bitcast i8* %3 to i8** + %5 = load i8*, i8** %4, align 4 + %6 = getelementptr inbounds i8, i8* %0, i32 8 + %7 = bitcast i8* %6 to i32* + %8 = load i32, i32* %7, align 4 + %9 = getelementptr inbounds i8, i8* %0, i32 12 + %10 = bitcast i8* %9 to i32* + %11 = load i32, i32* %10, align 4 + call void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(i8* %2, i8* %5, i32 %8, i32 %11, i8* undef, i8* undef) #0 + ret void +} + attributes #0 = { nounwind } attributes #1 = { nounwind "tinygo-gowrapper"="main.regularFunction" } attributes #2 = { nounwind "tinygo-gowrapper"="main.inlineFunctionGoroutine$1" } attributes #3 = { nounwind "tinygo-gowrapper"="main.closureFunctionGoroutine$1" } attributes #4 = { nounwind "tinygo-gowrapper" } +attributes #5 = { "tinygo-invoke"="reflect/methods.Print(string)" "tinygo-methods"="reflect/methods.Print(string)" } +attributes #6 = { nounwind "tinygo-gowrapper"="interface:{Print:func:{basic:string}{}}.Print$invoke" } diff --git a/compiler/testdata/goroutine-wasm.ll b/compiler/testdata/goroutine-wasm.ll index 4be8a0ed..ab628148 100644 --- a/compiler/testdata/goroutine-wasm.ll +++ b/compiler/testdata/goroutine-wasm.ll @@ -14,6 +14,7 @@ target triple = "wasm32-unknown-wasi" @"main.inlineFunctionGoroutine$pack" = private unnamed_addr constant { i32, i8* } { i32 5, i8* undef } @"reflect/types.funcid:func:{basic:int}{}" = external constant i8 @"main.closureFunctionGoroutine$1$withSignature" = linkonce_odr constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i32, i8*, i8*)* @"main.closureFunctionGoroutine$1" to i32), i8* @"reflect/types.funcid:func:{basic:int}{}" } +@"main.startInterfaceMethod$string" = internal unnamed_addr constant [4 x i8] c"test", align 1 declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*) @@ -115,4 +116,26 @@ entry: declare void @runtime.chanClose(%runtime.channel* dereferenceable_or_null(32), i8*, i8*) +; Function Attrs: nounwind +define hidden void @main.startInterfaceMethod(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr #0 { +entry: + %0 = call i8* @runtime.alloc(i32 16, i8* undef, i8* null) #0 + %1 = bitcast i8* %0 to i8** + store i8* %itf.value, i8** %1, align 4 + %2 = getelementptr inbounds i8, i8* %0, i32 4 + %.repack = bitcast i8* %2 to i8** + store i8* getelementptr inbounds ([4 x i8], [4 x i8]* @"main.startInterfaceMethod$string", i32 0, i32 0), i8** %.repack, align 4 + %.repack1 = getelementptr inbounds i8, i8* %0, i32 8 + %3 = bitcast i8* %.repack1 to i32* + store i32 4, i32* %3, align 4 + %4 = getelementptr inbounds i8, i8* %0, i32 12 + %5 = bitcast i8* %4 to i32* + store i32 %itf.typecode, i32* %5, align 4 + call void @"internal/task.start"(i32 ptrtoint (void (i8*, i8*, i32, i32, i8*, i8*)* @"interface:{Print:func:{basic:string}{}}.Print$invoke" to i32), i8* nonnull %0, i32 undef, i8* undef, i8* null) #0 + ret void +} + +declare void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(i8*, i8*, i32, i32, i8*, i8*) #1 + attributes #0 = { nounwind } +attributes #1 = { "tinygo-invoke"="reflect/methods.Print(string)" "tinygo-methods"="reflect/methods.Print(string)" } diff --git a/compiler/testdata/goroutine.go b/compiler/testdata/goroutine.go index 5d33082a..4585f5ea 100644 --- a/compiler/testdata/goroutine.go +++ b/compiler/testdata/goroutine.go @@ -41,3 +41,11 @@ func closeBuiltinGoroutine(ch chan int) { } func regularFunction(x int) + +type simpleInterface interface { + Print(string) +} + +func startInterfaceMethod(itf simpleInterface) { + go itf.Print("test") +} diff --git a/testdata/goroutines.go b/testdata/goroutines.go index e3cc62fa..eb38a5b1 100644 --- a/testdata/goroutines.go +++ b/testdata/goroutines.go @@ -75,6 +75,8 @@ func main() { testGoOnBuiltins() + testGoOnInterface(Foo(0)) + testCond() testIssue1790() @@ -203,6 +205,14 @@ func testCond() { var once sync.Once +func testGoOnInterface(f Itf) { + go f.Nowait() + time.Sleep(time.Millisecond) + go f.Wait() + time.Sleep(time.Millisecond * 2) + println("done with 'go on interface'") +} + // This tests a fix for issue 1790: // https://github.com/tinygo-org/tinygo/issues/1790 func testIssue1790() *int { @@ -210,3 +220,20 @@ func testIssue1790() *int { i := 0 return &i } + +type Itf interface { + Nowait() + Wait() +} + +type Foo string + +func (f Foo) Nowait() { + println("called: Foo.Nowait") +} + +func (f Foo) Wait() { + println("called: Foo.Wait") + time.Sleep(time.Microsecond) + println(" ...waited") +} diff --git a/testdata/goroutines.txt b/testdata/goroutines.txt index 1e29558a..b2debbb1 100644 --- a/testdata/goroutines.txt +++ b/testdata/goroutines.txt @@ -20,3 +20,7 @@ acquired mutex from goroutine released mutex from goroutine re-acquired mutex done +called: Foo.Nowait +called: Foo.Wait + ...waited +done with 'go on interface'