From 9bd36597d67f76230033adab4c3481536a755f71 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 21 Jan 2021 17:10:19 +0100 Subject: [PATCH] compiler: support all kinds of deferred builtins This change extends defer support to all supported builitin functions. Not all of them make sense (such as len, cap, real, imag, etc) but this change for example adds support for `defer(delete(m, key))` which is used in the Go 1.15 encoding/json package. --- compiler/channel.go | 3 +-- compiler/compiler.go | 55 +++++++++++++++++++++++++------------------- compiler/defer.go | 34 ++++++++++++++------------- testdata/calls.go | 32 ++++++++++++++++++++------ 4 files changed, 75 insertions(+), 49 deletions(-) diff --git a/compiler/channel.go b/compiler/channel.go index 6e2d4319..a036fbf4 100644 --- a/compiler/channel.go +++ b/compiler/channel.go @@ -79,8 +79,7 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value { } // createChanClose closes the given channel. -func (b *builder) createChanClose(param ssa.Value) { - ch := b.getValue(param) +func (b *builder) createChanClose(ch llvm.Value) { b.createRuntimeCall("chanClose", []llvm.Value{ch}, "") } diff --git a/compiler/compiler.go b/compiler/compiler.go index d9a5ed4e..d31d2077 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -130,7 +130,9 @@ func newBuilder(c *compilerContext, irbuilder llvm.Builder, f *ir.Function) *bui } type deferBuiltin struct { - funcName string + callName string + pos token.Pos + argTypes []types.Type callback int } @@ -1196,11 +1198,11 @@ func (b *builder) createInstruction(instr ssa.Instruction) { // createBuiltin lowers a builtin Go function (append, close, delete, etc.) to // LLVM IR. It uses runtime calls for some builtins. -func (b *builder) createBuiltin(args []ssa.Value, callName string, pos token.Pos) (llvm.Value, error) { +func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, callName string, pos token.Pos) (llvm.Value, error) { switch callName { case "append": - src := b.getValue(args[0]) - elems := b.getValue(args[1]) + src := argValues[0] + elems := argValues[1] srcBuf := b.CreateExtractValue(src, 0, "append.srcBuf") srcPtr := b.CreateBitCast(srcBuf, b.i8ptrType, "append.srcPtr") srcLen := b.CreateExtractValue(src, 1, "append.srcLen") @@ -1221,9 +1223,9 @@ func (b *builder) createBuiltin(args []ssa.Value, callName string, pos token.Pos newSlice = b.CreateInsertValue(newSlice, newCap, 2, "") return newSlice, nil case "cap": - value := b.getValue(args[0]) + value := argValues[0] var llvmCap llvm.Value - switch args[0].Type().(type) { + switch argTypes[0].(type) { case *types.Chan: llvmCap = b.createRuntimeCall("chanCap", []llvm.Value{value}, "cap") case *types.Slice: @@ -1236,12 +1238,12 @@ func (b *builder) createBuiltin(args []ssa.Value, callName string, pos token.Pos } return llvmCap, nil case "close": - b.createChanClose(args[0]) + b.createChanClose(argValues[0]) return llvm.Value{}, nil case "complex": - r := b.getValue(args[0]) - i := b.getValue(args[1]) - t := args[0].Type().Underlying().(*types.Basic) + r := argValues[0] + i := argValues[1] + t := argTypes[0].Underlying().(*types.Basic) var cplx llvm.Value switch t.Kind() { case types.Float32: @@ -1255,8 +1257,8 @@ func (b *builder) createBuiltin(args []ssa.Value, callName string, pos token.Pos cplx = b.CreateInsertValue(cplx, i, 1, "") return cplx, nil case "copy": - dst := b.getValue(args[0]) - src := b.getValue(args[1]) + dst := argValues[0] + src := argValues[1] dstLen := b.CreateExtractValue(dst, 1, "copy.dstLen") srcLen := b.CreateExtractValue(src, 1, "copy.srcLen") dstBuf := b.CreateExtractValue(dst, 0, "copy.dstArray") @@ -1267,16 +1269,16 @@ func (b *builder) createBuiltin(args []ssa.Value, callName string, pos token.Pos elemSize := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(elemType), false) return b.createRuntimeCall("sliceCopy", []llvm.Value{dstBuf, srcBuf, dstLen, srcLen, elemSize}, "copy.n"), nil case "delete": - m := b.getValue(args[0]) - key := b.getValue(args[1]) - return llvm.Value{}, b.createMapDelete(args[1].Type(), m, key, pos) + m := argValues[0] + key := argValues[1] + return llvm.Value{}, b.createMapDelete(argTypes[1], m, key, pos) case "imag": - cplx := b.getValue(args[0]) + cplx := argValues[0] return b.CreateExtractValue(cplx, 1, "imag"), nil case "len": - value := b.getValue(args[0]) + value := argValues[0] var llvmLen llvm.Value - switch args[0].Type().Underlying().(type) { + switch argTypes[0].Underlying().(type) { case *types.Basic, *types.Slice: // string or slice llvmLen = b.CreateExtractValue(value, 1, "len") @@ -1292,12 +1294,11 @@ func (b *builder) createBuiltin(args []ssa.Value, callName string, pos token.Pos } return llvmLen, nil case "print", "println": - for i, arg := range args { + for i, value := range argValues { if i >= 1 && callName == "println" { b.createRuntimeCall("printspace", nil, "") } - value := b.getValue(arg) - typ := arg.Type().Underlying() + typ := argTypes[i].Underlying() switch typ := typ.(type) { case *types.Basic: switch typ.Kind() { @@ -1349,13 +1350,13 @@ func (b *builder) createBuiltin(args []ssa.Value, callName string, pos token.Pos } return llvm.Value{}, nil // print() or println() returns void case "real": - cplx := b.getValue(args[0]) + cplx := argValues[0] return b.CreateExtractValue(cplx, 0, "real"), nil case "recover": return b.createRuntimeCall("_recover", nil, ""), nil case "ssa:wrapnilchk": // TODO: do an actual nil check? - return b.getValue(args[0]), nil + return argValues[0], nil default: return llvm.Value{}, b.makeError(pos, "todo: builtin: "+callName) } @@ -1432,7 +1433,13 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) exported = targetFunc.IsExported() } else if call, ok := instr.Value.(*ssa.Builtin); ok { // Builtin function (append, close, delete, etc.).) - return b.createBuiltin(instr.Args, call.Name(), instr.Pos()) + var argTypes []types.Type + var argValues []llvm.Value + for _, arg := range instr.Args { + argTypes = append(argTypes, arg.Type()) + argValues = append(argValues, b.getValue(arg)) + } + return b.createBuiltin(argTypes, argValues, call.Name(), instr.Pos()) } else { // Function pointer. value := b.getValue(instr.Value) diff --git a/compiler/defer.go b/compiler/defer.go index db2bed32..eb35ab23 100644 --- a/compiler/defer.go +++ b/compiler/defer.go @@ -155,19 +155,19 @@ func (b *builder) createDefer(instr *ssa.Defer) { 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 + 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)) } if _, ok := b.deferBuiltinFuncs[instr.Call.Value]; !ok { b.deferBuiltinFuncs[instr.Call.Value] = deferBuiltin{ - funcName, - len(b.allDeferFuncs), + callName: builtin.Name(), + pos: builtin.Pos(), + argTypes: argTypes, + callback: len(b.allDeferFuncs), } b.allDeferFuncs = append(b.allDeferFuncs, instr.Call.Value) } @@ -176,10 +176,9 @@ func (b *builder) createDefer(instr *ssa.Defer) { // 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()) + for _, param := range argValues { + values = append(values, param) + valueTypes = append(valueTypes, param.Type()) } } else { @@ -426,15 +425,18 @@ func (b *builder) createRunDefers() { deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame") // Extract the params from the struct. - var forwardParams []llvm.Value + 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") forwardParam := b.CreateLoad(gep, "param") - forwardParams = append(forwardParams, forwardParam) + argValues = append(argValues, forwardParam) } - b.createRuntimeCall(db.funcName, forwardParams, "") + _, 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") } diff --git a/testdata/calls.go b/testdata/calls.go index 9f6ba69f..d1c80dd0 100644 --- a/testdata/calls.go +++ b/testdata/calls.go @@ -72,7 +72,8 @@ func main() { regression1033() //Test deferred builtins - testDeferBuiltin() + testDeferBuiltinClose() + testDeferBuiltinDelete() } func runFunc(f func(int), arg int) { @@ -121,13 +122,30 @@ func testMultiFuncVar() { defer f(1) } -func testDeferBuiltin() { +func testDeferBuiltinClose() { i := make(chan int) - defer close(i) + func() { + defer close(i) + }() + if n, ok := <-i; n != 0 || ok { + println("expected to read 0 from closed channel") + } +} + +func testDeferBuiltinDelete() { + m := map[int]int{3: 30, 5: 50} + func() { + defer delete(m, 3) + if m[3] != 30 { + println("expected m[3] to be 30") + } + }() + if m[3] != 0 { + println("expected m[3] to be 0") + } } type dumb struct { - } func (*dumb) Value(key interface{}) interface{} { @@ -144,17 +162,17 @@ func exportedDefer() { } func deferFunc() (int, func(int)) { - return 0, func(i int){println("...extracted defer func ", i)} + 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("Should not have gotten here. i = ", i) } } - return func(i int){println("Called the correct function. i = ", i)} + return func(i int) { println("Called the correct function. i = ", i) } } func testBound(f func() string) {