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