compiler: add support for parameters to inline assembly

Этот коммит содержится в:
Ayke van Laethem 2018-10-15 19:37:09 +02:00
родитель 52199f4a14
коммит 392bba8394
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: E97FF5335DFDFDED
4 изменённых файлов: 123 добавлений и 17 удалений

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

@ -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())

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

@ -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)

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

@ -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

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

@ -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{})