diff --git a/.github/workflows/run-tests-with-beam.yaml b/.github/workflows/run-tests-with-beam.yaml index 247de957c0..80c30263e2 100644 --- a/.github/workflows/run-tests-with-beam.yaml +++ b/.github/workflows/run-tests-with-beam.yaml @@ -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' @@ -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' diff --git a/libs/jit/src/jit_arm32_asm.erl b/libs/jit/src/jit_arm32_asm.erl index aa38fd4d5f..fdd7895b19 100644 --- a/libs/jit/src/jit_arm32_asm.erl +++ b/libs/jit/src/jit_arm32_asm.erl @@ -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, - <>; pop([Reg]) -> %% LDR Rd, [SP], #4: single-register pop (matches GNU as encoding) RegNum = reg_to_num(Reg), diff --git a/libs/jit/src/jit_x86_64_asm.erl b/libs/jit/src/jit_x86_64_asm.erl index 0566617429..39d16d0b48 100644 --- a/libs/jit/src/jit_x86_64_asm.erl +++ b/libs/jit/src/jit_x86_64_asm.erl @@ -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), <>; -xorq(Imm, rax) when ?IS_UINT32_T(Imm) -> +xorq(Imm, rax) when ?IS_SINT32_T(Imm) -> <>; -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), <>; xorq(SrcReg, DestReg) when is_atom(SrcReg), is_atom(DestReg) -> diff --git a/tests/libs/jit/jit_arm32_asm_tests.erl b/tests/libs/jit/jit_arm32_asm_tests.erl index 06af5e1b96..bd69162270 100644 --- a/tests/libs/jit/jit_arm32_asm_tests.erl +++ b/tests/libs/jit/jit_arm32_asm_tests.erl @@ -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( @@ -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]) diff --git a/tests/libs/jit/jit_tests_common.erl b/tests/libs/jit/jit_tests_common.erl index afcd844bbf..70f63377e5 100644 --- a/tests/libs/jit/jit_tests_common.erl +++ b/tests/libs/jit/jit_tests_common.erl @@ -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 @@ -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). @@ -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 diff --git a/tests/libs/jit/jit_x86_64_asm_tests.erl b/tests/libs/jit/jit_x86_64_asm_tests.erl index f34f065248..d3e19d24f1 100644 --- a/tests/libs/jit/jit_x86_64_asm_tests.erl +++ b/tests/libs/jit/jit_x86_64_asm_tests.erl @@ -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 diff --git a/tests/libs/jit/tests.erl b/tests/libs/jit/tests.erl index cc62a1cc28..37d8c0e178 100644 --- a/tests/libs/jit/tests.erl +++ b/tests/libs/jit/tests.erl @@ -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, @@ -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.