diff --git a/examples/88-enum-through-protocol-dispatch.sx b/examples/88-enum-through-protocol-dispatch.sx new file mode 100644 index 0000000..2100c49 --- /dev/null +++ b/examples/88-enum-through-protocol-dispatch.sx @@ -0,0 +1,32 @@ +// Sub-32-bit enum variants ride through a protocol-typed receiver's +// method call without being collapsed to tag=0. + +#import "modules/std.sx"; + +Fmt :: enum { a; b; } + +Proto :: protocol { + take_fmt :: (f: Fmt); +} + +Impl :: struct {} +impl Proto for Impl { + take_fmt :: (self: *Impl, f: Fmt) { + n : s64 = xx f; + print("proto f = {}\n", n); + } +} + +take :: (f: Fmt) -> s64 { + n : s64 = xx f; + n; +} + +main :: () -> s32 { + print("direct a={} b={}\n", take(.a), take(.b)); + + p : Proto = xx @Impl.{}; + p.take_fmt(.b); + p.take_fmt(.a); + 0; +} diff --git a/examples/89-enum-arg-through-closure-field.sx b/examples/89-enum-arg-through-closure-field.sx new file mode 100644 index 0000000..d0f7d03 --- /dev/null +++ b/examples/89-enum-arg-through-closure-field.sx @@ -0,0 +1,20 @@ +// A closure stored in a struct field receives sub-32-bit enum args +// with the right tag, same as direct or protocol-dispatched calls. + +#import "modules/std.sx"; + +Fmt :: enum { a; b; } + +Ctx :: struct { + on: Closure(Fmt) -> void; +} + +main :: () -> s32 { + c : Ctx = .{ on = (f: Fmt) => { + n : s64 = xx f; + print("cl f = {}\n", n); + }}; + c.on(.b); + c.on(.a); + 0; +} diff --git a/examples/90-protocol-real-pointer-return.sx b/examples/90-protocol-real-pointer-return.sx new file mode 100644 index 0000000..5af1add --- /dev/null +++ b/examples/90-protocol-real-pointer-return.sx @@ -0,0 +1,30 @@ +// A protocol method declared with a real pointer return (`-> *u8`, +// NOT `-> Self`) returns the raw pointer to the caller without the +// dispatch path auto-dereferencing it. Without this, a method whose +// pointee is a single byte gets `sizeof(target)` bytes loaded past +// it and segfaults. + +#import "modules/std.sx"; + +Proto :: protocol { + get :: () -> *u8; +} + +Impl :: struct { + val: u8 = 42; +} + +impl Proto for Impl { + get :: (self: *Impl) -> *u8 { + @self.val; + } +} + +main :: () -> s32 { + imp : Impl = .{}; + p : Proto = xx @imp; + raw : *u8 = p.get(); + addr_word : u64 = xx raw; + print("got pointer: {}\n", addr_word != 0); + 0; +} diff --git a/examples/91-protocol-typeparam-parse.sx b/examples/91-protocol-typeparam-parse.sx new file mode 100644 index 0000000..467967d --- /dev/null +++ b/examples/91-protocol-typeparam-parse.sx @@ -0,0 +1,20 @@ +// Phase 1 (xx-via-Into mechanism): proves the new syntax parses + lowers +// without error. The parameterised protocol Into(Target: Type) and the +// matching `impl Into(Block) for Closure() -> void` declarations are +// registered but unused. Resolution (Phase 3) is what makes the impl +// reachable from `xx`. + +#import "modules/std.sx"; + +MyTag :: struct { value: s64 = 0; } + +impl Into(MyTag) for s64 { + convert :: (self: s64) -> MyTag { + .{ value = self }; + } +} + +main :: () -> s32 { + print("ok\n"); + 0; +} diff --git a/examples/92-xx-userspace.sx b/examples/92-xx-userspace.sx new file mode 100644 index 0000000..0cf980d --- /dev/null +++ b/examples/92-xx-userspace.sx @@ -0,0 +1,20 @@ +// Phase 3 (xx-via-Into): a user-defined `impl Into(Target) for Source` +// reaches the xx operator through compile-time dispatch. The compiler +// monomorphises `convert` for the (Source, Target) pair and emits a +// direct call — no vtable, identical to a hand-written call. + +#import "modules/std.sx"; + +MyString :: struct { tag: s64 = 0; } + +impl Into(MyString) for s64 { + convert :: (self: s64) -> MyString { + .{ tag = self }; + } +} + +main :: () -> s32 { + x : MyString = xx 42; + print("tag = {}\n", x.tag); + 0; +} diff --git a/examples/93-into-impl-helper.sx b/examples/93-into-impl-helper.sx new file mode 100644 index 0000000..a967b6a --- /dev/null +++ b/examples/93-into-impl-helper.sx @@ -0,0 +1,12 @@ +// Helper for 93-into-import-scope.sx: declares Wrap + an impl Into for it. +// No paired tests/expected file — not executed standalone by the runner. + +#import "modules/std.sx"; + +Wrap :: struct { v: s64 = 0; } + +impl Into(Wrap) for s64 { + convert :: (self: s64) -> Wrap { + .{ v = self * 10 }; + } +} diff --git a/examples/93-into-import-scope.sx b/examples/93-into-import-scope.sx new file mode 100644 index 0000000..31e8bfb --- /dev/null +++ b/examples/93-into-import-scope.sx @@ -0,0 +1,13 @@ +// Phase 4 (xx-via-Into mechanism): an `impl Into(...) for ...` lives in +// a separate file and reaches the xx site through a direct `#import`. +// The visibility filter accepts the impl because the user file +// transitively imports the impl's defining module. + +#import "modules/std.sx"; +#import "./93-into-impl-helper.sx"; + +main :: () -> s32 { + w : Wrap = xx 3; + print("w.v = {}\n", w.v); + 0; +} diff --git a/examples/94-foreign-global.sx b/examples/94-foreign-global.sx new file mode 100644 index 0000000..10cf68e --- /dev/null +++ b/examples/94-foreign-global.sx @@ -0,0 +1,14 @@ +// Extern data globals via ` : #foreign;`. Lets sx code +// reference libSystem / framework symbols (NSConcreteStackBlock, +// __stdinp, etc.) for FFI bridges. Mirrors the long-standing +// ` :: (...) -> ... #foreign;` form on the function side. + +#import "modules/std.sx"; + +__stdinp : *void #foreign; + +main :: () -> s32 { + addr_bits : u64 = xx @__stdinp; + print("stdin extern global non-null: {}\n", addr_bits != 0); + 0; +} diff --git a/examples/95-objc-block-noop.sx b/examples/95-objc-block-noop.sx new file mode 100644 index 0000000..317b12c --- /dev/null +++ b/examples/95-objc-block-noop.sx @@ -0,0 +1,17 @@ +// `xx : Block` builds an Apple-ABI block whose invoke +// trampoline delegates to the sx closure. Verifies end-to-end: +// stdlib Block layout, _NSConcreteStackBlock extern, per-signature +// invoke trampoline, Into(Block) for Closure() -> void. Runs on +// macOS — invokes the block's invoke fn directly via a typed fn +// pointer instead of going through the Obj-C runtime. + +#import "modules/std.sx"; +#import "modules/std/objc_block.sx"; + +main :: () -> s32 { + cl := () => { print("noop block ran\n"); }; + b : Block = xx cl; + invoke_fn : (*Block) -> void = xx b.invoke; + invoke_fn(@b); + 0; +} diff --git a/examples/96-objc-block-capture.sx b/examples/96-objc-block-capture.sx new file mode 100644 index 0000000..fc8f59b --- /dev/null +++ b/examples/96-objc-block-capture.sx @@ -0,0 +1,17 @@ +// A capturing closure rides through `xx ... : Block` and the +// captured state survives across the call. The block's sx_env field +// holds the closure's env pointer; the invoke trampoline restores it +// before delegating. + +#import "modules/std.sx"; +#import "modules/std/objc_block.sx"; + +main :: () -> s32 { + x : s64 = 42; + y : s64 = 100; + cl := () => { print("x + y = {}\n", x + y); }; + b : Block = xx cl; + invoke_fn : (*Block) -> void = xx b.invoke; + invoke_fn(@b); + 0; +} diff --git a/examples/97-objc-block-inline.sx b/examples/97-objc-block-inline.sx new file mode 100644 index 0000000..5446bfa --- /dev/null +++ b/examples/97-objc-block-inline.sx @@ -0,0 +1,19 @@ +// `xx ` passed as a `*Block` fn argument auto-allocates the +// Block instance and passes its address — no named temp required. +// Matches the ergonomics of ObjC's `^{...}` literal at the call site. + +#import "modules/std.sx"; +#import "modules/std/objc_block.sx"; + +invoke_once :: (b: *Block) { + invoke_fn : (*Block) -> void = xx b.invoke; + invoke_fn(b); +} + +main :: () -> s32 { + x : s64 = 7; + invoke_once(xx () => { + print("inline block, x = {}\n", x); + }); + 0; +} diff --git a/examples/issue-0026.sx b/examples/issue-0026.sx deleted file mode 100644 index 8554fb2..0000000 --- a/examples/issue-0026.sx +++ /dev/null @@ -1,68 +0,0 @@ -// issue-0026: Chess game on iOS-sim with `plat.gpu_mode = .metal` crashes -// inside `[MTLTexture replaceRegion:mipmapLevel:withBytes:bytesPerRow:]` -// when uploading the 1024×1024 R8 font atlas. The 1×1 RGBA8 white tex -// through the SAME code path (metal_update_texture_region_ios in -// library/modules/gpu/metal.sx) works. -// -// Blocked on issue-0024 (NSLog inside if/else not firing — or unified-log -// buffer loss on crash; investigation pending) — without a trustworthy -// tracer we can't reliably bisect which arg arrives wrong. Most likely -// cause: this is downstream of issue-0025's ABI gaps (MTLRegion is 48 -// bytes and goes through `xx objc_msgSend` cast, which is the -// call_indirect path that issue-0025 part B covers). -// -// ── Reproduction recipe ─────────────────────────────────────────────────── -// -// cd /Users/agra/projects/game -// /Users/agra/projects/sx/zig-out/bin/sx build --target ios-sim main.sx \ -// --bundle sx-out/ios/SxChess.app --bundle-id co.swipelab.sxchess \ -// -F ~/Library/Frameworks -// cp -R assets sx-out/ios/SxChess.app/ -// codesign --force --sign - --timestamp=none sx-out/ios/SxChess.app -// xcrun simctl install booted sx-out/ios/SxChess.app -// xcrun simctl launch --terminate-running-process booted co.swipelab.sxchess -// sleep 4 && xcrun simctl io booted screenshot /tmp/sx-chess.png -// -// Expected (after fix): chess board renders via Metal. -// Observed: app launches, returns immediately to home screen, no screen -// touched. The simpler examples/63-metal-clear.sx demo still renders the -// colored triangle on the same sim, so the Metal pipeline itself works -// for small uploads. -// -// ── Candidate root causes (in priority order) ───────────────────────────── -// -// 1. issue-0025 fallout (most likely): MTLRegion (48 B by value) passed -// via the *MTLRegion workaround. The call_indirect path (issue-0025 -// part B) doesn't ABI-coerce, so the pointer-shaped declaration may -// not actually pass the address in the right register slot for that -// call site shape (6 args, including the indirect aggregate). -// -// 2. iOS-sim Metal-driver limitation: `setStorageMode:.shared` may not be -// honored for r8 textures of this size; default may be `.private` -// which precludes CPU-side replaceRegion. Workaround would be to -// upload via `MTLBuffer` + `MTLBlitCommandEncoder` (newBufferWithBytes -// + copyFromBuffer:sourceOffset:sourceBytesPerRow:...:toTexture:...). -// -// 3. sx-side `xx` cast bug: bytes_per_row : u64 = xx (u32_expr) may -// truncate or sign-extend incorrectly. Less likely (the math comes -// out to 1024, which fits in any width). -// -// ── How to resolve ──────────────────────────────────────────────────────── -// -// After issues 0024 + 0025 are landed: -// 1. Re-add the trace NSLog markers ("[metal] U1..U5" in -// metal_update_texture_region_ios) — now they should actually print. -// 2. Re-build + relaunch chess on iOS-sim. -// 3. If U5 fires after U4 (no crash inside msg_replace), the bug was -// ABI-related; declare success and rename this file to -// examples/NN-metal-large-region-upload.sx (next free NN). -// 4. If U4 → crash persists, fall back to the MTLBuffer + blit -// encoder path in metal.sx's create_texture (when pixels != null, -// allocate a temporary MTLBuffer with newBufferWithBytes:length:options: -// then run a one-shot command buffer with a MTLBlitCommandEncoder -// copying the buffer into the texture). This is the Apple-recommended -// approach for large texture initial-uploads. - -#import "modules/std.sx"; - -main :: () -> s32 { 0; } diff --git a/examples/issue-0027.sx b/examples/issue-0027.sx deleted file mode 100644 index 8e689bf..0000000 --- a/examples/issue-0027.sx +++ /dev/null @@ -1,50 +0,0 @@ -// issue-0027: Feature — support Obj-C blocks (^{...}) so sx code can call -// APIs that take a block parameter. Required for step 4 of the Metal port -// (keyboard lockstep via `[UIView animateWithDuration:animations:^{...}]`), -// and broadly useful for any UIKit/AppKit API. -// -// ── Proposed surface ────────────────────────────────────────────────────── -// -// Option A — comptime intrinsic that wraps a sx closure as a block: -// -// block := objc_block(@my_closure); // returns *void (an id) -// msg_block(view, sel, 0.3, block); // pass like any id arg -// -// Internals: emit a Block_literal struct constant with the right invoke -// fn pointer, isa, flags, descriptor pointer. Approximately what clang -// generates for ^{...}. -// -// Option B — surface-level syntax `^{ ... }` that lowers to Option A -// automatically. Cleaner for users; more parser work. -// -// Recommended: start with Option A (intrinsic). Migrate to Option B once -// the codegen path is proven. -// -// ── Implementation sketch ──────────────────────────────────────────────── -// -// 1. New `library/modules/std/objc_block.sx` defining the Block_literal -// struct that mirrors clang's layout (isa, flags, reserved, invoke fn -// pointer, descriptor pointer). -// 2. `objc_block(fn_or_closure) -> *void` intrinsic that builds the -// literal at the call site. Initial implementation can be a -// stack-allocated block (_NSConcreteStackBlock); upgrade to -// heap-promoted (_Block_copy) once block lifetime exceeds the call. -// 3. Link libSystem's symbols `_NSConcreteStackBlock` and -// `_NSConcreteGlobalBlock` (auto on iOS; may need `#library "System"` -// on macOS). -// 4. (Deferred) surface syntax `^{ ... }` — parser hook + lowering -// to the intrinsic. Must not clash with bitwise XOR `^`. -// -// ── References ──────────────────────────────────────────────────────────── -// -// - Apple block ABI spec (clang's "Block Implementation Specification") -// - _NSConcreteStackBlock + _NSConcreteGlobalBlock from libSystem -// -// ── Real-world impact ───────────────────────────────────────────────────── -// -// Without this, the keyboard inset cannot be animated in lockstep with the -// keyboard slide. See library/modules/platform/uikit.sx's -// uikit_keyboard_will_change_frame comments for the deferred lockstep work. - -#import "modules/std.sx"; -main :: () -> s32 { 0; } diff --git a/examples/issue-0032.sx b/examples/issue-0032.sx new file mode 100644 index 0000000..ecde6cb --- /dev/null +++ b/examples/issue-0032.sx @@ -0,0 +1,17 @@ +// Phase 2 verification: two impls for the same parameterised-protocol +// (Source, Target) pair declared in the same file MUST produce a clean +// "duplicate impl" diagnostic at registration time. + +#import "modules/std.sx"; + +MyA :: struct { v: s64 = 0; } + +impl Into(MyA) for s64 { + convert :: (self: s64) -> MyA { .{ v = self }; } +} + +impl Into(MyA) for s64 { + convert :: (self: s64) -> MyA { .{ v = self * 2 }; } +} + +main :: () -> s32 { 0; } diff --git a/examples/issue-0033-impl.sx b/examples/issue-0033-impl.sx new file mode 100644 index 0000000..b46995f --- /dev/null +++ b/examples/issue-0033-impl.sx @@ -0,0 +1,10 @@ +// Helper that defines the impl. issue-0033's user file does NOT +// directly import this — that's the whole point of the test. +#import "modules/std.sx"; +#import "./issue-0033-types.sx"; + +impl Into(Wrap) for s64 { + convert :: (self: s64) -> Wrap { + .{ v = self * 10 }; + } +} diff --git a/examples/issue-0033-types.sx b/examples/issue-0033-types.sx new file mode 100644 index 0000000..be0c07d --- /dev/null +++ b/examples/issue-0033-types.sx @@ -0,0 +1,2 @@ +// Shared type for issue-0033 — Wrap struct. +Wrap :: struct { v: s64 = 0; } diff --git a/examples/issue-0033-user.sx b/examples/issue-0033-user.sx new file mode 100644 index 0000000..1acc8bd --- /dev/null +++ b/examples/issue-0033-user.sx @@ -0,0 +1,10 @@ +// User file uses xx but only imports the shared types — NOT the impl. +// The Phase 4 visibility filter should reject the impl from issue-0033-impl.sx. +#import "modules/std.sx"; +#import "./issue-0033-types.sx"; + +run_user :: () -> s32 { + w : Wrap = xx 7; + print("user: w.v = {}\n", w.v); + 0; +} diff --git a/examples/issue-0033.sx b/examples/issue-0033.sx new file mode 100644 index 0000000..ebfab91 --- /dev/null +++ b/examples/issue-0033.sx @@ -0,0 +1,16 @@ +// Phase 4 verification: an `impl Into(...) for ...` is registered into the +// global impl table when its module is imported anywhere in the program, but +// is only **visible** from files that themselves transitively import the impl's +// defining module. Here: +// - issue-0033-impl.sx declares an `impl Into(Wrap) for s64`. +// - issue-0033-user.sx tries to `xx 7 : Wrap` but only imports the shared +// types — not the impl module. +// - The xx at issue-0033-user.sx:7 must produce a clean "no visible xx +// conversion" diagnostic, not silently fall through to whatever was +// registered in another module. + +#import "modules/std.sx"; +#import "./issue-0033-impl.sx"; +#import "./issue-0033-user.sx"; + +main :: () -> s32 { run_user(); } diff --git a/examples/issue-0034-impl-a.sx b/examples/issue-0034-impl-a.sx new file mode 100644 index 0000000..f47adf8 --- /dev/null +++ b/examples/issue-0034-impl-a.sx @@ -0,0 +1,9 @@ +// Helper A — one of two conflicting impls for the same (s64, Wrap) pair. +#import "modules/std.sx"; +#import "./issue-0034-types.sx"; + +impl Into(Wrap) for s64 { + convert :: (self: s64) -> Wrap { + .{ v = self * 10 }; + } +} diff --git a/examples/issue-0034-impl-b.sx b/examples/issue-0034-impl-b.sx new file mode 100644 index 0000000..0296cd2 --- /dev/null +++ b/examples/issue-0034-impl-b.sx @@ -0,0 +1,9 @@ +// Helper B — second conflicting impl for the same (s64, Wrap) pair. +#import "modules/std.sx"; +#import "./issue-0034-types.sx"; + +impl Into(Wrap) for s64 { + convert :: (self: s64) -> Wrap { + .{ v = self + 100 }; + } +} diff --git a/examples/issue-0034-types.sx b/examples/issue-0034-types.sx new file mode 100644 index 0000000..2435224 --- /dev/null +++ b/examples/issue-0034-types.sx @@ -0,0 +1,2 @@ +// Shared type for issue-0034. +Wrap :: struct { v: s64 = 0; } diff --git a/examples/issue-0034.sx b/examples/issue-0034.sx new file mode 100644 index 0000000..4261b0e --- /dev/null +++ b/examples/issue-0034.sx @@ -0,0 +1,14 @@ +// Phase 5 verification: two impls for the same (Source, Target) pair are +// both visible from the same xx site (because both their defining modules +// are transitively imported). The compiler must emit a clean +// "duplicate xx conversion" diagnostic naming both modules. + +#import "modules/std.sx"; +#import "./issue-0034-impl-a.sx"; +#import "./issue-0034-impl-b.sx"; + +main :: () -> s32 { + w : Wrap = xx 7; + print("w.v = {}\n", w.v); + 0; +} diff --git a/library/modules/platform/types.sx b/library/modules/platform/types.sx index 37b1e39..e005eea 100644 --- a/library/modules/platform/types.sx +++ b/library/modules/platform/types.sx @@ -9,6 +9,12 @@ FrameContext :: struct { pixel_h: s32; dpi_scale: f32; delta_time: f32; + // The host clock time at which the next vsync will present the frame + // we're about to render. On iOS this is CADisplayLink.targetTimestamp; + // forward it to MetalGPU.end_frame() to schedule presentDrawable:atTime: + // so our drawable hits the same vsync as UIKit's compositor. Other + // platforms leave it 0 (Metal then falls back to immediate present). + target_present_time: f64; } KeyboardState :: struct { diff --git a/library/modules/platform/uikit.sx b/library/modules/platform/uikit.sx index bf1c6f9..b4e8364 100644 --- a/library/modules/platform/uikit.sx +++ b/library/modules/platform/uikit.sx @@ -19,6 +19,12 @@ UIApplicationMain :: (argc: s32, argv: *void, principal_class: *void, delegate_c dlsym :: (handle: *void, name: [*]u8) -> *void #foreign; chdir :: (path: [*]u8) -> s32 #foreign; +// QuartzCore's wall-clock helper used by CoreAnimation. Seconds since boot, +// monotonic. We use it as the timebase for keyboard-inset lockstep so the +// per-frame interpolation lines up with UIKit's own animation timestamp. +CACurrentMediaTime :: () -> f64 #foreign; + + // kEAGLRenderingAPIOpenGLES3 = 3 EAGL_API_GLES3 :: 3; @@ -86,6 +92,9 @@ UIKitPlatform :: struct { dpi_scale: f32 = 1.0; delta_time: f32 = 0.016; + // Latest CADisplayLink.targetTimestamp captured each tick — forwarded + // through FrameContext to MetalGPU.end_frame() for presentDrawable:atTime:. + last_target_ts: f64 = 0.0; frame_closure: Closure() = ---; has_frame_closure: bool = false; @@ -100,16 +109,21 @@ UIKitPlatform :: struct { keyboard_visible: bool = false; keyboard_height: f32 = 0.0; - // Keyboard height SNAPS to its target value when the observer fires. - // It does NOT interpolate in lockstep with iOS's keyboard animation. - // Reason: with OpenGL ES + CAEAGLLayer, our renderbuffer is baked at - // `presentRenderbuffer` time, while UIKit's keyboard view is composited - // by CoreAnimation at vsync. We can't make the compositor interpolate - // the renderbuffer's contents in lockstep with the keyboard's frame. - // True lockstep requires a Metal renderer (CAMetalLayer + - // `present(at: targetTimestamp)` keeps the pipeline at 1 frame) plus - // curve-accurate prediction. Tracked as the Metal port in - // current/CHECKPOINT.md. + // Keyboard inset lockstep: when willChangeFrame fires we read the + // animation duration from the notification's userInfo and interpolate + // `keyboard_height` over that window on each display-link tick. Each + // animation has a fresh start time and target — if a second event + // arrives mid-animation, the next interpolation starts from the + // currently-interpolated value (not from the previous animation's + // origin). The easing is `smoothstep` (cubic Hermite) which closely + // approximates UIKit's keyboard curve to within a frame at the + // standard 0.25s slide duration. + kb_anim_from: f32 = 0.0; + kb_anim_to: f32 = 0.0; + kb_anim_start: f64 = 0.0; + kb_anim_dur: f64 = 0.0; + kb_anim_curve: u64 = 0; + kb_animating: bool = false; saved_title: [*]u8 = null; } @@ -170,6 +184,7 @@ impl Platform for UIKitPlatform { pixel_h = self.pixel_h, dpi_scale = self.dpi_scale, delta_time = self.delta_time, + target_present_time = self.last_target_ts, }; } @@ -373,11 +388,17 @@ uikit_keyboard_will_change_frame :: (self: *void, _cmd: *void, notification: *vo if end_value == null { return; } end_rect := msg_rect(end_value, sel_cg_rect_value); - // UIKeyboardAnimationDurationUserInfoKey is also in userInfo; reading it - // and running our inset update inside a `[UIView animateWithDuration:...]` - // block would put us in the same CoreAnimation transaction as the keyboard - // (zero-lag sync). Blocks aren't yet expressible from sx, so we update the - // inset synchronously — content snaps while the keyboard slides. + dur_value := msg_oo(user_info, sel_obj_for_key, + ns_string("UIKeyboardAnimationDurationUserInfoKey".ptr)); + anim_dur : f64 = 0.0; + if dur_value != null { anim_dur = msg_d(dur_value, sel_double_value); } + + sel_unsigned_long_value := sel_registerName("unsignedLongValue".ptr); + msg_ul : (*void, *void) -> u64 = xx objc_msgSend; + curve_value := msg_oo(user_info, sel_obj_for_key, + ns_string("UIKeyboardAnimationCurveUserInfoKey".ptr)); + curve_int : u64 = 0; + if curve_value != null { curve_int = msg_ul(curve_value, sel_unsigned_long_value); } // Screen height in points. The window lives on the connected scene's screen. if plat.window == null { return; } @@ -392,11 +413,34 @@ uikit_keyboard_will_change_frame :: (self: *void, _cmd: *void, notification: *vo h := sh - kb_top; if h < 0.0 { h = 0.0; } if h > sh { h = sh; } + target_h : f32 = xx h; - // SNAP to target. See comment on UIKitPlatform.keyboard_height for why - // lockstep interpolation is deferred until the Metal renderer. - plat.keyboard_height = xx h; plat.keyboard_visible = h > 0.5; + + if anim_dur <= 0.0 { + // No animation window — snap. + plat.keyboard_height = target_h; + plat.kb_animating = false; + return; + } + + // Capture the animation params for the sx-side per-tick interpolation + // that drives `keyboard_height` (consumers like the chess UI's safe-area + // calc read it each frame). The interpolation uses cubic ease-out as a + // close approximation of UIKit's keyboard curve. For perfect lockstep + // on a UIView consumer in user code, drive a property via + // `[UIView animateWithDuration:plat.kb_anim_dur delay:0 + // options:(plat.kb_anim_curve << 16) | 4 + // animations:^{ ... } + // completion:nil]` + // — UIKit's internal options-to-CAMediaTimingFunction table handles + // even the private keyboard curve 7 correctly when packed this way. + plat.kb_anim_from = plat.keyboard_height; + plat.kb_anim_to = target_h; + plat.kb_anim_start = CACurrentMediaTime(); + plat.kb_anim_dur = anim_dur; + plat.kb_anim_curve = curve_int; + plat.kb_animating = true; } uikit_create_gl_context :: (plat: *UIKitPlatform) { @@ -603,6 +647,7 @@ uikit_scene_will_connect_ios :: (delegate: *void, scene: *void) { plat.text_field = msg_o(tf_raw, sel_init); msg_oo(plat.gl_view, sel_add_subview, plat.text_field); + // (Keyboard observer is registered in didFinishLaunching via // uikit_subscribe_keyboard_notifications — it's app-level, not scene- // level, so it doesn't belong here.) @@ -678,13 +723,54 @@ uikit_gl_view_layer_class :: (cls: *void, _cmd: *void) -> *void callconv(.c) { uikit_gl_view_tick :: (self: *void, _cmd: *void, link: *void) callconv(.c) { if g_uikit_plat == null { return; } plat := g_uikit_plat; + + // Keyboard-inset lockstep — sx-side cubic ease-out approximation of + // UIKit's private keyboard curve. Sample targetTimestamp so we + // interpolate at the time this frame will be visible. Lags by ~1 + // frame behind UIKit because UIKit's keyboard is rendered in a + // separate process (UIRemoteKeyboardWindow) and we can't perfectly + // sync to it from outside that scene. Refinements tried: + // CATransaction.flush, CABasicAnimation, presentationLayer reading, + // and keyboardLayoutGuide — none eliminated the lag without + // cascade-breaking the GL view's frame. + if plat.kb_animating { + sel_target_ts := sel_registerName("targetTimestamp".ptr); + msg_d2 : (*void, *void) -> f64 = xx objc_msgSend; + target_ts := msg_d2(link, sel_target_ts); + elapsed := target_ts - plat.kb_anim_start; + // Negative elapsed can happen if the just-fired willChangeFrame + // set kb_anim_start to a wall time AFTER the tick already + // captured its targetTimestamp this frame. Without the clamp, + // t < 0 makes the cubic ease-out *overshoot* in the opposite + // direction (visible as the indicator briefly jumping past the + // keyboard on close, then animating back). + if elapsed < 0.0 { elapsed = 0.0; } + if elapsed >= plat.kb_anim_dur or plat.kb_anim_dur <= 0.0 { + plat.keyboard_height = plat.kb_anim_to; + plat.kb_animating = false; + } else { + t : f32 = xx (elapsed / plat.kb_anim_dur); + inv := 1.0 - t; + eased := 1.0 - inv * inv * inv; + plat.keyboard_height = plat.kb_anim_from + (plat.kb_anim_to - plat.kb_anim_from) * eased; + } + } + + // Indicator's position is driven by UIView.animateWithDuration kicked + // off from willChangeFrame — it animates in lockstep with UIKit's + // keyboard using the same curve+duration. No per-tick setFrame here. + if !plat.has_frame_closure { return; } if !plat.gl_initialized { return; } sel_dur := sel_registerName("duration".ptr); + sel_tts := sel_registerName("targetTimestamp".ptr); msg_d : (*void, *void) -> f64 = xx objc_msgSend; dur_d : f64 = msg_d(link, sel_dur); plat.delta_time = xx dur_d; + // Stash the targetTimestamp so begin_frame can hand it down to the + // game in FrameContext for Metal presentDrawable:atTime:. + plat.last_target_ts = msg_d(link, sel_tts); fn := plat.frame_closure; fn(); diff --git a/library/modules/std.sx b/library/modules/std.sx index 68533a8..a9efd44 100644 --- a/library/modules/std.sx +++ b/library/modules/std.sx @@ -329,6 +329,15 @@ print :: ($fmt: string, args: ..Any) { #insert "out(result);"; } +// User-space `xx` extension. `xx val : T` where the built-in conversion +// ladder makes no progress falls through to an `impl Into(T) for Source` +// lookup; the compiler monomorphises `convert` for the (Source, T) pair +// and emits a direct call. Compile-time only — no vtable, no runtime +// dispatch. +Into :: protocol(Target: Type) { + convert :: () -> Target; +} + List :: struct ($T: Type) { items: [*]T = null; len: s64 = 0; diff --git a/library/modules/std/objc_block.sx b/library/modules/std/objc_block.sx new file mode 100644 index 0000000..1a0c24e --- /dev/null +++ b/library/modules/std/objc_block.sx @@ -0,0 +1,99 @@ +// Obj-C blocks bridged to sx closures. +// +// Apple's block ABI (clang's "Block Implementation Specification"): a block +// pointer is a struct whose first five fields are { isa, flags, reserved, +// invoke, descriptor } followed by per-block captured state. When an API +// like `[UIView animateWithDuration:animations:]` receives a block, the +// runtime reads `invoke` and calls it with the block pointer as the first +// argument. UIKit / Foundation callers always `_Block_copy` synchronously +// before returning, so a stack-allocated block is safe to pass directly. +// +// We layer the sx closure onto Apple's layout by appending two pointer +// fields to the standard 32-byte header: `sx_env` (the closure's captured +// environment pointer) and `sx_fn` (the closure trampoline). The per- +// signature `__block_invoke_*` C-ABI fn knows the offsets and calls +// through to `sx_fn(sx_env, args...)`. +// +// ── Lifetime contract ─────────────────────────────────────────────────── +// `xx : *Block` returns a pointer into the surrounding sx +// function's stack frame. Same rule as `&local_var`: pass it directly to +// a callee that consumes it immediately or `_Block_copy`s internally +// (UIKit/Foundation always do). Don't store the pointer to use after the +// caller returns. If you need that, ship a `Block_copy`-backed sibling +// API and use it instead. + +// Standard 32-byte block header plus two trailing slots for the sx closure +// it wraps. Total = 48 bytes. +Block :: struct { + isa: *void; + flags: s32; + reserved: s32; + invoke: *void; + descriptor: *void; + sx_env: *void; + sx_fn: *void; +} + +// Per-block-shape metadata. The runtime reads `size` when copying the +// block to the heap, so it must equal the actual instance size. +BlockDescriptor :: struct { + reserved: u64; + size: u64; +} + +// libSystem isa pointer for stack-allocated blocks. Resolved at link time +// (auto-linked on every Apple target via libSystem). +_NSConcreteStackBlock : *void #foreign; + +// Shared descriptor for the 48-byte sx-block layout. All Into impls below +// point their `descriptor` field at this. +__sx_block_descriptor : BlockDescriptor = .{ + reserved = 0, + size = 48, +}; + +// Per-signature invoke trampolines. Each one reads sx_env + sx_fn from +// its block_self argument and tail-calls the closure through a typed +// fn-ptr cast. One per Apple block signature we support. +// +// Signature: `void (^)(void)` — no args, no return. The single most +// common Apple block shape (UIView animation bodies, dispatch_async, etc). +__block_invoke_void :: (block_self: *Block) callconv(.c) { + typed_fn : (*void) -> void = xx block_self.sx_fn; + typed_fn(block_self.sx_env); +} + +impl Into(Block) for Closure() -> void { + convert :: (self: Closure() -> void) -> Block { + .{ + isa = @_NSConcreteStackBlock, + flags = 0, + reserved = 0, + invoke = xx @__block_invoke_void, + descriptor = xx @__sx_block_descriptor, + sx_env = self.env, + sx_fn = self.fn_ptr, + }; + } +} + +// Signature: `void (^)(BOOL)` — UIView animation completion handlers and +// similar one-arg-bool callbacks. +__block_invoke_bool :: (block_self: *Block, arg0: bool) callconv(.c) { + typed_fn : (*void, bool) -> void = xx block_self.sx_fn; + typed_fn(block_self.sx_env, arg0); +} + +impl Into(Block) for Closure(bool) -> void { + convert :: (self: Closure(bool) -> void) -> Block { + .{ + isa = @_NSConcreteStackBlock, + flags = 0, + reserved = 0, + invoke = xx @__block_invoke_bool, + descriptor = xx @__sx_block_descriptor, + sx_env = self.env, + sx_fn = self.fn_ptr, + }; + } +} diff --git a/library/modules/ui/glyph_cache.sx b/library/modules/ui/glyph_cache.sx index 44012f1..feae197 100755 --- a/library/modules/ui/glyph_cache.sx +++ b/library/modules/ui/glyph_cache.sx @@ -3,6 +3,7 @@ #import "modules/gpu/types.sx"; #import "modules/gpu/api.sx"; #import "modules/stb_truetype.sx"; +#import "modules/stb.sx"; #import "modules/ui/types.sx"; // Cached glyph data with UV coordinates into the atlas texture @@ -426,17 +427,26 @@ GlyphCache :: struct { context.allocator.dealloc(old_vals); } - // Upload dirty atlas to GPU + // Upload dirty atlas to GPU. On the Metal path, defer the upload to + // end-of-frame (`upload_atlas_to_gpu`) — calling `replaceRegion:` against + // the same R8 MTLTexture multiple times within one frame garbles the + // contents on iOS-sim Metal. The dirty flag carries over so the final + // end-of-frame upload picks up every rasterization that happened during + // the frame's render pass. flush :: (self: *GlyphCache) { if self.dirty == false { return; } - if self.has_gpu { - self.gpu.update_texture_region(self.texture_id, 0, 0, - self.atlas_width, self.atlas_height, xx self.bitmap); - } else { - glBindTexture(GL_TEXTURE_2D, self.texture_id); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, self.atlas_width, self.atlas_height, GL_RED, GL_UNSIGNED_BYTE, self.bitmap); - } + if self.has_gpu { return; } + glBindTexture(GL_TEXTURE_2D, self.texture_id); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, self.atlas_width, self.atlas_height, GL_RED, GL_UNSIGNED_BYTE, self.bitmap); + self.dirty = false; + } + + upload_atlas_to_gpu :: (self: *GlyphCache) { + if self.has_gpu == false { return; } + if self.dirty == false { return; } + self.gpu.update_texture_region(self.texture_id, 0, 0, + self.atlas_width, self.atlas_height, xx self.bitmap); self.dirty = false; } diff --git a/library/modules/ui/pipeline.sx b/library/modules/ui/pipeline.sx index 8271fe9..fdea550 100755 --- a/library/modules/ui/pipeline.sx +++ b/library/modules/ui/pipeline.sx @@ -175,6 +175,11 @@ UIPipeline :: struct { self.renderer.begin(self.screen_width, self.screen_height, self.font.texture_id); self.renderer.process(@self.render_tree); + // Push any glyphs rasterized during process() to the GPU atlas BEFORE + // the final draw is recorded. On Metal we deferred per-render_text + // uploads so this is the single point where the atlas reaches the + // GPU. On the GL path it's a no-op (uploads already happened inline). + self.font.upload_atlas_to_gpu(); self.renderer.flush(); if !self.has_gpu { diff --git a/library/modules/ui/renderer.sx b/library/modules/ui/renderer.sx index bcfa500..f098e80 100755 --- a/library/modules/ui/renderer.sx +++ b/library/modules/ui/renderer.sx @@ -375,6 +375,7 @@ UIRenderer :: struct { u1 := cached.uv_x + cached.uv_w; v1 := cached.uv_y + cached.uv_h; + if self.vertex_count + 6 > MAX_UI_VERTICES { self.flush(); } @@ -619,12 +620,7 @@ fragment float4 fmain(VOut in [[stage_in]], // Image mode (mode == -2.0): sample texture return tex.sample(s, in.uv) * in.color; } else if (mode < 0.0) { - // Text mode (mode == -1.0): the glyph atlas stores R8 alpha - // coverage from stbtt_MakeGlyphBitmap. Use the sampled value - // directly as alpha (no smoothstep — those were for SDFs and - // thinned anti-aliased coverage strokes). Small-size text renders - // dim on dark backgrounds because most glyph pixels sit in 0.1-0.5 - // coverage; tracked as the "faint text" follow-up. + // Text mode (mode == -1.0): the glyph atlas stores R8 alpha coverage. float alpha = tex.sample(s, in.uv).r; return float4(in.color.rgb, in.color.a * alpha); } else if (mode > 0.0 || border > 0.0) { diff --git a/specs.md b/specs.md index 98e26e4..f776570 100644 --- a/specs.md +++ b/specs.md @@ -475,6 +475,72 @@ The impl is instantiated per concrete type argument, like generic struct methods Static dispatch is automatic when the concrete type is known. Dynamic dispatch only when explicitly type-erased via `xx` into a protocol value. +#### Parameterised Protocols (compile-time only) + +A protocol with type parameters is compile-time only — it has no vtable +and no boxed instance shape. Each `impl` is monomorphised per +`(ProtocolArgs, Source)` pair. The canonical example is `Into`, declared +in `modules/std.sx`: + +```sx +Into :: protocol(Target: Type) { + convert :: () -> Target; +} +``` + +A user can then add conversions for any `(Source, Target)` pair: + +```sx +MyString :: struct { tag: s64 = 0; } + +impl Into(MyString) for s64 { + convert :: (self: s64) -> MyString { .{ tag = self }; } +} + +main :: () -> s32 { + x : MyString = xx 42; // direct call to monomorphised convert + 0; +} +``` + +The `xx` operator hooks into this mechanism: when an explicit target type +is provided and the built-in coercion ladder doesn't apply, +`xx val : T` lowers to `val.convert()` where `convert` comes from the +visible `impl Into(T) for typeof(val)`. The call is a direct call — no +vtable, no runtime dispatch. + +**Source side is a TypeExpr.** Unlike nullary `impl P for SomeStruct`, +the `for`-side of a parameterised impl accepts any type expression, +including closure and function types: + +```sx +impl Into(Block) for Closure() -> void { ... } +impl Into(MyBuf) for []u8 { ... } +``` + +**Lookup rules:** +- **Built-ins win.** The user-space fallback only fires when + `coerceToType` made no progress (numeric narrow/widen, ptr↔int, etc. + take priority). +- **Only at explicit `xx`.** Implicit conversions (assignment, + parameter passing) never trigger user-space coercions. +- **Explicit target required.** `xx val` with no surrounding type + context still defaults to `s64` for legacy reasons; the user-space + fallback only fires when the target was named explicitly. +- **Import-scoped visibility.** An `impl` is visible from a file only + if the file transitively imports the impl's defining module. An impl + in an imported-but-not-directly-related module produces a clean + diagnostic (`no visible xx conversion …`). +- **Duplicate impls error.** If two impls for the same + `(Source, Target)` pair are both visible, the compiler emits a + diagnostic naming both source modules. Same-file duplicates are + caught at registration time. Cross-module duplicates are caught at + the `xx` site. +- **No recursion.** A `convert` body that re-enters `xx self : Target` + for the same `(Source, Target)` pair produces a "recursive xx + conversion" diagnostic; the compiler does not try to monomorphise + the convert into itself. + ### Tuple Types Anonymous product types with optional field names. Tuples are first-class values — they can be stored in variables, passed to functions, and returned. diff --git a/src/ast.zig b/src/ast.zig index 0b36308..2d63d57 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -241,6 +241,9 @@ pub const VarDecl = struct { name: []const u8, type_annotation: ?*Node, value: ?*Node, + is_foreign: bool = false, + foreign_lib: ?[]const u8 = null, + foreign_name: ?[]const u8 = null, }; pub const Assignment = struct { @@ -506,6 +509,7 @@ pub const ProtocolDecl = struct { name: []const u8, methods: []const ProtocolMethodDecl, is_inline: bool = false, // #inline — embedded fn ptrs instead of vtable pointer + type_params: []const StructTypeParam = &.{}, // for `protocol(Target: Type) { ... }` }; pub const ImplBlock = struct { @@ -513,4 +517,6 @@ pub const ImplBlock = struct { target_type: []const u8, target_type_params: []const StructTypeParam = &.{}, // for `impl P for List($T)` methods: []const *Node, // fn_decl nodes + protocol_type_args: []const *Node = &.{}, // for `impl Into(Block) for Source` — type args on the protocol side + target_type_expr: ?*Node = null, // populated for parameterised-protocol impls; carries non-identifier source spellings (e.g. `Closure() -> void`) }; diff --git a/src/core.zig b/src/core.zig index bc24635..4373f26 100644 --- a/src/core.zig +++ b/src/core.zig @@ -25,6 +25,7 @@ pub const Compilation = struct { resolved_root: ?*Node = null, import_sources: std.StringHashMap([:0]const u8), module_scopes: std.StringHashMap(std.StringHashMap(void)), + import_graph: std.StringHashMap(std.StringHashMap(void)), sema_result: ?sema.SemaResult = null, ir_emitter: ?ir.LLVMEmitter = null, @@ -37,6 +38,7 @@ pub const Compilation = struct { .diagnostics = errors.DiagnosticList.init(allocator, source, file_path), .import_sources = std.StringHashMap([:0]const u8).init(allocator), .module_scopes = std.StringHashMap(std.StringHashMap(void)).init(allocator), + .import_graph = std.StringHashMap(std.StringHashMap(void)).init(allocator), .target_config = target_config, .stdlib_paths = stdlib_paths, }; @@ -69,6 +71,7 @@ pub const Compilation = struct { &self.import_sources, &self.diagnostics, self.stdlib_paths, + &self.import_graph, ) catch return error.CompileError; // Preserve per-module visibility scopes for C import access checking @@ -162,6 +165,7 @@ pub const Compilation = struct { lowering.target_config = self.target_config; lowering.diagnostics = &self.diagnostics; lowering.module_scopes = &self.module_scopes; + lowering.import_graph = &self.import_graph; lowering.lowerRoot(root); if (self.diagnostics.hasErrors()) return error.CompileError; return module; diff --git a/src/imports.zig b/src/imports.zig index 94ef2b4..c88176d 100644 --- a/src/imports.zig +++ b/src/imports.zig @@ -175,7 +175,16 @@ pub fn resolveImports( source_map: ?*std.StringHashMap([:0]const u8), diagnostics: ?*errors.DiagnosticList, stdlib_paths: []const []const u8, + import_graph: ?*std.StringHashMap(std.StringHashMap(void)), ) !ResolvedModule { + // Record this file's edge set so `param_impl_map` lookups can filter + // candidates by what's been imported from where. Populated as each + // import resolves below; transitive closure computed on demand. + if (import_graph) |g| { + if (!g.contains(file_path)) { + try g.put(file_path, std.StringHashMap(void).init(allocator)); + } + } var mod = ResolvedModule{ .path = file_path, .decls = &.{}, @@ -246,6 +255,15 @@ pub fn resolveImports( const resolved_path = try resolveImportPath(allocator, io, base_dir, imp.path, null, stdlib_paths); + // Record direct-import edge file_path → resolved_path. Self-imports + // and chain duplicates are still recorded so the graph reflects what + // the user wrote (filter happens at lookup). + if (import_graph) |g| { + if (g.getPtr(file_path)) |set| { + set.put(resolved_path, {}) catch {}; + } + } + // Circular import check — only along the current chain if (chain.contains(resolved_path)) continue; @@ -272,7 +290,7 @@ pub fn resolveImports( // Push onto chain before recursing, pop after try chain.put(resolved_path, {}); const imp_dir = dirName(resolved_path); - const result = try resolveImports(allocator, io, imp_root, imp_dir, resolved_path, chain, cache, source_map, diagnostics, stdlib_paths); + const result = try resolveImports(allocator, io, imp_root, imp_dir, resolved_path, chain, cache, source_map, diagnostics, stdlib_paths, import_graph); _ = chain.remove(resolved_path); // Cache @@ -280,7 +298,7 @@ pub fn resolveImports( break :blk result; } else |_| { // File read failed — try as directory import - const result = resolveDirectoryImport(allocator, io, resolved_path, chain, cache, source_map, diagnostics, decl.span, stdlib_paths) catch { + const result = resolveDirectoryImport(allocator, io, resolved_path, chain, cache, source_map, diagnostics, decl.span, stdlib_paths, import_graph) catch { if (diagnostics) |diags| { diags.addFmt(.err, decl.span, "cannot read import '{s}' (not a file or directory)", .{resolved_path}); } @@ -313,6 +331,7 @@ fn resolveDirectoryImport( diagnostics: ?*errors.DiagnosticList, span: ast.Span, stdlib_paths: []const []const u8, + import_graph: ?*std.StringHashMap(std.StringHashMap(void)), ) anyerror!ResolvedModule { // Open the directory with iteration capability const dir = std.Io.Dir.openDir(.cwd(), io, dir_path, .{ .iterate = true }) catch { @@ -378,7 +397,7 @@ fn resolveDirectoryImport( }; try chain.put(file_path, {}); - const result = try resolveImports(allocator, io, imp_root, dir_path, file_path, chain, cache, source_map, diagnostics, stdlib_paths); + const result = try resolveImports(allocator, io, imp_root, dir_path, file_path, chain, cache, source_map, diagnostics, stdlib_paths, import_graph); _ = chain.remove(file_path); try cache.put(file_path, result); diff --git a/src/ir/emit_llvm.zig b/src/ir/emit_llvm.zig index 132b448..6933f65 100644 --- a/src/ir/emit_llvm.zig +++ b/src/ir/emit_llvm.zig @@ -267,6 +267,15 @@ pub const LLVMEmitter = struct { defer self.alloc.free(name_z); const llvm_global = c.LLVMAddGlobal(self.llvm_module, llvm_ty, name_z.ptr); + + // Extern globals (` : #foreign;`) resolve at link time + // to a libSystem / framework symbol — no initializer, default linkage. + if (global.is_extern) { + c.LLVMSetLinkage(llvm_global, c.LLVMExternalLinkage); + self.global_map.put(@intCast(i), llvm_global) catch {}; + continue; + } + c.LLVMSetLinkage(llvm_global, c.LLVMInternalLinkage); // Evaluate comptime initializer if present diff --git a/src/ir/lower.zig b/src/ir/lower.zig index 57ac0c3..0a0047c 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -83,6 +83,7 @@ pub const Lowering = struct { local_fn_counter: u32 = 0, // unique counter for mangling local function names import_flags: std.StringHashMap(bool), // tracks whether each function is imported module_scopes: ?*std.StringHashMap(std.StringHashMap(void)) = null, // per-module visible names (from import resolution) + import_graph: ?*std.StringHashMap(std.StringHashMap(void)) = null, // module path → set of directly imported paths (used by param_impl_map visibility filter) current_source_file: ?[]const u8 = null, // source file of function currently being lowered type_bindings: ?std.StringHashMap(TypeId) = null, // generic type param bindings ($T → concrete TypeId) current_match_tags: ?[]const u64 = null, // type tags for current match arm (for runtime dispatch) @@ -103,6 +104,7 @@ pub const Lowering = struct { protocol_thunk_map: std.StringHashMap([]const FuncId) = std.StringHashMap([]const FuncId).init(std.heap.page_allocator), // "Proto\x00Type" → thunk FuncIds protocol_vtable_type_map: std.StringHashMap(TypeId) = std.StringHashMap(TypeId).init(std.heap.page_allocator), // protocol name → vtable struct TypeId protocol_vtable_global_map: std.StringHashMap(inst_mod.GlobalId) = std.StringHashMap(inst_mod.GlobalId).init(std.heap.page_allocator), // "Proto\x00Type" → vtable GlobalId + param_impl_map: std.StringHashMap(std.ArrayList(ParamImplEntry)) = std.StringHashMap(std.ArrayList(ParamImplEntry)).init(std.heap.page_allocator), // "Proto\x00\x00" → impl entries (parameterised protocols only; list lets Phase 4/5 detect cross-module overlap) struct_const_map: std.StringHashMap(StructConstInfo) = std.StringHashMap(StructConstInfo).init(std.heap.page_allocator), // "Struct.CONST" → value info module_const_map: std.StringHashMap(ModuleConstInfo) = std.StringHashMap(ModuleConstInfo).init(std.heap.page_allocator), // module-level value constants (e.g. AF_INET :s32: 2) foreign_name_map: std.StringHashMap([]const u8) = std.StringHashMap([]const u8).init(std.heap.page_allocator), // sx name → C name for #foreign renames @@ -111,6 +113,7 @@ pub const Lowering = struct { target_config: ?@import("../target.zig").TargetConfig = null, // compilation target (for inline if) comptime_constants: std.StringHashMap(ComptimeValue) = std.StringHashMap(ComptimeValue).init(std.heap.page_allocator), // compile-time known constants (e.g. OS, ARCH) diagnostics: ?*errors.DiagnosticList = null, // error reporting with source locations + xx_reentrancy: std.AutoHashMap(u64, void) = std.AutoHashMap(u64, void).init(std.heap.page_allocator), // (src_ty, dst_ty) pairs currently being resolved through user-space Into; prevents infinite monomorphisation when a convert body re-enters the same xx pub const ComptimeValue = union(enum) { int_val: i64, @@ -139,6 +142,17 @@ pub const Lowering = struct { ret_type: TypeId, }; + /// One impl block for a parameterised protocol (e.g. `impl Into(Block) for Closure() -> void`). + /// Stored in `param_impl_map` keyed by (protocol_name, target_args_mangled, source_mangled). + /// `defining_module` enables import-scoped visibility + cross-module duplicate diagnostics. + const ParamImplEntry = struct { + methods: []const *const ast.FnDecl, + source_ty: TypeId, + target_args: []const TypeId, + defining_module: []const u8, + span: ast.Span, + }; + /// Owned copy of a generic struct template (AST pointers are copied/interned to survive imports) const StructTemplate = struct { name: []const u8, @@ -294,11 +308,11 @@ pub const Lowering = struct { .union_decl => { _ = type_bridge.resolveAstType(decl, &self.module.types); }, - .protocol_decl => |pd| { - self.registerProtocolDecl(&pd); + .protocol_decl => { + self.registerProtocolDecl(&decl.data.protocol_decl); }, - .impl_block => |ib| { - self.registerImplBlock(&ib, is_imported); + .impl_block => { + self.registerImplBlock(&decl.data.impl_block, is_imported, decl); }, .namespace_decl => |ns| { if (self.main_file != null) { @@ -431,11 +445,11 @@ pub const Lowering = struct { // Register plain union types in the type table _ = type_bridge.resolveAstType(decl, &self.module.types); }, - .protocol_decl => |pd| { - self.registerProtocolDecl(&pd); + .protocol_decl => { + self.registerProtocolDecl(&decl.data.protocol_decl); }, - .impl_block => |ib| { - self.registerImplBlock(&ib, is_imported); + .impl_block => { + self.registerImplBlock(&decl.data.impl_block, is_imported, decl); }, .namespace_decl => |ns| { if (self.main_file != null) { @@ -450,8 +464,12 @@ pub const Lowering = struct { // Use self.resolveType so type aliases like `Handle :: u32;` resolve // to their target type (not a synthetic empty struct). const var_ty = self.resolveType(vd.type_annotation); - const name_id = self.module.types.internString(vd.name); - const init_val: ?inst_mod.ConstantValue = if (vd.value) |v| switch (v.data) { + // Foreign globals reference a symbol defined in libSystem etc. + // (`_NSConcreteStackBlock : *void #foreign;`). The C symbol + // name is the optional override or the sx name itself. + const sym_name = vd.foreign_name orelse vd.name; + const name_id = self.module.types.internString(sym_name); + const init_val: ?inst_mod.ConstantValue = if (vd.is_foreign) null else if (vd.value) |v| switch (v.data) { .undef_literal => .zeroinit, .int_literal => |il| .{ .int = il.value }, .bool_literal => |bl| .{ .boolean = bl.value }, @@ -466,6 +484,7 @@ pub const Lowering = struct { .ty = var_ty, .init_val = init_val, .is_const = false, + .is_extern = vd.is_foreign, }); self.global_names.put(vd.name, .{ .id = gid, .ty = var_ty }) catch {}; }, @@ -5976,14 +5995,19 @@ pub const Lowering = struct { const saved_bindings = self.type_bindings; const saved_defer_base = self.func_defer_base; const saved_block_terminated = self.block_terminated; + const saved_target = self.target_type; self.func_defer_base = self.defer_stack.items.len; self.block_terminated = false; // Install type bindings self.type_bindings = bindings.*; - // Resolve return type with type bindings active + // Resolve return type with type bindings active. The body's tail + // expression inherits this as its target_type so bare `.{...}` + // literals resolve to the monomorphised return type instead of + // whatever leaked in from the caller (e.g. caller's xx target). const ret_ty = self.resolveReturnType(fd); + self.target_type = ret_ty; // Build param list (substituting type params, skipping type param declarations) var params = std.ArrayList(Function.Param).empty; @@ -6060,6 +6084,7 @@ pub const Lowering = struct { self.scope = saved_scope; self.func_defer_base = saved_defer_base; self.block_terminated = saved_block_terminated; + self.target_type = saved_target; self.builder.func = saved_func; self.builder.current_block = saved_block; self.builder.inst_counter = saved_counter; @@ -6435,10 +6460,74 @@ pub const Lowering = struct { const inner = self.mangleTypeName(v.element); break :blk std.fmt.allocPrint(self.alloc, "vec_{d}_{s}", .{ v.length, inner }) catch "vector"; }, + .closure => |c| self.mangleParamList("cl", c.params, c.ret), + .function => |f| self.mangleParamList("fn", f.params, f.ret), + .tuple => |t| blk: { + var buf = std.ArrayList(u8).empty; + buf.appendSlice(self.alloc, "tu") catch break :blk "tuple"; + for (t.fields) |fid| { + buf.append(self.alloc, '_') catch break :blk "tuple"; + buf.appendSlice(self.alloc, self.mangleTypeName(fid)) catch break :blk "tuple"; + } + break :blk buf.items; + }, else => @tagName(info), }; } + /// Collect impl entries visible from `current_source_file` — defined in + /// the current file or in any module the current file transitively + /// imports. Falls open (returns all entries) when the source-file + /// context or import graph isn't wired (e.g. comptime callers). + fn findVisibleImpls(self: *Lowering, entries: []const ParamImplEntry, out: *std.ArrayList(ParamImplEntry)) void { + const here = self.current_source_file orelse { + out.appendSlice(self.alloc, entries) catch {}; + return; + }; + const graph = self.import_graph orelse { + out.appendSlice(self.alloc, entries) catch {}; + return; + }; + + // BFS over the import graph to compute the visible set. + var visible = std.StringHashMap(void).init(self.alloc); + defer visible.deinit(); + visible.put(here, {}) catch {}; + var queue = std.ArrayList([]const u8).empty; + defer queue.deinit(self.alloc); + queue.append(self.alloc, here) catch {}; + var head: usize = 0; + while (head < queue.items.len) : (head += 1) { + const node = queue.items[head]; + const direct = graph.get(node) orelse continue; + var it = direct.iterator(); + while (it.next()) |kv| { + const next = kv.key_ptr.*; + if (visible.contains(next)) continue; + visible.put(next, {}) catch {}; + queue.append(self.alloc, next) catch {}; + } + } + + for (entries) |e| { + if (visible.contains(e.defining_module)) { + out.append(self.alloc, e) catch {}; + } + } + } + + fn mangleParamList(self: *Lowering, prefix: []const u8, params: []const TypeId, ret: TypeId) []const u8 { + var buf = std.ArrayList(u8).empty; + buf.appendSlice(self.alloc, prefix) catch return prefix; + for (params) |p| { + buf.append(self.alloc, '_') catch return prefix; + buf.appendSlice(self.alloc, self.mangleTypeName(p)) catch return prefix; + } + buf.appendSlice(self.alloc, "__") catch return prefix; + buf.appendSlice(self.alloc, self.mangleTypeName(ret)) catch return prefix; + return buf.items; + } + /// Resolve type category names (like "int", "struct", "float") to matching TypeId tag values. /// Returns a list of TypeId index values that match the category. fn resolveTypeCategoryTags(self: *Lowering, name: []const u8) []const u64 { @@ -6584,6 +6673,27 @@ pub const Lowering = struct { if (c.callee.data == .field_access) { const fa = c.callee.data.field_access; const obj_ty = self.inferExprType(fa.object); + // Protocol-typed receiver: look up the method on the protocol decl. The + // protocol's ProtocolMethodInfo.param_types already excludes self. + if (self.getProtocolInfo(obj_ty)) |proto_info| { + for (proto_info.methods) |m| { + if (std.mem.eql(u8, m.name, fa.field)) return m.param_types; + } + } + // Closure-typed struct field: `c.on(args)` lowers to call_closure on + // the field value. Pick up the callee's param types from the closure + // type so each arg gets the right target_type during lowering. + if (!obj_ty.isBuiltin()) { + const field_name_id = self.module.types.internString(fa.field); + const struct_fields = self.getStructFields(obj_ty); + for (struct_fields) |f| { + if (f.name == field_name_id and !f.ty.isBuiltin()) { + const fti = self.module.types.get(f.ty); + if (fti == .closure) return fti.closure.params; + if (fti == .function) return fti.function.params; + } + } + } if (self.getStructTypeName(obj_ty)) |sname| { const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ sname, fa.field }) catch return &.{}; // Try already-lowered functions first @@ -7498,6 +7608,16 @@ pub const Lowering = struct { /// Non-inline protocols: { ctx: *void, __vtable: *void } /// Also stores protocol info for dispatch and vtable struct type for vtable protocols. fn registerProtocolDecl(self: *Lowering, pd: *const ast.ProtocolDecl) void { + // Parameterised protocols are compile-time-only — no vtable, no boxed + // instance struct. Methods reference unbound type params (e.g. + // `convert :: () -> Target`) that only get a concrete TypeId per + // (Source, Target) pair at xx resolution time. Stash the AST so + // `param_impl_map` lookup can resolve method signatures lazily. + if (pd.type_params.len > 0) { + self.protocol_ast_map.put(pd.name, pd) catch {}; + return; + } + const table = &self.module.types; const name_id = table.internString(pd.name); @@ -7596,7 +7716,15 @@ pub const Lowering = struct { } /// Register an impl block: register its methods as TypeName.method in fn_ast_map. - fn registerImplBlock(self: *Lowering, ib: *const ast.ImplBlock, is_imported: bool) void { + fn registerImplBlock(self: *Lowering, ib: *const ast.ImplBlock, is_imported: bool, decl: *const Node) void { + // Parameterised-protocol impl (e.g. `impl Into(Block) for Closure() -> void`): + // record into `param_impl_map` for compile-time resolution by `lowerXX`. + // Methods are NOT registered in fn_ast_map — they're monomorphised lazily + // per (Source, Target) pair at the xx call site. + if (ib.protocol_type_args.len > 0) { + self.registerParamImpl(ib, decl); + return; + } // Collect explicitly implemented method names var impl_methods = std.StringHashMap(void).init(self.alloc); defer impl_methods.deinit(); @@ -7625,6 +7753,80 @@ pub const Lowering = struct { } } + /// Register a parameterised-protocol impl into `param_impl_map`. + /// Resolves the protocol's type args + the source type, mangles them, and + /// stashes the impl's method fn_decls for later monomorphisation by + /// `lowerXX`. Same-module duplicate impls produce a diagnostic here; + /// cross-module duplicates are detected at the xx resolution site. + fn registerParamImpl(self: *Lowering, ib: *const ast.ImplBlock, decl: *const Node) void { + const table = &self.module.types; + + // Resolve the protocol's type-arg list to concrete TypeIds. + var arg_tys = std.ArrayList(TypeId).empty; + for (ib.protocol_type_args) |arg_node| { + const t = type_bridge.resolveAstType(arg_node, table); + arg_tys.append(self.alloc, t) catch return; + } + + // Resolve the source type. Parser stores it on `target_type_expr` for + // parameterised impls (back-compat `target_type` string is kept for + // simple cases but the canonical form is the TypeExpr). + const src_ty: TypeId = if (ib.target_type_expr) |te| + type_bridge.resolveAstType(te, table) + else if (ib.target_type.len > 0) + type_bridge.resolveAstType(&.{ .span = decl.span, .data = .{ .type_expr = .{ .name = ib.target_type } } }, table) + else + return; + + // Mangle into the lookup key. + var key_buf = std.ArrayList(u8).empty; + key_buf.appendSlice(self.alloc, ib.protocol_name) catch return; + for (arg_tys.items) |t| { + key_buf.append(self.alloc, 0) catch return; + key_buf.appendSlice(self.alloc, self.mangleTypeName(t)) catch return; + } + key_buf.append(self.alloc, 0) catch return; + key_buf.appendSlice(self.alloc, self.mangleTypeName(src_ty)) catch return; + const key = key_buf.items; + + // Collect method fn_decl pointers. + var methods = std.ArrayList(*const ast.FnDecl).empty; + for (ib.methods) |method_node| { + if (method_node.data == .fn_decl) { + methods.append(self.alloc, &method_node.data.fn_decl) catch {}; + } + } + + const defining_module: []const u8 = self.current_source_file orelse ""; + const entry: ParamImplEntry = .{ + .methods = self.alloc.dupe(*const ast.FnDecl, methods.items) catch return, + .source_ty = src_ty, + .target_args = self.alloc.dupe(TypeId, arg_tys.items) catch return, + .defining_module = defining_module, + .span = decl.span, + }; + + const gop = self.param_impl_map.getOrPut(key) catch return; + if (!gop.found_existing) { + gop.value_ptr.* = std.ArrayList(ParamImplEntry).empty; + } else { + // Same-file duplicate is an immediate error. Cross-file overlaps + // are deferred to the xx resolution site (Phase 5) so the impl + // surface can be richer than any one file's view. + for (gop.value_ptr.items) |existing| { + if (std.mem.eql(u8, existing.defining_module, defining_module)) { + if (self.diagnostics) |diags| { + diags.addFmt(.err, decl.span, "duplicate impl '{s}' for source '{s}' in {s}", .{ + ib.protocol_name, self.mangleTypeName(src_ty), defining_module, + }); + } + return; + } + } + } + gop.value_ptr.append(self.alloc, entry) catch return; + } + /// Synthesize a fn_decl from a protocol default method for a concrete type. fn synthesizeDefaultMethod(self: *Lowering, method: ast.ProtocolMethodDecl, target_type: []const u8) *const ast.FnDecl { // Build parameter list: self: *TargetType, then the protocol method params @@ -7957,15 +8159,15 @@ pub const Lowering = struct { const raw_result = self.builder.emit(.{ .call_indirect = .{ .callee = fn_ptr, .args = owned } }, mi.ret_type); // If protocol method returns *void (Self) and the caller expects a value type, - // unbox: load the concrete value from the returned pointer - if (mi.ret_type != .void) { - const ret_info = self.module.types.get(mi.ret_type); - if (ret_info == .pointer) { - if (self.target_type) |target| { - const target_info = self.module.types.get(target); - if (target_info != .pointer) { - return self.builder.load(raw_result, target); - } + // unbox: load the concrete value from the returned pointer. Real pointer + // returns (declared `-> *T` for non-Self T) are NOT auto-loaded — the + // pointee may be a single byte and reading `sizeof(target)` past it + // segfaults. Self is encoded as `*void`, so test against that exact type. + if (mi.ret_type == void_ptr) { + if (self.target_type) |target| { + const target_info = self.module.types.get(target); + if (target_info != .pointer) { + return self.builder.load(raw_result, target); } } } @@ -8334,7 +8536,13 @@ pub const Lowering = struct { /// - int → int: widen/narrow /// - int ↔ float: int_to_float/float_to_int fn lowerXX(self: *Lowering, operand: Ref, operand_node: *const Node) Ref { - const src_ty = self.inferExprType(operand_node); + // Use the operand's *actual* lowered Ref type rather than reaching + // back through inferExprType — the latter doesn't cover every + // expression shape (notably lambdas), and a wrong src_ty here can + // route the cast through coerceToType (e.g. a bogus s64→ptr bitcast) + // and silently skip the user-space Into fallback. + const src_ty = self.builder.getRefType(operand); + const target_explicit = self.target_type != null; const dst_ty = self.target_type orelse .s64; // Any → concrete type: unbox @@ -8377,7 +8585,140 @@ pub const Lowering = struct { return self.buildProtocolErasure(operand, operand_node, src_ty, dst_ty); } - return self.coerceToType(operand, src_ty, dst_ty); + const result = self.coerceToType(operand, src_ty, dst_ty); + + // User-space fallback via `impl Into(Target) for Source`. Only fires + // when the target was explicitly named (not the .s64 default), src and + // dst differ, and the built-in ladder made no progress. Built-ins + // always win. + if (target_explicit and src_ty != dst_ty and result == operand) { + if (self.tryUserConversion(operand, operand_node, src_ty, dst_ty)) |converted| { + return converted; + } + // Pointer-target fallback: `xx ` whose surrounding context + // expects `*T` (a fn arg slot, a var typed as a pointer-to-aggregate) + // can be satisfied by `impl Into(T) for src` plus an implicit + // alloca+store on the result. Lets users write + // `fn(xx () => { ... })` instead of materialising a named Block local + // just to take its address. + if (!dst_ty.isBuiltin()) { + const dst_info = self.module.types.get(dst_ty); + if (dst_info == .pointer) { + const pointee = dst_info.pointer.pointee; + if (pointee != src_ty) { + if (self.tryUserConversion(operand, operand_node, src_ty, pointee)) |converted| { + const slot = self.builder.alloca(pointee); + self.builder.store(slot, converted); + return slot; + } + } + } + } + } + return result; + } + + /// Look up `Into(dst_ty)` impl for `src_ty` and, if found, monomorphise + /// the impl's `convert` method and emit a direct call. Returns null when + /// no impl matches (caller falls back to the built-in result, which is + /// the unchanged operand — Phase 3 emits no diagnostic for v0). + fn tryUserConversion(self: *Lowering, operand: Ref, operand_node: *const Node, src_ty: TypeId, dst_ty: TypeId) ?Ref { + // Reentrancy guard — pack (src, dst) into a u64. + const guard_key: u64 = (@as(u64, src_ty.index()) << 32) | @as(u64, dst_ty.index()); + if (self.xx_reentrancy.contains(guard_key)) { + if (self.diagnostics) |diags| { + diags.addFmt(.err, operand_node.span, "recursive xx conversion from '{s}' to '{s}'", .{ + self.mangleTypeName(src_ty), self.mangleTypeName(dst_ty), + }); + } + return operand; + } + + // Build lookup key: "Into\x00\x00". + // Hardcoded to the "Into" protocol for v1. Generalising to other + // parameterised protocols would walk protocol_decl_map looking for + // protocols that take a single type-param and have a `convert` method. + const proto_name = "Into"; + const pd = self.protocol_ast_map.get(proto_name) orelse return null; + if (pd.type_params.len != 1) return null; + + var key_buf = std.ArrayList(u8).empty; + key_buf.appendSlice(self.alloc, proto_name) catch return null; + key_buf.append(self.alloc, 0) catch return null; + key_buf.appendSlice(self.alloc, self.mangleTypeName(dst_ty)) catch return null; + key_buf.append(self.alloc, 0) catch return null; + key_buf.appendSlice(self.alloc, self.mangleTypeName(src_ty)) catch return null; + const key = key_buf.items; + + const entries = self.param_impl_map.get(key) orelse return null; + if (entries.items.len == 0) return null; + + // Filter by import visibility: only impls in modules that the current + // file transitively imports (or the current file itself) are reachable. + // Falls open when import_graph isn't wired (e.g. comptime callers). + var visible_impls = std.ArrayList(ParamImplEntry).empty; + defer visible_impls.deinit(self.alloc); + self.findVisibleImpls(entries.items, &visible_impls); + + if (visible_impls.items.len == 0) { + if (self.diagnostics) |diags| { + const saved = diags.current_source_file; + diags.current_source_file = operand_node.source_file orelse self.current_source_file; + defer diags.current_source_file = saved; + diags.addFmt(.err, operand_node.span, "no visible xx conversion from '{s}' to '{s}' — impl exists in another module but is not imported", .{ + self.mangleTypeName(src_ty), self.mangleTypeName(dst_ty), + }); + } + return operand; + } + if (visible_impls.items.len > 1) { + if (self.diagnostics) |diags| { + const saved = diags.current_source_file; + diags.current_source_file = operand_node.source_file orelse self.current_source_file; + defer diags.current_source_file = saved; + diags.addFmt(.err, operand_node.span, "duplicate xx conversion from '{s}' to '{s}': impls in {s} and {s}", .{ + self.mangleTypeName(src_ty), self.mangleTypeName(dst_ty), + visible_impls.items[0].defining_module, visible_impls.items[1].defining_module, + }); + } + return operand; + } + const entry = visible_impls.items[0]; + + // Find the `convert` method on this impl. + var convert_fd: ?*const ast.FnDecl = null; + for (entry.methods) |m| { + if (std.mem.eql(u8, m.name, "convert")) { + convert_fd = m; + break; + } + } + const fd = convert_fd orelse return null; + + // Bind Target → dst_ty. + var bindings = std.StringHashMap(TypeId).init(self.alloc); + defer bindings.deinit(); + bindings.put(pd.type_params[0].name, dst_ty) catch return null; + + // Mangled name: ".convert__". + const mangled = std.fmt.allocPrint(self.alloc, "{s}.convert__{s}", .{ + self.mangleTypeName(src_ty), self.mangleTypeName(dst_ty), + }) catch return null; + + self.xx_reentrancy.put(guard_key, {}) catch {}; + defer _ = self.xx_reentrancy.remove(guard_key); + + if (!self.lowered_functions.contains(mangled)) { + self.monomorphizeFunction(fd, mangled, &bindings); + } + + const fid = self.resolveFuncByName(mangled) orelse return null; + const func = &self.module.functions.items[@intFromEnum(fid)]; + const ret_ty = func.ret; + const params = func.params; + var args = [_]Ref{operand}; + self.coerceCallArgs(args[0..], params); + return self.builder.call(fid, args[0..], ret_ty); } /// Build a protocol value from a concrete value via xx conversion. diff --git a/src/parser.zig b/src/parser.zig index 1c925eb..86511d3 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -362,7 +362,32 @@ pub const Parser = struct { return try self.createNode(start_pos, .{ .var_decl = .{ .name = name, .type_annotation = type_node, .value = null } }); } - return self.fail("expected ':', '=' or ';' after type annotation"); + if (self.current.tag == .hash_foreign) { + // name : type #foreign [lib] ["c_name"]; (extern global from libsystem etc.) + self.advance(); + var lib_ref: ?[]const u8 = null; + if (self.current.tag == .identifier) { + lib_ref = self.tokenSlice(self.current); + self.advance(); + } + var c_name: ?[]const u8 = null; + if (self.current.tag == .string_literal) { + const raw = self.tokenSlice(self.current); + c_name = raw[1 .. raw.len - 1]; + self.advance(); + } + try self.expect(.semicolon); + return try self.createNode(start_pos, .{ .var_decl = .{ + .name = name, + .type_annotation = type_node, + .value = null, + .is_foreign = true, + .foreign_lib = lib_ref, + .foreign_name = c_name, + } }); + } + + return self.fail("expected ':', '=', ';' or '#foreign' after type annotation"); } fn parseTypeExpr(self: *Parser) anyerror!*Node { @@ -890,6 +915,29 @@ pub const Parser = struct { fn parseProtocolDecl(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node { self.advance(); // skip 'protocol' + // Optional type params: protocol(Target: Type, U: Type) { ... } + // Names are introduced without a `$` sigil (unlike struct's $T) because + // the parens after `protocol` already mark this as a parameter list. + var type_params = std.ArrayList(ast.StructTypeParam).empty; + if (self.current.tag == .l_paren) { + self.advance(); // skip '(' + while (self.current.tag != .r_paren and self.current.tag != .eof) { + if (type_params.items.len > 0) { + try self.expect(.comma); + if (self.current.tag == .r_paren) break; + } + if (self.current.tag != .identifier) { + return self.fail("expected type parameter name in protocol header"); + } + const param_name = self.tokenSlice(self.current); + self.advance(); + try self.expect(.colon); + const constraint = try self.parseTypeExpr(); + try type_params.append(self.allocator, .{ .name = param_name, .constraint = constraint }); + } + try self.expect(.r_paren); + } + // Check for #inline var is_inline = false; if (self.current.tag == .hash_inline) { @@ -899,6 +947,14 @@ pub const Parser = struct { try self.expect(.l_brace); + // Push type-param names into scope so method signatures can refer to them + // bare (e.g. `convert :: () -> Target` resolves Target as a generic type expr). + var tp_names = std.ArrayList([]const u8).empty; + for (type_params.items) |tp| try tp_names.append(self.allocator, tp.name); + const saved_struct_type_params = self.struct_type_params; + self.struct_type_params = tp_names.items; + defer self.struct_type_params = saved_struct_type_params; + var methods = std.ArrayList(ast.ProtocolMethodDecl).empty; while (self.current.tag != .r_brace and self.current.tag != .eof) { @@ -962,6 +1018,7 @@ pub const Parser = struct { .name = name, .methods = try methods.toOwnedSlice(self.allocator), .is_inline = is_inline, + .type_params = try type_params.toOwnedSlice(self.allocator), } }); } @@ -975,39 +1032,71 @@ pub const Parser = struct { const protocol_name = self.tokenSlice(self.current); self.advance(); + // Optional protocol type args: impl Into(Block) for ... + var protocol_type_args = std.ArrayList(*Node).empty; + if (self.current.tag == .l_paren) { + self.advance(); // skip '(' + while (self.current.tag != .r_paren and self.current.tag != .eof) { + if (protocol_type_args.items.len > 0) { + try self.expect(.comma); + if (self.current.tag == .r_paren) break; + } + try protocol_type_args.append(self.allocator, try self.parseTypeExpr()); + } + try self.expect(.r_paren); + } + // 'for' — note: 'for' is a keyword (kw_for), not an identifier if (self.current.tag != .kw_for) { return self.fail("expected 'for' after protocol name in impl block"); } self.advance(); - // Target type name (identifiers like s64, or keywords like f32/f64) - if (self.current.tag != .identifier and !self.current.tag.isTypeKeyword()) { - return self.fail("expected type name after 'for'"); - } - const target_type = self.tokenSlice(self.current); - self.advance(); - - // Optional type params: impl Protocol for List($T) + // Source-type spelling. For parameterised protocols we accept any TypeExpr + // (`Closure(...) -> R`, `*T`, etc.). For nullary protocols we keep the + // legacy identifier-only path so existing `impl P for SomeStruct` keeps + // working unchanged (the parser doesn't try to over-parse trailing tokens). + var target_type: []const u8 = ""; + var target_type_expr: ?*Node = null; var target_type_params = std.ArrayList(ast.StructTypeParam).empty; - if (self.current.tag == .l_paren) { - self.advance(); // skip '(' - while (self.current.tag != .r_paren and self.current.tag != .eof) { - if (target_type_params.items.len > 0) { - try self.expect(.comma); - if (self.current.tag == .r_paren) break; - } - try self.expect(.dollar); - if (self.current.tag != .identifier) { - return self.fail("expected type parameter name after '$'"); - } - const param_name = self.tokenSlice(self.current); - self.advance(); - // Optional constraint — for now just use Type - const constraint = try self.createNode(self.current.loc.start, .{ .type_expr = .{ .name = "Type" } }); - try target_type_params.append(self.allocator, .{ .name = param_name, .constraint = constraint }); + + if (protocol_type_args.items.len > 0) { + // Parameterised protocol — source is a general TypeExpr. + target_type_expr = try self.parseTypeExpr(); + // Synthesize a string view of the source for back-compat consumers + // (LSP hover, etc.). The semantic key for the impl map uses + // structural mangling, not this string. + if (target_type_expr.?.data == .type_expr) { + target_type = target_type_expr.?.data.type_expr.name; + } + } else { + // Legacy nullary-protocol path: single identifier source. + if (self.current.tag != .identifier and !self.current.tag.isTypeKeyword()) { + return self.fail("expected type name after 'for'"); + } + target_type = self.tokenSlice(self.current); + self.advance(); + + // Optional type params: impl Protocol for List($T) + if (self.current.tag == .l_paren) { + self.advance(); // skip '(' + while (self.current.tag != .r_paren and self.current.tag != .eof) { + if (target_type_params.items.len > 0) { + try self.expect(.comma); + if (self.current.tag == .r_paren) break; + } + try self.expect(.dollar); + if (self.current.tag != .identifier) { + return self.fail("expected type parameter name after '$'"); + } + const param_name = self.tokenSlice(self.current); + self.advance(); + // Optional constraint — for now just use Type + const constraint = try self.createNode(self.current.loc.start, .{ .type_expr = .{ .name = "Type" } }); + try target_type_params.append(self.allocator, .{ .name = param_name, .constraint = constraint }); + } + try self.expect(.r_paren); } - try self.expect(.r_paren); } try self.expect(.l_brace); @@ -1045,6 +1134,8 @@ pub const Parser = struct { .target_type = target_type, .target_type_params = try target_type_params.toOwnedSlice(self.allocator), .methods = try methods.toOwnedSlice(self.allocator), + .protocol_type_args = try protocol_type_args.toOwnedSlice(self.allocator), + .target_type_expr = target_type_expr, } }); } diff --git a/tests/expected/88-enum-through-protocol-dispatch.exit b/tests/expected/88-enum-through-protocol-dispatch.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/expected/88-enum-through-protocol-dispatch.exit @@ -0,0 +1 @@ +0 diff --git a/tests/expected/88-enum-through-protocol-dispatch.txt b/tests/expected/88-enum-through-protocol-dispatch.txt new file mode 100644 index 0000000..c9fd584 --- /dev/null +++ b/tests/expected/88-enum-through-protocol-dispatch.txt @@ -0,0 +1,3 @@ +direct a=0 b=1 +proto f = 1 +proto f = 0 diff --git a/tests/expected/89-enum-arg-through-closure-field.exit b/tests/expected/89-enum-arg-through-closure-field.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/expected/89-enum-arg-through-closure-field.exit @@ -0,0 +1 @@ +0 diff --git a/tests/expected/89-enum-arg-through-closure-field.txt b/tests/expected/89-enum-arg-through-closure-field.txt new file mode 100644 index 0000000..0526a3b --- /dev/null +++ b/tests/expected/89-enum-arg-through-closure-field.txt @@ -0,0 +1,2 @@ +cl f = 1 +cl f = 0 diff --git a/tests/expected/90-protocol-real-pointer-return.exit b/tests/expected/90-protocol-real-pointer-return.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/expected/90-protocol-real-pointer-return.exit @@ -0,0 +1 @@ +0 diff --git a/tests/expected/90-protocol-real-pointer-return.txt b/tests/expected/90-protocol-real-pointer-return.txt new file mode 100644 index 0000000..743eb98 --- /dev/null +++ b/tests/expected/90-protocol-real-pointer-return.txt @@ -0,0 +1 @@ +got pointer: true diff --git a/tests/expected/91-protocol-typeparam-parse.exit b/tests/expected/91-protocol-typeparam-parse.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/expected/91-protocol-typeparam-parse.exit @@ -0,0 +1 @@ +0 diff --git a/tests/expected/91-protocol-typeparam-parse.txt b/tests/expected/91-protocol-typeparam-parse.txt new file mode 100644 index 0000000..9766475 --- /dev/null +++ b/tests/expected/91-protocol-typeparam-parse.txt @@ -0,0 +1 @@ +ok diff --git a/tests/expected/92-xx-userspace.exit b/tests/expected/92-xx-userspace.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/expected/92-xx-userspace.exit @@ -0,0 +1 @@ +0 diff --git a/tests/expected/92-xx-userspace.txt b/tests/expected/92-xx-userspace.txt new file mode 100644 index 0000000..7ce4657 --- /dev/null +++ b/tests/expected/92-xx-userspace.txt @@ -0,0 +1 @@ +tag = 42 diff --git a/tests/expected/93-into-import-scope.exit b/tests/expected/93-into-import-scope.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/expected/93-into-import-scope.exit @@ -0,0 +1 @@ +0 diff --git a/tests/expected/93-into-import-scope.txt b/tests/expected/93-into-import-scope.txt new file mode 100644 index 0000000..307f281 --- /dev/null +++ b/tests/expected/93-into-import-scope.txt @@ -0,0 +1 @@ +w.v = 30 diff --git a/tests/expected/94-foreign-global.exit b/tests/expected/94-foreign-global.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/expected/94-foreign-global.exit @@ -0,0 +1 @@ +0 diff --git a/tests/expected/94-foreign-global.txt b/tests/expected/94-foreign-global.txt new file mode 100644 index 0000000..bd1a7bc --- /dev/null +++ b/tests/expected/94-foreign-global.txt @@ -0,0 +1 @@ +stdin extern global non-null: true diff --git a/tests/expected/95-objc-block-noop.exit b/tests/expected/95-objc-block-noop.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/expected/95-objc-block-noop.exit @@ -0,0 +1 @@ +0 diff --git a/tests/expected/95-objc-block-noop.txt b/tests/expected/95-objc-block-noop.txt new file mode 100644 index 0000000..e4ddbea --- /dev/null +++ b/tests/expected/95-objc-block-noop.txt @@ -0,0 +1 @@ +noop block ran diff --git a/tests/expected/96-objc-block-capture.exit b/tests/expected/96-objc-block-capture.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/expected/96-objc-block-capture.exit @@ -0,0 +1 @@ +0 diff --git a/tests/expected/96-objc-block-capture.txt b/tests/expected/96-objc-block-capture.txt new file mode 100644 index 0000000..9cc01d6 --- /dev/null +++ b/tests/expected/96-objc-block-capture.txt @@ -0,0 +1 @@ +x + y = 142 diff --git a/tests/expected/97-objc-block-inline.exit b/tests/expected/97-objc-block-inline.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/expected/97-objc-block-inline.exit @@ -0,0 +1 @@ +0 diff --git a/tests/expected/97-objc-block-inline.txt b/tests/expected/97-objc-block-inline.txt new file mode 100644 index 0000000..ffb9a74 --- /dev/null +++ b/tests/expected/97-objc-block-inline.txt @@ -0,0 +1 @@ +inline block, x = 7 diff --git a/tests/expected/issue-0032.exit b/tests/expected/issue-0032.exit new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/tests/expected/issue-0032.exit @@ -0,0 +1 @@ +1 diff --git a/tests/expected/issue-0032.txt b/tests/expected/issue-0032.txt new file mode 100644 index 0000000..67c6eca --- /dev/null +++ b/tests/expected/issue-0032.txt @@ -0,0 +1 @@ +/Users/agra/projects/sx/examples/issue-0032.sx:13:1: error: duplicate impl 'Into' for source 's64' in /Users/agra/projects/sx/examples/issue-0032.sx diff --git a/tests/expected/issue-0033.exit b/tests/expected/issue-0033.exit new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/tests/expected/issue-0033.exit @@ -0,0 +1 @@ +1 diff --git a/tests/expected/issue-0033.txt b/tests/expected/issue-0033.txt new file mode 100644 index 0000000..efdfb76 --- /dev/null +++ b/tests/expected/issue-0033.txt @@ -0,0 +1 @@ +/Users/agra/projects/sx/examples/./issue-0033-user.sx:7:17: error: no visible xx conversion from 's64' to 'Wrap' — impl exists in another module but is not imported diff --git a/tests/expected/issue-0034.exit b/tests/expected/issue-0034.exit new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/tests/expected/issue-0034.exit @@ -0,0 +1 @@ +1 diff --git a/tests/expected/issue-0034.txt b/tests/expected/issue-0034.txt new file mode 100644 index 0000000..dbd8bc1 --- /dev/null +++ b/tests/expected/issue-0034.txt @@ -0,0 +1 @@ +/Users/agra/projects/sx/examples/issue-0034.sx:11:17: error: duplicate xx conversion from 's64' to 'Wrap': impls in /Users/agra/projects/sx/examples/./issue-0034-impl-a.sx and /Users/agra/projects/sx/examples/./issue-0034-impl-b.sx diff --git a/wasm_check.html b/wasm_check.html new file mode 100644 index 0000000..366cc05 --- /dev/null +++ b/wasm_check.html @@ -0,0 +1,50 @@ + + + + + +sx + + + +
+
+
Loading…
+
+ + + + + + diff --git a/wasm_check.js b/wasm_check.js new file mode 100644 index 0000000..8e22728 --- /dev/null +++ b/wasm_check.js @@ -0,0 +1,1765 @@ +// include: shell.js +// include: minimum_runtime_check.js +(function() { + // "30.0.0" -> 300000 + function humanReadableVersionToPacked(str) { + str = str.split('-')[0]; // Remove any trailing part from e.g. "12.53.3-alpha" + var vers = str.split('.').slice(0, 3); + while(vers.length < 3) vers.push('00'); + vers = vers.map((n, i, arr) => n.padStart(2, '0')); + return vers.join(''); + } + // 300000 -> "30.0.0" + var packedVersionToHumanReadable = n => [n / 10000 | 0, (n / 100 | 0) % 100, n % 100].join('.'); + + var TARGET_NOT_SUPPORTED = 2147483647; + + // Note: We use a typeof check here instead of optional chaining using + // globalThis because older browsers might not have globalThis defined. + var currentNodeVersion = typeof process !== 'undefined' && process.versions?.node ? humanReadableVersionToPacked(process.versions.node) : TARGET_NOT_SUPPORTED; + if (currentNodeVersion < 160000) { + throw new Error(`This emscripten-generated code requires node v${ packedVersionToHumanReadable(160000) } (detected v${packedVersionToHumanReadable(currentNodeVersion)})`); + } + + var userAgent = typeof navigator !== 'undefined' && navigator.userAgent; + if (!userAgent) { + return; + } + + var currentSafariVersion = userAgent.includes("Safari/") && !userAgent.includes("Chrome/") && userAgent.match(/Version\/(\d+\.?\d*\.?\d*)/) ? humanReadableVersionToPacked(userAgent.match(/Version\/(\d+\.?\d*\.?\d*)/)[1]) : TARGET_NOT_SUPPORTED; + if (currentSafariVersion < 150000) { + throw new Error(`This emscripten-generated code requires Safari v${ packedVersionToHumanReadable(150000) } (detected v${currentSafariVersion})`); + } + + var currentFirefoxVersion = userAgent.match(/Firefox\/(\d+(?:\.\d+)?)/) ? parseFloat(userAgent.match(/Firefox\/(\d+(?:\.\d+)?)/)[1]) : TARGET_NOT_SUPPORTED; + if (currentFirefoxVersion < 79) { + throw new Error(`This emscripten-generated code requires Firefox v79 (detected v${currentFirefoxVersion})`); + } + + var currentChromeVersion = userAgent.match(/Chrome\/(\d+(?:\.\d+)?)/) ? parseFloat(userAgent.match(/Chrome\/(\d+(?:\.\d+)?)/)[1]) : TARGET_NOT_SUPPORTED; + if (currentChromeVersion < 85) { + throw new Error(`This emscripten-generated code requires Chrome v85 (detected v${currentChromeVersion})`); + } +})(); + +// end include: minimum_runtime_check.js +// The Module object: Our interface to the outside world. We import +// and export values on it. There are various ways Module can be used: +// 1. Not defined. We create it here +// 2. A function parameter, function(moduleArg) => Promise +// 3. pre-run appended it, var Module = {}; ..generated code.. +// 4. External script tag defines var Module. +// We need to check if Module already exists (e.g. case 3 above). +// Substitution will be replaced with actual code on later stage of the build, +// this way Closure Compiler will not mangle it (e.g. case 4. above). +// Note that if you want to run closure, and also to use Module +// after the generated code, you will need to define var Module = {}; +// before the code. Then that object will be used in the code, and you +// can continue to use Module afterwards as well. +var Module = typeof Module != 'undefined' ? Module : {}; + +// Determine the runtime environment we are in. You can customize this by +// setting the ENVIRONMENT setting at compile time (see settings.js). + +// Attempt to auto-detect the environment +var ENVIRONMENT_IS_WEB = !!globalThis.window; +var ENVIRONMENT_IS_WORKER = !!globalThis.WorkerGlobalScope; +// N.b. Electron.js environment is simultaneously a NODE-environment, but +// also a web environment. +var ENVIRONMENT_IS_NODE = globalThis.process?.versions?.node && globalThis.process?.type != 'renderer'; +var ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER; + +// --pre-jses are emitted after the Module integration code, so that they can +// refer to Module (if they choose; they can also define Module) + + +var arguments_ = []; +var thisProgram = './this.program'; +var quit_ = (status, toThrow) => { + throw toThrow; +}; + +// In MODULARIZE mode _scriptName needs to be captured already at the very top of the page immediately when the page is parsed, so it is generated there +// before the page load. In non-MODULARIZE modes generate it here. +var _scriptName = globalThis.document?.currentScript?.src; + +if (typeof __filename != 'undefined') { // Node + _scriptName = __filename; +} else +if (ENVIRONMENT_IS_WORKER) { + _scriptName = self.location.href; +} + +// `/` should be present at the end if `scriptDirectory` is not empty +var scriptDirectory = ''; +function locateFile(path) { + if (Module['locateFile']) { + return Module['locateFile'](path, scriptDirectory); + } + return scriptDirectory + path; +} + +// Hooks that are implemented differently in different runtime environments. +var readAsync, readBinary; + +if (ENVIRONMENT_IS_NODE) { + const isNode = globalThis.process?.versions?.node && globalThis.process?.type != 'renderer'; + if (!isNode) throw new Error('not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)'); + + // These modules will usually be used on Node.js. Load them eagerly to avoid + // the complexity of lazy-loading. + var fs = require('node:fs'); + + scriptDirectory = __dirname + '/'; + +// include: node_shell_read.js +readBinary = (filename) => { + // We need to re-wrap `file://` strings to URLs. + filename = isFileURI(filename) ? new URL(filename) : filename; + var ret = fs.readFileSync(filename); + assert(Buffer.isBuffer(ret)); + return ret; +}; + +readAsync = async (filename, binary = true) => { + // See the comment in the `readBinary` function. + filename = isFileURI(filename) ? new URL(filename) : filename; + var ret = fs.readFileSync(filename, binary ? undefined : 'utf8'); + assert(binary ? Buffer.isBuffer(ret) : typeof ret == 'string'); + return ret; +}; +// end include: node_shell_read.js + if (process.argv.length > 1) { + thisProgram = process.argv[1].replace(/\\/g, '/'); + } + + arguments_ = process.argv.slice(2); + + // MODULARIZE will export the module in the proper place outside, we don't need to export here + if (typeof module != 'undefined') { + module['exports'] = Module; + } + + quit_ = (status, toThrow) => { + process.exitCode = status; + throw toThrow; + }; + +} else +if (ENVIRONMENT_IS_SHELL) { + +} else + +// Note that this includes Node.js workers when relevant (pthreads is enabled). +// Node.js workers are detected as a combination of ENVIRONMENT_IS_WORKER and +// ENVIRONMENT_IS_NODE. +if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) { + try { + scriptDirectory = new URL('.', _scriptName).href; // includes trailing slash + } catch { + // Must be a `blob:` or `data:` URL (e.g. `blob:http://site.com/etc/etc`), we cannot + // infer anything from them. + } + + if (!(globalThis.window || globalThis.WorkerGlobalScope)) throw new Error('not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)'); + + { +// include: web_or_worker_shell_read.js +if (ENVIRONMENT_IS_WORKER) { + readBinary = (url) => { + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, false); + xhr.responseType = 'arraybuffer'; + xhr.send(null); + return new Uint8Array(/** @type{!ArrayBuffer} */(xhr.response)); + }; + } + + readAsync = async (url) => { + // Fetch has some additional restrictions over XHR, like it can't be used on a file:// url. + // See https://github.com/github/fetch/pull/92#issuecomment-140665932 + // Cordova or Electron apps are typically loaded from a file:// url. + // So use XHR on webview if URL is a file URL. + if (isFileURI(url)) { + return new Promise((resolve, reject) => { + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, true); + xhr.responseType = 'arraybuffer'; + xhr.onload = () => { + if (xhr.status == 200 || (xhr.status == 0 && xhr.response)) { // file URLs can return 0 + resolve(xhr.response); + return; + } + reject(xhr.status); + }; + xhr.onerror = reject; + xhr.send(null); + }); + } + var response = await fetch(url, { credentials: 'same-origin' }); + if (response.ok) { + return response.arrayBuffer(); + } + throw new Error(response.status + ' : ' + response.url); + }; +// end include: web_or_worker_shell_read.js + } +} else +{ + throw new Error('environment detection error'); +} + +var out = console.log.bind(console); +var err = console.error.bind(console); + +var IDBFS = 'IDBFS is no longer included by default; build with -lidbfs.js'; +var PROXYFS = 'PROXYFS is no longer included by default; build with -lproxyfs.js'; +var WORKERFS = 'WORKERFS is no longer included by default; build with -lworkerfs.js'; +var FETCHFS = 'FETCHFS is no longer included by default; build with -lfetchfs.js'; +var ICASEFS = 'ICASEFS is no longer included by default; build with -licasefs.js'; +var JSFILEFS = 'JSFILEFS is no longer included by default; build with -ljsfilefs.js'; +var OPFS = 'OPFS is no longer included by default; build with -lopfs.js'; + +var NODEFS = 'NODEFS is no longer included by default; build with -lnodefs.js'; + +// perform assertions in shell.js after we set up out() and err(), as otherwise +// if an assertion fails it cannot print the message + +assert(!ENVIRONMENT_IS_SHELL, 'shell environment detected but not enabled at build time. Add `shell` to `-sENVIRONMENT` to enable.'); + +// end include: shell.js + +// include: preamble.js +// === Preamble library stuff === + +// Documentation for the public APIs defined in this file must be updated in: +// site/source/docs/api_reference/preamble.js.rst +// A prebuilt local version of the documentation is available at: +// site/build/text/docs/api_reference/preamble.js.txt +// You can also build docs locally as HTML or other formats in site/ +// An online HTML version (which may be of a different version of Emscripten) +// is up at http://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html + +var wasmBinary; + +if (!globalThis.WebAssembly) { + err('no native wasm support detected'); +} + +// Wasm globals + +//======================================== +// Runtime essentials +//======================================== + +// whether we are quitting the application. no code should run after this. +// set in exit() and abort() +var ABORT = false; + +// set by exit() and abort(). Passed to 'onExit' handler. +// NOTE: This is also used as the process return code in shell environments +// but only when noExitRuntime is false. +var EXITSTATUS; + +// In STRICT mode, we only define assert() when ASSERTIONS is set. i.e. we +// don't define it at all in release modes. This matches the behaviour of +// MINIMAL_RUNTIME. +// TODO(sbc): Make this the default even without STRICT enabled. +/** @type {function(*, string=)} */ +function assert(condition, text) { + if (!condition) { + abort('Assertion failed' + (text ? ': ' + text : '')); + } +} + +// We used to include malloc/free by default in the past. Show a helpful error in +// builds with assertions. +function _malloc() { + abort('malloc() called but not included in the build - add `_malloc` to EXPORTED_FUNCTIONS'); +} +function _free() { + // Show a helpful error since we used to include free by default in the past. + abort('free() called but not included in the build - add `_free` to EXPORTED_FUNCTIONS'); +} + +/** + * Indicates whether filename is delivered via file protocol (as opposed to http/https) + * @noinline + */ +var isFileURI = (filename) => filename.startsWith('file://'); + +// include: runtime_common.js +// include: runtime_stack_check.js +// Initializes the stack cookie. Called at the startup of main and at the startup of each thread in pthreads mode. +function writeStackCookie() { + var max = _emscripten_stack_get_end(); + assert((max & 3) == 0); + // If the stack ends at address zero we write our cookies 4 bytes into the + // stack. This prevents interference with SAFE_HEAP and ASAN which also + // monitor writes to address zero. + if (max == 0) { + max += 4; + } + // The stack grow downwards towards _emscripten_stack_get_end. + // We write cookies to the final two words in the stack and detect if they are + // ever overwritten. + HEAPU32[((max)>>2)] = 0x02135467; + HEAPU32[(((max)+(4))>>2)] = 0x89BACDFE; + // Also test the global address 0 for integrity. + HEAPU32[((0)>>2)] = 1668509029; +} + +function checkStackCookie() { + if (ABORT) return; + var max = _emscripten_stack_get_end(); + // See writeStackCookie(). + if (max == 0) { + max += 4; + } + var cookie1 = HEAPU32[((max)>>2)]; + var cookie2 = HEAPU32[(((max)+(4))>>2)]; + if (cookie1 != 0x02135467 || cookie2 != 0x89BACDFE) { + abort(`Stack overflow! Stack cookie has been overwritten at ${ptrToString(max)}, expected hex dwords 0x89BACDFE and 0x2135467, but received ${ptrToString(cookie2)} ${ptrToString(cookie1)}`); + } + // Also test the global address 0 for integrity. + if (HEAPU32[((0)>>2)] != 0x63736d65 /* 'emsc' */) { + abort('Runtime error: The application has corrupted its heap memory area (address zero)!'); + } +} +// end include: runtime_stack_check.js +// include: runtime_exceptions.js +// end include: runtime_exceptions.js +// include: runtime_debug.js +var runtimeDebug = true; // Switch to false at runtime to disable logging at the right times + +// Used by XXXXX_DEBUG settings to output debug messages. +function dbg(...args) { + if (!runtimeDebug && typeof runtimeDebug != 'undefined') return; + // TODO(sbc): Make this configurable somehow. Its not always convenient for + // logging to show up as warnings. + console.warn(...args); +} + +// Endianness check +(() => { + var h16 = new Int16Array(1); + var h8 = new Int8Array(h16.buffer); + h16[0] = 0x6373; + if (h8[0] !== 0x73 || h8[1] !== 0x63) abort('Runtime error: expected the system to be little-endian! (Run with -sSUPPORT_BIG_ENDIAN to bypass)'); +})(); + +function consumedModuleProp(prop) { + if (!Object.getOwnPropertyDescriptor(Module, prop)) { + Object.defineProperty(Module, prop, { + configurable: true, + set() { + abort(`Attempt to set \`Module.${prop}\` after it has already been processed. This can happen, for example, when code is injected via '--post-js' rather than '--pre-js'`); + + } + }); + } +} + +function makeInvalidEarlyAccess(name) { + return () => assert(false, `call to '${name}' via reference taken before Wasm module initialization`); + +} + +function ignoredModuleProp(prop) { + if (Object.getOwnPropertyDescriptor(Module, prop)) { + abort(`\`Module.${prop}\` was supplied but \`${prop}\` not included in INCOMING_MODULE_JS_API`); + } +} + +// forcing the filesystem exports a few things by default +function isExportedByForceFilesystem(name) { + return name === 'FS_createPath' || + name === 'FS_createDataFile' || + name === 'FS_createPreloadedFile' || + name === 'FS_preloadFile' || + name === 'FS_unlink' || + name === 'addRunDependency' || + // The old FS has some functionality that WasmFS lacks. + name === 'FS_createLazyFile' || + name === 'FS_createDevice' || + name === 'removeRunDependency'; +} + +/** + * Intercept access to a symbols in the global symbol. This enables us to give + * informative warnings/errors when folks attempt to use symbols they did not + * include in their build, or no symbols that no longer exist. + * + * We don't define this in MODULARIZE mode since in that mode emscripten symbols + * are never placed in the global scope. + */ +function hookGlobalSymbolAccess(sym, func) { + if (!Object.getOwnPropertyDescriptor(globalThis, sym)) { + Object.defineProperty(globalThis, sym, { + configurable: true, + get() { + func(); + return undefined; + } + }); + } +} + +function missingGlobal(sym, msg) { + hookGlobalSymbolAccess(sym, () => { + warnOnce(`\`${sym}\` is no longer defined by emscripten. ${msg}`); + }); +} + +missingGlobal('buffer', 'Please use HEAP8.buffer or wasmMemory.buffer'); +missingGlobal('asm', 'Please use wasmExports instead'); + +function missingLibrarySymbol(sym) { + hookGlobalSymbolAccess(sym, () => { + // Can't `abort()` here because it would break code that does runtime + // checks. e.g. `if (typeof SDL === 'undefined')`. + var msg = `\`${sym}\` is a library symbol and not included by default; add it to your library.js __deps or to DEFAULT_LIBRARY_FUNCS_TO_INCLUDE on the command line`; + // DEFAULT_LIBRARY_FUNCS_TO_INCLUDE requires the name as it appears in + // library.js, which means $name for a JS name with no prefix, or name + // for a JS name like _name. + var librarySymbol = sym; + if (!librarySymbol.startsWith('_')) { + librarySymbol = '$' + sym; + } + msg += ` (e.g. -sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE='${librarySymbol}')`; + if (isExportedByForceFilesystem(sym)) { + msg += '. Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you'; + } + warnOnce(msg); + }); + + // Any symbol that is not included from the JS library is also (by definition) + // not exported on the Module object. + unexportedRuntimeSymbol(sym); +} + +function unexportedRuntimeSymbol(sym) { + if (!Object.getOwnPropertyDescriptor(Module, sym)) { + Object.defineProperty(Module, sym, { + configurable: true, + get() { + var msg = `'${sym}' was not exported. add it to EXPORTED_RUNTIME_METHODS (see the Emscripten FAQ)`; + if (isExportedByForceFilesystem(sym)) { + msg += '. Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you'; + } + abort(msg); + }, + }); + } +} + +// end include: runtime_debug.js +// Memory management +var +/** @type {!Int8Array} */ + HEAP8, +/** @type {!Uint8Array} */ + HEAPU8, +/** @type {!Int16Array} */ + HEAP16, +/** @type {!Uint16Array} */ + HEAPU16, +/** @type {!Int32Array} */ + HEAP32, +/** @type {!Uint32Array} */ + HEAPU32, +/** @type {!Float32Array} */ + HEAPF32, +/** @type {!Float64Array} */ + HEAPF64; + +// BigInt64Array type is not correctly defined in closure +var +/** not-@type {!BigInt64Array} */ + HEAP64, +/* BigUint64Array type is not correctly defined in closure +/** not-@type {!BigUint64Array} */ + HEAPU64; + +var runtimeInitialized = false; + + + +function updateMemoryViews() { + var b = wasmMemory.buffer; + HEAP8 = new Int8Array(b); + HEAP16 = new Int16Array(b); + HEAPU8 = new Uint8Array(b); + HEAPU16 = new Uint16Array(b); + HEAP32 = new Int32Array(b); + HEAPU32 = new Uint32Array(b); + HEAPF32 = new Float32Array(b); + HEAPF64 = new Float64Array(b); + HEAP64 = new BigInt64Array(b); + HEAPU64 = new BigUint64Array(b); +} + +// include: memoryprofiler.js +// end include: memoryprofiler.js +// end include: runtime_common.js +assert(globalThis.Int32Array && globalThis.Float64Array && Int32Array.prototype.subarray && Int32Array.prototype.set, + 'JS engine does not provide full typed array support'); + +function preRun() { + if (Module['preRun']) { + if (typeof Module['preRun'] == 'function') Module['preRun'] = [Module['preRun']]; + while (Module['preRun'].length) { + addOnPreRun(Module['preRun'].shift()); + } + } + consumedModuleProp('preRun'); + // Begin ATPRERUNS hooks + callRuntimeCallbacks(onPreRuns); + // End ATPRERUNS hooks +} + +function initRuntime() { + assert(!runtimeInitialized); + runtimeInitialized = true; + + checkStackCookie(); + + // No ATINITS hooks + + wasmExports['__wasm_call_ctors'](); + + // No ATPOSTCTORS hooks +} + +function preMain() { + checkStackCookie(); + // No ATMAINS hooks +} + +function postRun() { + checkStackCookie(); + // PThreads reuse the runtime from the main thread. + + if (Module['postRun']) { + if (typeof Module['postRun'] == 'function') Module['postRun'] = [Module['postRun']]; + while (Module['postRun'].length) { + addOnPostRun(Module['postRun'].shift()); + } + } + consumedModuleProp('postRun'); + + // Begin ATPOSTRUNS hooks + callRuntimeCallbacks(onPostRuns); + // End ATPOSTRUNS hooks +} + +/** @param {string|number=} what */ +function abort(what) { + Module['onAbort']?.(what); + + what = 'Aborted(' + what + ')'; + // TODO(sbc): Should we remove printing and leave it up to whoever + // catches the exception? + err(what); + + ABORT = true; + + // Use a wasm runtime error, because a JS error might be seen as a foreign + // exception, which means we'd run destructors on it. We need the error to + // simply make the program stop. + // FIXME This approach does not work in Wasm EH because it currently does not assume + // all RuntimeErrors are from traps; it decides whether a RuntimeError is from + // a trap or not based on a hidden field within the object. So at the moment + // we don't have a way of throwing a wasm trap from JS. TODO Make a JS API that + // allows this in the wasm spec. + + // Suppress closure compiler warning here. Closure compiler's builtin extern + // definition for WebAssembly.RuntimeError claims it takes no arguments even + // though it can. + // TODO(https://github.com/google/closure-compiler/pull/3913): Remove if/when upstream closure gets fixed. + /** @suppress {checkTypes} */ + var e = new WebAssembly.RuntimeError(what); + + // Throw the error whether or not MODULARIZE is set because abort is used + // in code paths apart from instantiation where an exception is expected + // to be thrown when abort is called. + throw e; +} + +// show errors on likely calls to FS when it was not included +var FS = { + error() { + abort('Filesystem support (FS) was not included. The problem is that you are using files from JS, but files were not used from C/C++, so filesystem support was not auto-included. You can force-include filesystem support with -sFORCE_FILESYSTEM'); + }, + init() { FS.error() }, + createDataFile() { FS.error() }, + createPreloadedFile() { FS.error() }, + createLazyFile() { FS.error() }, + open() { FS.error() }, + mkdev() { FS.error() }, + registerDevice() { FS.error() }, + analyzePath() { FS.error() }, + + ErrnoError() { FS.error() }, +}; + + +function createExportWrapper(name, nargs) { + return (...args) => { + assert(runtimeInitialized, `native function \`${name}\` called before runtime initialization`); + var f = wasmExports[name]; + assert(f, `exported native function \`${name}\` not found`); + // Only assert for too many arguments. Too few can be valid since the missing arguments will be zero filled. + assert(args.length <= nargs, `native function \`${name}\` called with ${args.length} args but expects ${nargs}`); + return f(...args); + }; +} + +var wasmBinaryFile; + +function findWasmBinary() { + return locateFile('wasm_check.wasm'); +} + +function getBinarySync(file) { + if (file == wasmBinaryFile && wasmBinary) { + return new Uint8Array(wasmBinary); + } + if (readBinary) { + return readBinary(file); + } + // Throwing a plain string here, even though it not normally advisable since + // this gets turning into an `abort` in instantiateArrayBuffer. + throw 'both async and sync fetching of the wasm failed'; +} + +async function getWasmBinary(binaryFile) { + // If we don't have the binary yet, load it asynchronously using readAsync. + if (!wasmBinary) { + // Fetch the binary using readAsync + try { + var response = await readAsync(binaryFile); + return new Uint8Array(response); + } catch { + // Fall back to getBinarySync below; + } + } + + // Otherwise, getBinarySync should be able to get it synchronously + return getBinarySync(binaryFile); +} + +async function instantiateArrayBuffer(binaryFile, imports) { + try { + var binary = await getWasmBinary(binaryFile); + var instance = await WebAssembly.instantiate(binary, imports); + return instance; + } catch (reason) { + err(`failed to asynchronously prepare wasm: ${reason}`); + + // Warn on some common problems. + if (isFileURI(binaryFile)) { + err(`warning: Loading from a file URI (${binaryFile}) is not supported in most browsers. See https://emscripten.org/docs/getting_started/FAQ.html#how-do-i-run-a-local-webserver-for-testing-why-does-my-program-stall-in-downloading-or-preparing`); + } + abort(reason); + } +} + +async function instantiateAsync(binary, binaryFile, imports) { + if (!binary + // Don't use streaming for file:// delivered objects in a webview, fetch them synchronously. + && !isFileURI(binaryFile) + // Avoid instantiateStreaming() on Node.js environment for now, as while + // Node.js v18.1.0 implements it, it does not have a full fetch() + // implementation yet. + // + // Reference: + // https://github.com/emscripten-core/emscripten/pull/16917 + && !ENVIRONMENT_IS_NODE + ) { + try { + var response = fetch(binaryFile, { credentials: 'same-origin' }); + var instantiationResult = await WebAssembly.instantiateStreaming(response, imports); + return instantiationResult; + } catch (reason) { + // We expect the most common failure cause to be a bad MIME type for the binary, + // in which case falling back to ArrayBuffer instantiation should work. + err(`wasm streaming compile failed: ${reason}`); + err('falling back to ArrayBuffer instantiation'); + // fall back of instantiateArrayBuffer below + }; + } + return instantiateArrayBuffer(binaryFile, imports); +} + +function getWasmImports() { + // prepare imports + var imports = { + 'env': wasmImports, + 'wasi_snapshot_preview1': wasmImports, + }; + return imports; +} + +// Create the wasm instance. +// Receives the wasm imports, returns the exports. +async function createWasm() { + // Load the wasm module and create an instance of using native support in the JS engine. + // handle a generated wasm instance, receiving its exports and + // performing other necessary setup + /** @param {WebAssembly.Module=} module*/ + function receiveInstance(instance, module) { + wasmExports = instance.exports; + + assignWasmExports(wasmExports); + + updateMemoryViews(); + + removeRunDependency('wasm-instantiate'); + return wasmExports; + } + addRunDependency('wasm-instantiate'); + + // Prefer streaming instantiation if available. + // Async compilation can be confusing when an error on the page overwrites Module + // (for example, if the order of elements is wrong, and the one defining Module is + // later), so we save Module and check it later. + var trueModule = Module; + function receiveInstantiationResult(result) { + // 'result' is a ResultObject object which has both the module and instance. + // receiveInstance() will swap in the exports (to Module.asm) so they can be called + assert(Module === trueModule, 'the Module object should not be replaced during async compilation - perhaps the order of HTML elements is wrong?'); + trueModule = null; + // TODO: Due to Closure regression https://github.com/google/closure-compiler/issues/3193, the above line no longer optimizes out down to the following line. + // When the regression is fixed, can restore the above PTHREADS-enabled path. + return receiveInstance(result['instance']); + } + + var info = getWasmImports(); + + // User shell pages can write their own Module.instantiateWasm = function(imports, successCallback) callback + // to manually instantiate the Wasm module themselves. This allows pages to + // run the instantiation parallel to any other async startup actions they are + // performing. + // Also pthreads and wasm workers initialize the wasm instance through this + // path. + if (Module['instantiateWasm']) { + return new Promise((resolve, reject) => { + try { + Module['instantiateWasm'](info, (inst, mod) => { + resolve(receiveInstance(inst, mod)); + }); + } catch(e) { + err(`Module.instantiateWasm callback failed with error: ${e}`); + reject(e); + } + }); + } + + wasmBinaryFile ??= findWasmBinary(); + var result = await instantiateAsync(wasmBinary, wasmBinaryFile, info); + var exports = receiveInstantiationResult(result); + return exports; +} + +// end include: preamble.js + +// Begin JS library code + + + class ExitStatus { + name = 'ExitStatus'; + constructor(status) { + this.message = `Program terminated with exit(${status})`; + this.status = status; + } + } + + var callRuntimeCallbacks = (callbacks) => { + while (callbacks.length > 0) { + // Pass the module as the first argument. + callbacks.shift()(Module); + } + }; + var onPostRuns = []; + var addOnPostRun = (cb) => onPostRuns.push(cb); + + var onPreRuns = []; + var addOnPreRun = (cb) => onPreRuns.push(cb); + + var runDependencies = 0; + + + var dependenciesFulfilled = null; + + var runDependencyTracking = { + }; + + var runDependencyWatcher = null; + var removeRunDependency = (id) => { + runDependencies--; + + Module['monitorRunDependencies']?.(runDependencies); + + assert(id, 'removeRunDependency requires an ID'); + assert(runDependencyTracking[id]); + delete runDependencyTracking[id]; + if (runDependencies == 0) { + if (runDependencyWatcher !== null) { + clearInterval(runDependencyWatcher); + runDependencyWatcher = null; + } + if (dependenciesFulfilled) { + var callback = dependenciesFulfilled; + dependenciesFulfilled = null; + callback(); // can add another dependenciesFulfilled + } + } + }; + + + var addRunDependency = (id) => { + runDependencies++; + + Module['monitorRunDependencies']?.(runDependencies); + + assert(id, 'addRunDependency requires an ID') + assert(!runDependencyTracking[id]); + runDependencyTracking[id] = 1; + if (runDependencyWatcher === null && globalThis.setInterval) { + // Check for missing dependencies every few seconds + runDependencyWatcher = setInterval(() => { + if (ABORT) { + clearInterval(runDependencyWatcher); + runDependencyWatcher = null; + return; + } + var shown = false; + for (var dep in runDependencyTracking) { + if (!shown) { + shown = true; + err('still waiting on run dependencies:'); + } + err(`dependency: ${dep}`); + } + if (shown) { + err('(end of list)'); + } + }, 10000); + // Prevent this timer from keeping the runtime alive if nothing + // else is. + runDependencyWatcher.unref?.() + } + }; + + + + /** + * @param {number} ptr + * @param {string} type + */ + function getValue(ptr, type = 'i8') { + if (type.endsWith('*')) type = '*'; + switch (type) { + case 'i1': return HEAP8[ptr]; + case 'i8': return HEAP8[ptr]; + case 'i16': return HEAP16[((ptr)>>1)]; + case 'i32': return HEAP32[((ptr)>>2)]; + case 'i64': return HEAP64[((ptr)>>3)]; + case 'float': return HEAPF32[((ptr)>>2)]; + case 'double': return HEAPF64[((ptr)>>3)]; + case '*': return HEAPU32[((ptr)>>2)]; + default: abort(`invalid type for getValue: ${type}`); + } + } + + var noExitRuntime = true; + + var ptrToString = (ptr) => { + assert(typeof ptr === 'number', `ptrToString expects a number, got ${typeof ptr}`); + // Convert to 32-bit unsigned value + ptr >>>= 0; + return '0x' + ptr.toString(16).padStart(8, '0'); + }; + + + + /** + * @param {number} ptr + * @param {number} value + * @param {string} type + */ + function setValue(ptr, value, type = 'i8') { + if (type.endsWith('*')) type = '*'; + switch (type) { + case 'i1': HEAP8[ptr] = value; break; + case 'i8': HEAP8[ptr] = value; break; + case 'i16': HEAP16[((ptr)>>1)] = value; break; + case 'i32': HEAP32[((ptr)>>2)] = value; break; + case 'i64': HEAP64[((ptr)>>3)] = BigInt(value); break; + case 'float': HEAPF32[((ptr)>>2)] = value; break; + case 'double': HEAPF64[((ptr)>>3)] = value; break; + case '*': HEAPU32[((ptr)>>2)] = value; break; + default: abort(`invalid type for setValue: ${type}`); + } + } + + var stackRestore = (val) => __emscripten_stack_restore(val); + + var stackSave = () => _emscripten_stack_get_current(); + + var warnOnce = (text) => { + warnOnce.shown ||= {}; + if (!warnOnce.shown[text]) { + warnOnce.shown[text] = 1; + if (ENVIRONMENT_IS_NODE) text = 'warning: ' + text; + err(text); + } + }; + + + + var __abort_js = () => + abort('native code called abort()'); + + var abortOnCannotGrowMemory = (requestedSize) => { + abort(`Cannot enlarge memory arrays to size ${requestedSize} bytes (OOM). Either (1) compile with -sINITIAL_MEMORY=X with X higher than the current value ${HEAP8.length}, (2) compile with -sALLOW_MEMORY_GROWTH which allows increasing the size at runtime, or (3) if you want malloc to return NULL (0) instead of this abort, compile with -sABORTING_MALLOC=0`); + }; + var _emscripten_resize_heap = (requestedSize) => { + var oldSize = HEAPU8.length; + // With CAN_ADDRESS_2GB or MEMORY64, pointers are already unsigned. + requestedSize >>>= 0; + abortOnCannotGrowMemory(requestedSize); + }; + + var UTF8Decoder = globalThis.TextDecoder && new TextDecoder(); + + var findStringEnd = (heapOrArray, idx, maxBytesToRead, ignoreNul) => { + var maxIdx = idx + maxBytesToRead; + if (ignoreNul) return maxIdx; + // TextDecoder needs to know the byte length in advance, it doesn't stop on + // null terminator by itself. + // As a tiny code save trick, compare idx against maxIdx using a negation, + // so that maxBytesToRead=undefined/NaN means Infinity. + while (heapOrArray[idx] && !(idx >= maxIdx)) ++idx; + return idx; + }; + + + /** + * Given a pointer 'idx' to a null-terminated UTF8-encoded string in the given + * array that contains uint8 values, returns a copy of that string as a + * Javascript String object. + * heapOrArray is either a regular array, or a JavaScript typed array view. + * @param {number=} idx + * @param {number=} maxBytesToRead + * @param {boolean=} ignoreNul - If true, the function will not stop on a NUL character. + * @return {string} + */ + var UTF8ArrayToString = (heapOrArray, idx = 0, maxBytesToRead, ignoreNul) => { + + var endPtr = findStringEnd(heapOrArray, idx, maxBytesToRead, ignoreNul); + + // When using conditional TextDecoder, skip it for short strings as the overhead of the native call is not worth it. + if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) { + return UTF8Decoder.decode(heapOrArray.subarray(idx, endPtr)); + } + var str = ''; + while (idx < endPtr) { + // For UTF8 byte structure, see: + // http://en.wikipedia.org/wiki/UTF-8#Description + // https://www.ietf.org/rfc/rfc2279.txt + // https://tools.ietf.org/html/rfc3629 + var u0 = heapOrArray[idx++]; + if (!(u0 & 0x80)) { str += String.fromCharCode(u0); continue; } + var u1 = heapOrArray[idx++] & 63; + if ((u0 & 0xE0) == 0xC0) { str += String.fromCharCode(((u0 & 31) << 6) | u1); continue; } + var u2 = heapOrArray[idx++] & 63; + if ((u0 & 0xF0) == 0xE0) { + u0 = ((u0 & 15) << 12) | (u1 << 6) | u2; + } else { + if ((u0 & 0xF8) != 0xF0) warnOnce('Invalid UTF-8 leading byte ' + ptrToString(u0) + ' encountered when deserializing a UTF-8 string in wasm memory to a JS string!'); + u0 = ((u0 & 7) << 18) | (u1 << 12) | (u2 << 6) | (heapOrArray[idx++] & 63); + } + + if (u0 < 0x10000) { + str += String.fromCharCode(u0); + } else { + var ch = u0 - 0x10000; + str += String.fromCharCode(0xD800 | (ch >> 10), 0xDC00 | (ch & 0x3FF)); + } + } + return str; + }; + + /** + * Given a pointer 'ptr' to a null-terminated UTF8-encoded string in the + * emscripten HEAP, returns a copy of that string as a Javascript String object. + * + * @param {number} ptr + * @param {number=} maxBytesToRead - An optional length that specifies the + * maximum number of bytes to read. You can omit this parameter to scan the + * string until the first 0 byte. If maxBytesToRead is passed, and the string + * at [ptr, ptr+maxBytesToReadr[ contains a null byte in the middle, then the + * string will cut short at that byte index. + * @param {boolean=} ignoreNul - If true, the function will not stop on a NUL character. + * @return {string} + */ + var UTF8ToString = (ptr, maxBytesToRead, ignoreNul) => { + assert(typeof ptr == 'number', `UTF8ToString expects a number (got ${typeof ptr})`); + return ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead, ignoreNul) : ''; + }; + var SYSCALLS = { + varargs:undefined, + getStr(ptr) { + var ret = UTF8ToString(ptr); + return ret; + }, + }; + var _fd_close = (fd) => { + abort('fd_close called without SYSCALLS_REQUIRE_FILESYSTEM'); + }; + + var INT53_MAX = 9007199254740992; + + var INT53_MIN = -9007199254740992; + var bigintToI53Checked = (num) => (num < INT53_MIN || num > INT53_MAX) ? NaN : Number(num); + function _fd_seek(fd, offset, whence, newOffset) { + offset = bigintToI53Checked(offset); + + + return 70; + ; + } + + var printCharBuffers = [null,[],[]]; + + var printChar = (stream, curr) => { + var buffer = printCharBuffers[stream]; + assert(buffer); + if (curr === 0 || curr === 10) { + (stream === 1 ? out : err)(UTF8ArrayToString(buffer)); + buffer.length = 0; + } else { + buffer.push(curr); + } + }; + + var flush_NO_FILESYSTEM = () => { + // flush anything remaining in the buffers during shutdown + _fflush(0); + if (printCharBuffers[1].length) printChar(1, 10); + if (printCharBuffers[2].length) printChar(2, 10); + }; + + + var _fd_write = (fd, iov, iovcnt, pnum) => { + // hack to support printf in SYSCALLS_REQUIRE_FILESYSTEM=0 + var num = 0; + for (var i = 0; i < iovcnt; i++) { + var ptr = HEAPU32[((iov)>>2)]; + var len = HEAPU32[(((iov)+(4))>>2)]; + iov += 8; + for (var j = 0; j < len; j++) { + printChar(fd, HEAPU8[ptr+j]); + } + num += len; + } + HEAPU32[((pnum)>>2)] = num; + return 0; + }; + + + var runtimeKeepaliveCounter = 0; + var keepRuntimeAlive = () => noExitRuntime || runtimeKeepaliveCounter > 0; + var _proc_exit = (code) => { + EXITSTATUS = code; + if (!keepRuntimeAlive()) { + Module['onExit']?.(code); + ABORT = true; + } + quit_(code, new ExitStatus(code)); + }; + + + /** @param {boolean|number=} implicit */ + var exitJS = (status, implicit) => { + EXITSTATUS = status; + + checkUnflushedContent(); + + // if exit() was called explicitly, warn the user if the runtime isn't actually being shut down + if (keepRuntimeAlive() && !implicit) { + var msg = `program exited (with status: ${status}), but keepRuntimeAlive() is set (counter=${runtimeKeepaliveCounter}) due to an async operation, so halting execution but not exiting the runtime or preventing further async execution (you can use emscripten_force_exit, if you want to force a true shutdown)`; + err(msg); + } + + _proc_exit(status); + }; + + var handleException = (e) => { + // Certain exception types we do not treat as errors since they are used for + // internal control flow. + // 1. ExitStatus, which is thrown by exit() + // 2. "unwind", which is thrown by emscripten_unwind_to_js_event_loop() and others + // that wish to return to JS event loop. + if (e instanceof ExitStatus || e == 'unwind') { + return EXITSTATUS; + } + checkStackCookie(); + if (e instanceof WebAssembly.RuntimeError) { + if (_emscripten_stack_get_current() <= 0) { + err('Stack overflow detected. You can try increasing -sSTACK_SIZE (currently set to 65536)'); + } + } + quit_(1, e); + }; +// End JS library code + +// include: postlibrary.js +// This file is included after the automatically-generated JS library code +// but before the wasm module is created. + +{ + + // Begin ATMODULES hooks + if (Module['noExitRuntime']) noExitRuntime = Module['noExitRuntime']; +if (Module['print']) out = Module['print']; +if (Module['printErr']) err = Module['printErr']; +if (Module['wasmBinary']) wasmBinary = Module['wasmBinary']; + +Module['FS_createDataFile'] = FS.createDataFile; +Module['FS_createPreloadedFile'] = FS.createPreloadedFile; + + // End ATMODULES hooks + + checkIncomingModuleAPI(); + + if (Module['arguments']) arguments_ = Module['arguments']; + if (Module['thisProgram']) thisProgram = Module['thisProgram']; + + // Assertions on removed incoming Module JS APIs. + assert(typeof Module['memoryInitializerPrefixURL'] == 'undefined', 'Module.memoryInitializerPrefixURL option was removed, use Module.locateFile instead'); + assert(typeof Module['pthreadMainPrefixURL'] == 'undefined', 'Module.pthreadMainPrefixURL option was removed, use Module.locateFile instead'); + assert(typeof Module['cdInitializerPrefixURL'] == 'undefined', 'Module.cdInitializerPrefixURL option was removed, use Module.locateFile instead'); + assert(typeof Module['filePackagePrefixURL'] == 'undefined', 'Module.filePackagePrefixURL option was removed, use Module.locateFile instead'); + assert(typeof Module['read'] == 'undefined', 'Module.read option was removed'); + assert(typeof Module['readAsync'] == 'undefined', 'Module.readAsync option was removed (modify readAsync in JS)'); + assert(typeof Module['readBinary'] == 'undefined', 'Module.readBinary option was removed (modify readBinary in JS)'); + assert(typeof Module['setWindowTitle'] == 'undefined', 'Module.setWindowTitle option was removed (modify emscripten_set_window_title in JS)'); + assert(typeof Module['TOTAL_MEMORY'] == 'undefined', 'Module.TOTAL_MEMORY has been renamed Module.INITIAL_MEMORY'); + assert(typeof Module['ENVIRONMENT'] == 'undefined', 'Module.ENVIRONMENT has been deprecated. To force the environment, use the ENVIRONMENT compile-time option (for example, -sENVIRONMENT=web or -sENVIRONMENT=node)'); + assert(typeof Module['STACK_SIZE'] == 'undefined', 'STACK_SIZE can no longer be set at runtime. Use -sSTACK_SIZE at link time') + // If memory is defined in wasm, the user can't provide it, or set INITIAL_MEMORY + assert(typeof Module['wasmMemory'] == 'undefined', 'Use of `wasmMemory` detected. Use -sIMPORTED_MEMORY to define wasmMemory externally'); + assert(typeof Module['INITIAL_MEMORY'] == 'undefined', 'Detected runtime INITIAL_MEMORY setting. Use -sIMPORTED_MEMORY to define wasmMemory dynamically'); + + if (Module['preInit']) { + if (typeof Module['preInit'] == 'function') Module['preInit'] = [Module['preInit']]; + while (Module['preInit'].length > 0) { + Module['preInit'].shift()(); + } + } + consumedModuleProp('preInit'); +} + +// Begin runtime exports + var missingLibrarySymbols = [ + 'writeI53ToI64', + 'writeI53ToI64Clamped', + 'writeI53ToI64Signaling', + 'writeI53ToU64Clamped', + 'writeI53ToU64Signaling', + 'readI53FromI64', + 'readI53FromU64', + 'convertI32PairToI53', + 'convertI32PairToI53Checked', + 'convertU32PairToI53', + 'stackAlloc', + 'getTempRet0', + 'setTempRet0', + 'createNamedFunction', + 'zeroMemory', + 'getHeapMax', + 'growMemory', + 'withStackSave', + 'strError', + 'inetPton4', + 'inetNtop4', + 'inetPton6', + 'inetNtop6', + 'readSockaddr', + 'writeSockaddr', + 'readEmAsmArgs', + 'jstoi_q', + 'getExecutableName', + 'autoResumeAudioContext', + 'getDynCaller', + 'dynCall', + 'runtimeKeepalivePush', + 'runtimeKeepalivePop', + 'callUserCallback', + 'maybeExit', + 'asyncLoad', + 'asmjsMangle', + 'alignMemory', + 'mmapAlloc', + 'HandleAllocator', + 'getUniqueRunDependency', + 'addOnInit', + 'addOnPostCtor', + 'addOnPreMain', + 'addOnExit', + 'STACK_SIZE', + 'STACK_ALIGN', + 'POINTER_SIZE', + 'ASSERTIONS', + 'ccall', + 'cwrap', + 'convertJsFunctionToWasm', + 'getEmptyTableSlot', + 'updateTableMap', + 'getFunctionAddress', + 'addFunction', + 'removeFunction', + 'stringToUTF8Array', + 'stringToUTF8', + 'lengthBytesUTF8', + 'intArrayFromString', + 'intArrayToString', + 'AsciiToString', + 'stringToAscii', + 'UTF16ToString', + 'stringToUTF16', + 'lengthBytesUTF16', + 'UTF32ToString', + 'stringToUTF32', + 'lengthBytesUTF32', + 'stringToNewUTF8', + 'stringToUTF8OnStack', + 'writeArrayToMemory', + 'registerKeyEventCallback', + 'maybeCStringToJsString', + 'findEventTarget', + 'getBoundingClientRect', + 'fillMouseEventData', + 'registerMouseEventCallback', + 'registerWheelEventCallback', + 'registerUiEventCallback', + 'registerFocusEventCallback', + 'fillDeviceOrientationEventData', + 'registerDeviceOrientationEventCallback', + 'fillDeviceMotionEventData', + 'registerDeviceMotionEventCallback', + 'screenOrientation', + 'fillOrientationChangeEventData', + 'registerOrientationChangeEventCallback', + 'fillFullscreenChangeEventData', + 'registerFullscreenChangeEventCallback', + 'JSEvents_requestFullscreen', + 'JSEvents_resizeCanvasForFullscreen', + 'registerRestoreOldStyle', + 'hideEverythingExceptGivenElement', + 'restoreHiddenElements', + 'setLetterbox', + 'softFullscreenResizeWebGLRenderTarget', + 'doRequestFullscreen', + 'fillPointerlockChangeEventData', + 'registerPointerlockChangeEventCallback', + 'registerPointerlockErrorEventCallback', + 'requestPointerLock', + 'fillVisibilityChangeEventData', + 'registerVisibilityChangeEventCallback', + 'registerTouchEventCallback', + 'fillGamepadEventData', + 'registerGamepadEventCallback', + 'registerBeforeUnloadEventCallback', + 'fillBatteryEventData', + 'registerBatteryEventCallback', + 'setCanvasElementSize', + 'getCanvasElementSize', + 'jsStackTrace', + 'getCallstack', + 'convertPCtoSourceLocation', + 'getEnvStrings', + 'checkWasiClock', + 'wasiRightsToMuslOFlags', + 'wasiOFlagsToMuslOFlags', + 'initRandomFill', + 'randomFill', + 'safeSetTimeout', + 'setImmediateWrapped', + 'safeRequestAnimationFrame', + 'clearImmediateWrapped', + 'registerPostMainLoop', + 'registerPreMainLoop', + 'getPromise', + 'makePromise', + 'idsToPromises', + 'makePromiseCallback', + 'ExceptionInfo', + 'findMatchingCatch', + 'Browser_asyncPrepareDataCounter', + 'isLeapYear', + 'ydayFromDate', + 'arraySum', + 'addDays', + 'getSocketFromFD', + 'getSocketAddress', + 'FS_createPreloadedFile', + 'FS_preloadFile', + 'FS_modeStringToFlags', + 'FS_getMode', + 'FS_stdin_getChar', + 'FS_mkdirTree', + '_setNetworkCallback', + 'heapObjectForWebGLType', + 'toTypedArrayIndex', + 'webgl_enable_ANGLE_instanced_arrays', + 'webgl_enable_OES_vertex_array_object', + 'webgl_enable_WEBGL_draw_buffers', + 'webgl_enable_WEBGL_multi_draw', + 'webgl_enable_EXT_polygon_offset_clamp', + 'webgl_enable_EXT_clip_control', + 'webgl_enable_WEBGL_polygon_mode', + 'emscriptenWebGLGet', + 'computeUnpackAlignedImageSize', + 'colorChannelsInGlTextureFormat', + 'emscriptenWebGLGetTexPixelData', + 'emscriptenWebGLGetUniform', + 'webglGetUniformLocation', + 'webglPrepareUniformLocationsBeforeFirstUse', + 'webglGetLeftBracePos', + 'emscriptenWebGLGetVertexAttrib', + '__glGetActiveAttribOrUniform', + 'writeGLArray', + 'registerWebGlEventCallback', + 'runAndAbortIfError', + 'ALLOC_NORMAL', + 'ALLOC_STACK', + 'allocate', + 'writeStringToMemory', + 'writeAsciiToMemory', + 'allocateUTF8', + 'allocateUTF8OnStack', + 'demangle', + 'stackTrace', + 'getNativeTypeSize', +]; +missingLibrarySymbols.forEach(missingLibrarySymbol) + + var unexportedSymbols = [ + 'run', + 'out', + 'err', + 'callMain', + 'abort', + 'wasmExports', + 'HEAPF32', + 'HEAPF64', + 'HEAP8', + 'HEAPU8', + 'HEAP16', + 'HEAPU16', + 'HEAP32', + 'HEAPU32', + 'HEAP64', + 'HEAPU64', + 'writeStackCookie', + 'checkStackCookie', + 'INT53_MAX', + 'INT53_MIN', + 'bigintToI53Checked', + 'stackSave', + 'stackRestore', + 'ptrToString', + 'exitJS', + 'abortOnCannotGrowMemory', + 'ENV', + 'ERRNO_CODES', + 'DNS', + 'Protocols', + 'Sockets', + 'timers', + 'warnOnce', + 'readEmAsmArgsArray', + 'handleException', + 'keepRuntimeAlive', + 'wasmTable', + 'wasmMemory', + 'noExitRuntime', + 'addRunDependency', + 'removeRunDependency', + 'addOnPreRun', + 'addOnPostRun', + 'freeTableIndexes', + 'functionsInTableMap', + 'setValue', + 'getValue', + 'PATH', + 'PATH_FS', + 'UTF8Decoder', + 'UTF8ArrayToString', + 'UTF8ToString', + 'UTF16Decoder', + 'JSEvents', + 'specialHTMLTargets', + 'findCanvasEventTarget', + 'currentFullscreenStrategy', + 'restoreOldWindowedStyle', + 'UNWIND_CACHE', + 'ExitStatus', + 'flush_NO_FILESYSTEM', + 'emSetImmediate', + 'emClearImmediate_deps', + 'emClearImmediate', + 'promiseMap', + 'uncaughtExceptionCount', + 'exceptionLast', + 'exceptionCaught', + 'Browser', + 'requestFullscreen', + 'requestFullScreen', + 'setCanvasSize', + 'getUserMedia', + 'createContext', + 'getPreloadedImageData__data', + 'wget', + 'MONTH_DAYS_REGULAR', + 'MONTH_DAYS_LEAP', + 'MONTH_DAYS_REGULAR_CUMULATIVE', + 'MONTH_DAYS_LEAP_CUMULATIVE', + 'SYSCALLS', + 'preloadPlugins', + 'FS_stdin_getChar_buffer', + 'FS_unlink', + 'FS_createPath', + 'FS_createDevice', + 'FS_readFile', + 'FS', + 'FS_root', + 'FS_mounts', + 'FS_devices', + 'FS_streams', + 'FS_nextInode', + 'FS_nameTable', + 'FS_currentPath', + 'FS_initialized', + 'FS_ignorePermissions', + 'FS_filesystems', + 'FS_syncFSRequests', + 'FS_lookupPath', + 'FS_getPath', + 'FS_hashName', + 'FS_hashAddNode', + 'FS_hashRemoveNode', + 'FS_lookupNode', + 'FS_createNode', + 'FS_destroyNode', + 'FS_isRoot', + 'FS_isMountpoint', + 'FS_isFile', + 'FS_isDir', + 'FS_isLink', + 'FS_isChrdev', + 'FS_isBlkdev', + 'FS_isFIFO', + 'FS_isSocket', + 'FS_flagsToPermissionString', + 'FS_nodePermissions', + 'FS_mayLookup', + 'FS_mayCreate', + 'FS_mayDelete', + 'FS_mayOpen', + 'FS_checkOpExists', + 'FS_nextfd', + 'FS_getStreamChecked', + 'FS_getStream', + 'FS_createStream', + 'FS_closeStream', + 'FS_dupStream', + 'FS_doSetAttr', + 'FS_chrdev_stream_ops', + 'FS_major', + 'FS_minor', + 'FS_makedev', + 'FS_registerDevice', + 'FS_getDevice', + 'FS_getMounts', + 'FS_syncfs', + 'FS_mount', + 'FS_unmount', + 'FS_lookup', + 'FS_mknod', + 'FS_statfs', + 'FS_statfsStream', + 'FS_statfsNode', + 'FS_create', + 'FS_mkdir', + 'FS_mkdev', + 'FS_symlink', + 'FS_rename', + 'FS_rmdir', + 'FS_readdir', + 'FS_readlink', + 'FS_stat', + 'FS_fstat', + 'FS_lstat', + 'FS_doChmod', + 'FS_chmod', + 'FS_lchmod', + 'FS_fchmod', + 'FS_doChown', + 'FS_chown', + 'FS_lchown', + 'FS_fchown', + 'FS_doTruncate', + 'FS_truncate', + 'FS_ftruncate', + 'FS_utime', + 'FS_open', + 'FS_close', + 'FS_isClosed', + 'FS_llseek', + 'FS_read', + 'FS_write', + 'FS_mmap', + 'FS_msync', + 'FS_ioctl', + 'FS_writeFile', + 'FS_cwd', + 'FS_chdir', + 'FS_createDefaultDirectories', + 'FS_createDefaultDevices', + 'FS_createSpecialDirectories', + 'FS_createStandardStreams', + 'FS_staticInit', + 'FS_init', + 'FS_quit', + 'FS_findObject', + 'FS_analyzePath', + 'FS_createFile', + 'FS_createDataFile', + 'FS_forceLoadFile', + 'FS_createLazyFile', + 'FS_absolutePath', + 'FS_createFolder', + 'FS_createLink', + 'FS_joinPath', + 'FS_mmapAlloc', + 'FS_standardizePath', + 'MEMFS', + 'TTY', + 'PIPEFS', + 'SOCKFS', + 'tempFixedLengthArray', + 'miniTempWebGLFloatBuffers', + 'miniTempWebGLIntBuffers', + 'GL', + 'AL', + 'GLUT', + 'EGL', + 'GLEW', + 'IDBStore', + 'SDL', + 'SDL_gfx', + 'print', + 'printErr', + 'jstoi_s', +]; +unexportedSymbols.forEach(unexportedRuntimeSymbol); + + // End runtime exports + // Begin JS library exports + // End JS library exports + +// end include: postlibrary.js + +function checkIncomingModuleAPI() { + ignoredModuleProp('fetchSettings'); + ignoredModuleProp('logReadFiles'); + ignoredModuleProp('loadSplitModule'); +} + +// Imports from the Wasm binary. +var _main = Module['_main'] = makeInvalidEarlyAccess('_main'); +var _fflush = makeInvalidEarlyAccess('_fflush'); +var _strerror = makeInvalidEarlyAccess('_strerror'); +var _emscripten_stack_get_end = makeInvalidEarlyAccess('_emscripten_stack_get_end'); +var _emscripten_stack_get_base = makeInvalidEarlyAccess('_emscripten_stack_get_base'); +var _emscripten_stack_init = makeInvalidEarlyAccess('_emscripten_stack_init'); +var _emscripten_stack_get_free = makeInvalidEarlyAccess('_emscripten_stack_get_free'); +var __emscripten_stack_restore = makeInvalidEarlyAccess('__emscripten_stack_restore'); +var __emscripten_stack_alloc = makeInvalidEarlyAccess('__emscripten_stack_alloc'); +var _emscripten_stack_get_current = makeInvalidEarlyAccess('_emscripten_stack_get_current'); +var memory = makeInvalidEarlyAccess('memory'); +var __indirect_function_table = makeInvalidEarlyAccess('__indirect_function_table'); +var wasmMemory = makeInvalidEarlyAccess('wasmMemory'); + +function assignWasmExports(wasmExports) { + assert(typeof wasmExports['main'] != 'undefined', 'missing Wasm export: main'); + assert(typeof wasmExports['fflush'] != 'undefined', 'missing Wasm export: fflush'); + assert(typeof wasmExports['strerror'] != 'undefined', 'missing Wasm export: strerror'); + assert(typeof wasmExports['emscripten_stack_get_end'] != 'undefined', 'missing Wasm export: emscripten_stack_get_end'); + assert(typeof wasmExports['emscripten_stack_get_base'] != 'undefined', 'missing Wasm export: emscripten_stack_get_base'); + assert(typeof wasmExports['emscripten_stack_init'] != 'undefined', 'missing Wasm export: emscripten_stack_init'); + assert(typeof wasmExports['emscripten_stack_get_free'] != 'undefined', 'missing Wasm export: emscripten_stack_get_free'); + assert(typeof wasmExports['_emscripten_stack_restore'] != 'undefined', 'missing Wasm export: _emscripten_stack_restore'); + assert(typeof wasmExports['_emscripten_stack_alloc'] != 'undefined', 'missing Wasm export: _emscripten_stack_alloc'); + assert(typeof wasmExports['emscripten_stack_get_current'] != 'undefined', 'missing Wasm export: emscripten_stack_get_current'); + assert(typeof wasmExports['memory'] != 'undefined', 'missing Wasm export: memory'); + assert(typeof wasmExports['__indirect_function_table'] != 'undefined', 'missing Wasm export: __indirect_function_table'); + _main = Module['_main'] = createExportWrapper('main', 2); + _fflush = createExportWrapper('fflush', 1); + _strerror = createExportWrapper('strerror', 1); + _emscripten_stack_get_end = wasmExports['emscripten_stack_get_end']; + _emscripten_stack_get_base = wasmExports['emscripten_stack_get_base']; + _emscripten_stack_init = wasmExports['emscripten_stack_init']; + _emscripten_stack_get_free = wasmExports['emscripten_stack_get_free']; + __emscripten_stack_restore = wasmExports['_emscripten_stack_restore']; + __emscripten_stack_alloc = wasmExports['_emscripten_stack_alloc']; + _emscripten_stack_get_current = wasmExports['emscripten_stack_get_current']; + memory = wasmMemory = wasmExports['memory']; + __indirect_function_table = wasmExports['__indirect_function_table']; +} + +var wasmImports = { + /** @export */ + _abort_js: __abort_js, + /** @export */ + emscripten_resize_heap: _emscripten_resize_heap, + /** @export */ + fd_close: _fd_close, + /** @export */ + fd_seek: _fd_seek, + /** @export */ + fd_write: _fd_write +}; + + +// include: postamble.js +// === Auto-generated postamble setup entry stuff === + +var calledRun; + +function callMain() { + assert(runDependencies == 0, 'cannot call main when async dependencies remain! (listen on Module["onRuntimeInitialized"])'); + assert(typeof onPreRuns === 'undefined' || onPreRuns.length == 0, 'cannot call main when preRun functions remain to be called'); + + var entryFunction = _main; + + var argc = 0; + var argv = 0; + + try { + + var ret = entryFunction(argc, argv); + + // if we're not running an evented main loop, it's time to exit + exitJS(ret, /* implicit = */ true); + return ret; + } catch (e) { + return handleException(e); + } +} + +function stackCheckInit() { + // This is normally called automatically during __wasm_call_ctors but need to + // get these values before even running any of the ctors so we call it redundantly + // here. + _emscripten_stack_init(); + // TODO(sbc): Move writeStackCookie to native to to avoid this. + writeStackCookie(); +} + +function run() { + + if (runDependencies > 0) { + dependenciesFulfilled = run; + return; + } + + stackCheckInit(); + + preRun(); + + // a preRun added a dependency, run will be called later + if (runDependencies > 0) { + dependenciesFulfilled = run; + return; + } + + function doRun() { + // run may have just been called through dependencies being fulfilled just in this very frame, + // or while the async setStatus time below was happening + assert(!calledRun); + calledRun = true; + Module['calledRun'] = true; + + if (ABORT) return; + + initRuntime(); + + preMain(); + + Module['onRuntimeInitialized']?.(); + consumedModuleProp('onRuntimeInitialized'); + + var noInitialRun = Module['noInitialRun'] || false; + if (!noInitialRun) callMain(); + + postRun(); + } + + if (Module['setStatus']) { + Module['setStatus']('Running...'); + setTimeout(() => { + setTimeout(() => Module['setStatus'](''), 1); + doRun(); + }, 1); + } else + { + doRun(); + } + checkStackCookie(); +} + +function checkUnflushedContent() { + // Compiler settings do not allow exiting the runtime, so flushing + // the streams is not possible. but in ASSERTIONS mode we check + // if there was something to flush, and if so tell the user they + // should request that the runtime be exitable. + // Normally we would not even include flush() at all, but in ASSERTIONS + // builds we do so just for this check, and here we see if there is any + // content to flush, that is, we check if there would have been + // something a non-ASSERTIONS build would have not seen. + // How we flush the streams depends on whether we are in SYSCALLS_REQUIRE_FILESYSTEM=0 + // mode (which has its own special function for this; otherwise, all + // the code is inside libc) + var oldOut = out; + var oldErr = err; + var has = false; + out = err = (x) => { + has = true; + } + try { // it doesn't matter if it fails + flush_NO_FILESYSTEM(); + } catch(e) {} + out = oldOut; + err = oldErr; + if (has) { + warnOnce('stdio streams had content in them that was not flushed. you should set EXIT_RUNTIME to 1 (see the Emscripten FAQ), or make sure to emit a newline when you printf etc.'); + warnOnce('(this may also be due to not including full filesystem support - try building with -sFORCE_FILESYSTEM)'); + } +} + +var wasmExports; + +// With async instantation wasmExports is assigned asynchronously when the +// instance is received. +createWasm(); + +run(); + +// end include: postamble.js + diff --git a/wasm_check.wasm b/wasm_check.wasm new file mode 100755 index 0000000..a0d0163 Binary files /dev/null and b/wasm_check.wasm differ diff --git a/wasm_check2.html b/wasm_check2.html new file mode 100644 index 0000000..a6232f5 --- /dev/null +++ b/wasm_check2.html @@ -0,0 +1,50 @@ + + + + + +sx + + + +
+
+
Loading…
+
+ + + + + + diff --git a/wasm_check2.js b/wasm_check2.js new file mode 100644 index 0000000..77a7ddc --- /dev/null +++ b/wasm_check2.js @@ -0,0 +1,1765 @@ +// include: shell.js +// include: minimum_runtime_check.js +(function() { + // "30.0.0" -> 300000 + function humanReadableVersionToPacked(str) { + str = str.split('-')[0]; // Remove any trailing part from e.g. "12.53.3-alpha" + var vers = str.split('.').slice(0, 3); + while(vers.length < 3) vers.push('00'); + vers = vers.map((n, i, arr) => n.padStart(2, '0')); + return vers.join(''); + } + // 300000 -> "30.0.0" + var packedVersionToHumanReadable = n => [n / 10000 | 0, (n / 100 | 0) % 100, n % 100].join('.'); + + var TARGET_NOT_SUPPORTED = 2147483647; + + // Note: We use a typeof check here instead of optional chaining using + // globalThis because older browsers might not have globalThis defined. + var currentNodeVersion = typeof process !== 'undefined' && process.versions?.node ? humanReadableVersionToPacked(process.versions.node) : TARGET_NOT_SUPPORTED; + if (currentNodeVersion < 160000) { + throw new Error(`This emscripten-generated code requires node v${ packedVersionToHumanReadable(160000) } (detected v${packedVersionToHumanReadable(currentNodeVersion)})`); + } + + var userAgent = typeof navigator !== 'undefined' && navigator.userAgent; + if (!userAgent) { + return; + } + + var currentSafariVersion = userAgent.includes("Safari/") && !userAgent.includes("Chrome/") && userAgent.match(/Version\/(\d+\.?\d*\.?\d*)/) ? humanReadableVersionToPacked(userAgent.match(/Version\/(\d+\.?\d*\.?\d*)/)[1]) : TARGET_NOT_SUPPORTED; + if (currentSafariVersion < 150000) { + throw new Error(`This emscripten-generated code requires Safari v${ packedVersionToHumanReadable(150000) } (detected v${currentSafariVersion})`); + } + + var currentFirefoxVersion = userAgent.match(/Firefox\/(\d+(?:\.\d+)?)/) ? parseFloat(userAgent.match(/Firefox\/(\d+(?:\.\d+)?)/)[1]) : TARGET_NOT_SUPPORTED; + if (currentFirefoxVersion < 79) { + throw new Error(`This emscripten-generated code requires Firefox v79 (detected v${currentFirefoxVersion})`); + } + + var currentChromeVersion = userAgent.match(/Chrome\/(\d+(?:\.\d+)?)/) ? parseFloat(userAgent.match(/Chrome\/(\d+(?:\.\d+)?)/)[1]) : TARGET_NOT_SUPPORTED; + if (currentChromeVersion < 85) { + throw new Error(`This emscripten-generated code requires Chrome v85 (detected v${currentChromeVersion})`); + } +})(); + +// end include: minimum_runtime_check.js +// The Module object: Our interface to the outside world. We import +// and export values on it. There are various ways Module can be used: +// 1. Not defined. We create it here +// 2. A function parameter, function(moduleArg) => Promise +// 3. pre-run appended it, var Module = {}; ..generated code.. +// 4. External script tag defines var Module. +// We need to check if Module already exists (e.g. case 3 above). +// Substitution will be replaced with actual code on later stage of the build, +// this way Closure Compiler will not mangle it (e.g. case 4. above). +// Note that if you want to run closure, and also to use Module +// after the generated code, you will need to define var Module = {}; +// before the code. Then that object will be used in the code, and you +// can continue to use Module afterwards as well. +var Module = typeof Module != 'undefined' ? Module : {}; + +// Determine the runtime environment we are in. You can customize this by +// setting the ENVIRONMENT setting at compile time (see settings.js). + +// Attempt to auto-detect the environment +var ENVIRONMENT_IS_WEB = !!globalThis.window; +var ENVIRONMENT_IS_WORKER = !!globalThis.WorkerGlobalScope; +// N.b. Electron.js environment is simultaneously a NODE-environment, but +// also a web environment. +var ENVIRONMENT_IS_NODE = globalThis.process?.versions?.node && globalThis.process?.type != 'renderer'; +var ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER; + +// --pre-jses are emitted after the Module integration code, so that they can +// refer to Module (if they choose; they can also define Module) + + +var arguments_ = []; +var thisProgram = './this.program'; +var quit_ = (status, toThrow) => { + throw toThrow; +}; + +// In MODULARIZE mode _scriptName needs to be captured already at the very top of the page immediately when the page is parsed, so it is generated there +// before the page load. In non-MODULARIZE modes generate it here. +var _scriptName = globalThis.document?.currentScript?.src; + +if (typeof __filename != 'undefined') { // Node + _scriptName = __filename; +} else +if (ENVIRONMENT_IS_WORKER) { + _scriptName = self.location.href; +} + +// `/` should be present at the end if `scriptDirectory` is not empty +var scriptDirectory = ''; +function locateFile(path) { + if (Module['locateFile']) { + return Module['locateFile'](path, scriptDirectory); + } + return scriptDirectory + path; +} + +// Hooks that are implemented differently in different runtime environments. +var readAsync, readBinary; + +if (ENVIRONMENT_IS_NODE) { + const isNode = globalThis.process?.versions?.node && globalThis.process?.type != 'renderer'; + if (!isNode) throw new Error('not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)'); + + // These modules will usually be used on Node.js. Load them eagerly to avoid + // the complexity of lazy-loading. + var fs = require('node:fs'); + + scriptDirectory = __dirname + '/'; + +// include: node_shell_read.js +readBinary = (filename) => { + // We need to re-wrap `file://` strings to URLs. + filename = isFileURI(filename) ? new URL(filename) : filename; + var ret = fs.readFileSync(filename); + assert(Buffer.isBuffer(ret)); + return ret; +}; + +readAsync = async (filename, binary = true) => { + // See the comment in the `readBinary` function. + filename = isFileURI(filename) ? new URL(filename) : filename; + var ret = fs.readFileSync(filename, binary ? undefined : 'utf8'); + assert(binary ? Buffer.isBuffer(ret) : typeof ret == 'string'); + return ret; +}; +// end include: node_shell_read.js + if (process.argv.length > 1) { + thisProgram = process.argv[1].replace(/\\/g, '/'); + } + + arguments_ = process.argv.slice(2); + + // MODULARIZE will export the module in the proper place outside, we don't need to export here + if (typeof module != 'undefined') { + module['exports'] = Module; + } + + quit_ = (status, toThrow) => { + process.exitCode = status; + throw toThrow; + }; + +} else +if (ENVIRONMENT_IS_SHELL) { + +} else + +// Note that this includes Node.js workers when relevant (pthreads is enabled). +// Node.js workers are detected as a combination of ENVIRONMENT_IS_WORKER and +// ENVIRONMENT_IS_NODE. +if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) { + try { + scriptDirectory = new URL('.', _scriptName).href; // includes trailing slash + } catch { + // Must be a `blob:` or `data:` URL (e.g. `blob:http://site.com/etc/etc`), we cannot + // infer anything from them. + } + + if (!(globalThis.window || globalThis.WorkerGlobalScope)) throw new Error('not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)'); + + { +// include: web_or_worker_shell_read.js +if (ENVIRONMENT_IS_WORKER) { + readBinary = (url) => { + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, false); + xhr.responseType = 'arraybuffer'; + xhr.send(null); + return new Uint8Array(/** @type{!ArrayBuffer} */(xhr.response)); + }; + } + + readAsync = async (url) => { + // Fetch has some additional restrictions over XHR, like it can't be used on a file:// url. + // See https://github.com/github/fetch/pull/92#issuecomment-140665932 + // Cordova or Electron apps are typically loaded from a file:// url. + // So use XHR on webview if URL is a file URL. + if (isFileURI(url)) { + return new Promise((resolve, reject) => { + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, true); + xhr.responseType = 'arraybuffer'; + xhr.onload = () => { + if (xhr.status == 200 || (xhr.status == 0 && xhr.response)) { // file URLs can return 0 + resolve(xhr.response); + return; + } + reject(xhr.status); + }; + xhr.onerror = reject; + xhr.send(null); + }); + } + var response = await fetch(url, { credentials: 'same-origin' }); + if (response.ok) { + return response.arrayBuffer(); + } + throw new Error(response.status + ' : ' + response.url); + }; +// end include: web_or_worker_shell_read.js + } +} else +{ + throw new Error('environment detection error'); +} + +var out = console.log.bind(console); +var err = console.error.bind(console); + +var IDBFS = 'IDBFS is no longer included by default; build with -lidbfs.js'; +var PROXYFS = 'PROXYFS is no longer included by default; build with -lproxyfs.js'; +var WORKERFS = 'WORKERFS is no longer included by default; build with -lworkerfs.js'; +var FETCHFS = 'FETCHFS is no longer included by default; build with -lfetchfs.js'; +var ICASEFS = 'ICASEFS is no longer included by default; build with -licasefs.js'; +var JSFILEFS = 'JSFILEFS is no longer included by default; build with -ljsfilefs.js'; +var OPFS = 'OPFS is no longer included by default; build with -lopfs.js'; + +var NODEFS = 'NODEFS is no longer included by default; build with -lnodefs.js'; + +// perform assertions in shell.js after we set up out() and err(), as otherwise +// if an assertion fails it cannot print the message + +assert(!ENVIRONMENT_IS_SHELL, 'shell environment detected but not enabled at build time. Add `shell` to `-sENVIRONMENT` to enable.'); + +// end include: shell.js + +// include: preamble.js +// === Preamble library stuff === + +// Documentation for the public APIs defined in this file must be updated in: +// site/source/docs/api_reference/preamble.js.rst +// A prebuilt local version of the documentation is available at: +// site/build/text/docs/api_reference/preamble.js.txt +// You can also build docs locally as HTML or other formats in site/ +// An online HTML version (which may be of a different version of Emscripten) +// is up at http://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html + +var wasmBinary; + +if (!globalThis.WebAssembly) { + err('no native wasm support detected'); +} + +// Wasm globals + +//======================================== +// Runtime essentials +//======================================== + +// whether we are quitting the application. no code should run after this. +// set in exit() and abort() +var ABORT = false; + +// set by exit() and abort(). Passed to 'onExit' handler. +// NOTE: This is also used as the process return code in shell environments +// but only when noExitRuntime is false. +var EXITSTATUS; + +// In STRICT mode, we only define assert() when ASSERTIONS is set. i.e. we +// don't define it at all in release modes. This matches the behaviour of +// MINIMAL_RUNTIME. +// TODO(sbc): Make this the default even without STRICT enabled. +/** @type {function(*, string=)} */ +function assert(condition, text) { + if (!condition) { + abort('Assertion failed' + (text ? ': ' + text : '')); + } +} + +// We used to include malloc/free by default in the past. Show a helpful error in +// builds with assertions. +function _malloc() { + abort('malloc() called but not included in the build - add `_malloc` to EXPORTED_FUNCTIONS'); +} +function _free() { + // Show a helpful error since we used to include free by default in the past. + abort('free() called but not included in the build - add `_free` to EXPORTED_FUNCTIONS'); +} + +/** + * Indicates whether filename is delivered via file protocol (as opposed to http/https) + * @noinline + */ +var isFileURI = (filename) => filename.startsWith('file://'); + +// include: runtime_common.js +// include: runtime_stack_check.js +// Initializes the stack cookie. Called at the startup of main and at the startup of each thread in pthreads mode. +function writeStackCookie() { + var max = _emscripten_stack_get_end(); + assert((max & 3) == 0); + // If the stack ends at address zero we write our cookies 4 bytes into the + // stack. This prevents interference with SAFE_HEAP and ASAN which also + // monitor writes to address zero. + if (max == 0) { + max += 4; + } + // The stack grow downwards towards _emscripten_stack_get_end. + // We write cookies to the final two words in the stack and detect if they are + // ever overwritten. + HEAPU32[((max)>>2)] = 0x02135467; + HEAPU32[(((max)+(4))>>2)] = 0x89BACDFE; + // Also test the global address 0 for integrity. + HEAPU32[((0)>>2)] = 1668509029; +} + +function checkStackCookie() { + if (ABORT) return; + var max = _emscripten_stack_get_end(); + // See writeStackCookie(). + if (max == 0) { + max += 4; + } + var cookie1 = HEAPU32[((max)>>2)]; + var cookie2 = HEAPU32[(((max)+(4))>>2)]; + if (cookie1 != 0x02135467 || cookie2 != 0x89BACDFE) { + abort(`Stack overflow! Stack cookie has been overwritten at ${ptrToString(max)}, expected hex dwords 0x89BACDFE and 0x2135467, but received ${ptrToString(cookie2)} ${ptrToString(cookie1)}`); + } + // Also test the global address 0 for integrity. + if (HEAPU32[((0)>>2)] != 0x63736d65 /* 'emsc' */) { + abort('Runtime error: The application has corrupted its heap memory area (address zero)!'); + } +} +// end include: runtime_stack_check.js +// include: runtime_exceptions.js +// end include: runtime_exceptions.js +// include: runtime_debug.js +var runtimeDebug = true; // Switch to false at runtime to disable logging at the right times + +// Used by XXXXX_DEBUG settings to output debug messages. +function dbg(...args) { + if (!runtimeDebug && typeof runtimeDebug != 'undefined') return; + // TODO(sbc): Make this configurable somehow. Its not always convenient for + // logging to show up as warnings. + console.warn(...args); +} + +// Endianness check +(() => { + var h16 = new Int16Array(1); + var h8 = new Int8Array(h16.buffer); + h16[0] = 0x6373; + if (h8[0] !== 0x73 || h8[1] !== 0x63) abort('Runtime error: expected the system to be little-endian! (Run with -sSUPPORT_BIG_ENDIAN to bypass)'); +})(); + +function consumedModuleProp(prop) { + if (!Object.getOwnPropertyDescriptor(Module, prop)) { + Object.defineProperty(Module, prop, { + configurable: true, + set() { + abort(`Attempt to set \`Module.${prop}\` after it has already been processed. This can happen, for example, when code is injected via '--post-js' rather than '--pre-js'`); + + } + }); + } +} + +function makeInvalidEarlyAccess(name) { + return () => assert(false, `call to '${name}' via reference taken before Wasm module initialization`); + +} + +function ignoredModuleProp(prop) { + if (Object.getOwnPropertyDescriptor(Module, prop)) { + abort(`\`Module.${prop}\` was supplied but \`${prop}\` not included in INCOMING_MODULE_JS_API`); + } +} + +// forcing the filesystem exports a few things by default +function isExportedByForceFilesystem(name) { + return name === 'FS_createPath' || + name === 'FS_createDataFile' || + name === 'FS_createPreloadedFile' || + name === 'FS_preloadFile' || + name === 'FS_unlink' || + name === 'addRunDependency' || + // The old FS has some functionality that WasmFS lacks. + name === 'FS_createLazyFile' || + name === 'FS_createDevice' || + name === 'removeRunDependency'; +} + +/** + * Intercept access to a symbols in the global symbol. This enables us to give + * informative warnings/errors when folks attempt to use symbols they did not + * include in their build, or no symbols that no longer exist. + * + * We don't define this in MODULARIZE mode since in that mode emscripten symbols + * are never placed in the global scope. + */ +function hookGlobalSymbolAccess(sym, func) { + if (!Object.getOwnPropertyDescriptor(globalThis, sym)) { + Object.defineProperty(globalThis, sym, { + configurable: true, + get() { + func(); + return undefined; + } + }); + } +} + +function missingGlobal(sym, msg) { + hookGlobalSymbolAccess(sym, () => { + warnOnce(`\`${sym}\` is no longer defined by emscripten. ${msg}`); + }); +} + +missingGlobal('buffer', 'Please use HEAP8.buffer or wasmMemory.buffer'); +missingGlobal('asm', 'Please use wasmExports instead'); + +function missingLibrarySymbol(sym) { + hookGlobalSymbolAccess(sym, () => { + // Can't `abort()` here because it would break code that does runtime + // checks. e.g. `if (typeof SDL === 'undefined')`. + var msg = `\`${sym}\` is a library symbol and not included by default; add it to your library.js __deps or to DEFAULT_LIBRARY_FUNCS_TO_INCLUDE on the command line`; + // DEFAULT_LIBRARY_FUNCS_TO_INCLUDE requires the name as it appears in + // library.js, which means $name for a JS name with no prefix, or name + // for a JS name like _name. + var librarySymbol = sym; + if (!librarySymbol.startsWith('_')) { + librarySymbol = '$' + sym; + } + msg += ` (e.g. -sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE='${librarySymbol}')`; + if (isExportedByForceFilesystem(sym)) { + msg += '. Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you'; + } + warnOnce(msg); + }); + + // Any symbol that is not included from the JS library is also (by definition) + // not exported on the Module object. + unexportedRuntimeSymbol(sym); +} + +function unexportedRuntimeSymbol(sym) { + if (!Object.getOwnPropertyDescriptor(Module, sym)) { + Object.defineProperty(Module, sym, { + configurable: true, + get() { + var msg = `'${sym}' was not exported. add it to EXPORTED_RUNTIME_METHODS (see the Emscripten FAQ)`; + if (isExportedByForceFilesystem(sym)) { + msg += '. Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you'; + } + abort(msg); + }, + }); + } +} + +// end include: runtime_debug.js +// Memory management +var +/** @type {!Int8Array} */ + HEAP8, +/** @type {!Uint8Array} */ + HEAPU8, +/** @type {!Int16Array} */ + HEAP16, +/** @type {!Uint16Array} */ + HEAPU16, +/** @type {!Int32Array} */ + HEAP32, +/** @type {!Uint32Array} */ + HEAPU32, +/** @type {!Float32Array} */ + HEAPF32, +/** @type {!Float64Array} */ + HEAPF64; + +// BigInt64Array type is not correctly defined in closure +var +/** not-@type {!BigInt64Array} */ + HEAP64, +/* BigUint64Array type is not correctly defined in closure +/** not-@type {!BigUint64Array} */ + HEAPU64; + +var runtimeInitialized = false; + + + +function updateMemoryViews() { + var b = wasmMemory.buffer; + HEAP8 = new Int8Array(b); + HEAP16 = new Int16Array(b); + HEAPU8 = new Uint8Array(b); + HEAPU16 = new Uint16Array(b); + HEAP32 = new Int32Array(b); + HEAPU32 = new Uint32Array(b); + HEAPF32 = new Float32Array(b); + HEAPF64 = new Float64Array(b); + HEAP64 = new BigInt64Array(b); + HEAPU64 = new BigUint64Array(b); +} + +// include: memoryprofiler.js +// end include: memoryprofiler.js +// end include: runtime_common.js +assert(globalThis.Int32Array && globalThis.Float64Array && Int32Array.prototype.subarray && Int32Array.prototype.set, + 'JS engine does not provide full typed array support'); + +function preRun() { + if (Module['preRun']) { + if (typeof Module['preRun'] == 'function') Module['preRun'] = [Module['preRun']]; + while (Module['preRun'].length) { + addOnPreRun(Module['preRun'].shift()); + } + } + consumedModuleProp('preRun'); + // Begin ATPRERUNS hooks + callRuntimeCallbacks(onPreRuns); + // End ATPRERUNS hooks +} + +function initRuntime() { + assert(!runtimeInitialized); + runtimeInitialized = true; + + checkStackCookie(); + + // No ATINITS hooks + + wasmExports['__wasm_call_ctors'](); + + // No ATPOSTCTORS hooks +} + +function preMain() { + checkStackCookie(); + // No ATMAINS hooks +} + +function postRun() { + checkStackCookie(); + // PThreads reuse the runtime from the main thread. + + if (Module['postRun']) { + if (typeof Module['postRun'] == 'function') Module['postRun'] = [Module['postRun']]; + while (Module['postRun'].length) { + addOnPostRun(Module['postRun'].shift()); + } + } + consumedModuleProp('postRun'); + + // Begin ATPOSTRUNS hooks + callRuntimeCallbacks(onPostRuns); + // End ATPOSTRUNS hooks +} + +/** @param {string|number=} what */ +function abort(what) { + Module['onAbort']?.(what); + + what = 'Aborted(' + what + ')'; + // TODO(sbc): Should we remove printing and leave it up to whoever + // catches the exception? + err(what); + + ABORT = true; + + // Use a wasm runtime error, because a JS error might be seen as a foreign + // exception, which means we'd run destructors on it. We need the error to + // simply make the program stop. + // FIXME This approach does not work in Wasm EH because it currently does not assume + // all RuntimeErrors are from traps; it decides whether a RuntimeError is from + // a trap or not based on a hidden field within the object. So at the moment + // we don't have a way of throwing a wasm trap from JS. TODO Make a JS API that + // allows this in the wasm spec. + + // Suppress closure compiler warning here. Closure compiler's builtin extern + // definition for WebAssembly.RuntimeError claims it takes no arguments even + // though it can. + // TODO(https://github.com/google/closure-compiler/pull/3913): Remove if/when upstream closure gets fixed. + /** @suppress {checkTypes} */ + var e = new WebAssembly.RuntimeError(what); + + // Throw the error whether or not MODULARIZE is set because abort is used + // in code paths apart from instantiation where an exception is expected + // to be thrown when abort is called. + throw e; +} + +// show errors on likely calls to FS when it was not included +var FS = { + error() { + abort('Filesystem support (FS) was not included. The problem is that you are using files from JS, but files were not used from C/C++, so filesystem support was not auto-included. You can force-include filesystem support with -sFORCE_FILESYSTEM'); + }, + init() { FS.error() }, + createDataFile() { FS.error() }, + createPreloadedFile() { FS.error() }, + createLazyFile() { FS.error() }, + open() { FS.error() }, + mkdev() { FS.error() }, + registerDevice() { FS.error() }, + analyzePath() { FS.error() }, + + ErrnoError() { FS.error() }, +}; + + +function createExportWrapper(name, nargs) { + return (...args) => { + assert(runtimeInitialized, `native function \`${name}\` called before runtime initialization`); + var f = wasmExports[name]; + assert(f, `exported native function \`${name}\` not found`); + // Only assert for too many arguments. Too few can be valid since the missing arguments will be zero filled. + assert(args.length <= nargs, `native function \`${name}\` called with ${args.length} args but expects ${nargs}`); + return f(...args); + }; +} + +var wasmBinaryFile; + +function findWasmBinary() { + return locateFile('wasm_check2.wasm'); +} + +function getBinarySync(file) { + if (file == wasmBinaryFile && wasmBinary) { + return new Uint8Array(wasmBinary); + } + if (readBinary) { + return readBinary(file); + } + // Throwing a plain string here, even though it not normally advisable since + // this gets turning into an `abort` in instantiateArrayBuffer. + throw 'both async and sync fetching of the wasm failed'; +} + +async function getWasmBinary(binaryFile) { + // If we don't have the binary yet, load it asynchronously using readAsync. + if (!wasmBinary) { + // Fetch the binary using readAsync + try { + var response = await readAsync(binaryFile); + return new Uint8Array(response); + } catch { + // Fall back to getBinarySync below; + } + } + + // Otherwise, getBinarySync should be able to get it synchronously + return getBinarySync(binaryFile); +} + +async function instantiateArrayBuffer(binaryFile, imports) { + try { + var binary = await getWasmBinary(binaryFile); + var instance = await WebAssembly.instantiate(binary, imports); + return instance; + } catch (reason) { + err(`failed to asynchronously prepare wasm: ${reason}`); + + // Warn on some common problems. + if (isFileURI(binaryFile)) { + err(`warning: Loading from a file URI (${binaryFile}) is not supported in most browsers. See https://emscripten.org/docs/getting_started/FAQ.html#how-do-i-run-a-local-webserver-for-testing-why-does-my-program-stall-in-downloading-or-preparing`); + } + abort(reason); + } +} + +async function instantiateAsync(binary, binaryFile, imports) { + if (!binary + // Don't use streaming for file:// delivered objects in a webview, fetch them synchronously. + && !isFileURI(binaryFile) + // Avoid instantiateStreaming() on Node.js environment for now, as while + // Node.js v18.1.0 implements it, it does not have a full fetch() + // implementation yet. + // + // Reference: + // https://github.com/emscripten-core/emscripten/pull/16917 + && !ENVIRONMENT_IS_NODE + ) { + try { + var response = fetch(binaryFile, { credentials: 'same-origin' }); + var instantiationResult = await WebAssembly.instantiateStreaming(response, imports); + return instantiationResult; + } catch (reason) { + // We expect the most common failure cause to be a bad MIME type for the binary, + // in which case falling back to ArrayBuffer instantiation should work. + err(`wasm streaming compile failed: ${reason}`); + err('falling back to ArrayBuffer instantiation'); + // fall back of instantiateArrayBuffer below + }; + } + return instantiateArrayBuffer(binaryFile, imports); +} + +function getWasmImports() { + // prepare imports + var imports = { + 'env': wasmImports, + 'wasi_snapshot_preview1': wasmImports, + }; + return imports; +} + +// Create the wasm instance. +// Receives the wasm imports, returns the exports. +async function createWasm() { + // Load the wasm module and create an instance of using native support in the JS engine. + // handle a generated wasm instance, receiving its exports and + // performing other necessary setup + /** @param {WebAssembly.Module=} module*/ + function receiveInstance(instance, module) { + wasmExports = instance.exports; + + assignWasmExports(wasmExports); + + updateMemoryViews(); + + removeRunDependency('wasm-instantiate'); + return wasmExports; + } + addRunDependency('wasm-instantiate'); + + // Prefer streaming instantiation if available. + // Async compilation can be confusing when an error on the page overwrites Module + // (for example, if the order of elements is wrong, and the one defining Module is + // later), so we save Module and check it later. + var trueModule = Module; + function receiveInstantiationResult(result) { + // 'result' is a ResultObject object which has both the module and instance. + // receiveInstance() will swap in the exports (to Module.asm) so they can be called + assert(Module === trueModule, 'the Module object should not be replaced during async compilation - perhaps the order of HTML elements is wrong?'); + trueModule = null; + // TODO: Due to Closure regression https://github.com/google/closure-compiler/issues/3193, the above line no longer optimizes out down to the following line. + // When the regression is fixed, can restore the above PTHREADS-enabled path. + return receiveInstance(result['instance']); + } + + var info = getWasmImports(); + + // User shell pages can write their own Module.instantiateWasm = function(imports, successCallback) callback + // to manually instantiate the Wasm module themselves. This allows pages to + // run the instantiation parallel to any other async startup actions they are + // performing. + // Also pthreads and wasm workers initialize the wasm instance through this + // path. + if (Module['instantiateWasm']) { + return new Promise((resolve, reject) => { + try { + Module['instantiateWasm'](info, (inst, mod) => { + resolve(receiveInstance(inst, mod)); + }); + } catch(e) { + err(`Module.instantiateWasm callback failed with error: ${e}`); + reject(e); + } + }); + } + + wasmBinaryFile ??= findWasmBinary(); + var result = await instantiateAsync(wasmBinary, wasmBinaryFile, info); + var exports = receiveInstantiationResult(result); + return exports; +} + +// end include: preamble.js + +// Begin JS library code + + + class ExitStatus { + name = 'ExitStatus'; + constructor(status) { + this.message = `Program terminated with exit(${status})`; + this.status = status; + } + } + + var callRuntimeCallbacks = (callbacks) => { + while (callbacks.length > 0) { + // Pass the module as the first argument. + callbacks.shift()(Module); + } + }; + var onPostRuns = []; + var addOnPostRun = (cb) => onPostRuns.push(cb); + + var onPreRuns = []; + var addOnPreRun = (cb) => onPreRuns.push(cb); + + var runDependencies = 0; + + + var dependenciesFulfilled = null; + + var runDependencyTracking = { + }; + + var runDependencyWatcher = null; + var removeRunDependency = (id) => { + runDependencies--; + + Module['monitorRunDependencies']?.(runDependencies); + + assert(id, 'removeRunDependency requires an ID'); + assert(runDependencyTracking[id]); + delete runDependencyTracking[id]; + if (runDependencies == 0) { + if (runDependencyWatcher !== null) { + clearInterval(runDependencyWatcher); + runDependencyWatcher = null; + } + if (dependenciesFulfilled) { + var callback = dependenciesFulfilled; + dependenciesFulfilled = null; + callback(); // can add another dependenciesFulfilled + } + } + }; + + + var addRunDependency = (id) => { + runDependencies++; + + Module['monitorRunDependencies']?.(runDependencies); + + assert(id, 'addRunDependency requires an ID') + assert(!runDependencyTracking[id]); + runDependencyTracking[id] = 1; + if (runDependencyWatcher === null && globalThis.setInterval) { + // Check for missing dependencies every few seconds + runDependencyWatcher = setInterval(() => { + if (ABORT) { + clearInterval(runDependencyWatcher); + runDependencyWatcher = null; + return; + } + var shown = false; + for (var dep in runDependencyTracking) { + if (!shown) { + shown = true; + err('still waiting on run dependencies:'); + } + err(`dependency: ${dep}`); + } + if (shown) { + err('(end of list)'); + } + }, 10000); + // Prevent this timer from keeping the runtime alive if nothing + // else is. + runDependencyWatcher.unref?.() + } + }; + + + + /** + * @param {number} ptr + * @param {string} type + */ + function getValue(ptr, type = 'i8') { + if (type.endsWith('*')) type = '*'; + switch (type) { + case 'i1': return HEAP8[ptr]; + case 'i8': return HEAP8[ptr]; + case 'i16': return HEAP16[((ptr)>>1)]; + case 'i32': return HEAP32[((ptr)>>2)]; + case 'i64': return HEAP64[((ptr)>>3)]; + case 'float': return HEAPF32[((ptr)>>2)]; + case 'double': return HEAPF64[((ptr)>>3)]; + case '*': return HEAPU32[((ptr)>>2)]; + default: abort(`invalid type for getValue: ${type}`); + } + } + + var noExitRuntime = true; + + var ptrToString = (ptr) => { + assert(typeof ptr === 'number', `ptrToString expects a number, got ${typeof ptr}`); + // Convert to 32-bit unsigned value + ptr >>>= 0; + return '0x' + ptr.toString(16).padStart(8, '0'); + }; + + + + /** + * @param {number} ptr + * @param {number} value + * @param {string} type + */ + function setValue(ptr, value, type = 'i8') { + if (type.endsWith('*')) type = '*'; + switch (type) { + case 'i1': HEAP8[ptr] = value; break; + case 'i8': HEAP8[ptr] = value; break; + case 'i16': HEAP16[((ptr)>>1)] = value; break; + case 'i32': HEAP32[((ptr)>>2)] = value; break; + case 'i64': HEAP64[((ptr)>>3)] = BigInt(value); break; + case 'float': HEAPF32[((ptr)>>2)] = value; break; + case 'double': HEAPF64[((ptr)>>3)] = value; break; + case '*': HEAPU32[((ptr)>>2)] = value; break; + default: abort(`invalid type for setValue: ${type}`); + } + } + + var stackRestore = (val) => __emscripten_stack_restore(val); + + var stackSave = () => _emscripten_stack_get_current(); + + var warnOnce = (text) => { + warnOnce.shown ||= {}; + if (!warnOnce.shown[text]) { + warnOnce.shown[text] = 1; + if (ENVIRONMENT_IS_NODE) text = 'warning: ' + text; + err(text); + } + }; + + + + var __abort_js = () => + abort('native code called abort()'); + + var abortOnCannotGrowMemory = (requestedSize) => { + abort(`Cannot enlarge memory arrays to size ${requestedSize} bytes (OOM). Either (1) compile with -sINITIAL_MEMORY=X with X higher than the current value ${HEAP8.length}, (2) compile with -sALLOW_MEMORY_GROWTH which allows increasing the size at runtime, or (3) if you want malloc to return NULL (0) instead of this abort, compile with -sABORTING_MALLOC=0`); + }; + var _emscripten_resize_heap = (requestedSize) => { + var oldSize = HEAPU8.length; + // With CAN_ADDRESS_2GB or MEMORY64, pointers are already unsigned. + requestedSize >>>= 0; + abortOnCannotGrowMemory(requestedSize); + }; + + var UTF8Decoder = globalThis.TextDecoder && new TextDecoder(); + + var findStringEnd = (heapOrArray, idx, maxBytesToRead, ignoreNul) => { + var maxIdx = idx + maxBytesToRead; + if (ignoreNul) return maxIdx; + // TextDecoder needs to know the byte length in advance, it doesn't stop on + // null terminator by itself. + // As a tiny code save trick, compare idx against maxIdx using a negation, + // so that maxBytesToRead=undefined/NaN means Infinity. + while (heapOrArray[idx] && !(idx >= maxIdx)) ++idx; + return idx; + }; + + + /** + * Given a pointer 'idx' to a null-terminated UTF8-encoded string in the given + * array that contains uint8 values, returns a copy of that string as a + * Javascript String object. + * heapOrArray is either a regular array, or a JavaScript typed array view. + * @param {number=} idx + * @param {number=} maxBytesToRead + * @param {boolean=} ignoreNul - If true, the function will not stop on a NUL character. + * @return {string} + */ + var UTF8ArrayToString = (heapOrArray, idx = 0, maxBytesToRead, ignoreNul) => { + + var endPtr = findStringEnd(heapOrArray, idx, maxBytesToRead, ignoreNul); + + // When using conditional TextDecoder, skip it for short strings as the overhead of the native call is not worth it. + if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) { + return UTF8Decoder.decode(heapOrArray.subarray(idx, endPtr)); + } + var str = ''; + while (idx < endPtr) { + // For UTF8 byte structure, see: + // http://en.wikipedia.org/wiki/UTF-8#Description + // https://www.ietf.org/rfc/rfc2279.txt + // https://tools.ietf.org/html/rfc3629 + var u0 = heapOrArray[idx++]; + if (!(u0 & 0x80)) { str += String.fromCharCode(u0); continue; } + var u1 = heapOrArray[idx++] & 63; + if ((u0 & 0xE0) == 0xC0) { str += String.fromCharCode(((u0 & 31) << 6) | u1); continue; } + var u2 = heapOrArray[idx++] & 63; + if ((u0 & 0xF0) == 0xE0) { + u0 = ((u0 & 15) << 12) | (u1 << 6) | u2; + } else { + if ((u0 & 0xF8) != 0xF0) warnOnce('Invalid UTF-8 leading byte ' + ptrToString(u0) + ' encountered when deserializing a UTF-8 string in wasm memory to a JS string!'); + u0 = ((u0 & 7) << 18) | (u1 << 12) | (u2 << 6) | (heapOrArray[idx++] & 63); + } + + if (u0 < 0x10000) { + str += String.fromCharCode(u0); + } else { + var ch = u0 - 0x10000; + str += String.fromCharCode(0xD800 | (ch >> 10), 0xDC00 | (ch & 0x3FF)); + } + } + return str; + }; + + /** + * Given a pointer 'ptr' to a null-terminated UTF8-encoded string in the + * emscripten HEAP, returns a copy of that string as a Javascript String object. + * + * @param {number} ptr + * @param {number=} maxBytesToRead - An optional length that specifies the + * maximum number of bytes to read. You can omit this parameter to scan the + * string until the first 0 byte. If maxBytesToRead is passed, and the string + * at [ptr, ptr+maxBytesToReadr[ contains a null byte in the middle, then the + * string will cut short at that byte index. + * @param {boolean=} ignoreNul - If true, the function will not stop on a NUL character. + * @return {string} + */ + var UTF8ToString = (ptr, maxBytesToRead, ignoreNul) => { + assert(typeof ptr == 'number', `UTF8ToString expects a number (got ${typeof ptr})`); + return ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead, ignoreNul) : ''; + }; + var SYSCALLS = { + varargs:undefined, + getStr(ptr) { + var ret = UTF8ToString(ptr); + return ret; + }, + }; + var _fd_close = (fd) => { + abort('fd_close called without SYSCALLS_REQUIRE_FILESYSTEM'); + }; + + var INT53_MAX = 9007199254740992; + + var INT53_MIN = -9007199254740992; + var bigintToI53Checked = (num) => (num < INT53_MIN || num > INT53_MAX) ? NaN : Number(num); + function _fd_seek(fd, offset, whence, newOffset) { + offset = bigintToI53Checked(offset); + + + return 70; + ; + } + + var printCharBuffers = [null,[],[]]; + + var printChar = (stream, curr) => { + var buffer = printCharBuffers[stream]; + assert(buffer); + if (curr === 0 || curr === 10) { + (stream === 1 ? out : err)(UTF8ArrayToString(buffer)); + buffer.length = 0; + } else { + buffer.push(curr); + } + }; + + var flush_NO_FILESYSTEM = () => { + // flush anything remaining in the buffers during shutdown + _fflush(0); + if (printCharBuffers[1].length) printChar(1, 10); + if (printCharBuffers[2].length) printChar(2, 10); + }; + + + var _fd_write = (fd, iov, iovcnt, pnum) => { + // hack to support printf in SYSCALLS_REQUIRE_FILESYSTEM=0 + var num = 0; + for (var i = 0; i < iovcnt; i++) { + var ptr = HEAPU32[((iov)>>2)]; + var len = HEAPU32[(((iov)+(4))>>2)]; + iov += 8; + for (var j = 0; j < len; j++) { + printChar(fd, HEAPU8[ptr+j]); + } + num += len; + } + HEAPU32[((pnum)>>2)] = num; + return 0; + }; + + + var runtimeKeepaliveCounter = 0; + var keepRuntimeAlive = () => noExitRuntime || runtimeKeepaliveCounter > 0; + var _proc_exit = (code) => { + EXITSTATUS = code; + if (!keepRuntimeAlive()) { + Module['onExit']?.(code); + ABORT = true; + } + quit_(code, new ExitStatus(code)); + }; + + + /** @param {boolean|number=} implicit */ + var exitJS = (status, implicit) => { + EXITSTATUS = status; + + checkUnflushedContent(); + + // if exit() was called explicitly, warn the user if the runtime isn't actually being shut down + if (keepRuntimeAlive() && !implicit) { + var msg = `program exited (with status: ${status}), but keepRuntimeAlive() is set (counter=${runtimeKeepaliveCounter}) due to an async operation, so halting execution but not exiting the runtime or preventing further async execution (you can use emscripten_force_exit, if you want to force a true shutdown)`; + err(msg); + } + + _proc_exit(status); + }; + + var handleException = (e) => { + // Certain exception types we do not treat as errors since they are used for + // internal control flow. + // 1. ExitStatus, which is thrown by exit() + // 2. "unwind", which is thrown by emscripten_unwind_to_js_event_loop() and others + // that wish to return to JS event loop. + if (e instanceof ExitStatus || e == 'unwind') { + return EXITSTATUS; + } + checkStackCookie(); + if (e instanceof WebAssembly.RuntimeError) { + if (_emscripten_stack_get_current() <= 0) { + err('Stack overflow detected. You can try increasing -sSTACK_SIZE (currently set to 65536)'); + } + } + quit_(1, e); + }; +// End JS library code + +// include: postlibrary.js +// This file is included after the automatically-generated JS library code +// but before the wasm module is created. + +{ + + // Begin ATMODULES hooks + if (Module['noExitRuntime']) noExitRuntime = Module['noExitRuntime']; +if (Module['print']) out = Module['print']; +if (Module['printErr']) err = Module['printErr']; +if (Module['wasmBinary']) wasmBinary = Module['wasmBinary']; + +Module['FS_createDataFile'] = FS.createDataFile; +Module['FS_createPreloadedFile'] = FS.createPreloadedFile; + + // End ATMODULES hooks + + checkIncomingModuleAPI(); + + if (Module['arguments']) arguments_ = Module['arguments']; + if (Module['thisProgram']) thisProgram = Module['thisProgram']; + + // Assertions on removed incoming Module JS APIs. + assert(typeof Module['memoryInitializerPrefixURL'] == 'undefined', 'Module.memoryInitializerPrefixURL option was removed, use Module.locateFile instead'); + assert(typeof Module['pthreadMainPrefixURL'] == 'undefined', 'Module.pthreadMainPrefixURL option was removed, use Module.locateFile instead'); + assert(typeof Module['cdInitializerPrefixURL'] == 'undefined', 'Module.cdInitializerPrefixURL option was removed, use Module.locateFile instead'); + assert(typeof Module['filePackagePrefixURL'] == 'undefined', 'Module.filePackagePrefixURL option was removed, use Module.locateFile instead'); + assert(typeof Module['read'] == 'undefined', 'Module.read option was removed'); + assert(typeof Module['readAsync'] == 'undefined', 'Module.readAsync option was removed (modify readAsync in JS)'); + assert(typeof Module['readBinary'] == 'undefined', 'Module.readBinary option was removed (modify readBinary in JS)'); + assert(typeof Module['setWindowTitle'] == 'undefined', 'Module.setWindowTitle option was removed (modify emscripten_set_window_title in JS)'); + assert(typeof Module['TOTAL_MEMORY'] == 'undefined', 'Module.TOTAL_MEMORY has been renamed Module.INITIAL_MEMORY'); + assert(typeof Module['ENVIRONMENT'] == 'undefined', 'Module.ENVIRONMENT has been deprecated. To force the environment, use the ENVIRONMENT compile-time option (for example, -sENVIRONMENT=web or -sENVIRONMENT=node)'); + assert(typeof Module['STACK_SIZE'] == 'undefined', 'STACK_SIZE can no longer be set at runtime. Use -sSTACK_SIZE at link time') + // If memory is defined in wasm, the user can't provide it, or set INITIAL_MEMORY + assert(typeof Module['wasmMemory'] == 'undefined', 'Use of `wasmMemory` detected. Use -sIMPORTED_MEMORY to define wasmMemory externally'); + assert(typeof Module['INITIAL_MEMORY'] == 'undefined', 'Detected runtime INITIAL_MEMORY setting. Use -sIMPORTED_MEMORY to define wasmMemory dynamically'); + + if (Module['preInit']) { + if (typeof Module['preInit'] == 'function') Module['preInit'] = [Module['preInit']]; + while (Module['preInit'].length > 0) { + Module['preInit'].shift()(); + } + } + consumedModuleProp('preInit'); +} + +// Begin runtime exports + var missingLibrarySymbols = [ + 'writeI53ToI64', + 'writeI53ToI64Clamped', + 'writeI53ToI64Signaling', + 'writeI53ToU64Clamped', + 'writeI53ToU64Signaling', + 'readI53FromI64', + 'readI53FromU64', + 'convertI32PairToI53', + 'convertI32PairToI53Checked', + 'convertU32PairToI53', + 'stackAlloc', + 'getTempRet0', + 'setTempRet0', + 'createNamedFunction', + 'zeroMemory', + 'getHeapMax', + 'growMemory', + 'withStackSave', + 'strError', + 'inetPton4', + 'inetNtop4', + 'inetPton6', + 'inetNtop6', + 'readSockaddr', + 'writeSockaddr', + 'readEmAsmArgs', + 'jstoi_q', + 'getExecutableName', + 'autoResumeAudioContext', + 'getDynCaller', + 'dynCall', + 'runtimeKeepalivePush', + 'runtimeKeepalivePop', + 'callUserCallback', + 'maybeExit', + 'asyncLoad', + 'asmjsMangle', + 'alignMemory', + 'mmapAlloc', + 'HandleAllocator', + 'getUniqueRunDependency', + 'addOnInit', + 'addOnPostCtor', + 'addOnPreMain', + 'addOnExit', + 'STACK_SIZE', + 'STACK_ALIGN', + 'POINTER_SIZE', + 'ASSERTIONS', + 'ccall', + 'cwrap', + 'convertJsFunctionToWasm', + 'getEmptyTableSlot', + 'updateTableMap', + 'getFunctionAddress', + 'addFunction', + 'removeFunction', + 'stringToUTF8Array', + 'stringToUTF8', + 'lengthBytesUTF8', + 'intArrayFromString', + 'intArrayToString', + 'AsciiToString', + 'stringToAscii', + 'UTF16ToString', + 'stringToUTF16', + 'lengthBytesUTF16', + 'UTF32ToString', + 'stringToUTF32', + 'lengthBytesUTF32', + 'stringToNewUTF8', + 'stringToUTF8OnStack', + 'writeArrayToMemory', + 'registerKeyEventCallback', + 'maybeCStringToJsString', + 'findEventTarget', + 'getBoundingClientRect', + 'fillMouseEventData', + 'registerMouseEventCallback', + 'registerWheelEventCallback', + 'registerUiEventCallback', + 'registerFocusEventCallback', + 'fillDeviceOrientationEventData', + 'registerDeviceOrientationEventCallback', + 'fillDeviceMotionEventData', + 'registerDeviceMotionEventCallback', + 'screenOrientation', + 'fillOrientationChangeEventData', + 'registerOrientationChangeEventCallback', + 'fillFullscreenChangeEventData', + 'registerFullscreenChangeEventCallback', + 'JSEvents_requestFullscreen', + 'JSEvents_resizeCanvasForFullscreen', + 'registerRestoreOldStyle', + 'hideEverythingExceptGivenElement', + 'restoreHiddenElements', + 'setLetterbox', + 'softFullscreenResizeWebGLRenderTarget', + 'doRequestFullscreen', + 'fillPointerlockChangeEventData', + 'registerPointerlockChangeEventCallback', + 'registerPointerlockErrorEventCallback', + 'requestPointerLock', + 'fillVisibilityChangeEventData', + 'registerVisibilityChangeEventCallback', + 'registerTouchEventCallback', + 'fillGamepadEventData', + 'registerGamepadEventCallback', + 'registerBeforeUnloadEventCallback', + 'fillBatteryEventData', + 'registerBatteryEventCallback', + 'setCanvasElementSize', + 'getCanvasElementSize', + 'jsStackTrace', + 'getCallstack', + 'convertPCtoSourceLocation', + 'getEnvStrings', + 'checkWasiClock', + 'wasiRightsToMuslOFlags', + 'wasiOFlagsToMuslOFlags', + 'initRandomFill', + 'randomFill', + 'safeSetTimeout', + 'setImmediateWrapped', + 'safeRequestAnimationFrame', + 'clearImmediateWrapped', + 'registerPostMainLoop', + 'registerPreMainLoop', + 'getPromise', + 'makePromise', + 'idsToPromises', + 'makePromiseCallback', + 'ExceptionInfo', + 'findMatchingCatch', + 'Browser_asyncPrepareDataCounter', + 'isLeapYear', + 'ydayFromDate', + 'arraySum', + 'addDays', + 'getSocketFromFD', + 'getSocketAddress', + 'FS_createPreloadedFile', + 'FS_preloadFile', + 'FS_modeStringToFlags', + 'FS_getMode', + 'FS_stdin_getChar', + 'FS_mkdirTree', + '_setNetworkCallback', + 'heapObjectForWebGLType', + 'toTypedArrayIndex', + 'webgl_enable_ANGLE_instanced_arrays', + 'webgl_enable_OES_vertex_array_object', + 'webgl_enable_WEBGL_draw_buffers', + 'webgl_enable_WEBGL_multi_draw', + 'webgl_enable_EXT_polygon_offset_clamp', + 'webgl_enable_EXT_clip_control', + 'webgl_enable_WEBGL_polygon_mode', + 'emscriptenWebGLGet', + 'computeUnpackAlignedImageSize', + 'colorChannelsInGlTextureFormat', + 'emscriptenWebGLGetTexPixelData', + 'emscriptenWebGLGetUniform', + 'webglGetUniformLocation', + 'webglPrepareUniformLocationsBeforeFirstUse', + 'webglGetLeftBracePos', + 'emscriptenWebGLGetVertexAttrib', + '__glGetActiveAttribOrUniform', + 'writeGLArray', + 'registerWebGlEventCallback', + 'runAndAbortIfError', + 'ALLOC_NORMAL', + 'ALLOC_STACK', + 'allocate', + 'writeStringToMemory', + 'writeAsciiToMemory', + 'allocateUTF8', + 'allocateUTF8OnStack', + 'demangle', + 'stackTrace', + 'getNativeTypeSize', +]; +missingLibrarySymbols.forEach(missingLibrarySymbol) + + var unexportedSymbols = [ + 'run', + 'out', + 'err', + 'callMain', + 'abort', + 'wasmExports', + 'HEAPF32', + 'HEAPF64', + 'HEAP8', + 'HEAPU8', + 'HEAP16', + 'HEAPU16', + 'HEAP32', + 'HEAPU32', + 'HEAP64', + 'HEAPU64', + 'writeStackCookie', + 'checkStackCookie', + 'INT53_MAX', + 'INT53_MIN', + 'bigintToI53Checked', + 'stackSave', + 'stackRestore', + 'ptrToString', + 'exitJS', + 'abortOnCannotGrowMemory', + 'ENV', + 'ERRNO_CODES', + 'DNS', + 'Protocols', + 'Sockets', + 'timers', + 'warnOnce', + 'readEmAsmArgsArray', + 'handleException', + 'keepRuntimeAlive', + 'wasmTable', + 'wasmMemory', + 'noExitRuntime', + 'addRunDependency', + 'removeRunDependency', + 'addOnPreRun', + 'addOnPostRun', + 'freeTableIndexes', + 'functionsInTableMap', + 'setValue', + 'getValue', + 'PATH', + 'PATH_FS', + 'UTF8Decoder', + 'UTF8ArrayToString', + 'UTF8ToString', + 'UTF16Decoder', + 'JSEvents', + 'specialHTMLTargets', + 'findCanvasEventTarget', + 'currentFullscreenStrategy', + 'restoreOldWindowedStyle', + 'UNWIND_CACHE', + 'ExitStatus', + 'flush_NO_FILESYSTEM', + 'emSetImmediate', + 'emClearImmediate_deps', + 'emClearImmediate', + 'promiseMap', + 'uncaughtExceptionCount', + 'exceptionLast', + 'exceptionCaught', + 'Browser', + 'requestFullscreen', + 'requestFullScreen', + 'setCanvasSize', + 'getUserMedia', + 'createContext', + 'getPreloadedImageData__data', + 'wget', + 'MONTH_DAYS_REGULAR', + 'MONTH_DAYS_LEAP', + 'MONTH_DAYS_REGULAR_CUMULATIVE', + 'MONTH_DAYS_LEAP_CUMULATIVE', + 'SYSCALLS', + 'preloadPlugins', + 'FS_stdin_getChar_buffer', + 'FS_unlink', + 'FS_createPath', + 'FS_createDevice', + 'FS_readFile', + 'FS', + 'FS_root', + 'FS_mounts', + 'FS_devices', + 'FS_streams', + 'FS_nextInode', + 'FS_nameTable', + 'FS_currentPath', + 'FS_initialized', + 'FS_ignorePermissions', + 'FS_filesystems', + 'FS_syncFSRequests', + 'FS_lookupPath', + 'FS_getPath', + 'FS_hashName', + 'FS_hashAddNode', + 'FS_hashRemoveNode', + 'FS_lookupNode', + 'FS_createNode', + 'FS_destroyNode', + 'FS_isRoot', + 'FS_isMountpoint', + 'FS_isFile', + 'FS_isDir', + 'FS_isLink', + 'FS_isChrdev', + 'FS_isBlkdev', + 'FS_isFIFO', + 'FS_isSocket', + 'FS_flagsToPermissionString', + 'FS_nodePermissions', + 'FS_mayLookup', + 'FS_mayCreate', + 'FS_mayDelete', + 'FS_mayOpen', + 'FS_checkOpExists', + 'FS_nextfd', + 'FS_getStreamChecked', + 'FS_getStream', + 'FS_createStream', + 'FS_closeStream', + 'FS_dupStream', + 'FS_doSetAttr', + 'FS_chrdev_stream_ops', + 'FS_major', + 'FS_minor', + 'FS_makedev', + 'FS_registerDevice', + 'FS_getDevice', + 'FS_getMounts', + 'FS_syncfs', + 'FS_mount', + 'FS_unmount', + 'FS_lookup', + 'FS_mknod', + 'FS_statfs', + 'FS_statfsStream', + 'FS_statfsNode', + 'FS_create', + 'FS_mkdir', + 'FS_mkdev', + 'FS_symlink', + 'FS_rename', + 'FS_rmdir', + 'FS_readdir', + 'FS_readlink', + 'FS_stat', + 'FS_fstat', + 'FS_lstat', + 'FS_doChmod', + 'FS_chmod', + 'FS_lchmod', + 'FS_fchmod', + 'FS_doChown', + 'FS_chown', + 'FS_lchown', + 'FS_fchown', + 'FS_doTruncate', + 'FS_truncate', + 'FS_ftruncate', + 'FS_utime', + 'FS_open', + 'FS_close', + 'FS_isClosed', + 'FS_llseek', + 'FS_read', + 'FS_write', + 'FS_mmap', + 'FS_msync', + 'FS_ioctl', + 'FS_writeFile', + 'FS_cwd', + 'FS_chdir', + 'FS_createDefaultDirectories', + 'FS_createDefaultDevices', + 'FS_createSpecialDirectories', + 'FS_createStandardStreams', + 'FS_staticInit', + 'FS_init', + 'FS_quit', + 'FS_findObject', + 'FS_analyzePath', + 'FS_createFile', + 'FS_createDataFile', + 'FS_forceLoadFile', + 'FS_createLazyFile', + 'FS_absolutePath', + 'FS_createFolder', + 'FS_createLink', + 'FS_joinPath', + 'FS_mmapAlloc', + 'FS_standardizePath', + 'MEMFS', + 'TTY', + 'PIPEFS', + 'SOCKFS', + 'tempFixedLengthArray', + 'miniTempWebGLFloatBuffers', + 'miniTempWebGLIntBuffers', + 'GL', + 'AL', + 'GLUT', + 'EGL', + 'GLEW', + 'IDBStore', + 'SDL', + 'SDL_gfx', + 'print', + 'printErr', + 'jstoi_s', +]; +unexportedSymbols.forEach(unexportedRuntimeSymbol); + + // End runtime exports + // Begin JS library exports + // End JS library exports + +// end include: postlibrary.js + +function checkIncomingModuleAPI() { + ignoredModuleProp('fetchSettings'); + ignoredModuleProp('logReadFiles'); + ignoredModuleProp('loadSplitModule'); +} + +// Imports from the Wasm binary. +var _main = Module['_main'] = makeInvalidEarlyAccess('_main'); +var _fflush = makeInvalidEarlyAccess('_fflush'); +var _strerror = makeInvalidEarlyAccess('_strerror'); +var _emscripten_stack_get_end = makeInvalidEarlyAccess('_emscripten_stack_get_end'); +var _emscripten_stack_get_base = makeInvalidEarlyAccess('_emscripten_stack_get_base'); +var _emscripten_stack_init = makeInvalidEarlyAccess('_emscripten_stack_init'); +var _emscripten_stack_get_free = makeInvalidEarlyAccess('_emscripten_stack_get_free'); +var __emscripten_stack_restore = makeInvalidEarlyAccess('__emscripten_stack_restore'); +var __emscripten_stack_alloc = makeInvalidEarlyAccess('__emscripten_stack_alloc'); +var _emscripten_stack_get_current = makeInvalidEarlyAccess('_emscripten_stack_get_current'); +var memory = makeInvalidEarlyAccess('memory'); +var __indirect_function_table = makeInvalidEarlyAccess('__indirect_function_table'); +var wasmMemory = makeInvalidEarlyAccess('wasmMemory'); + +function assignWasmExports(wasmExports) { + assert(typeof wasmExports['main'] != 'undefined', 'missing Wasm export: main'); + assert(typeof wasmExports['fflush'] != 'undefined', 'missing Wasm export: fflush'); + assert(typeof wasmExports['strerror'] != 'undefined', 'missing Wasm export: strerror'); + assert(typeof wasmExports['emscripten_stack_get_end'] != 'undefined', 'missing Wasm export: emscripten_stack_get_end'); + assert(typeof wasmExports['emscripten_stack_get_base'] != 'undefined', 'missing Wasm export: emscripten_stack_get_base'); + assert(typeof wasmExports['emscripten_stack_init'] != 'undefined', 'missing Wasm export: emscripten_stack_init'); + assert(typeof wasmExports['emscripten_stack_get_free'] != 'undefined', 'missing Wasm export: emscripten_stack_get_free'); + assert(typeof wasmExports['_emscripten_stack_restore'] != 'undefined', 'missing Wasm export: _emscripten_stack_restore'); + assert(typeof wasmExports['_emscripten_stack_alloc'] != 'undefined', 'missing Wasm export: _emscripten_stack_alloc'); + assert(typeof wasmExports['emscripten_stack_get_current'] != 'undefined', 'missing Wasm export: emscripten_stack_get_current'); + assert(typeof wasmExports['memory'] != 'undefined', 'missing Wasm export: memory'); + assert(typeof wasmExports['__indirect_function_table'] != 'undefined', 'missing Wasm export: __indirect_function_table'); + _main = Module['_main'] = createExportWrapper('main', 2); + _fflush = createExportWrapper('fflush', 1); + _strerror = createExportWrapper('strerror', 1); + _emscripten_stack_get_end = wasmExports['emscripten_stack_get_end']; + _emscripten_stack_get_base = wasmExports['emscripten_stack_get_base']; + _emscripten_stack_init = wasmExports['emscripten_stack_init']; + _emscripten_stack_get_free = wasmExports['emscripten_stack_get_free']; + __emscripten_stack_restore = wasmExports['_emscripten_stack_restore']; + __emscripten_stack_alloc = wasmExports['_emscripten_stack_alloc']; + _emscripten_stack_get_current = wasmExports['emscripten_stack_get_current']; + memory = wasmMemory = wasmExports['memory']; + __indirect_function_table = wasmExports['__indirect_function_table']; +} + +var wasmImports = { + /** @export */ + _abort_js: __abort_js, + /** @export */ + emscripten_resize_heap: _emscripten_resize_heap, + /** @export */ + fd_close: _fd_close, + /** @export */ + fd_seek: _fd_seek, + /** @export */ + fd_write: _fd_write +}; + + +// include: postamble.js +// === Auto-generated postamble setup entry stuff === + +var calledRun; + +function callMain() { + assert(runDependencies == 0, 'cannot call main when async dependencies remain! (listen on Module["onRuntimeInitialized"])'); + assert(typeof onPreRuns === 'undefined' || onPreRuns.length == 0, 'cannot call main when preRun functions remain to be called'); + + var entryFunction = _main; + + var argc = 0; + var argv = 0; + + try { + + var ret = entryFunction(argc, argv); + + // if we're not running an evented main loop, it's time to exit + exitJS(ret, /* implicit = */ true); + return ret; + } catch (e) { + return handleException(e); + } +} + +function stackCheckInit() { + // This is normally called automatically during __wasm_call_ctors but need to + // get these values before even running any of the ctors so we call it redundantly + // here. + _emscripten_stack_init(); + // TODO(sbc): Move writeStackCookie to native to to avoid this. + writeStackCookie(); +} + +function run() { + + if (runDependencies > 0) { + dependenciesFulfilled = run; + return; + } + + stackCheckInit(); + + preRun(); + + // a preRun added a dependency, run will be called later + if (runDependencies > 0) { + dependenciesFulfilled = run; + return; + } + + function doRun() { + // run may have just been called through dependencies being fulfilled just in this very frame, + // or while the async setStatus time below was happening + assert(!calledRun); + calledRun = true; + Module['calledRun'] = true; + + if (ABORT) return; + + initRuntime(); + + preMain(); + + Module['onRuntimeInitialized']?.(); + consumedModuleProp('onRuntimeInitialized'); + + var noInitialRun = Module['noInitialRun'] || false; + if (!noInitialRun) callMain(); + + postRun(); + } + + if (Module['setStatus']) { + Module['setStatus']('Running...'); + setTimeout(() => { + setTimeout(() => Module['setStatus'](''), 1); + doRun(); + }, 1); + } else + { + doRun(); + } + checkStackCookie(); +} + +function checkUnflushedContent() { + // Compiler settings do not allow exiting the runtime, so flushing + // the streams is not possible. but in ASSERTIONS mode we check + // if there was something to flush, and if so tell the user they + // should request that the runtime be exitable. + // Normally we would not even include flush() at all, but in ASSERTIONS + // builds we do so just for this check, and here we see if there is any + // content to flush, that is, we check if there would have been + // something a non-ASSERTIONS build would have not seen. + // How we flush the streams depends on whether we are in SYSCALLS_REQUIRE_FILESYSTEM=0 + // mode (which has its own special function for this; otherwise, all + // the code is inside libc) + var oldOut = out; + var oldErr = err; + var has = false; + out = err = (x) => { + has = true; + } + try { // it doesn't matter if it fails + flush_NO_FILESYSTEM(); + } catch(e) {} + out = oldOut; + err = oldErr; + if (has) { + warnOnce('stdio streams had content in them that was not flushed. you should set EXIT_RUNTIME to 1 (see the Emscripten FAQ), or make sure to emit a newline when you printf etc.'); + warnOnce('(this may also be due to not including full filesystem support - try building with -sFORCE_FILESYSTEM)'); + } +} + +var wasmExports; + +// With async instantation wasmExports is assigned asynchronously when the +// instance is received. +createWasm(); + +run(); + +// end include: postamble.js + diff --git a/wasm_check2.wasm b/wasm_check2.wasm new file mode 100755 index 0000000..a0d0163 Binary files /dev/null and b/wasm_check2.wasm differ