From 1d430e81f77543ebb556bf1cbca033748388c997 Mon Sep 17 00:00:00 2001 From: Louis Pearson Date: Fri, 14 Jan 2022 19:33:23 -0700 Subject: [PATCH] Initial commit --- .gitignore | 2 + build.zig | 69 ++++++++++++++++++++++++++ src/main.zig | 25 ++++++++++ src/wasm4.zig | 133 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 229 insertions(+) create mode 100644 .gitignore create mode 100644 build.zig create mode 100644 src/main.zig create mode 100644 src/wasm4.zig diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..93c1b5f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/zig-cache +/zig-out diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..d415388 --- /dev/null +++ b/build.zig @@ -0,0 +1,69 @@ +const std = @import("std"); + +// Returns true if the version includes https://github.com/ziglang/zig/pull/10572/commits. +// When this is false, trying to place the stack first will result in data corruption. +fn version_supports_stack_first(zig_version: std.SemanticVersion) !bool { + if (zig_version.order(try std.SemanticVersion.parse("0.10.0")).compare(.gte)) { + // Merged here: https://github.com/ziglang/zig/pull/10572 + return true; + } + if (zig_version.major == 0 and zig_version.minor == 10) { + // Check for 0.10.0-dev.258+. Conservatively check the prefix of the tag + // in case zig uses other prefixes that don't respect semver ordering. + if (zig_version.pre) |pre| { + // Merged here: https://github.com/ziglang/zig/pull/10572 + return std.mem.startsWith(u8, pre, "dev.") and zig_version.order(try std.SemanticVersion.parse("0.10.0-dev.258")).compare(.gte); + } + } + // Backported here: https://github.com/ziglang/zig/commit/6f49233ac6a6569b909b689f22fc260dc8c19234 + return zig_version.order(try std.SemanticVersion.parse("0.9.1")).compare(.gte); +} + +test "stack version check" { + const expect = std.testing.expect; + const parse = std.SemanticVersion.parse; + try expect(!try version_supports_stack_first(try parse("0.8.0"))); + + try expect(!try version_supports_stack_first(try parse("0.9.0"))); + try expect(!try version_supports_stack_first(try parse("0.9.1-dev.259"))); + try expect(try version_supports_stack_first(try parse("0.9.1"))); + + // Conservatively don't recognize tags other than 'dev'. + try expect(!try version_supports_stack_first(try parse("0.10.0-aev.259"))); + try expect(!try version_supports_stack_first(try parse("0.10.0-zev.259"))); + + try expect(!try version_supports_stack_first(try parse("0.10.0-dev.257"))); + try expect(try version_supports_stack_first(try parse("0.10.0-dev.258"))); + try expect(try version_supports_stack_first(try parse("0.10.0-dev.259"))); + try expect(try version_supports_stack_first(try parse("0.10.0"))); + + try expect(try version_supports_stack_first(try parse("0.10.1-dev.100"))); + try expect(try version_supports_stack_first(try parse("0.10.1-dev.300"))); + try expect(try version_supports_stack_first(try parse("0.10.1"))); + + try expect(try version_supports_stack_first(try parse("1.0.0"))); +} + +pub fn build(b: *std.build.Builder) !void { + const zig_version = @import("builtin").zig_version; + const mode = b.standardReleaseOptions(); + const lib = b.addSharedLibrary("cart", "src/main.zig", .unversioned); + lib.setBuildMode(mode); + lib.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }); + lib.import_memory = true; + lib.initial_memory = 65536; + lib.max_memory = 65536; + if (try version_supports_stack_first(zig_version)) { + lib.stack_size = 14752; + } else { + // `--stack-first` option have been reenabled on wasm targets with https://github.com/ziglang/zig/pull/10572 + std.log.warn("Update to Zig >=0.9.1 (or >=0.10.0-dev.258 for nightly) to detect stack overflows at runtime.", .{}); + lib.global_base = 6560; + lib.stack_size = 8192; + } + // Workaround https://github.com/ziglang/zig/issues/2910, preventing + // functions from compiler_rt getting incorrectly marked as exported, which + // prevents them from being removed even if unused. + lib.export_symbol_names = &[_][]const u8{ "start", "update" }; + lib.install(); +} diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..15628b4 --- /dev/null +++ b/src/main.zig @@ -0,0 +1,25 @@ +const w4 = @import("wasm4.zig"); + +const smiley = [8]u8{ + 0b11000011, + 0b10000001, + 0b00100100, + 0b00100100, + 0b00000000, + 0b00100100, + 0b10011001, + 0b11000011, +}; + +export fn update() void { + w4.DRAW_COLORS.* = 2; + w4.text("Hello from Zig!", 10, 10); + + const gamepad = w4.GAMEPAD1.*; + if (gamepad & w4.BUTTON_1 != 0) { + w4.DRAW_COLORS.* = 4; + } + + w4.blit(&smiley, 76, 76, 8, 8, w4.BLIT_1BPP); + w4.text("Press X to blink", 16, 90); +} diff --git a/src/wasm4.zig b/src/wasm4.zig new file mode 100644 index 0000000..322dc92 --- /dev/null +++ b/src/wasm4.zig @@ -0,0 +1,133 @@ +// +// WASM-4: https://wasm4.org/docs + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Platform Constants │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +pub const CANVAS_SIZE: u32 = 160; + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Memory Addresses │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +pub const PALETTE: *[4]u32 = @intToPtr(*[4]u32, 0x04); +pub const DRAW_COLORS: *u16 = @intToPtr(*u16, 0x14); +pub const GAMEPAD1: *const u8 = @intToPtr(*const u8, 0x16); +pub const GAMEPAD2: *const u8 = @intToPtr(*const u8, 0x17); +pub const GAMEPAD3: *const u8 = @intToPtr(*const u8, 0x18); +pub const GAMEPAD4: *const u8 = @intToPtr(*const u8, 0x19); +pub const MOUSE_X: *const i16 = @intToPtr(*const i16, 0x1a); +pub const MOUSE_Y: *const i16 = @intToPtr(*const i16, 0x1c); +pub const MOUSE_BUTTONS: *const u8 = @intToPtr(*const u8, 0x1e); +pub const SYSTEM_FLAGS: *u8 = @intToPtr(*u8, 0x1f); +pub const FRAMEBUFFER: *[6400]u8 = @intToPtr(*[6400]u8, 0xA0); + +pub const BUTTON_1: u8 = 1; +pub const BUTTON_2: u8 = 2; +pub const BUTTON_LEFT: u8 = 16; +pub const BUTTON_RIGHT: u8 = 32; +pub const BUTTON_UP: u8 = 64; +pub const BUTTON_DOWN: u8 = 128; + +pub const MOUSE_LEFT: u8 = 1; +pub const MOUSE_RIGHT: u8 = 2; +pub const MOUSE_MIDDLE: u8 = 4; + +pub const SYSTEM_PRESERVE_FRAMEBUFFER: u8 = 1; +pub const SYSTEM_HIDE_GAMEPAD_OVERLAY: u8 = 2; + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Drawing Functions │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +/// Copies pixels to the framebuffer. +pub extern fn blit(sprite: [*]const u8, x: i32, y: i32, width: i32, height: i32, flags: u32) void; + +/// Copies a subregion within a larger sprite atlas to the framebuffer. +pub extern fn blitSub(sprite: [*]const u8, x: i32, y: i32, width: i32, height: i32, src_x: u32, src_y: u32, stride: i32, flags: u32) void; + +pub const BLIT_2BPP: u32 = 1; +pub const BLIT_1BPP: u32 = 0; +pub const BLIT_FLIP_X: u32 = 2; +pub const BLIT_FLIP_Y: u32 = 4; +pub const BLIT_ROTATE: u32 = 8; + +/// Draws a line between two points. +pub extern fn line(x1: i32, y1: i32, x2: i32, y2: i32) void; + +/// Draws an oval (or circle). +pub extern fn oval(x: i32, y: i32, width: i32, height: i32) void; + +/// Draws a rectangle. +pub extern fn rect(x: i32, y: i32, width: u32, height: u32) void; + +/// Draws text using the built-in system font. +pub fn text(str: []const u8, x: i32, y: i32) void { + textUtf8(str.ptr, str.len, x, y); +} +extern fn textUtf8(strPtr: [*]const u8, strLen: usize, x: i32, y: i32) void; + +/// Draws a vertical line +pub extern fn vline(x: i32, y: i32, len: u32) void; + +/// Draws a horizontal line +pub extern fn hline(x: i32, y: i32, len: u32) void; + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Sound Functions │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +/// Plays a sound tone. +pub extern fn tone(frequency: u32, duration: u32, volume: u32, flags: u32) void; + +pub const TONE_PULSE1: u32 = 0; +pub const TONE_PULSE2: u32 = 1; +pub const TONE_TRIANGLE: u32 = 2; +pub const TONE_NOISE: u32 = 3; +pub const TONE_MODE1: u32 = 0; +pub const TONE_MODE2: u32 = 4; +pub const TONE_MODE3: u32 = 8; +pub const TONE_MODE4: u32 = 12; + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Storage Functions │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +/// Reads up to `size` bytes from persistent storage into the pointer `dest`. +pub extern fn diskr(dest: [*]u8, size: u32) u32; + +/// Writes up to `size` bytes from the pointer `src` into persistent storage. +pub extern fn diskw(src: [*]const u8, size: u32) u32; + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Other Functions │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +/// Prints a message to the debug console. +pub fn trace(x: []const u8) void { + traceUtf8(x.ptr, x.len); +} +extern fn traceUtf8(strPtr: [*]const u8, strLen: usize) void; + +/// Use with caution, as there's no compile-time type checking. +/// +/// * %c, %d, and %x expect 32-bit integers. +/// * %f expects 64-bit floats. +/// * %s expects a *zero-terminated* string pointer. +/// +/// See https://github.com/aduros/wasm4/issues/244 for discussion and type-safe +/// alternatives. +pub extern fn tracef(x: [*:0]const u8, ...) void;