
This is a big change: apart from removing LLVM 14 it also removes typed pointer support (which was only fully supported in LLVM up to version 14). This removes about 200 lines of code, but more importantly removes a ton of special cases for LLVM 14.
619 строки
24 КиБ
Go
619 строки
24 КиБ
Go
package compiler
|
|
|
|
// This file implements the 'defer' keyword in Go.
|
|
// Defer statements are implemented by transforming the function in the
|
|
// following way:
|
|
// * Creating an alloca in the entry block that contains a pointer (initially
|
|
// null) to the linked list of defer frames.
|
|
// * Every time a defer statement is executed, a new defer frame is created
|
|
// using alloca with a pointer to the previous defer frame, and the head
|
|
// pointer in the entry block is replaced with a pointer to this defer
|
|
// frame.
|
|
// * On return, runtime.rundefers is called which calls all deferred functions
|
|
// from the head of the linked list until it has gone through all defer
|
|
// frames.
|
|
|
|
import (
|
|
"go/types"
|
|
"strconv"
|
|
|
|
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
|
"golang.org/x/tools/go/ssa"
|
|
"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 "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.
|
|
func (b *builder) deferInitFunc() {
|
|
// Some setup.
|
|
b.deferFuncs = make(map[*ssa.Function]int)
|
|
b.deferInvokeFuncs = make(map[string]int)
|
|
b.deferClosureFuncs = make(map[*ssa.Function]int)
|
|
b.deferExprFuncs = make(map[ssa.Value]int)
|
|
b.deferBuiltinFuncs = make(map[ssa.Value]deferBuiltin)
|
|
|
|
// Create defer list pointer.
|
|
b.deferPtr = b.CreateAlloca(b.dataPtrType, "deferPtr")
|
|
b.CreateStore(llvm.ConstPointerNull(b.dataPtrType), 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.getLLVMRuntimeType("deferFrame")
|
|
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.
|
|
var asmString, constraints string
|
|
resultType := b.uintptrType
|
|
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},~{x19},~{x20},~{x21},~{x22},~{x23},~{x24},~{x25},~{x26},~{x27},~{x28},~{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}"
|
|
if b.GOOS != "darwin" && b.GOOS != "windows" {
|
|
// These registers cause the following warning when compiling for
|
|
// MacOS and Windows:
|
|
// warning: inline asm clobber list contains reserved registers:
|
|
// X18, FP
|
|
// Reserved registers on the clobber list may not be preserved
|
|
// across the asm statement, and clobbering them may lead to
|
|
// undefined behaviour.
|
|
constraints += ",~{x18},~{fp}"
|
|
}
|
|
// TODO: SVE registers, which we don't use in TinyGo at the moment.
|
|
case "avr":
|
|
// Note: the Y register (R28:R29) is a fixed register and therefore
|
|
// needs to be saved manually. TODO: do this only once per function with
|
|
// a defer frame, not for every call.
|
|
resultType = b.ctx.Int8Type()
|
|
asmString = `
|
|
ldi r24, pm_lo8(1f)
|
|
ldi r25, pm_hi8(1f)
|
|
std z+2, r24
|
|
std z+3, r25
|
|
std z+4, r28
|
|
std z+5, r29
|
|
ldi r24, 0
|
|
1:`
|
|
constraints = "={r24},z,~{r0},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{r13},~{r14},~{r15},~{r16},~{r17},~{r18},~{r19},~{r20},~{r21},~{r22},~{r23},~{r25},~{r26},~{r27}"
|
|
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())
|
|
}
|
|
asmType := llvm.FunctionType(resultType, []llvm.Type{b.deferFrame.Type()}, false)
|
|
asm := llvm.InlineAsm(asmType, asmString, constraints, false, false, 0, false)
|
|
result := b.CreateCall(asmType, 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(resultType, 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.
|
|
func isInLoop(start *ssa.BasicBlock) bool {
|
|
// Use a breadth-first search to scan backwards through the block graph.
|
|
queue := []*ssa.BasicBlock{start}
|
|
checked := map[*ssa.BasicBlock]struct{}{}
|
|
|
|
for len(queue) > 0 {
|
|
// pop a block off of the queue
|
|
block := queue[len(queue)-1]
|
|
queue = queue[:len(queue)-1]
|
|
|
|
// Search through predecessors.
|
|
// Searching backwards means that this is pretty fast when the block is close to the start of the function.
|
|
// Defers are often placed near the start of the function.
|
|
for _, pred := range block.Preds {
|
|
if pred == start {
|
|
// cycle found
|
|
return true
|
|
}
|
|
|
|
if _, ok := checked[pred]; ok {
|
|
// block already checked
|
|
continue
|
|
}
|
|
|
|
// add to queue and checked map
|
|
queue = append(queue, pred)
|
|
checked[pred] = struct{}{}
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// createDefer emits a single defer instruction, to be run when this function
|
|
// returns.
|
|
func (b *builder) createDefer(instr *ssa.Defer) {
|
|
// The pointer to the previous defer struct, which we will replace to
|
|
// make a linked list.
|
|
next := b.CreateLoad(b.dataPtrType, b.deferPtr, "defer.next")
|
|
|
|
var values []llvm.Value
|
|
valueTypes := []llvm.Type{b.uintptrType, next.Type()}
|
|
if instr.Call.IsInvoke() {
|
|
// Method call on an interface.
|
|
|
|
// Get callback type number.
|
|
methodName := instr.Call.Method.FullName()
|
|
if _, ok := b.deferInvokeFuncs[methodName]; !ok {
|
|
b.deferInvokeFuncs[methodName] = len(b.allDeferFuncs)
|
|
b.allDeferFuncs = append(b.allDeferFuncs, &instr.Call)
|
|
}
|
|
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferInvokeFuncs[methodName]), false)
|
|
|
|
// Collect all values to be put in the struct (starting with
|
|
// runtime._defer fields, followed by the call parameters).
|
|
itf := b.getValue(instr.Call.Value, getPos(instr)) // interface
|
|
typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode")
|
|
receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver")
|
|
values = []llvm.Value{callback, next, typecode, receiverValue}
|
|
valueTypes = append(valueTypes, b.dataPtrType, b.dataPtrType)
|
|
for _, arg := range instr.Call.Args {
|
|
val := b.getValue(arg, getPos(instr))
|
|
values = append(values, val)
|
|
valueTypes = append(valueTypes, val.Type())
|
|
}
|
|
|
|
} else if callee, ok := instr.Call.Value.(*ssa.Function); ok {
|
|
// Regular function call.
|
|
if _, ok := b.deferFuncs[callee]; !ok {
|
|
b.deferFuncs[callee] = len(b.allDeferFuncs)
|
|
b.allDeferFuncs = append(b.allDeferFuncs, callee)
|
|
}
|
|
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferFuncs[callee]), false)
|
|
|
|
// Collect all values to be put in the struct (starting with
|
|
// runtime._defer fields).
|
|
values = []llvm.Value{callback, next}
|
|
for _, param := range instr.Call.Args {
|
|
llvmParam := b.getValue(param, getPos(instr))
|
|
values = append(values, llvmParam)
|
|
valueTypes = append(valueTypes, llvmParam.Type())
|
|
}
|
|
|
|
} else if makeClosure, ok := instr.Call.Value.(*ssa.MakeClosure); ok {
|
|
// Immediately applied function literal with free variables.
|
|
|
|
// Extract the context from the closure. We won't need the function
|
|
// pointer.
|
|
// TODO: ignore this closure entirely and put pointers to the free
|
|
// variables directly in the defer struct, avoiding a memory allocation.
|
|
closure := b.getValue(instr.Call.Value, getPos(instr))
|
|
context := b.CreateExtractValue(closure, 0, "")
|
|
|
|
// Get the callback number.
|
|
fn := makeClosure.Fn.(*ssa.Function)
|
|
if _, ok := b.deferClosureFuncs[fn]; !ok {
|
|
b.deferClosureFuncs[fn] = len(b.allDeferFuncs)
|
|
b.allDeferFuncs = append(b.allDeferFuncs, makeClosure)
|
|
}
|
|
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferClosureFuncs[fn]), false)
|
|
|
|
// Collect all values to be put in the struct (starting with
|
|
// runtime._defer fields, followed by all parameters including the
|
|
// context pointer).
|
|
values = []llvm.Value{callback, next}
|
|
for _, param := range instr.Call.Args {
|
|
llvmParam := b.getValue(param, getPos(instr))
|
|
values = append(values, llvmParam)
|
|
valueTypes = append(valueTypes, llvmParam.Type())
|
|
}
|
|
values = append(values, context)
|
|
valueTypes = append(valueTypes, context.Type())
|
|
|
|
} else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok {
|
|
var argTypes []types.Type
|
|
var argValues []llvm.Value
|
|
for _, arg := range instr.Call.Args {
|
|
argTypes = append(argTypes, arg.Type())
|
|
argValues = append(argValues, b.getValue(arg, getPos(instr)))
|
|
}
|
|
|
|
if _, ok := b.deferBuiltinFuncs[instr.Call.Value]; !ok {
|
|
b.deferBuiltinFuncs[instr.Call.Value] = deferBuiltin{
|
|
callName: builtin.Name(),
|
|
pos: builtin.Pos(),
|
|
argTypes: argTypes,
|
|
callback: len(b.allDeferFuncs),
|
|
}
|
|
b.allDeferFuncs = append(b.allDeferFuncs, instr.Call.Value)
|
|
}
|
|
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferBuiltinFuncs[instr.Call.Value].callback), false)
|
|
|
|
// Collect all values to be put in the struct (starting with
|
|
// runtime._defer fields).
|
|
values = []llvm.Value{callback, next}
|
|
for _, param := range argValues {
|
|
values = append(values, param)
|
|
valueTypes = append(valueTypes, param.Type())
|
|
}
|
|
|
|
} else {
|
|
funcValue := b.getValue(instr.Call.Value, getPos(instr))
|
|
|
|
if _, ok := b.deferExprFuncs[instr.Call.Value]; !ok {
|
|
b.deferExprFuncs[instr.Call.Value] = len(b.allDeferFuncs)
|
|
b.allDeferFuncs = append(b.allDeferFuncs, &instr.Call)
|
|
}
|
|
|
|
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferExprFuncs[instr.Call.Value]), false)
|
|
|
|
// Collect all values to be put in the struct (starting with
|
|
// runtime._defer fields, followed by all parameters including the
|
|
// context pointer).
|
|
values = []llvm.Value{callback, next, funcValue}
|
|
valueTypes = append(valueTypes, funcValue.Type())
|
|
for _, param := range instr.Call.Args {
|
|
llvmParam := b.getValue(param, getPos(instr))
|
|
values = append(values, llvmParam)
|
|
valueTypes = append(valueTypes, llvmParam.Type())
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
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, deferredCallType, "defer.alloca")
|
|
} else {
|
|
// This may be hit a variable number of times, so use a heap allocation.
|
|
size := b.targetData.TypeAllocSize(deferredCallType)
|
|
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
|
|
nilPtr := llvm.ConstNull(b.dataPtrType)
|
|
alloca = b.createRuntimeCall("alloc", []llvm.Value{sizeValue, nilPtr}, "defer.alloc.call")
|
|
}
|
|
if b.NeedsStackObjects {
|
|
b.trackPointer(alloca)
|
|
}
|
|
b.CreateStore(deferredCall, alloca)
|
|
|
|
// Push it on top of the linked list by replacing deferPtr.
|
|
b.CreateStore(alloca, b.deferPtr)
|
|
}
|
|
|
|
// createRunDefers emits code to run all deferred functions.
|
|
func (b *builder) createRunDefers() {
|
|
deferType := b.getLLVMRuntimeType("_defer")
|
|
|
|
// Add a loop like the following:
|
|
// for stack != nil {
|
|
// _stack := stack
|
|
// stack = stack.next
|
|
// switch _stack.callback {
|
|
// case 0:
|
|
// // run first deferred call
|
|
// case 1:
|
|
// // run second deferred call
|
|
// // etc.
|
|
// default:
|
|
// unreachable
|
|
// }
|
|
// }
|
|
|
|
// 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:
|
|
// for stack != nil {
|
|
b.SetInsertPointAtEnd(loophead)
|
|
deferData := b.CreateLoad(b.dataPtrType, b.deferPtr, "")
|
|
stackIsNil := b.CreateICmp(llvm.IntEQ, deferData, llvm.ConstPointerNull(deferData.Type()), "stackIsNil")
|
|
b.CreateCondBr(stackIsNil, end, loop)
|
|
|
|
// Create loop body:
|
|
// _stack := stack
|
|
// stack = stack.next
|
|
// switch stack.callback {
|
|
b.SetInsertPointAtEnd(loop)
|
|
nextStackGEP := b.CreateInBoundsGEP(deferType, deferData, []llvm.Value{
|
|
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
|
llvm.ConstInt(b.ctx.Int32Type(), 1, false), // .next field
|
|
}, "stack.next.gep")
|
|
nextStack := b.CreateLoad(b.dataPtrType, nextStackGEP, "stack.next")
|
|
b.CreateStore(nextStack, b.deferPtr)
|
|
gep := b.CreateInBoundsGEP(deferType, deferData, []llvm.Value{
|
|
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
|
llvm.ConstInt(b.ctx.Int32Type(), 0, false), // .callback field
|
|
}, "callback.gep")
|
|
callback := b.CreateLoad(b.uintptrType, gep, "callback")
|
|
sw := b.CreateSwitch(callback, unreachable, len(b.allDeferFuncs))
|
|
|
|
for i, callback := range b.allDeferFuncs {
|
|
// Create switch case, for example:
|
|
// case 0:
|
|
// // run first deferred call
|
|
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) {
|
|
case *ssa.CallCommon:
|
|
// Call on an value or interface value.
|
|
|
|
// Get the real defer struct type and cast to it.
|
|
valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType}
|
|
|
|
if !callback.IsInvoke() {
|
|
//Expect funcValue to be passed through the deferred call.
|
|
valueTypes = append(valueTypes, b.getFuncType(callback.Signature()))
|
|
} else {
|
|
//Expect typecode
|
|
valueTypes = append(valueTypes, b.dataPtrType, b.dataPtrType)
|
|
}
|
|
|
|
for _, arg := range callback.Args {
|
|
valueTypes = append(valueTypes, b.getLLVMType(arg.Type()))
|
|
}
|
|
|
|
// Extract the params from the struct (including receiver).
|
|
forwardParams := []llvm.Value{}
|
|
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
|
|
deferredCallType := b.ctx.StructType(valueTypes, false)
|
|
for i := 2; i < len(valueTypes); i++ {
|
|
gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "gep")
|
|
forwardParam := b.CreateLoad(valueTypes[i], gep, "param")
|
|
forwardParams = append(forwardParams, forwardParam)
|
|
}
|
|
|
|
var fnPtr llvm.Value
|
|
var fnType llvm.Type
|
|
|
|
if !callback.IsInvoke() {
|
|
// Isolate the func value.
|
|
funcValue := forwardParams[0]
|
|
forwardParams = forwardParams[1:]
|
|
|
|
//Get function pointer and context
|
|
var context llvm.Value
|
|
fnPtr, context = b.decodeFuncValue(funcValue)
|
|
fnType = b.getLLVMFunctionType(callback.Signature())
|
|
|
|
//Pass context
|
|
forwardParams = append(forwardParams, context)
|
|
} else {
|
|
// Move typecode from the start to the end of the list of
|
|
// parameters.
|
|
forwardParams = append(forwardParams[1:], forwardParams[0])
|
|
fnPtr = b.getInvokeFunction(callback)
|
|
fnType = fnPtr.GlobalValueType()
|
|
|
|
// Add the context parameter. An interface call cannot also be a
|
|
// closure but we have to supply the parameter anyway for platforms
|
|
// with a strict calling convention.
|
|
forwardParams = append(forwardParams, llvm.Undef(b.dataPtrType))
|
|
}
|
|
|
|
b.createCall(fnType, fnPtr, forwardParams, "")
|
|
|
|
case *ssa.Function:
|
|
// Direct call.
|
|
|
|
// Get the real defer struct type and cast to it.
|
|
valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType}
|
|
for _, param := range getParams(callback.Signature) {
|
|
valueTypes = append(valueTypes, b.getLLVMType(param.Type()))
|
|
}
|
|
deferredCallType := b.ctx.StructType(valueTypes, false)
|
|
|
|
// 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(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
|
|
forwardParam := b.CreateLoad(valueTypes[i+2], gep, "param")
|
|
forwardParams = append(forwardParams, forwardParam)
|
|
}
|
|
|
|
// Plain TinyGo functions add some extra parameters to implement async functionality and function receivers.
|
|
// These parameters should not be supplied when calling into an external C/ASM function.
|
|
if !b.getFunctionInfo(callback).exported {
|
|
// Add the context parameter. We know it is ignored by the receiving
|
|
// function, but we have to pass one anyway.
|
|
forwardParams = append(forwardParams, llvm.Undef(b.dataPtrType))
|
|
}
|
|
|
|
// Call real function.
|
|
fnType, fn := b.getFunction(callback)
|
|
b.createInvoke(fnType, fn, forwardParams, "")
|
|
|
|
case *ssa.MakeClosure:
|
|
// Get the real defer struct type and cast to it.
|
|
fn := callback.Fn.(*ssa.Function)
|
|
valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType}
|
|
params := fn.Signature.Params()
|
|
for i := 0; i < params.Len(); i++ {
|
|
valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type()))
|
|
}
|
|
valueTypes = append(valueTypes, b.dataPtrType) // closure
|
|
deferredCallType := b.ctx.StructType(valueTypes, false)
|
|
|
|
// 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(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "")
|
|
forwardParam := b.CreateLoad(valueTypes[i], gep, "param")
|
|
forwardParams = append(forwardParams, forwardParam)
|
|
}
|
|
|
|
// Call deferred function.
|
|
fnType, llvmFn := b.getFunction(fn)
|
|
b.createCall(fnType, llvmFn, forwardParams, "")
|
|
case *ssa.Builtin:
|
|
db := b.deferBuiltinFuncs[callback]
|
|
|
|
//Get parameter types
|
|
valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType}
|
|
|
|
//Get signature from call results
|
|
params := callback.Type().Underlying().(*types.Signature).Params()
|
|
for i := 0; i < params.Len(); i++ {
|
|
valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type()))
|
|
}
|
|
|
|
deferredCallType := b.ctx.StructType(valueTypes, false)
|
|
|
|
// 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(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
|
|
forwardParam := b.CreateLoad(valueTypes[i+2], gep, "param")
|
|
argValues = append(argValues, forwardParam)
|
|
}
|
|
|
|
_, err := b.createBuiltin(db.argTypes, argValues, db.callName, db.pos)
|
|
if err != nil {
|
|
b.diagnostics = append(b.diagnostics, err)
|
|
}
|
|
default:
|
|
panic("unknown deferred function type")
|
|
}
|
|
|
|
// Branch back to the start of the loop.
|
|
b.CreateBr(loophead)
|
|
}
|
|
|
|
// Create default unreachable block:
|
|
// default:
|
|
// unreachable
|
|
// }
|
|
b.SetInsertPointAtEnd(unreachable)
|
|
b.CreateUnreachable()
|
|
|
|
// End of loop.
|
|
b.SetInsertPointAtEnd(end)
|
|
}
|