Odin icon indicating copy to clipboard operation
Odin copied to clipboard

Compiler crash when global struct field is initialized with a proc call outside main

Open zen3ger opened this issue 1 year ago • 1 comments

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

zen3ger avatar Jul 29 '24 23:07 zen3ger

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.

zen3ger avatar Aug 04 '24 19:08 zen3ger