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  }