diff --git a/build.zig b/build.zig index 89630f8..68d4e18 100644 --- a/build.zig +++ b/build.zig @@ -6,6 +6,7 @@ pub fn build(b: *std.Build) !void { const WARM_RootPath = b.dependency("WAMR", .{}).path(""); const WARM_IncludePath = b.pathJoin(&.{ WARM_RootPath.getPath(b), "core/iwasm/include" }); + const WARM_corePath = b.pathJoin(&.{ WARM_RootPath.getPath(b), "core" }); // TODO: https://github.com/ziglang/zig/issues/20630 const wasmC_bindgen = b.addTranslateC(.{ @@ -35,6 +36,46 @@ pub fn build(b: *std.Build) !void { }, .use_clang = true, // TODO: set 'false' use fno-llvm/fno-clang }); + const bh_reader_bindgen = b.addTranslateC(.{ + .link_libc = true, + .optimize = optimize, + .target = target, + .root_source_file = .{ + // get absolute path of core/iwasm/include/wasm_export.h + .cwd_relative = b.pathJoin(&.{ + WARM_corePath, + "shared", + "utils", + "uncommon", + "bh_read_file.c", + }), + }, + .use_clang = true, // TODO: set 'false' use fno-llvm/fno-clang + }); + bh_reader_bindgen.addIncludeDir( + b.pathJoin(&.{ + WARM_corePath, + "shared", + "utils", + "uncommon", + }), + ); + bh_reader_bindgen.addIncludeDir( + b.pathJoin(&.{ + WARM_corePath, + "shared", + "utils", + }), + ); + bh_reader_bindgen.addIncludeDir( + b.pathJoin(&.{ + WARM_corePath, + "shared", + "platform", + @tagName(target.result.os.tag), + }), + ); + const vmlib = try buildCMake(b, WARM_RootPath); wasmExport_bindgen.step.dependOn(&vmlib.step); @@ -46,13 +87,18 @@ pub fn build(b: *std.Build) !void { }); wamr_module.addImport("wasm_export", wasmExport_bindgen.addModule("wasm_export")); wamr_module.addImport("wasm_c_api", wasmC_bindgen.addModule("wasm_c_api")); + wamr_module.addImport("bh_read_file", bh_reader_bindgen.addModule("bh_read_file")); wamr_module.addLibraryPath(b.path(".zig-cache")); - - if (target.query.isNative()) { - wamr_module.linkSystemLibrary("iwasm", .{}); - } else { - wamr_module.linkSystemLibrary("vmlib", .{ .use_pkg_config = .no }); - } + wamr_module.linkSystemLibrary("vmlib", .{ + .use_pkg_config = .no, + }); + // if (target.query.isNative()) { + // wamr_module.linkSystemLibrary("iwasm", .{}); + // } else { + // for (llvm_libs) |lib| { + // wamr_module.linkSystemLibrary(lib, .{}); + // } + // } buildTest(b, wamr_module); } @@ -64,12 +110,12 @@ fn buildCMake(b: *std.Build, dependency: std.Build.LazyPath) !*std.Build.Step.Ru cmake_config.addPrefixedDirectoryArg("-S", dependency); cmake_config.addPrefixedDirectoryArg("-B", b.path(".zig-cache")); - const cpu_count = b.fmt("{}", .{if (!@import("builtin").single_threaded) try std.Thread.getCpuCount() / 2 else 1}); + const cpu_count = b.fmt("{}", .{std.Thread.getCpuCount() catch 1}); const cmake_build = b.addSystemCommand(&.{ cmake_app, "--build", - b.cache_root.path.?, + ".zig-cache", "--parallel", cpu_count, }); @@ -90,3 +136,71 @@ fn buildTest(b: *std.Build, module: *std.Build.Module) void { const test_step = b.step("test", "Run unit tests"); test_step.dependOn(&run_lib_unit_tests.step); } + +const llvm_libs = [_][]const u8{ + "LLVMAggressiveInstCombine", + "LLVMAnalysis", + "LLVMAsmParser", + "LLVMAsmPrinter", + "LLVMBitReader", + "LLVMBitWriter", + "LLVMCFGuard", + "LLVMCodeGen", + "LLVMCoroutines", + "LLVMCoverage", + "LLVMDWARFLinker", + "LLVMDWP", + "LLVMDebugInfoCodeView", + "LLVMDebugInfoDWARF", + "LLVMDebugInfoGSYM", + "LLVMDebugInfoMSF", + "LLVMDebugInfoPDB", + "LLVMDlltoolDriver", + "LLVMExecutionEngine", + "LLVMExtensions", + "LLVMFileCheck", + "LLVMFrontendOpenACC", + "LLVMFrontendOpenMP", + "LLVMFuzzMutate", + "LLVMGlobalISel", + "LLVMIRReader", + "LLVMInstCombine", + "LLVMInstrumentation", + "LLVMInterfaceStub", + "LLVMInterpreter", + "LLVMJITLink", + "LLVMLTO", + "LLVMLibDriver", + "LLVMLineEditor", + "LLVMLinker", + "LLVMMC", + "LLVMMCA", + "LLVMMCDisassembler", + "LLVMMCJIT", + "LLVMMCParser", + "LLVMMIRParser", + "LLVMObjCARCOpts", + "LLVMObject", + "LLVMObjectYAML", + "LLVMOption", + "LLVMOrcJIT", + "LLVMOrcShared", + "LLVMOrcTargetProcess", + "LLVMPasses", + "LLVMProfileData", + "LLVMRuntimeDyld", + "LLVMScalarOpts", + "LLVMSelectionDAG", + "LLVMSymbolize", + "LLVMTarget", + "LLVMTextAPI", + "LLVMTransformUtils", + "LLVMVectorize", + "LLVMX86AsmParser", + "LLVMX86CodeGen", + "LLVMX86Desc", + "LLVMX86Disassembler", + "LLVMX86Info", + "LLVMXRay", + "LLVMipo", +}; diff --git a/src/bindings.zig b/src/bindings.zig index ec72416..f177c98 100644 --- a/src/bindings.zig +++ b/src/bindings.zig @@ -2,4 +2,5 @@ pub const c = struct { pub const wasm_export = @import("wasm_export"); pub const wasm_c = @import("wasm_c_api"); + pub const bh_platform = @import("bh_read_file"); }; diff --git a/tests/build.zig b/tests/build.zig new file mode 100644 index 0000000..310b619 --- /dev/null +++ b/tests/build.zig @@ -0,0 +1,17 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const wamr_zig = b.dependency("wamr_zig", .{}); + + const exe = b.addExecutable(.{ + .name = "hello", + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }); + exe.root_module.addImport("wamr", wamr_zig.module("wamr")); + b.installArtifact(exe); +} diff --git a/tests/build.zig.zon b/tests/build.zig.zon new file mode 100644 index 0000000..41870cc --- /dev/null +++ b/tests/build.zig.zon @@ -0,0 +1,10 @@ +.{ + .name = "tests", + .version = "0.0.0", + .dependencies = .{ + .wamr_zig = .{ + .path = "..", + }, + }, + .paths = .{""}, +} diff --git a/tests/src/main.zig b/tests/src/main.zig new file mode 100644 index 0000000..1efc49d --- /dev/null +++ b/tests/src/main.zig @@ -0,0 +1,178 @@ +const std = @import("std"); +const wamr = @import("wamr").c; +const c = wamr.wasm_export; +// const c_bh = wamr.bh_platform; + +pub const WasmRuntime = struct { + module: c.wasm_module_t, + module_inst: c.wasm_module_inst_t, + exec_env: c.wasm_exec_env_t, + buffer: []u8, + allocator: std.mem.Allocator, + + pub fn init(allocator: std.mem.Allocator, wasm_path: []const u8, wasi_dir: []const u8) !*WasmRuntime { + var self = try allocator.create(WasmRuntime); + errdefer allocator.destroy(self); + + self.* = WasmRuntime{ + .module = null, + .module_inst = null, + .exec_env = null, + .buffer = undefined, + .allocator = allocator, + }; + + var init_args: c.RuntimeInitArgs = std.mem.zeroes(c.RuntimeInitArgs); + init_args.mem_alloc_type = c.Alloc_With_Pool; + init_args.mem_alloc_option.pool.heap_buf = &global_heap_buf; + init_args.mem_alloc_option.pool.heap_size = global_heap_buf.len; + + if (!c.wasm_runtime_full_init(&init_args)) { + return error.RuntimeInitFailed; + } + + // var buf_size: u32 = undefined; + // self.buffer = c_bh.bh_read_file_to_buffer(wasm_path.ptr, &buf_size); + // if (self.buffer == null) { + // return error.FileReadFailed; + // } + // buf_size = @intCast(std.mem.len(self.buffer)); + + std.debug.print("Attempting to open file: {s}\n", .{wasm_path}); + + // Read file using Zig's std lib + const file = std.fs.cwd().openFile(wasm_path, .{}) catch |err| { + std.log.err("Failed to open file: {s}, error: {}\n", .{ wasm_path, err }); + return error.FileOpenFailed; + }; + defer file.close(); + + const file_size = file.getEndPos() catch |err| { + std.log.err("Failed to get file size, error: {}\n", .{err}); + return error.FileReadFailed; + }; + + std.debug.print("File size: {}\n", .{file_size}); + + if (file_size == 0) { + std.log.err("File is empty\n", .{}); + return error.EmptyFile; + } + + self.buffer = allocator.alloc(u8, file_size) catch |err| { + std.log.err("Failed to allocate buffer, error: {}\n", .{err}); + return error.MemoryAllocationFailed; + }; + errdefer allocator.free(self.buffer); + + const bytes_read = file.readAll(self.buffer) catch |err| { + std.log.err("Failed to read file, error: {}\n", .{err}); + return error.FileReadFailed; + }; + + std.debug.print("Bytes read: {}\n", .{bytes_read}); + + if (bytes_read != file_size) { + std.log.err("Incomplete file read: {} of {} bytes\n", .{ bytes_read, file_size }); + return error.FileReadIncomplete; + } + + std.debug.print("Attempting to load WASM module. Buffer size: {}\n", .{self.buffer.len}); + + const buf_size: u32 = @min(self.buffer.len, 1024 * 1024); //@intCast(self.buffer.len); + if (!validateWasmFile(self.buffer)) { + std.log.err("Invalid WASM file format\n", .{}); + return error.InvalidWasmFormat; + } + + var error_buf: [128]u8 = std.mem.zeroes([128]u8); + self.module = c.wasm_runtime_load(self.buffer.ptr, buf_size, &error_buf, error_buf.len); + if (self.module == null) { + std.debug.print("Buffer size: {}\n", .{self.buffer.len}); + std.log.err("Failed to load WASM module. Error: {s}\n", .{error_buf}); + std.debug.print("First 16 bytes of WASM file: ", .{}); + for (self.buffer[0..@min(16, buf_size)]) |byte| { + std.debug.print("{x:0>2} ", .{byte}); + } + std.debug.print("\n", .{}); + return error.ModuleLoadFailed; + } + + const wasi_dir_ptr: [*c][*c]const u8 = @ptrCast(@alignCast(@constCast(wasi_dir.ptr))); + _ = c.wasm_runtime_set_wasi_args_ex(self.module, wasi_dir_ptr, 1, null, 0, null, 0, null, 0, 0, 1, 2); + + const stack_size: u32 = 8092; + const heap_size: u32 = 8092; + + self.module_inst = c.wasm_runtime_instantiate(self.module, stack_size, heap_size, &error_buf, error_buf.len); + if (self.module_inst == null) { + return error.ModuleInstantiateFailed; + } + + self.exec_env = c.wasm_runtime_create_exec_env(self.module_inst, stack_size); + if (self.exec_env == null) { + return error.ExecEnvCreateFailed; + } + + return self; + } + + pub fn deinit(self: *WasmRuntime) void { + if (self.exec_env) |env| c.wasm_runtime_destroy_exec_env(env); + if (self.module_inst) |inst| c.wasm_runtime_deinstantiate(inst); + if (self.module) |mod| c.wasm_runtime_unload(mod); + // if (self.buffer) |buf| std.c.free(@ptrCast(@constCast(&buf))); + self.allocator.free(self.buffer); + c.wasm_runtime_destroy(); + self.allocator.destroy(self); + } + + pub fn executeMain(self: *WasmRuntime) !u32 { + if (c.wasm_application_execute_main(self.module_inst, 0, null)) { + return c.wasm_runtime_get_wasi_exit_code(self.module_inst); + } else { + const exception = c.wasm_runtime_get_exception(self.module_inst); + std.log.err("WASM execution failed. Exception: {s}\n", .{exception}); + return error.MainExecutionFailed; + } + } + var global_heap_buf: [512 * 1024]u8 = undefined; +}; + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{ + .verbose_log = (@import("builtin").mode == .Debug), + }){}; + defer { + if (gpa.detectLeaks()) { + std.log.err("Memory leak detected!\n", .{}); + } + } + const allocator = gpa.allocator(); + const args = try std.process.argsAlloc(allocator); + defer std.process.argsFree(allocator, args); + + if (args.len != 3) { + std.debug.print("Usage: {s} \n", .{args[0]}); + return; + } + + var runtime = WasmRuntime.init(allocator, args[1], args[2]) catch |err| { + std.log.err("Failed to initialize WasmRuntime: {}\n", .{err}); + return; + }; + defer runtime.deinit(); + + const result = runtime.executeMain() catch |err| { + std.log.err("Failed to execute WASM main: {}\n", .{err}); + return; + }; + std.debug.print("WASM application exited with code: {}\n", .{result}); +} + +fn validateWasmFile(buffer: []const u8) bool { + if (buffer.len < 8) return false; + const magic_number = [_]u8{ 0x00, 0x61, 0x73, 0x6d }; + const version = [_]u8{ 0x01, 0x00, 0x00, 0x00 }; + return std.mem.eql(u8, buffer[0..4], &magic_number) and std.mem.eql(u8, buffer[4..8], &version); +} diff --git a/tests/wasmfiles/gcd.zig b/tests/wasmfiles/gcd.zig new file mode 100644 index 0000000..be3bc6e --- /dev/null +++ b/tests/wasmfiles/gcd.zig @@ -0,0 +1,9 @@ +const std = @import("std"); + +export fn gcd(a: u32, b: u32) callconv(.C) u32 { + return if (b != 0) gcd(b, @mod(a, b)) else a; +} + +pub fn main() void { + _ = std.c.printf("Hello, world! Please call gcd(10, 5) to see the result.\n"); +} diff --git a/tests/wasmfiles/gcd_wasm32_wasi.wasm b/tests/wasmfiles/gcd_wasm32_wasi.wasm new file mode 100644 index 0000000..583d166 Binary files /dev/null and b/tests/wasmfiles/gcd_wasm32_wasi.wasm differ