blob: e0760e2776dfee80d8cb67b726de166712b953d0 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <zircon/assert.h>
#include "tools/kazoo/output_util.h"
#include "tools/kazoo/outputs.h"
namespace {
void PrintStub(Writer* writer, Syscall* syscall) {
writer->Printf("func vdsoCall_zx_%s(", syscall->name().c_str());
for (size_t i = 0; i < syscall->num_kernel_args(); ++i) {
if (i > 0) {
writer->Puts(", ");
}
const StructMember& arg = syscall->kernel_arguments()[i];
writer->Printf("%s %s", RemapReservedGoName(arg.name()).c_str(),
GetNativeGoName(arg.type()).c_str());
}
writer->Puts(")");
if (!syscall->is_noreturn() && !syscall->kernel_return_type().IsVoid()) {
writer->Printf(" %s", GetNativeGoName(syscall->kernel_return_type()).c_str());
}
writer->Puts("\n");
}
size_t GoTypeSize(const Type& type) {
std::string native_name = GetNativeGoName(type);
if (native_name == "void") {
return 0;
}
if (native_name == "bool" || native_name == "uint8") {
return 1;
}
if (native_name == "int16" || native_name == "uint16") {
return 2;
}
if (native_name == "int32" || native_name == "uint32") {
return 4;
}
if (native_name == "uintptr" || native_name == "uint" || native_name == "int64" ||
native_name == "uint64" || native_name == "unsafe.Pointer") {
return 8;
}
ZX_ASSERT(false && "unhandled GoTypeSize");
}
enum class Arch {
kArm64,
kX86,
};
bool IsSpecialGoRuntimeFunction(const Syscall& syscall) {
// These functions can't call runtime·entersyscall and exitsyscall, otherwise
// the system will hang.
return syscall.name() == "nanosleep" || syscall.name() == "futex_wait";
}
void PrintAsm(Writer* writer, Syscall* syscall, Arch arch) {
static const char* kX86RegArgs[] = {"DI", "SI", "DX", "CX", "R8", "R9", "R12", "R13"};
static const char* kArm64RegArgs[] = {"R0", "R1", "R2", "R3", "R4", "R5", "R6", "R7"};
size_t arg_size = 0;
for (size_t i = 0; i < syscall->num_kernel_args(); ++i) {
const StructMember& arg = syscall->kernel_arguments()[i];
size_t sz = GoTypeSize(arg.type());
if (arch == Arch::kArm64 && sz == 1) {
sz = 8;
}
while (arg_size % sz != 0) {
// Add padding until the arg_size is aligned to the type we're adding.
++arg_size;
}
arg_size += sz;
}
if (arg_size % 8 == 4) {
// Force the return argument on the stack to be 8 byte aligned, not 4.
arg_size += 4;
}
const size_t ret_size = GoTypeSize(syscall->kernel_return_type());
int frame_size = 0;
const char** reg_args = nullptr;
std::string call_ins, ret_reg, suffix4, suffix8;
switch (arch) {
case Arch::kX86:
reg_args = kX86RegArgs;
call_ins = "CALL";
ret_reg = "AX";
suffix8 = "Q";
suffix4 = "L";
frame_size = 8;
if (syscall->num_kernel_args() == 7) {
frame_size += 16 + 8;
} else if (syscall->num_kernel_args() == 8) {
frame_size += 16 + 2 * 8;
}
break;
case Arch::kArm64:
reg_args = kArm64RegArgs;
call_ins = "BL";
ret_reg = "R0";
suffix8 = "D";
suffix4 = "W";
break;
}
writer->Printf("TEXT runtime·vdsoCall_zx_%s(SB),NOSPLIT,$%d-%zu\n", syscall->name().c_str(),
frame_size, arg_size + ret_size);
writer->Puts("\tGO_ARGS\n");
writer->Puts("\tNO_LOCAL_POINTERS\n");
// Set vdso{PC,SP} so that pprof tracebacks work for VDSO calls.
switch (arch) {
case Arch::kX86:
writer->Puts("\tget_tls(CX)\n");
writer->Puts("\tMOVQ g(CX), AX\n");
writer->Puts("\tMOVQ g_m(AX), R14\n");
writer->Puts("\tPUSHQ R14\n");
writer->Printf("\tMOVQ %d(SP), DX\n", frame_size + 16);
writer->Puts("\tMOVQ DX, m_vdsoPC(R14)\n");
writer->Printf("\tLEAQ %d(SP), DX\n", frame_size + 16);
writer->Puts("\tMOVQ DX, m_vdsoSP(R14)\n");
break;
case Arch::kArm64:
writer->Puts("\tMOVD g_m(g), R21\n");
writer->Puts("\tMOVD LR, m_vdsoPC(R21)\n");
writer->Puts("\tMOVD RSP, R20\n");
writer->Puts("\tMOVD R20, m_vdsoSP(R21)\n");
break;
}
if (syscall->HasAttribute("blocking") && !IsSpecialGoRuntimeFunction(*syscall)) {
writer->Puts("\tCALL runtime·entersyscall(SB)\n");
}
size_t off = 0;
for (size_t i = 0; i < syscall->num_kernel_args(); ++i) {
const StructMember& arg = syscall->kernel_arguments()[i];
std::string name = RemapReservedGoName(arg.name());
std::string suffix = suffix8;
size_t sz = GoTypeSize(arg.type());
if (sz == 4) {
suffix = suffix4;
} else if (arch == Arch::kArm64 && sz == 1) {
sz = 8;
}
while (off % sz != 0) {
// Add padding until the offset is aligned to the type we are accessing
++off;
}
writer->Printf("\tMOV%s %s+%zu(FP), %s\n", suffix.c_str(), name.c_str(), off, reg_args[i]);
off += sz;
}
switch (arch) {
case Arch::kX86:
if (syscall->num_kernel_args() >= 7) {
writer->Puts("\tMOVQ SP, BP // BP is preserved across vsdo call by the x86-64 ABI\n");
writer->Puts("\tANDQ $~15, SP // stack alignment for x86-64 ABI\n");
if (syscall->num_kernel_args() == 8) {
writer->Puts("\tPUSHQ R13\n");
}
writer->Puts("\tPUSHQ R12\n");
}
writer->Printf("\tMOVQ vdso_zx_%s(SB), AX\n", syscall->name().c_str());
writer->Puts("\tCALL AX\n");
if (syscall->num_kernel_args() >= 7) {
writer->Puts("\tPOPQ R12\n");
if (syscall->num_kernel_args() == 8) {
writer->Puts("\tPOPQ R13\n");
}
writer->Puts("\tMOVQ BP, SP\n");
}
break;
case Arch::kArm64:
writer->Printf("\tBL vdso_zx_%s(SB)\n", syscall->name().c_str());
}
if (ret_size > 0) {
std::string suffix = suffix8;
if (ret_size == 4) {
suffix = suffix4;
}
writer->Printf("\tMOV%s %s, ret+%zu(FP)\n", suffix.c_str(), ret_reg.c_str(), arg_size);
}
if (syscall->HasAttribute("blocking") && !IsSpecialGoRuntimeFunction(*syscall)) {
writer->Printf("\t%s runtime·exitsyscall(SB)\n", call_ins.c_str());
}
// Clear vdsoSP. sigprof only checks vdsoSP for generating tracebacks, so we can leave vdsoPC
// alone.
switch (arch) {
case Arch::kX86:
writer->Puts("\tPOPQ R14\n");
writer->Puts("\tMOVQ $0, m_vdsoSP(R14)\n");
break;
case Arch::kArm64:
writer->Puts("\tMOVD g_m(g), R21\n");
writer->Puts("\tMOVD $0, m_vdsoSP(R21)\n");
break;
}
writer->Puts("\tRET\n");
}
bool VdsoCalls(const SyscallLibrary& library, Writer* writer, Arch arch) {
CopyrightHeaderWithCppComments(writer);
writer->Puts("#include \"go_asm.h\"\n");
writer->Puts("#include \"go_tls.h\"\n");
writer->Puts("#include \"textflag.h\"\n");
writer->Puts("#include \"funcdata.h\"\n\n");
for (const auto& syscall : library.syscalls()) {
writer->Printf("// ");
PrintStub(writer, syscall.get());
PrintAsm(writer, syscall.get(), arch);
writer->Puts("\n");
}
return true;
}
} // namespace
bool GoVdsoKeys(const SyscallLibrary& library, Writer* writer) {
CopyrightHeaderWithCppComments(writer);
writer->Puts("package runtime\n\n");
writer->Puts("import \"unsafe\"\n\n");
writer->Puts("const (\n");
writer->Puts(
"\t// vdsoArrayMax is the byte-size of a maximally sized array on this architecture.\n");
writer->Puts("\t// See cmd/compile/internal/amd64/galign.go arch.MAXWIDTH initialization.\n");
writer->Puts("\tvdsoArrayMax = 1<<50 - 1\n");
writer->Puts(")\n\n");
writer->Puts("var vdsoSymbolKeys = []vdsoSymbolKey{\n");
for (const auto& syscall : library.syscalls()) {
std::string sym("_zx_" + syscall->name());
writer->Printf("\t{\"%s\", 0x%x, &vdso%s},\n", sym.c_str(), DJBHash(sym), sym.c_str());
}
writer->Puts("}\n");
writer->Puts("\n");
for (const auto& syscall : library.syscalls()) {
writer->Printf("//go:cgo_import_dynamic vdso_zx_%s zx_%s\n", syscall->name().c_str(),
syscall->name().c_str());
}
writer->Puts("\n");
for (const auto& syscall : library.syscalls()) {
writer->Printf("//go:linkname vdso_zx_%s vdso_zx_%s\n", syscall->name().c_str(),
syscall->name().c_str());
}
writer->Puts("\n");
for (const auto& syscall : library.syscalls()) {
writer->Puts("//go:noescape\n");
writer->Puts("//go:nosplit\n");
PrintStub(writer, syscall.get());
writer->Puts("\n");
}
writer->Puts("var (\n");
for (const auto& syscall : library.syscalls()) {
writer->Printf("\tvdso_zx_%s uintptr\n", syscall->name().c_str());
}
writer->Puts(")\n");
return true;
}
bool GoVdsoArm64Calls(const SyscallLibrary& library, Writer* writer) {
return VdsoCalls(library, writer, Arch::kArm64);
}
bool GoVdsoX86Calls(const SyscallLibrary& library, Writer* writer) {
return VdsoCalls(library, writer, Arch::kX86);
}