syscall.go raw

   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  	"strings"
   9  
  10  	"golang.org/x/tools/go/ssa"
  11  	"tinygo.org/x/go-llvm"
  12  )
  13  
  14  // createRawSyscall creates a system call with the provided system call number
  15  // and returns the result as a single integer (the system call result). The
  16  // result is not further interpreted (with the exception of MIPS to use the same
  17  // return value everywhere).
  18  func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
  19  	num := b.getValue(call.Args[0], getPos(call))
  20  	switch {
  21  	case b.GOARCH == "amd64" && b.GOOS == "linux":
  22  		// Sources:
  23  		//   https://stackoverflow.com/a/2538212
  24  		//   https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#syscall
  25  		args := []llvm.Value{num}
  26  		argTypes := []llvm.Type{b.uintptrType}
  27  		// Constraints will look something like:
  28  		//   "={rax},0,{rdi},{rsi},{rdx},{r10},{r8},{r9},~{rcx},~{r11}"
  29  		constraints := "={rax},0"
  30  		for i, arg := range call.Args[1:] {
  31  			constraints += "," + [...]string{
  32  				"{rdi}",
  33  				"{rsi}",
  34  				"{rdx}",
  35  				"{r10}",
  36  				"{r8}",
  37  				"{r9}",
  38  			}[i]
  39  			llvmValue := b.getValue(arg, getPos(call))
  40  			args = append(args, llvmValue)
  41  			argTypes = append(argTypes, llvmValue.Type())
  42  		}
  43  		// rcx and r11 are clobbered by the syscall, so make sure they are not used
  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  
  49  	case b.GOARCH == "386" && b.GOOS == "linux":
  50  		// Sources:
  51  		//   syscall(2) man page
  52  		//   https://stackoverflow.com/a/2538212
  53  		//   https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#int_0x80
  54  		args := []llvm.Value{num}
  55  		argTypes := []llvm.Type{b.uintptrType}
  56  		// Constraints will look something like:
  57  		//   "={eax},0,{ebx},{ecx},{edx},{esi},{edi},{ebp}"
  58  		constraints := "={eax},0"
  59  		for i, arg := range call.Args[1:] {
  60  			constraints += "," + [...]string{
  61  				"{ebx}",
  62  				"{ecx}",
  63  				"{edx}",
  64  				"{esi}",
  65  				"{edi}",
  66  				"{ebp}",
  67  			}[i]
  68  			llvmValue := b.getValue(arg, getPos(call))
  69  			args = append(args, llvmValue)
  70  			argTypes = append(argTypes, llvmValue.Type())
  71  		}
  72  		fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
  73  		target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel, false)
  74  		return b.CreateCall(fnType, target, args, ""), nil
  75  
  76  	case b.GOARCH == "arm" && b.GOOS == "linux":
  77  		if arch := b.archFamily(); arch != "arm" {
  78  			// Some targets pretend to be linux/arm for compatibility but aren't
  79  			// actually such a system. Make sure we emit an error instead of
  80  			// creating inline assembly that will fail to compile.
  81  			// See: https://moxie/issues/4959
  82  			return llvm.Value{}, b.makeError(call.Pos(), "system calls are not supported: target emulates a linux/arm system on "+arch)
  83  		}
  84  
  85  		// Implement the EABI system call convention for Linux.
  86  		// Source: syscall(2) man page.
  87  		args := []llvm.Value{}
  88  		argTypes := []llvm.Type{}
  89  		// Constraints will look something like:
  90  		//   ={r0},0,{r1},{r2},{r7},~{r3}
  91  		constraints := "={r0}"
  92  		for i, arg := range call.Args[1:] {
  93  			constraints += "," + [...]string{
  94  				"0", // tie to output
  95  				"{r1}",
  96  				"{r2}",
  97  				"{r3}",
  98  				"{r4}",
  99  				"{r5}",
 100  				"{r6}",
 101  			}[i]
 102  			llvmValue := b.getValue(arg, getPos(call))
 103  			args = append(args, llvmValue)
 104  			argTypes = append(argTypes, llvmValue.Type())
 105  		}
 106  		args = append(args, num)
 107  		argTypes = append(argTypes, b.uintptrType)
 108  		constraints += ",{r7}" // syscall number
 109  		for i := len(call.Args) - 1; i < 4; i++ {
 110  			// r0-r3 get clobbered after the syscall returns
 111  			constraints += ",~{r" + strconv.Itoa(i) + "}"
 112  		}
 113  		fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
 114  		target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0, false)
 115  		return b.CreateCall(fnType, target, args, ""), nil
 116  
 117  	case b.GOARCH == "arm64" && b.GOOS == "linux":
 118  		// Source: syscall(2) man page.
 119  		args := []llvm.Value{}
 120  		argTypes := []llvm.Type{}
 121  		// Constraints will look something like:
 122  		//   ={x0},0,{x1},{x2},{x8},~{x3},~{x4},~{x5},~{x6},~{x7},~{x16},~{x17}
 123  		constraints := "={x0}"
 124  		for i, arg := range call.Args[1:] {
 125  			constraints += "," + [...]string{
 126  				"0", // tie to output
 127  				"{x1}",
 128  				"{x2}",
 129  				"{x3}",
 130  				"{x4}",
 131  				"{x5}",
 132  			}[i]
 133  			llvmValue := b.getValue(arg, getPos(call))
 134  			args = append(args, llvmValue)
 135  			argTypes = append(argTypes, llvmValue.Type())
 136  		}
 137  		args = append(args, num)
 138  		argTypes = append(argTypes, b.uintptrType)
 139  		constraints += ",{x8}" // syscall number
 140  		for i := len(call.Args) - 1; i < 8; i++ {
 141  			// x0-x7 may get clobbered during the syscall following the aarch64
 142  			// calling convention.
 143  			constraints += ",~{x" + strconv.Itoa(i) + "}"
 144  		}
 145  		constraints += ",~{x16},~{x17}" // scratch registers
 146  		fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
 147  		target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0, false)
 148  		return b.CreateCall(fnType, target, args, ""), nil
 149  
 150  	case (b.GOARCH == "mips" || b.GOARCH == "mipsle") && b.GOOS == "linux":
 151  		// Implement the system call convention for Linux.
 152  		// Source: syscall(2) man page and musl:
 153  		// https://git.musl-libc.org/cgit/musl/tree/arch/mips/syscall_arch.h
 154  		// Also useful:
 155  		// https://web.archive.org/web/20220529105937/https://www.linux-mips.org/wiki/Syscall
 156  		// The syscall number goes in r2, the result also in r2.
 157  		// Register r7 is both an input parameter and an output parameter: if it
 158  		// is non-zero, the system call failed and r2 is the error code.
 159  		// The code below implements the O32 syscall ABI, not the N32 ABI. It
 160  		// could implement both at the same time if needed (like what appears to
 161  		// be done in musl) by forcing arg5-arg7 into the right registers but
 162  		// letting the compiler decide the registers should result in _slightly_
 163  		// faster and smaller code.
 164  		args := []llvm.Value{num}
 165  		argTypes := []llvm.Type{b.uintptrType}
 166  		constraints := "={$2},={$7},0"
 167  		syscallParams := call.Args[1:]
 168  		if len(syscallParams) > 7 {
 169  			// There is one syscall that uses 7 parameters: sync_file_range.
 170  			// But only 7, not more. Go however only has Syscall6 and Syscall9.
 171  			// Therefore, we can ignore the remaining parameters.
 172  			syscallParams = syscallParams[:7]
 173  		}
 174  		for i, arg := range syscallParams {
 175  			constraints += "," + [...]string{
 176  				"{$4}", // arg1
 177  				"{$5}", // arg2
 178  				"{$6}", // arg3
 179  				"1",    // arg4, error return
 180  				"r",    // arg5 on the stack
 181  				"r",    // arg6 on the stack
 182  				"r",    // arg7 on the stack
 183  			}[i]
 184  			llvmValue := b.getValue(arg, getPos(call))
 185  			args = append(args, llvmValue)
 186  			argTypes = append(argTypes, llvmValue.Type())
 187  		}
 188  		// Create assembly code.
 189  		// Parameters beyond the first 4 are passed on the stack instead of in
 190  		// registers in the O32 syscall ABI.
 191  		// We need ".set noat" because LLVM might pick register $1 ($at) as the
 192  		// register for a parameter and apparently this is not allowed on MIPS
 193  		// unless you use this specific pragma.
 194  		asm := "syscall"
 195  		switch len(syscallParams) {
 196  		case 5:
 197  			asm = "" +
 198  				".set noat\n" +
 199  				"subu $$sp, $$sp, 32\n" +
 200  				"sw $7, 16($$sp)\n" + // arg5
 201  				"syscall\n" +
 202  				"addu $$sp, $$sp, 32\n" +
 203  				".set at\n"
 204  		case 6:
 205  			asm = "" +
 206  				".set noat\n" +
 207  				"subu $$sp, $$sp, 32\n" +
 208  				"sw $7, 16($$sp)\n" + // arg5
 209  				"sw $8, 20($$sp)\n" + // arg6
 210  				"syscall\n" +
 211  				"addu $$sp, $$sp, 32\n" +
 212  				".set at\n"
 213  		case 7:
 214  			asm = "" +
 215  				".set noat\n" +
 216  				"subu $$sp, $$sp, 32\n" +
 217  				"sw $7, 16($$sp)\n" + // arg5
 218  				"sw $8, 20($$sp)\n" + // arg6
 219  				"sw $9, 24($$sp)\n" + // arg7
 220  				"syscall\n" +
 221  				"addu $$sp, $$sp, 32\n" +
 222  				".set at\n"
 223  		}
 224  		constraints += ",~{$3},~{$4},~{$5},~{$6},~{$8},~{$9},~{$10},~{$11},~{$12},~{$13},~{$14},~{$15},~{$24},~{$25},~{hi},~{lo},~{memory}"
 225  		returnType := b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType}, false)
 226  		fnType := llvm.FunctionType(returnType, argTypes, false)
 227  		target := llvm.InlineAsm(fnType, asm, constraints, true, true, 0, false)
 228  		call := b.CreateCall(fnType, target, args, "")
 229  		resultCode := b.CreateExtractValue(call, 0, "") // r2
 230  		errorFlag := b.CreateExtractValue(call, 1, "")  // r7
 231  		// Pseudocode to return the result with the same convention as other
 232  		// archs:
 233  		//    return (errorFlag != 0) ? -resultCode : resultCode;
 234  		// At least on QEMU with the O32 ABI, the error code is always positive.
 235  		zero := llvm.ConstInt(b.uintptrType, 0, false)
 236  		isError := b.CreateICmp(llvm.IntNE, errorFlag, zero, "")
 237  		negativeResult := b.CreateSub(zero, resultCode, "")
 238  		result := b.CreateSelect(isError, negativeResult, resultCode, "")
 239  		return result, nil
 240  
 241  	default:
 242  		return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH)
 243  	}
 244  }
 245  
 246  // createSyscall emits instructions for the syscall.Syscall* family of
 247  // functions, depending on the target OS/arch.
 248  func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
 249  	switch b.GOOS {
 250  	case "linux":
 251  		syscallResult, err := b.createRawSyscall(call)
 252  		if err != nil {
 253  			return syscallResult, err
 254  		}
 255  		// Return values: r0, r1 uintptr, err Errno
 256  		// Pseudocode:
 257  		//     var err uintptr
 258  		//     if syscallResult < 0 && syscallResult > -4096 {
 259  		//         err = -syscallResult
 260  		//     }
 261  		//     return syscallResult, 0, err
 262  		zero := llvm.ConstInt(b.uintptrType, 0, false)
 263  		inrange1 := b.CreateICmp(llvm.IntSLT, syscallResult, llvm.ConstInt(b.uintptrType, 0, false), "")
 264  		inrange2 := b.CreateICmp(llvm.IntSGT, syscallResult, llvm.ConstInt(b.uintptrType, 0xfffffffffffff000, true), "") // -4096
 265  		hasError := b.CreateAnd(inrange1, inrange2, "")
 266  		errResult := b.CreateSelect(hasError, b.CreateSub(zero, syscallResult, ""), zero, "syscallError")
 267  		retval := llvm.Undef(b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType, b.uintptrType}, false))
 268  		retval = b.CreateInsertValue(retval, syscallResult, 0, "")
 269  		retval = b.CreateInsertValue(retval, zero, 1, "")
 270  		retval = b.CreateInsertValue(retval, errResult, 2, "")
 271  		return retval, nil
 272  	case "windows":
 273  		// On Windows, syscall.Syscall* is basically just a function pointer
 274  		// call. This is complicated in gc because of stack switching and the
 275  		// different ABI, but easy in Moxie: just call the function pointer.
 276  		// The signature looks like this:
 277  		//   func Syscall(trap, nargs, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)
 278  
 279  		isI386 := strings.HasPrefix(b.Triple, "i386-")
 280  
 281  		// Prepare input values.
 282  		var paramTypes []llvm.Type
 283  		var params []llvm.Value
 284  		for _, val := range call.Args[2:] {
 285  			param := b.getValue(val, getPos(call))
 286  			params = append(params, param)
 287  			paramTypes = append(paramTypes, param.Type())
 288  		}
 289  		llvmType := llvm.FunctionType(b.uintptrType, paramTypes, false)
 290  		fn := b.getValue(call.Args[0], getPos(call))
 291  		fnPtr := b.CreateIntToPtr(fn, b.dataPtrType, "")
 292  
 293  		// Prepare some functions that will be called later.
 294  		setLastError := b.mod.NamedFunction("SetLastError")
 295  		if setLastError.IsNil() {
 296  			llvmType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.ctx.Int32Type()}, false)
 297  			setLastError = llvm.AddFunction(b.mod, "SetLastError", llvmType)
 298  			if isI386 {
 299  				setLastError.SetFunctionCallConv(llvm.X86StdcallCallConv)
 300  			}
 301  		}
 302  		getLastError := b.mod.NamedFunction("GetLastError")
 303  		if getLastError.IsNil() {
 304  			llvmType := llvm.FunctionType(b.ctx.Int32Type(), nil, false)
 305  			getLastError = llvm.AddFunction(b.mod, "GetLastError", llvmType)
 306  			if isI386 {
 307  				getLastError.SetFunctionCallConv(llvm.X86StdcallCallConv)
 308  			}
 309  		}
 310  
 311  		// Now do the actual call. Pseudocode:
 312  		//     SetLastError(0)
 313  		//     r1 = trap(a1, a2, a3, ...)
 314  		//     err = uintptr(GetLastError())
 315  		//     return r1, 0, err
 316  		// Note that SetLastError/GetLastError could be replaced with direct
 317  		// access to the thread control block, which is probably smaller and
 318  		// faster. The Go runtime does this in assembly.
 319  		// On windows/386, we also need to save/restore the stack pointer. I'm
 320  		// not entirely sure why this is needed, but without it these calls
 321  		// change the stack pointer leading to a crash soon after.
 322  		setLastErrorCall := b.CreateCall(setLastError.GlobalValueType(), setLastError, []llvm.Value{llvm.ConstNull(b.ctx.Int32Type())}, "")
 323  		var sp llvm.Value
 324  		if isI386 {
 325  			setLastErrorCall.SetInstructionCallConv(llvm.X86StdcallCallConv)
 326  			sp = b.readStackPointer()
 327  		}
 328  		syscallResult := b.CreateCall(llvmType, fnPtr, params, "")
 329  		if isI386 {
 330  			syscallResult.SetInstructionCallConv(llvm.X86StdcallCallConv)
 331  			b.writeStackPointer(sp)
 332  		}
 333  		errResult := b.CreateCall(getLastError.GlobalValueType(), getLastError, nil, "err")
 334  		if isI386 {
 335  			errResult.SetInstructionCallConv(llvm.X86StdcallCallConv)
 336  		}
 337  		if b.uintptrType != b.ctx.Int32Type() {
 338  			errResult = b.CreateZExt(errResult, b.uintptrType, "err.uintptr")
 339  		}
 340  
 341  		// Return r1, 0, err
 342  		retval := llvm.ConstNull(b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType, b.uintptrType}, false))
 343  		retval = b.CreateInsertValue(retval, syscallResult, 0, "")
 344  		retval = b.CreateInsertValue(retval, errResult, 2, "")
 345  		return retval, nil
 346  
 347  	default:
 348  		return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH)
 349  	}
 350  }
 351  
 352  // createRawSyscallNoError emits instructions for the Linux-specific
 353  // syscall.rawSyscallNoError function.
 354  func (b *builder) createRawSyscallNoError(call *ssa.CallCommon) (llvm.Value, error) {
 355  	syscallResult, err := b.createRawSyscall(call)
 356  	if err != nil {
 357  		return syscallResult, err
 358  	}
 359  	retval := llvm.ConstNull(b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType}, false))
 360  	retval = b.CreateInsertValue(retval, syscallResult, 0, "")
 361  	retval = b.CreateInsertValue(retval, llvm.ConstInt(b.uintptrType, 0, false), 1, "")
 362  	return retval, nil
 363  }
 364  
 365  // Lower a call to internal/abi.FuncPCABI0 on MacOS.
 366  // This function is called like this:
 367  //
 368  //	syscall(abi.FuncPCABI0(libc_mkdir_trampoline), uintptr(unsafe.Pointer(_p0)), uintptr(mode), 0)
 369  //
 370  // So we'll want to return a function pointer (as uintptr) that points to the
 371  // libc function. Specifically, we _don't_ want to point to the trampoline
 372  // function (which is implemented in Go assembly which we can't read), but
 373  // rather to the actually intended function. For this we're going to assume that
 374  // all the functions follow a specific pattern: libc_<functionname>_trampoline.
 375  //
 376  // The return value is the function pointer as an uintptr, or a nil value if
 377  // this isn't possible (and a regular call should be made as fallback).
 378  func (b *builder) createDarwinFuncPCABI0Call(instr *ssa.CallCommon) llvm.Value {
 379  	if b.GOOS != "darwin" {
 380  		// This has only been tested on MacOS (and only seems to be used there).
 381  		return llvm.Value{}
 382  	}
 383  
 384  	// Check that it uses a function call like syscall.libc_*_trampoline
 385  	itf := instr.Args[0].(*ssa.MakeInterface)
 386  	calledFn := itf.X.(*ssa.Function)
 387  	if pkgName := calledFn.Pkg.Pkg.Path(); pkgName != "syscall" && pkgName != "internal/syscall/unix" {
 388  		return llvm.Value{}
 389  	}
 390  	if !strings.HasPrefix(calledFn.Name(), "libc_") || !strings.HasSuffix(calledFn.Name(), "_trampoline") {
 391  
 392  		return llvm.Value{}
 393  	}
 394  
 395  	// Extract the libc function name.
 396  	name := strings.TrimPrefix(strings.TrimSuffix(calledFn.Name(), "_trampoline"), "libc_")
 397  	if name == "open" {
 398  		// Special case: open() is a variadic function and can't be called like
 399  		// a regular function. Therefore, we need to use a wrapper implemented
 400  		// in C.
 401  		name = "syscall_libc_open"
 402  	}
 403  	if b.GOARCH == "amd64" {
 404  		if name == "fdopendir" || name == "readdir_r" {
 405  			// Hack to support amd64, which needs the $INODE64 suffix.
 406  			// This is also done in upstream Go:
 407  			// https://github.com/golang/go/commit/096ab3c21b88ccc7d411379d09fe6274e3159467
 408  			name += "$INODE64"
 409  		}
 410  	}
 411  
 412  	// Obtain the C function.
 413  	// Use a simple function (no parameters or return value) because all we need
 414  	// is the address of the function.
 415  	llvmFn := b.mod.NamedFunction(name)
 416  	if llvmFn.IsNil() {
 417  		llvmFnType := llvm.FunctionType(b.ctx.VoidType(), nil, false)
 418  		llvmFn = llvm.AddFunction(b.mod, name, llvmFnType)
 419  	}
 420  
 421  	// Cast the function pointer to a uintptr (because that's what
 422  	// abi.FuncPCABI0 returns).
 423  	return b.CreatePtrToInt(llvmFn, b.uintptrType, "")
 424  }
 425