github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/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 "go/token" 9 "regexp" 10 "strconv" 11 "strings" 12 13 "golang.org/x/tools/go/ssa" 14 "tinygo.org/x/go-llvm" 15 ) 16 17 // This is a compiler builtin, which emits a piece of inline assembly with no 18 // operands or return values. It is useful for trivial instructions, like wfi in 19 // ARM or sleep in AVR. 20 // 21 // func Asm(asm string) 22 // 23 // The provided assembly must be a constant. 24 func (b *builder) createInlineAsm(args []ssa.Value) (llvm.Value, error) { 25 // Magic function: insert inline assembly instead of calling it. 26 fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{}, false) 27 asm := constant.StringVal(args[0].(*ssa.Const).Value) 28 target := llvm.InlineAsm(fnType, asm, "", true, false, 0, false) 29 return b.CreateCall(fnType, target, nil, ""), nil 30 } 31 32 // This is a compiler builtin, which allows assembly to be called in a flexible 33 // way. 34 // 35 // func AsmFull(asm string, regs map[string]interface{}) uintptr 36 // 37 // The asm parameter must be a constant string. The regs parameter must be 38 // provided immediately. For example: 39 // 40 // arm.AsmFull( 41 // "str {value}, {result}", 42 // map[string]interface{}{ 43 // "value": 1 44 // "result": &dest, 45 // }) 46 func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error) { 47 asmString := constant.StringVal(instr.Args[0].(*ssa.Const).Value) 48 registers := map[string]llvm.Value{} 49 if registerMap, ok := instr.Args[1].(*ssa.MakeMap); ok { 50 for _, r := range *registerMap.Referrers() { 51 switch r := r.(type) { 52 case *ssa.DebugRef: 53 // ignore 54 case *ssa.MapUpdate: 55 if r.Block() != registerMap.Block() { 56 return llvm.Value{}, b.makeError(instr.Pos(), "register value map must be created in the same basic block") 57 } 58 key := constant.StringVal(r.Key.(*ssa.Const).Value) 59 registers[key] = b.getValue(r.Value.(*ssa.MakeInterface).X, getPos(instr)) 60 case *ssa.Call: 61 if r.Common() == instr { 62 break 63 } 64 default: 65 return llvm.Value{}, b.makeError(instr.Pos(), "don't know how to handle argument to inline assembly: "+r.String()) 66 } 67 } 68 } 69 // TODO: handle dollar signs in asm string 70 registerNumbers := map[string]int{} 71 var err error 72 argTypes := []llvm.Type{} 73 args := []llvm.Value{} 74 constraints := []string{} 75 hasOutput := false 76 asmString = regexp.MustCompile(`\{\}`).ReplaceAllStringFunc(asmString, func(s string) string { 77 hasOutput = true 78 return "$0" 79 }) 80 if hasOutput { 81 constraints = append(constraints, "=&r") 82 registerNumbers[""] = 0 83 } 84 asmString = regexp.MustCompile(`\{[a-zA-Z]+\}`).ReplaceAllStringFunc(asmString, func(s string) string { 85 // TODO: skip strings like {r4} etc. that look like ARM push/pop 86 // instructions. 87 name := s[1 : len(s)-1] 88 if _, ok := registers[name]; !ok { 89 if err == nil { 90 err = b.makeError(instr.Pos(), "unknown register name: "+name) 91 } 92 return s 93 } 94 if _, ok := registerNumbers[name]; !ok { 95 registerNumbers[name] = len(registerNumbers) 96 argTypes = append(argTypes, registers[name].Type()) 97 args = append(args, registers[name]) 98 switch registers[name].Type().TypeKind() { 99 case llvm.IntegerTypeKind: 100 constraints = append(constraints, "r") 101 case llvm.PointerTypeKind: 102 // Memory references require a type starting with LLVM 14, 103 // probably as a preparation for opaque pointers. 104 err = b.makeError(instr.Pos(), "support for pointer operands was dropped in TinyGo 0.23") 105 return s 106 default: 107 err = b.makeError(instr.Pos(), "unknown type in inline assembly for value: "+name) 108 return s 109 } 110 } 111 return fmt.Sprintf("${%v}", registerNumbers[name]) 112 }) 113 if err != nil { 114 return llvm.Value{}, err 115 } 116 var outputType llvm.Type 117 if hasOutput { 118 outputType = b.uintptrType 119 } else { 120 outputType = b.ctx.VoidType() 121 } 122 fnType := llvm.FunctionType(outputType, argTypes, false) 123 target := llvm.InlineAsm(fnType, asmString, strings.Join(constraints, ","), true, false, 0, false) 124 result := b.CreateCall(fnType, target, args, "") 125 if hasOutput { 126 return result, nil 127 } else { 128 // Make sure we return something valid. 129 return llvm.ConstInt(b.uintptrType, 0, false), nil 130 } 131 } 132 133 // This is a compiler builtin which emits an inline SVCall instruction. It can 134 // be one of: 135 // 136 // func SVCall0(num uintptr) uintptr 137 // func SVCall1(num uintptr, a1 interface{}) uintptr 138 // func SVCall2(num uintptr, a1, a2 interface{}) uintptr 139 // func SVCall3(num uintptr, a1, a2, a3 interface{}) uintptr 140 // func SVCall4(num uintptr, a1, a2, a3, a4 interface{}) uintptr 141 // 142 // The num parameter must be a constant. All other parameters may be any scalar 143 // value supported by LLVM inline assembly. 144 func (b *builder) emitSVCall(args []ssa.Value, pos token.Pos) (llvm.Value, error) { 145 num, _ := constant.Uint64Val(args[0].(*ssa.Const).Value) 146 llvmArgs := []llvm.Value{} 147 argTypes := []llvm.Type{} 148 asm := "svc #" + strconv.FormatUint(num, 10) 149 constraints := "={r0}" 150 for i, arg := range args[1:] { 151 arg = arg.(*ssa.MakeInterface).X 152 if i == 0 { 153 constraints += ",0" 154 } else { 155 constraints += ",{r" + strconv.Itoa(i) + "}" 156 } 157 llvmValue := b.getValue(arg, pos) 158 llvmArgs = append(llvmArgs, llvmValue) 159 argTypes = append(argTypes, llvmValue.Type()) 160 } 161 // Implement the ARM calling convention by marking r1-r3 as 162 // clobbered. r0 is used as an output register so doesn't have to be 163 // marked as clobbered. 164 constraints += ",~{r1},~{r2},~{r3}" 165 fnType := llvm.FunctionType(b.uintptrType, argTypes, false) 166 target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0, false) 167 return b.CreateCall(fnType, target, llvmArgs, ""), nil 168 } 169 170 // This is a compiler builtin which emits an inline SVCall instruction. It can 171 // be one of: 172 // 173 // func SVCall0(num uintptr) uintptr 174 // func SVCall1(num uintptr, a1 interface{}) uintptr 175 // func SVCall2(num uintptr, a1, a2 interface{}) uintptr 176 // func SVCall3(num uintptr, a1, a2, a3 interface{}) uintptr 177 // func SVCall4(num uintptr, a1, a2, a3, a4 interface{}) uintptr 178 // 179 // The num parameter must be a constant. All other parameters may be any scalar 180 // value supported by LLVM inline assembly. 181 // Same as emitSVCall but for AArch64 182 func (b *builder) emitSV64Call(args []ssa.Value, pos token.Pos) (llvm.Value, error) { 183 num, _ := constant.Uint64Val(args[0].(*ssa.Const).Value) 184 llvmArgs := []llvm.Value{} 185 argTypes := []llvm.Type{} 186 asm := "svc #" + strconv.FormatUint(num, 10) 187 constraints := "={x0}" 188 for i, arg := range args[1:] { 189 arg = arg.(*ssa.MakeInterface).X 190 if i == 0 { 191 constraints += ",0" 192 } else { 193 constraints += ",{x" + strconv.Itoa(i) + "}" 194 } 195 llvmValue := b.getValue(arg, pos) 196 llvmArgs = append(llvmArgs, llvmValue) 197 argTypes = append(argTypes, llvmValue.Type()) 198 } 199 // Implement the ARM64 calling convention by marking x1-x7 as 200 // clobbered. x0 is used as an output register so doesn't have to be 201 // marked as clobbered. 202 constraints += ",~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7}" 203 fnType := llvm.FunctionType(b.uintptrType, argTypes, false) 204 target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0, false) 205 return b.CreateCall(fnType, target, llvmArgs, ""), nil 206 } 207 208 // This is a compiler builtin which emits CSR instructions. It can be one of: 209 // 210 // func (csr CSR) Get() uintptr 211 // func (csr CSR) Set(uintptr) 212 // func (csr CSR) SetBits(uintptr) uintptr 213 // func (csr CSR) ClearBits(uintptr) uintptr 214 // 215 // The csr parameter (method receiver) must be a constant. Other parameter can 216 // be any value. 217 func (b *builder) emitCSROperation(call *ssa.CallCommon) (llvm.Value, error) { 218 csrConst, ok := call.Args[0].(*ssa.Const) 219 if !ok { 220 return llvm.Value{}, b.makeError(call.Pos(), "CSR must be constant") 221 } 222 csr := csrConst.Uint64() 223 switch name := call.StaticCallee().Name(); name { 224 case "Get": 225 // Note that this instruction may have side effects, and thus must be 226 // marked as such. 227 fnType := llvm.FunctionType(b.uintptrType, nil, false) 228 asm := fmt.Sprintf("csrr $0, %d", csr) 229 target := llvm.InlineAsm(fnType, asm, "=r", true, false, 0, false) 230 return b.CreateCall(fnType, target, nil, ""), nil 231 case "Set": 232 fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.uintptrType}, false) 233 asm := fmt.Sprintf("csrw %d, $0", csr) 234 target := llvm.InlineAsm(fnType, asm, "r", true, false, 0, false) 235 return b.CreateCall(fnType, target, []llvm.Value{b.getValue(call.Args[1], getPos(call))}, ""), nil 236 case "SetBits": 237 // Note: it may be possible to optimize this to csrrsi in many cases. 238 fnType := llvm.FunctionType(b.uintptrType, []llvm.Type{b.uintptrType}, false) 239 asm := fmt.Sprintf("csrrs $0, %d, $1", csr) 240 target := llvm.InlineAsm(fnType, asm, "=r,r", true, false, 0, false) 241 return b.CreateCall(fnType, target, []llvm.Value{b.getValue(call.Args[1], getPos(call))}, ""), nil 242 case "ClearBits": 243 // Note: it may be possible to optimize this to csrrci in many cases. 244 fnType := llvm.FunctionType(b.uintptrType, []llvm.Type{b.uintptrType}, false) 245 asm := fmt.Sprintf("csrrc $0, %d, $1", csr) 246 target := llvm.InlineAsm(fnType, asm, "=r,r", true, false, 0, false) 247 return b.CreateCall(fnType, target, []llvm.Value{b.getValue(call.Args[1], getPos(call))}, ""), nil 248 default: 249 return llvm.Value{}, b.makeError(call.Pos(), "unknown CSR operation: "+name) 250 } 251 }