Compare commits

..

4 commits

Author SHA1 Message Date
e9ebc4457f . 2026-02-19 23:40:59 -07:00
1075d4fff9 support wasm build commands 2026-02-19 23:40:54 -07:00
f13054ee32 add itty bitty http server for running wasm 2026-02-19 23:40:33 -07:00
22ee3e232c fix blocksruntime wasm build 2026-02-19 23:38:59 -07:00
6 changed files with 227 additions and 31 deletions

2
.gitignore vendored
View file

@ -1,4 +1,6 @@
.zed/ .zed/
out/p2601* out/p2601*
out/*.o
thirdparty/zig* thirdparty/zig*
thirdparty/emsdk
.DS_Store .DS_Store

90
do.ps1
View file

@ -29,6 +29,7 @@ $EXE_NAME = "p2601";
function Get-Ext($platform) { function Get-Ext($platform) {
if ($platform -eq "windows") { return ".exe"; } if ($platform -eq "windows") { return ".exe"; }
if ($platform -eq "emscripten") { return ".o"; }
return ""; return "";
} }
@ -37,9 +38,14 @@ $ZIG_SLUG = "zig-${HOST_ARCH}-${HOST_OS}-${ZIG_VERSION}";
$ZIG_DIR = Join-Path $DEPS_DIR $ZIG_SLUG $ZIG_DIR = Join-Path $DEPS_DIR $ZIG_SLUG
$ZIG = Join-Path $ZIG_DIR "zig$(Get-Ext $HOST_OS)" $ZIG = Join-Path $ZIG_DIR "zig$(Get-Ext $HOST_OS)"
$BLOCKS_RT_DIR = Join-Path $DEPS_DIR "blocksruntime"; $BLOCKS_RT_DIR = Join-Path $DEPS_DIR "blocksruntime";
$BLOCKS_RT_FLAGS = "-I$BLOCKS_RT_DIR -I$(Join-Path $BLOCKS_RT_DIR "BlocksRuntime")"; $BLOCKS_RT_FLAGS = "-I$BLOCKS_RT_DIR -I$(Join-Path $BLOCKS_RT_DIR "BlocksRuntime")";
$BLOCKS_RT_SOURCE = (Join-Path $BLOCKS_RT_DIR "BlocksRuntime" "runtime.c") + " " + (Join-Path $BLOCKS_RT_DIR "BlocksRuntime" "data.c"); $BLOCKS_RT_RUNTIME = (Join-Path $BLOCKS_RT_DIR "BlocksRuntime" "runtime.c");
$BLOCKS_RT_DATA = (Join-Path $BLOCKS_RT_DIR "BlocksRuntime" "data.c");
$BLOCKS_RT_SOURCE = "$BLOCKS_RT_RUNTIME $BLOCKS_RT_DATA";
$EMSDK_REPO = "https://github.com/emscripten-core/emsdk.git";
$EMSDK_DIR = Join-Path $DEPS_DIR "emsdk";
$C_SOURCE = "src/main.c"; $C_SOURCE = "src/main.c";
$C_INCLUDES = "-Isrc -I$DEPS_DIR"; $C_INCLUDES = "-Isrc -I$DEPS_DIR";
@ -49,7 +55,7 @@ $RELEASE_FLAGS = "-Ofast -Wno-everything -ffast-math";
$WINDOWS_FLAGS = "-ld3d11 -lgdi32"; $WINDOWS_FLAGS = "-ld3d11 -lgdi32";
$MACOS_FLAGS = "-x objective-c -lobjc -framework Foundation -framework Cocoa -framework CoreFoundation -framework CoreGraphics -framework QuartzCore -framework Metal -framework MetalKit"; $MACOS_FLAGS = "-x objective-c -lobjc -framework Foundation -framework Cocoa -framework CoreFoundation -framework CoreGraphics -framework QuartzCore -framework Metal -framework MetalKit";
$LINUX_FLAGS = "-lgl"; $LINUX_FLAGS = "-lgl";
$WASM_FLAGS = "-c -fno-sanitize=address -fno-sanitize=undefined -isystem " + (Join-Path $EMSDK_DIR "upstream" "emscripten" "cache" "sysroot" "include");
# try vendoring zig # try vendoring zig
if (-not (Test-Path $ZIG)) { if (-not (Test-Path $ZIG)) {
@ -74,6 +80,21 @@ if (-not (Test-Path $ZIG)) {
} }
} }
# try vendoring emsdk
if (-not (Test-Path $EMSDK_DIR)) {
git clone $EMSDK_REPO $EMSDK_DIR
Set-Location $EMSDK_DIR
if ($IsWindows) {
&"./emsdk.bat" install latest
&"./emsdk.bat" activate latest
} else {
&"./emsdk" install latest
&"./emsdk" activate latest
}
}
# ensure the out directory exists # ensure the out directory exists
New-Item -ItemType Directory -Force -Path $OUT_DIR | Out-Null; New-Item -ItemType Directory -Force -Path $OUT_DIR | Out-Null;
@ -92,6 +113,9 @@ function Get-Flags($os, $release) {
"linux" { "linux" {
$flags += " $LINUX_FLAGS $BLOCKS_RT_FLAGS -DPLATFORM_LINUX"; $flags += " $LINUX_FLAGS $BLOCKS_RT_FLAGS -DPLATFORM_LINUX";
} }
"emscripten" {
$flags += " $WASM_FLAGS $BLOCKS_RT_FLAGS -DPLATFORM_WASM -DPOINTER_32"
}
default { default {
Write-Error "Unsupported operating system '$os'"; Write-Error "Unsupported operating system '$os'";
} }
@ -117,6 +141,9 @@ function Get-Source($os) {
"linux" { "linux" {
$source += " $BLOCKS_RT_SOURCE"; $source += " $BLOCKS_RT_SOURCE";
} }
"emscripten" {
$source += " $BLOCKS_RT_SOURCE"
}
default { default {
Write-Error "Unsupported operating system '$os'"; Write-Error "Unsupported operating system '$os'";
} }
@ -139,7 +166,7 @@ switch ($command) {
Write-Host (":: Compiling natively" + $(if ($release) { " (release mode)" } else { "" })); Write-Host (":: Compiling natively" + $(if ($release) { " (release mode)" } else { "" }));
Write-Host $cmd; Write-Host $cmd;
Invoke-Expression $cmd | Write-Host; Invoke-Expression $cmd | Write-Host;
} }
{($_ -eq "build-cross") -or ($_ -eq "bc")} { {($_ -eq "build-cross") -or ($_ -eq "bc")} {
@ -152,12 +179,39 @@ switch ($command) {
Write-Error "Unsupported target '$target' - run list-targets to see possible targets"; Write-Error "Unsupported target '$target' - run list-targets to see possible targets";
} }
$cmd = "$(CompileCommand $target_os $release) --target=$target -o $(Join-Path $OUT_DIR $($EXE_NAME + (Get-Ext $target_os)))"; $out = Join-Path $OUT_DIR ($EXE_NAME + (Get-Ext $target_os));
$cmd = "$(CompileCommand $target_os $release) --target=$target -o $out";
Write-Host (":: Cross-compiling for $target" + $(if ($release) { " (release mode)" } else { "" })); Write-Host (":: Cross-compiling for $target" + $(if ($release) { " (release mode)" } else { "" }));
Write-Host $cmd; Write-Host $cmd;
Invoke-Expression $cmd | Write-Host; Invoke-Expression $cmd | Write-Host;
if ($target_os -eq "emscripten") {
if (-not $IsWindows) {
Set-Alias "python" "python3";
}
[Environment]::SetEnvironmentVariable("EMSDK_QUIET", 1);
& (Join-Path $EMSDK_DIR "emsdk_env.ps1");
$runtime_o = (Join-Path $OUT_DIR "p2601-blocks-runtime.o");
$cmd = "$ZIG cc $WASM_FLAGS $BLOCKS_RT_FLAGS --target=$target -c $BLOCKS_RT_RUNTIME -o $runtime_o";
Write-Host $cmd;
Invoke-Expression $cmd | Write-Host;
$data_o = (Join-Path $OUT_DIR "p2601-blocks-data.o");
$cmd = "$ZIG cc $WASM_FLAGS $BLOCKS_RT_FLAGS --target=$target -c $BLOCKS_RT_DATA -o $data_O";
Write-Host $cmd;
Invoke-Expression $cmd | Write-Host;
$binary = Join-Path $OUT_DIR ($EXE_NAME + (Get-Ext $HOST_OS));
$cmd = "emcc $out $runtime_o $data_o -sUSE_WEBGL2=1 -sFULL_ES3=1 -sALLOW_MEMORY_GROWTH -o " + (Join-Path $OUT_DIR ($EXE_NAME + ".html"));
Write-Host $cmd;
Invoke-Expression $cmd | Write-Host;
}
} }
{($_ -eq "run") -or ($_ -eq "r")} { {($_ -eq "run") -or ($_ -eq "r")} {
$release = $rest[0] -eq "release" -or $rest[0] -eq "fast"; $release = $rest[0] -eq "release" -or $rest[0] -eq "fast";
@ -166,26 +220,40 @@ switch ($command) {
Write-Host (":: Compiling natively" + $(if ($release) { " (release mode)" } else { "" })); Write-Host (":: Compiling natively" + $(if ($release) { " (release mode)" } else { "" }));
Write-Host $cmd; Write-Host $cmd;
Invoke-Expression $cmd | Write-Host; Invoke-Expression $cmd | Write-Host;
if ($LASTEXITCODE -eq 0) { if ($LASTEXITCODE -eq 0) {
Invoke-Expression $binary | Write-Host; Invoke-Expression $binary | Write-Host;
} }
} }
"run-wasm" {
if (-not (Test-Path (Join-Path $OUT_DIR "p2601.wasm"))) {
&"./do.ps1" build-cross wasm32-emscripten;
}
$cmd = "$ZIG run " + (Join-Path $DEPS_DIR "tinyhttp.zig");
Write-Host $cmd;
Invoke-Expression $cmd | Write-Host;
}
{($_ -eq "clean") -or ($_ -eq "c")} {
Write-Host ":: Cleaning up build artifacts...";
Remove-Item -Path (Join-Path $OUT_DIR ($EXE_NAME + "*")) -Force;
}
{($_ -eq "list-targets") -or ($_ -eq "lt")} { {($_ -eq "list-targets") -or ($_ -eq "lt")} {
# zig target doesn't actually output json... # zig target doesn't actually output json...
Write-Host ":: Available cross-compilation targets"; Write-Host ":: Available cross-compilation targets";
Write-Host " x86_64-windows-gnu"; Write-Host " x86_64-windows-gnu";
Write-Host " x86_64-linux-gnu"; Write-Host " x86_64-linux-gnu";
Write-Host " wasm32-wasi"; Write-Host " wasm32-emscripten";
Write-Host " wasm32-freestanding";
} }
default { default {
Write-Host ":: Available things to do"; Write-Host ":: Available things to do";
Write-Host " build (b) compiles the program natively"; Write-Host " build (b) compiles the program natively";
Write-Host " build-cross (bc) <target> cross-compiles the program for the target triple (ex. x86_64-windows-gnu)"; Write-Host " build-cross (bc) <target> cross-compiles the program for the target triple (ex. x86_64-windows-gnu)";
Write-Host " run (r) compiles and runs the native program"; Write-Host " run (r) compiles and runs the native program";
Write-Host " run-wasm compiles and runs the wasm program";
Write-Host " clean (c) cleans up all build artifacts";
Write-Host " list-targets (lt) lists all cross-compliation targets"; Write-Host " list-targets (lt) lists all cross-compliation targets";
return; return;
} }

View file

@ -13,7 +13,7 @@ typedef closure(void*, arena__action, uword, uword, void*, const char*, sword) A
#define $(a1, a2, a3, a4, a5, a6) ^void* (arena__action a1, uword a2, uword a3, void* a4, const char* a5, sword a6) #define $(a1, a2, a3, a4, a5, a6) ^void* (arena__action a1, uword a2, uword a3, void* a4, const char* a5, sword a6)
static Arena static Arena
Static(uint8* data, uword count) { Static(u8* data, uword count) {
memset(data, 0, count); memset(data, 0, count);
capture uword offset = 0; capture uword offset = 0;

View file

@ -10,7 +10,7 @@
#elifdef PLATFORM_LINUX #elifdef PLATFORM_LINUX
#define SOKOL_GLES3 #define SOKOL_GLES3
#elifdef PLATFORM_WASM #elifdef PLATFORM_WASM
#define SOKOL_GLCORE #define SOKOL_GLES3
#else #else
#error "unsupported platform" #error "unsupported platform"
#endif #endif
@ -145,8 +145,6 @@ event(const sapp_event* event) {
sapp_desc sapp_desc
sokol_main(int argc, char* argv[]) { sokol_main(int argc, char* argv[]) {
(void)argc;
(void)argv;
return (sapp_desc) { return (sapp_desc) {
.init_cb = init, .init_cb = init,
.frame_cb = frame, .frame_cb = frame,
@ -154,8 +152,8 @@ sokol_main(int argc, char* argv[]) {
.event_cb = event, .event_cb = event,
.logger.func = slog_func, .logger.func = slog_func,
.width = 400, .width = 1280,
.height = 300, .height = 720,
.window_title = "hello, world", .window_title = "hello, world",
}; };
} }

View file

@ -8,10 +8,10 @@
* distribute, sublicense, and/or sell copies of the Software, and to permit * distribute, sublicense, and/or sell copies of the Software, and to permit
* persons to whom the Software is furnished to do so, subject to the following * persons to whom the Software is furnished to do so, subject to the following
* conditions: * conditions:
* *
* The above copyright notice and this permission notice shall be included in * The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software. * all copies or substantial portions of the Software.
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -23,10 +23,23 @@
*/ */
#include "Block_private.h" #include "Block_private.h"
#include <stdio.h>
#include <stdlib.h> #ifdef PLATFORM_WASM
#include <string.h> void* malloc(unsigned long s);
void free(void* ptr);
int printf_(const char*, ...);
#define printf printf_
int sprintf(char* restrict, const char* restrict, ...);
#define memmove(a, b, c) __builtin_memmove(a, b, c)
#define exit(a) do { if (a != 0) { __builtin_trap(); } } while (0)
#else
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#endif
#include <stdint.h> #include <stdint.h>
#include <stddef.h>
#include "config.h" #include "config.h"
@ -259,7 +272,7 @@ void _Block_use_GC5( void *(*alloc)(const unsigned long, const bool isOne, const
_Block_use_GC(alloc, setHasRefcount, gc_assign, gc_assign_weak, _Block_memmove_gc_broken); _Block_use_GC(alloc, setHasRefcount, gc_assign, gc_assign_weak, _Block_memmove_gc_broken);
} }
/* /*
* Called from objc-auto to alternatively turn on retain/release. * Called from objc-auto to alternatively turn on retain/release.
* Prior to this the only "object" support we can provide is for those * Prior to this the only "object" support we can provide is for those
@ -286,10 +299,10 @@ static void *_Block_copy_internal(const void *arg, const int flags) {
struct Block_layout *aBlock; struct Block_layout *aBlock;
const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE; const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;
//printf("_Block_copy_internal(%p, %x)\n", arg, flags); //printf("_Block_copy_internal(%p, %x)\n", arg, flags);
if (!arg) return NULL; if (!arg) return NULL;
// The following would be better done as a switch statement // The following would be better done as a switch statement
aBlock = (struct Block_layout *)arg; aBlock = (struct Block_layout *)arg;
if (aBlock->flags & BLOCK_NEEDS_FREE) { if (aBlock->flags & BLOCK_NEEDS_FREE) {
@ -368,7 +381,7 @@ static void *_Block_copy_internal(const void *arg, const int flags) {
static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) { static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
struct Block_byref **destp = (struct Block_byref **)dest; struct Block_byref **destp = (struct Block_byref **)dest;
struct Block_byref *src = (struct Block_byref *)arg; struct Block_byref *src = (struct Block_byref *)arg;
//printf("_Block_byref_assign_copy called, byref destp %p, src %p, flags %x\n", destp, src, flags); //printf("_Block_byref_assign_copy called, byref destp %p, src %p, flags %x\n", destp, src, flags);
//printf("src dump: %s\n", _Block_byref_dump(src)); //printf("src dump: %s\n", _Block_byref_dump(src));
if (src->forwarding->flags & BLOCK_IS_GC) { if (src->forwarding->flags & BLOCK_IS_GC) {
@ -417,7 +430,7 @@ static void _Block_byref_release(const void *arg) {
// dereference the forwarding pointer since the compiler isn't doing this anymore (ever?) // dereference the forwarding pointer since the compiler isn't doing this anymore (ever?)
shared_struct = shared_struct->forwarding; shared_struct = shared_struct->forwarding;
//printf("_Block_byref_release %p called, flags are %x\n", shared_struct, shared_struct->flags); //printf("_Block_byref_release %p called, flags are %x\n", shared_struct, shared_struct->flags);
// To support C++ destructors under GC we arrange for there to be a finalizer for this // To support C++ destructors under GC we arrange for there to be a finalizer for this
// by using an isa that directs the code to a finalizer that calls the byref_destroy method. // by using an isa that directs the code to a finalizer that calls the byref_destroy method.
@ -519,7 +532,7 @@ unsigned long int Block_size(void *arg) {
#pragma mark Compiler SPI entry points #pragma mark Compiler SPI entry points
#endif /* if 0 */ #endif /* if 0 */
/******************************************************* /*******************************************************
Entry points used by the compiler - the real API! Entry points used by the compiler - the real API!
@ -548,7 +561,7 @@ So the __block copy/dispose helpers will generate flag values of 3 or 7 for obje
__weak block id 128+3+16 __weak block id 128+3+16
__block (^Block) 128+7 __block (^Block) 128+7
__weak __block (^Block) 128+7+16 __weak __block (^Block) 128+7+16
The implementation of the two routines would be improved by switch statements enumerating the eight cases. The implementation of the two routines would be improved by switch statements enumerating the eight cases.
********************************************************/ ********************************************************/
@ -697,4 +710,3 @@ const char *_Block_byref_dump(struct Block_byref *src) {
} }
return buffer; return buffer;
} }

116
thirdparty/tinyhttp.zig vendored Normal file
View file

@ -0,0 +1,116 @@
const std = @import("std");
pub fn main() !void {
const addr = try std.net.Address.parseIp("0.0.0.0", 8080);
var server = try addr.listen(.{ .reuse_address = true });
defer server.deinit();
std.debug.print(":: Listening at http://localhost:8080\n", .{});
while (true) {
const conn = server.accept() catch continue;
defer conn.stream.close();
handle_request(conn.stream) catch continue;
}
}
fn handle_request(stream: std.net.Stream) !void {
var req_buf: [4096]u8 = undefined;
const n = try stream.read(&req_buf);
if (n == 0) return error.Empty;
const request = req_buf[0..n];
// Parse "GET /path HTTP/1.x\r\n..."
const path = blk: {
if (!std.mem.startsWith(u8, request, "GET ")) return send_error(stream, "405 Method Not Allowed");
const rest = request[4..];
const end = std.mem.indexOfScalar(u8, rest, ' ') orelse return error.BadRequest;
break :blk rest[0..end];
};
const raw = if (std.mem.eql(u8, path, "/")) "/p2601.html" else path;
// Strip leading '/' and reject anything suspicious
const rel = std.mem.trimLeft(u8, raw, "/");
if (rel.len == 0 or
std.mem.indexOf(u8, rel, "..") != null or
std.mem.indexOfScalar(u8, rel, '\\') != null or
rel[0] == '/')
{
return send_error(stream, "403 Forbidden");
}
// Open from ./out/
const dir = std.fs.cwd().openDir("out", .{}) catch return send_error(stream, "500 Internal Server Error");
const file = dir.openFile(rel, .{}) catch return send_error(stream, "404 Not Found");
defer file.close();
const stat = try file.stat();
const size = stat.size;
const mime = mime_from_path(rel);
// Send header
var hdr_buf: [512]u8 = undefined;
const header = std.fmt.bufPrint(&hdr_buf,
"HTTP/1.1 200 OK\r\n" ++
"Content-Type: {s}\r\n" ++
"Content-Length: {d}\r\n" ++
"Connection: close\r\n" ++
"\r\n",
.{ mime, size },
) catch return error.HeaderTooLarge;
stream.writeAll(header) catch return;
// Stream the file in chunks
var buf: [8192]u8 = undefined;
while (true) {
const bytes = file.read(&buf) catch return;
if (bytes == 0) break;
stream.writeAll(buf[0..bytes]) catch return;
}
}
fn send_error(stream: std.net.Stream, comptime status: []const u8) error{SendFailed} {
const body = status;
var hdr_buf: [256]u8 = undefined;
const header = std.fmt.bufPrint(&hdr_buf,
"HTTP/1.1 " ++ status ++ "\r\n" ++
"Content-Type: text/plain\r\n" ++
"Content-Length: {d}\r\n" ++
"Connection: close\r\n" ++
"\r\n",
.{body.len},
) catch return error.SendFailed;
stream.writeAll(header) catch {};
stream.writeAll(body) catch {};
return error.SendFailed;
}
fn mime_from_path(path: []const u8) []const u8 {
const ext = std.fs.path.extension(path);
const map = .{
.{ ".html", "text/html;charset=utf-8" },
.{ ".css", "text/css;charset=utf-8" },
.{ ".js", "application/javascript;charset=utf-8" },
.{ ".json", "application/json" },
.{ ".png", "image/png" },
.{ ".jpg", "image/jpeg" },
.{ ".jpeg", "image/jpeg" },
.{ ".gif", "image/gif" },
.{ ".svg", "image/svg+xml" },
.{ ".ico", "image/x-icon" },
.{ ".woff", "font/woff" },
.{ ".woff2", "font/woff2" },
.{ ".ttf", "font/ttf" },
.{ ".wasm", "application/wasm" },
.{ ".pdf", "application/pdf" },
.{ ".txt", "text/plain;charset=utf-8" },
.{ ".xml", "application/xml" },
};
inline for (map) |entry| {
if (std.mem.eql(u8, ext, entry[0])) return entry[1];
}
return "application/octet-stream";
}