Skip to content

Update EMSDK and add Emmaloc #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,24 @@ Add zemscripten's "root" module to your wasm compile target., then create an `em
b.getInstallStep().dependOn(emcc_step);
```

To use a custom html file emccStep() accepts a shell_file_path option:
```zig
const emcc_step = @import("zemscripten").emccStep(
b,
wasm,
.{
.optimize = optimize,
.flags = emcc_flags,
.settings = emcc_settings,
.use_preload_plugins = true,
.embed_paths = &.{},
.preload_paths = &.{},
.install_dir = .{ .custom = "web" },
.shell_file_path = "path/to/file"
},
);
```

Now you can use the provided Zig panic and log overrides in your wasm's root module and define the entry point that invoked by the js output of `emcc` (by default it looks for a symbol named `main`). For example:
```zig
const std = @import("std");
Expand Down Expand Up @@ -85,3 +103,4 @@ You can also define a run step that invokes `emrun`. This will serve the html lo
b.step("emrun", "Build and open the web app locally using emrun").dependOn(emrun_step);
```
See the [emrun documentation](https://emscripten.org/docs/compiling/Running-html-files-with-emrun.html) for the difference args that can be used.

10 changes: 9 additions & 1 deletion build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ const std = @import("std");

pub const emsdk_ver_major = "3";
pub const emsdk_ver_minor = "1";
pub const emsdk_ver_tiny = "52";
pub const emsdk_ver_tiny = "73";
pub const emsdk_version = emsdk_ver_major ++ "." ++ emsdk_ver_minor ++ "." ++ emsdk_ver_tiny;

pub fn build(b: *std.Build) void {
_ = b.addModule("root", .{ .root_source_file = b.path("src/zemscripten.zig") });
_ = b.addModule("dummy", .{ .root_source_file = b.path("src/dummy.zig") });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is dummy.zig for?

Copy link
Contributor Author

@ckrowland ckrowland Feb 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you run natively, @import("zemscripten") imports the dummy.zig instead of zemscripten.zig, so you can run both natively and emscripten in the same program. There might be a better way to do this. We could make it so that importing zemscripten means only a emscripten build could run. I forgot to add to the README, when you build like this it would look like

const zemscripten = b.dependency("zemscripten", .{});
if (target.result.os.tag != .emscripten) {
    exe.root_module.addImport("zemscripten", zemscripten.module("dummy"));
} else {
    exe.root_module.addImport("zemscripten", zemscripten.module("root"));
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multiple main files is the strategy I use in personal projects. I also intend to refactor the zig-gamedev samples to be like sdl2_demo (https://github.com/zig-gamedev/zig-gamedev/tree/main/samples/sdl2_demo/src).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, yeah I do the same thing. So safe to remove dummy.zig?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think so. We could put it back if it is useful to someone later.

}

pub fn emccPath(b: *std.Build) []const u8 {
Expand All @@ -32,6 +33,13 @@ pub fn emrunPath(b: *std.Build) []const u8 {
}) catch unreachable;
}

pub fn htmlPath(b: *std.Build) []const u8 {
return std.fs.path.join(b.allocator, &.{
b.dependency("emsdk", .{}).path("").getPath(b),
"upstream/emscripten/src/shell.html",
}) catch unreachable;
}

pub fn activateEmsdkStep(b: *std.Build) *std.Build.Step {
const emsdk_script_path = std.fs.path.join(b.allocator, &.{
b.dependency("emsdk", .{}).path("").getPath(b),
Expand Down
1 change: 1 addition & 0 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"build.zig",
"build.zig.zon",
"src",
"content",
"LICENSE",
"README.md",
},
Expand Down
Empty file added src/dummy.zig
Empty file.
122 changes: 118 additions & 4 deletions src/zemscripten.zig
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@ comptime {
_ = std.testing.refAllDeclsRecursive(@This());
}

extern fn emscripten_err([*c]const u8) void;
extern fn emscripten_console_error([*c]const u8) void;
extern fn emscripten_console_warn([*c]const u8) void;
extern fn emscripten_console_log([*c]const u8) void;
pub extern fn emscripten_sleep(ms: u32) void;

pub const MainLoopCallback = *const fn () callconv(.C) void;
extern fn emscripten_set_main_loop(MainLoopCallback, c_int, c_int) void;
Expand All @@ -21,6 +18,123 @@ pub const AnimationFrameCallback = *const fn (f64, ?*anyopaque) callconv(.C) c_i
extern fn emscripten_request_animation_frame_loop(AnimationFrameCallback, ?*anyopaque) void;
pub const requestAnimationFrameLoop = emscripten_request_animation_frame_loop;

pub const EmscriptenResult = enum(i16) {
success = 0,
deferred = 1,
not_supported = -1,
failed_not_deferred = -2,
invalid_target = -3,
unknown_target = -4,
invalid_param = -5,
failed = -6,
no_data = -7,
timed_out = -8,
};
pub const CanvasSizeChangedCallback = *const fn (
i16,
*anyopaque,
?*anyopaque,
) callconv(.C) c_int;
pub fn setResizeCallback(
cb: CanvasSizeChangedCallback,
use_capture: bool,
user_data: ?*anyopaque,
) EmscriptenResult {
const result = emscripten_set_resize_callback_on_thread(
"2",
user_data,
@intFromBool(use_capture),
cb,
2,
);
return @enumFromInt(result);
}
extern fn emscripten_set_resize_callback_on_thread(
[*:0]const u8,
?*anyopaque,
c_int,
CanvasSizeChangedCallback,
c_int,
) c_int;

pub fn getElementCssSize(
target_id: [:0]const u8,
width: *f64,
height: *f64,
) EmscriptenResult {
return @enumFromInt(emscripten_get_element_css_size(
target_id,
width,
height,
));
}
extern fn emscripten_get_element_css_size([*:0]const u8, *f64, *f64) c_int;

// EmmalocAllocator allocator
// use with linker flag -sMALLOC=emmalloc
// for details see docs: https://github.com/emscripten-core/emscripten/blob/main/system/lib/emmalloc.c
extern fn emmalloc_memalign(u32, u32) ?*anyopaque;
extern fn emmalloc_realloc_try(?*anyopaque, u32) ?*anyopaque;
extern fn emmalloc_free(?*anyopaque) void;
pub const EmmalocAllocator = struct {
const Self = @This();
dummy: u32 = undefined,

pub fn allocator(self: *Self) std.mem.Allocator {
return .{
.ptr = self,
.vtable = &.{
.alloc = &alloc,
.resize = &resize,
.free = &free,
},
};
}

fn alloc(
ctx: *anyopaque,
len: usize,
ptr_align_log2: u8,
return_address: usize,
) ?[*]u8 {
_ = ctx;
_ = return_address;
const ptr_align: u32 = @as(u32, 1) << @as(u5, @intCast(ptr_align_log2));
if (!std.math.isPowerOfTwo(ptr_align)) unreachable;
const ptr = emmalloc_memalign(ptr_align, len) orelse return null;
return @ptrCast(ptr);
}

fn resize(
ctx: *anyopaque,
buf: []u8,
buf_align_log2: u8,
new_len: usize,
return_address: usize,
) bool {
_ = ctx;
_ = return_address;
_ = buf_align_log2;
return emmalloc_realloc_try(buf.ptr, new_len) != null;
}

fn free(
ctx: *anyopaque,
buf: []u8,
buf_align_log2: u8,
return_address: usize,
) void {
_ = ctx;
_ = buf_align_log2;
_ = return_address;
return emmalloc_free(buf.ptr);
}
};

extern fn emscripten_err([*c]const u8) void;
extern fn emscripten_console_error([*c]const u8) void;
extern fn emscripten_console_warn([*c]const u8) void;
extern fn emscripten_console_log([*c]const u8) void;
/// std.panic impl
pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, ret_addr: ?usize) noreturn {
_ = error_return_trace;
Expand Down
Loading