@@ -71,6 +71,9 @@ pub struct LLVMBackend<'ctx> {
7171
7272 /// Current function being compiled (for accessing locals, blocks, etc.)
7373 current_function : Option < FunctionValue < ' ctx > > ,
74+
75+ /// Maps HIR global IDs to compiled LLVM global values (persists across functions)
76+ globals_map : IndexMap < HirId , BasicValueEnum < ' ctx > > ,
7477}
7578
7679impl < ' ctx > LLVMBackend < ' ctx > {
@@ -93,6 +96,7 @@ impl<'ctx> LLVMBackend<'ctx> {
9396 block_map : IndexMap :: new ( ) ,
9497 phi_map : IndexMap :: new ( ) ,
9598 current_function : None ,
99+ globals_map : IndexMap :: new ( ) ,
96100 }
97101 }
98102
@@ -259,6 +263,20 @@ impl<'ctx> LLVMBackend<'ctx> {
259263 let undef_value = llvm_type. const_zero ( ) ; // or use undef if available
260264 self . value_map . insert ( * value_id, undef_value) ;
261265 }
266+ HirValueKind :: Undef => {
267+ // Map undef values to zero constants (for IDF-based SSA)
268+ // This handles void-returning function calls where SSA creates undef placeholders
269+ let llvm_type = self . translate_type ( & value. ty ) ?;
270+ let undef_value = llvm_type. const_zero ( ) ;
271+ self . value_map . insert ( * value_id, undef_value) ;
272+ }
273+ HirValueKind :: Global ( global_id) => {
274+ // Map global references to their LLVM global values
275+ // The global should have been compiled in phase 1
276+ if let Some ( & global_value) = self . globals_map . get ( global_id) {
277+ self . value_map . insert ( * value_id, global_value) ;
278+ }
279+ }
262280 _ => { }
263281 }
264282 }
@@ -325,12 +343,48 @@ impl<'ctx> LLVMBackend<'ctx> {
325343 /// This creates LLVM global variables with appropriate linkage and initializers.
326344 /// For vtables, the initializer contains an array of function pointers.
327345 fn compile_global ( & mut self , id : HirId , global : & HirGlobal ) -> CompilerResult < ( ) > {
328- // Translate the global's type to LLVM type
329- let llvm_ty = self . translate_type ( & global. ty ) ?;
330-
331346 // Create unique name for the global
332347 let global_name = format ! ( "global__{:?}" , id) ;
333348
349+ // Handle string constants specially - emit in Haxe String format: [length: i32][utf8_bytes...]
350+ // This matches the Cranelift backend format so runtime functions work correctly
351+ if let Some ( HirConstant :: String ( s) ) = & global. initializer {
352+ let actual_string = s. resolve_global ( ) . unwrap_or_else ( || {
353+ log:: warn!( "Could not resolve InternedString for global, using empty string" ) ;
354+ std:: string:: String :: new ( )
355+ } ) ;
356+
357+ // Get UTF-8 bytes
358+ let bytes = actual_string. as_bytes ( ) ;
359+ let length = bytes. len ( ) as i32 ;
360+
361+ // Create Haxe String structure: [length: i32][utf8_bytes...]
362+ // The struct is { i32, [N x i8] }
363+ let i32_type = self . context . i32_type ( ) ;
364+ let byte_array_type = self . context . i8_type ( ) . array_type ( bytes. len ( ) as u32 ) ;
365+ let haxe_string_type = self . context . struct_type ( & [ i32_type. into ( ) , byte_array_type. into ( ) ] , false ) ;
366+
367+ // Create the length constant
368+ let length_const = i32_type. const_int ( length as u64 , false ) ;
369+
370+ // Create the byte array constant (no null terminator needed)
371+ let byte_const = self . context . const_string ( bytes, false ) ;
372+
373+ // Create the struct constant
374+ let haxe_string_const = haxe_string_type. const_named_struct ( & [ length_const. into ( ) , byte_const. into ( ) ] ) ;
375+
376+ let global_value = self . module . add_global ( haxe_string_type, Some ( AddressSpace :: default ( ) ) , & global_name) ;
377+ global_value. set_linkage ( inkwell:: module:: Linkage :: External ) ;
378+ global_value. set_initializer ( & haxe_string_const) ;
379+
380+ // Store the pointer to the global (address of the Haxe string struct)
381+ self . globals_map . insert ( id, global_value. as_pointer_value ( ) . into ( ) ) ;
382+ return Ok ( ( ) ) ;
383+ }
384+
385+ // Translate the global's type to LLVM type
386+ let llvm_ty = self . translate_type ( & global. ty ) ?;
387+
334388 // Add global variable to module
335389 let global_value = self . module . add_global ( llvm_ty, Some ( AddressSpace :: default ( ) ) , & global_name) ;
336390
@@ -376,8 +430,8 @@ impl<'ctx> LLVMBackend<'ctx> {
376430 global_value. set_initializer ( & llvm_ty. const_zero ( ) ) ;
377431 }
378432
379- // Store the global in value_map so it can be referenced
380- self . value_map . insert ( id, global_value. as_pointer_value ( ) . into ( ) ) ;
433+ // Store the global in globals_map so it can be referenced across functions
434+ self . globals_map . insert ( id, global_value. as_pointer_value ( ) . into ( ) ) ;
381435
382436 Ok ( ( ) )
383437 }
@@ -547,7 +601,32 @@ impl<'ctx> LLVMBackend<'ctx> {
547601 }
548602
549603 HirTerminator :: Unreachable => {
550- self . builder . build_unreachable ( ) ?;
604+ // For void-returning functions, emit a return instead of unreachable/trap
605+ // This handles Haxe/other languages where main() returns Void and has no explicit return
606+ if let Some ( func) = self . current_function {
607+ let return_type = func. get_type ( ) . get_return_type ( ) ;
608+ if return_type. is_none ( ) {
609+ // Void function - emit return
610+ self . builder . build_return ( None ) ?;
611+ } else if let Some ( ty) = return_type {
612+ // Check if it's an empty struct (our representation of Unit/Void type)
613+ if let inkwell:: types:: BasicTypeEnum :: StructType ( st) = ty {
614+ if st. count_fields ( ) == 0 {
615+ // Empty struct (Unit) - emit return with undef value
616+ let ret_val = st. get_undef ( ) ;
617+ self . builder . build_return ( Some ( & ret_val) ) ?;
618+ } else {
619+ self . builder . build_unreachable ( ) ?;
620+ }
621+ } else {
622+ self . builder . build_unreachable ( ) ?;
623+ }
624+ } else {
625+ self . builder . build_unreachable ( ) ?;
626+ }
627+ } else {
628+ self . builder . build_unreachable ( ) ?;
629+ }
551630 }
552631
553632 HirTerminator :: PatternMatch { value, patterns, default } => {
@@ -2725,7 +2804,12 @@ impl<'ctx> LLVMBackend<'ctx> {
27252804
27262805 // String constant
27272806 String ( s) => {
2728- let string_value = self . context . const_string ( s. to_string ( ) . as_bytes ( ) , true ) ;
2807+ // Resolve the InternedString to get the actual string value
2808+ let actual_string = s. resolve_global ( ) . unwrap_or_else ( || {
2809+ log:: warn!( "Could not resolve InternedString, using empty string" ) ;
2810+ std:: string:: String :: new ( )
2811+ } ) ;
2812+ let string_value = self . context . const_string ( actual_string. as_bytes ( ) , true ) ;
27292813 string_value. into ( )
27302814 }
27312815
0 commit comments