// -*- c -*- // // %CopyrightBegin% // // Copyright Ericsson AB 2020-2021. 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% // NewBeamOp(stp, op) { $op = beamopallocator_new_op(&($stp)->op_allocator); $op->next = NULL; } BeamOpNameArity(op, name, arity) { #define __make_opname__(NAME, ARITY) genop_##NAME##_##ARITY $op->op = __make_opname__($name, $arity); $op->arity = $arity; #undef __make_opname__ } BeamOpArity(op, arity) { ASSERT($op->a == $op->def_args); $op->arity = $arity; $op->a = erts_alloc(ERTS_ALC_T_LOADER_TMP, $op->arity * sizeof(BeamOpArg)); } NativeEndian(flags) { #if defined(WORDS_BIGENDIAN) if (($flags).val & BSF_NATIVE) { ($flags).val &= ~(BSF_LITTLE|BSF_NATIVE); } #else if (($flags).val & BSF_NATIVE) { ($flags).val &= ~BSF_NATIVE; ($flags).val |= BSF_LITTLE; } #endif } gen_bs_save_restore(reg, index, instr) { BeamOp* op; $NewBeamOp(S, op); $BeamOpNameArity(op, $instr, 2); op->a[0] = $reg; op->a[1] = $index; if (Index.type == TAG_u) { op->a[1].val = $index.val+1; } else if ($index.type == TAG_a && $index.val == am_start) { op->a[1].type = TAG_u; op->a[1].val = 0; } return op; } gen.bs_save(Reg, Index) { $gen_bs_save_restore(Reg, Index, i_bs_save2); } gen.bs_restore(Reg, Index) { $gen_bs_save_restore(Reg, Index, i_bs_restore2); } // Generate an instruction to fetch a float from a binary. gen.get_float2(Fail, Ms, Live, Size, Unit, Flags, Dst) { BeamOp* op; $NewBeamOp(S, op); $NativeEndian(Flags); $BeamOpNameArity(op, i_bs_get_float2, 6); op->a[0] = Ms; op->a[1] = Fail; op->a[2] = Live; op->a[3] = Size; op->a[4].type = TAG_u; op->a[4].val = (Unit.val << 3) | Flags.val; op->a[5] = Dst; return op; } gen.get_utf16(Fail, Ms, Flags, Dst) { BeamOp* op; $NewBeamOp(S, op); $NativeEndian(Flags); $BeamOpNameArity(op, i_bs_get_utf16, 4); op->a[0] = Ms; op->a[1] = Fail; op->a[2] = Flags; op->a[3] = Dst; return op; } gen.put_binary(Fail, Size, Unit, Flags, Src) { BeamOp* op; $NewBeamOp(S, op); $NativeEndian(Flags); if (Size.type == TAG_a && Size.val == am_all) { $BeamOpNameArity(op, i_new_bs_put_binary_all, 3); op->a[0] = Src; op->a[1] = Fail; op->a[2] = Unit; } else if (Size.type == TAG_i) { $BeamOpNameArity(op, i_new_bs_put_binary_imm, 3); op->a[0] = Fail; op->a[1].type = TAG_u; if (beam_load_safe_mul(Size.val, Unit.val, &op->a[1].val)) { op->a[2] = Src; } else { error: /* * Invalid size. This instruction can't possibly be * reached, because bs_add or bs_init* would already * have raised a system_limit exception. */ $BeamOpNameArity(op, delete_me, 0); } } else if (Size.type == TAG_q) { #ifdef ARCH_64 /* * There is no way that this binary would fit in memory. */ goto error; #else Eterm big = beamfile_get_literal(&S->beam, Size.val); Uint bigval; Uint size; if (!term_to_Uint(big, &bigval) || !beam_load_safe_mul(bigval, Unit.val, &size)) { goto error; } $BeamOpNameArity(op, i_new_bs_put_binary_imm, 3); op->a[0] = Fail; op->a[1].type = TAG_u; op->a[1].val = size; op->a[2] = Src; #endif } else { $BeamOpNameArity(op, i_new_bs_put_binary, 4); op->a[0] = Fail; op->a[1] = Size; op->a[2].type = TAG_u; op->a[2].val = (Unit.val << 3) | (Flags.val & 7); op->a[3] = Src; } return op; } gen.put_integer(Fail, Size, Unit, Flags, Src) { BeamOp* op; $NewBeamOp(S, op); $NativeEndian(Flags); /* Negative size must fail */ if (Size.type == TAG_i) { Uint size; if (!beam_load_safe_mul(Size.val, Unit.val, &size)) { error: /* * Invalid size. This instruction can't possibly be * reached, because bs_add or bs_init* would already * have raised a system_limit exception. */ $BeamOpNameArity(op, delete_me, 0); return op; } $BeamOpNameArity(op, i_new_bs_put_integer_imm, 4); op->a[0] = Src; op->a[1] = Fail; op->a[2].type = TAG_u; op->a[2].val = size; op->a[3].type = Flags.type; op->a[3].val = (Flags.val & 7); } else if (Size.type == TAG_q) { Eterm big = beamfile_get_literal(&S->beam, Size.val); Uint bigval; Uint size; if (!term_to_Uint(big, &bigval) || !beam_load_safe_mul(bigval, Unit.val, &size)) { goto error; } $BeamOpNameArity(op, i_new_bs_put_integer_imm, 4); op->a[0] = Src; op->a[1] = Fail; op->a[2].type = TAG_u; op->a[2].val = size; op->a[3].type = Flags.type; op->a[3].val = (Flags.val & 7); } else { $BeamOpNameArity(op, i_new_bs_put_integer, 4); op->a[0] = Fail; op->a[1] = Size; op->a[2].type = TAG_u; op->a[2].val = (Unit.val << 3) | (Flags.val & 7); op->a[3] = Src; } return op; } gen.put_float(Fail, Size, Unit, Flags, Src) { BeamOp* op; $NewBeamOp(S, op); $NativeEndian(Flags); if (Size.type == TAG_i) { $BeamOpNameArity(op, i_new_bs_put_float_imm, 4); op->a[0] = Fail; op->a[1].type = TAG_u; if (!beam_load_safe_mul(Size.val, Unit.val, &op->a[1].val)) { /* * Size overflow. This instruction can't possibly be reached, because * bs_add or bs_init* would already have raised a system_limit * exception. */ $BeamOpNameArity(op, delete_me, 0); } else { op->a[2] = Flags; op->a[3] = Src; } } else { $BeamOpNameArity(op, i_new_bs_put_float, 4); op->a[0] = Fail; op->a[1] = Size; op->a[2].type = TAG_u; op->a[2].val = (Unit.val << 3) | (Flags.val & 7); op->a[3] = Src; } return op; } gen.put_utf16(Fail, Flags, Src) { BeamOp* op; $NewBeamOp(S, op); $NativeEndian(Flags); $BeamOpNameArity(op, i_bs_put_utf16, 3); op->a[0] = Fail; op->a[1] = Flags; op->a[2] = Src; return op; } // Generate the fastest instruction for bs_skip_bits. gen.skip_bits2(Fail, Ms, Size, Unit, Flags) { BeamOp* op; $NewBeamOp(S, op); $NativeEndian(Flags); if (Size.type == TAG_a && Size.val == am_all) { /* * This kind of skip instruction will only be found in modules * compiled before OTP 19. From OTP 19, the compiler generates * a test_unit instruction of a bs_skip at the end of a * binary. * * It is safe to replace the skip instruction with a test_unit * instruction, because the position will never be used again. * If the match context itself is used again, it will be used by * a bs_restore2 instruction which will overwrite the position * by one of the stored positions. */ $BeamOpNameArity(op, bs_test_unit, 3); op->a[0] = Fail; op->a[1] = Ms; op->a[2] = Unit; } else if (Size.type == TAG_i) { $BeamOpNameArity(op, i_bs_skip_bits_imm2, 3); op->a[0] = Fail; op->a[1] = Ms; op->a[2].type = TAG_u; if (!beam_load_safe_mul(Size.val, Unit.val, &op->a[2].val)) { goto error; } } else if (Size.type == TAG_q) { Eterm big = beamfile_get_literal(&S->beam, Size.val); Uint bigval; if (!term_to_Uint(big, &bigval)) { error: $BeamOpNameArity(op, jump, 1); op->a[0] = Fail; } else { $BeamOpNameArity(op, i_bs_skip_bits_imm2, 3); op->a[0] = Fail; op->a[1] = Ms; op->a[2].type = TAG_u; if (!beam_load_safe_mul(bigval, Unit.val, &op->a[2].val)) { goto error; } } } else if (Size.type == TAG_x || Size.type == TAG_y) { $BeamOpNameArity(op, i_bs_skip_bits2, 4); op->a[0] = Ms; op->a[1] = Size; op->a[2] = Fail; op->a[3] = Unit; } else { /* * Invalid literal size. Can only happen if compiler * optimizations are selectively disabled. For example, * at the time of writing, [no_copt, no_type_opt] will allow * skip instructions with invalid sizes to slip through. */ goto error; } return op; } gen_increment(stp, reg, val, dst) { BeamOp* op; $NewBeamOp($stp, op); $BeamOpNameArity(op, i_increment, 3); op->a[0] = $reg; op->a[1].type = TAG_u; op->a[1].val = $val; op->a[2] = $dst; return op; } gen.increment(Reg, Integer, Dst) { $gen_increment(S, Reg, Integer.val, Dst); } gen.increment_from_minus(Reg, Integer, Dst) { $gen_increment(S, Reg, -Integer.val, Dst); } gen.plus_from_minus(Fail, Live, Src, Integer, Dst) { BeamOp* op; ASSERT(Integer.type == TAG_i && IS_SSMALL(-(Sint)Integer.val)); $NewBeamOp(S, op); $BeamOpNameArity(op, gen_plus, 5); op->a[0] = Fail; op->a[1] = Live; op->a[2] = Src; op->a[3].type = TAG_i; op->a[3].val = -(Sint)Integer.val; op->a[4] = Dst; return op; } gen.make_fun2(idx) { BeamOp* op; BeamOp* th; $NewBeamOp(S, op); if (idx.val >= S->beam.lambdas.count) { $BeamOpNameArity(op, i_lambda_error, 1); op->a[0].type = TAG_o; op->a[0].val = 0; return op; } else { int i; BeamFile_LambdaEntry *entry = &S->beam.lambdas.entries[idx.val]; unsigned num_free = entry->num_free; $NewBeamOp(S, th); $BeamOpNameArity(th, test_heap, 2); th->a[0].type = TAG_u; th->a[0].val = ERL_FUN_SIZE + num_free; th->a[1].type = TAG_u; th->a[1].val = num_free; th->next = op; $BeamOpNameArity(op, i_make_fun3, 3); $BeamOpArity(op, 3 + num_free); op->a[0].type = TAG_u; op->a[0].val = idx.val; op->a[1].type = TAG_x; op->a[1].val = 0; op->a[2].type = TAG_u; op->a[2].val = num_free; for (i = 0; i < num_free; i++) { op->a[i+3].type = TAG_x; op->a[i+3].val = i; } return th; } } gen.make_fun3(idx, Dst, NumFree, Env) { BeamOp* op; $NewBeamOp(S, op); if (idx.val < S->beam.lambdas.count) { BeamFile_LambdaEntry *entry = &S->beam.lambdas.entries[idx.val]; if (NumFree.val == entry->num_free) { int i; $BeamOpNameArity(op, i_make_fun3, 3); $BeamOpArity(op, 3 + NumFree.val); op->a[0].type = TAG_u; op->a[0].val = idx.val; op->a[1] = Dst; op->a[2].type = TAG_u; op->a[2].val = entry->num_free; for (i = 0; i < NumFree.val; i++) { op->a[i+3] = Env[i]; } return op; } } $BeamOpNameArity(op, i_lambda_error, 1); op->a[0].type = TAG_o; op->a[0].val = 0; return op; } gen.tuple_append_put5(Arity, Dst, Puts, S1, S2, S3, S4, S5) { BeamOp* op; int arity = Arity.val; /* Arity of tuple, not the instruction */ int i; $NewBeamOp(S, op); $BeamOpNameArity(op, i_put_tuple, 2); $BeamOpArity(op, arity+2+5); op->a[0] = Dst; op->a[1].type = TAG_u; op->a[1].val = arity + 5; for (i = 0; i < arity; i++) { op->a[i+2] = Puts[i]; } op->a[arity+2] = S1; op->a[arity+3] = S2; op->a[arity+4] = S3; op->a[arity+5] = S4; op->a[arity+6] = S5; return op; } gen.tuple_append_put(Arity, Dst, Puts, Src) { BeamOp* op; int arity = Arity.val; /* Arity of tuple, not the instruction */ int i; $NewBeamOp(S, op); $BeamOpNameArity(op, i_put_tuple, 2); $BeamOpArity(op, arity+2+1); op->a[0] = Dst; op->a[1].type = TAG_u; op->a[1].val = arity + 1; for (i = 0; i < arity; i++) { op->a[i+2] = Puts[i]; } op->a[arity+2] = Src; return op; } // Generate an instruction for get/1. gen.get(Src, Dst) { BeamOp* op; Eterm key_term; $NewBeamOp(S, op); key_term = beam_load_get_term(S, Src); if (is_value(key_term)) { $BeamOpNameArity(op, i_get_hash, 3); op->a[0] = Src; op->a[1].type = TAG_u; op->a[1].val = (BeamInstr) erts_pd_make_hx(key_term); op->a[2] = Dst; } else { $BeamOpNameArity(op, i_get, 2); op->a[0] = Src; op->a[1] = Dst; } return op; } // Replace a get_map_elements instruction with a single key to an // instruction with one element. gen.get_map_element(Fail, Src, Size, Rest) { BeamOp* op; BeamOpArg Key; Eterm key_term; ASSERT(Size.type == TAG_u); $NewBeamOp(S, op); op->a[0] = Fail; op->a[1] = Src; op->a[2] = Rest[0]; Key = Rest[0]; key_term = beam_load_get_term(S, Key); if (is_value(key_term)) { $BeamOpNameArity(op, i_get_map_element_hash, 5); op->a[3].type = TAG_u; op->a[3].val = (BeamInstr) hashmap_make_hash(key_term); op->a[4] = Rest[1]; } else { $BeamOpNameArity(op, i_get_map_element, 4); op->a[3] = Rest[1]; } return op; } gen.get_map_elements(Fail, Src, Size, Rest) { BeamOp* op; Uint i; BeamOpArg* dst; Eterm key_term; ASSERT(Size.type == TAG_u); $NewBeamOp(S, op); $BeamOpNameArity(op, i_get_map_elements, 3); $BeamOpArity(op, 3 + 3*(Size.val/2)); op->a[0] = Fail; op->a[1] = Src; op->a[2].type = TAG_u; op->a[2].val = 3*(Size.val/2); dst = op->a+3; for (i = 0; i < Size.val / 2; i++) { dst[0] = Rest[2*i]; dst[1] = Rest[2*i+1]; dst[2].type = TAG_u; key_term = beam_load_get_term(S, dst[0]); dst[2].val = (BeamInstr) hashmap_make_hash(key_term); dst += 3; } return op; } gen.has_map_fields(Fail, Src, Size, Rest) { BeamOp* op; Uint i; Uint n; ASSERT(Size.type == TAG_u); n = Size.val; $NewBeamOp(S, op); $BeamOpNameArity(op, get_map_elements, 3); $BeamOpArity(op, 3 + 2*n); op->a[0] = Fail; op->a[1] = Src; op->a[2].type = TAG_u; op->a[2].val = 2*n; for (i = 0; i < n; i++) { op->a[3+2*i] = Rest[i]; op->a[3+2*i+1].type = TAG_x; op->a[3+2*i+1].val = SCRATCH_X_REG; /* Ignore result */ } return op; }