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 Nov 29, 2021
1 parent 2561ae3 commit 2e95949
Show file tree
Hide file tree
Showing 13 changed files with 276 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", "", "coroutines"},
{"defer.go", "", ""},
{"pragma.go", "", ""},
{"goroutine.go", "wasm", "asyncify"},
{"goroutine.go", "wasm", "coroutines"},
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 @@ -400,8 +400,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 @@ -34,17 +34,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, i8* null) #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, i8* null) #0
unreachable
}

declare void @runtime.divideByZeroPanic(i8*, i8*)
Expand All @@ -55,13 +55,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, i8* null) #0
unreachable

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

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

; Function Attrs: nounwind
Expand All @@ -70,17 +70,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, i8* null) #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, i8* null) #0
unreachable
}

; Function Attrs: nounwind
Expand All @@ -89,13 +89,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, i8* null) #0
unreachable

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

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

; Function Attrs: nounwind
Expand Down
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()
}
146 changes: 146 additions & 0 deletions compiler/testdata/defer.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
; ModuleID = 'defer.go'
source_filename = "defer.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32-unknown-wasi"

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

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

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

declare void @main.external(i8*, i8*)

; Function Attrs: nounwind
define hidden void @main.deferSimple(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%defer.alloca = alloca { i32, %runtime._defer* }, align 8
%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 8
%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, 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
%2 = bitcast %runtime._defer** %stack.next.gep to i32*
%stack.next2 = load i32, i32* %2, align 4
%3 = bitcast %runtime._defer** %deferPtr to i32*
store i32 %stack.next2, i32* %3, 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, 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, i8* %parentHandle) unnamed_addr #0 {
entry:
call void @runtime.printint32(i32 3, i8* undef, i8* null) #0
ret void
}

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

; Function Attrs: nounwind
define hidden void @main.deferMultiple(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%defer.alloca2 = alloca { i32, %runtime._defer* }, align 8
%defer.alloca = alloca { i32, %runtime._defer* }, align 8
%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 8
%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 8
%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, 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
%4 = bitcast %runtime._defer** %stack.next.gep to i32*
%stack.next8 = load i32, i32* %4, align 4
%5 = bitcast %runtime._defer** %deferPtr to i32*
store i32 %stack.next8, i32* %5, 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, i8* undef)
br label %rundefers.loophead

rundefers.callback1: ; preds = %rundefers.loop
call void @"main.deferMultiple$2"(i8* undef, 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, i8* %parentHandle) unnamed_addr #0 {
entry:
call void @runtime.printint32(i32 3, i8* undef, i8* null) #0
ret void
}

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

attributes #0 = { nounwind }
8 changes: 4 additions & 4 deletions compiler/testdata/func-coroutines.ll
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ entry:
%1 = icmp eq i32 %0, 0
br i1 %1, label %fpcall.throw, label %fpcall.next

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

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

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

declare i32 @runtime.getFuncPtr(i8*, i32, i8* dereferenceable_or_null(1), i8*, i8*)
Expand Down
Loading

0 comments on commit 2e95949

Please sign in to comment.