To clarify, this is not implementation-defined or undefined in LLVM in the absence of nsw/nuw. LLVM’s lang ref is explicit about wrapping on overflow being the behavior add/sub/mul: LLVM Language Reference Manual — LLVM 22.0.0git documentation. I think what you are pointing out could be that there’s no ‘wrap’ keyword to indicate wrapping in LLVM, but this seems like more of a syntactic property to me since the semantics for these instructions very explicitly define wrapping.
This proposal would result in the same default being documented at the level of arith. And in the future, nothing stops from adding attributes or new ops for different behaviors like nsw/nuw or saturation. I’d like to avoid an all-or-nothing approach and document what the current semantics are before we branch out and start exploring possible future implementations.
To generalize this line of thinking, I think that we could say that the lowering chain has to result in refinement at each step, up until the hardware/runtime. It’s generally fine to make things more defined as we lower, but making the behavior less defined is not. Because the source language may leave overflow undefined/implementation defined, it may be useful to preserve it through the lowering chain, hoping some layers can exploit it and simplify the code.
This justifies having mechanisms like nsw/nuw in llvm, but I don’t see how leaving things implementation-defined in the middle of the lowering chain helps. For example, I wouldn’t want an ‘implementation-defined’ addition overflow that returns a non-deterministic result (even for the same pair of inputs). I’d rather have something with tighter semantics like poison that I can reason about. That’s why I see making overflow ‘implementation-defined’ a strictly worse option that any other set of semantics described.