roc/test/int/platform/host.zig
Luke Boswell cb42d26e11
Roc platform host shim (#8214)
* use zig Builder to generate LLVM bc for platform host shim

* WIP

* Working embedded LLVM compilation

* lints

* Update test_shared_memory_system.zig

* Update main.zig

* Refactor out builder into separate file

* remove clang fallback, remove -Dllvm build flag

* WIP change platform header

* WIP no hardcoded platform entrypoints

* WIP no hardcoded entrypoints

* remove hardcoded interpreter shim entrypoint

* WIP multiple entrypoints

* WIP multiple entrypoints

* WIP alignment issue

* it's working!!

* cleanup

* fmt

* fix tests and snapshots

* fix cross-compile

* WIP fix linking linux

* improved debug logging in rocRun

* WIP fix linux segfaults

* cleanup stray dbgs

* fix Windows linking

* WIP remove std.debug.print in prebuilt shim

* remove libc stubs, try C shim for platform host main

* WORKING ON LINUX!!!

* WIP fixing int platform

* prevent double mapping of shared memory

* WIP

* remove clang fallback

* cleanup test platforms

* vendor linux object files for musl and gnu in test platforms

* fix windows __main export

* typo

* avoide hardcoding libc path and dynamic linker on linux

* some fixes

* try CI fix for linux ARM64

* try use system CRT for ARM64

* use absolute pats for vendored CRT

* minimal roc build

* use target os for filename not host os

* implement app stubs for test platforms

* fix glibc

* WIP cross compilation

* fix run, fix build for native

* Merge remote/main

* fix macos

* add bookends

* remove hosts

* fix cross-compile for linux on macos

* fix files paths for Windows

* Update main.zig

* test ubu 24.04 too

Signed-off-by: Anton-4 <17049058+Anton-4@users.noreply.github.com>

* debug: log all used instructions

* fmt

* get bad instruction with gdb

* install gdb

* Revert last 4 commits

* simpler cross-compile arm64 runtime files

* add minimal cross-compilation for glibc

* simplify stub generation

* Update test_int_platform.sh

* Update ci_cross_compile.yml

* Update ci_cross_compile.yml

* try alpine3.18 x64musl files

* Claude's fix + removed CPU instructions

* fix fuzz crash empty after merge

* update snapshot

* restore full CI

* some cleanup

* remove debugging from CI - print all supported CPU instructions

* try using generic when LLVM compiles app stub

* enhanced error handling for app stub compilation using LLVM

* check machine ABI on linux

* try more debugging

* update test script to inidicate truncated error logs, temporarily display everything for arm64 linux errors

* fix missing arm64 libc search paths

* back to normal CI

* try without .git 

See https://github.com/ziglang/zig/issues/21316#issuecomment-2408071050

Signed-off-by: Anton-4 <17049058+Anton-4@users.noreply.github.com>

* undo .git change

Signed-off-by: Anton-4 <17049058+Anton-4@users.noreply.github.com>

* retry zig build command

Signed-off-by: Anton-4 <17049058+Anton-4@users.noreply.github.com>

* different retry strategy

Signed-off-by: Anton-4 <17049058+Anton-4@users.noreply.github.com>

---------

Signed-off-by: Anton-4 <17049058+Anton-4@users.noreply.github.com>
Co-authored-by: Anton-4 <17049058+Anton-4@users.noreply.github.com>
2025-09-09 13:15:23 +02:00

168 lines
6.3 KiB
Zig

//! Simple platform host that calls into a simplified Roc entrypoint with two integers and prints the result.
const std = @import("std");
const builtins = @import("builtins");
/// Host environment - contains our arena allocator
const HostEnv = struct {
arena: std.heap.ArenaAllocator,
};
/// Roc allocation function
fn rocAllocFn(roc_alloc: *builtins.host_abi.RocAlloc, env: *anyopaque) callconv(.C) void {
const host: *HostEnv = @ptrCast(@alignCast(env));
const allocator = host.arena.allocator();
const log2_align = std.math.log2_int(u32, @intCast(roc_alloc.alignment));
const align_enum: std.mem.Alignment = @enumFromInt(log2_align);
const result = allocator.rawAlloc(roc_alloc.length, align_enum, @returnAddress());
roc_alloc.answer = result orelse {
@panic("Host allocation failed");
};
}
/// Roc deallocation function
fn rocDeallocFn(roc_dealloc: *builtins.host_abi.RocDealloc, env: *anyopaque) callconv(.C) void {
_ = roc_dealloc;
_ = env;
// NoOp as our arena frees all memory at once
}
/// Roc reallocation function
fn rocReallocFn(roc_realloc: *builtins.host_abi.RocRealloc, env: *anyopaque) callconv(.C) void {
_ = roc_realloc;
_ = env;
@panic("Realloc not implemented in this example");
}
/// Roc debug function
fn rocDbgFn(roc_dbg: *const builtins.host_abi.RocDbg, env: *anyopaque) callconv(.C) void {
_ = env;
const message = roc_dbg.utf8_bytes[0..roc_dbg.len];
std.debug.print("ROC DBG: {s}\n", .{message});
}
/// Roc expect failed function
fn rocExpectFailedFn(roc_expect: *const builtins.host_abi.RocExpectFailed, env: *anyopaque) callconv(.C) void {
_ = env;
const message = roc_expect.utf8_bytes[0..roc_expect.len];
std.debug.print("ROC EXPECT FAILED: {s}\n", .{message});
}
/// Roc crashed function
fn rocCrashedFn(roc_crashed: *const builtins.host_abi.RocCrashed, env: *anyopaque) callconv(.C) noreturn {
_ = env;
const message = roc_crashed.utf8_bytes[0..roc_crashed.len];
@panic(message);
}
// External symbols provided by the Roc runtime object file
// Follows RocCall ABI: ops, ret_ptr, then argument pointers
extern fn roc__addInts(ops: *builtins.host_abi.RocOps, ret_ptr: *anyopaque, arg_ptr: ?*anyopaque) callconv(.C) void;
extern fn roc__multiplyInts(ops: *builtins.host_abi.RocOps, ret_ptr: *anyopaque, arg_ptr: ?*anyopaque) callconv(.C) void;
// OS-specific entry point handling
comptime {
// Export main for all platforms
@export(&main, .{ .name = "main" });
// Windows MinGW/MSVCRT compatibility: export __main stub
if (@import("builtin").os.tag == .windows) {
@export(&__main, .{ .name = "__main" });
}
}
// Windows MinGW/MSVCRT compatibility stub
// The C runtime on Windows calls __main from main for constructor initialization
fn __main() callconv(.C) void {}
// C compatible main for runtime
fn main(argc: c_int, argv: [*][*:0]u8) callconv(.C) c_int {
_ = argc;
_ = argv;
platform_main() catch |err| {
std.io.getStdErr().writer().print("HOST ERROR: {?}", .{err}) catch unreachable;
return 1;
};
return 0;
}
/// Platform host entrypoint -- this is where the roc application starts and does platform things
/// before the platform calls into Roc to do application-specific things.
fn platform_main() !void {
var host_env = HostEnv{
.arena = std.heap.ArenaAllocator.init(std.heap.page_allocator),
};
defer host_env.arena.deinit(); // Clean up all allocations on exit
const stdout = std.io.getStdOut().writer();
// Create the RocOps struct
var roc_ops = builtins.host_abi.RocOps{
.env = @as(*anyopaque, @ptrCast(&host_env)),
.roc_alloc = rocAllocFn,
.roc_dealloc = rocDeallocFn,
.roc_realloc = rocReallocFn,
.roc_dbg = rocDbgFn,
.roc_expect_failed = rocExpectFailedFn,
.roc_crashed = rocCrashedFn,
.host_fns = undefined, // No host functions for this simple example
};
// Generate random integers using current timestamp as seed
var rand = std.Random.DefaultPrng.init(@intCast(std.time.timestamp()));
const a = rand.random().intRangeAtMost(i64, 0, 100);
const b = rand.random().intRangeAtMost(i64, 0, 100);
// Arguments struct for passing two integers to Roc as a tuple
const Args = extern struct { a: i64, b: i64 };
var args = Args{ .a = a, .b = b };
try stdout.print("Generated numbers: a = {}, b = {}\n", .{ a, b });
// Test first entrypoint: addInts (entry_idx = 0)
try stdout.print("\n=== Testing addInts (entry_idx = 0) ===\n", .{});
var add_result: i64 = undefined;
roc__addInts(&roc_ops, @as(*anyopaque, @ptrCast(&add_result)), @as(*anyopaque, @ptrCast(&args)));
const expected_add = a +% b; // Use wrapping addition to match Roc behavior
try stdout.print("Expected add result: {}\n", .{expected_add});
try stdout.print("Roc computed add: {}\n", .{add_result});
var success_count: u32 = 0;
if (add_result == expected_add) {
try stdout.print("\x1b[32mSUCCESS\x1b[0m: addInts results match!\n", .{});
success_count += 1;
} else {
try stdout.print("\x1b[31mFAIL\x1b[0m: addInts results differ!\n", .{});
}
// Test second entrypoint: multiplyInts (entry_idx = 1)
try stdout.print("\n=== Testing multiplyInts (entry_idx = 1) ===\n", .{});
var multiply_result: i64 = undefined;
roc__multiplyInts(&roc_ops, @as(*anyopaque, @ptrCast(&multiply_result)), @as(*anyopaque, @ptrCast(&args)));
const expected_multiply = a *% b; // Use wrapping multiplication to match Roc behavior
try stdout.print("Expected multiply result: {}\n", .{expected_multiply});
try stdout.print("Roc computed multiply: {}\n", .{multiply_result});
if (multiply_result == expected_multiply) {
try stdout.print("\x1b[32mSUCCESS\x1b[0m: multiplyInts results match!\n", .{});
success_count += 1;
} else {
try stdout.print("\x1b[31mFAIL\x1b[0m: multiplyInts results differ!\n", .{});
}
// Final summary
try stdout.print("\n=== FINAL RESULT ===\n", .{});
if (success_count == 2) {
try stdout.print("\x1b[32mALL TESTS PASSED\x1b[0m: Both entrypoints work correctly!\n", .{});
} else {
try stdout.print("\x1b[31mSOME TESTS FAILED\x1b[0m: {}/2 tests passed\n", .{success_count});
std.process.exit(1);
}
}