summaryrefslogtreecommitdiff
path: root/erts/emulator/beam/jit/x86/beam_asm_module.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'erts/emulator/beam/jit/x86/beam_asm_module.cpp')
-rw-r--r--erts/emulator/beam/jit/x86/beam_asm_module.cpp494
1 files changed, 170 insertions, 324 deletions
diff --git a/erts/emulator/beam/jit/x86/beam_asm_module.cpp b/erts/emulator/beam/jit/x86/beam_asm_module.cpp
index bada1c8395..be01db5ac3 100644
--- a/erts/emulator/beam/jit/x86/beam_asm_module.cpp
+++ b/erts/emulator/beam/jit/x86/beam_asm_module.cpp
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2020-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2020-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,11 +24,6 @@
#include "beam_asm.hpp"
using namespace asmjit;
-static std::string getAtom(Eterm atom) {
- Atom *ap = atom_tab(atom_val(atom));
- return std::string((char *)ap->name, ap->len);
-}
-
#ifdef BEAMASM_DUMP_SIZES
# include <mutex>
@@ -67,127 +62,31 @@ extern "C" void beamasm_dump_sizes() {
}
#endif
-BeamModuleAssembler::BeamModuleAssembler(BeamGlobalAssembler *ga,
- Eterm mod,
- unsigned num_labels)
- : BeamAssembler(getAtom(mod)) {
- this->ga = ga;
- this->mod = mod;
-
- labels.reserve(num_labels + 1);
- for (unsigned i = 1; i < num_labels; i++) {
- Label lbl;
-
-#ifdef DEBUG
- std::string lblName = "label_" + std::to_string(i);
- lbl = a.newNamedLabel(lblName.data());
-#else
- lbl = a.newLabel();
-#endif
+ErtsCodePtr BeamModuleAssembler::getCode(BeamLabel label) {
+ ASSERT(label < rawLabels.size() + 1);
+ return (ErtsCodePtr)getCode(rawLabels[label]);
+}
- labels[i] = lbl;
- }
+ErtsCodePtr BeamModuleAssembler::getLambda(unsigned index) {
+ const auto &lambda = lambdas[index];
+ return (ErtsCodePtr)getCode(lambda.trampoline);
}
BeamModuleAssembler::BeamModuleAssembler(BeamGlobalAssembler *ga,
Eterm mod,
- unsigned num_labels,
- unsigned num_functions)
- : BeamModuleAssembler(ga, mod, num_labels) {
- codeHeader = a.newLabel();
+ int num_labels,
+ int num_functions,
+ const BeamFile *file)
+ : BeamModuleAssembler(ga, mod, num_labels, file) {
+ code_header = a.newLabel();
a.align(AlignMode::kCode, 8);
- a.bind(codeHeader);
+ a.bind(code_header);
embed_zeros(sizeof(BeamCodeHeader) +
sizeof(ErtsCodeInfo *) * num_functions);
-
- floatMax = a.newLabel();
- a.align(AlignMode::kCode, 8);
- a.bind(floatMax);
- double max = DBL_MAX;
- a.embed((char *)&max, sizeof(double));
-
- floatSignMask = a.newLabel();
- a.align(AlignMode::kCode, 16); /* 128-bit aligned */
- a.bind(floatSignMask);
- uint64_t signMask = 0x7FFFFFFFFFFFFFFFul;
- a.embed((char *)&signMask, sizeof(double));
-
- /* Shared trampoline for function_clause errors, which can't jump straight
- * to `i_func_info_shared` due to size restrictions. */
- funcInfo = a.newLabel();
- a.align(AlignMode::kCode, 8);
- a.bind(funcInfo);
- abs_jmp(ga->get_i_func_info_shared());
-
- /* Shared trampoline for yielding on function ingress. */
- funcYield = a.newLabel();
- a.align(AlignMode::kCode, 8);
- a.bind(funcYield);
- abs_jmp(ga->get_i_test_yield_shared());
-
- /* Setup the early_nif/breakpoint trampoline. */
- genericBPTramp = a.newLabel();
- a.align(AlignMode::kCode, 16);
- a.bind(genericBPTramp);
- {
- a.ret();
-
- a.align(AlignMode::kCode, 16);
- abs_jmp(ga->get_call_nif_early());
-
- a.align(AlignMode::kCode, 16);
- aligned_call(ga->get_generic_bp_local());
- a.ret();
-
- a.align(AlignMode::kCode, 16);
- ASSERT(a.offset() - code.labelOffsetFromBase(genericBPTramp) == 16 * 3);
- aligned_call(ga->get_generic_bp_local());
- abs_jmp(ga->get_call_nif_early());
- }
-}
-
-ErtsCodePtr BeamModuleAssembler::getCode(unsigned label) {
- ASSERT(label < labels.size() + 1);
- return (ErtsCodePtr)getCode(labels[label]);
}
-void BeamAssembler::embed_rodata(const char *labelName,
- const char *buff,
- size_t size) {
- Label label = a.newNamedLabel(labelName);
-
- a.section(rodata);
- a.bind(label);
- a.embed(buff, size);
- a.section(code.textSection());
-}
-
-void BeamAssembler::embed_bss(const char *labelName, size_t size) {
- Label label = a.newNamedLabel(labelName);
-
- /* Reuse rodata section for now */
- a.section(rodata);
- a.bind(label);
- embed_zeros(size);
- a.section(code.textSection());
-}
-
-void BeamAssembler::embed_zeros(size_t size) {
- static constexpr size_t buf_size = 16384;
- static const char zeros[buf_size] = {};
-
- while (size >= buf_size) {
- a.embed(zeros, buf_size);
- size -= buf_size;
- }
-
- if (size > 0) {
- a.embed(zeros, size);
- }
-}
-
-Label BeamModuleAssembler::embed_vararg_rodata(const std::vector<ArgVal> &args,
+Label BeamModuleAssembler::embed_vararg_rodata(const Span<ArgVal> &args,
int y_offset) {
Label label = a.newLabel();
@@ -206,27 +105,33 @@ Label BeamModuleAssembler::embed_vararg_rodata(const std::vector<ArgVal> &args,
a.align(AlignMode::kData, 8);
switch (arg.getType()) {
- case TAG_x:
- data.as_beam = make_loader_x_reg(arg.getValue());
+ case ArgVal::XReg: {
+ auto index = arg.as<ArgXRegister>().get();
+ data.as_beam = make_loader_x_reg(index);
a.embed(&data.as_char, sizeof(data.as_beam));
- break;
- case TAG_y:
- data.as_beam = make_loader_y_reg(arg.getValue() + y_offset);
+ } break;
+ case ArgVal::YReg: {
+ auto index = arg.as<ArgYRegister>().get();
+ data.as_beam = make_loader_y_reg(index + y_offset);
a.embed(&data.as_char, sizeof(data.as_beam));
+ } break;
+ case ArgVal::Literal: {
+ auto index = arg.as<ArgLiteral>().get();
+ make_word_patch(literals[index].patches);
+ } break;
+ case ArgVal::Label:
+ a.embedLabel(resolve_beam_label(arg));
break;
- case TAG_q:
- make_word_patch(literals[arg.getValue()].patches);
- break;
- case TAG_f:
- a.embedLabel(labels[arg.getValue()]);
+ case ArgVal::Immediate:
+ data.as_beam = arg.as<ArgImmed>().get();
+ a.embed(&data.as_char, sizeof(data.as_beam));
break;
- case TAG_i:
- case TAG_u:
- /* Tagged immediate or untagged word. */
- data.as_beam = arg.getValue();
+ case ArgVal::Word:
+ data.as_beam = arg.as<ArgWord>().get();
a.embed(&data.as_char, sizeof(data.as_beam));
break;
default:
+ erts_fprintf(stderr, "tag: %li\n", arg.getType());
ERTS_ASSERT(!"error");
}
}
@@ -236,15 +141,11 @@ Label BeamModuleAssembler::embed_vararg_rodata(const std::vector<ArgVal> &args,
return label;
}
-static void i_emit_nyi(char *msg) {
- erts_exit(ERTS_ERROR_EXIT, "NYI: %s\n", msg);
-}
-
void BeamModuleAssembler::emit_i_nif_padding() {
const size_t minimum_size = sizeof(UWord[BEAM_NATIVE_MIN_FUNC_SZ]);
size_t prev_func_start, diff;
- prev_func_start = code.labelOffsetFromBase(labels[functions.back() + 1]);
+ prev_func_start = code.labelOffsetFromBase(rawLabels[functions.back() + 1]);
diff = a.offset() - prev_func_start;
if (diff < minimum_size) {
@@ -252,34 +153,86 @@ void BeamModuleAssembler::emit_i_nif_padding() {
}
}
+void BeamGlobalAssembler::emit_i_breakpoint_trampoline_shared() {
+ constexpr ssize_t flag_offset =
+ sizeof(ErtsCodeInfo) + BEAM_ASM_FUNC_PROLOGUE_SIZE -
+ offsetof(ErtsCodeInfo, u.metadata.breakpoint_flag);
+
+ Label bp_and_nif = a.newLabel(), bp_only = a.newLabel(),
+ nif_only = a.newLabel();
+
+ a.mov(RET, x86::qword_ptr(x86::rsp));
+ a.movzx(RETd, x86::byte_ptr(RET, -flag_offset));
+
+ a.cmp(RETd, imm(ERTS_ASM_BP_FLAG_BP_NIF_CALL_NIF_EARLY));
+ a.short_().je(bp_and_nif);
+ a.cmp(RETd, imm(ERTS_ASM_BP_FLAG_CALL_NIF_EARLY));
+ a.short_().je(nif_only);
+ a.cmp(RETd, imm(ERTS_ASM_BP_FLAG_BP));
+ a.short_().je(bp_only);
+
+#ifndef DEBUG
+ a.ret();
+#else
+ Label error = a.newLabel();
+
+ /* RET must be a valid breakpoint flag. */
+ a.test(RETd, RETd);
+ a.short_().jnz(error);
+ a.ret();
+
+ a.bind(error);
+ a.ud2();
+#endif
+
+ a.bind(bp_and_nif);
+ {
+ aligned_call(labels[generic_bp_local]);
+ /* FALL THROUGH */
+ }
+
+ a.bind(nif_only);
+ {
+ /* call_nif_early returns on its own, unlike generic_bp_local. */
+ a.jmp(labels[call_nif_early]);
+ }
+
+ a.bind(bp_only);
+ {
+ aligned_call(labels[generic_bp_local]);
+ a.ret();
+ }
+}
+
void BeamModuleAssembler::emit_i_breakpoint_trampoline() {
/* This little prologue is used by nif loading and tracing to insert
* alternative instructions. The call is filled with a relative call to a
* trampoline in the module header and then the jmp target is zeroed so that
* it effectively becomes a nop */
- byte flag = ERTS_ASM_BP_FLAG_NONE;
Label next = a.newLabel();
a.short_().jmp(next);
- /* We embed a zero byte here, which is used to flag whether to make an early
- * nif call, call a breakpoint handler, or both. */
- a.embed(&flag, sizeof(flag));
-
- if (genericBPTramp.isValid()) {
- a.call(genericBPTramp);
+ if (code_header.isValid()) {
+ auto fragment = ga->get_i_breakpoint_trampoline_shared();
+ aligned_call(resolve_fragment(fragment));
} else {
/* NIF or BIF stub; we're not going to use this trampoline as-is, but
* we need to reserve space for it. */
a.ud2();
+ a.align(AlignMode::kCode, sizeof(UWord));
}
- a.align(AlignMode::kCode, 8);
+ ASSERT(a.offset() % sizeof(UWord) == 0);
a.bind(next);
- ASSERT((a.offset() - code.labelOffsetFromBase(currLabel)) ==
+ ASSERT((a.offset() - code.labelOffsetFromBase(current_label)) ==
BEAM_ASM_FUNC_PROLOGUE_SIZE);
}
+static void i_emit_nyi(char *msg) {
+ erts_exit(ERTS_ERROR_EXIT, "NYI: %s\n", msg);
+}
+
void BeamModuleAssembler::emit_nyi(const char *msg) {
emit_enter_runtime();
@@ -293,8 +246,7 @@ void BeamModuleAssembler::emit_nyi() {
emit_nyi("<unspecified>");
}
-bool BeamModuleAssembler::emit(unsigned specific_op,
- const std::vector<ArgVal> &args) {
+bool BeamModuleAssembler::emit(unsigned specific_op, const Span<ArgVal> &args) {
comment(opc[specific_op].name);
#ifdef BEAMASM_DUMP_SIZES
@@ -309,18 +261,6 @@ bool BeamModuleAssembler::emit(unsigned specific_op,
break;
}
- if (getOffset() == last_error_offset) {
- /*
- * The previous PC where an exception may occur is equal to the
- * current offset, which is also the offset of the next
- * instruction. If the next instruction happens to be a
- * line instruction, the location for the exception will
- * be that line instruction, which is probably wrong.
- * To avoid that, bump the instruction offset.
- */
- a.nop();
- }
-
#ifdef BEAMASM_DUMP_SIZES
{
std::lock_guard<std::mutex> lock(size_lock);
@@ -341,81 +281,100 @@ void BeamGlobalAssembler::emit_i_func_info_shared() {
/* Pop the ErtsCodeInfo address into ARG1 and mask out the offset added by
* the call instruction. */
a.pop(ARG1);
- a.and_(ARG1, ~0x7);
+ a.and_(ARG1, imm(~0x7));
- a.lea(ARG1, x86::qword_ptr(ARG1, offsetof(ErtsCodeInfo, mfa)));
+ a.add(ARG1, imm(offsetof(ErtsCodeInfo, mfa)));
a.mov(x86::qword_ptr(c_p, offsetof(Process, freason)), EXC_FUNCTION_CLAUSE);
a.mov(x86::qword_ptr(c_p, offsetof(Process, current)), ARG1);
- a.jmp(labels[error_action_code]);
+
+ mov_imm(ARG2, 0);
+ mov_imm(ARG4, 0);
+ a.jmp(labels[raise_exception_shared]);
}
-void BeamModuleAssembler::emit_i_func_info(const ArgVal &Label,
- const ArgVal &Module,
- const ArgVal &Function,
- const ArgVal &Arity) {
+void BeamModuleAssembler::emit_i_func_info(const ArgWord &Label,
+ const ArgAtom &Module,
+ const ArgAtom &Function,
+ const ArgWord &Arity) {
ErtsCodeInfo info;
- functions.push_back(Label.getValue());
+ /* `op_i_func_info_IaaI` is used in various places in the emulator, so this
+ * label is always encoded as a word, even though the signature ought to
+ * be `op_i_func_info_LaaI`. */
+ functions.push_back(Label.get());
- info.mfa.module = Module.getValue();
- info.mfa.function = Function.getValue();
- info.mfa.arity = Arity.getValue();
- info.u.gen_bp = NULL;
+ info.mfa.module = Module.get();
+ info.mfa.function = Function.get();
+ info.mfa.arity = Arity.get();
+ info.gen_bp = NULL;
comment("%T:%T/%d", info.mfa.module, info.mfa.function, info.mfa.arity);
/* This is an ErtsCodeInfo structure that has a valid x86 opcode as its `op`
- * field, which *calls* the funcInfo trampoline so we can trace it back to
- * this particular function.
+ * field, which *calls* the `_i_func_info_shared` fragment so we can trace
+ * it back to this particular function.
*
- * We make a relative call to a trampoline in the module header because this
- * needs to fit into a word, and an directy call to `i_func_info_shared`
- * would be too large. */
- if (funcInfo.isValid()) {
- a.call(funcInfo);
- } else {
- a.nop();
- }
-
- a.align(AlignMode::kCode, sizeof(UWord));
- a.embed(&info.u.gen_bp, sizeof(info.u.gen_bp));
+ * We also use this field to store the current breakpoint flag so that we
+ * only have to modify a single branch target when changing breakpoints. */
+ a.call(resolve_fragment(ga->get_i_func_info_shared()));
+ a.nop();
+ a.nop();
+ a.embedUInt8(ERTS_ASM_BP_FLAG_NONE);
+
+ ASSERT(a.offset() % sizeof(UWord) == 0);
+ a.embed(&info.gen_bp, sizeof(info.gen_bp));
a.embed(&info.mfa, sizeof(info.mfa));
}
-void BeamModuleAssembler::emit_label(const ArgVal &Label) {
- currLabel = labels[Label.getValue()];
- a.bind(currLabel);
+void BeamModuleAssembler::emit_label(const ArgLabel &Label) {
+ ASSERT(Label.isLabel());
+
+ current_label = rawLabels[Label.get()];
+ a.bind(current_label);
}
-void BeamModuleAssembler::emit_aligned_label(const ArgVal &Label,
- const ArgVal &Alignment) {
- ASSERT(Alignment.getType() == ArgVal::u);
- a.align(AlignMode::kCode, Alignment.getValue());
+void BeamModuleAssembler::emit_aligned_label(const ArgLabel &Label,
+ const ArgWord &Alignment) {
+ a.align(AlignMode::kCode, Alignment.get());
emit_label(Label);
}
void BeamModuleAssembler::emit_on_load() {
- on_load = currLabel;
+ on_load = current_label;
}
void BeamModuleAssembler::emit_int_code_end() {
/* This label is used to figure out the end of the last function */
- labels[labels.size() + 1] = a.newLabel();
- a.bind(labels[labels.size()]);
+ code_end = a.newLabel();
+ a.bind(code_end);
emit_nyi("int_code_end");
+
+ for (auto pair : _dispatchTable) {
+ a.bind(pair.second);
+ a.jmp(imm(pair.first));
+ }
}
-void BeamModuleAssembler::emit_line(const ArgVal &) {
- /*
- * There is no need to align the line instruction. In the loaded
- * code, the type of the pointer will be void* and that pointer
- * will only be used in comparisons.
- */
+void BeamModuleAssembler::emit_line(const ArgWord &Loc) {
+ /* There is no need to align the line instruction. In the loaded code, the
+ * type of the pointer will be void* and that pointer will only be used in
+ * comparisons.
+ *
+ * We only need to do something when there's a possibility of raising an
+ * exception at the very end of the preceding instruction (and thus
+ * pointing at the start of this one). If we were to do nothing, the error
+ * would erroneously refer to this instead of the preceding line.
+ *
+ * Since line addresses are taken _after_ line instructions we can avoid
+ * this by adding a nop when we detect this condition. */
+ if (a.offset() == last_error_offset) {
+ a.nop();
+ }
}
-void BeamModuleAssembler::emit_func_line(const ArgVal &Loc) {
+void BeamModuleAssembler::emit_func_line(const ArgWord &Loc) {
emit_line(Loc);
}
@@ -434,7 +393,7 @@ void BeamModuleAssembler::emit_i_generic_breakpoint() {
emit_nyi("i_generic_breakpoint should never be called");
}
-void BeamModuleAssembler::emit_trace_jump(const ArgVal &) {
+void BeamModuleAssembler::emit_trace_jump(const ArgWord &) {
emit_nyi("trace_jump should never be called");
}
@@ -442,129 +401,6 @@ void BeamModuleAssembler::emit_call_error_handler() {
emit_nyi("call_error_handler should never be called");
}
-void BeamModuleAssembler::codegen(JitAllocator *allocator,
- const void **executable_ptr,
- void **writable_ptr,
- const BeamCodeHeader *in_hdr,
- const BeamCodeHeader **out_exec_hdr,
- BeamCodeHeader **out_rw_hdr) {
- const BeamCodeHeader *code_hdr_exec;
- BeamCodeHeader *code_hdr_rw;
-
- codegen(allocator, executable_ptr, writable_ptr);
-
- {
- auto offset = code.labelOffsetFromBase(codeHeader);
-
- auto base_exec = (const char *)(*executable_ptr);
- code_hdr_exec = (const BeamCodeHeader *)&base_exec[offset];
-
- auto base_rw = (const char *)(*writable_ptr);
- code_hdr_rw = (BeamCodeHeader *)&base_rw[offset];
- }
-
- sys_memcpy(code_hdr_rw, in_hdr, sizeof(BeamCodeHeader));
- code_hdr_rw->on_load = getOnLoad();
-
- for (unsigned i = 0; i < functions.size(); i++) {
- ErtsCodeInfo *ci = (ErtsCodeInfo *)getCode(functions[i]);
- code_hdr_rw->functions[i] = ci;
- }
-
- char *module_end = (char *)code.baseAddress() + a.offset();
- code_hdr_rw->functions[functions.size()] = (ErtsCodeInfo *)module_end;
-
- *out_exec_hdr = code_hdr_exec;
- *out_rw_hdr = code_hdr_rw;
-}
-
-void BeamModuleAssembler::codegen(JitAllocator *allocator,
- const void **executable_ptr,
- void **writable_ptr) {
- _codegen(allocator, executable_ptr, writable_ptr);
-
-#ifndef WIN32
- if (functions.size()) {
- char *buff = (char *)erts_alloc(ERTS_ALC_T_TMP, 1024);
- std::vector<AsmRange> ranges;
- std::string name = getAtom(mod);
- ranges.reserve(functions.size() + 2);
-
- /* Push info about the header */
- ranges.push_back({.start = (ErtsCodePtr)getBaseAddress(),
- .stop = getCode(functions[0]),
- .name = name + "::codeHeader"});
-
- for (unsigned i = 0; i < functions.size(); i++) {
- ErtsCodePtr start, stop;
- const ErtsCodeInfo *ci;
- int n;
-
- start = getCode(functions[i]);
- ci = (const ErtsCodeInfo *)start;
-
- n = erts_snprintf(buff,
- 1024,
- "%T:%T/%d",
- ci->mfa.module,
- ci->mfa.function,
- ci->mfa.arity);
- stop = ((const char *)erts_codeinfo_to_code(ci)) +
- BEAM_ASM_FUNC_PROLOGUE_SIZE;
-
- /* We use a different symbol for CodeInfo and the Prologue
- in order for the perf disassembly to be better. */
- std::string name(buff, n);
- ranges.push_back({.start = start,
- .stop = stop,
- .name = name + "-CodeInfoPrologue"});
-
- /* The actual code */
- start = stop;
- if (i + 1 < functions.size()) {
- stop = getCode(functions[i + 1]);
- } else {
- stop = getCode(labels.size());
- }
-
- ranges.push_back({.start = start, .stop = stop, .name = name});
- }
-
- /* Push info about the footer */
- ranges.push_back(
- {.start = ranges.back().stop,
- .stop = (ErtsCodePtr)(code.baseAddress() + code.codeSize()),
- .name = name + "::codeFooter"});
-
- update_gdb_jit_info(name, ranges);
- beamasm_update_perf_info(name, ranges);
- erts_free(ERTS_ALC_T_TMP, buff);
- }
-#endif
-}
-
-void BeamModuleAssembler::codegen(char *buff, size_t len) {
- code.flatten();
- code.resolveUnresolvedLinks();
- ERTS_ASSERT(code.codeSize() <= len);
- code.relocateToBase((uint64_t)buff);
- code.copyFlattenedData(buff,
- code.codeSize(),
- CopySectionFlags::kPadSectionBuffer);
-}
-
-BeamCodeHeader *BeamModuleAssembler::getCodeHeader() {
- return (BeamCodeHeader *)getCode(codeHeader);
-}
-
-const ErtsCodeInfo *BeamModuleAssembler::getOnLoad() {
- if (on_load.isValid()) {
- return erts_code_to_codeinfo((ErtsCodePtr)getCode(on_load));
- } else {
- return 0;
- }
-}
-
unsigned BeamModuleAssembler::patchCatches(char *rw_base) {
unsigned catch_no = BEAM_CATCHES_NIL;
@@ -637,3 +473,13 @@ void BeamModuleAssembler::patchStrings(char *rw_base,
*where = string_table + patch.val_offs;
}
}
+
+const Label &BeamModuleAssembler::resolve_fragment(void (*fragment)()) {
+ auto it = _dispatchTable.find(fragment);
+
+ if (it == _dispatchTable.end()) {
+ it = _dispatchTable.emplace(fragment, a.newLabel()).first;
+ }
+
+ return it->second;
+}