tinygo/compiler/defer.go
Ayke van Laethem 3fec22e819
compiler: avoid function pointers in defer calls
Implement defer in a different way, which results in smaller binaries.
The binary produced from testdata/calls.go (the only test case with
defer) is reduced a bit in size, but the savings in bytes greatly vary
by architecture:

Cortex-M0:    -96 .text / flash
WebAssembly: -215 entire file
Linux x64:    -32 .text

Deferred functions in TinyGo were implemented by creating a linked list
of struct objects that contain a function pointer to a thunk, a pointer
to the next object, and a list of parameters. When it was time to run
deferred functions, a helper runtime function called each function
pointer (the thunk) with the struct pointer as a parameter. This thunk
would then in turn extract the saved function parameter from the struct
and call the real function.

What this commit changes, is that the loop to call deferred functions is
moved into the end of the function (practically inlining it) and
replacing the thunks with direct calls inside this loop. This makes it
much easier for LLVM to perform all kinds of optimizations like inlining
and dead argument elimination.
2018-12-09 16:44:37 +01:00

329 строки
12 КиБ
Go

package compiler
// This file implements the 'defer' keyword in Go.
// Defer statements are implemented by transforming the function in the
// following way:
// * Creating an alloca in the entry block that contains a pointer (initially
// null) to the linked list of defer frames.
// * Every time a defer statement is executed, a new defer frame is created
// using alloca with a pointer to the previous defer frame, and the head
// pointer in the entry block is replaced with a pointer to this defer
// frame.
// * On return, runtime.rundefers is called which calls all deferred functions
// from the head of the linked list until it has gone through all defer
// frames.
import (
"go/types"
"github.com/aykevl/go-llvm"
"github.com/aykevl/tinygo/ir"
"golang.org/x/tools/go/ssa"
)
// deferInitFunc sets up this function for future deferred calls. It must be
// called from within the entry block when this function contains deferred
// calls.
func (c *Compiler) deferInitFunc(frame *Frame) {
// Some setup.
frame.deferFuncs = make(map[*ir.Function]int)
frame.deferInvokeFuncs = make(map[string]int)
frame.deferClosureFuncs = make(map[*ir.Function]int)
// Create defer list pointer.
deferType := llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)
frame.deferPtr = c.builder.CreateAlloca(deferType, "deferPtr")
c.builder.CreateStore(llvm.ConstPointerNull(deferType), frame.deferPtr)
}
// emitDefer emits a single defer instruction, to be run when this function
// returns.
func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
// The pointer to the previous defer struct, which we will replace to
// make a linked list.
next := c.builder.CreateLoad(frame.deferPtr, "defer.next")
var values []llvm.Value
valueTypes := []llvm.Type{c.uintptrType, next.Type()}
if instr.Call.IsInvoke() {
// Method call on an interface.
// Get callback type number.
methodName := instr.Call.Method.FullName()
if _, ok := frame.deferInvokeFuncs[methodName]; !ok {
frame.deferInvokeFuncs[methodName] = len(frame.allDeferFuncs)
frame.allDeferFuncs = append(frame.allDeferFuncs, &instr.Call)
}
callback := llvm.ConstInt(c.uintptrType, uint64(frame.deferInvokeFuncs[methodName]), false)
// Collect all values to be put in the struct (starting with
// runtime._defer fields, followed by the call parameters).
itf, err := c.parseExpr(frame, instr.Call.Value) // interface
if err != nil {
return err
}
receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver")
values = []llvm.Value{callback, next, receiverValue}
valueTypes = append(valueTypes, c.i8ptrType)
for _, arg := range instr.Call.Args {
val, err := c.parseExpr(frame, arg)
if err != nil {
return err
}
values = append(values, val)
valueTypes = append(valueTypes, val.Type())
}
} else if callee, ok := instr.Call.Value.(*ssa.Function); ok {
// Regular function call.
fn := c.ir.GetFunction(callee)
if _, ok := frame.deferFuncs[fn]; !ok {
frame.deferFuncs[fn] = len(frame.allDeferFuncs)
frame.allDeferFuncs = append(frame.allDeferFuncs, fn)
}
callback := llvm.ConstInt(c.uintptrType, uint64(frame.deferFuncs[fn]), false)
// Collect all values to be put in the struct (starting with
// runtime._defer fields).
values = []llvm.Value{callback, next}
for _, param := range instr.Call.Args {
llvmParam, err := c.parseExpr(frame, param)
if err != nil {
return err
}
values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type())
}
} else if makeClosure, ok := instr.Call.Value.(*ssa.MakeClosure); ok {
// Immediately applied function literal with free variables.
// Extract the context from the closure. We won't need the function
// pointer.
// TODO: ignore this closure entirely and put pointers to the free
// variables directly in the defer struct, avoiding a memory allocation.
closure, err := c.parseExpr(frame, instr.Call.Value)
if err != nil {
return err
}
context := c.builder.CreateExtractValue(closure, 0, "")
// Get the callback number.
fn := c.ir.GetFunction(makeClosure.Fn.(*ssa.Function))
if _, ok := frame.deferClosureFuncs[fn]; !ok {
frame.deferClosureFuncs[fn] = len(frame.allDeferFuncs)
frame.allDeferFuncs = append(frame.allDeferFuncs, makeClosure)
}
callback := llvm.ConstInt(c.uintptrType, uint64(frame.deferClosureFuncs[fn]), false)
// Collect all values to be put in the struct (starting with
// runtime._defer fields, followed by all parameters including the
// context pointer).
values = []llvm.Value{callback, next}
for _, param := range instr.Call.Args {
llvmParam, err := c.parseExpr(frame, param)
if err != nil {
return err
}
values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type())
}
values = append(values, context)
valueTypes = append(valueTypes, context.Type())
} else {
return c.makeError(instr.Pos(), "todo: defer on uncommon function call type")
}
// Make a struct out of the collected values to put in the defer frame.
deferFrameType := c.ctx.StructType(valueTypes, false)
deferFrame, err := c.getZeroValue(deferFrameType)
if err != nil {
return err
}
for i, value := range values {
deferFrame = c.builder.CreateInsertValue(deferFrame, value, i, "")
}
// Put this struct in an alloca.
alloca := c.builder.CreateAlloca(deferFrameType, "defer.alloca")
c.builder.CreateStore(deferFrame, alloca)
// Push it on top of the linked list by replacing deferPtr.
allocaCast := c.builder.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
c.builder.CreateStore(allocaCast, frame.deferPtr)
return nil
}
// emitRunDefers emits code to run all deferred functions.
func (c *Compiler) emitRunDefers(frame *Frame) error {
// Add a loop like the following:
// for stack != nil {
// _stack := stack
// stack = stack.next
// switch _stack.callback {
// case 0:
// // run first deferred call
// case 1:
// // run second deferred call
// // etc.
// default:
// unreachable
// }
// }
// Create loop.
loophead := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loophead")
loop := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loop")
unreachable := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.default")
end := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.end")
c.builder.CreateBr(loophead)
// Create loop head:
// for stack != nil {
c.builder.SetInsertPointAtEnd(loophead)
deferData := c.builder.CreateLoad(frame.deferPtr, "")
stackIsNil := c.builder.CreateICmp(llvm.IntEQ, deferData, llvm.ConstPointerNull(deferData.Type()), "stackIsNil")
c.builder.CreateCondBr(stackIsNil, end, loop)
// Create loop body:
// _stack := stack
// stack = stack.next
// switch stack.callback {
c.builder.SetInsertPointAtEnd(loop)
nextStackGEP := c.builder.CreateGEP(deferData, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 1, false), // .next field
}, "stack.next.gep")
nextStack := c.builder.CreateLoad(nextStackGEP, "stack.next")
c.builder.CreateStore(nextStack, frame.deferPtr)
gep := c.builder.CreateGEP(deferData, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false), // .callback field
}, "callback.gep")
callback := c.builder.CreateLoad(gep, "callback")
sw := c.builder.CreateSwitch(callback, unreachable, len(frame.allDeferFuncs))
for i, callback := range frame.allDeferFuncs {
// Create switch case, for example:
// case 0:
// // run first deferred call
block := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.callback")
sw.AddCase(llvm.ConstInt(c.uintptrType, uint64(i), false), block)
c.builder.SetInsertPointAtEnd(block)
switch callback := callback.(type) {
case *ssa.CallCommon:
// Call on an interface value.
if !callback.IsInvoke() {
panic("expected an invoke call, not a direct call")
}
// Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0), c.i8ptrType}
for _, arg := range callback.Args {
llvmType, err := c.getLLVMType(arg.Type())
if err != nil {
return err
}
valueTypes = append(valueTypes, llvmType)
}
deferFrameType := c.ctx.StructType(valueTypes, false)
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
// Extract the params from the struct (including receiver).
forwardParams := []llvm.Value{}
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
for i := 2; i < len(valueTypes); i++ {
gep := c.builder.CreateGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false)}, "gep")
forwardParam := c.builder.CreateLoad(gep, "param")
forwardParams = append(forwardParams, forwardParam)
}
if c.ir.SignatureNeedsContext(callback.Method.Type().(*types.Signature)) {
// This function takes an extra context parameter. An interface call
// cannot also be a closure but we have to supply the parameter
// anyway for platforms with a strict calling convention.
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
}
fnPtr, _, err := c.getInvokeCall(frame, callback)
if err != nil {
return err
}
c.createCall(fnPtr, forwardParams, "")
case *ir.Function:
// Direct call.
// Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)}
for _, param := range callback.Params {
llvmType, err := c.getLLVMType(param.Type())
if err != nil {
return err
}
valueTypes = append(valueTypes, llvmType)
}
deferFrameType := c.ctx.StructType(valueTypes, false)
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
// Extract the params from the struct.
forwardParams := []llvm.Value{}
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
for i := range callback.Params {
gep := c.builder.CreateGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i+2), false)}, "gep")
forwardParam := c.builder.CreateLoad(gep, "param")
forwardParams = append(forwardParams, forwardParam)
}
// Call real function.
c.createCall(callback.LLVMFn, forwardParams, "")
case *ssa.MakeClosure:
// Get the real defer struct type and cast to it.
fn := c.ir.GetFunction(callback.Fn.(*ssa.Function))
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)}
params := fn.Signature.Params()
for i := 0; i < params.Len(); i++ {
llvmType, err := c.getLLVMType(params.At(i).Type())
if err != nil {
return err
}
valueTypes = append(valueTypes, llvmType)
}
valueTypes = append(valueTypes, c.i8ptrType) // closure
deferFrameType := c.ctx.StructType(valueTypes, false)
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
// Extract the params from the struct.
forwardParams := []llvm.Value{}
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
for i := 2; i < len(valueTypes); i++ {
gep := c.builder.CreateGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false)}, "")
forwardParam := c.builder.CreateLoad(gep, "param")
forwardParams = append(forwardParams, forwardParam)
}
// Call deferred function.
c.createCall(fn.LLVMFn, forwardParams, "")
default:
panic("unknown deferred function type")
}
// Branch back to the start of the loop.
c.builder.CreateBr(loophead)
}
// Create default unreachable block:
// default:
// unreachable
// }
c.builder.SetInsertPointAtEnd(unreachable)
c.builder.CreateUnreachable()
// End of loop.
c.builder.SetInsertPointAtEnd(end)
return nil
}