Skip to content

Commit

Permalink
compiler: implement func value and builtin defers
Browse files Browse the repository at this point in the history
Co-authored-by: Justin A. Wilson <[email protected]>
  • Loading branch information
waj334 and justin12343 authored Jul 30, 2020
1 parent 3650c2c commit 848c3e5
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 13 deletions.
7 changes: 7 additions & 0 deletions compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,14 @@ type builder struct {
deferFuncs map[*ir.Function]int
deferInvokeFuncs map[string]int
deferClosureFuncs map[*ir.Function]int
deferExprFuncs map[ssa.Value]int
selectRecvBuf map[*ssa.Select]llvm.Value
deferBuiltinFuncs map[ssa.Value]deferBuiltin
}

type deferBuiltin struct {
funcName string
callback int
}

type phiNode struct {
Expand Down
121 changes: 108 additions & 13 deletions compiler/defer.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package compiler
import (
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"github.com/tinygo-org/tinygo/ir"
"go/types"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
Expand All @@ -28,6 +29,8 @@ func (b *builder) deferInitFunc() {
b.deferFuncs = make(map[*ir.Function]int)
b.deferInvokeFuncs = make(map[string]int)
b.deferClosureFuncs = make(map[*ir.Function]int)
b.deferExprFuncs = make(map[ssa.Value]int)
b.deferBuiltinFuncs = make(map[ssa.Value]deferBuiltin)

// Create defer list pointer.
deferType := llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)
Expand Down Expand Up @@ -151,9 +154,54 @@ func (b *builder) createDefer(instr *ssa.Defer) {
values = append(values, context)
valueTypes = append(valueTypes, context.Type())

} else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok {
var funcName string
switch builtin.Name() {
case "close":
funcName = "chanClose"
default:
b.addError(instr.Pos(), "todo: Implement defer for "+builtin.Name())
return
}

if _, ok := b.deferBuiltinFuncs[instr.Call.Value]; !ok {
b.deferBuiltinFuncs[instr.Call.Value] = deferBuiltin{
funcName,
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 instr.Call.Args {
llvmParam := b.getValue(param)
values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type())
}

} else {
b.addError(instr.Pos(), "todo: defer on uncommon function call type")
return
funcValue := b.getValue(instr.Call.Value)

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)
values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type())
}
}

// Make a struct out of the collected values to put in the defer frame.
Expand Down Expand Up @@ -243,16 +291,23 @@ func (b *builder) createRunDefers() {
b.SetInsertPointAtEnd(block)
switch callback := callback.(type) {
case *ssa.CallCommon:
// Call on an interface value.
// Call on an value or interface value.

// Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}

if !callback.IsInvoke() {
panic("expected an invoke call, not a direct call")
//Expect funcValue to be passed through the defer frame.
valueTypes = append(valueTypes, b.getFuncType(callback.Signature()))
} else {
//Expect typecode
valueTypes = append(valueTypes, b.uintptrType, b.i8ptrType)
}

// Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0), b.uintptrType, b.i8ptrType}
for _, arg := range callback.Args {
valueTypes = append(valueTypes, b.getLLVMType(arg.Type()))
}

deferFrameType := b.ctx.StructType(valueTypes, false)
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")

Expand All @@ -265,18 +320,34 @@ func (b *builder) createRunDefers() {
forwardParams = append(forwardParams, forwardParam)
}

// Isolate the typecode.
typecode, forwardParams := forwardParams[0], forwardParams[1:]
var fnPtr llvm.Value

// 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.i8ptrType))
if !callback.IsInvoke() {
// Isolate the func value.
funcValue := forwardParams[0]
forwardParams = forwardParams[1:]

//Get function pointer and context
fp, context := b.decodeFuncValue(funcValue, callback.Signature())
fnPtr = fp

//Pass context
forwardParams = append(forwardParams, context)
} else {
// Isolate the typecode.
typecode := forwardParams[0]
forwardParams = forwardParams[1:]
fnPtr = b.getInvokePtr(callback, typecode)

// 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.i8ptrType))
}

// Parent coroutine handle.
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))

fnPtr := b.getInvokePtr(callback, typecode)
b.createCall(fnPtr, forwardParams, "")

case *ir.Function:
Expand Down Expand Up @@ -339,7 +410,31 @@ func (b *builder) createRunDefers() {

// Call deferred function.
b.createCall(fn.LLVMFn, forwardParams, "")
case *ssa.Builtin:
db := b.deferBuiltinFuncs[callback]

//Get parameter types
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}

//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()))
}

deferFrameType := b.ctx.StructType(valueTypes, false)
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")

// Extract the params from the struct.
var forwardParams []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")
forwardParam := b.CreateLoad(gep, "param")
forwardParams = append(forwardParams, forwardParam)
}

b.createRuntimeCall(db.funcName, forwardParams, "")
default:
panic("unknown deferred function type")
}
Expand Down
49 changes: 49 additions & 0 deletions testdata/calls.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ func main() {
// defers in loop
testDeferLoop()

//defer func variable call
testDeferFuncVar()

//More complicated func variable call
testMultiFuncVar()

// Take a bound method and use it as a function pointer.
// This function pointer needs a context pointer.
testBound(thing.String)
Expand All @@ -64,6 +70,9 @@ func main() {

// regression testing
regression1033()

//Test deferred builtins
testDeferBuiltin()
}

func runFunc(f func(int), arg int) {
Expand Down Expand Up @@ -91,6 +100,8 @@ func testDefer() {
defer t.Print("bar")

println("deferring...")
d := dumb{}
defer d.Value(0)
}

func testDeferLoop() {
Expand All @@ -99,6 +110,30 @@ func testDeferLoop() {
}
}

func testDeferFuncVar() {
dummy, f := deferFunc()
dummy++
defer f(1)
}

func testMultiFuncVar() {
f := multiFuncDefer()
defer f(1)
}

func testDeferBuiltin() {
i := make(chan int)
defer close(i)
}

type dumb struct {

}

func (*dumb) Value(key interface{}) interface{} {
return nil
}

func deferred(msg string, i int) {
println(msg, i)
}
Expand All @@ -108,6 +143,20 @@ func exportedDefer() {
println("...exported defer")
}

func deferFunc() (int, func(int)) {
return 0, func(i int){println("...extracted defer func ", i)}
}

func multiFuncDefer() func(int) {
i := 0

if i > 0 {
return func(i int){println("Should not have gotten here. i = ", i)}
}

return func(i int){println("Called the correct function. i = ", i)}
}

func testBound(f func() string) {
println("bound method:", f())
}
Expand Down
2 changes: 2 additions & 0 deletions testdata/calls.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ loop 3
loop 2
loop 1
loop 0
...extracted defer func 1
Called the correct function. i = 1
bound method: foo
thing inside closure: foo
inside fp closure: foo 3
Expand Down

0 comments on commit 848c3e5

Please sign in to comment.