Skip to content

Commit

Permalink
compiler: refactor function calling
Browse files Browse the repository at this point in the history
  • Loading branch information
aykevl authored and deadprogram committed Mar 25, 2020
1 parent d46934d commit d752e66
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 171 deletions.
108 changes: 55 additions & 53 deletions compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -1066,7 +1066,7 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) {
// A goroutine call on a func value, but the callee is trivial to find. For
// example: immediately applied functions.
funcValue := frame.getValue(value)
context = c.extractFuncContext(funcValue)
context = frame.extractFuncContext(funcValue)
default:
panic("StaticCallee returned an unexpected value")
}
Expand All @@ -1078,7 +1078,7 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) {
// goroutine:
// * The function context, for closures.
// * The function pointer (for tasks).
funcPtr, context := c.decodeFuncValue(frame.getValue(instr.Call.Value), instr.Call.Value.Type().(*types.Signature))
funcPtr, context := frame.decodeFuncValue(frame.getValue(instr.Call.Value), instr.Call.Value.Type().(*types.Signature))
params = append(params, context) // context parameter
switch c.Scheduler() {
case "none", "coroutines":
Expand Down Expand Up @@ -1315,86 +1315,90 @@ func (b *builder) createBuiltin(args []ssa.Value, callName string, pos token.Pos
}
}

func (c *Compiler) parseFunctionCall(frame *Frame, args []ssa.Value, llvmFn, context llvm.Value, exported bool) llvm.Value {
var params []llvm.Value
for _, param := range args {
params = append(params, frame.getValue(param))
}

if !exported {
// This function takes a context parameter.
// Add it to the end of the parameter list.
params = append(params, context)

// Parent coroutine handle.
params = append(params, llvm.Undef(c.i8ptrType))
}

return c.createCall(llvmFn, params, "")
}

func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
// createFunctionCall lowers a Go SSA call instruction (to a simple function,
// closure, function pointer, builtin, method, etc.) to LLVM IR, usually a call
// instruction.
//
// This is also where compiler intrinsics are implemented.
func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) {
if instr.IsInvoke() {
fnCast, args := frame.getInvokeCall(instr)
return c.createCall(fnCast, args, ""), nil
fnCast, args := b.getInvokeCall(instr)
return b.createCall(fnCast, args, ""), nil
}

// Try to call the function directly for trivially static calls.
var callee, context llvm.Value
exported := false
if fn := instr.StaticCallee(); fn != nil {
// Direct function call, either to a named or anonymous (directly
// applied) function call. If it is anonymous, it may be a closure.
name := fn.RelString(nil)
switch {
case name == "device/arm.ReadRegister" || name == "device/riscv.ReadRegister":
return c.emitReadRegister(name, instr.Args)
return b.createReadRegister(name, instr.Args)
case name == "device/arm.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm":
return c.emitAsm(instr.Args)
return b.createInlineAsm(instr.Args)
case name == "device/arm.AsmFull" || name == "device/avr.AsmFull" || name == "device/riscv.AsmFull":
return c.emitAsmFull(frame, instr)
return b.createInlineAsmFull(instr)
case strings.HasPrefix(name, "device/arm.SVCall"):
return c.emitSVCall(frame, instr.Args)
return b.emitSVCall(instr.Args)
case strings.HasPrefix(name, "(device/riscv.CSR)."):
return c.emitCSROperation(frame, instr)
return b.emitCSROperation(instr)
case strings.HasPrefix(name, "syscall.Syscall"):
return c.emitSyscall(frame, instr)
return b.createSyscall(instr)
case strings.HasPrefix(name, "runtime/volatile.Load"):
return c.emitVolatileLoad(frame, instr)
return b.createVolatileLoad(instr)
case strings.HasPrefix(name, "runtime/volatile.Store"):
return c.emitVolatileStore(frame, instr)
return b.createVolatileStore(instr)
case name == "runtime/interrupt.New":
return c.emitInterruptGlobal(frame, instr)
return b.createInterruptGlobal(instr)
}

targetFunc := c.ir.GetFunction(fn)
targetFunc := b.ir.GetFunction(fn)
if targetFunc.LLVMFn.IsNil() {
return llvm.Value{}, c.makeError(instr.Pos(), "undefined function: "+targetFunc.LinkName())
return llvm.Value{}, b.makeError(instr.Pos(), "undefined function: "+targetFunc.LinkName())
}
var context llvm.Value
switch value := instr.Value.(type) {
case *ssa.Function:
// Regular function call. No context is necessary.
context = llvm.Undef(c.i8ptrType)
context = llvm.Undef(b.i8ptrType)
case *ssa.MakeClosure:
// A call on a func value, but the callee is trivial to find. For
// example: immediately applied functions.
funcValue := frame.getValue(value)
context = c.extractFuncContext(funcValue)
funcValue := b.getValue(value)
context = b.extractFuncContext(funcValue)
default:
panic("StaticCallee returned an unexpected value")
}
return c.parseFunctionCall(frame, instr.Args, targetFunc.LLVMFn, context, targetFunc.IsExported()), nil
}

// Builtin or function pointer.
switch call := instr.Value.(type) {
case *ssa.Builtin:
return frame.createBuiltin(instr.Args, call.Name(), instr.Pos())
default: // function pointer
value := frame.getValue(instr.Value)
callee = targetFunc.LLVMFn
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())
} else {
// Function pointer.
value := b.getValue(instr.Value)
// This is a func value, which cannot be called directly. We have to
// extract the function pointer and context first from the func value.
funcPtr, context := c.decodeFuncValue(value, instr.Value.Type().Underlying().(*types.Signature))
frame.createNilCheck(funcPtr, "fpcall")
return c.parseFunctionCall(frame, instr.Args, funcPtr, context, false), nil
callee, context = b.decodeFuncValue(value, instr.Value.Type().Underlying().(*types.Signature))
b.createNilCheck(callee, "fpcall")
}

var params []llvm.Value
for _, param := range instr.Args {
params = append(params, b.getValue(param))
}

if !exported {
// This function takes a context parameter.
// Add it to the end of the parameter list.
params = append(params, context)

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

return b.createCall(callee, params, ""), nil
}

// getValue returns the LLVM value of a constant, function value, global, or
Expand Down Expand Up @@ -1462,9 +1466,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
y := frame.getValue(expr.Y)
return frame.createBinOp(expr.Op, expr.X.Type(), x, y, expr.Pos())
case *ssa.Call:
// Passing the current task here to the subroutine. It is only used when
// the subroutine is blocking.
return c.parseCall(frame, expr.Common())
return frame.createFunctionCall(expr.Common())
case *ssa.ChangeInterface:
// Do not change between interface types: always use the underlying
// (concrete) type in the type number of the interface. Every method
Expand Down
20 changes: 10 additions & 10 deletions compiler/func.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,22 +71,22 @@ func (b *builder) extractFuncScalar(funcValue llvm.Value) llvm.Value {

// extractFuncContext extracts the context pointer from this function value. It
// is a cheap operation.
func (c *Compiler) extractFuncContext(funcValue llvm.Value) llvm.Value {
return c.builder.CreateExtractValue(funcValue, 0, "")
func (b *builder) extractFuncContext(funcValue llvm.Value) llvm.Value {
return b.CreateExtractValue(funcValue, 0, "")
}

// decodeFuncValue extracts the context and the function pointer from this func
// value. This may be an expensive operation.
func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value) {
context = c.builder.CreateExtractValue(funcValue, 0, "")
switch c.FuncImplementation() {
func (b *builder) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value) {
context = b.CreateExtractValue(funcValue, 0, "")
switch b.FuncImplementation() {
case compileopts.FuncValueDoubleword:
funcPtr = c.builder.CreateExtractValue(funcValue, 1, "")
funcPtr = b.CreateExtractValue(funcValue, 1, "")
case compileopts.FuncValueSwitch:
llvmSig := c.getRawFuncType(sig)
sigGlobal := c.getTypeCode(sig)
funcPtr = c.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
funcPtr = c.builder.CreateIntToPtr(funcPtr, llvmSig, "")
llvmSig := b.getRawFuncType(sig)
sigGlobal := b.getTypeCode(sig)
funcPtr = b.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
funcPtr = b.CreateIntToPtr(funcPtr, llvmSig, "")
default:
panic("unimplemented func value variant")
}
Expand Down
58 changes: 29 additions & 29 deletions compiler/inlineasm.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import (
// func ReadRegister(name string) uintptr
//
// The register name must be a constant, for example "sp".
func (c *Compiler) emitReadRegister(name string, args []ssa.Value) (llvm.Value, error) {
fnType := llvm.FunctionType(c.uintptrType, []llvm.Type{}, false)
func (b *builder) createReadRegister(name string, args []ssa.Value) (llvm.Value, error) {
fnType := llvm.FunctionType(b.uintptrType, []llvm.Type{}, false)
regname := constant.StringVal(args[0].(*ssa.Const).Value)
var asm string
switch name {
Expand All @@ -31,7 +31,7 @@ func (c *Compiler) emitReadRegister(name string, args []ssa.Value) (llvm.Value,
panic("unknown architecture")
}
target := llvm.InlineAsm(fnType, asm, "=r", false, false, 0)
return c.builder.CreateCall(target, nil, ""), nil
return b.CreateCall(target, nil, ""), nil
}

// This is a compiler builtin, which emits a piece of inline assembly with no
Expand All @@ -41,12 +41,12 @@ func (c *Compiler) emitReadRegister(name string, args []ssa.Value) (llvm.Value,
// func Asm(asm string)
//
// The provided assembly must be a constant.
func (c *Compiler) emitAsm(args []ssa.Value) (llvm.Value, error) {
func (b *builder) createInlineAsm(args []ssa.Value) (llvm.Value, error) {
// Magic function: insert inline assembly instead of calling it.
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{}, false)
fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{}, false)
asm := constant.StringVal(args[0].(*ssa.Const).Value)
target := llvm.InlineAsm(fnType, asm, "", true, false, 0)
return c.builder.CreateCall(target, nil, ""), nil
return b.CreateCall(target, nil, ""), nil
}

// This is a compiler builtin, which allows assembly to be called in a flexible
Expand All @@ -63,7 +63,7 @@ func (c *Compiler) emitAsm(args []ssa.Value) (llvm.Value, error) {
// "value": 1
// "result": &dest,
// })
func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error) {
asmString := constant.StringVal(instr.Args[0].(*ssa.Const).Value)
registers := map[string]llvm.Value{}
registerMap := instr.Args[1].(*ssa.MakeMap)
Expand All @@ -73,17 +73,17 @@ func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value,
// ignore
case *ssa.MapUpdate:
if r.Block() != registerMap.Block() {
return llvm.Value{}, c.makeError(instr.Pos(), "register value map must be created in the same basic block")
return llvm.Value{}, b.makeError(instr.Pos(), "register value map must be created in the same basic block")
}
key := constant.StringVal(r.Key.(*ssa.Const).Value)
//println("value:", r.Value.(*ssa.MakeInterface).X.String())
registers[key] = frame.getValue(r.Value.(*ssa.MakeInterface).X)
registers[key] = b.getValue(r.Value.(*ssa.MakeInterface).X)
case *ssa.Call:
if r.Common() == instr {
break
}
default:
return llvm.Value{}, c.makeError(instr.Pos(), "don't know how to handle argument to inline assembly: "+r.String())
return llvm.Value{}, b.makeError(instr.Pos(), "don't know how to handle argument to inline assembly: "+r.String())
}
}
// TODO: handle dollar signs in asm string
Expand All @@ -98,7 +98,7 @@ func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value,
name := s[1 : len(s)-1]
if _, ok := registers[name]; !ok {
if err == nil {
err = c.makeError(instr.Pos(), "unknown register name: "+name)
err = b.makeError(instr.Pos(), "unknown register name: "+name)
}
return s
}
Expand All @@ -112,7 +112,7 @@ func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value,
case llvm.PointerTypeKind:
constraints = append(constraints, "*m")
default:
err = c.makeError(instr.Pos(), "unknown type in inline assembly for value: "+name)
err = b.makeError(instr.Pos(), "unknown type in inline assembly for value: "+name)
return s
}
}
Expand All @@ -121,9 +121,9 @@ func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value,
if err != nil {
return llvm.Value{}, err
}
fnType := llvm.FunctionType(c.ctx.VoidType(), argTypes, false)
fnType := llvm.FunctionType(b.ctx.VoidType(), argTypes, false)
target := llvm.InlineAsm(fnType, asmString, strings.Join(constraints, ","), true, false, 0)
return c.builder.CreateCall(target, args, ""), nil
return b.CreateCall(target, args, ""), nil
}

// This is a compiler builtin which emits an inline SVCall instruction. It can
Expand All @@ -137,7 +137,7 @@ func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value,
//
// The num parameter must be a constant. All other parameters may be any scalar
// value supported by LLVM inline assembly.
func (c *Compiler) emitSVCall(frame *Frame, args []ssa.Value) (llvm.Value, error) {
func (b *builder) emitSVCall(args []ssa.Value) (llvm.Value, error) {
num, _ := constant.Uint64Val(args[0].(*ssa.Const).Value)
llvmArgs := []llvm.Value{}
argTypes := []llvm.Type{}
Expand All @@ -150,17 +150,17 @@ func (c *Compiler) emitSVCall(frame *Frame, args []ssa.Value) (llvm.Value, error
} else {
constraints += ",{r" + strconv.Itoa(i) + "}"
}
llvmValue := frame.getValue(arg)
llvmValue := b.getValue(arg)
llvmArgs = append(llvmArgs, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
// Implement the ARM calling convention by marking r1-r3 as
// clobbered. r0 is used as an output register so doesn't have to be
// marked as clobbered.
constraints += ",~{r1},~{r2},~{r3}"
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0)
return c.builder.CreateCall(target, llvmArgs, ""), nil
return b.CreateCall(target, llvmArgs, ""), nil
}

// This is a compiler builtin which emits CSR instructions. It can be one of:
Expand All @@ -172,38 +172,38 @@ func (c *Compiler) emitSVCall(frame *Frame, args []ssa.Value) (llvm.Value, error
//
// The csr parameter (method receiver) must be a constant. Other parameter can
// be any value.
func (c *Compiler) emitCSROperation(frame *Frame, call *ssa.CallCommon) (llvm.Value, error) {
func (b *builder) emitCSROperation(call *ssa.CallCommon) (llvm.Value, error) {
csrConst, ok := call.Args[0].(*ssa.Const)
if !ok {
return llvm.Value{}, c.makeError(call.Pos(), "CSR must be constant")
return llvm.Value{}, b.makeError(call.Pos(), "CSR must be constant")
}
csr := csrConst.Uint64()
switch name := call.StaticCallee().Name(); name {
case "Get":
// Note that this instruction may have side effects, and thus must be
// marked as such.
fnType := llvm.FunctionType(c.uintptrType, nil, false)
fnType := llvm.FunctionType(b.uintptrType, nil, false)
asm := fmt.Sprintf("csrr $0, %d", csr)
target := llvm.InlineAsm(fnType, asm, "=r", true, false, 0)
return c.builder.CreateCall(target, nil, ""), nil
return b.CreateCall(target, nil, ""), nil
case "Set":
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.uintptrType}, false)
fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.uintptrType}, false)
asm := fmt.Sprintf("csrw %d, $0", csr)
target := llvm.InlineAsm(fnType, asm, "r", true, false, 0)
return c.builder.CreateCall(target, []llvm.Value{frame.getValue(call.Args[1])}, ""), nil
return b.CreateCall(target, []llvm.Value{b.getValue(call.Args[1])}, ""), nil
case "SetBits":
// Note: it may be possible to optimize this to csrrsi in many cases.
fnType := llvm.FunctionType(c.uintptrType, []llvm.Type{c.uintptrType}, false)
fnType := llvm.FunctionType(b.uintptrType, []llvm.Type{b.uintptrType}, false)
asm := fmt.Sprintf("csrrs $0, %d, $1", csr)
target := llvm.InlineAsm(fnType, asm, "=r,r", true, false, 0)
return c.builder.CreateCall(target, []llvm.Value{frame.getValue(call.Args[1])}, ""), nil
return b.CreateCall(target, []llvm.Value{b.getValue(call.Args[1])}, ""), nil
case "ClearBits":
// Note: it may be possible to optimize this to csrrci in many cases.
fnType := llvm.FunctionType(c.uintptrType, []llvm.Type{c.uintptrType}, false)
fnType := llvm.FunctionType(b.uintptrType, []llvm.Type{b.uintptrType}, false)
asm := fmt.Sprintf("csrrc $0, %d, $1", csr)
target := llvm.InlineAsm(fnType, asm, "=r,r", true, false, 0)
return c.builder.CreateCall(target, []llvm.Value{frame.getValue(call.Args[1])}, ""), nil
return b.CreateCall(target, []llvm.Value{b.getValue(call.Args[1])}, ""), nil
default:
return llvm.Value{}, c.makeError(call.Pos(), "unknown CSR operation: "+name)
return llvm.Value{}, b.makeError(call.Pos(), "unknown CSR operation: "+name)
}
}
Loading

0 comments on commit d752e66

Please sign in to comment.