compiler: add support for running a builtin in a goroutine
Not sure why you would ever do this, but it appears to be allowed by the Go specification and previously TinyGo would crash with an unhelpful error message when you would do this. I don't see any practical use of it. The implementation simply runs the builtin directly.
Этот коммит содержится в:
родитель
87c2ccb0b9
коммит
ec325c0643
5 изменённых файлов: 130 добавлений и 0 удалений
|
@ -40,6 +40,37 @@ func (b *builder) createGo(instr *ssa.Go) {
|
|||
}
|
||||
params = append(params, context) // context parameter
|
||||
funcPtr = b.getFunction(callee)
|
||||
} else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok {
|
||||
// We cheat. None of the builtins do any long or blocking operation, so
|
||||
// we might as well run these builtins right away without the program
|
||||
// noticing the difference.
|
||||
// Possible exceptions:
|
||||
// - copy: this is a possibly long operation, but not a blocking
|
||||
// operation. Semantically it makes no difference to run it right
|
||||
// away (not in a goroutine). However, in practice it makes no sense
|
||||
// to run copy in a goroutine as there is no way to (safely) know
|
||||
// when it is finished.
|
||||
// - panic: the error message would appear in the parent goroutine.
|
||||
// But because `go panic("err")` would halt the program anyway
|
||||
// (there is no recover), panicking right away would give the same
|
||||
// behavior as creating a goroutine, switching the scheduler to that
|
||||
// goroutine, and panicking there. So this optimization seems
|
||||
// correct.
|
||||
// - recover: because it runs in a new goroutine, it is never a
|
||||
// deferred function. Thus this is a no-op.
|
||||
if builtin.Name() == "recover" {
|
||||
// This is a no-op, even in a deferred function:
|
||||
// go recover()
|
||||
return
|
||||
}
|
||||
var argTypes []types.Type
|
||||
var argValues []llvm.Value
|
||||
for _, arg := range instr.Call.Args {
|
||||
argTypes = append(argTypes, arg.Type())
|
||||
argValues = append(argValues, b.getValue(arg))
|
||||
}
|
||||
b.createBuiltin(argTypes, argValues, builtin.Name(), instr.Pos())
|
||||
return
|
||||
} else if !instr.Call.IsInvoke() {
|
||||
// This is a function pointer.
|
||||
// At the moment, two extra params are passed to the newly started
|
||||
|
|
27
compiler/testdata/goroutine-cortex-m-qemu.ll
предоставленный
27
compiler/testdata/goroutine-cortex-m-qemu.ll
предоставленный
|
@ -3,6 +3,12 @@ source_filename = "goroutine.go"
|
|||
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
|
||||
target triple = "armv7m-none-eabi"
|
||||
|
||||
%runtime.channel = type { i32, i32, i8, %runtime.channelBlockedList*, i32, i32, i32, i8* }
|
||||
%runtime.channelBlockedList = type { %runtime.channelBlockedList*, %"internal/task.Task"*, %runtime.chanSelectState*, { %runtime.channelBlockedList*, i32, i32 } }
|
||||
%"internal/task.Task" = type { %"internal/task.Task"*, i8*, i32, %"internal/task.state" }
|
||||
%"internal/task.state" = type { i32, i32* }
|
||||
%runtime.chanSelectState = type { %runtime.channel*, i8* }
|
||||
|
||||
@"main.regularFunctionGoroutine$pack" = private unnamed_addr constant { i32, i8* } { i32 5, i8* undef }
|
||||
@"main.inlineFunctionGoroutine$pack" = private unnamed_addr constant { i32, i8* } { i32 5, i8* undef }
|
||||
|
||||
|
@ -138,6 +144,27 @@ entry:
|
|||
ret void
|
||||
}
|
||||
|
||||
define hidden void @main.recoverBuiltinGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden void @main.copyBuiltinGoroutine(i8* %dst.data, i32 %dst.len, i32 %dst.cap, i8* %src.data, i32 %src.len, i32 %src.cap, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%copy.n = call i32 @runtime.sliceCopy(i8* %dst.data, i8* %src.data, i32 %dst.len, i32 %src.len, i32 1, i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
|
||||
declare i32 @runtime.sliceCopy(i8* nocapture writeonly, i8* nocapture readonly, i32, i32, i32, i8*, i8*)
|
||||
|
||||
define hidden void @main.closeBuiltinGoroutine(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
call void @runtime.chanClose(%runtime.channel* %ch, i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
|
||||
declare void @runtime.chanClose(%runtime.channel* dereferenceable_or_null(32), i8*, i8*)
|
||||
|
||||
attributes #0 = { "tinygo-gowrapper"="main.regularFunction" }
|
||||
attributes #1 = { "tinygo-gowrapper"="main.inlineFunctionGoroutine$1" }
|
||||
attributes #2 = { "tinygo-gowrapper"="main.closureFunctionGoroutine$1" }
|
||||
|
|
26
compiler/testdata/goroutine-wasm.ll
предоставленный
26
compiler/testdata/goroutine-wasm.ll
предоставленный
|
@ -4,6 +4,11 @@ target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
|||
target triple = "wasm32--wasi"
|
||||
|
||||
%runtime.funcValueWithSignature = type { i32, i8* }
|
||||
%runtime.channel = type { i32, i32, i8, %runtime.channelBlockedList*, i32, i32, i32, i8* }
|
||||
%runtime.channelBlockedList = type { %runtime.channelBlockedList*, %"internal/task.Task"*, %runtime.chanSelectState*, { %runtime.channelBlockedList*, i32, i32 } }
|
||||
%"internal/task.Task" = type { %"internal/task.Task"*, i8*, i32, %"internal/task.state" }
|
||||
%"internal/task.state" = type { i8* }
|
||||
%runtime.chanSelectState = type { %runtime.channel*, i8* }
|
||||
|
||||
@"main.regularFunctionGoroutine$pack" = private unnamed_addr constant { i32, i8* } { i32 5, i8* undef }
|
||||
@"main.inlineFunctionGoroutine$pack" = private unnamed_addr constant { i32, i8* } { i32 5, i8* undef }
|
||||
|
@ -88,3 +93,24 @@ entry:
|
|||
}
|
||||
|
||||
declare i32 @runtime.getFuncPtr(i8*, i32, i8* dereferenceable_or_null(1), i8*, i8*)
|
||||
|
||||
define hidden void @main.recoverBuiltinGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden void @main.copyBuiltinGoroutine(i8* %dst.data, i32 %dst.len, i32 %dst.cap, i8* %src.data, i32 %src.len, i32 %src.cap, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%copy.n = call i32 @runtime.sliceCopy(i8* %dst.data, i8* %src.data, i32 %dst.len, i32 %src.len, i32 1, i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
|
||||
declare i32 @runtime.sliceCopy(i8* nocapture writeonly, i8* nocapture readonly, i32, i32, i32, i8*, i8*)
|
||||
|
||||
define hidden void @main.closeBuiltinGoroutine(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
call void @runtime.chanClose(%runtime.channel* %ch, i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
|
||||
declare void @runtime.chanClose(%runtime.channel* dereferenceable_or_null(32), i8*, i8*)
|
||||
|
|
19
compiler/testdata/goroutine.go
предоставленный
19
compiler/testdata/goroutine.go
предоставленный
|
@ -21,4 +21,23 @@ func funcGoroutine(fn func(x int)) {
|
|||
go fn(5)
|
||||
}
|
||||
|
||||
func recoverBuiltinGoroutine() {
|
||||
// This is a no-op.
|
||||
go recover()
|
||||
}
|
||||
|
||||
func copyBuiltinGoroutine(dst, src []byte) {
|
||||
// This is not run in a goroutine. While this copy operation can indeed take
|
||||
// some time (if there is a lot of data to copy), there is no race-free way
|
||||
// to make use of the result so it's unlikely applications will make use of
|
||||
// it. And doing it this way should be just within the Go specification.
|
||||
go copy(dst, src)
|
||||
}
|
||||
|
||||
func closeBuiltinGoroutine(ch chan int) {
|
||||
// This builtin is executed directly, not in a goroutine.
|
||||
// The observed behavior is the same.
|
||||
go close(ch)
|
||||
}
|
||||
|
||||
func regularFunction(x int)
|
||||
|
|
27
testdata/goroutines.go
предоставленный
27
testdata/goroutines.go
предоставленный
|
@ -73,6 +73,8 @@ func main() {
|
|||
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
|
||||
testGoOnBuiltins()
|
||||
|
||||
testCond()
|
||||
}
|
||||
|
||||
|
@ -131,6 +133,31 @@ type simpleFunc func()
|
|||
func emptyFunc() {
|
||||
}
|
||||
|
||||
func testGoOnBuiltins() {
|
||||
// Test copy builtin (there is no non-racy practical use of this).
|
||||
go copy(make([]int, 8), []int{2, 5, 8, 4})
|
||||
|
||||
// Test recover builtin (no-op).
|
||||
go recover()
|
||||
|
||||
// Test close builtin.
|
||||
ch := make(chan int)
|
||||
go close(ch)
|
||||
n, ok := <-ch
|
||||
if n != 0 || ok != false {
|
||||
println("error: expected closed channel to return 0, false")
|
||||
}
|
||||
|
||||
// Test delete builtin.
|
||||
m := map[string]int{"foo": 3}
|
||||
go delete(m, "foo")
|
||||
time.Sleep(time.Millisecond)
|
||||
v, ok := m["foo"]
|
||||
if v != 0 || ok != false {
|
||||
println("error: expected deleted map entry to be 0, false")
|
||||
}
|
||||
}
|
||||
|
||||
func testCond() {
|
||||
var cond runtime.Cond
|
||||
go func() {
|
||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче