github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/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  	"strconv"
     8  
     9  	"golang.org/x/tools/go/ssa"
    10  	"tinygo.org/x/go-llvm"
    11  )
    12  
    13  // createRawSyscall creates a system call with the provided system call number
    14  // and returns the result as a single integer (the system call result). The
    15  // result is not further interpreted.
    16  func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
    17  	num := b.getValue(call.Args[0], getPos(call))
    18  	switch {
    19  	case b.GOARCH == "amd64" && b.GOOS == "linux":
    20  		// Sources:
    21  		//   https://stackoverflow.com/a/2538212
    22  		//   https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#syscall
    23  		args := []llvm.Value{num}
    24  		argTypes := []llvm.Type{b.uintptrType}
    25  		// Constraints will look something like:
    26  		//   "={rax},0,{rdi},{rsi},{rdx},{r10},{r8},{r9},~{rcx},~{r11}"
    27  		constraints := "={rax},0"
    28  		for i, arg := range call.Args[1:] {
    29  			constraints += "," + [...]string{
    30  				"{rdi}",
    31  				"{rsi}",
    32  				"{rdx}",
    33  				"{r10}",
    34  				"{r8}",
    35  				"{r9}",
    36  				"{r11}",
    37  				"{r12}",
    38  				"{r13}",
    39  			}[i]
    40  			llvmValue := b.getValue(arg, getPos(call))
    41  			args = append(args, llvmValue)
    42  			argTypes = append(argTypes, llvmValue.Type())
    43  		}
    44  		constraints += ",~{rcx},~{r11}"
    45  		fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
    46  		target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel, false)
    47  		return b.CreateCall(fnType, target, args, ""), nil
    48  	case b.GOARCH == "386" && b.GOOS == "linux":
    49  		// Sources:
    50  		//   syscall(2) man page
    51  		//   https://stackoverflow.com/a/2538212
    52  		//   https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#int_0x80
    53  		args := []llvm.Value{num}
    54  		argTypes := []llvm.Type{b.uintptrType}
    55  		// Constraints will look something like:
    56  		//   "={eax},0,{ebx},{ecx},{edx},{esi},{edi},{ebp}"
    57  		constraints := "={eax},0"
    58  		for i, arg := range call.Args[1:] {
    59  			constraints += "," + [...]string{
    60  				"{ebx}",
    61  				"{ecx}",
    62  				"{edx}",
    63  				"{esi}",
    64  				"{edi}",
    65  				"{ebp}",
    66  			}[i]
    67  			llvmValue := b.getValue(arg, getPos(call))
    68  			args = append(args, llvmValue)
    69  			argTypes = append(argTypes, llvmValue.Type())
    70  		}
    71  		fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
    72  		target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel, false)
    73  		return b.CreateCall(fnType, target, args, ""), nil
    74  	case b.GOARCH == "arm" && b.GOOS == "linux":
    75  		// Implement the EABI system call convention for Linux.
    76  		// Source: syscall(2) man page.
    77  		args := []llvm.Value{}
    78  		argTypes := []llvm.Type{}
    79  		// Constraints will look something like:
    80  		//   ={r0},0,{r1},{r2},{r7},~{r3}
    81  		constraints := "={r0}"
    82  		for i, arg := range call.Args[1:] {
    83  			constraints += "," + [...]string{
    84  				"0", // tie to output
    85  				"{r1}",
    86  				"{r2}",
    87  				"{r3}",
    88  				"{r4}",
    89  				"{r5}",
    90  				"{r6}",
    91  			}[i]
    92  			llvmValue := b.getValue(arg, getPos(call))
    93  			args = append(args, llvmValue)
    94  			argTypes = append(argTypes, llvmValue.Type())
    95  		}
    96  		args = append(args, num)
    97  		argTypes = append(argTypes, b.uintptrType)
    98  		constraints += ",{r7}" // syscall number
    99  		for i := len(call.Args) - 1; i < 4; i++ {
   100  			// r0-r3 get clobbered after the syscall returns
   101  			constraints += ",~{r" + strconv.Itoa(i) + "}"
   102  		}
   103  		fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
   104  		target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0, false)
   105  		return b.CreateCall(fnType, target, args, ""), nil
   106  	case b.GOARCH == "arm64" && b.GOOS == "linux":
   107  		// Source: syscall(2) man page.
   108  		args := []llvm.Value{}
   109  		argTypes := []llvm.Type{}
   110  		// Constraints will look something like:
   111  		//   ={x0},0,{x1},{x2},{x8},~{x3},~{x4},~{x5},~{x6},~{x7},~{x16},~{x17}
   112  		constraints := "={x0}"
   113  		for i, arg := range call.Args[1:] {
   114  			constraints += "," + [...]string{
   115  				"0", // tie to output
   116  				"{x1}",
   117  				"{x2}",
   118  				"{x3}",
   119  				"{x4}",
   120  				"{x5}",
   121  			}[i]
   122  			llvmValue := b.getValue(arg, getPos(call))
   123  			args = append(args, llvmValue)
   124  			argTypes = append(argTypes, llvmValue.Type())
   125  		}
   126  		args = append(args, num)
   127  		argTypes = append(argTypes, b.uintptrType)
   128  		constraints += ",{x8}" // syscall number
   129  		for i := len(call.Args) - 1; i < 8; i++ {
   130  			// x0-x7 may get clobbered during the syscall following the aarch64
   131  			// calling convention.
   132  			constraints += ",~{x" + strconv.Itoa(i) + "}"
   133  		}
   134  		constraints += ",~{x16},~{x17}" // scratch registers
   135  		fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
   136  		target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0, false)
   137  		return b.CreateCall(fnType, target, args, ""), nil
   138  	default:
   139  		return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH)
   140  	}
   141  }
   142  
   143  // createSyscall emits instructions for the syscall.Syscall* family of
   144  // functions, depending on the target OS/arch.
   145  func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
   146  	switch b.GOOS {
   147  	case "linux":
   148  		syscallResult, err := b.createRawSyscall(call)
   149  		if err != nil {
   150  			return syscallResult, err
   151  		}
   152  		// Return values: r0, r1 uintptr, err Errno
   153  		// Pseudocode:
   154  		//     var err uintptr
   155  		//     if syscallResult < 0 && syscallResult > -4096 {
   156  		//         err = -syscallResult
   157  		//     }
   158  		//     return syscallResult, 0, err
   159  		zero := llvm.ConstInt(b.uintptrType, 0, false)
   160  		inrange1 := b.CreateICmp(llvm.IntSLT, syscallResult, llvm.ConstInt(b.uintptrType, 0, false), "")
   161  		inrange2 := b.CreateICmp(llvm.IntSGT, syscallResult, llvm.ConstInt(b.uintptrType, 0xfffffffffffff000, true), "") // -4096
   162  		hasError := b.CreateAnd(inrange1, inrange2, "")
   163  		errResult := b.CreateSelect(hasError, b.CreateSub(zero, syscallResult, ""), zero, "syscallError")
   164  		retval := llvm.Undef(b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType, b.uintptrType}, false))
   165  		retval = b.CreateInsertValue(retval, syscallResult, 0, "")
   166  		retval = b.CreateInsertValue(retval, zero, 1, "")
   167  		retval = b.CreateInsertValue(retval, errResult, 2, "")
   168  		return retval, nil
   169  	case "windows":
   170  		// On Windows, syscall.Syscall* is basically just a function pointer
   171  		// call. This is complicated in gc because of stack switching and the
   172  		// different ABI, but easy in TinyGo: just call the function pointer.
   173  		// The signature looks like this:
   174  		//   func Syscall(trap, nargs, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)
   175  
   176  		// Prepare input values.
   177  		var paramTypes []llvm.Type
   178  		var params []llvm.Value
   179  		for _, val := range call.Args[2:] {
   180  			param := b.getValue(val, getPos(call))
   181  			params = append(params, param)
   182  			paramTypes = append(paramTypes, param.Type())
   183  		}
   184  		llvmType := llvm.FunctionType(b.uintptrType, paramTypes, false)
   185  		fn := b.getValue(call.Args[0], getPos(call))
   186  		fnPtr := b.CreateIntToPtr(fn, b.dataPtrType, "")
   187  
   188  		// Prepare some functions that will be called later.
   189  		setLastError := b.mod.NamedFunction("SetLastError")
   190  		if setLastError.IsNil() {
   191  			llvmType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.ctx.Int32Type()}, false)
   192  			setLastError = llvm.AddFunction(b.mod, "SetLastError", llvmType)
   193  		}
   194  		getLastError := b.mod.NamedFunction("GetLastError")
   195  		if getLastError.IsNil() {
   196  			llvmType := llvm.FunctionType(b.ctx.Int32Type(), nil, false)
   197  			getLastError = llvm.AddFunction(b.mod, "GetLastError", llvmType)
   198  		}
   199  
   200  		// Now do the actual call. Pseudocode:
   201  		//     SetLastError(0)
   202  		//     r1 = trap(a1, a2, a3, ...)
   203  		//     err = uintptr(GetLastError())
   204  		//     return r1, 0, err
   205  		// Note that SetLastError/GetLastError could be replaced with direct
   206  		// access to the thread control block, which is probably smaller and
   207  		// faster. The Go runtime does this in assembly.
   208  		b.CreateCall(setLastError.GlobalValueType(), setLastError, []llvm.Value{llvm.ConstNull(b.ctx.Int32Type())}, "")
   209  		syscallResult := b.CreateCall(llvmType, fnPtr, params, "")
   210  		errResult := b.CreateCall(getLastError.GlobalValueType(), getLastError, nil, "err")
   211  		if b.uintptrType != b.ctx.Int32Type() {
   212  			errResult = b.CreateZExt(errResult, b.uintptrType, "err.uintptr")
   213  		}
   214  
   215  		// Return r1, 0, err
   216  		retval := llvm.ConstNull(b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType, b.uintptrType}, false))
   217  		retval = b.CreateInsertValue(retval, syscallResult, 0, "")
   218  		retval = b.CreateInsertValue(retval, errResult, 2, "")
   219  		return retval, nil
   220  	default:
   221  		return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH)
   222  	}
   223  }
   224  
   225  // createRawSyscallNoError emits instructions for the Linux-specific
   226  // syscall.rawSyscallNoError function.
   227  func (b *builder) createRawSyscallNoError(call *ssa.CallCommon) (llvm.Value, error) {
   228  	syscallResult, err := b.createRawSyscall(call)
   229  	if err != nil {
   230  		return syscallResult, err
   231  	}
   232  	retval := llvm.ConstNull(b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType}, false))
   233  	retval = b.CreateInsertValue(retval, syscallResult, 0, "")
   234  	retval = b.CreateInsertValue(retval, llvm.ConstInt(b.uintptrType, 0, false), 1, "")
   235  	return retval, nil
   236  }