Odin
Odin copied to clipboard
Compiler crash when global struct field is initialized with a proc call outside main
Context
Odin: dev-2024-07:7f17d4eb7
OS: openSUSE Tumbleweed, Linux 6.9.9-1-default
CPU: AMD Ryzen 9 5900X 12-Core Processor
RAM: 32001 MiB
Backend: LLVM 18.1.8
Expected Behavior
The compiler does not crash when a procedure is used to initialize a global variable outside of main.
Current Behavior
The compiler crashes with an assertion failure. Other than the backtrace below, ~~occasionally I can reproduce the same issue with a much smaller example based on how I order procedure and the global definition in the file~~.
Failure Information (for bugs)
Steps to Reproduce
The always crashing case:
package abort_always
import "core/os/os2"
main :: proc() {
f, err := os2.create_temp_file("", "foo.*")
}
odin run .- observe crash
~~In the intermittent case, it seems to only happen if the called proc is defined later than the global.~~
Edit, after fiddling I found that it's enough to just take a pointer to a compound literal and it will crash just like in case of os2/file_linux.odin.
package abort_intermittent
import "core:fmt"
G :: struct {
g: int,
}
_g := &G {
g = do_init(),
}
main :: proc() {
fmt.println(_g.g)
}
do_init :: proc() -> int {
return 42
}
Failure Logs
src/llvm_backend_general.cpp(3000): Assertion Failure: `LLVMIsConstant(value.value)` = load 7s2.File_Impl-1250, ptr , align 8
Thread 1 "odin" received signal SIGILL, Illegal instruction.
0x0000555555781383 in lb_add_global_generated (m=0x7fffbd4549d0, type=0x7fffe0560130, value=..., entity_=0x0)
at src/llvm_backend_general.cpp:3000
3000 GB_ASSERT_MSG(LLVMIsConstant(value.value), LLVMPrintValueToString(value.value));
(gdb) bt
#0 0x0000555555781383 in lb_add_global_generated (m=0x7fffbd4549d0, type=0x7fffe0560130, value=..., entity_=0x0)
at src/llvm_backend_general.cpp:3000
#1 0x0000555555788831 in lb_build_unary_and (p=0x7fffbd45a9f0, expr=0x7fffd63b57c0) at src/llvm_backend_expr.cpp:3189
#2 0x0000555555783a0b in lb_build_expr_internal (p=0x7fffbd45a9f0, expr=0x7fffd63b57c0) at src/llvm_backend_expr.cpp:3639
#3 0x0000555555780e49 in lb_build_expr (p=0x7fffbd45a9f0, expr=0x7fffd63b57c0) at src/llvm_backend_expr.cpp:3396
#4 0x0000555555798c44 in lb_build_addr_compound_lit (p=0x7fffbd45a9f0, expr=0x7fffd63b5d10) at src/llvm_backend_expr.cpp:4726
#5 0x000055555578df09 in lb_build_addr_internal (p=0x7fffbd45a9f0, expr=0x7fffd63b5d10) at src/llvm_backend_expr.cpp:5331
#6 0x0000555555784b0d in lb_build_addr (p=0x7fffbd45a9f0, expr=0x7fffd63b5d10) at src/llvm_backend_expr.cpp:3856
#7 0x0000555555783cd3 in lb_build_expr_internal (p=0x7fffbd45a9f0, expr=0x7fffd63b5d10) at src/llvm_backend_expr.cpp:3657
#8 0x0000555555780e49 in lb_build_expr (p=0x7fffbd45a9f0, expr=0x7fffd63b5d10) at src/llvm_backend_expr.cpp:3396
#9 0x000055555572f471 in lb_create_startup_runtime (main_module=0x7fffbd4549d0, objc_names=0x0, global_variables=...)
at src/llvm_backend.cpp:1218
#10 0x000055555559f316 in lb_generate_code (gen=0x7fffbd4548f0) at src/llvm_backend.cpp:3353
#11 0x00005555555812c2 in main (arg_count=3, arg_ptr=0x7fffffffe1a8) at src/main.cpp:3394
So I've been staring at the debugger for a while, to maybe fix this. (I haven't got any smarter when it comes to LLVM codegen...)
I did a very dump thing, because I just couldn't figure out what LLVM wants from me.
diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp
index a91c1d1fe..ca685bfdc 100644
--- a/src/llvm_backend_general.cpp
+++ b/src/llvm_backend_general.cpp
@@ -2996,7 +2996,7 @@ gb_internal lbAddr lb_add_global_generated(lbModule *m, Type *type, lbValue valu
lbValue g = {};
g.type = alloc_type_pointer(type);
g.value = LLVMAddGlobal(m->mod, lb_type(m, type), cast(char const *)str);
- if (value.value != nullptr) {
+ if (value.value != nullptr && LLVMIsConstant(value.value)) {
GB_ASSERT_MSG(LLVMIsConstant(value.value), LLVMPrintValueToString(value.value));
LLVMSetInitializer(g.value, value.value);
} else {
That change will end up generating code that works, although I highly doubt that I did the right thing... The odin code was
package repro
import "core:os"
G :: struct {
val: int
}
_g := &G {
val = init_global()
}
init_global :: proc() -> int {
return 42
}
main :: proc() {
os.exit(_g.val)
}
; Function Attrs: noinline optnone
define void @"__$startup_runtime"(ptr noalias nocapture nonnull %__.context_ptr) #0 {
decls:
%0 = alloca %repro.G, align 8
br label %entry
entry: ; preds = %decls
call void @llvm.memset.inline.p0.i64(ptr %0, i8 0, i64 8, i1 false)
store %repro.G zeroinitializer, ptr %0, align 8
%1 = call i64 @repro.init_global(ptr %__.context_ptr)
%2 = getelementptr inbounds %repro.G, ptr %0, i32 0, i32 0
store i64 %1, ptr %2, align 8
%3 = load %repro.G, ptr %0, align 8
store %repro.G %3, ptr @"ggv$0", align 8
ret void
}
If someone has some pointers on what I should be looking for I'm happy to keep digging.