Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/build-and-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
33 changes: 20 additions & 13 deletions libs/jit/src/jit_precompile.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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)).
Expand All @@ -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(<<Version:32, _Count:32, TypeData/binary>>) when Version =:= ?BEAM_TYPES_VERSION ->
Types = parse_type_entries(TypeData, []),
type_resolver(<<Version:32, _Count:32, TypeData/binary>>) 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, <<Header:16, Rest0/binary>>, 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>> = <<Header:16>>,
parse_type_entry(Version, TypeBits, HasLowerBound, HasUpperBound, HasUnit, Rest0, Acc);
parse_type_entries(?BEAM_TYPES_VERSION_V4 = Version, <<Header:16, Rest0/binary>>, 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`).
<<HasUnit:1, HasUpperBound:1, HasLowerBound:1, TypeBits:13>> = <<Header:16>>,
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
),
Expand Down Expand Up @@ -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, <<Value:64/signed, Rest/binary>>, '-inf', '+inf', 1) ->
parse_extra(0, HasUpperBound, HasUnit, Rest, Value, '+inf', 1);
Expand Down
64 changes: 46 additions & 18 deletions src/libAtomVM/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,13 @@
#include <zlib.h>
#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)
Expand All @@ -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
Expand Down Expand Up @@ -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"));
}

Expand All @@ -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;
Expand All @@ -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;
}
}
Expand All @@ -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:
Expand Down Expand Up @@ -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"));
Expand Down
31 changes: 31 additions & 0 deletions tests/libs/jit/jit_tests.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading