
This commit adds getValue which gets a const, global, or result of a local SSA expression and replaces (almost) all uses of parseExpr with getValue. The only remaining use is in parseInstr, which makes sure an instruction is only evaluated once.
167 строки
6,3 КиБ
Go
167 строки
6,3 КиБ
Go
package compiler
|
|
|
|
// This file implements the syscall.Syscall and syscall.Syscall6 instructions as
|
|
// compiler builtins.
|
|
|
|
import (
|
|
"go/constant"
|
|
"strconv"
|
|
|
|
"golang.org/x/tools/go/ssa"
|
|
"tinygo.org/x/go-llvm"
|
|
)
|
|
|
|
// emitSyscall emits an inline system call instruction, depending on the target
|
|
// OS/arch.
|
|
func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, error) {
|
|
num, _ := constant.Uint64Val(call.Args[0].(*ssa.Const).Value)
|
|
var syscallResult llvm.Value
|
|
switch {
|
|
case c.GOARCH == "amd64":
|
|
if c.GOOS == "darwin" {
|
|
// Darwin adds this magic number to system call numbers:
|
|
//
|
|
// > Syscall classes for 64-bit system call entry.
|
|
// > For 64-bit users, the 32-bit syscall number is partitioned
|
|
// > with the high-order bits representing the class and low-order
|
|
// > bits being the syscall number within that class.
|
|
// > The high-order 32-bits of the 64-bit syscall number are unused.
|
|
// > All system classes enter the kernel via the syscall instruction.
|
|
//
|
|
// Source: https://opensource.apple.com/source/xnu/xnu-792.13.8/osfmk/mach/i386/syscall_sw.h
|
|
num += 0x2000000
|
|
}
|
|
// Sources:
|
|
// https://stackoverflow.com/a/2538212
|
|
// https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#syscall
|
|
args := []llvm.Value{llvm.ConstInt(c.uintptrType, num, false)}
|
|
argTypes := []llvm.Type{c.uintptrType}
|
|
// Constraints will look something like:
|
|
// "={rax},0,{rdi},{rsi},{rdx},{r10},{r8},{r9},~{rcx},~{r11}"
|
|
constraints := "={rax},0"
|
|
for i, arg := range call.Args[1:] {
|
|
constraints += "," + [...]string{
|
|
"{rdi}",
|
|
"{rsi}",
|
|
"{rdx}",
|
|
"{r10}",
|
|
"{r8}",
|
|
"{r9}",
|
|
"{r11}",
|
|
"{r12}",
|
|
"{r13}",
|
|
}[i]
|
|
llvmValue := c.getValue(frame, arg)
|
|
args = append(args, llvmValue)
|
|
argTypes = append(argTypes, llvmValue.Type())
|
|
}
|
|
constraints += ",~{rcx},~{r11}"
|
|
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
|
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel)
|
|
syscallResult = c.builder.CreateCall(target, args, "")
|
|
case c.GOARCH == "arm" && c.GOOS == "linux":
|
|
// Implement the EABI system call convention for Linux.
|
|
// Source: syscall(2) man page.
|
|
args := []llvm.Value{}
|
|
argTypes := []llvm.Type{}
|
|
// Constraints will look something like:
|
|
// ={r0},0,{r1},{r2},{r7},~{r3}
|
|
constraints := "={r0}"
|
|
for i, arg := range call.Args[1:] {
|
|
constraints += "," + [...]string{
|
|
"0", // tie to output
|
|
"{r1}",
|
|
"{r2}",
|
|
"{r3}",
|
|
"{r4}",
|
|
"{r5}",
|
|
"{r6}",
|
|
}[i]
|
|
llvmValue := c.getValue(frame, arg)
|
|
args = append(args, llvmValue)
|
|
argTypes = append(argTypes, llvmValue.Type())
|
|
}
|
|
args = append(args, llvm.ConstInt(c.uintptrType, num, false))
|
|
argTypes = append(argTypes, c.uintptrType)
|
|
constraints += ",{r7}" // syscall number
|
|
for i := len(call.Args) - 1; i < 4; i++ {
|
|
// r0-r3 get clobbered after the syscall returns
|
|
constraints += ",~{r" + strconv.Itoa(i) + "}"
|
|
}
|
|
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
|
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
|
|
syscallResult = c.builder.CreateCall(target, args, "")
|
|
case c.GOARCH == "arm64" && c.GOOS == "linux":
|
|
// Source: syscall(2) man page.
|
|
args := []llvm.Value{}
|
|
argTypes := []llvm.Type{}
|
|
// Constraints will look something like:
|
|
// ={x0},0,{x1},{x2},{x8},~{x3},~{x4},~{x5},~{x6},~{x7},~{x16},~{x17}
|
|
constraints := "={x0}"
|
|
for i, arg := range call.Args[1:] {
|
|
constraints += "," + [...]string{
|
|
"0", // tie to output
|
|
"{x1}",
|
|
"{x2}",
|
|
"{x3}",
|
|
"{x4}",
|
|
"{x5}",
|
|
}[i]
|
|
llvmValue := c.getValue(frame, arg)
|
|
args = append(args, llvmValue)
|
|
argTypes = append(argTypes, llvmValue.Type())
|
|
}
|
|
args = append(args, llvm.ConstInt(c.uintptrType, num, false))
|
|
argTypes = append(argTypes, c.uintptrType)
|
|
constraints += ",{x8}" // syscall number
|
|
for i := len(call.Args) - 1; i < 8; i++ {
|
|
// x0-x7 may get clobbered during the syscall following the aarch64
|
|
// calling convention.
|
|
constraints += ",~{x" + strconv.Itoa(i) + "}"
|
|
}
|
|
constraints += ",~{x16},~{x17}" // scratch registers
|
|
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
|
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
|
|
syscallResult = c.builder.CreateCall(target, args, "")
|
|
default:
|
|
return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS+"/"+c.GOARCH)
|
|
}
|
|
switch c.GOOS {
|
|
case "linux":
|
|
// Return values: r0, r1 uintptr, err Errno
|
|
// Pseudocode:
|
|
// var err uintptr
|
|
// if syscallResult < 0 && syscallResult > -4096 {
|
|
// err = -syscallResult
|
|
// }
|
|
// return syscallResult, 0, err
|
|
zero := llvm.ConstInt(c.uintptrType, 0, false)
|
|
inrange1 := c.builder.CreateICmp(llvm.IntSLT, syscallResult, llvm.ConstInt(c.uintptrType, 0, false), "")
|
|
inrange2 := c.builder.CreateICmp(llvm.IntSGT, syscallResult, llvm.ConstInt(c.uintptrType, 0xfffffffffffff000, true), "") // -4096
|
|
hasError := c.builder.CreateAnd(inrange1, inrange2, "")
|
|
errResult := c.builder.CreateSelect(hasError, c.builder.CreateSub(zero, syscallResult, ""), zero, "syscallError")
|
|
retval := llvm.Undef(llvm.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
|
|
retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
|
|
retval = c.builder.CreateInsertValue(retval, zero, 1, "")
|
|
retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
|
|
return retval, nil
|
|
case "darwin":
|
|
// Return values: r0, r1 uintptr, err Errno
|
|
// Pseudocode:
|
|
// var err uintptr
|
|
// if syscallResult != 0 {
|
|
// err = syscallResult
|
|
// }
|
|
// return syscallResult, 0, err
|
|
zero := llvm.ConstInt(c.uintptrType, 0, false)
|
|
hasError := c.builder.CreateICmp(llvm.IntNE, syscallResult, llvm.ConstInt(c.uintptrType, 0, false), "")
|
|
errResult := c.builder.CreateSelect(hasError, syscallResult, zero, "syscallError")
|
|
retval := llvm.Undef(llvm.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
|
|
retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
|
|
retval = c.builder.CreateInsertValue(retval, zero, 1, "")
|
|
retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
|
|
return retval, nil
|
|
default:
|
|
return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS+"/"+c.GOARCH)
|
|
}
|
|
}
|