From 392bba8394ece9e8b90ee2d6e82590b1492bc62d Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 15 Oct 2018 19:37:09 +0200 Subject: [PATCH] compiler: add support for parameters to inline assembly --- compiler/compiler.go | 81 +++++++++++++++++++++++++++++++++++---- docs/microcontrollers.rst | 21 +++++++++- src/device/arm/arm.go | 21 ++++++++-- src/device/avr/avr.go | 17 ++++++-- 4 files changed, 123 insertions(+), 17 deletions(-) diff --git a/compiler/compiler.go b/compiler/compiler.go index f015146f..9c5b5ac8 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -9,6 +9,7 @@ import ( "go/types" "os" "path/filepath" + "regexp" "runtime" "strconv" "strings" @@ -1892,15 +1893,81 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon, parentHandle l // Try to call the function directly for trivially static calls. if fn := instr.StaticCallee(); fn != nil { - if fn.Name() == "Asm" && len(instr.Args) == 1 { + if fn.RelString(nil) == "device/arm.Asm" || fn.RelString(nil) == "device/avr.Asm" { // Magic function: insert inline assembly instead of calling it. - if named, ok := instr.Args[0].Type().(*types.Named); ok && named.Obj().Name() == "__asm" { - fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{}, false) - asm := constant.StringVal(instr.Args[0].(*ssa.Const).Value) - target := llvm.InlineAsm(fnType, asm, "", true, false, 0) - return c.builder.CreateCall(target, nil, ""), nil - } + fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{}, false) + asm := constant.StringVal(instr.Args[0].(*ssa.Const).Value) + target := llvm.InlineAsm(fnType, asm, "", true, false, 0) + return c.builder.CreateCall(target, nil, ""), nil } + + if fn.RelString(nil) == "device/arm.AsmFull" || fn.RelString(nil) == "device/avr.AsmFull" { + asmString := constant.StringVal(instr.Args[0].(*ssa.Const).Value) + registers := map[string]llvm.Value{} + registerMap := instr.Args[1].(*ssa.MakeMap) + for _, r := range *registerMap.Referrers() { + switch r := r.(type) { + case *ssa.DebugRef: + // ignore + case *ssa.MapUpdate: + if r.Block() != registerMap.Block() { + return llvm.Value{}, errors.New("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()) + value, err := c.parseExpr(frame, r.Value.(*ssa.MakeInterface).X) + if err != nil { + return llvm.Value{}, err + } + registers[key] = value + case *ssa.Call: + if r.Common() == instr { + break + } + default: + return llvm.Value{}, errors.New("don't know how to handle argument to inline assembly: " + r.String()) + } + } + // TODO: handle dollar signs in asm string + registerNumbers := map[string]int{} + var err error + argTypes := []llvm.Type{} + args := []llvm.Value{} + constraints := []string{} + asmString = regexp.MustCompile("\\{[a-zA-Z]+\\}").ReplaceAllStringFunc(asmString, func(s string) string { + // TODO: skip strings like {r4} etc. that look like ARM push/pop + // instructions. + name := s[1 : len(s)-1] + if _, ok := registers[name]; !ok { + if err == nil { + err = errors.New("unknown register name: " + name) + } + return s + } + if _, ok := registerNumbers[name]; !ok { + registerNumbers[name] = len(registerNumbers) + argTypes = append(argTypes, registers[name].Type()) + args = append(args, registers[name]) + switch registers[name].Type().TypeKind() { + case llvm.IntegerTypeKind: + constraints = append(constraints, "r") + case llvm.PointerTypeKind: + constraints = append(constraints, "*m") + default: + err = errors.New("unknown type in inline assembly for value: " + name) + return s + } + } + return fmt.Sprintf("${%v}", registerNumbers[name]) + }) + if err != nil { + return llvm.Value{}, err + } + fnType := llvm.FunctionType(c.ctx.VoidType(), argTypes, false) + target := llvm.InlineAsm(fnType, asmString, strings.Join(constraints, ","), true, false, 0) + return c.builder.CreateCall(target, args, ""), nil + } + targetFunc := c.ir.GetFunction(fn) if targetFunc.LLVMFn.IsNil() { return llvm.Value{}, errors.New("undefined function: " + targetFunc.LinkName()) diff --git a/docs/microcontrollers.rst b/docs/microcontrollers.rst index 7509190d..0e764643 100644 --- a/docs/microcontrollers.rst +++ b/docs/microcontrollers.rst @@ -113,8 +113,25 @@ The device-specific packages like ``device/avr`` and ``device/arm`` provide arm.Asm("wfi") -There is no support yet for inline assembly that takes (register) parameters or -returns a value. +You can also pass parameters to the inline assembly:: + + var result int32 + arm.AsmFull(` + lsls {value}, #1 + str {value}, {result} + `, map[string]interface{}{ + "value": 42, + "result": &result, + }) + println("result:", result) + +In general, types are autodetected. That is, integer types are passed as raw +registers and pointer types are passed as memory locations. This means you can't +easily do pointer arithmetic. To do that, convert a pointer value to a +``uintptr``. + +Inline assembly support is expected to change in the future and may change in a +backwards-incompatible manner. Harvard architectures (AVR) diff --git a/src/device/arm/arm.go b/src/device/arm/arm.go index 7f6858fb..d351348b 100644 --- a/src/device/arm/arm.go +++ b/src/device/arm/arm.go @@ -33,13 +33,26 @@ import ( "unsafe" ) +// Run the given assembly code. The code will be marked as having side effects, +// as it doesn't produce output and thus would normally be eliminated by the +// optimizer. +func Asm(asm string) + +// Run the given inline assembly. The code will be marked as having side +// effects, as it would otherwise be optimized away. The inline assembly string +// recognizes template values in the form {name}, like so: +// +// arm.AsmFull( +// "str {value}, {result}", +// map[string]interface{}{ +// "value": 1 +// "result": &dest, +// }) +func AsmFull(asm string, regs map[string]interface{}) + //go:volatile type RegValue uint32 -type __asm string - -func Asm(s __asm) - const ( SCS_BASE = 0xE000E000 NVIC_BASE = SCS_BASE + 0x0100 diff --git a/src/device/avr/avr.go b/src/device/avr/avr.go index ba4347d2..7fa6bf95 100644 --- a/src/device/avr/avr.go +++ b/src/device/avr/avr.go @@ -1,9 +1,18 @@ package avr -// Magic type recognozed by the compiler to mark this string as inline assembly. -type __asm string - // Run the given assembly code. The code will be marked as having side effects, // as it doesn't produce output and thus would normally be eliminated by the // optimizer. -func Asm(asm __asm) +func Asm(asm string) + +// Run the given inline assembly. The code will be marked as having side +// effects, as it would otherwise be optimized away. The inline assembly string +// recognizes template values in the form {name}, like so: +// +// avr.AsmFull( +// "str {value}, {result}", +// map[string]interface{}{ +// "value": 1 +// "result": &dest, +// }) +func AsmFull(asm string, regs map[string]interface{})