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
8 changes: 6 additions & 2 deletions .github/workflows/run-tests-with-beam.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,11 @@ jobs:
if: runner.os == 'Linux'
run: |
apt update -y
apt install -y cmake gperf zlib1g-dev ninja-build
apt install -y cmake gperf zlib1g-dev ninja-build \
binutils-aarch64-linux-gnu \
binutils-arm-linux-gnueabihf \
binutils-riscv64-linux-gnu \
wabt

- name: "Normalize ImageOS for setup-beam"
if: matrix.os == 'macos-26'
Expand All @@ -107,7 +111,7 @@ jobs:

- name: "Install deps (macOS)"
if: runner.os == 'macOS'
run: brew update && HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew install gperf mbedtls@3
run: brew update && HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew install gperf mbedtls@3 aarch64-elf-binutils arm-none-eabi-binutils riscv64-elf-binutils wabt

- name: "macOS setup mbedtls@3 environment"
if: runner.os == 'macOS'
Expand Down
5 changes: 0 additions & 5 deletions libs/jit/src/jit_arm32_asm.erl
Original file line number Diff line number Diff line change
Expand Up @@ -525,11 +525,6 @@ push(RegList) ->
%% POP {reglist} = LDMIA SP!, {reglist}
%% Encoding: cond[31:28] 100 0 1 0 1 1 1101 reglist[15:0]
-spec pop([arm_gpr_register()]) -> binary().
pop([sp]) ->
%% GNU as uses LDMIA for pop {sp} (LDR SP,[SP],#4 has undefined behavior)
RegMask = 1 bsl 13,
Instr = (14 bsl 28) bor (2#100010111101 bsl 16) bor RegMask,
<<Instr:32/little>>;
pop([Reg]) ->
%% LDR Rd, [SP], #4: single-register pop (matches GNU as encoding)
RegNum = reg_to_num(Reg),
Expand Down
6 changes: 4 additions & 2 deletions libs/jit/src/jit_x86_64_asm.erl
Original file line number Diff line number Diff line change
Expand Up @@ -570,12 +570,14 @@ xorl(SrcReg, DestReg) when is_atom(SrcReg), is_atom(DestReg) ->
_ -> <<(16#40 bor (REX_R bsl 2) bor REX_B), 16#31, 3:2, MODRM_REG:3, MODRM_RM:3>>
end).

xorq(Imm, DestReg) when is_integer(Imm), Imm >= 16#80000000, Imm < 16#100000000, is_atom(DestReg) ->
xorq(Imm - 16#100000000, DestReg);
xorq(Imm, DestReg) when ?IS_SINT8_T(Imm) andalso is_atom(DestReg) ->
{REX_B, MODRM_RM} = x86_64_x_reg(DestReg),
<<?X86_64_REX(1, 0, 0, REX_B), 16#83, 3:2, 6:3, MODRM_RM:3, Imm>>;
xorq(Imm, rax) when ?IS_UINT32_T(Imm) ->
xorq(Imm, rax) when ?IS_SINT32_T(Imm) ->
<<?X86_64_REX(1, 0, 0, 0), 16#35, Imm:32/little>>;
xorq(Imm, DestReg) when ?IS_UINT32_T(Imm) andalso is_atom(DestReg) ->
xorq(Imm, DestReg) when ?IS_SINT32_T(Imm) andalso is_atom(DestReg) ->
{REX_B, MODRM_RM} = x86_64_x_reg(DestReg),
<<?X86_64_REX(1, 0, 0, REX_B), 16#81, 3:2, 6:3, MODRM_RM:3, Imm:32/little>>;
xorq(SrcReg, DestReg) when is_atom(SrcReg), is_atom(DestReg) ->
Expand Down
4 changes: 2 additions & 2 deletions tests/libs/jit/jit_arm32_asm_tests.erl
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ push_test_() ->
[
%% single-register PUSH = STR Rd, [SP, #-4]!
?_assertAsmEqual(<<16#E52D0004:32/little>>, "push {r0}", jit_arm32_asm:push([r0])),
%% gcc as accepts to encode this, even if behavior is undefined
%% gcc as uses STMDB for push {sp}
?_assertAsmEqual(<<16#E92D2000:32/little>>, "push {sp}", jit_arm32_asm:push([sp])),
%% multi-register PUSH = STMDB SP!
?_assertAsmEqual(
Expand All @@ -304,7 +304,7 @@ pop_test_() ->
%% single-register POP = LDR Rd, [SP], #4
?_assertAsmEqual(<<16#E49D0004:32/little>>, "pop {r0}", jit_arm32_asm:pop([r0])),
%% gcc as accepts to encode this, even if behavior is undefined
?_assertAsmEqual(<<16#E8BD2000:32/little>>, "pop {sp}", jit_arm32_asm:pop([sp])),
?_assertAsmEqual(<<16#E49DD004:32/little>>, "pop {sp}", jit_arm32_asm:pop([sp])),
%% multi-register POP = LDMIA SP!
?_assertAsmEqual(
<<16#E8BD0007:32/little>>, "pop {r0, r1, r2}", jit_arm32_asm:pop([r0, r1, r2])
Expand Down
72 changes: 65 additions & 7 deletions tests/libs/jit/jit_tests_common.erl
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ find_binutils_beam(Arch) ->
_ ->
Prefixes0
end,
find_binutils_from_list([{P ++ "-as", P ++ "-objdump"} || P <- Prefixes]).
find_binutils_from_list(Arch, [{P ++ "-as", P ++ "-objdump"} || P <- Prefixes]).

%% Private functions

Expand All @@ -150,7 +150,16 @@ toolchain_prefixes(arm32) ->
toolchain_prefixes(arm);
toolchain_prefixes(Arch) ->
ArchStr = atom_to_list(Arch),
Variants = ["-esp-elf", "-unknown-elf", "-elf", "-none-eabi", "-linux-gnu", "-linux-gnueabihf"],
Variants = [
"-esp-elf",
"-unknown-elf",
"-elf",
"-none-eabi",
"-linux",
"-linux-gnu",
"-linux-gnueabihf",
"-buildroot-linux-uclibc"
],
[ArchStr ++ V || V <- Variants].

%% Find wat2wasm for wasm32 asm() cross-validation (does not need wasm-objdump).
Expand All @@ -162,15 +171,64 @@ find_wat2wasm() ->
end.

%% Generic helper function to find binutils from a list
-spec find_binutils_from_list([{string(), string()}]) -> {ok, string(), string()} | false.
find_binutils_from_list([]) ->
-spec find_binutils_from_list(atom(), [{string(), string()}]) -> {ok, string(), string()} | false.
find_binutils_from_list(_Arch, []) ->
false;
find_binutils_from_list([{AsCmd, ObjdumpCmd} | Rest]) ->
find_binutils_from_list(Arch, [{AsCmd, ObjdumpCmd} | Rest]) ->
case os:cmd("which " ++ AsCmd) of
[] ->
find_binutils_from_list(Rest);
find_binutils_from_list(Arch, Rest);
_ ->
{ok, AsCmd, ObjdumpCmd}
case is_as_version_buggy(Arch, AsCmd) of
false -> {ok, AsCmd, ObjdumpCmd};
true -> find_binutils_from_list(Arch, Rest)
end
end.

%% Check if assembler has a buggy version that doesn't encode certain instructions correctly.
%% For riscv32-as: binutils version 2.40 and lower have a bug encoding jal instruction.
-spec is_as_version_buggy(atom(), string()) -> boolean().
is_as_version_buggy(riscv32, AsCmd) ->
VersionOutput = os:cmd(AsCmd ++ " --version"),
case parse_binutils_version(VersionOutput) of
Version when Version =< {2, 40} ->
io:format("Skipping ~s version ~p (buggy for riscv32 jal encoding)~n", [
AsCmd, Version
]),
true;
_ ->
false
end;
is_as_version_buggy(_, _AsCmd) ->
false.

%% Parse binutils version from the first line of --version output.
%% Expected format: "GNU assembler (GNU Binutils) 2.40.0"
%% Returns a {Major, Minor} tuple for comparison.
-spec parse_binutils_version(string()) -> {non_neg_integer(), non_neg_integer()}.
parse_binutils_version(VersionOutput) ->
case binary:split(list_to_binary(VersionOutput), <<"\n">>) of
[FirstLine | _] ->
%% Look for version pattern like "2.40" in "GNU assembler (GNU Binutils) 2.40.0"
%% Match digits.digits after a space
case
re:run(
FirstLine,
<<" ([0-9]+)\\.([0-9]+)">>,
[{capture, all_but_first, binary}]
)
of
{match, [Major, Minor]} ->
{
binary_to_integer(Major),
binary_to_integer(Minor)
};
_ ->
%% If we can't parse, assume a high version (not buggy)
{infinity, infinity}
end;
_ ->
{infinity, infinity}
end.

%% Get architecture-specific assembly file header
Expand Down
10 changes: 5 additions & 5 deletions tests/libs/jit/jit_x86_64_asm_tests.erl
Original file line number Diff line number Diff line change
Expand Up @@ -812,20 +812,20 @@ xorq_test_() ->
"xor $0x12345678,%rcx",
jit_x86_64_asm:xorq(16#12345678, rcx)
),
% xorq uint32 immediates (0x80000000-0xFFFFFFFF, previously rejected by IS_SINT32_T)
% xorq uint32 immediates (0x80000000-0xFFFFFFFF, normalized to signed before encoding)
?_assertAsmEqual(
<<16#48, 16#35, 16#00, 16#00, 16#00, 16#80>>,
"xor $0x80000000,%rax",
"xor $-0x80000000,%rax",
jit_x86_64_asm:xorq(16#80000000, rax)
),
?_assertAsmEqual(
<<16#48, 16#81, 16#F1, 16#FF, 16#FF, 16#FF, 16#FF>>,
"xor $0xffffffff,%rcx",
<<16#48, 16#83, 16#F1, 16#FF>>,
"xor $-1,%rcx",
jit_x86_64_asm:xorq(16#FFFFFFFF, rcx)
),
?_assertAsmEqual(
<<16#49, 16#81, 16#F3, 16#00, 16#00, 16#00, 16#80>>,
"xor $0x80000000,%r11",
"xor $-0x80000000,%r11",
jit_x86_64_asm:xorq(16#80000000, r11)
),
% xorq reg, reg
Expand Down
9 changes: 7 additions & 2 deletions tests/libs/jit/tests.erl
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
% Module is used when running tests with BEAM.
% When running tests with AtomVM, eunit:start/0 is used instead.
start() ->
etest:test([
"BEAM" = erlang:system_info(machine),
Result = etest:test([
jit_tests,
jit_dwarf_tests,
jit_aarch64_tests,
Expand All @@ -42,4 +43,8 @@ start() ->
jit_riscv64_asm_tests,
jit_x86_64_tests,
jit_x86_64_asm_tests
]).
]),
case Result of
ok -> ok;
_ -> erlang:halt(1)
end.
Loading