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