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 }