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.
Этот коммит содержится в:
Ayke van Laethem 2021-01-21 17:10:19 +01:00 коммит произвёл Ron Evans
родитель d85ac4b3cc
коммит 9bd36597d6
4 изменённых файлов: 75 добавлений и 49 удалений

Просмотреть файл

@ -79,8 +79,7 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
} }
// createChanClose closes the given channel. // createChanClose closes the given channel.
func (b *builder) createChanClose(param ssa.Value) { func (b *builder) createChanClose(ch llvm.Value) {
ch := b.getValue(param)
b.createRuntimeCall("chanClose", []llvm.Value{ch}, "") b.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
} }

Просмотреть файл

@ -130,7 +130,9 @@ func newBuilder(c *compilerContext, irbuilder llvm.Builder, f *ir.Function) *bui
} }
type deferBuiltin struct { type deferBuiltin struct {
funcName string callName string
pos token.Pos
argTypes []types.Type
callback int callback int
} }
@ -1196,11 +1198,11 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
// createBuiltin lowers a builtin Go function (append, close, delete, etc.) to // createBuiltin lowers a builtin Go function (append, close, delete, etc.) to
// LLVM IR. It uses runtime calls for some builtins. // 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 { switch callName {
case "append": case "append":
src := b.getValue(args[0]) src := argValues[0]
elems := b.getValue(args[1]) elems := argValues[1]
srcBuf := b.CreateExtractValue(src, 0, "append.srcBuf") srcBuf := b.CreateExtractValue(src, 0, "append.srcBuf")
srcPtr := b.CreateBitCast(srcBuf, b.i8ptrType, "append.srcPtr") srcPtr := b.CreateBitCast(srcBuf, b.i8ptrType, "append.srcPtr")
srcLen := b.CreateExtractValue(src, 1, "append.srcLen") 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, "") newSlice = b.CreateInsertValue(newSlice, newCap, 2, "")
return newSlice, nil return newSlice, nil
case "cap": case "cap":
value := b.getValue(args[0]) value := argValues[0]
var llvmCap llvm.Value var llvmCap llvm.Value
switch args[0].Type().(type) { switch argTypes[0].(type) {
case *types.Chan: case *types.Chan:
llvmCap = b.createRuntimeCall("chanCap", []llvm.Value{value}, "cap") llvmCap = b.createRuntimeCall("chanCap", []llvm.Value{value}, "cap")
case *types.Slice: case *types.Slice:
@ -1236,12 +1238,12 @@ func (b *builder) createBuiltin(args []ssa.Value, callName string, pos token.Pos
} }
return llvmCap, nil return llvmCap, nil
case "close": case "close":
b.createChanClose(args[0]) b.createChanClose(argValues[0])
return llvm.Value{}, nil return llvm.Value{}, nil
case "complex": case "complex":
r := b.getValue(args[0]) r := argValues[0]
i := b.getValue(args[1]) i := argValues[1]
t := args[0].Type().Underlying().(*types.Basic) t := argTypes[0].Underlying().(*types.Basic)
var cplx llvm.Value var cplx llvm.Value
switch t.Kind() { switch t.Kind() {
case types.Float32: 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, "") cplx = b.CreateInsertValue(cplx, i, 1, "")
return cplx, nil return cplx, nil
case "copy": case "copy":
dst := b.getValue(args[0]) dst := argValues[0]
src := b.getValue(args[1]) src := argValues[1]
dstLen := b.CreateExtractValue(dst, 1, "copy.dstLen") dstLen := b.CreateExtractValue(dst, 1, "copy.dstLen")
srcLen := b.CreateExtractValue(src, 1, "copy.srcLen") srcLen := b.CreateExtractValue(src, 1, "copy.srcLen")
dstBuf := b.CreateExtractValue(dst, 0, "copy.dstArray") 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) elemSize := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(elemType), false)
return b.createRuntimeCall("sliceCopy", []llvm.Value{dstBuf, srcBuf, dstLen, srcLen, elemSize}, "copy.n"), nil return b.createRuntimeCall("sliceCopy", []llvm.Value{dstBuf, srcBuf, dstLen, srcLen, elemSize}, "copy.n"), nil
case "delete": case "delete":
m := b.getValue(args[0]) m := argValues[0]
key := b.getValue(args[1]) key := argValues[1]
return llvm.Value{}, b.createMapDelete(args[1].Type(), m, key, pos) return llvm.Value{}, b.createMapDelete(argTypes[1], m, key, pos)
case "imag": case "imag":
cplx := b.getValue(args[0]) cplx := argValues[0]
return b.CreateExtractValue(cplx, 1, "imag"), nil return b.CreateExtractValue(cplx, 1, "imag"), nil
case "len": case "len":
value := b.getValue(args[0]) value := argValues[0]
var llvmLen llvm.Value var llvmLen llvm.Value
switch args[0].Type().Underlying().(type) { switch argTypes[0].Underlying().(type) {
case *types.Basic, *types.Slice: case *types.Basic, *types.Slice:
// string or slice // string or slice
llvmLen = b.CreateExtractValue(value, 1, "len") 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 return llvmLen, nil
case "print", "println": case "print", "println":
for i, arg := range args { for i, value := range argValues {
if i >= 1 && callName == "println" { if i >= 1 && callName == "println" {
b.createRuntimeCall("printspace", nil, "") b.createRuntimeCall("printspace", nil, "")
} }
value := b.getValue(arg) typ := argTypes[i].Underlying()
typ := arg.Type().Underlying()
switch typ := typ.(type) { switch typ := typ.(type) {
case *types.Basic: case *types.Basic:
switch typ.Kind() { 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 return llvm.Value{}, nil // print() or println() returns void
case "real": case "real":
cplx := b.getValue(args[0]) cplx := argValues[0]
return b.CreateExtractValue(cplx, 0, "real"), nil return b.CreateExtractValue(cplx, 0, "real"), nil
case "recover": case "recover":
return b.createRuntimeCall("_recover", nil, ""), nil return b.createRuntimeCall("_recover", nil, ""), nil
case "ssa:wrapnilchk": case "ssa:wrapnilchk":
// TODO: do an actual nil check? // TODO: do an actual nil check?
return b.getValue(args[0]), nil return argValues[0], nil
default: default:
return llvm.Value{}, b.makeError(pos, "todo: builtin: "+callName) 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() exported = targetFunc.IsExported()
} else if call, ok := instr.Value.(*ssa.Builtin); ok { } else if call, ok := instr.Value.(*ssa.Builtin); ok {
// Builtin function (append, close, delete, etc.).) // 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 { } else {
// Function pointer. // Function pointer.
value := b.getValue(instr.Value) value := b.getValue(instr.Value)

Просмотреть файл

@ -155,19 +155,19 @@ func (b *builder) createDefer(instr *ssa.Defer) {
valueTypes = append(valueTypes, context.Type()) valueTypes = append(valueTypes, context.Type())
} else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok { } else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok {
var funcName string var argTypes []types.Type
switch builtin.Name() { var argValues []llvm.Value
case "close": for _, arg := range instr.Call.Args {
funcName = "chanClose" argTypes = append(argTypes, arg.Type())
default: argValues = append(argValues, b.getValue(arg))
b.addError(instr.Pos(), "todo: Implement defer for "+builtin.Name())
return
} }
if _, ok := b.deferBuiltinFuncs[instr.Call.Value]; !ok { if _, ok := b.deferBuiltinFuncs[instr.Call.Value]; !ok {
b.deferBuiltinFuncs[instr.Call.Value] = deferBuiltin{ b.deferBuiltinFuncs[instr.Call.Value] = deferBuiltin{
funcName, callName: builtin.Name(),
len(b.allDeferFuncs), pos: builtin.Pos(),
argTypes: argTypes,
callback: len(b.allDeferFuncs),
} }
b.allDeferFuncs = append(b.allDeferFuncs, instr.Call.Value) 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 // Collect all values to be put in the struct (starting with
// runtime._defer fields). // runtime._defer fields).
values = []llvm.Value{callback, next} values = []llvm.Value{callback, next}
for _, param := range instr.Call.Args { for _, param := range argValues {
llvmParam := b.getValue(param) values = append(values, param)
values = append(values, llvmParam) valueTypes = append(valueTypes, param.Type())
valueTypes = append(valueTypes, llvmParam.Type())
} }
} else { } else {
@ -426,15 +425,18 @@ func (b *builder) createRunDefers() {
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame") deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
// Extract the params from the struct. // Extract the params from the struct.
var forwardParams []llvm.Value var argValues []llvm.Value
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := 0; i < params.Len(); i++ { 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(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
forwardParam := b.CreateLoad(gep, "param") 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: default:
panic("unknown deferred function type") panic("unknown deferred function type")
} }

32
testdata/calls.go предоставленный
Просмотреть файл

@ -72,7 +72,8 @@ func main() {
regression1033() regression1033()
//Test deferred builtins //Test deferred builtins
testDeferBuiltin() testDeferBuiltinClose()
testDeferBuiltinDelete()
} }
func runFunc(f func(int), arg int) { func runFunc(f func(int), arg int) {
@ -121,13 +122,30 @@ func testMultiFuncVar() {
defer f(1) defer f(1)
} }
func testDeferBuiltin() { func testDeferBuiltinClose() {
i := make(chan int) 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 { type dumb struct {
} }
func (*dumb) Value(key interface{}) interface{} { func (*dumb) Value(key interface{}) interface{} {
@ -144,17 +162,17 @@ func exportedDefer() {
} }
func deferFunc() (int, func(int)) { 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) { func multiFuncDefer() func(int) {
i := 0 i := 0
if 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) { func testBound(f func() string) {