@@ -625,7 +625,8 @@ fn generatePlatformHostShim(allocs: *Allocators, cache_dir: []const u8, entrypoi
625625 }
626626
627627 // Create the complete platform shim
628- platform_host_shim .createInterpreterShim (& llvm_builder , entrypoints .items ) catch | err | {
628+ // Note: Symbol names include platform-specific prefixes (underscore for macOS)
629+ platform_host_shim .createInterpreterShim (& llvm_builder , entrypoints .items , target ) catch | err | {
629630 std .log .err ("Failed to create interpreter shim: {}" , .{err });
630631 return err ;
631632 };
@@ -1103,15 +1104,16 @@ fn runWithWindowsHandleInheritance(allocs: *Allocators, exe_path: []const u8, sh
11031104 _ = ipc .platform .windows .CloseHandle (process_info .hProcess );
11041105 _ = ipc .platform .windows .CloseHandle (process_info .hThread );
11051106
1106- // Check exit code
1107+ // Check exit code and propagate to parent
11071108 if (exit_code != 0 ) {
1108- std .log .err ("Child process {s} exited with code: {}" , .{ exe_path , exit_code });
1109+ std .log .debug ("Child process {s} exited with code: {}" , .{ exe_path , exit_code });
11091110 if (exit_code == 0xC0000005 ) { // STATUS_ACCESS_VIOLATION
11101111 std .log .err ("Child process crashed with access violation (segfault)" , .{});
11111112 } else if (exit_code >= 0xC0000000 ) { // NT status codes for exceptions
11121113 std .log .err ("Child process crashed with exception code: 0x{X}" , .{exit_code });
11131114 }
1114- return error .ProcessExitedWithError ;
1115+ // Propagate the exit code (truncated to u8 for compatibility)
1116+ std .process .exit (@truncate (exit_code ));
11151117 }
11161118
11171119 std .log .debug ("Child process completed successfully" , .{});
@@ -1198,9 +1200,9 @@ fn runWithPosixFdInheritance(allocs: *Allocators, exe_path: []const u8, shm_hand
11981200 if (exit_code == 0 ) {
11991201 std .log .debug ("Child process completed successfully" , .{});
12001202 } else {
1201- // The host exited with an error - it should have printed any error messages
1203+ // Propagate the exit code from the child process to our parent
12021204 std .log .debug ("Child process {s} exited with code: {}" , .{ temp_exe_path , exit_code });
1203- return error . ProcessExitedWithError ;
1205+ std . process . exit ( exit_code ) ;
12041206 }
12051207 },
12061208 .Signal = > | signal | {
@@ -1212,7 +1214,8 @@ fn runWithPosixFdInheritance(allocs: *Allocators, exe_path: []const u8, shm_hand
12121214 } else if (signal == 9 ) { // SIGKILL
12131215 std .log .err ("Child process was killed (SIGKILL)" , .{});
12141216 }
1215- return error .ProcessKilledBySignal ;
1217+ // Standard POSIX convention: exit with 128 + signal number
1218+ std .process .exit (128 + | @as (u8 , @truncate (signal )));
12161219 },
12171220 .Stopped = > | signal | {
12181221 std .log .err ("Child process {s} stopped by signal: {}" , .{ temp_exe_path , signal });
@@ -1358,6 +1361,10 @@ pub fn setupSharedMemoryWithModuleEnv(allocs: *Allocators, roc_file_path: []cons
13581361 entry_count : u32 ,
13591362 def_indices_offset : u64 ,
13601363 module_envs_offset : u64 ,
1364+ /// Offset to platform's main.roc env (0 if no platform, entry points are in app)
1365+ platform_main_env_offset : u64 ,
1366+ /// Offset to app env (always present, used for e_lookup_required resolution)
1367+ app_env_offset : u64 ,
13611368 };
13621369
13631370 const header_ptr = try shm_allocator .create (Header );
@@ -1624,7 +1631,24 @@ pub fn setupSharedMemoryWithModuleEnv(allocs: *Allocators, roc_file_path: []cons
16241631 // Store app env at the last index (N-1, after platform modules at 0..N-2)
16251632 module_env_offsets_ptr [total_module_count - 1 ] = @intFromPtr (app_env_ptr ) - @intFromPtr (shm .base_ptr );
16261633
1627- const exports_slice = app_env .store .sliceDefs (app_env .exports );
1634+ // Store app env offset for e_lookup_required resolution
1635+ header_ptr .app_env_offset = @intFromPtr (app_env_ptr ) - @intFromPtr (shm .base_ptr );
1636+
1637+ // Entry points are defined in the platform's `provides` section.
1638+ // The platform wraps app-provided functions (from `requires`) and exports them for the host.
1639+ // For example: `provides { main_for_host!: "main" }` where `main_for_host! = main!`
1640+ const platform_env = platform_main_env orelse {
1641+ std .log .err ("No platform found. Every Roc app requires a platform." , .{});
1642+ return error .NoPlatformFound ;
1643+ };
1644+ const exports_slice = platform_env .store .sliceDefs (platform_env .exports );
1645+ if (exports_slice .len == 0 ) {
1646+ std .log .err ("Platform has no exports in `provides` clause." , .{});
1647+ return error .NoEntrypointFound ;
1648+ }
1649+
1650+ // Store platform env offset for entry point lookups
1651+ header_ptr .platform_main_env_offset = @intFromPtr (platform_env ) - @intFromPtr (shm .base_ptr );
16281652 header_ptr .entry_count = @intCast (exports_slice .len );
16291653
16301654 const def_indices_ptr = try shm_allocator .alloc (u32 , exports_slice .len );
@@ -2347,15 +2371,39 @@ fn extractEntrypointsFromPlatform(allocs: *Allocators, roc_file_path: []const u8
23472371 const provides_coll = parse_ast .store .getCollection (platform_header .provides );
23482372 const provides_fields = parse_ast .store .recordFieldSlice (.{ .span = provides_coll .span });
23492373
2350- // Extract all field names as entrypoints
2374+ // Extract FFI symbol names from provides clause
2375+ // Format: `provides { roc_identifier: "ffi_symbol_name" }`
2376+ // The string value specifies the symbol name exported to the host (becomes roc__<symbol>)
23512377 for (provides_fields ) | field_idx | {
23522378 const field = parse_ast .store .getRecordField (field_idx );
2353- const field_name = parse_ast .resolve (field .name );
2354- // Strip trailing '!' from effectful function names for the exported symbol
2355- const symbol_name = if (std .mem .endsWith (u8 , field_name , "!" ))
2356- field_name [0 .. field_name .len - 1 ]
2357- else
2358- field_name ;
2379+
2380+ // Require explicit string value for symbol name
2381+ const symbol_name = if (field .value ) | value_idx | blk : {
2382+ const value_expr = parse_ast .store .getExpr (value_idx );
2383+ switch (value_expr ) {
2384+ .string = > | str_like | {
2385+ const parts = parse_ast .store .exprSlice (str_like .parts );
2386+ if (parts .len > 0 ) {
2387+ const first_part = parse_ast .store .getExpr (parts [0 ]);
2388+ switch (first_part ) {
2389+ .string_part = > | sp | break :blk parse_ast .resolve (sp .token ),
2390+ else = > {},
2391+ }
2392+ }
2393+ std .log .err ("Invalid provides entry: string value is empty" , .{});
2394+ return error .InvalidProvidesEntry ;
2395+ },
2396+ .string_part = > | str_part | break :blk parse_ast .resolve (str_part .token ),
2397+ else = > {
2398+ std .log .err ("Invalid provides entry: expected string value for symbol name" , .{});
2399+ return error .InvalidProvidesEntry ;
2400+ },
2401+ }
2402+ } else {
2403+ const field_name = parse_ast .resolve (field .name );
2404+ std .log .err ("Provides entry '{s}' missing symbol name. Use format: {{ {s}: \" symbol_name\" }}" , .{ field_name , field_name });
2405+ return error .InvalidProvidesEntry ;
2406+ };
23592407 try entrypoints .append (try allocs .arena .dupe (u8 , symbol_name ));
23602408 }
23612409
0 commit comments