Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@gkgoat1
Copy link
Contributor

@gkgoat1 gkgoat1 commented Jul 19, 2025

Some now-legacy Apple toolchains used these versions of Clang.

Some now-legacy Apple toolchains used these versions of Clang.
@turbolent
Copy link
Owner

turbolent commented Jul 19, 2025

Thank you for your contribution!

A couple questions:

  • What exactly is the problem of using the currently generated code with those compiler versions? Do they lead to compilation errors?
  • What does the fix accomplish? Did you come up with the solution yourself or did you maybe find it somewhere (issue tracker, Stackoverflow, etc.)?

So far the generated code has been target independent and target specific differences have been implemented through the pre-processor instead of compiler flags. For example, see #101. Could the fix maybe be implemented via a define?

@gkgoat1
Copy link
Contributor Author

gkgoat1 commented Jul 19, 2025

Thank you for your contribution!

A couple questions:

  • What exactly is the problem of using the currently generated code with those compiler versions? Do they lead to compilation errors?

If an infinite loop occurs in WASM code recompiled with thoe Clang/LLVM versions, it would be UB, even though the C standard and other compilers define that behavior.

  • What does the fix accomplish? Did you come up with the solution yourself or did you maybe find it somewhere (issue tracker, Stackoverflow, etc.)?

This volatile __asm__ fix was from the Rust issue with this same LLVM bug.

So far the generated code has been target independent and target specific differences have been implemented through the pre-processor instead of compiler flags. For example, see #101. Could the fix maybe be implemented via a define?

Probably; I'll see (edit: I just switched to a macro)

@turbolent
Copy link
Owner

If an infinite loop occurs in WASM code recompiled with thoe Clang/LLVM versions, it would be UB, even though the C standard and other compilers define that behavior.

So the fix is not for labels, but actually (infinite) loops? I'm a bit confused why the code is needed at the start of a label. Do you have an example of a WebAssembly module for which the current code gets miscompiled by clang <= 12?

This volatile asm fix was from the Rust issue with this same LLVM bug.

Do you have a link to that issue? What does the __asm__ volatile() achieve?

@gkgoat1
Copy link
Contributor Author

gkgoat1 commented Jul 19, 2025

If an infinite loop occurs in WASM code recompiled with thoe Clang/LLVM versions, it would be UB, even though the C standard and other compilers define that behavior.

So the fix is not for labels, but actually (infinite) loops? I'm a bit confused why the code is needed at the start of a label. Do you have an example of a WebAssembly module for which the current code gets miscompiled by clang <= 12?

That's a good point; the code is at the start of a label to begin all basic blocks, but you're right about only needing it in loops.

You can try (module (func (export "ub") (result i32) (loop $loop (br $loop)) i32.const 1) ). It should loop forever; those clang versions would do other things, including delete the function body (when disassembled) entirely

This volatile asm fix was from the Rust issue with this same LLVM bug.

Do you have a link to that issue? What does the __asm__ volatile() achieve?

rust-lang/rust#28728

The __asm__ volatile forces LLVM to not "optimize" the loop away.

@gkgoat1
Copy link
Contributor Author

gkgoat1 commented Jul 19, 2025

Note: iirc the clang.wasm in this repo is version 8.0.1, and thus is prone to this bug.

@turbolent
Copy link
Owner

Thank you for the explanation and the link!

@turbolent
Copy link
Owner

turbolent commented Jul 19, 2025

So I tried to verify both the bug, and the fix, given the example you provided, and I'm still a bit confused:

(module (func (export "ub") (result i32) (loop $loop (br $loop)) i32.const 1))

gets translated to

typedef unsigned int U32;

U32 f0(void) {
U32 si0;
L1:;
{
goto L1;
}
si0=1U;
L0:;
return si0;
}

Testing compilation of this function with -O3 on several clang versions <12 actually seems to work, the loop is not optimized away. For example with clang 11: https://godbolt.org/z/4nW7o37dr

f0():
.LBB0_1:
        jmp     .LBB0_1

However, versions >=12 start to optimize away the loop, including latest clang 20: https://godbolt.org/z/eMGqqv3r4 (try selecting other version)

f0():

So should __clang_major__ <= 12 rather be __clang_major__ >= 12?

When trying to include the fix, clang shows an error:

<source>:6:18: error: expected string literal in 'asm'
__asm__ volatile(); 

It looks like the function is missing an argument and should be __asm__ volatile("");. Once that's fixed, even clang >=12 seems to keep the loop 👍

@gkgoat1
Copy link
Contributor Author

gkgoat1 commented Jul 19, 2025

So I tried to verify both the bug, and the fix, given the example you provided, and I'm still a bit confused:

(module (func (export "ub") (result i32) (loop $loop (br $loop)) i32.const 1))

gets translated to

typedef unsigned int U32;

U32 f0(void) {
U32 si0;
L1:;
{
goto L1;
}
si0=1U;
L0:;
return si0;
}

Testing compilation of this function with -O3 on several clang versions <12 actually seems to work, the loop is not optimized away. For example with clang 11: https://godbolt.org/z/4nW7o37dr

f0():
.LBB0_1:
        jmp     .LBB0_1

However, versions >=12 start to optimize away the loop, including latest clang 20: https://godbolt.org/z/eMGqqv3r4 (try selecting other version)

f0():

So should __clang_major__ <= 12 rather be __clang_major__ >= 12?

Well that seems like a bug (C permits goto loops iirc because they aren't iteration statements). I'd recommend reporting (I might), and I'll bump the version guard to 20 (and also now modern compiled code is affected)

When trying to include the fix, clang shows an error:

<source>:6:18: error: expected string literal in 'asm'
__asm__ volatile(); 

It looks like the function is missing an argument and should be __asm__ volatile("");. Once that's fixed, even clang >=12 seems to keep the loop 👍

Updating rn

@turbolent
Copy link
Owner

Yeah, this might just be llvm/llvm-project#60622, which looks like a "wontfix". Let's just ensure the generated C code will have the behaviour of the original WebAssembly module, a proper infinite loop, and ensure the loop is not optimized away. From what it looks like that starts in clang 12

@turbolent
Copy link
Owner

turbolent commented Jul 19, 2025

Once the version range is adjusted I'm happy to merge this. Thank you for bringing this up and submitting a fix 🙏

Co-authored-by: Bastian Müller <[email protected]>
@turbolent turbolent merged commit cd8efe0 into turbolent:main Jul 19, 2025
63 of 73 checks passed
@gkgoat1
Copy link
Contributor Author

gkgoat1 commented Jul 20, 2025

So I tried to verify both the bug, and the fix, given the example you provided, and I'm still a bit confused:

(module (func (export "ub") (result i32) (loop $loop (br $loop)) i32.const 1))

gets translated to

typedef unsigned int U32;

U32 f0(void) {
U32 si0;
L1:;
{
goto L1;
}
si0=1U;
L0:;
return si0;
}

Testing compilation of this function with -O3 on several clang versions <12 actually seems to work, the loop is not optimized away. For example with clang 11: https://godbolt.org/z/4nW7o37dr

f0():
.LBB0_1:
        jmp     .LBB0_1

However, versions >=12 start to optimize away the loop, including latest clang 20: https://godbolt.org/z/eMGqqv3r4 (try selecting other version)

f0():

So should __clang_major__ <= 12 rather be __clang_major__ >= 12?

Well that seems like a bug (C permits goto loops iirc because they aren't iteration statements). I'd recommend reporting (I might), and I'll bump the version guard to 20 (and also now modern compiled code is affected)

When trying to include the fix, clang shows an error:

<source>:6:18: error: expected string literal in 'asm'
__asm__ volatile(); 

It looks like the function is missing an argument and should be __asm__ volatile("");. Once that's fixed, even clang >=12 seems to keep the loop 👍

Updating rn

It turns out the godbolt links were c++, which has different forward progress rules. We might need to revert this PR. wdyt?

@turbolent
Copy link
Owner

turbolent commented Jul 20, 2025

Ugh, wow, I had no idea that would make a difference. I tried several older and newer clang versions on multiple architectures on Compiler Explorer (with -O3) and none of them optimized away the loop if the code is compiled as C. So it looks like the workaround isn't actually needed.

However, if the issue is then that the generated C code changes semantics when compiled as C++, maybe the workaround shouldn't be removed, but rather be applied if the code is compiled a C++. Maybe the ifdef should just be #ifdef __cplusplus?

@gkgoat1
Copy link
Contributor Author

gkgoat1 commented Jul 20, 2025

Good point. I'll submit a follow-up after I get a stable connection to my computer.

@turbolent turbolent changed the title add full support for legacy LLVM (-12) Fix loops in clang >= 12 Jul 20, 2025
@turbolent
Copy link
Owner

Just opened #122

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants