compiler: implement recover() built-in function

This commit is contained in:
Ayke van Laethem
2021-11-29 14:54:34 +01:00
committed by Ron Evans
parent 79ba6a50c3
commit 8d6b210c09
31 changed files with 868 additions and 191 deletions
+168 -22
View File
@@ -22,6 +22,32 @@ import (
"tinygo.org/x/go-llvm"
)
// supportsRecover returns whether the compiler supports the recover() builtin
// for the current architecture.
func (b *builder) supportsRecover() bool {
switch b.archFamily() {
case "wasm32":
// Probably needs to be implemented using the exception handling
// proposal of WebAssembly:
// https://github.com/WebAssembly/exception-handling
return false
case "avr", "riscv64", "xtensa":
// TODO: add support for these architectures
return false
default:
return true
}
}
// hasDeferFrame returns whether the current function needs to catch panics and
// run defers.
func (b *builder) hasDeferFrame() bool {
if b.fn.Recover == nil {
return false
}
return b.supportsRecover()
}
// deferInitFunc sets up this function for future deferred calls. It must be
// called from within the entry block when this function contains deferred
// calls.
@@ -37,6 +63,125 @@ func (b *builder) deferInitFunc() {
deferType := llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)
b.deferPtr = b.CreateAlloca(deferType, "deferPtr")
b.CreateStore(llvm.ConstPointerNull(deferType), b.deferPtr)
if b.hasDeferFrame() {
// Set up the defer frame with the current stack pointer.
// This assumes that the stack pointer doesn't move outside of the
// function prologue/epilogue (an invariant maintained by TinyGo but
// possibly broken by the C alloca function).
// The frame pointer is _not_ saved, because it is marked as clobbered
// in the setjmp-like inline assembly.
deferFrameType := b.getLLVMType(b.program.ImportedPackage("internal/task").Members["DeferFrame"].Type())
b.deferFrame = b.CreateAlloca(deferFrameType, "deferframe.buf")
stackPointer := b.readStackPointer()
b.createRuntimeCall("setupDeferFrame", []llvm.Value{b.deferFrame, stackPointer}, "")
// Create the landing pad block, which is where control transfers after
// a panic.
b.landingpad = b.ctx.AddBasicBlock(b.llvmFn, "lpad")
}
}
// createLandingPad fills in the landing pad block. This block runs the deferred
// functions and returns (by jumping to the recover block). If the function is
// still panicking after the defers are run, the panic will be re-raised in
// destroyDeferFrame.
func (b *builder) createLandingPad() {
b.SetInsertPointAtEnd(b.landingpad)
// Add debug info, if needed.
// The location used is the closing bracket of the function.
if b.Debug {
pos := b.program.Fset.Position(b.fn.Syntax().End())
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{})
}
b.createRunDefers()
// Continue at the 'recover' block, which returns to the parent in an
// appropriate way.
b.CreateBr(b.blockEntries[b.fn.Recover])
}
// createInvokeCheckpoint saves the function state at the given point, to
// continue at the landing pad if a panic happened. This is implemented using a
// setjmp-like construct.
func (b *builder) createInvokeCheckpoint() {
// Construct inline assembly equivalents of setjmp.
// The assembly works as follows:
// * All registers (both callee-saved and caller saved) are clobbered
// after the inline assembly returns.
// * The assembly stores the address just past the end of the assembly
// into the jump buffer.
// * The return value (eax, rax, r0, etc) is set to zero in the inline
// assembly but set to an unspecified non-zero value when jumping using
// a longjmp.
asmType := llvm.FunctionType(b.uintptrType, []llvm.Type{b.deferFrame.Type()}, false)
var asmString, constraints string
switch b.archFamily() {
case "i386":
asmString = `
xorl %eax, %eax
movl $$1f, 4(%ebx)
1:`
constraints = "={eax},{ebx},~{ebx},~{ecx},~{edx},~{esi},~{edi},~{ebp},~{xmm0},~{xmm1},~{xmm2},~{xmm3},~{xmm4},~{xmm5},~{xmm6},~{xmm7},~{fpsr},~{fpcr},~{flags},~{dirflag},~{memory}"
// This doesn't include the floating point stack because TinyGo uses
// newer floating point instructions.
case "x86_64":
asmString = `
leaq 1f(%rip), %rax
movq %rax, 8(%rbx)
xorq %rax, %rax
1:`
constraints = "={rax},{rbx},~{rbx},~{rcx},~{rdx},~{rsi},~{rdi},~{rbp},~{r8},~{r9},~{r10},~{r11},~{r12},~{r13},~{r14},~{r15},~{xmm0},~{xmm1},~{xmm2},~{xmm3},~{xmm4},~{xmm5},~{xmm6},~{xmm7},~{xmm8},~{xmm9},~{xmm10},~{xmm11},~{xmm12},~{xmm13},~{xmm14},~{xmm15},~{xmm16},~{xmm17},~{xmm18},~{xmm19},~{xmm20},~{xmm21},~{xmm22},~{xmm23},~{xmm24},~{xmm25},~{xmm26},~{xmm27},~{xmm28},~{xmm29},~{xmm30},~{xmm31},~{fpsr},~{fpcr},~{flags},~{dirflag},~{memory}"
// This list doesn't include AVX/AVX512 registers because TinyGo
// doesn't currently enable support for AVX instructions.
case "arm":
// Note: the following assembly takes into account that the PC is
// always 4 bytes ahead on ARM. The PC that is stored always points
// to the instruction just after the assembly fragment so that
// tinygo_longjmp lands at the correct instruction.
if b.isThumb() {
// Instructions are 2 bytes in size.
asmString = `
movs r0, #0
mov r2, pc
str r2, [r1, #4]`
} else {
// Instructions are 4 bytes in size.
asmString = `
str pc, [r1, #4]
movs r0, #0`
}
constraints = "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"
case "aarch64":
asmString = `
adr x2, 1f
str x2, [x1, #8]
mov x0, #0
1:
`
constraints = "={x0},{x1},~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7},~{x8},~{x9},~{x10},~{x11},~{x12},~{x13},~{x14},~{x15},~{x16},~{x17},~{x18},~{x19},~{x20},~{x21},~{x22},~{x23},~{x24},~{x25},~{x26},~{x27},~{x28},~{fp},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{q16},~{q17},~{q18},~{q19},~{q20},~{q21},~{q22},~{q23},~{q24},~{q25},~{q26},~{q27},~{q28},~{q29},~{q30},~{nzcv},~{ffr},~{vg},~{memory}"
// TODO: SVE registers, which we don't use in TinyGo at the moment.
case "riscv32":
asmString = `
la a2, 1f
sw a2, 4(a1)
li a0, 0
1:`
constraints = "={a0},{a1},~{a1},~{a2},~{a3},~{a4},~{a5},~{a6},~{a7},~{s0},~{s1},~{s2},~{s3},~{s4},~{s5},~{s6},~{s7},~{s8},~{s9},~{s10},~{s11},~{t0},~{t1},~{t2},~{t3},~{t4},~{t5},~{t6},~{ra},~{f0},~{f1},~{f2},~{f3},~{f4},~{f5},~{f6},~{f7},~{f8},~{f9},~{f10},~{f11},~{f12},~{f13},~{f14},~{f15},~{f16},~{f17},~{f18},~{f19},~{f20},~{f21},~{f22},~{f23},~{f24},~{f25},~{f26},~{f27},~{f28},~{f29},~{f30},~{f31},~{memory}"
default:
// This case should have been handled by b.supportsRecover().
b.addError(b.fn.Pos(), "unknown architecture for defer: "+b.archFamily())
}
asm := llvm.InlineAsm(asmType, asmString, constraints, false, false, 0, false)
result := b.CreateCall(asm, []llvm.Value{b.deferFrame}, "setjmp")
result.AddCallSiteAttribute(-1, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("returns_twice"), 0))
isZero := b.CreateICmp(llvm.IntEQ, result, llvm.ConstInt(b.uintptrType, 0, false), "setjmp.result")
continueBB := b.insertBasicBlock("")
b.CreateCondBr(isZero, continueBB, b.landingpad)
b.SetInsertPointAtEnd(continueBB)
b.blockExits[b.currentBlock] = continueBB
}
// isInLoop checks if there is a path from a basic block to itself.
@@ -202,30 +347,31 @@ func (b *builder) createDefer(instr *ssa.Defer) {
}
}
// Make a struct out of the collected values to put in the defer frame.
deferFrameType := b.ctx.StructType(valueTypes, false)
deferFrame := llvm.ConstNull(deferFrameType)
// Make a struct out of the collected values to put in the deferred call
// struct.
deferredCallType := b.ctx.StructType(valueTypes, false)
deferredCall := llvm.ConstNull(deferredCallType)
for i, value := range values {
deferFrame = b.CreateInsertValue(deferFrame, value, i, "")
deferredCall = b.CreateInsertValue(deferredCall, value, i, "")
}
// Put this struct in an allocation.
var alloca llvm.Value
if !isInLoop(instr.Block()) {
// This can safely use a stack allocation.
alloca = llvmutil.CreateEntryBlockAlloca(b.Builder, deferFrameType, "defer.alloca")
alloca = llvmutil.CreateEntryBlockAlloca(b.Builder, deferredCallType, "defer.alloca")
} else {
// This may be hit a variable number of times, so use a heap allocation.
size := b.targetData.TypeAllocSize(deferFrameType)
size := b.targetData.TypeAllocSize(deferredCallType)
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
nilPtr := llvm.ConstNull(b.i8ptrType)
allocCall := b.createRuntimeCall("alloc", []llvm.Value{sizeValue, nilPtr}, "defer.alloc.call")
alloca = b.CreateBitCast(allocCall, llvm.PointerType(deferFrameType, 0), "defer.alloc")
alloca = b.CreateBitCast(allocCall, llvm.PointerType(deferredCallType, 0), "defer.alloc")
}
if b.NeedsStackObjects {
b.trackPointer(alloca)
}
b.CreateStore(deferFrame, alloca)
b.CreateStore(deferredCall, alloca)
// Push it on top of the linked list by replacing deferPtr.
allocaCast := b.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
@@ -296,7 +442,7 @@ func (b *builder) createRunDefers() {
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
if !callback.IsInvoke() {
//Expect funcValue to be passed through the defer frame.
//Expect funcValue to be passed through the deferred call.
valueTypes = append(valueTypes, b.getFuncType(callback.Signature()))
} else {
//Expect typecode
@@ -307,14 +453,14 @@ func (b *builder) createRunDefers() {
valueTypes = append(valueTypes, b.getLLVMType(arg.Type()))
}
deferFrameType := b.ctx.StructType(valueTypes, false)
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
deferredCallType := b.ctx.StructType(valueTypes, false)
deferredCallPtr := b.CreateBitCast(deferData, llvm.PointerType(deferredCallType, 0), "defercall")
// Extract the params from the struct (including receiver).
forwardParams := []llvm.Value{}
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := 2; i < len(valueTypes); i++ {
gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "gep")
gep := b.CreateInBoundsGEP(deferredCallPtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "gep")
forwardParam := b.CreateLoad(gep, "param")
forwardParams = append(forwardParams, forwardParam)
}
@@ -354,14 +500,14 @@ func (b *builder) createRunDefers() {
for _, param := range getParams(callback.Signature) {
valueTypes = append(valueTypes, b.getLLVMType(param.Type()))
}
deferFrameType := b.ctx.StructType(valueTypes, false)
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
deferredCallType := b.ctx.StructType(valueTypes, false)
deferredCallPtr := b.CreateBitCast(deferData, llvm.PointerType(deferredCallType, 0), "defercall")
// Extract the params from the struct.
forwardParams := []llvm.Value{}
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := range getParams(callback.Signature) {
gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
gep := b.CreateInBoundsGEP(deferredCallPtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
forwardParam := b.CreateLoad(gep, "param")
forwardParams = append(forwardParams, forwardParam)
}
@@ -375,7 +521,7 @@ func (b *builder) createRunDefers() {
}
// Call real function.
b.createCall(b.getFunction(callback), forwardParams, "")
b.createInvoke(b.getFunction(callback), forwardParams, "")
case *ssa.MakeClosure:
// Get the real defer struct type and cast to it.
@@ -386,14 +532,14 @@ func (b *builder) createRunDefers() {
valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type()))
}
valueTypes = append(valueTypes, b.i8ptrType) // closure
deferFrameType := b.ctx.StructType(valueTypes, false)
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
deferredCallType := b.ctx.StructType(valueTypes, false)
deferredCallPtr := b.CreateBitCast(deferData, llvm.PointerType(deferredCallType, 0), "defercall")
// Extract the params from the struct.
forwardParams := []llvm.Value{}
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := 2; i < len(valueTypes); i++ {
gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "")
gep := b.CreateInBoundsGEP(deferredCallPtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "")
forwardParam := b.CreateLoad(gep, "param")
forwardParams = append(forwardParams, forwardParam)
}
@@ -412,14 +558,14 @@ func (b *builder) createRunDefers() {
valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type()))
}
deferFrameType := b.ctx.StructType(valueTypes, false)
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
deferredCallType := b.ctx.StructType(valueTypes, false)
deferredCallPtr := b.CreateBitCast(deferData, llvm.PointerType(deferredCallType, 0), "defercall")
// Extract the params from the struct.
var argValues []llvm.Value
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := 0; i < params.Len(); i++ {
gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
gep := b.CreateInBoundsGEP(deferredCallPtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
forwardParam := b.CreateLoad(gep, "param")
argValues = append(argValues, forwardParam)
}