diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml index 5a6c2ac187..d1173af5bc 100644 --- a/.github/workflows/build-and-test.yaml +++ b/.github/workflows/build-and-test.yaml @@ -210,12 +210,12 @@ jobs: cmake_opts_other: "-DAVM_DISABLE_JIT=OFF" jit_target_arch: "x86_64" - # JIT build with OTP-29.0-rc1 + # JIT build with OTP-29.0 - os: "ubuntu-24.04" cc: "cc" cxx: "c++" cflags: "" - otp: "29.0-rc1" + otp: "29.0" version_type: "strict" elixir_version: "1.19.5" rebar3_version: "3.27.0" diff --git a/libs/jit/src/jit_precompile.erl b/libs/jit/src/jit_precompile.erl index 681bdabfe9..3477eb0a63 100644 --- a/libs/jit/src/jit_precompile.erl +++ b/libs/jit/src/jit_precompile.erl @@ -280,8 +280,9 @@ parse_imported_functions_chunk0( ImportedFunction = {Module, Function, Arity}, parse_imported_functions_chunk0(N - 1, Rest, AtomResolver, [ImportedFunction | Acc]). -%% Version (from beam_types.hrl) --define(BEAM_TYPES_VERSION, 3). +%% Versions (from beam_types.hrl). v3 is OTP < 29, v4 is OTP >= 29 +-define(BEAM_TYPES_VERSION_V3, 3). +-define(BEAM_TYPES_VERSION_V4, 4). %% Type chunk constants (from beam_types.erl) -define(BEAM_TYPE_ATOM, (1 bsl 0)). @@ -297,21 +298,27 @@ parse_imported_functions_chunk0( -define(BEAM_TYPE_REFERENCE, (1 bsl 10)). -define(BEAM_TYPE_TUPLE, (1 bsl 11)). --define(BEAM_TYPE_HAS_LOWER_BOUND, (1 bsl 12)). --define(BEAM_TYPE_HAS_UPPER_BOUND, (1 bsl 13)). --define(BEAM_TYPE_HAS_UNIT, (1 bsl 14)). - -type_resolver(<>) when Version =:= ?BEAM_TYPES_VERSION -> - Types = parse_type_entries(TypeData, []), +type_resolver(<>) when + Version =:= ?BEAM_TYPES_VERSION_V3; Version =:= ?BEAM_TYPES_VERSION_V4 +-> + Types = parse_type_entries(Version, TypeData, []), fun(Index) -> lists:nth(Index + 1, Types) end; type_resolver(_) -> fun(_) -> any end. -parse_type_entries(<<>>, Acc) -> +parse_type_entries(_Version, <<>>, Acc) -> lists:reverse(Acc); -parse_type_entries( - <<0:1, HasUnit:1, HasUpperBound:1, HasLowerBound:1, TypeBits:12, Rest0/binary>>, Acc -) -> +parse_type_entries(?BEAM_TYPES_VERSION_V3 = Version, <>, Acc) -> + %% v3: bit15 unused, bits 14/13/12 = unit/upper/lower flags, bits 0..11 type. + <<0:1, HasUnit:1, HasUpperBound:1, HasLowerBound:1, TypeBits:12>> = <>, + parse_type_entry(Version, TypeBits, HasLowerBound, HasUpperBound, HasUnit, Rest0, Acc); +parse_type_entries(?BEAM_TYPES_VERSION_V4 = Version, <>, Acc) -> + %% v4: bits 15/14/13 = unit/upper/lower flags, bits 0..12 type (bit 12 is + %% BEAM_TYPE_RECORD, which we treat as `any`). + <> = <>, + parse_type_entry(Version, TypeBits, HasLowerBound, HasUpperBound, HasUnit, Rest0, Acc). + +parse_type_entry(Version, TypeBits, HasLowerBound, HasUpperBound, HasUnit, Rest0, Acc) -> {Rest, LowerBound, UpperBound, Unit} = parse_extra( HasLowerBound, HasUpperBound, HasUnit, Rest0, '-inf', '+inf', 1 ), @@ -348,7 +355,7 @@ parse_type_entries( _ -> any end, - parse_type_entries(Rest, [Type | Acc]). + parse_type_entries(Version, Rest, [Type | Acc]). parse_extra(1, HasUpperBound, HasUnit, <>, '-inf', '+inf', 1) -> parse_extra(0, HasUpperBound, HasUnit, Rest, Value, '+inf', 1); diff --git a/src/libAtomVM/module.c b/src/libAtomVM/module.c index d75221dc5d..301d579492 100644 --- a/src/libAtomVM/module.c +++ b/src/libAtomVM/module.c @@ -53,8 +53,13 @@ #include #endif +// BEAM Types version from OTP source code: +// lib/compiler/src/beam_types.hrl +#define BEAM_TYPES_VERSION_V3 3 +#define BEAM_TYPES_VERSION_V4 4 + // BEAM Type constants from OTP source code: -// /opt/src/otp/lib/compiler/src/beam_types.erl lines 1446-1461 +// lib/compiler/src/beam_types.erl #define BEAM_TYPE_ATOM (1 << 0) #define BEAM_TYPE_BITSTRING (1 << 1) #define BEAM_TYPE_CONS (1 << 2) @@ -67,14 +72,18 @@ #define BEAM_TYPE_PORT (1 << 9) #define BEAM_TYPE_REFERENCE (1 << 10) #define BEAM_TYPE_TUPLE (1 << 11) - -#define BEAM_TYPE_HAS_LOWER_BOUND (1 << 12) -#define BEAM_TYPE_HAS_UPPER_BOUND (1 << 13) -#define BEAM_TYPE_HAS_UNIT (1 << 14) - -// BEAM Types version from OTP source code: -// /opt/src/otp/lib/compiler/src/beam_types.hrl line 22 -#define BEAM_TYPES_VERSION 3 +#define BEAM_TYPE_RECORD (1 << 12) // v4 only + +// v3 flag bits (OTP 27/28) +#define BEAM_TYPE_V3_HAS_LOWER_BOUND (1 << 12) +#define BEAM_TYPE_V3_HAS_UPPER_BOUND (1 << 13) +#define BEAM_TYPE_V3_HAS_UNIT (1 << 14) +#define BEAM_TYPE_V3_BITS_MASK 0xFFF +// v4 flag bits (OTP 29+) +#define BEAM_TYPE_V4_HAS_LOWER_BOUND (1 << 13) +#define BEAM_TYPE_V4_HAS_UPPER_BOUND (1 << 14) +#define BEAM_TYPE_V4_HAS_UNIT (1 << 15) +#define BEAM_TYPE_V4_BITS_MASK 0x1FFF #define LITT_UNCOMPRESSED_SIZE_OFFSET 8 #define LITT_HEADER_SIZE 12 @@ -1411,7 +1420,7 @@ term module_get_type_by_index(const Module *mod, int type_index, Context *ctx) uint32_t count = READ_32_UNALIGNED(types_data + 4); // Check if version is supported - if (version != BEAM_TYPES_VERSION) { + if (version != BEAM_TYPES_VERSION_V3 && version != BEAM_TYPES_VERSION_V4) { return globalcontext_make_atom(ctx->global, ATOM_STR("\x3", "any")); } @@ -1420,6 +1429,22 @@ term module_get_type_by_index(const Module *mod, int type_index, Context *ctx) return globalcontext_make_atom(ctx->global, ATOM_STR("\x3", "any")); } + uint16_t has_lower_bound_mask; + uint16_t has_upper_bound_mask; + uint16_t has_unit_mask; + uint16_t type_bits_mask; + if (version == BEAM_TYPES_VERSION_V4) { + has_lower_bound_mask = BEAM_TYPE_V4_HAS_LOWER_BOUND; + has_upper_bound_mask = BEAM_TYPE_V4_HAS_UPPER_BOUND; + has_unit_mask = BEAM_TYPE_V4_HAS_UNIT; + type_bits_mask = BEAM_TYPE_V4_BITS_MASK; + } else { + has_lower_bound_mask = BEAM_TYPE_V3_HAS_LOWER_BOUND; + has_upper_bound_mask = BEAM_TYPE_V3_HAS_UPPER_BOUND; + has_unit_mask = BEAM_TYPE_V3_HAS_UNIT; + type_bits_mask = BEAM_TYPE_V3_BITS_MASK; + } + // Skip to type data const uint8_t *type_entries = types_data + 8; const uint8_t *pos = type_entries; @@ -1430,13 +1455,13 @@ term module_get_type_by_index(const Module *mod, int type_index, Context *ctx) pos += 2; // Skip extra data if present - if (type_bits & BEAM_TYPE_HAS_LOWER_BOUND) { + if (type_bits & has_lower_bound_mask) { pos += 8; } - if (type_bits & BEAM_TYPE_HAS_UPPER_BOUND) { + if (type_bits & has_upper_bound_mask) { pos += 8; } - if (type_bits & BEAM_TYPE_HAS_UNIT) { + if (type_bits & has_unit_mask) { pos += 1; } } @@ -1452,24 +1477,24 @@ term module_get_type_by_index(const Module *mod, int type_index, Context *ctx) bool has_lower = false; bool has_upper = false; - if (type_bits & BEAM_TYPE_HAS_LOWER_BOUND) { + if (type_bits & has_lower_bound_mask) { lower_bound = (int64_t) READ_64_UNALIGNED(pos); pos += 8; has_lower = true; } - if (type_bits & BEAM_TYPE_HAS_UPPER_BOUND) { + if (type_bits & has_upper_bound_mask) { upper_bound = (int64_t) READ_64_UNALIGNED(pos); pos += 8; has_upper = true; } - if (type_bits & BEAM_TYPE_HAS_UNIT) { + if (type_bits & has_unit_mask) { unit = *pos + 1; // Stored as unit-1 pos += 1; } // Decode type based on TypeBits (matching jit_precompile.erl exact pattern matching) - // From OTP source code: /opt/src/otp/lib/compiler/src/beam_types.erl decode_type function - uint16_t type_pattern = type_bits & 0xFFF; // Mask out flags, keep type bits + // From OTP source code: lib/compiler/src/beam_types.erl decode_type function + uint16_t type_pattern = type_bits & type_bits_mask; switch (type_pattern) { case BEAM_TYPE_ATOM: @@ -1581,6 +1606,9 @@ term module_get_type_by_index(const Module *mod, int type_index, Context *ctx) case BEAM_TYPE_TUPLE: return globalcontext_make_atom(ctx->global, ATOM_STR("\x7", "t_tuple")); + case BEAM_TYPE_RECORD: + return globalcontext_make_atom(ctx->global, ATOM_STR("\x8", "t_record")); + default: // Default fallback for any other combination or union types return globalcontext_make_atom(ctx->global, ATOM_STR("\x3", "any")); diff --git a/tests/libs/jit/jit_tests.erl b/tests/libs/jit/jit_tests.erl index 81384748ee..f731bdac50 100644 --- a/tests/libs/jit/jit_tests.erl +++ b/tests/libs/jit/jit_tests.erl @@ -363,6 +363,37 @@ small_integer_bounds_test_() -> ?_assertEqual({-(1 bsl 27), (1 bsl 27) - 1}, jit:small_integer_bounds(jit_armv6m)) ]. +%% BEAM types format version 3 (OTP < 29). Bound/unit flags live at bits +%% 12/13/14; type bits are 0..11. +type_resolver_v3_test_() -> + IntLowerBoundEntry = <<((1 bsl 5) bor (1 bsl 12)):16, 2:64/signed>>, + Chunk = <<3:32, 1:32, IntLowerBoundEntry/binary>>, + R = jit_precompile:type_resolver(Chunk), + [?_assertEqual({t_integer, {2, '+inf'}}, R(0))]. + +%% BEAM types format version 4 (OTP >= 29). v4 inserts BEAM_TYPE_RECORD at bit +%% 12, pushing the bound/unit flags up to bits 13/14/15; type bits are 0..12. +%% Mirrors src/libAtomVM/module.c which already handles both versions. +type_resolver_v4_test_() -> + %% t_integer with lower bound 2, no upper bound. + IntLowerBound = <<((1 bsl 5) bor (1 bsl 13)):16, 2:64/signed>>, + %% t_integer with both bounds {2, 100}. + IntBothBounds = <<((1 bsl 5) bor (1 bsl 13) bor (1 bsl 14)):16, 2:64/signed, 100:64/signed>>, + %% t_bitstring with unit (HAS_UNIT at bit 15), unit byte stores unit-1. + BitstringUnit = <<((1 bsl 1) bor (1 bsl 15)):16, 0:8>>, + %% Plain atom, no extra bytes. + AtomEntry = <<(1 bsl 0):16>>, + Chunk = + <<4:32, 4:32, IntLowerBound/binary, IntBothBounds/binary, BitstringUnit/binary, + AtomEntry/binary>>, + R = jit_precompile:type_resolver(Chunk), + [ + ?_assertEqual({t_integer, {2, '+inf'}}, R(0)), + ?_assertEqual({t_integer, {2, 100}}, R(1)), + ?_assertEqual({t_bs_matchable, 1}, R(2)), + ?_assertEqual(t_atom, R(3)) + ]. + is_small_integer_range_test_() -> [ % Both ranges within 32-bit small integer bounds