Skip to content

Commit

Permalink
compiler: insert basic blocks at an appropriate location
Browse files Browse the repository at this point in the history
For example, this commit moves the 'throw' branch of an assertion (nil
check, slice index check, etc) to the end of the function while
inserting the "continue" branch right after the insert location. This
makes the resulting IR easier to follow.

For some reason, this also reduces code size a bit on average. The
TinyGo smoke tests saw a reduction of 0.22%, mainly from WebAssembly.
The drivers repo saw little average change in code size (-0.01%).

This commit also adds a few compiler tests for the defer keyword.
  • Loading branch information
aykevl committed Jun 15, 2022
1 parent 405dd87 commit f2fd452
Show file tree
Hide file tree
Showing 13 changed files with 272 additions and 89 deletions.
4 changes: 3 additions & 1 deletion compiler/asserts.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,10 @@ func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc
}
}

// Put the fault block at the end of the function and the next block at the
// current insert position.
faultBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".throw")
nextBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".next")
nextBlock := b.insertBasicBlock(blockPrefix + ".next")
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes

// Now branch to the out-of-bounds or the regular block.
Expand Down
1 change: 1 addition & 0 deletions compiler/compiler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func TestCompiler(t *testing.T) {
{"float.go", "", ""},
{"interface.go", "", ""},
{"func.go", "", ""},
{"defer.go", "cortex-m-qemu", ""},
{"pragma.go", "", ""},
{"goroutine.go", "wasm", "asyncify"},
{"goroutine.go", "cortex-m-qemu", "tasks"},
Expand Down
13 changes: 7 additions & 6 deletions compiler/defer.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ package compiler

import (
"go/types"
"strconv"

"github.com/tinygo-org/tinygo/compiler/llvmutil"
"golang.org/x/tools/go/ssa"
Expand Down Expand Up @@ -248,11 +249,11 @@ func (b *builder) createRunDefers() {
// }
// }

// Create loop.
loophead := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.loophead")
loop := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.loop")
unreachable := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.default")
end := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.end")
// Create loop, in the order: loophead, loop, callback0, callback1, ..., unreachable, end.
end := b.insertBasicBlock("rundefers.end")
unreachable := b.ctx.InsertBasicBlock(end, "rundefers.default")
loop := b.ctx.InsertBasicBlock(unreachable, "rundefers.loop")
loophead := b.ctx.InsertBasicBlock(loop, "rundefers.loophead")
b.CreateBr(loophead)

// Create loop head:
Expand Down Expand Up @@ -284,7 +285,7 @@ func (b *builder) createRunDefers() {
// Create switch case, for example:
// case 0:
// // run first deferred call
block := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.callback")
block := b.insertBasicBlock("rundefers.callback" + strconv.Itoa(i))
sw.AddCase(llvm.ConstInt(b.uintptrType, uint64(i), false), block)
b.SetInsertPointAtEnd(block)
switch callback := callback.(type) {
Expand Down
4 changes: 2 additions & 2 deletions compiler/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,8 +389,8 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
// value.

prevBlock := b.GetInsertBlock()
okBlock := b.ctx.AddBasicBlock(b.llvmFn, "typeassert.ok")
nextBlock := b.ctx.AddBasicBlock(b.llvmFn, "typeassert.next")
okBlock := b.insertBasicBlock("typeassert.ok")
nextBlock := b.insertBasicBlock("typeassert.next")
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
b.CreateCondBr(commaOk, okBlock, nextBlock)

Expand Down
17 changes: 17 additions & 0 deletions compiler/llvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,23 @@ func (b *builder) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitca
return llvmutil.CreateTemporaryAlloca(b.Builder, b.mod, t, name)
}

// insertBasicBlock inserts a new basic block after the current basic block.
// This is useful when inserting new basic blocks while converting a
// *ssa.BasicBlock to a llvm.BasicBlock and the LLVM basic block needs some
// extra blocks.
// It does not update b.blockExits, this must be done by the caller.
func (b *builder) insertBasicBlock(name string) llvm.BasicBlock {
currentBB := b.Builder.GetInsertBlock()
nextBB := llvm.NextBasicBlock(currentBB)
if nextBB.IsNil() {
// Last basic block in the function, so add one to the end.
return b.ctx.AddBasicBlock(b.llvmFn, name)
}
// Insert a basic block before the next basic block - that is, at the
// current insert location.
return b.ctx.InsertBasicBlock(nextBB, name)
}

// emitLifetimeEnd signals the end of an (alloca) lifetime by calling the
// llvm.lifetime.end intrinsic. It is commonly used together with
// createTemporaryAlloca.
Expand Down
32 changes: 16 additions & 16 deletions compiler/testdata/basic.ll
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,17 @@ entry:
%0 = icmp eq i32 %y, 0
br i1 %0, label %divbyzero.throw, label %divbyzero.next

divbyzero.throw: ; preds = %entry
call void @runtime.divideByZeroPanic(i8* undef) #0
unreachable

divbyzero.next: ; preds = %entry
%1 = icmp eq i32 %y, -1
%2 = icmp eq i32 %x, -2147483648
%3 = and i1 %1, %2
%4 = select i1 %3, i32 1, i32 %y
%5 = sdiv i32 %x, %4
ret i32 %5

divbyzero.throw: ; preds = %entry
call void @runtime.divideByZeroPanic(i8* undef) #0
unreachable
}

declare void @runtime.divideByZeroPanic(i8*)
Expand All @@ -57,13 +57,13 @@ entry:
%0 = icmp eq i32 %y, 0
br i1 %0, label %divbyzero.throw, label %divbyzero.next

divbyzero.throw: ; preds = %entry
call void @runtime.divideByZeroPanic(i8* undef) #0
unreachable

divbyzero.next: ; preds = %entry
%1 = udiv i32 %x, %y
ret i32 %1

divbyzero.throw: ; preds = %entry
call void @runtime.divideByZeroPanic(i8* undef) #0
unreachable
}

; Function Attrs: nounwind
Expand All @@ -72,17 +72,17 @@ entry:
%0 = icmp eq i32 %y, 0
br i1 %0, label %divbyzero.throw, label %divbyzero.next

divbyzero.throw: ; preds = %entry
call void @runtime.divideByZeroPanic(i8* undef) #0
unreachable

divbyzero.next: ; preds = %entry
%1 = icmp eq i32 %y, -1
%2 = icmp eq i32 %x, -2147483648
%3 = and i1 %1, %2
%4 = select i1 %3, i32 1, i32 %y
%5 = srem i32 %x, %4
ret i32 %5

divbyzero.throw: ; preds = %entry
call void @runtime.divideByZeroPanic(i8* undef) #0
unreachable
}

; Function Attrs: nounwind
Expand All @@ -91,13 +91,13 @@ entry:
%0 = icmp eq i32 %y, 0
br i1 %0, label %divbyzero.throw, label %divbyzero.next

divbyzero.throw: ; preds = %entry
call void @runtime.divideByZeroPanic(i8* undef) #0
unreachable

divbyzero.next: ; preds = %entry
%1 = urem i32 %x, %y
ret i32 %1

divbyzero.throw: ; preds = %entry
call void @runtime.divideByZeroPanic(i8* undef) #0
unreachable
}

; Function Attrs: nounwind
Expand Down
142 changes: 142 additions & 0 deletions compiler/testdata/defer-cortex-m-qemu.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
; ModuleID = 'defer.go'
source_filename = "defer.go"
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "thumbv7m-unknown-unknown-eabi"

%runtime._defer = type { i32, %runtime._defer* }

declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)

; Function Attrs: nounwind
define hidden void @main.init(i8* %context) unnamed_addr #0 {
entry:
ret void
}

declare void @main.external(i8*)

; Function Attrs: nounwind
define hidden void @main.deferSimple(i8* %context) unnamed_addr #0 {
entry:
%defer.alloca = alloca { i32, %runtime._defer* }, align 4
%deferPtr = alloca %runtime._defer*, align 4
store %runtime._defer* null, %runtime._defer** %deferPtr, align 4
%defer.alloca.repack = getelementptr inbounds { i32, %runtime._defer* }, { i32, %runtime._defer* }* %defer.alloca, i32 0, i32 0
store i32 0, i32* %defer.alloca.repack, align 4
%defer.alloca.repack1 = getelementptr inbounds { i32, %runtime._defer* }, { i32, %runtime._defer* }* %defer.alloca, i32 0, i32 1
store %runtime._defer* null, %runtime._defer** %defer.alloca.repack1, align 4
%0 = bitcast %runtime._defer** %deferPtr to { i32, %runtime._defer* }**
store { i32, %runtime._defer* }* %defer.alloca, { i32, %runtime._defer* }** %0, align 4
call void @main.external(i8* undef) #0
br label %rundefers.loophead

rundefers.loophead: ; preds = %rundefers.callback0, %entry
%1 = load %runtime._defer*, %runtime._defer** %deferPtr, align 4
%stackIsNil = icmp eq %runtime._defer* %1, null
br i1 %stackIsNil, label %rundefers.end, label %rundefers.loop

rundefers.loop: ; preds = %rundefers.loophead
%stack.next.gep = getelementptr inbounds %runtime._defer, %runtime._defer* %1, i32 0, i32 1
%stack.next = load %runtime._defer*, %runtime._defer** %stack.next.gep, align 4
store %runtime._defer* %stack.next, %runtime._defer** %deferPtr, align 4
%callback.gep = getelementptr inbounds %runtime._defer, %runtime._defer* %1, i32 0, i32 0
%callback = load i32, i32* %callback.gep, align 4
switch i32 %callback, label %rundefers.default [
i32 0, label %rundefers.callback0
]

rundefers.callback0: ; preds = %rundefers.loop
call void @"main.deferSimple$1"(i8* undef)
br label %rundefers.loophead

rundefers.default: ; preds = %rundefers.loop
unreachable

rundefers.end: ; preds = %rundefers.loophead
ret void

recover: ; No predecessors!
ret void
}

; Function Attrs: nounwind
define hidden void @"main.deferSimple$1"(i8* %context) unnamed_addr #0 {
entry:
call void @runtime.printint32(i32 3, i8* undef) #0
ret void
}

declare void @runtime.printint32(i32, i8*)

; Function Attrs: nounwind
define hidden void @main.deferMultiple(i8* %context) unnamed_addr #0 {
entry:
%defer.alloca2 = alloca { i32, %runtime._defer* }, align 4
%defer.alloca = alloca { i32, %runtime._defer* }, align 4
%deferPtr = alloca %runtime._defer*, align 4
store %runtime._defer* null, %runtime._defer** %deferPtr, align 4
%defer.alloca.repack = getelementptr inbounds { i32, %runtime._defer* }, { i32, %runtime._defer* }* %defer.alloca, i32 0, i32 0
store i32 0, i32* %defer.alloca.repack, align 4
%defer.alloca.repack5 = getelementptr inbounds { i32, %runtime._defer* }, { i32, %runtime._defer* }* %defer.alloca, i32 0, i32 1
store %runtime._defer* null, %runtime._defer** %defer.alloca.repack5, align 4
%0 = bitcast %runtime._defer** %deferPtr to { i32, %runtime._defer* }**
store { i32, %runtime._defer* }* %defer.alloca, { i32, %runtime._defer* }** %0, align 4
%defer.alloca2.repack = getelementptr inbounds { i32, %runtime._defer* }, { i32, %runtime._defer* }* %defer.alloca2, i32 0, i32 0
store i32 1, i32* %defer.alloca2.repack, align 4
%defer.alloca2.repack6 = getelementptr inbounds { i32, %runtime._defer* }, { i32, %runtime._defer* }* %defer.alloca2, i32 0, i32 1
%1 = bitcast %runtime._defer** %defer.alloca2.repack6 to { i32, %runtime._defer* }**
store { i32, %runtime._defer* }* %defer.alloca, { i32, %runtime._defer* }** %1, align 4
%2 = bitcast %runtime._defer** %deferPtr to { i32, %runtime._defer* }**
store { i32, %runtime._defer* }* %defer.alloca2, { i32, %runtime._defer* }** %2, align 4
call void @main.external(i8* undef) #0
br label %rundefers.loophead

rundefers.loophead: ; preds = %rundefers.callback1, %rundefers.callback0, %entry
%3 = load %runtime._defer*, %runtime._defer** %deferPtr, align 4
%stackIsNil = icmp eq %runtime._defer* %3, null
br i1 %stackIsNil, label %rundefers.end, label %rundefers.loop

rundefers.loop: ; preds = %rundefers.loophead
%stack.next.gep = getelementptr inbounds %runtime._defer, %runtime._defer* %3, i32 0, i32 1
%stack.next = load %runtime._defer*, %runtime._defer** %stack.next.gep, align 4
store %runtime._defer* %stack.next, %runtime._defer** %deferPtr, align 4
%callback.gep = getelementptr inbounds %runtime._defer, %runtime._defer* %3, i32 0, i32 0
%callback = load i32, i32* %callback.gep, align 4
switch i32 %callback, label %rundefers.default [
i32 0, label %rundefers.callback0
i32 1, label %rundefers.callback1
]

rundefers.callback0: ; preds = %rundefers.loop
call void @"main.deferMultiple$1"(i8* undef)
br label %rundefers.loophead

rundefers.callback1: ; preds = %rundefers.loop
call void @"main.deferMultiple$2"(i8* undef)
br label %rundefers.loophead

rundefers.default: ; preds = %rundefers.loop
unreachable

rundefers.end: ; preds = %rundefers.loophead
ret void

recover: ; No predecessors!
ret void
}

; Function Attrs: nounwind
define hidden void @"main.deferMultiple$1"(i8* %context) unnamed_addr #0 {
entry:
call void @runtime.printint32(i32 3, i8* undef) #0
ret void
}

; Function Attrs: nounwind
define hidden void @"main.deferMultiple$2"(i8* %context) unnamed_addr #0 {
entry:
call void @runtime.printint32(i32 5, i8* undef) #0
ret void
}

attributes #0 = { nounwind }
20 changes: 20 additions & 0 deletions compiler/testdata/defer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package main

func external()

func deferSimple() {
defer func() {
print(3)
}()
external()
}

func deferMultiple() {
defer func() {
print(3)
}()
defer func() {
print(5)
}()
external()
}
8 changes: 4 additions & 4 deletions compiler/testdata/func.ll
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ entry:
%0 = icmp eq void ()* %callback.funcptr, null
br i1 %0, label %fpcall.throw, label %fpcall.next

fpcall.throw: ; preds = %entry
call void @runtime.nilPanic(i8* undef) #0
unreachable

fpcall.next: ; preds = %entry
%1 = bitcast void ()* %callback.funcptr to void (i32, i8*)*
call void %1(i32 3, i8* %callback.context) #0
ret void

fpcall.throw: ; preds = %entry
call void @runtime.nilPanic(i8* undef) #0
unreachable
}

declare void @runtime.nilPanic(i8*)
Expand Down
Loading

0 comments on commit f2fd452

Please sign in to comment.