It's especially funny when this happens in Rust, which is marketed as a "safer" alternative.
Would you like a segfault out of nowhere in safe Rust? The issue is still open after two years by the way: https://github.com/rust-lang/rust/issues/107975
It's especially funny when this happens in Rust, which is marketed as a "safer" alternative.
Would you like a segfault out of nowhere in safe Rust? The issue is still open after two years by the way: https://github.com/rust-lang/rust/issues/107975
The difference is zero, but the compiler thinks it is non-zero because it thinks they are unequal.
From there you turn it into type confusion through an array, and then whatever you want. Almost any wrong compiler assumption can be exploited. This particular way to do it has also been used several times to exploit bugs in Javscript engines.
I got involved in a discussion with a Rust guy when trying to get C with SIMD intrinsics into wasi-libc where something that the C standard explicitly state is “implementation defined” (and so, sane, as we're targeting a single implementation - LLVM) can't be trusted, because LLVM may turn it back into UB because “reasons.”
At this point Go and Zig made the right choice to dump it. I don't know about Rust.
The issue is that frontends want to emit code that is as optimizeable as possible, so they opt into the complexity of specifying additional constraints, attributes, and guarantees, each of which risks triggering undefined behavior if the frontend has a bug and emits wrong information.
I was not the one making this claim:
> However, I believe that currently, there is no well-defined way to actually achieve this on the LLVM IR level. Using plain loads for this is UB (even if it may usually work out in practice, and I'm sure plenty of C code just does that).
My claim is that the below snippet is implemention defined (not UB):
// Casting through uintptr_t makes this implementation-defined,
// rather than undefined behavior.
uintptr_t align = (uintptr_t)s % sizeof(v128_t);
const v128_t *v = (v128_t *)((uintptr_t)s - align);
Further, that this is actually defined by the implementation to do the correct thing, by any good faith reading of the standard:> The mapping functions for converting a pointer to an integer or an integer to a pointer are intended to be consistent with the addressing structure of the execution environment.
I further suggested laundering the pointer with something like the below, but was told it would amount to nothing, again the blame being put on LLVM:
asm ("" : "+r"(v))
I honestly don't know if LLVM or clang should be to blame. I was told LLVM IR and took it in good faith.A straightforward implementation of memchr, i.e. finding the index of a particular byte inside an array of bytes, looks like this:
for (bytes, 0..) |byte, i| {
if (byte == search) return i;
}
return null;
This is trivial to lower to well-defined LLVM IR.But it's desirable to use tricks to make the function really fast, such as assuming that you can read up to the page boundary with SIMD instructions[1]. This is generally true on real world hardware, but this is incompatible with the pointer provenance memory model, which is load-bearing for important optimizations that C, C++, Rust, and Zig all rely on.
So if you want to do such tricks you have to do it in a black box that is exempt from the memory model rules. The Zig code I link to here is unsound because it does not do this. An optimization pass, whether it be implemented in Zig pipeline or LLVM pipeline, would be able to prove that it writes outside a pointer provenance, mark that particular control flow unreachable, and thereby cause undefined behavior if it happens.
This is not really LLVM's fault. This is a language shortcoming in C, C++, Rust, Zig, and probably many others. It's a fundamental conflict between the utility of pointer provenance rules, and the utility of ignoring that crap and just doing what you know the machine allows you to do.
[1]: https://github.com/ziglang/zig/blob/0.14.1/lib/std/mem.zig#L...
I was the original contributor of the SIMD code, and got this… pushback.
I still don't quite understand how you can marry ”pointer provenance” with the intent that converting between pointers and integers is “to be consistent with the addressing structure of the execution environment” and want to allow DMA in your language, but then this is UB.
But well, a workable version of it got submitted, I've made subsequent contributions (memchr, strchr, str[c]spn…), all good.
Just makes me salty on C, as if I needed more reasons to.
https://github.com/llvm/llvm-project/blob/main/flang/docs/Hi...
Maybe the work in Swift (SIL), Rust (MIR), Julia (SSAIR) that were partially the inspiration for MLIR, alongside work done at Google designing Tensorflow compiler?
The main goal being an IR that would accomodate all use cases of those high level IRs.
Here are the presentation talk slides at European LLVM Developers Meeting back in 2019,
https://llvm.org/devmtg/2019-04/slides/Keynote-ShpeismanLatt...
Also you can find many general purpose enough users around this listing,
C and C++ took over many of the use cases people where using Fortran for during those decades.
In 2025, while it is a general purpose language, its use is constrained to scientific computing and HPC.
Most wannabe CUDA replacements keep forgetting Fortran is one of the reasons scientific community ignored OpenCL.
So maybe all those universities, companies and the LLVM project know kind of what they are doing.
Mojo and Flang, were designed from scratch using MLIR, as there are many other newer languages on the LLVM ecosystem.
I see it as the field experience of folks that know a little bit more than I ever will about compiler design.