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.
This commit is contained in:
Ayke van Laethem
2018-12-09 16:14:47 +01:00
parent e42289ce61
commit 3fec22e819
3 changed files with 240 additions and 237 deletions
+16 -21
View File
@@ -62,26 +62,27 @@ type Compiler struct {
coroEndFunc llvm.Value
coroFreeFunc llvm.Value
initFuncs []llvm.Value
deferFuncs []*ir.Function
deferInvokeFuncs []InvokeDeferFunction
ctxDeferFuncs []ContextDeferFunction
interfaceInvokeWrappers []interfaceInvokeWrapper
ir *ir.Program
}
type Frame struct {
fn *ir.Function
locals map[ssa.Value]llvm.Value // local variables
blockEntries map[*ssa.BasicBlock]llvm.BasicBlock // a *ssa.BasicBlock may be split up
blockExits map[*ssa.BasicBlock]llvm.BasicBlock // these are the exit blocks
currentBlock *ssa.BasicBlock
phis []Phi
blocking bool
taskHandle llvm.Value
cleanupBlock llvm.BasicBlock
suspendBlock llvm.BasicBlock
deferPtr llvm.Value
difunc llvm.Metadata
fn *ir.Function
locals map[ssa.Value]llvm.Value // local variables
blockEntries map[*ssa.BasicBlock]llvm.BasicBlock // a *ssa.BasicBlock may be split up
blockExits map[*ssa.BasicBlock]llvm.BasicBlock // these are the exit blocks
currentBlock *ssa.BasicBlock
phis []Phi
blocking bool
taskHandle llvm.Value
cleanupBlock llvm.BasicBlock
suspendBlock llvm.BasicBlock
deferPtr llvm.Value
difunc llvm.Metadata
allDeferFuncs []interface{}
deferFuncs map[*ir.Function]int
deferInvokeFuncs map[string]int
deferClosureFuncs map[*ir.Function]int
}
type Phi struct {
@@ -342,12 +343,6 @@ func (c *Compiler) Compile(mainPath string) error {
}
}
// Create thunks for deferred functions.
err = c.finalizeDefers()
if err != nil {
return err
}
// Define the already declared functions that wrap methods for use in
// interfaces.
for _, state := range c.interfaceInvokeWrappers {