Skip to content

Commit 79ba6a5

Browse files
aykevldeadprogram
authored andcommitted
compiler: insert basic blocks at an appropriate location
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.
1 parent 2fb5174 commit 79ba6a5

File tree

13 files changed

+272
-89
lines changed

13 files changed

+272
-89
lines changed

compiler/asserts.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,8 +240,10 @@ func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc
240240
}
241241
}
242242

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

247249
// Now branch to the out-of-bounds or the regular block.

compiler/compiler_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ func TestCompiler(t *testing.T) {
4949
{"float.go", "", ""},
5050
{"interface.go", "", ""},
5151
{"func.go", "", ""},
52+
{"defer.go", "cortex-m-qemu", ""},
5253
{"pragma.go", "", ""},
5354
{"goroutine.go", "wasm", "asyncify"},
5455
{"goroutine.go", "cortex-m-qemu", "tasks"},

compiler/defer.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ package compiler
1515

1616
import (
1717
"go/types"
18+
"strconv"
1819

1920
"github.com/tinygo-org/tinygo/compiler/llvmutil"
2021
"golang.org/x/tools/go/ssa"
@@ -248,11 +249,11 @@ func (b *builder) createRunDefers() {
248249
// }
249250
// }
250251

251-
// Create loop.
252-
loophead := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.loophead")
253-
loop := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.loop")
254-
unreachable := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.default")
255-
end := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.end")
252+
// Create loop, in the order: loophead, loop, callback0, callback1, ..., unreachable, end.
253+
end := b.insertBasicBlock("rundefers.end")
254+
unreachable := b.ctx.InsertBasicBlock(end, "rundefers.default")
255+
loop := b.ctx.InsertBasicBlock(unreachable, "rundefers.loop")
256+
loophead := b.ctx.InsertBasicBlock(loop, "rundefers.loophead")
256257
b.CreateBr(loophead)
257258

258259
// Create loop head:
@@ -284,7 +285,7 @@ func (b *builder) createRunDefers() {
284285
// Create switch case, for example:
285286
// case 0:
286287
// // run first deferred call
287-
block := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.callback")
288+
block := b.insertBasicBlock("rundefers.callback" + strconv.Itoa(i))
288289
sw.AddCase(llvm.ConstInt(b.uintptrType, uint64(i), false), block)
289290
b.SetInsertPointAtEnd(block)
290291
switch callback := callback.(type) {

compiler/interface.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -389,8 +389,8 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
389389
// value.
390390

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

compiler/llvm.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,23 @@ func (b *builder) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitca
2323
return llvmutil.CreateTemporaryAlloca(b.Builder, b.mod, t, name)
2424
}
2525

26+
// insertBasicBlock inserts a new basic block after the current basic block.
27+
// This is useful when inserting new basic blocks while converting a
28+
// *ssa.BasicBlock to a llvm.BasicBlock and the LLVM basic block needs some
29+
// extra blocks.
30+
// It does not update b.blockExits, this must be done by the caller.
31+
func (b *builder) insertBasicBlock(name string) llvm.BasicBlock {
32+
currentBB := b.Builder.GetInsertBlock()
33+
nextBB := llvm.NextBasicBlock(currentBB)
34+
if nextBB.IsNil() {
35+
// Last basic block in the function, so add one to the end.
36+
return b.ctx.AddBasicBlock(b.llvmFn, name)
37+
}
38+
// Insert a basic block before the next basic block - that is, at the
39+
// current insert location.
40+
return b.ctx.InsertBasicBlock(nextBB, name)
41+
}
42+
2643
// emitLifetimeEnd signals the end of an (alloca) lifetime by calling the
2744
// llvm.lifetime.end intrinsic. It is commonly used together with
2845
// createTemporaryAlloca.

compiler/testdata/basic.ll

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,17 @@ entry:
3636
%0 = icmp eq i32 %y, 0
3737
br i1 %0, label %divbyzero.throw, label %divbyzero.next
3838

39-
divbyzero.throw: ; preds = %entry
40-
call void @runtime.divideByZeroPanic(i8* undef) #0
41-
unreachable
42-
4339
divbyzero.next: ; preds = %entry
4440
%1 = icmp eq i32 %y, -1
4541
%2 = icmp eq i32 %x, -2147483648
4642
%3 = and i1 %1, %2
4743
%4 = select i1 %3, i32 1, i32 %y
4844
%5 = sdiv i32 %x, %4
4945
ret i32 %5
46+
47+
divbyzero.throw: ; preds = %entry
48+
call void @runtime.divideByZeroPanic(i8* undef) #0
49+
unreachable
5050
}
5151

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

60-
divbyzero.throw: ; preds = %entry
61-
call void @runtime.divideByZeroPanic(i8* undef) #0
62-
unreachable
63-
6460
divbyzero.next: ; preds = %entry
6561
%1 = udiv i32 %x, %y
6662
ret i32 %1
63+
64+
divbyzero.throw: ; preds = %entry
65+
call void @runtime.divideByZeroPanic(i8* undef) #0
66+
unreachable
6767
}
6868

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

75-
divbyzero.throw: ; preds = %entry
76-
call void @runtime.divideByZeroPanic(i8* undef) #0
77-
unreachable
78-
7975
divbyzero.next: ; preds = %entry
8076
%1 = icmp eq i32 %y, -1
8177
%2 = icmp eq i32 %x, -2147483648
8278
%3 = and i1 %1, %2
8379
%4 = select i1 %3, i32 1, i32 %y
8480
%5 = srem i32 %x, %4
8581
ret i32 %5
82+
83+
divbyzero.throw: ; preds = %entry
84+
call void @runtime.divideByZeroPanic(i8* undef) #0
85+
unreachable
8686
}
8787

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

94-
divbyzero.throw: ; preds = %entry
95-
call void @runtime.divideByZeroPanic(i8* undef) #0
96-
unreachable
97-
9894
divbyzero.next: ; preds = %entry
9995
%1 = urem i32 %x, %y
10096
ret i32 %1
97+
98+
divbyzero.throw: ; preds = %entry
99+
call void @runtime.divideByZeroPanic(i8* undef) #0
100+
unreachable
101101
}
102102

103103
; Function Attrs: nounwind
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
; ModuleID = 'defer.go'
2+
source_filename = "defer.go"
3+
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
4+
target triple = "thumbv7m-unknown-unknown-eabi"
5+
6+
%runtime._defer = type { i32, %runtime._defer* }
7+
8+
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
9+
10+
; Function Attrs: nounwind
11+
define hidden void @main.init(i8* %context) unnamed_addr #0 {
12+
entry:
13+
ret void
14+
}
15+
16+
declare void @main.external(i8*)
17+
18+
; Function Attrs: nounwind
19+
define hidden void @main.deferSimple(i8* %context) unnamed_addr #0 {
20+
entry:
21+
%defer.alloca = alloca { i32, %runtime._defer* }, align 4
22+
%deferPtr = alloca %runtime._defer*, align 4
23+
store %runtime._defer* null, %runtime._defer** %deferPtr, align 4
24+
%defer.alloca.repack = getelementptr inbounds { i32, %runtime._defer* }, { i32, %runtime._defer* }* %defer.alloca, i32 0, i32 0
25+
store i32 0, i32* %defer.alloca.repack, align 4
26+
%defer.alloca.repack1 = getelementptr inbounds { i32, %runtime._defer* }, { i32, %runtime._defer* }* %defer.alloca, i32 0, i32 1
27+
store %runtime._defer* null, %runtime._defer** %defer.alloca.repack1, align 4
28+
%0 = bitcast %runtime._defer** %deferPtr to { i32, %runtime._defer* }**
29+
store { i32, %runtime._defer* }* %defer.alloca, { i32, %runtime._defer* }** %0, align 4
30+
call void @main.external(i8* undef) #0
31+
br label %rundefers.loophead
32+
33+
rundefers.loophead: ; preds = %rundefers.callback0, %entry
34+
%1 = load %runtime._defer*, %runtime._defer** %deferPtr, align 4
35+
%stackIsNil = icmp eq %runtime._defer* %1, null
36+
br i1 %stackIsNil, label %rundefers.end, label %rundefers.loop
37+
38+
rundefers.loop: ; preds = %rundefers.loophead
39+
%stack.next.gep = getelementptr inbounds %runtime._defer, %runtime._defer* %1, i32 0, i32 1
40+
%stack.next = load %runtime._defer*, %runtime._defer** %stack.next.gep, align 4
41+
store %runtime._defer* %stack.next, %runtime._defer** %deferPtr, align 4
42+
%callback.gep = getelementptr inbounds %runtime._defer, %runtime._defer* %1, i32 0, i32 0
43+
%callback = load i32, i32* %callback.gep, align 4
44+
switch i32 %callback, label %rundefers.default [
45+
i32 0, label %rundefers.callback0
46+
]
47+
48+
rundefers.callback0: ; preds = %rundefers.loop
49+
call void @"main.deferSimple$1"(i8* undef)
50+
br label %rundefers.loophead
51+
52+
rundefers.default: ; preds = %rundefers.loop
53+
unreachable
54+
55+
rundefers.end: ; preds = %rundefers.loophead
56+
ret void
57+
58+
recover: ; No predecessors!
59+
ret void
60+
}
61+
62+
; Function Attrs: nounwind
63+
define hidden void @"main.deferSimple$1"(i8* %context) unnamed_addr #0 {
64+
entry:
65+
call void @runtime.printint32(i32 3, i8* undef) #0
66+
ret void
67+
}
68+
69+
declare void @runtime.printint32(i32, i8*)
70+
71+
; Function Attrs: nounwind
72+
define hidden void @main.deferMultiple(i8* %context) unnamed_addr #0 {
73+
entry:
74+
%defer.alloca2 = alloca { i32, %runtime._defer* }, align 4
75+
%defer.alloca = alloca { i32, %runtime._defer* }, align 4
76+
%deferPtr = alloca %runtime._defer*, align 4
77+
store %runtime._defer* null, %runtime._defer** %deferPtr, align 4
78+
%defer.alloca.repack = getelementptr inbounds { i32, %runtime._defer* }, { i32, %runtime._defer* }* %defer.alloca, i32 0, i32 0
79+
store i32 0, i32* %defer.alloca.repack, align 4
80+
%defer.alloca.repack5 = getelementptr inbounds { i32, %runtime._defer* }, { i32, %runtime._defer* }* %defer.alloca, i32 0, i32 1
81+
store %runtime._defer* null, %runtime._defer** %defer.alloca.repack5, align 4
82+
%0 = bitcast %runtime._defer** %deferPtr to { i32, %runtime._defer* }**
83+
store { i32, %runtime._defer* }* %defer.alloca, { i32, %runtime._defer* }** %0, align 4
84+
%defer.alloca2.repack = getelementptr inbounds { i32, %runtime._defer* }, { i32, %runtime._defer* }* %defer.alloca2, i32 0, i32 0
85+
store i32 1, i32* %defer.alloca2.repack, align 4
86+
%defer.alloca2.repack6 = getelementptr inbounds { i32, %runtime._defer* }, { i32, %runtime._defer* }* %defer.alloca2, i32 0, i32 1
87+
%1 = bitcast %runtime._defer** %defer.alloca2.repack6 to { i32, %runtime._defer* }**
88+
store { i32, %runtime._defer* }* %defer.alloca, { i32, %runtime._defer* }** %1, align 4
89+
%2 = bitcast %runtime._defer** %deferPtr to { i32, %runtime._defer* }**
90+
store { i32, %runtime._defer* }* %defer.alloca2, { i32, %runtime._defer* }** %2, align 4
91+
call void @main.external(i8* undef) #0
92+
br label %rundefers.loophead
93+
94+
rundefers.loophead: ; preds = %rundefers.callback1, %rundefers.callback0, %entry
95+
%3 = load %runtime._defer*, %runtime._defer** %deferPtr, align 4
96+
%stackIsNil = icmp eq %runtime._defer* %3, null
97+
br i1 %stackIsNil, label %rundefers.end, label %rundefers.loop
98+
99+
rundefers.loop: ; preds = %rundefers.loophead
100+
%stack.next.gep = getelementptr inbounds %runtime._defer, %runtime._defer* %3, i32 0, i32 1
101+
%stack.next = load %runtime._defer*, %runtime._defer** %stack.next.gep, align 4
102+
store %runtime._defer* %stack.next, %runtime._defer** %deferPtr, align 4
103+
%callback.gep = getelementptr inbounds %runtime._defer, %runtime._defer* %3, i32 0, i32 0
104+
%callback = load i32, i32* %callback.gep, align 4
105+
switch i32 %callback, label %rundefers.default [
106+
i32 0, label %rundefers.callback0
107+
i32 1, label %rundefers.callback1
108+
]
109+
110+
rundefers.callback0: ; preds = %rundefers.loop
111+
call void @"main.deferMultiple$1"(i8* undef)
112+
br label %rundefers.loophead
113+
114+
rundefers.callback1: ; preds = %rundefers.loop
115+
call void @"main.deferMultiple$2"(i8* undef)
116+
br label %rundefers.loophead
117+
118+
rundefers.default: ; preds = %rundefers.loop
119+
unreachable
120+
121+
rundefers.end: ; preds = %rundefers.loophead
122+
ret void
123+
124+
recover: ; No predecessors!
125+
ret void
126+
}
127+
128+
; Function Attrs: nounwind
129+
define hidden void @"main.deferMultiple$1"(i8* %context) unnamed_addr #0 {
130+
entry:
131+
call void @runtime.printint32(i32 3, i8* undef) #0
132+
ret void
133+
}
134+
135+
; Function Attrs: nounwind
136+
define hidden void @"main.deferMultiple$2"(i8* %context) unnamed_addr #0 {
137+
entry:
138+
call void @runtime.printint32(i32 5, i8* undef) #0
139+
ret void
140+
}
141+
142+
attributes #0 = { nounwind }

compiler/testdata/defer.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package main
2+
3+
func external()
4+
5+
func deferSimple() {
6+
defer func() {
7+
print(3)
8+
}()
9+
external()
10+
}
11+
12+
func deferMultiple() {
13+
defer func() {
14+
print(3)
15+
}()
16+
defer func() {
17+
print(5)
18+
}()
19+
external()
20+
}

compiler/testdata/func.ll

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ entry:
1919
%0 = icmp eq void ()* %callback.funcptr, null
2020
br i1 %0, label %fpcall.throw, label %fpcall.next
2121

22-
fpcall.throw: ; preds = %entry
23-
call void @runtime.nilPanic(i8* undef) #0
24-
unreachable
25-
2622
fpcall.next: ; preds = %entry
2723
%1 = bitcast void ()* %callback.funcptr to void (i32, i8*)*
2824
call void %1(i32 3, i8* %callback.context) #0
2925
ret void
26+
27+
fpcall.throw: ; preds = %entry
28+
call void @runtime.nilPanic(i8* undef) #0
29+
unreachable
3030
}
3131

3232
declare void @runtime.nilPanic(i8*)

0 commit comments

Comments
 (0)