github.com/aykevl/tinygo@v0.5.0/compiler/inlineasm.go (about) 1 package compiler 2 3 // This file implements inline asm support by calling special functions. 4 5 import ( 6 "fmt" 7 "go/constant" 8 "regexp" 9 "strconv" 10 "strings" 11 12 "golang.org/x/tools/go/ssa" 13 "tinygo.org/x/go-llvm" 14 ) 15 16 // This is a compiler builtin, which reads the given register by name: 17 // 18 // func ReadRegister(name string) uintptr 19 // 20 // The register name must be a constant, for example "sp". 21 func (c *Compiler) emitReadRegister(args []ssa.Value) (llvm.Value, error) { 22 fnType := llvm.FunctionType(c.uintptrType, []llvm.Type{}, false) 23 regname := constant.StringVal(args[0].(*ssa.Const).Value) 24 target := llvm.InlineAsm(fnType, "mov $0, "+regname, "=r", false, false, 0) 25 return c.builder.CreateCall(target, nil, ""), nil 26 } 27 28 // This is a compiler builtin, which emits a piece of inline assembly with no 29 // operands or return values. It is useful for trivial instructions, like wfi in 30 // ARM or sleep in AVR. 31 // 32 // func Asm(asm string) 33 // 34 // The provided assembly must be a constant. 35 func (c *Compiler) emitAsm(args []ssa.Value) (llvm.Value, error) { 36 // Magic function: insert inline assembly instead of calling it. 37 fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{}, false) 38 asm := constant.StringVal(args[0].(*ssa.Const).Value) 39 target := llvm.InlineAsm(fnType, asm, "", true, false, 0) 40 return c.builder.CreateCall(target, nil, ""), nil 41 } 42 43 // This is a compiler builtin, which allows assembly to be called in a flexible 44 // way. 45 // 46 // func AsmFull(asm string, regs map[string]interface{}) 47 // 48 // The asm parameter must be a constant string. The regs parameter must be 49 // provided immediately. For example: 50 // 51 // arm.AsmFull( 52 // "str {value}, {result}", 53 // map[string]interface{}{ 54 // "value": 1 55 // "result": &dest, 56 // }) 57 func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) { 58 asmString := constant.StringVal(instr.Args[0].(*ssa.Const).Value) 59 registers := map[string]llvm.Value{} 60 registerMap := instr.Args[1].(*ssa.MakeMap) 61 for _, r := range *registerMap.Referrers() { 62 switch r := r.(type) { 63 case *ssa.DebugRef: 64 // ignore 65 case *ssa.MapUpdate: 66 if r.Block() != registerMap.Block() { 67 return llvm.Value{}, c.makeError(instr.Pos(), "register value map must be created in the same basic block") 68 } 69 key := constant.StringVal(r.Key.(*ssa.Const).Value) 70 //println("value:", r.Value.(*ssa.MakeInterface).X.String()) 71 value, err := c.parseExpr(frame, r.Value.(*ssa.MakeInterface).X) 72 if err != nil { 73 return llvm.Value{}, err 74 } 75 registers[key] = value 76 case *ssa.Call: 77 if r.Common() == instr { 78 break 79 } 80 default: 81 return llvm.Value{}, c.makeError(instr.Pos(), "don't know how to handle argument to inline assembly: "+r.String()) 82 } 83 } 84 // TODO: handle dollar signs in asm string 85 registerNumbers := map[string]int{} 86 var err error 87 argTypes := []llvm.Type{} 88 args := []llvm.Value{} 89 constraints := []string{} 90 asmString = regexp.MustCompile("\\{[a-zA-Z]+\\}").ReplaceAllStringFunc(asmString, func(s string) string { 91 // TODO: skip strings like {r4} etc. that look like ARM push/pop 92 // instructions. 93 name := s[1 : len(s)-1] 94 if _, ok := registers[name]; !ok { 95 if err == nil { 96 err = c.makeError(instr.Pos(), "unknown register name: "+name) 97 } 98 return s 99 } 100 if _, ok := registerNumbers[name]; !ok { 101 registerNumbers[name] = len(registerNumbers) 102 argTypes = append(argTypes, registers[name].Type()) 103 args = append(args, registers[name]) 104 switch registers[name].Type().TypeKind() { 105 case llvm.IntegerTypeKind: 106 constraints = append(constraints, "r") 107 case llvm.PointerTypeKind: 108 constraints = append(constraints, "*m") 109 default: 110 err = c.makeError(instr.Pos(), "unknown type in inline assembly for value: "+name) 111 return s 112 } 113 } 114 return fmt.Sprintf("${%v}", registerNumbers[name]) 115 }) 116 if err != nil { 117 return llvm.Value{}, err 118 } 119 fnType := llvm.FunctionType(c.ctx.VoidType(), argTypes, false) 120 target := llvm.InlineAsm(fnType, asmString, strings.Join(constraints, ","), true, false, 0) 121 return c.builder.CreateCall(target, args, ""), nil 122 } 123 124 // This is a compiler builtin which emits an inline SVCall instruction. It can 125 // be one of: 126 // 127 // func SVCall0(num uintptr) uintptr 128 // func SVCall1(num uintptr, a1 interface{}) uintptr 129 // func SVCall2(num uintptr, a1, a2 interface{}) uintptr 130 // func SVCall3(num uintptr, a1, a2, a3 interface{}) uintptr 131 // func SVCall4(num uintptr, a1, a2, a3, a4 interface{}) uintptr 132 // 133 // The num parameter must be a constant. All other parameters may be any scalar 134 // value supported by LLVM inline assembly. 135 func (c *Compiler) emitSVCall(frame *Frame, args []ssa.Value) (llvm.Value, error) { 136 num, _ := constant.Uint64Val(args[0].(*ssa.Const).Value) 137 llvmArgs := []llvm.Value{} 138 argTypes := []llvm.Type{} 139 asm := "svc #" + strconv.FormatUint(num, 10) 140 constraints := "={r0}" 141 for i, arg := range args[1:] { 142 arg = arg.(*ssa.MakeInterface).X 143 if i == 0 { 144 constraints += ",0" 145 } else { 146 constraints += ",{r" + strconv.Itoa(i) + "}" 147 } 148 llvmValue, err := c.parseExpr(frame, arg) 149 if err != nil { 150 return llvm.Value{}, err 151 } 152 llvmArgs = append(llvmArgs, llvmValue) 153 argTypes = append(argTypes, llvmValue.Type()) 154 } 155 // Implement the ARM calling convention by marking r1-r3 as 156 // clobbered. r0 is used as an output register so doesn't have to be 157 // marked as clobbered. 158 constraints += ",~{r1},~{r2},~{r3}" 159 fnType := llvm.FunctionType(c.uintptrType, argTypes, false) 160 target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0) 161 return c.builder.CreateCall(target, llvmArgs, ""), nil 162 }