Skip to content

test failures on s390x (big-endian) #2983

Open

Description

@rathann

When running version 95 tests on a big-endian machine like s390x, I get the following test error:

./check.py --binaryen-bin s390x-redhat-linux-gnu/bin
warning: no mozjs found (did not check native wasm support nor asm.js validation)
warning: no interpreter provided (did not test spec interpreter validation)
...
.. fuzz-exec_all-features.wast
executing:  /builddir/build/BUILD/binaryen-version_95/s390x-redhat-linux-gnu/bin/wasm-opt --fuzz-exec --all-features split.wast --print
executing:  /builddir/build/BUILD/binaryen-version_95/s390x-redhat-linux-gnu/bin/wasm-opt --fuzz-exec --all-features split.wast --print --debug
executing:  /builddir/build/BUILD/binaryen-version_95/s390x-redhat-linux-gnu/bin/wasm-opt --fuzz-exec --all-features split.wast --print
executing:  /builddir/build/BUILD/binaryen-version_95/s390x-redhat-linux-gnu/bin/wasm-opt --fuzz-exec --all-features split.wast --print
executing:  /builddir/build/BUILD/binaryen-version_95/s390x-redhat-linux-gnu/bin/wasm-opt --fuzz-exec --all-features split.wast --print --debug
executing:  /builddir/build/BUILD/binaryen-version_95/s390x-redhat-linux-gnu/bin/wasm-opt --fuzz-exec --all-features split.wast --print
executing:  /builddir/build/BUILD/binaryen-version_95/s390x-redhat-linux-gnu/bin/wasm-opt --fuzz-exec --all-features split.wast --print
executing:  /builddir/build/BUILD/binaryen-version_95/s390x-redhat-linux-gnu/bin/wasm-opt --fuzz-exec --all-features split.wast --print --debug
executing:  /builddir/build/BUILD/binaryen-version_95/s390x-redhat-linux-gnu/bin/wasm-opt --fuzz-exec --all-features split.wast --print
executing:  /builddir/build/BUILD/binaryen-version_95/s390x-redhat-linux-gnu/bin/wasm-opt --fuzz-exec --all-features split.wast --print
executing:  /builddir/build/BUILD/binaryen-version_95/s390x-redhat-linux-gnu/bin/wasm-opt --fuzz-exec --all-features split.wast --print --debug
executing:  /builddir/build/BUILD/binaryen-version_95/s390x-redhat-linux-gnu/bin/wasm-opt --fuzz-exec --all-features split.wast --print
executing:  /builddir/build/BUILD/binaryen-version_95/s390x-redhat-linux-gnu/bin/wasm-opt --fuzz-exec --all-features split.wast --print
executing:  /builddir/build/BUILD/binaryen-version_95/s390x-redhat-linux-gnu/bin/wasm-opt --fuzz-exec --all-features split.wast --print --debug
executing:  /builddir/build/BUILD/binaryen-version_95/s390x-redhat-linux-gnu/bin/wasm-opt --fuzz-exec --all-features split.wast --print
incorrect output, diff:
--- /builddir/build/BUILD/binaryen-version_95/test/passes/fuzz-exec_all-features.txt
+++ actual
@@ -66,7 +66,7 @@
 [fuzz-exec] calling unaligned_notify
 [trap unaligned atomic operation]
 [fuzz-exec] calling wrap_cmpxchg
-[LoggingExternalInterface logging 42]
+[LoggingExternalInterface logging 704643072]
 [fuzz-exec] calling oob_notify
 [trap final > memory: 18446744073709551512 > 65514]
 (module
@@ -135,7 +135,7 @@
 [fuzz-exec] calling unaligned_notify
 [trap unaligned atomic operation]
 [fuzz-exec] calling wrap_cmpxchg
-[LoggingExternalInterface logging 42]
+[LoggingExternalInterface logging 704643072]
 [fuzz-exec] calling oob_notify
 [trap final > memory: 18446744073709551512 > 65514]
 [fuzz-exec] comparing aligned_for_size
Traceback (most recent call last):
  File "/builddir/build/BUILD/binaryen-version_95/./check.py", line 403, in <module>
    sys.exit(main())
  File "/builddir/build/BUILD/binaryen-version_95/./check.py", line 386, in main
    TEST_SUITES[test]()
  File "/builddir/build/BUILD/binaryen-version_95/scripts/test/wasm_opt.py", line 75, in test_wasm_opt
    shared.fail_if_not_identical_to_file(actual, expected_file)
  File "/builddir/build/BUILD/binaryen-version_95/scripts/test/shared.py", line 348, in fail_if_not_identical_to_file
    fail_if_not_identical(actual, f.read(), fromfile=expected_file)
  File "/builddir/build/BUILD/binaryen-version_95/scripts/test/shared.py", line 337, in fail_if_not_identical
    fail(actual, expected, fromfile=fromfile)
  File "/builddir/build/BUILD/binaryen-version_95/scripts/test/shared.py", line 332, in fail
    fail_with_error("incorrect output, diff:\n\n%s" % diff_str)
  File "/builddir/build/BUILD/binaryen-version_95/scripts/test/shared.py", line 320, in fail_with_error
    raise Exception(msg)
Exception: incorrect output, diff:
--- /builddir/build/BUILD/binaryen-version_95/test/passes/fuzz-exec_all-features.txt
+++ actual
@@ -66,7 +66,7 @@
 [fuzz-exec] calling unaligned_notify
 [trap unaligned atomic operation]
 [fuzz-exec] calling wrap_cmpxchg
-[LoggingExternalInterface logging 42]
+[LoggingExternalInterface logging 704643072]
 [fuzz-exec] calling oob_notify
 [trap final > memory: 18446744073709551512 > 65514]
 (module
@@ -135,7 +135,7 @@
 [fuzz-exec] calling unaligned_notify
 [trap unaligned atomic operation]
 [fuzz-exec] calling wrap_cmpxchg
-[LoggingExternalInterface logging 42]
+[LoggingExternalInterface logging 704643072]
 [fuzz-exec] calling oob_notify
 [trap final > memory: 18446744073709551512 > 65514]
 [fuzz-exec] comparing aligned_for_size

Activity

  1. rathann commented on Sep 15, 2021

    @rathann
    ContributorAuthor

    Still reproducible with 102 release.

  2. kripken commented on Sep 15, 2021

    @kripken
    Member

    Is there a way to debug something like this without big-endian hardware?

    If not, at least reducing it on your side would be helpful - a tiny testcase could make this easy to figure out. The fastest way to get a reduced testcase might be to run ./scripts/fuzz_opt.py, and if it fails quickly, to follow the instructions it gives for reducing what it found.

  3. rathann commented on Sep 17, 2021

    @rathann
    ContributorAuthor

    @kripken You can reach out to one of Fedora s390x arch guys, Dan Horák to get SSH access to a s390x machine.
    You can also emulate s390x using QEMU.
    I'll follow your instructions to reduce the testcase, too. Stay tuned.

  4. rathann commented on Sep 20, 2021

    @rathann
    ContributorAuthor
    Details
    $ scripts/fuzz_opt.py --binaryen-bin /builddir/build/BUILDROOT/binaryen-102-1.fc34.s390x/usr/bin --binaryen-lib /builddir/build/BUILDROOT/binaryen-102-1.fc34.s390x/usr/lib64/binaryen 7749852837398145992
    warning: no wabt found: expected str, bytes or os.PathLike object, not NoneType
    warning: no wabt found: expected str, bytes or os.PathLike object, not NoneType
    /builddir/build/BUILDROOT/binaryen-102-1.fc34.s390x/usr/bin/wasm-opt --print-features /builddir/build/BUILD/binaryen-version_102/test/hello_world.wat --all-features
    warning: no output file specified, not emitting output
    POSSIBLE_FEATURE_OPTS: ['--disable-threads', '--disable-mutable-globals', '--disable-nontrapping-float-to-int', '--disable-simd', '--disable-bulk-memory', '--disable-sign-ext', '--disable-exception-handling', '--disable-tail-call', '--disable-reference-types', '--disable-multivalue', '--disable-gc', '--disable-memory64', '--disable-typed-function-references']
    checking a single given seed 7749852837398145992
    
    ITERATION: 1 seed: 7749852837398145992 size: 14608 (mean: 14608.0, stddev: 0.0) speed: 21076.904522613066 iters/sec,  0.0 wasm_bytes/sec
    
    randomized pass debug: 
    randomized feature opts: --all-features --disable-threads --disable-mutable-globals --disable-nontrapping-float-to-int --disable-simd --disable-bulk-memory --disable-sign-ext --disable-exception-handling --disable-tail-call --disable-reference-types --disable-multivalue --disable-gc --disable-memory64 --disable-typed-function-references
    randomized settings (NaNs, OOB, legalize): False True False
    randomized opts: --optimize-stack-ir --remove-unused-names --heap2local -Os --remove-unused-names --generate-stack-ir --optimize-stack-ir --reorder-locals --remove-unused-brs -fimfs=99999999
    
    /builddir/build/BUILDROOT/binaryen-102-1.fc34.s390x/usr/bin/wasm-opt input.dat -ttf -o a.wasm --denan --all-features --disable-threads --disable-mutable-globals --disable-nontrapping-float-to-int --disable-simd --disable-bulk-memory --disable-sign-ext --disable-exception-handling --disable-tail-call --disable-reference-types --disable-multivalue --disable-gc --disable-memory64 --disable-typed-function-references
    pre wasm size: 2421
    /builddir/build/BUILDROOT/binaryen-102-1.fc34.s390x/usr/bin/wasm-opt a.wasm --all-features --disable-threads --disable-mutable-globals --disable-nontrapping-float-to-int --disable-simd --disable-bulk-memory --disable-sign-ext --disable-exception-handling --disable-tail-call --disable-reference-types --disable-multivalue --disable-gc --disable-memory64 --disable-typed-function-references --print-features
    warning: no output file specified, not emitting output
    [] []
    /builddir/build/BUILDROOT/binaryen-102-1.fc34.s390x/usr/bin/wasm-opt a.wasm -o b.wasm --optimize-stack-ir --remove-unused-names --heap2local -Os --remove-unused-names --generate-stack-ir --optimize-stack-ir --reorder-locals --remove-unused-brs -fimfs=99999999 --denan
    post wasm size: 1057
    running testcase handler: Asyncify
    /builddir/build/BUILDROOT/binaryen-102-1.fc34.s390x/usr/bin/wasm-opt a.wasm --legalize-js-interface -o async.a.wasm
    /builddir/build/BUILDROOT/binaryen-102-1.fc34.s390x/usr/bin/wasm-opt b.wasm --legalize-js-interface -o async.b.wasm
    !
    -----------------------------------------
    Exception:
      File "/builddir/build/BUILD/binaryen-version_102/scripts/fuzz_opt.py", line 1213, in <module>
        total_wasm_size += test_one(raw_input_data, given_wasm)
      File "/builddir/build/BUILD/binaryen-version_102/scripts/fuzz_opt.py", line 1021, in test_one
        testcase_handler.handle_pair(input=random_input, before_wasm='a.wasm', after_wasm='b.wasm', opts=opts + FEATURE_OPTS)
      File "/builddir/build/BUILD/binaryen-version_102/scripts/fuzz_opt.py", line 869, in handle_pair
        before = fix_output(run_d8_wasm(before_wasm))
      File "/builddir/build/BUILD/binaryen-version_102/scripts/fuzz_opt.py", line 486, in run_d8_wasm
        return run_d8_js(in_binaryen('scripts', 'fuzz_shell.js'), [wasm], liftoff=liftoff)
      File "/builddir/build/BUILD/binaryen-version_102/scripts/fuzz_opt.py", line 482, in run_d8_js
        return run_vm(cmd)
      File "/builddir/build/BUILD/binaryen-version_102/scripts/fuzz_opt.py", line 446, in run_vm
        return filter_known_issues(run(cmd))
      File "/builddir/build/BUILD/binaryen-version_102/scripts/fuzz_opt.py", line 74, in run
        print(' '.join(cmd))
    -----------------------------------------
    !
    sequence item 0: expected str instance, NoneType found
    ================================================================================
    You found a bug! Please report it with
    
      seed: 7749852837398145992
    
    and the exact version of Binaryen you found it on, plus the exact Python
    version (hopefully deterministic random numbers will be identical).
    
    You can run that testcase again with "fuzz_opt.py 7749852837398145992"
    
    The initial wasm file used here is saved as /builddir/build/BUILD/binaryen-version_102/out/test/original.wasm
    
    You can reduce the testcase by running this now:
    
    ||||
    vvvv
    
    
    /builddir/build/BUILDROOT/binaryen-102-1.fc34.s390x/usr/bin/wasm-reduce /builddir/build/BUILD/binaryen-version_102/out/test/original.wasm '--command=bash /builddir/build/BUILD/binaryen-version_102/out/test/reduce.sh' -t /builddir/build/BUILD/binaryen-version_102/out/test/t.wasm -w /builddir/build/BUILD/binaryen-version_102/out/test/w.wasm
    
    
    ^^^^
    ||||
    
    Make sure to verify by eye that the output says something like this:
    
    At least one of the next two values should be 0:
      0
      1
    The following value should be 1:
      1
    
    (If it does not, then one possible issue is that the fuzzer fails to write a
    valid binary. If so, you can print the output of the fuzzer's first command
    (using -ttf / --translate-to-fuzz) in text form and run the reduction from that,
    passing --text to the reducer.)
    
    You can also read "/builddir/build/BUILD/binaryen-version_102/out/test/reduce.sh" which has been filled out for you and includes
    docs and suggestions.
    
    After reduction, the reduced file will be in /builddir/build/BUILD/binaryen-version_102/out/test/w.wasm
    ================================================================================
                    
    (finished running seed 7749852837398145992, see error above)
    $ /builddir/build/BUILDROOT/binaryen-102-1.fc34.s390x/usr/bin/wasm-reduce /builddir/build/BUILD/binaryen-version_102/out/test/original.wasm '--command=bash /builddir/build/BUILD/binaryen-version_102/out/test/reduce.sh' -t /builddir/build/BUILD/binaryen-version_102/out/test/t.wasm -w /builddir/build/BUILD/binaryen-version_102/out/test/w.wasm
    |wasm-reduce
    |input: /builddir/build/BUILD/binaryen-version_102/out/test/original.wasm
    |test: /builddir/build/BUILD/binaryen-version_102/out/test/t.wasm
    |working: /builddir/build/BUILD/binaryen-version_102/out/test/w.wasm
    |bin dir: /builddir/build/BUILDROOT/binaryen-102-1.fc34.s390x/usr/bin
    |expected result:
    [ProgramResult] code: 0 stdout: 
    At least one of the next two values should be 0:
       0
       0
    The following value should be 1:
       1
    [====]
    in 0.285102 seconds
    [/ProgramResult]
    
    |!! Make sure the above is what you expect! !!
    
    |checking that command has different behavior on different inputs (this verifies that the test file is used by the command)
    |checking that command has expected behavior on canonicalized (read-written) binary
    |input size: 2421
    |starting reduction!
    |  reduce using passes...
    |    command "/builddir/build/BUILDROOT/binaryen-102-1.fc34.s390x/usr/bin/wasm-opt /builddir/build/BUILD/binaryen-version_102/out/test/w.wasm -o /builddir/build/BUILD/binaryen-version_102/out/test/t.wasm -Oz -all" succeeded, reduced size to 920
    |    command "/builddir/build/BUILDROOT/binaryen-102-1.fc34.s390x/usr/bin/wasm-opt /builddir/build/BUILD/binaryen-version_102/out/test/w.wasm -o /builddir/build/BUILD/binaryen-version_102/out/test/t.wasm -O3 -all" succeeded, reduced size to 863
    |    command "/builddir/build/BUILDROOT/binaryen-102-1.fc34.s390x/usr/bin/wasm-opt /builddir/build/BUILD/binaryen-version_102/out/test/w.wasm -o /builddir/build/BUILD/binaryen-version_102/out/test/t.wasm -O4 -all" succeeded, reduced size to 811
    |    command "/builddir/build/BUILDROOT/binaryen-102-1.fc34.s390x/usr/bin/wasm-opt /builddir/build/BUILD/binaryen-version_102/out/test/w.wasm -o /builddir/build/BUILD/binaryen-version_102/out/test/t.wasm --remove-imports -all" succeeded, reduced size to 766
    |    command "/builddir/build/BUILDROOT/binaryen-102-1.fc34.s390x/usr/bin/wasm-opt /builddir/build/BUILD/binaryen-version_102/out/test/w.wasm -o /builddir/build/BUILD/binaryen-version_102/out/test/t.wasm --remove-memory -all" succeeded, reduced size to 738
    |    command "/builddir/build/BUILDROOT/binaryen-102-1.fc34.s390x/usr/bin/wasm-opt /builddir/build/BUILD/binaryen-version_102/out/test/w.wasm -o /builddir/build/BUILD/binaryen-version_102/out/test/t.wasm --simplify-locals --vacuum -all" succeeded, reduced size to 734
    |  after pass reduction: 734
    |  pass progress: 1687, last destructive: 0
    |  progress is good, do not quickly decrease factor
    |  reduce destructively... (factor: 183)
    |      tryToReplaceCurrent succeeded (in 0)
    |      tryToReplaceCurrent succeeded (in 0)
    |      tryToReplaceCurrent succeeded (in 0)
    |    9% of funcs complete
    |    18% of funcs complete
    |      tryToReplaceCurrent succeeded (in 2)
    |    27% of funcs complete
    |      tryToReplaceCurrent succeeded (in 3)
    |    36% of funcs complete
    |    45% of funcs complete
    |    54% of funcs complete
    |    63% of funcs complete
    |    72% of funcs complete
    |      tryToReplaceCurrent succeeded (in 8)
    |      tryToReplaceCurrent succeeded (in 8)
    |    81% of funcs complete
    |    90% of funcs complete
    |    100% of funcs complete
    |    try to simplify memory
    |    try to remove functions
    |        emptied 1 / 1 functions
    |        emptied 2 / 2 functions
    |        emptied 4 / 4 functions
    |        emptied 1 / 1 functions
    |    try to remove functions
    |        removed 1 functions
    |        removed 2 functions
    |        removed 4 functions
    |        removed 1 functions
    |    try to remove functions
    |        emptied 1 / 1 functions
    |        emptied 1 / 1 functions
    |    try to remove functions
    |        removed 1 functions
    |        removed 1 functions
    |    try to remove functions
    |        emptied 1 / 1 functions
    |    try to simplify elem segments
    |    try to remove exports (with factor 183)
    |      removed 1 exports
    |  destructive reduction led to size: 50
    |  reduce using passes...
    |    command "/builddir/build/BUILDROOT/binaryen-102-1.fc34.s390x/usr/bin/wasm-opt /builddir/build/BUILD/binaryen-version_102/out/test/w.wasm -o /builddir/build/BUILD/binaryen-version_102/out/test/t.wasm -Oz -all" succeeded, reduced size to 8
    |  after pass reduction: 8
    |  pass progress: 42, last destructive: 29
    |  reduce destructively... (factor: 2)
    |    try to simplify memory
    |    try to remove functions
    |    try to simplify elem segments
    |    try to remove exports (with factor 2)
    |  reduce destructively... (factor: 1)
    |    try to simplify memory
    |    try to remove functions
    |    try to simplify elem segments
    |    try to remove exports (with factor 1)
    |  destructive reduction led to size: 8
    |  reduce using passes...
    |  after pass reduction: 8
    |finished, final size: 8
    

    original.wasm.gz
    t.wasm.gz
    w.wasm.gz

    I hope this is what you need.

  5. kripken commented on Sep 20, 2021

    @kripken
    Member

    Size 8 is the smallest possible wasm file, so it's failing even to load that..?

    What error do you get when you do wasm-opt w.wasm on that last wasm file?

  6. rathann commented on Sep 21, 2021

    @rathann
    ContributorAuthor

    No error:

    $ /builddir/build/BUILDROOT/binaryen-102-1.fc34.s390x/usr/bin/wasm-opt /builddir/build/BUILD/binaryen-version_102/out/test/w.wasm
    warning: no passes specified, not doing any work
    warning: no output file specified, not emitting output
    $ 
    
  7. kripken commented on Sep 21, 2021

    @kripken
    Member

    Oh, then something went wrong with the reduction. When the reducer ran such a command, it failed for it - perhaps you are in a different directory or otherwise it can't find wasm-opt. Running the fuzzer script from the binaryen directory is what I normally do (./scripts/fuzz_opt.py) and it's possible you need to set up the PATH otherwise, or pass --binaries to the reduction script so it finds them.

    You can run this to see:

    ./scripts/fuzz_opt.py 7749852837398145992 /builddir/build/BUILD/binaryen-version_102/out/test/w.wasm
    

    That runs the fuzzer script with the seed that failed, and gives it the w.wasm file that the reduction arrived it. If that succeeds, try

    ./scripts/fuzz_opt.py 7749852837398145992
    

    (without the file at the end, it will run the entire testcase).

  8. kapouer commented on Feb 12, 2025

    @kapouer

    Note that practically all big-endian architectures have the same test failure (hppa, powerpc, ppc64, sparc64, s390x).
    Is there something preventing binaryen to run on a big-endian host ?

  9. kripken commented on Feb 12, 2025

    @kripken
    Member

    I can't think of any big-endian specific issue. We don't do any low-level bit manipulation of that sort, in general. But perhaps we have undefined behavior somewhere, that's my best guess.

  10. rathann commented on Feb 25, 2025

    @rathann
    ContributorAuthor

    Well, this is still reproducible with 122:

    incorrect output, diff:
    --- /builddir/build/BUILD/binaryen-122-build/binaryen-version_122/test/passes/fuzz-exec_all-features.txt
    +++ actual
    @@ -66,7 +66,7 @@
     [fuzz-exec] calling unaligned_notify
     [trap unaligned atomic operation]
     [fuzz-exec] calling wrap_cmpxchg
    -[LoggingExternalInterface logging 42]
    +[LoggingExternalInterface logging 704643072]
     [fuzz-exec] calling oob_notify
     [trap final > memory: 18446744073709551512 > 65514]
     (module
    @@ -135,7 +135,7 @@
     [fuzz-exec] calling unaligned_notify
     [trap unaligned atomic operation]
     [fuzz-exec] calling wrap_cmpxchg
    -[LoggingExternalInterface logging 42]
    +[LoggingExternalInterface logging 704643072]
     [fuzz-exec] calling oob_notify
     [trap final > memory: 18446744073709551512 > 65514]
     [fuzz-exec] comparing aligned_for_size
    Traceback (most recent call last):
      File "/builddir/build/BUILD/binaryen-122-build/binaryen-version_122/./check.py", line 408, in <module>
        sys.exit(main())
                 ~~~~^^
      File "/builddir/build/BUILD/binaryen-122-build/binaryen-version_122/./check.py", line 391, in main
        TEST_SUITES[test]()
        ~~~~~~~~~~~~~~~~~^^
      File "/builddir/build/BUILD/binaryen-122-build/binaryen-version_122/scripts/test/wasm_opt.py", line 83, in test_wasm_opt
        shared.fail_if_not_identical_to_file(actual, expected_file)
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
      File "/builddir/build/BUILD/binaryen-122-build/binaryen-version_122/scripts/test/shared.py", line 363, in fail_if_not_identical_to_file
        fail_if_not_identical(actual, f.read(), fromfile=expected_file)
        ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/builddir/build/BUILD/binaryen-122-build/binaryen-version_122/scripts/test/shared.py", line 352, in fail_if_not_identical
        fail(actual, expected, fromfile=fromfile)
        ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/builddir/build/BUILD/binaryen-122-build/binaryen-version_122/scripts/test/shared.py", line 347, in fail
        fail_with_error("incorrect output, diff:\n\n%s" % diff_str)
        ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/builddir/build/BUILD/binaryen-122-build/binaryen-version_122/scripts/test/shared.py", line 335, in fail_with_error
        raise Exception(msg)
    Exception: incorrect output, diff:
    --- /builddir/build/BUILD/binaryen-122-build/binaryen-version_122/test/passes/fuzz-exec_all-features.txt
    +++ actual
    @@ -66,7 +66,7 @@
     [fuzz-exec] calling unaligned_notify
     [trap unaligned atomic operation]
     [fuzz-exec] calling wrap_cmpxchg
    -[LoggingExternalInterface logging 42]
    +[LoggingExternalInterface logging 704643072]
     [fuzz-exec] calling oob_notify
     [trap final > memory: 18446744073709551512 > 65514]
     (module
    @@ -135,7 +135,7 @@
     [fuzz-exec] calling unaligned_notify
     [trap unaligned atomic operation]
     [fuzz-exec] calling wrap_cmpxchg
    -[LoggingExternalInterface logging 42]
    +[LoggingExternalInterface logging 704643072]
     [fuzz-exec] calling oob_notify
     [trap final > memory: 18446744073709551512 > 65514]
     [fuzz-exec] comparing aligned_for_size
    

    Note that treating the little-endian bit-representation of 42 as big-endian gives us exactly 704643072. See in Python:

    >>> int(bin(42)+24*'0',2)
    704643072
  11. tlively commented on Feb 25, 2025

    @tlively
    Member

    Looks like ShellExternalInterface::Memory::{set,get} do not behave correctly on big-endian architectures. They should be reversing the bytes before storing and after loading values.

  12. sertonix commented on Jan 23, 2026

    @sertonix
    Contributor

    If that is the case I would suspect issues in code using CtorEvalExternalInterface::{doStore,doLoad} as well.

    In general a lot of memcpy and reinterpret_cast do not take endianess into account. And v128 values probably needs special care by swapping endianess when converting to and from normal integer types (haven't found the code if that is done anywhere).

    Edit: also static_cast

    Is there any opinion on ether using something with std::byteswap on big-endian systems or using endian independent code like (b[1] << 8) | b[0]? (Bit shifting seems to already be used in some places)

  13. kripken commented on Jan 26, 2026

    @kripken
    Member

    Is there any opinion on ether using something with std::byteswap on big-endian systems or using endian independent code like (b[1] << 8) | b[0]?

    If we need to fix places that way, that sounds good to do, of course. Where specifically were you looking?

  14. sertonix commented on Jan 27, 2026

    @sertonix
    Contributor

    I was looking for places which converts pointers to integers to pointers to integers of different width (like uint8_t[2] a = {1, 0}; uint16_t *v = &a;). Unfortunately that can come in many different ways so there is no specific way to look for them but memcpy, casts, read and write operations are good places to start.

  15. 12 remaining items

  16. added a commit that references this issue on Mar 20, 2026
  17. sertonix commented on May 21, 2026

    @sertonix
    Contributor

    Is it needed for performance that in src/wasm/literal.cpp the union members are accessed directly (through .i32) instead of the wrappers that have an assert (like .geti32())? Still need to finish running tests but I think there are a few places where the accessed union member does not match the type and the assert would help with noticing such bugs more easily.

  18. kripken commented on May 21, 2026

    @kripken
    Member

    Using the wrappers is probably fine, though it would be worth measuring first.

  19. added a commit that references this issue on May 21, 2026
  20. sertonix commented on May 21, 2026

    @sertonix
    Contributor

    5 test failure left and it's not getting easier 😅

  21. sertonix commented on May 21, 2026

    @sertonix
    Contributor

    I think the assumption that (i32 & 1) is the low bit of a shared_ptr is broken. On s390x it's roughly in the middle of the pointer (and likely set to 1).

    binaryen/src/literal.h

    Lines 53 to 54 in cfc3d50

    // need more bits we can use the i64 field instead. This scheme also depends
    // on the low bit of a shared_ptr not being used.

    binaryen/src/literal.h

    Lines 311 to 314 in cfc3d50

    bool hasExternPayload() const {
    assert(type.getHeapType().isMaybeShared(HeapType::ext));
    return (i32 & 1) == 1;
    }

    Fixing that seems to require a larger change. The only portable way I can think of to have multiple union members be accessible without UB would be to use a struct like the following. The good news is that the union is always 128-bits so there shouldn't be size increase (on >= 64-bit systems).

    Edit: Oh, shared_ptr is already 128-bit

    (just an idea, not functional)

    --- a/src/literal.h
    +++ b/src/literal.h
    @@ -56,7 +56,7 @@ class Literal {
         int64_t i64;
         uint8_t v128[16];
         // A reference to Function data.
    -    std::shared_ptr<FuncData> funcData;
    +    struct { int32_t i32; std::shared_ptr<FuncData> ptr; } funcData;
         // A reference to GC data, either a Struct or an Array. For both of those we
         // store the referred data as a Literals object (which is natural for an
         // Array, and for a Struct, is just the fields in order). The type is used
    @@ -65,11 +65,11 @@ class Literal {
         // heap. For externalized or internalized references (including strings),
         // gcData holds a single value, which is the wrapped internal or external
         // reference.
    -    std::shared_ptr<GCData> gcData;
    +    struct { int32_t i32; std::shared_ptr<GCData> ptr; } gcData;
         // A reference to Exn data.
    -    std::shared_ptr<ExnData> exnData;
    +    struct { int32_t i32; std::shared_ptr<ExnData> ptr; } exnData;
         // A reference to a Continuation.
    -    std::shared_ptr<ContData> contData;
    +    struct { int32_t i32; std::shared_ptr<ContData> ptr; } contData;
       };
     
     public:

    Is there any other idea and/or opinion on how the issue can be fixed?

  22. sertonix commented on May 21, 2026

    @sertonix
    Contributor

    My idea doesn't look good.

    Moving the externref payloads to GCData like before c3bc803 seems better.

    CC @tlively

  23. tlively commented on May 22, 2026

    @tlively
    Member

    I guess one option is to change which bit distinguishes externref payloads depending on the platform so that it always happens to line up with the low bit of the shared_ptr. This should be possible no matter where that low bit ends up being because we can use the v128 field if we have to. But that's really gross and probably brittle.

    I agree that moving externref payloads to the GCData seems better. If we have an externref Literal, we can look at its GCData and if it contains a single i32 Literal, we know it is a boxed externref payload. Otherwise it should contain some reference-typed Literal and we know it is an externalized reference.

  24. sertonix commented on May 22, 2026

    @sertonix
    Contributor

    You mean something like this? The concept works on s390x but the patch is broken somewhere since it triggers an assert on all arches. Help with doing this right would be appreciated

    diff --git a/src/literal.h b/src/literal.h
    index 686348d19..2c7b82896 100644
    --- a/src/literal.h
    +++ b/src/literal.h
    @@ -43,15 +43,6 @@ class Literal {
         // Note: i31 is stored in the |i32| field, with the lower 31 bits containing
         // the value if there is one, and the highest bit containing whether there
         // is a value. Thus, a null is |i32 === 0|.
    -    //
    -    // Externref payloads, which serve to differentiate different external
    -    // references but are otherwise meaningless, are also stored in the i32
    -    // field, with their low bit set to differentiate an externref with a
    -    // payload from an externalized internal reference, which uses the gcData
    -    // field instead. This scheme supports 31 bits of payload for externrefs,
    -    // which should be sufficient for spec test and fuzzing purposes, but if we
    -    // need more bits we can use the i64 field instead. This scheme also depends
    -    // on the low bit of a shared_ptr not being used.
         int32_t i32;
         int64_t i64;
         uint8_t v128[16];
    @@ -266,11 +257,7 @@ public:
         lit.i32 = value | 0x80000000;
         return lit;
       }
    -  static Literal makeExtern(int32_t payload, Shareability share) {
    -    auto lit = Literal(Type(HeapTypes::ext.getBasic(share), NonNullable));
    -    lit.i32 = (payload << 1) | 1;
    -    return lit;
    -  }
    +  static Literal makeExtern(int32_t payload, Shareability share);
       // Wasm has nondeterministic rules for NaN propagation in some operations. For
       // example. f32.neg is deterministic and just flips the sign, even of a NaN,
       // but f32.add is nondeterministic, and if one or more of the inputs is a NaN,
    @@ -310,11 +297,11 @@ public:
       }
       bool hasExternPayload() const {
         assert(type.getHeapType().isMaybeShared(HeapType::ext));
    -    return (i32 & 1) == 1;
    +    return internalize().type == Type::i32;
       }
       int32_t getExternPayload() const {
         assert(hasExternPayload());
    -    return int32_t(uint32_t(i32) >> 1);
    +    return internalize().i32;
       }
       int64_t geti64() const {
         assert(type == Type::i64);
    diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp
    index 8c5074e1c..002f9e838 100644
    --- a/src/wasm/literal.cpp
    +++ b/src/wasm/literal.cpp
    @@ -64,12 +64,6 @@ Literal::Literal(Type type) : type(type) {
         return;
       }
     
    -  if (type.isRef() && type.getHeapType().isMaybeShared(HeapType::ext)) {
    -    assert(type.isNonNullable());
    -    i32 = 1;
    -    return;
    -  }
    -
       WASM_UNREACHABLE("Unexpected literal type");
     }
     
    @@ -92,6 +86,12 @@ Literal Literal::makeFunc(Name func, Module& wasm) {
       return makeFunc(func, wasm.getFunction(func)->type);
     }
     
    +Literal Literal::makeExtern(int32_t payload, Shareability share) {
    +  auto ext = HeapTypes::ext.getBasic(share);
    +  return Literal(std::make_shared<GCData>(Literals{Literal(payload)}), ext);
    +}
    +
    +
     Literal::Literal(std::shared_ptr<GCData> gcData, HeapType type)
       : gcData(gcData), type(type,
                              gcData ? NonNullable : Nullable,
    @@ -175,17 +175,8 @@ Literal::Literal(const Literal& other) : type(other.type) {
         case HeapType::exn:
           new (&exnData) std::shared_ptr<ExnData>(other.exnData);
           return;
    -    case HeapType::ext: {
    -      if (other.hasExternPayload()) {
    -        i32 = other.i32;
    -      } else {
    -        // Externalized internal reference.
    -        new (&gcData) std::shared_ptr<GCData>(other.gcData);
    -      }
    -      return;
    -    }
    +    case HeapType::ext:
         case HeapType::any:
    -      // Internalized external reference or string.
           new (&gcData) std::shared_ptr<GCData>(other.gcData);
           return;
         case HeapType::none:
    @@ -210,12 +201,8 @@ Literal::~Literal() {
       if (type.isBasic()) {
         return;
       }
    -  if (type.getHeapType().isMaybeShared(HeapType::ext) && !hasExternPayload()) {
    -    // Externalized internal reference.
    -    gcData.~shared_ptr();
    -    return;
    -  }
    -  if (isNull() || isData() || type.getHeapType().isMaybeShared(HeapType::any)) {
    +  if (isNull() || isData() || type.getHeapType().isMaybeShared(HeapType::ext) ||
    +      type.getHeapType().isMaybeShared(HeapType::any)) {
         gcData.~shared_ptr();
       } else if (isFunction()) {
         funcData.~shared_ptr();
    @@ -506,12 +493,6 @@ bool Literal::operator==(const Literal& other) const {
           return i32 == other.i32;
         }
         if (heapType.isMaybeShared(HeapType::ext)) {
    -      if (hasExternPayload()) {
    -        if (!other.hasExternPayload()) {
    -          return false;
    -        }
    -        return getExternPayload() == other.getExternPayload();
    -      }
           return internalize() == other.internalize();
         }
         if (heapType.isMaybeShared(HeapType::any)) {
    @@ -3038,7 +3019,7 @@ Literal Literal::internalize() const {
         auto none = HeapTypes::none.getBasic(heapType.getShared());
         return Literal(nullptr, none);
       }
    -  if (isString() || hasExternPayload()) {
    +  if (isString()) {
         // This is an external reference. Wrap it.
         auto any = HeapTypes::any.getBasic(heapType.getShared());
         return Literal(std::make_shared<GCData>(Literals{*this}), any);
    @@ -3056,7 +3037,7 @@ Literal Literal::unwrap() const {
         // An internalized external reference (possibly a string).
         return externalize();
       }
    -  if (type.getHeapType().isMaybeShared(HeapType::ext) && !hasExternPayload()) {
    +  if (type.getHeapType().isMaybeShared(HeapType::ext)) {
         // An externalized internal reference.
         return internalize();
       }
  25. sertonix commented on May 22, 2026

    @sertonix
    Contributor

    I think I tracked down the last test failures. In javascript binaryen uses typed arrays such as Int32Array which depend on the host endianess. For exactly that reason MDN recommends the usage of DataView when dealing with WASM memory: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView#endianness

    I verified the concept with test/unit/input/asyncify.js. Using DataView everywhere seems to require large changes (see git grep -E '\b(((Big)?(Ui|I)nt|Float)(16|32|64)Array|HEAP(U|F|)(8|16|32|64))\b').

    Edit: Atomics.add seems to use host endianess without an option to change that. It may need to be replaced with something like Atomics.compareExchange.

    Patch for test_asyncify_js
    --- a/test/unit/input/asyncify.js
    +++ b/test/unit/input/asyncify.js
    @@ -35,17 +35,17 @@ function sleepTests() {
               // Unwinding.
               // Fill in the data structure. The first value has the stack location,
               // which for simplicity we can start right after the data structure itself.
    -          view[DATA_ADDR >> 2] = DATA_ADDR + 8;
    +          view.setInt32(DATA_ADDR, DATA_ADDR + 8, true);
               // The end of the stack will not be reached here anyhow.
    -          view[DATA_ADDR + 4 >> 2] = 1024;
    +          view.setInt32(DATA_ADDR + 4, 1024, true);
               exports.asyncify_start_unwind(DATA_ADDR);
             } else {
               // We are called as part of a resume/rewind. Stop sleeping.
               console.log('resume...');
               exports.asyncify_stop_rewind();
               // The stack should have been all used up, and so returned to the original state.
    -          assert(view[DATA_ADDR >> 2] == DATA_ADDR + 8);
    -          assert(view[DATA_ADDR + 4 >> 2] == 1024);
    +          assert(view.getInt32(DATA_ADDR, true) == DATA_ADDR + 8);
    +          assert(view.getInt32(DATA_ADDR + 4, true) == 1024);
               sleeping = false;
             }
             logMemory();
    @@ -58,11 +58,11 @@ function sleepTests() {
       });
     
       var exports = instance.exports;
    -  var view = new Int32Array(exports.memory.buffer);
    +  var view = new DataView(exports.memory.buffer);
     
       function logMemory() {
         // Log the relevant memory locations for debugging purposes.
    -    console.log('memory: ', view[0 >> 2], view[4 >> 2], view[8 >> 2], view[12 >> 2], view[16 >> 2], view[20 >> 2], view[24 >> 2]);
    +    console.log('memory: ', view.getInt32(0, true), view.getInt32(4, true), view.getInt32(8, true), view.getInt32(12, true), view.getInt32(16, true), view.getInt32(20, true), view.getInt32(24, true));
       }
     
       function runTest(name, expectedSleeps, expectedResult, params) {
    @@ -176,8 +176,8 @@ function coroutineTests() {
         };
         this.startUnwind = function() {
           // Initialize the data.
    -      view[dataStart >> 2] = dataStart + 8;
    -      view[dataStart + 4 >> 2] = dataEnd;
    +      view.setInt32(dataStart, dataStart + 8, true);
    +      view.setInt32(dataStart + 4, dataEnd, true);
           exports.asyncify_start_unwind(dataStart);
           // (With C etc. coroutines we would also have
           // a C stack to pause and resume here.)
    @@ -242,7 +242,7 @@ function coroutineTests() {
       });
     
       var exports = instance.exports;
    -  var view = new Int32Array(exports.memory.buffer);
    +  var view = new DataView(exports.memory.buffer);
     
       Runtime.run(4);
       console.log(Runtime.values);
    @@ -271,17 +271,17 @@ function stackOverflowAssertTests() {
           sleep: function() {
             console.log('sleep...');
             exports.asyncify_start_unwind(DATA_ADDR);
    -        view[DATA_ADDR >> 2] = DATA_ADDR + 8;
    +        view.setInt32(DATA_ADDR, DATA_ADDR + 8, true);
             // The end of the stack will be reached as the stack is tiny.
    -        view[DATA_ADDR + 4 >> 2] = view[DATA_ADDR >> 2] + 1;
    +        view.setInt32(DATA_ADDR + 4, view.getInt32(DATA_ADDR, true) + 1, true);
           }
         }
       });
     
       var exports = instance.exports;
    -  var view = new Int32Array(exports.memory.buffer);
    +  var view = new DataView(exports.memory.buffer);
       exports.many_locals();
    -  assert(view[DATA_ADDR >> 2] > view[DATA_ADDR + 4 >> 2], 'should have wrote past the end of the stack');
    +  assert(view.getInt32(DATA_ADDR, true) > view.getInt32(DATA_ADDR + 4, true), 'should have wrote past the end of the stack');
       // All API calls should now fail, since we wrote past the end of the
       // stack
       var fails = 0;
  26. tlively commented on May 23, 2026

    @tlively
    Member

    I uploaded #8765 to fix the problem with externref representation.

  27. added a commit that references this issue on Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions