inlineasm.go raw
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, llvm.InlineAsmDialectATT, 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": uintptr(unsafe.Pointer(&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 Moxie 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, llvm.InlineAsmDialectATT, 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, llvm.InlineAsmDialectATT, 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, llvm.InlineAsmDialectATT, 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, llvm.InlineAsmDialectATT, 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, llvm.InlineAsmDialectATT, 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, llvm.InlineAsmDialectATT, 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, llvm.InlineAsmDialectATT, 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 }
252
253 // Implement runtime/interrupt.Checkpoint.Save. It needs to be implemented
254 // directly at the call site. If it isn't implemented directly at the call site
255 // (but instead through a function call), it might result in an overwritten
256 // stack in the non-jump return case.
257 func (b *builder) createInterruptCheckpoint(ptr ssa.Value) llvm.Value {
258 addr := b.getValue(ptr, ptr.Pos())
259 b.createNilCheck(ptr, addr, "deref")
260 stackPointer := b.readStackPointer()
261 b.CreateStore(stackPointer, addr)
262 return b.createCheckpoint(addr)
263 }
264