erts/emulator/beam/jit/asm_load.c (630 lines of code) (raw):
/*
* %CopyrightBegin%
*
* Copyright Ericsson AB 2020-2020. 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* %CopyrightEnd%
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "sys.h"
#include "global.h"
#include "erl_binary.h"
#include "beam_catches.h"
#include "beam_load.h"
#include "erl_version.h"
#include "beam_bp.h"
#include "beam_asm.h"
static void init_label(Label *lp);
void beam_load_prepare_emit(LoaderState *stp) {
BeamCodeHeader *hdr;
int i;
stp->ba = beamasm_new_assembler(stp->module,
stp->beam.code.label_count,
stp->beam.code.function_count);
/* Initialize code header */
stp->codev_size = stp->beam.code.function_count + 1;
hdr = erts_alloc(ERTS_ALC_T_CODE,
(offsetof(BeamCodeHeader, functions) +
sizeof(BeamInstr) * stp->codev_size));
hdr->num_functions = stp->beam.code.function_count;
/* Let the codev array start at functions[0] in order to index
* both function pointers and the loaded code itself that follows.
*/
stp->codev = (BeamInstr *)&hdr->functions;
stp->ci = hdr->num_functions + 1;
hdr->attr_ptr = NULL;
hdr->attr_size = 0;
hdr->attr_size_on_heap = 0;
hdr->compile_ptr = NULL;
hdr->compile_size = 0;
hdr->compile_size_on_heap = 0;
hdr->literal_area = NULL;
hdr->md5_ptr = NULL;
stp->load_hdr = hdr;
stp->labels = erts_alloc(ERTS_ALC_T_PREPARED_CODE,
stp->beam.code.label_count * sizeof(Label));
for (i = 0; i < stp->beam.code.label_count; i++) {
init_label(&stp->labels[i]);
}
stp->bif_imports =
erts_alloc(ERTS_ALC_T_PREPARED_CODE,
stp->beam.imports.count * sizeof(BifEntry **));
for (i = 0; i < stp->beam.imports.count; i++) {
BeamFile_ImportEntry *import;
Export *export;
int bif_number;
import = &stp->beam.imports.entries[i];
export = erts_active_export_entry(import->module,
import->function,
import->arity);
stp->bif_imports[i] = NULL;
if (export) {
bif_number = export->bif_number;
if (bif_number >= 0) {
if (bif_number == BIF_load_nif_2) {
stp->may_load_nif = 1;
}
stp->bif_imports[i] = &bif_table[bif_number];
}
}
}
stp->current_li = 0;
if (stp->beam.lines.item_count > 0) {
stp->line_instr = erts_alloc(ERTS_ALC_T_PREPARED_CODE,
stp->beam.lines.instruction_count *
sizeof(LineInstr));
stp->func_line = erts_alloc(ERTS_ALC_T_PREPARED_CODE,
stp->beam.code.function_count *
sizeof(unsigned int));
}
}
static void init_label(Label *lp) {
sys_memset(lp, 0, sizeof(*lp));
}
void beam_load_prepared_free(Binary *magic) {
beam_load_prepared_dtor(magic);
erts_bin_release(magic);
}
/* This destructor function can safely be called multiple times. */
int beam_load_prepared_dtor(Binary *magic) {
LoaderState *stp = ERTS_MAGIC_BIN_DATA(magic);
/* This should have been freed earlier! */
ASSERT(stp->op_allocator.beamop_blocks == NULL);
beamfile_free(&stp->beam);
beamopallocator_dtor(&stp->op_allocator);
if (stp->bin) {
driver_free_binary(stp->bin);
stp->bin = NULL;
}
if (stp->load_hdr) {
BeamCodeHeader *hdr = stp->load_hdr;
if (hdr->literal_area) {
erts_release_literal_area(hdr->literal_area);
hdr->literal_area = NULL;
}
erts_free(ERTS_ALC_T_CODE, hdr);
stp->load_hdr = NULL;
stp->codev = NULL;
}
if (stp->labels) {
erts_free(ERTS_ALC_T_PREPARED_CODE, (void *)stp->labels);
stp->labels = NULL;
}
if (stp->bif_imports) {
erts_free(ERTS_ALC_T_PREPARED_CODE, stp->bif_imports);
stp->bif_imports = NULL;
}
if (stp->line_instr) {
erts_free(ERTS_ALC_T_PREPARED_CODE, stp->line_instr);
stp->line_instr = NULL;
}
if (stp->func_line) {
erts_free(ERTS_ALC_T_PREPARED_CODE, stp->func_line);
stp->func_line = NULL;
}
if (stp->ba) {
beamasm_delete_assembler(stp->ba);
stp->ba = NULL;
}
if (stp->native_module) {
beamasm_purge_module(stp->native_module);
stp->native_module = NULL;
}
return 1;
}
static int add_line_entry(LoaderState *stp,
BeamInstr item,
int insert_duplicates) {
int is_duplicate;
unsigned int li;
if (!stp->beam.lines.item_count) {
return 0;
}
if (item >= stp->beam.lines.item_count) {
BeamLoadError2(stp,
"line instruction index overflow (%u/%u)",
item,
stp->beam.lines.item_count);
}
li = stp->current_li;
if (li >= stp->beam.lines.instruction_count) {
BeamLoadError2(stp,
"line instruction table overflow (%u/%u)",
li,
stp->beam.lines.instruction_count);
}
is_duplicate = li && (stp->line_instr[li - 1].loc == item);
if (insert_duplicates || !is_duplicate) {
stp->line_instr[li].pos = beamasm_get_offset(stp->ba);
stp->line_instr[li].loc = item;
stp->current_li++;
}
return 0;
load_error:
return -1;
}
int beam_load_emit_op(LoaderState *stp, BeamOp *tmp_op) {
const char *sign;
int arg;
/*
* Verify and massage the operands for the specific instruction.
*
* After the massaging, TAG_i denotes a tagged immediate value,
* either NIL, an atom, or a small integer. The tags TAG_n and
* TAG_a are no longer used. TAG_f can either be a non-zero label
* number (in guards) or 0 (in function bodies) to indicate that
* an exception should be generated on failure. TAG_j is no longer
* used.
*/
sign = opc[stp->specific_op].sign;
arg = 0;
ASSERT(sign != NULL);
while (*sign) {
Uint tag;
BeamOpArg *curr = &stp->genop->a[arg];
ASSERT(arg < stp->genop->arity);
tag = curr->type;
switch (*sign) {
case 'n': /* Nil */
ASSERT(tag != TAG_r);
curr->type = TAG_i;
curr->val = NIL;
BeamLoadVerifyTag(stp, tag_to_letter[tag], *sign);
break;
case 'x': /* x(N) */
case 'y': /* y(N) */
BeamLoadVerifyTag(stp, tag_to_letter[tag], *sign);
break;
case 'a': /* Tagged atom */
BeamLoadVerifyTag(stp, tag_to_letter[tag], *sign);
curr->type = TAG_i;
break;
case 'c': /* Tagged constant */
switch (tag) {
case TAG_i:
curr->val = make_small((Uint)curr->val);
break;
case TAG_a:
curr->type = TAG_i;
break;
case TAG_n:
curr->val = NIL;
curr->type = TAG_i;
break;
case TAG_q:
break;
default:
BeamLoadError1(stp,
"bad tag %d for tagged constant",
curr->type);
break;
}
break;
case 's':
/* Any source (tagged constant or register) */
switch (tag) {
case TAG_x:
break;
case TAG_y:
break;
case TAG_i:
curr->val = (BeamInstr)make_small(curr->val);
break;
case TAG_a:
curr->type = TAG_i;
break;
case TAG_n:
curr->type = TAG_i;
curr->val = NIL;
break;
case TAG_q: {
Eterm term = beamfile_get_literal(&stp->beam, curr->val);
switch (loader_tag(term)) {
case LOADER_X_REG:
case LOADER_Y_REG:
BeamLoadError1(stp,
"the term '%T' would be confused "
"with a register",
term);
}
} break;
default:
BeamLoadError1(stp,
"bad tag %d for general source",
curr->type);
break;
}
break;
case 'd': /* Destination (x(N), y(N) */
case 'S': /* Source (x(N), y(N)) */
switch (tag) {
case TAG_x:
break;
case TAG_y:
break;
default:
BeamLoadError1(stp, "bad tag %d for destination", curr->type);
break;
}
break;
case 't': /* Small untagged integer (16 bits) -- can be packed. */
case 'I': /* Untagged integer (32 bits) -- can be packed. */
case 'W': /* Untagged integer or pointer (machine word). */
#ifdef DEBUG
switch (*sign) {
case 't':
/* 't'-typed values must fit in 16 bits. */
ASSERT((curr->val >> 16) == 0);
break;
# ifdef ARCH_64
case 'I':
case 'V':
/* 'I'- and 'V'-typed values must fit in 32 bits. */
ASSERT((curr->val >> 32) == 0);
break;
# endif
}
#endif
BeamLoadVerifyTag(stp, tag, TAG_u);
break;
case 'A': /* Arity value. */
BeamLoadVerifyTag(stp, tag, TAG_u);
curr->val = make_arityval(curr->val);
break;
case 'f': /* Destination label */
BeamLoadVerifyTag(stp, tag_to_letter[tag], *sign);
break;
case 'j': /* 'f' or 'p' */
switch (tag) {
case TAG_p:
curr->type = TAG_f;
curr->val = 0;
break;
case TAG_f:
break;
default:
BeamLoadError3(stp,
"bad tag %d; expected %d or %d",
tag,
TAG_f,
TAG_p);
}
break;
case 'L': /* Define label */
ASSERT(stp->specific_op == op_label_L ||
stp->specific_op == op_aligned_label_L);
BeamLoadVerifyTag(stp, tag, TAG_u);
stp->last_label = curr->val;
if (stp->last_label < 0 ||
stp->last_label >= stp->beam.code.label_count) {
BeamLoadError2(stp,
"invalid label num %u (0 < label < %u)",
curr->val,
stp->beam.code.label_count);
}
if (stp->labels[stp->last_label].value != 0) {
BeamLoadError1(stp,
"label %d defined more than once",
stp->last_label);
}
stp->labels[stp->last_label].value = 1;
break;
case 'e': /* Export entry */
BeamLoadVerifyTag(stp, tag, TAG_u);
if (curr->val >= stp->beam.imports.count) {
BeamLoadError1(stp, "invalid import table index %d", curr->val);
}
curr->type = TAG_r;
break;
case 'b': {
int i = tmp_op->a[arg].val;
BeamLoadVerifyTag(stp, tag, TAG_u);
if (i >= stp->beam.imports.count) {
BeamLoadError1(stp, "invalid import table index %d", i);
} else if (stp->bif_imports[i] == NULL) {
BeamLoadError1(stp, "import %d not a BIF", i);
} else {
curr->val = (BeamInstr)stp->bif_imports[i]->f;
}
} break;
case 'P': /* Byte offset into tuple */
BeamLoadVerifyTag(stp, tag, TAG_u);
curr->val = (BeamInstr)((curr->val + 1) * sizeof(Eterm));
break;
case 'l': /* Floating point register. */
BeamLoadVerifyTag(stp, tag_to_letter[tag], *sign);
curr->val = curr->val * sizeof(FloatDef);
break;
case 'q': /* Literal */
BeamLoadVerifyTag(stp, tag, TAG_q);
break;
case 'F': /* Fun entry */
BeamLoadVerifyTag(stp, tag, TAG_u);
break;
default:
BeamLoadError1(stp, "bad argument tag: %d", *sign);
}
sign++;
arg++;
}
/*
* Verify and massage any list arguments according to the primitive tags.
*
* TAG_i will denote a tagged immediate value (NIL, small integer,
* atom, or tuple arity). TAG_n, TAG_a, and TAG_v will no longer be used.
*/
for (; arg < tmp_op->arity; arg++) {
BeamOpArg *curr = &tmp_op->a[arg];
switch (tmp_op->a[arg].type) {
case TAG_i:
curr->val = make_small(tmp_op->a[arg].val);
break;
case TAG_n:
curr->val = NIL;
curr->type = TAG_i;
break;
case TAG_a:
case TAG_v:
curr->type = TAG_i;
break;
case TAG_u:
case TAG_f:
case TAG_x:
case TAG_y:
case TAG_q:
break;
default:
BeamLoadError1(stp,
"unsupported primitive type '%c'",
tag_to_letter[tmp_op->a[arg].type]);
}
}
/* Handle a few special cases. */
switch (stp->specific_op) {
case op_i_func_info_IaaI:
if (stp->function_number >= stp->beam.code.function_count) {
BeamLoadError1(stp,
"too many functions in module (header said %u)",
stp->beam.code.function_count);
}
/* Save current offset in the function table, pointing before the
* func_info instruction. */
stp->codev[stp->function_number] = beamasm_get_offset(stp->ba);
stp->function_number++;
/* Save context for error messages. */
stp->function = tmp_op->a[2].val;
stp->arity = tmp_op->a[3].val;
if (stp->arity > MAX_ARG) {
BeamLoadError1(stp, "too many arguments: %d", stp->arity);
}
break;
case op_func_line_I:
/* This is the first line instruction of a function, preceding
* the func_info instruction. */
if (stp->func_line) {
stp->func_line[stp->function_number] = stp->current_li;
}
break;
}
/* Generate assembly code for the specific instruction. */
if (beamasm_emit(stp->ba, stp->specific_op, tmp_op) == 0) {
BeamLoadError1(stp, "failed to emit asm for %d", stp->specific_op);
}
switch (stp->specific_op) {
case op_func_line_I:
/* Since this is the beginning of a new function, force insertion
* of the line entry even if it happens to be a duplicate of the
* previous one. */
if (add_line_entry(stp, tmp_op->a[0].val, 1)) {
goto load_error;
}
break;
case op_line_I:
/* We'll save some memory by not inserting a line entry that
* is equal to the previous one. */
if (add_line_entry(stp, tmp_op->a[0].val, 0)) {
goto load_error;
}
break;
case op_int_code_end:
/* End of code found. */
if (stp->function_number != stp->beam.code.function_count) {
BeamLoadError2(stp,
"too few functions (%u) in module (header said %u)",
stp->function_number,
stp->beam.code.function_count);
}
stp->function = THE_NON_VALUE;
stp->genop = NULL;
stp->specific_op = -1;
}
return 1;
load_error:
return 0;
}
int beam_load_finish_emit(LoaderState *stp) {
BeamCodeHeader *code_hdr = NULL;
Sint decoded_size;
int i;
char *module_base;
size_t module_size;
if (stp->line_instr != 0) {
Uint line_size = offsetof(BeamCodeLineTab, func_tab);
/* func_tab */
line_size += (stp->beam.code.function_count + 1) * sizeof(BeamInstr **);
/* line items */
line_size += (stp->current_li + 1) * sizeof(BeamInstr *);
/* fname table */
line_size += stp->beam.lines.name_count * sizeof(Eterm);
/* loc_tab */;
line_size += (stp->current_li + 1) * stp->beam.lines.location_size;
beamasm_embed_bss(stp->ba, "line", line_size);
}
/* Place the string table and, optionally, attributes here. */
beamasm_embed_rodata(stp->ba,
"str",
(const char *)stp->beam.strings.data,
stp->beam.strings.size);
beamasm_embed_rodata(stp->ba,
"attr",
(const char *)stp->beam.attributes.data,
stp->beam.attributes.size);
beamasm_embed_rodata(stp->ba,
"compile",
(const char *)stp->beam.compile_info.data,
stp->beam.compile_info.size);
beamasm_embed_rodata(stp->ba,
"md5",
(const char *)stp->beam.checksum,
sizeof(stp->beam.checksum));
/* Move the code to its final location. */
stp->native_module =
beamasm_codegen(stp->ba, stp->load_hdr, &stp->code_hdr);
code_hdr = stp->code_hdr;
module_base = beamasm_get_base(stp->ba);
module_size = beamasm_get_offset(stp->ba);
stp->on_load = beamasm_get_on_load(stp->ba);
/* If there is line information, place it here. This must be done after
* code generation to make sure the addresses are correct.
*
* Ideally we'd want this to be embedded in the module itself just like the
* string table is and use offsets rather than absolute addresses, but this
* will do for now. */
if (stp->line_instr == 0) {
code_hdr->line_table = NULL;
} else {
const unsigned int ftab_size = stp->beam.code.function_count;
const unsigned int num_instrs = stp->current_li;
const void *locp_base;
BeamCodeLineTab *const line_tab =
(BeamCodeLineTab *)beamasm_get_rodata(stp->ba, "line");
const void **const line_items =
(const void **)&line_tab->func_tab[ftab_size + 1];
code_hdr->line_table = line_tab;
for (i = 0; i < ftab_size; i++) {
line_tab->func_tab[i] = line_items + stp->func_line[i];
}
line_tab->func_tab[i] = line_items + num_instrs;
for (i = 0; i < num_instrs; i++) {
line_items[i] = (void *)&module_base[stp->line_instr[i].pos];
}
line_items[i] = (void *)&module_base[module_size];
line_tab->fname_ptr = (Eterm *)&line_items[i + 1];
if (stp->beam.lines.name_count) {
sys_memcpy(line_tab->fname_ptr,
stp->beam.lines.names,
stp->beam.lines.name_count * sizeof(Eterm));
}
locp_base = &line_tab->fname_ptr[stp->beam.lines.name_count];
line_tab->loc_size = stp->beam.lines.location_size;
if (stp->beam.lines.location_size == sizeof(Uint16)) {
Uint16 *locp = (Uint16 *)locp_base;
line_tab->loc_tab.p2 = locp;
for (i = 0; i < num_instrs; i++) {
BeamFile_LineEntry *entry;
int idx;
idx = stp->line_instr[i].loc;
entry = &stp->beam.lines.items[idx];
*locp++ = MAKE_LOCATION(entry->name_index, entry->location);
}
*locp++ = LINE_INVALID_LOCATION;
} else {
Uint32 *locp = (Uint32 *)locp_base;
line_tab->loc_tab.p4 = locp;
ASSERT(stp->beam.lines.location_size == sizeof(Uint32));
for (i = 0; i < num_instrs; i++) {
BeamFile_LineEntry *entry;
int idx;
idx = stp->line_instr[i].loc;
entry = &stp->beam.lines.items[idx];
*locp++ = MAKE_LOCATION(entry->name_index, entry->location);
}
*locp++ = LINE_INVALID_LOCATION;
}
}
/*
* Place the literals in their own allocated heap (for fast range check)
* and fix up all instructions that refer to it.
*/
{
Eterm *ptr;
ErlOffHeap code_off_heap;
ErtsLiteralArea *literal_area;
Uint tot_lit_size;
Uint lit_asize;
tot_lit_size = stp->beam.static_literals.heap_size +
stp->beam.dynamic_literals.heap_size;
ERTS_INIT_OFF_HEAP(&code_off_heap);
lit_asize = ERTS_LITERAL_AREA_ALLOC_SIZE(tot_lit_size);
literal_area = erts_alloc(ERTS_ALC_T_LITERAL, lit_asize);
ptr = &literal_area->start[0];
literal_area->end = ptr + tot_lit_size;
beamfile_move_literals(&stp->beam, &ptr, &code_off_heap);
/* MAJOR HACK: literal identifiers are opaque at the moment. Make it
* more like string patching. */
for (i = 0; i < stp->beam.static_literals.count; i++) {
Eterm lit = beamfile_get_literal(&stp->beam, i);
beamasm_patch_literal(stp->ba, i, lit);
}
for (i = 0; i < stp->beam.dynamic_literals.count; i++) {
Eterm lit = beamfile_get_literal(&stp->beam, ~i);
beamasm_patch_literal(stp->ba, ~i, lit);
}
literal_area->off_heap = code_off_heap.first;
code_hdr->literal_area = literal_area;
/* Ensure deallocation of literals in case the prepared code is
* deallocated (without calling erlang:finish_loading/1). */
stp->load_hdr->literal_area = literal_area;
}
if (stp->beam.attributes.size) {
byte *attr = beamasm_get_rodata(stp->ba, "attr");
code_hdr->attr_ptr = attr;
code_hdr->attr_size = stp->beam.attributes.size;
decoded_size = erts_decode_ext_size(attr, code_hdr->attr_size);
if (decoded_size < 0) {
BeamLoadError0(stp,
"bad external term representation of module "
"attributes");
}
code_hdr->attr_size_on_heap = decoded_size;
}
if (stp->beam.compile_info.size) {
byte *compile_info = beamasm_get_rodata(stp->ba, "compile");
code_hdr->compile_ptr = compile_info;
code_hdr->compile_size = stp->beam.compile_info.size;
decoded_size =
erts_decode_ext_size(compile_info, stp->beam.compile_info.size);
if (decoded_size < 0) {
BeamLoadError0(stp,
"bad external term representation of compilation "
"information");
}
code_hdr->compile_size_on_heap = decoded_size;
}
{
byte *md5_sum = beamasm_get_rodata(stp->ba, "md5");
sys_memcpy(md5_sum, stp->beam.checksum, sizeof(stp->beam.checksum));
code_hdr->md5_ptr = md5_sum;
}
/* Patch all instructions that refer to the string table. */
if (stp->beam.strings.size) {
beamasm_patch_strings(stp->ba, beamasm_get_rodata(stp->ba, "str"));
}
/* Save the updated code pointer and code size. */
stp->codev = (BeamInstr *)&code_hdr->functions;
stp->loaded_size = module_size;
return 1;
load_error:
return 0;
}
void beam_load_finalize_code(LoaderState *stp,
struct erl_module_instance *inst_p) {
int staging_ix, code_size, i;
code_size = beamasm_get_header(stp->ba, &stp->code_hdr);
erts_total_code_size += code_size;
inst_p->native_module = stp->native_module;
inst_p->code_hdr = stp->code_hdr;
inst_p->code_length = code_size;
/* Update ranges (used for finding a function from a PC value). */
erts_update_ranges((BeamInstr *)inst_p->code_hdr, code_size);
/* Prevent code from being freed. */
stp->native_module = NULL;
stp->code_hdr = NULL;
stp->codev = NULL;
stp->load_hdr->literal_area = NULL;
staging_ix = erts_staging_code_ix();
/* Allocate catch indices and fix up all catch_yf instructions. */
inst_p->catches = beamasm_get_catches(stp->ba);
/* Exported functions */
for (i = 0; i < stp->beam.exports.count; i++) {
BeamFile_ExportEntry *entry = &stp->beam.exports.entries[i];
BeamInstr *address;
Export *ep;
address = beamasm_get_code(stp->ba, entry->label);
ep = erts_export_put(stp->module, entry->function, entry->arity);
if (stp->on_load) {
/* on_load: Don't make any of the exported functions
* callable yet. Keep any function in the current
* code callable. */
ep->trampoline.not_loaded.deferred = (BeamInstr)address;
} else {
ep->addressv[staging_ix] = address;
}
}
/* Patch external function calls, this is done after exporting functions as
* the module may remote-call itself*/
for (i = 0; i < stp->beam.imports.count; i++) {
BeamFile_ImportEntry *entry = &stp->beam.imports.entries[i];
BeamInstr import;
import = (BeamInstr)erts_export_put(entry->module,
entry->function,
entry->arity);
beamasm_patch_import(stp->ba, i, import);
}
/* Patch fun creation. */
if (stp->beam.lambdas.count) {
BeamFile_LambdaTable *lambda_table = &stp->beam.lambdas;
for (i = 0; i < lambda_table->count; i++) {
BeamFile_LambdaEntry *lambda;
ErlFunEntry *fun_entry;
lambda = &lambda_table->entries[i];
fun_entry = erts_put_fun_entry2(stp->module,
lambda->old_uniq,
i,
stp->beam.checksum,
lambda->index,
lambda->arity - lambda->num_free);
if (fun_entry->address[0] != 0) {
/* We've reloaded a module over itself and inherited the old
* instance's fun entries, so we need to undo the reference
* bump in `erts_put_fun_entry2` to make fun purging work. */
erts_refc_dectest(&fun_entry->refc, 1);
}
fun_entry->address = beamasm_get_code(stp->ba, lambda->label);
beamasm_patch_lambda(stp->ba, i, (BeamInstr)fun_entry);
}
}
}