github.com/aykevl/tinygo@v0.5.0/compiler/syscall.go (about)

     1  package compiler
     2  
     3  // This file implements the syscall.Syscall and syscall.Syscall6 instructions as
     4  // compiler builtins.
     5  
     6  import (
     7  	"go/constant"
     8  	"strconv"
     9  
    10  	"golang.org/x/tools/go/ssa"
    11  	"tinygo.org/x/go-llvm"
    12  )
    13  
    14  // emitSyscall emits an inline system call instruction, depending on the target
    15  // OS/arch.
    16  func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, error) {
    17  	num, _ := constant.Uint64Val(call.Args[0].(*ssa.Const).Value)
    18  	var syscallResult llvm.Value
    19  	switch {
    20  	case c.GOARCH == "amd64":
    21  		if c.GOOS == "darwin" {
    22  			// Darwin adds this magic number to system call numbers:
    23  			//
    24  			// > Syscall classes for 64-bit system call entry.
    25  			// > For 64-bit users, the 32-bit syscall number is partitioned
    26  			// > with the high-order bits representing the class and low-order
    27  			// > bits being the syscall number within that class.
    28  			// > The high-order 32-bits of the 64-bit syscall number are unused.
    29  			// > All system classes enter the kernel via the syscall instruction.
    30  			//
    31  			// Source: https://opensource.apple.com/source/xnu/xnu-792.13.8/osfmk/mach/i386/syscall_sw.h
    32  			num += 0x2000000
    33  		}
    34  		// Sources:
    35  		//   https://stackoverflow.com/a/2538212
    36  		//   https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#syscall
    37  		args := []llvm.Value{llvm.ConstInt(c.uintptrType, num, false)}
    38  		argTypes := []llvm.Type{c.uintptrType}
    39  		// Constraints will look something like:
    40  		//   "={rax},0,{rdi},{rsi},{rdx},{r10},{r8},{r9},~{rcx},~{r11}"
    41  		constraints := "={rax},0"
    42  		for i, arg := range call.Args[1:] {
    43  			constraints += "," + [...]string{
    44  				"{rdi}",
    45  				"{rsi}",
    46  				"{rdx}",
    47  				"{r10}",
    48  				"{r8}",
    49  				"{r9}",
    50  				"{r11}",
    51  				"{r12}",
    52  				"{r13}",
    53  			}[i]
    54  			llvmValue, err := c.parseExpr(frame, arg)
    55  			if err != nil {
    56  				return llvm.Value{}, err
    57  			}
    58  			args = append(args, llvmValue)
    59  			argTypes = append(argTypes, llvmValue.Type())
    60  		}
    61  		constraints += ",~{rcx},~{r11}"
    62  		fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
    63  		target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel)
    64  		syscallResult = c.builder.CreateCall(target, args, "")
    65  	case c.GOARCH == "arm" && c.GOOS == "linux":
    66  		// Implement the EABI system call convention for Linux.
    67  		// Source: syscall(2) man page.
    68  		args := []llvm.Value{}
    69  		argTypes := []llvm.Type{}
    70  		// Constraints will look something like:
    71  		//   ={r0},0,{r1},{r2},{r7},~{r3}
    72  		constraints := "={r0}"
    73  		for i, arg := range call.Args[1:] {
    74  			constraints += "," + [...]string{
    75  				"0", // tie to output
    76  				"{r1}",
    77  				"{r2}",
    78  				"{r3}",
    79  				"{r4}",
    80  				"{r5}",
    81  				"{r6}",
    82  			}[i]
    83  			llvmValue, err := c.parseExpr(frame, arg)
    84  			if err != nil {
    85  				return llvm.Value{}, err
    86  			}
    87  			args = append(args, llvmValue)
    88  			argTypes = append(argTypes, llvmValue.Type())
    89  		}
    90  		args = append(args, llvm.ConstInt(c.uintptrType, num, false))
    91  		argTypes = append(argTypes, c.uintptrType)
    92  		constraints += ",{r7}" // syscall number
    93  		for i := len(call.Args) - 1; i < 4; i++ {
    94  			// r0-r3 get clobbered after the syscall returns
    95  			constraints += ",~{r" + strconv.Itoa(i) + "}"
    96  		}
    97  		fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
    98  		target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
    99  		syscallResult = c.builder.CreateCall(target, args, "")
   100  	case c.GOARCH == "arm64" && c.GOOS == "linux":
   101  		// Source: syscall(2) man page.
   102  		args := []llvm.Value{}
   103  		argTypes := []llvm.Type{}
   104  		// Constraints will look something like:
   105  		//   ={x0},0,{x1},{x2},{x8},~{x3},~{x4},~{x5},~{x6},~{x7},~{x16},~{x17}
   106  		constraints := "={x0}"
   107  		for i, arg := range call.Args[1:] {
   108  			constraints += "," + [...]string{
   109  				"0", // tie to output
   110  				"{x1}",
   111  				"{x2}",
   112  				"{x3}",
   113  				"{x4}",
   114  				"{x5}",
   115  			}[i]
   116  			llvmValue, err := c.parseExpr(frame, arg)
   117  			if err != nil {
   118  				return llvm.Value{}, err
   119  			}
   120  			args = append(args, llvmValue)
   121  			argTypes = append(argTypes, llvmValue.Type())
   122  		}
   123  		args = append(args, llvm.ConstInt(c.uintptrType, num, false))
   124  		argTypes = append(argTypes, c.uintptrType)
   125  		constraints += ",{x8}" // syscall number
   126  		for i := len(call.Args) - 1; i < 8; i++ {
   127  			// x0-x7 may get clobbered during the syscall following the aarch64
   128  			// calling convention.
   129  			constraints += ",~{x" + strconv.Itoa(i) + "}"
   130  		}
   131  		constraints += ",~{x16},~{x17}" // scratch registers
   132  		fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
   133  		target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
   134  		syscallResult = c.builder.CreateCall(target, args, "")
   135  	default:
   136  		return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS+"/"+c.GOARCH)
   137  	}
   138  	switch c.GOOS {
   139  	case "linux":
   140  		// Return values: r0, r1 uintptr, err Errno
   141  		// Pseudocode:
   142  		//     var err uintptr
   143  		//     if syscallResult < 0 && syscallResult > -4096 {
   144  		//         err = -syscallResult
   145  		//     }
   146  		//     return syscallResult, 0, err
   147  		zero := llvm.ConstInt(c.uintptrType, 0, false)
   148  		inrange1 := c.builder.CreateICmp(llvm.IntSLT, syscallResult, llvm.ConstInt(c.uintptrType, 0, false), "")
   149  		inrange2 := c.builder.CreateICmp(llvm.IntSGT, syscallResult, llvm.ConstInt(c.uintptrType, 0xfffffffffffff000, true), "") // -4096
   150  		hasError := c.builder.CreateAnd(inrange1, inrange2, "")
   151  		errResult := c.builder.CreateSelect(hasError, c.builder.CreateSub(zero, syscallResult, ""), zero, "syscallError")
   152  		retval := llvm.Undef(llvm.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
   153  		retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
   154  		retval = c.builder.CreateInsertValue(retval, zero, 1, "")
   155  		retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
   156  		return retval, nil
   157  	case "darwin":
   158  		// Return values: r0, r1 uintptr, err Errno
   159  		// Pseudocode:
   160  		//     var err uintptr
   161  		//     if syscallResult != 0 {
   162  		//         err = syscallResult
   163  		//     }
   164  		//     return syscallResult, 0, err
   165  		zero := llvm.ConstInt(c.uintptrType, 0, false)
   166  		hasError := c.builder.CreateICmp(llvm.IntNE, syscallResult, llvm.ConstInt(c.uintptrType, 0, false), "")
   167  		errResult := c.builder.CreateSelect(hasError, syscallResult, zero, "syscallError")
   168  		retval := llvm.Undef(llvm.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
   169  		retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
   170  		retval = c.builder.CreateInsertValue(retval, zero, 1, "")
   171  		retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
   172  		return retval, nil
   173  	default:
   174  		return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS+"/"+c.GOARCH)
   175  	}
   176  }