compiler: skip context parameter when starting regular goroutine

Do not store the context parameter (which is used for closures and
function pointers) in the goroutine start parameter bundle for direct
functions that don't need a context parameter. This avoids storing the
(undef) context parameter and thus makes the IR to start a new goroutine
simpler in most cases.

This reduces code size in the channel.go and goroutines.go tests.
Surprisingly, all test cases (when compiled with -target=microbit) have
a changed binary, I haven't investigated why but I suppose the codegen
is slightly different for the runtime.run function (which starts the
main goroutine).
Этот коммит содержится в:
Ayke van Laethem 2021-05-26 00:00:25 +02:00 коммит произвёл Ron Evans
родитель 3edcdb5f0d
коммит c93ddb630b
3 изменённых файлов: 39 добавлений и 26 удалений

Просмотреть файл

@ -23,7 +23,7 @@ import (
// Version of the compiler pacakge. Must be incremented each time the compiler // Version of the compiler pacakge. Must be incremented each time the compiler
// package changes in a way that affects the generated LLVM module. // package changes in a way that affects the generated LLVM module.
// This version is independent of the TinyGo version number. // This version is independent of the TinyGo version number.
const Version = 9 // last change: implement reflect.New() const Version = 10 // last change: context parameter in go wrapper
func init() { func init() {
llvm.InitializeAllTargets() llvm.InitializeAllTargets()

Просмотреть файл

@ -22,6 +22,7 @@ func (b *builder) createGo(instr *ssa.Go) {
var prefix string var prefix string
var funcPtr llvm.Value var funcPtr llvm.Value
hasContext := false
if callee := instr.Call.StaticCallee(); callee != nil { if callee := instr.Call.StaticCallee(); callee != nil {
// Static callee is known. This makes it easier to start a new // Static callee is known. This makes it easier to start a new
// goroutine. // goroutine.
@ -29,7 +30,11 @@ func (b *builder) createGo(instr *ssa.Go) {
switch value := instr.Call.Value.(type) { switch value := instr.Call.Value.(type) {
case *ssa.Function: case *ssa.Function:
// Goroutine call is regular function call. No context is necessary. // Goroutine call is regular function call. No context is necessary.
if b.Scheduler == "coroutines" {
// The context parameter is assumed to be always present in the
// coroutines scheduler.
context = llvm.Undef(b.i8ptrType) context = llvm.Undef(b.i8ptrType)
}
case *ssa.MakeClosure: case *ssa.MakeClosure:
// A goroutine call on a func value, but the callee is trivial to find. For // A goroutine call on a func value, but the callee is trivial to find. For
// example: immediately applied functions. // example: immediately applied functions.
@ -38,7 +43,10 @@ func (b *builder) createGo(instr *ssa.Go) {
default: default:
panic("StaticCallee returned an unexpected value") panic("StaticCallee returned an unexpected value")
} }
if !context.IsNil() {
params = append(params, context) // context parameter params = append(params, context) // context parameter
hasContext = true
}
funcPtr = b.getFunction(callee) funcPtr = b.getFunction(callee)
} else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok { } else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok {
// We cheat. None of the builtins do any long or blocking operation, so // We cheat. None of the builtins do any long or blocking operation, so
@ -80,6 +88,7 @@ func (b *builder) createGo(instr *ssa.Go) {
var context llvm.Value var context llvm.Value
funcPtr, context = b.decodeFuncValue(b.getValue(instr.Call.Value), instr.Call.Value.Type().Underlying().(*types.Signature)) funcPtr, context = b.decodeFuncValue(b.getValue(instr.Call.Value), instr.Call.Value.Type().Underlying().(*types.Signature))
params = append(params, context) // context parameter params = append(params, context) // context parameter
hasContext = true
switch b.Scheduler { switch b.Scheduler {
case "none", "coroutines": case "none", "coroutines":
// There are no additional parameters needed for the goroutine start operation. // There are no additional parameters needed for the goroutine start operation.
@ -99,7 +108,7 @@ func (b *builder) createGo(instr *ssa.Go) {
var callee, stackSize llvm.Value var callee, stackSize llvm.Value
switch b.Scheduler { switch b.Scheduler {
case "none", "tasks": case "none", "tasks":
callee = b.createGoroutineStartWrapper(funcPtr, prefix, instr.Pos()) callee = b.createGoroutineStartWrapper(funcPtr, prefix, hasContext, instr.Pos())
if b.AutomaticStackSize { if b.AutomaticStackSize {
// The stack size is not known until after linking. Call a dummy // The stack size is not known until after linking. Call a dummy
// function that will be replaced with a load from a special ELF // function that will be replaced with a load from a special ELF
@ -145,7 +154,12 @@ func (b *builder) createGo(instr *ssa.Go) {
// allows a single (pointer) argument to the newly started goroutine. Also, it // allows a single (pointer) argument to the newly started goroutine. Also, it
// ignores the return value because newly started goroutines do not have a // ignores the return value because newly started goroutines do not have a
// return value. // return value.
func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix string, pos token.Pos) llvm.Value { //
// The hasContext parameter indicates whether the context parameter (the second
// to last parameter of the function) is used for this wrapper. If hasContext is
// false, the parameter bundle is assumed to have no context parameter and undef
// is passed instead.
func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix string, hasContext bool, pos token.Pos) llvm.Value {
var wrapper llvm.Value var wrapper llvm.Value
builder := c.ctx.NewBuilder() builder := c.ctx.NewBuilder()
@ -192,8 +206,15 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
// Create the list of params for the call. // Create the list of params for the call.
paramTypes := fn.Type().ElementType().ParamTypes() paramTypes := fn.Type().ElementType().ParamTypes()
params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes[:len(paramTypes)-1]) paramTypes = paramTypes[:len(paramTypes)-1] // strip parentHandle parameter
params = append(params, llvm.Undef(c.i8ptrType)) if !hasContext {
paramTypes = paramTypes[:len(paramTypes)-1] // strip context parameter
}
params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes)
if !hasContext {
params = append(params, llvm.Undef(c.i8ptrType)) // add dummy context parameter
}
params = append(params, llvm.Undef(c.i8ptrType)) // add dummy parentHandle parameter
// Create the call. // Create the call.
builder.CreateCall(fn, params, "") builder.CreateCall(fn, params, "")
@ -255,8 +276,11 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
// Get the function pointer. // Get the function pointer.
fnPtr := params[len(params)-1] fnPtr := params[len(params)-1]
// Ignore the last param, which isn't used anymore. // The last parameter in the packed object has somewhat of a dual role.
// TODO: avoid this extra "parent handle" parameter in most functions. // Inside the parameter bundle it's the function pointer, stored right
// after the context pointer. But in the IR call instruction, it's the
// parentHandle function that's always undef outside of the coroutines
// scheduler. Thus, make the parameter undef here.
params[len(params)-1] = llvm.Undef(c.i8ptrType) params[len(params)-1] = llvm.Undef(c.i8ptrType)
// Create the call. // Create the call.

23
compiler/testdata/goroutine-cortex-m-qemu.ll предоставленный
Просмотреть файл

@ -9,9 +9,6 @@ target triple = "armv7m-none-eabi"
%"internal/task.state" = type { i32, i32* } %"internal/task.state" = type { i32, i32* }
%runtime.chanSelectState = type { %runtime.channel*, 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 }
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*) declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr { define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
@ -22,7 +19,7 @@ entry:
define hidden void @main.regularFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr { define hidden void @main.regularFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
entry: entry:
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @"main.regularFunction$gowrapper" to i32), i8* undef, i8* undef) %stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @"main.regularFunction$gowrapper" to i32), i8* undef, i8* undef)
call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @"main.regularFunction$gowrapper" to i32), i8* bitcast ({ i32, i8* }* @"main.regularFunctionGoroutine$pack" to i8*), i32 %stacksize, i8* undef, i8* null) call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @"main.regularFunction$gowrapper" to i32), i8* nonnull inttoptr (i32 5 to i8*), i32 %stacksize, i8* undef, i8* null)
ret void ret void
} }
@ -30,12 +27,8 @@ declare void @main.regularFunction(i32, i8*, i8*)
define linkonce_odr void @"main.regularFunction$gowrapper"(i8* %0) unnamed_addr #0 { define linkonce_odr void @"main.regularFunction$gowrapper"(i8* %0) unnamed_addr #0 {
entry: entry:
%1 = bitcast i8* %0 to i32* %unpack.int = ptrtoint i8* %0 to i32
%2 = load i32, i32* %1, align 4 call void @main.regularFunction(i32 %unpack.int, i8* undef, i8* undef)
%3 = getelementptr inbounds i8, i8* %0, i32 4
%4 = bitcast i8* %3 to i8**
%5 = load i8*, i8** %4, align 4
call void @main.regularFunction(i32 %2, i8* %5, i8* undef)
ret void ret void
} }
@ -46,7 +39,7 @@ declare void @"internal/task.start"(i32, i8*, i32, i8*, i8*)
define hidden void @main.inlineFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr { define hidden void @main.inlineFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
entry: entry:
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @"main.inlineFunctionGoroutine$1$gowrapper" to i32), i8* undef, i8* undef) %stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @"main.inlineFunctionGoroutine$1$gowrapper" to i32), i8* undef, i8* undef)
call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @"main.inlineFunctionGoroutine$1$gowrapper" to i32), i8* bitcast ({ i32, i8* }* @"main.inlineFunctionGoroutine$pack" to i8*), i32 %stacksize, i8* undef, i8* null) call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @"main.inlineFunctionGoroutine$1$gowrapper" to i32), i8* nonnull inttoptr (i32 5 to i8*), i32 %stacksize, i8* undef, i8* null)
ret void ret void
} }
@ -57,12 +50,8 @@ entry:
define linkonce_odr void @"main.inlineFunctionGoroutine$1$gowrapper"(i8* %0) unnamed_addr #1 { define linkonce_odr void @"main.inlineFunctionGoroutine$1$gowrapper"(i8* %0) unnamed_addr #1 {
entry: entry:
%1 = bitcast i8* %0 to i32* %unpack.int = ptrtoint i8* %0 to i32
%2 = load i32, i32* %1, align 4 call void @"main.inlineFunctionGoroutine$1"(i32 %unpack.int, i8* undef, i8* undef)
%3 = getelementptr inbounds i8, i8* %0, i32 4
%4 = bitcast i8* %3 to i8**
%5 = load i8*, i8** %4, align 4
call void @"main.inlineFunctionGoroutine$1"(i32 %2, i8* %5, i8* undef)
ret void ret void
} }