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  }