From 6389e45d992c134ffc749e44be8fe19473bb33d1 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 6 Apr 2020 17:06:34 +0200 Subject: [PATCH] all: replace ReadRegister with AsmFull inline assembly This makes AsmFull more powerful (by supporting return values) and avoids a compiler builtin. --- compiler/compiler.go | 2 - compiler/inlineasm.go | 82 ++++++++++++++++----------------- src/device/arm/arm.go | 9 ++-- src/device/avr/avr.go | 5 +- src/device/riscv/riscv.go | 17 +++++-- src/runtime/arch_arm.go | 2 +- src/runtime/arch_cortexm.go | 2 +- src/runtime/arch_tinygoriscv.go | 2 +- 8 files changed, 66 insertions(+), 55 deletions(-) diff --git a/compiler/compiler.go b/compiler/compiler.go index 6ab553b4..e4cd8d4b 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1364,8 +1364,6 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) return b.createMemoryCopyCall(fn, instr.Args) case name == "runtime.memzero": return b.createMemoryZeroCall(instr.Args) - case name == "device/arm.ReadRegister" || name == "device/riscv.ReadRegister": - return b.createReadRegister(name, instr.Args) case name == "device/arm.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm": return b.createInlineAsm(instr.Args) case name == "device/arm.AsmFull" || name == "device/avr.AsmFull" || name == "device/riscv.AsmFull": diff --git a/compiler/inlineasm.go b/compiler/inlineasm.go index 2727228a..6089f096 100644 --- a/compiler/inlineasm.go +++ b/compiler/inlineasm.go @@ -13,27 +13,6 @@ import ( "tinygo.org/x/go-llvm" ) -// This is a compiler builtin, which reads the given register by name: -// -// func ReadRegister(name string) uintptr -// -// The register name must be a constant, for example "sp". -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 { - case "device/arm.ReadRegister": - asm = "mov $0, " + regname - case "device/riscv.ReadRegister": - asm = "mv $0, " + regname - default: - panic("unknown architecture") - } - target := llvm.InlineAsm(fnType, asm, "=r", false, false, 0) - return b.CreateCall(target, nil, ""), nil -} - // This is a compiler builtin, which emits a piece of inline assembly with no // operands or return values. It is useful for trivial instructions, like wfi in // ARM or sleep in AVR. @@ -52,7 +31,7 @@ func (b *builder) createInlineAsm(args []ssa.Value) (llvm.Value, error) { // This is a compiler builtin, which allows assembly to be called in a flexible // way. // -// func AsmFull(asm string, regs map[string]interface{}) +// func AsmFull(asm string, regs map[string]interface{}) uintptr // // The asm parameter must be a constant string. The regs parameter must be // provided immediately. For example: @@ -66,24 +45,24 @@ func (b *builder) createInlineAsm(args []ssa.Value) (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) - for _, r := range *registerMap.Referrers() { - switch r := r.(type) { - case *ssa.DebugRef: - // ignore - case *ssa.MapUpdate: - if r.Block() != registerMap.Block() { - return llvm.Value{}, b.makeError(instr.Pos(), "register value map must be created in the same basic block") + if registerMap, ok := instr.Args[1].(*ssa.MakeMap); ok { + for _, r := range *registerMap.Referrers() { + switch r := r.(type) { + case *ssa.DebugRef: + // ignore + case *ssa.MapUpdate: + if r.Block() != registerMap.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) + registers[key] = b.getValue(r.Value.(*ssa.MakeInterface).X) + case *ssa.Call: + if r.Common() == instr { + break + } + default: + return llvm.Value{}, b.makeError(instr.Pos(), "don't know how to handle argument to inline assembly: "+r.String()) } - key := constant.StringVal(r.Key.(*ssa.Const).Value) - //println("value:", r.Value.(*ssa.MakeInterface).X.String()) - registers[key] = b.getValue(r.Value.(*ssa.MakeInterface).X) - case *ssa.Call: - if r.Common() == instr { - break - } - default: - 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 @@ -92,6 +71,15 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error) argTypes := []llvm.Type{} args := []llvm.Value{} constraints := []string{} + hasOutput := false + asmString = regexp.MustCompile("\\{\\}").ReplaceAllStringFunc(asmString, func(s string) string { + hasOutput = true + return "$0" + }) + if hasOutput { + constraints = append(constraints, "=&r") + registerNumbers[""] = 0 + } 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. @@ -121,9 +109,21 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error) if err != nil { return llvm.Value{}, err } - fnType := llvm.FunctionType(b.ctx.VoidType(), argTypes, false) + var outputType llvm.Type + if hasOutput { + outputType = b.uintptrType + } else { + outputType = b.ctx.VoidType() + } + fnType := llvm.FunctionType(outputType, argTypes, false) target := llvm.InlineAsm(fnType, asmString, strings.Join(constraints, ","), true, false, 0) - return b.CreateCall(target, args, ""), nil + result := b.CreateCall(target, args, "") + if hasOutput { + return result, nil + } else { + // Make sure we return something valid. + return llvm.ConstInt(b.uintptrType, 0, false), nil + } } // This is a compiler builtin which emits an inline SVCall instruction. It can diff --git a/src/device/arm/arm.go b/src/device/arm/arm.go index 21ba3bbd..ac11199e 100644 --- a/src/device/arm/arm.go +++ b/src/device/arm/arm.go @@ -52,11 +52,10 @@ func Asm(asm string) // "value": 1 // "result": &dest, // }) -func AsmFull(asm string, regs map[string]interface{}) - -// ReadRegister returns the contents of the specified register. The register -// must be a processor register, reachable with the "mov" instruction. -func ReadRegister(name string) uintptr +// +// You can use {} in the asm string (which expands to a register) to set the +// return value. +func AsmFull(asm string, regs map[string]interface{}) uintptr // Run the following system call (SVCall) with 0 arguments. func SVCall0(num uintptr) uintptr diff --git a/src/device/avr/avr.go b/src/device/avr/avr.go index 7fa6bf95..12f145dd 100644 --- a/src/device/avr/avr.go +++ b/src/device/avr/avr.go @@ -15,4 +15,7 @@ func Asm(asm string) // "value": 1 // "result": &dest, // }) -func AsmFull(asm string, regs map[string]interface{}) +// +// You can use {} in the asm string (which expands to a register) to set the +// return value. +func AsmFull(asm string, regs map[string]interface{}) uintptr diff --git a/src/device/riscv/riscv.go b/src/device/riscv/riscv.go index 947b3f18..3a2e6cc4 100644 --- a/src/device/riscv/riscv.go +++ b/src/device/riscv/riscv.go @@ -5,6 +5,17 @@ package riscv // optimizer. func Asm(asm string) -// ReadRegister returns the contents of the specified register. The register -// must be a processor register, reachable with the "mov" instruction. -func ReadRegister(name string) uintptr +// 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( +// "st {value}, {result}", +// map[string]interface{}{ +// "value": 1 +// "result": &dest, +// }) +// +// You can use {} in the asm string (which expands to a register) to set the +// return value. +func AsmFull(asm string, regs map[string]interface{}) uintptr diff --git a/src/runtime/arch_arm.go b/src/runtime/arch_arm.go index 995e6d24..0a98f6d5 100644 --- a/src/runtime/arch_arm.go +++ b/src/runtime/arch_arm.go @@ -15,5 +15,5 @@ func align(ptr uintptr) uintptr { } func getCurrentStackPointer() uintptr { - return arm.ReadRegister("sp") + return arm.AsmFull("mov {}, sp", nil) } diff --git a/src/runtime/arch_cortexm.go b/src/runtime/arch_cortexm.go index 7a0aeffd..17b82408 100644 --- a/src/runtime/arch_cortexm.go +++ b/src/runtime/arch_cortexm.go @@ -17,5 +17,5 @@ func align(ptr uintptr) uintptr { } func getCurrentStackPointer() uintptr { - return arm.ReadRegister("sp") + return arm.AsmFull("mov {}, sp", nil) } diff --git a/src/runtime/arch_tinygoriscv.go b/src/runtime/arch_tinygoriscv.go index 1c5d8b78..242f9b96 100644 --- a/src/runtime/arch_tinygoriscv.go +++ b/src/runtime/arch_tinygoriscv.go @@ -15,5 +15,5 @@ func align(ptr uintptr) uintptr { } func getCurrentStackPointer() uintptr { - return riscv.ReadRegister("sp") + return riscv.AsmFull("mv {}, sp", nil) }