`no-builtins` fails on `__muloti4` on WASM, causing infinite recursion in Zig's compiler-rt implementation
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
```llvm
; ModuleID = 'repro'
source_filename = "repro"
target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20"
target triple = "wasm32-unknown-wasi0.1.0-unknown"
; Function Attrs: noredzone nounwind uwtable
define i128 @custom_muloti4(i128 %0, i128 %1, ptr nonnull %2) unnamed_addr #0 align 1 {
3:
store i32 0, ptr %2, align 4
%4 = mul i128 %0, %1
%5 = icmp slt i128 %0, 0
br i1 %5, label %9, label %11
6:
ret i128 %4
7:
%8 = phi i1 [ %10, %9 ], [ false, %11 ]
br i1 %8, label %14, label %15
9:
%10 = icmp eq i128 %1, -170141183460469231731687303715884105728
br label %7
11:
br label %7
12:
%13 = phi i1 [ true, %14 ], [ %18, %17 ]
br i1 %13, label %23, label %24
14:
br label %12
15:
%16 = icmp ne i128 %0, 0
br i1 %16, label %19, label %22
17:
%18 = phi i1 [ %21, %19 ], [ false, %22 ]
br label %12
19:
%20 = sdiv i128 %4, %0
%21 = icmp ne i128 %20, %1
br label %17
22:
br label %17
23:
store i32 1, ptr %2, align 4
br label %6
24:
br label %6
}
attributes #0 = { noredzone nounwind uwtable "frame-pointer"="all" "target-cpu"="lime1" "target-features"="+bulk-memory-opt,+call-indirect-overlong,+extended-const,+multivalue,+mutable-globals,+nontrapping-fptoint,+sign-ext,-atomics,-bulk-memory,-exception-handling,-fp16,-multimemory,-reference-types,-relaxed-simd,-simd128,-tail-call,-wide-arithmetic" "no-builtins" }
!llvm.module.flags = !{}
```
```sh-session
$ opt -passes=instcombine repro.ll | llc -O0 | head -n35
```
```wasm
.file "repro"
.functype __muloti4 (i32, i64, i64, i64, i64, i32) -> ()
.globaltype __stack_pointer, i32
.functype custom_muloti4 (i32, i64, i64, i64, i64, i32) -> ()
.section .text.custom_muloti4,"",@
.globl custom_muloti4 # -- Begin function custom_muloti4
.type custom_muloti4,@function
custom_muloti4: # @custom_muloti4
.functype custom_muloti4 (i32, i64, i64, i64, i64, i32) -> ()
.local i32, i32, i64, i64, i32, i32, i32, i32
# %bb.0:
global.get __stack_pointer
i32.const 32
i32.sub
local.set 6
local.get 6
global.set __stack_pointer
i32.const 0
local.set 7
local.get 5
local.get 7
i32.store 0
local.get 6
local.get 7
i32.store 28
local.get 6
local.get 1
local.get 2
local.get 3
local.get 4
local.get 6
i32.const 28
i32.add
call __muloti4
local.get 6
```
Observe the generated `call __muloti4` instruction in the assembly, despite the original LLVM IR having the `no-builtins` attribute. Reproduces on `release/22.x` (4592b0b6388d105ea9b688759c1810ad519fded3).
This does *not* happen if the function is actually named `@__muloti4` in the IR (regardless of whether the `no-builtins` attribute is used---it seems to have no effect either way). Presumably the backend is checking the function name to avoid recursion in LLVM's implementation of compiler-rt.
This affects the Zig project because we have our own compiler-rt implementation, written and maintained in Zig. It so happens that our `__muloti4` implementation is in a function which is not named `@__muloti4` in the IR, because it is instead exposed by an alias. (Of course, you don't *need* to use `alias` to hit this bug; you could just have your `@__muloti4` function *call* `@custom_muloti4`.) We have implemented a [workaround](https://codeberg.org/ziglang/zig/pulls/31678/commits/f08a2998235a91c12bf3e68ef32a30a950facb8a), but it seems rather brittle.
The instcombine pass is introducing a call to `@llvm.smul.with.overflow.i128`. This happens on all targets, but is usually unproblematic, because backends will lower it directly. I'm not really sure how `no-builtins` is intended to work, but I suspect that this instcombine rule should be disabled when `no-builtins` is enabled if `@llvm.smul.with.overflow.i128` requires a libcall on the target.
Contributor guide
Assessment
This issue has not been assessed yet.