Native Data.Int.Bits intrinsics; fix a dropped perform of user Effect functions - #42
Conversation
…oreign path already does)
…opping it A discarded `perform` of a non-inlined user-defined `Effect`-returning function (e.g. `perform (bad "x")`) silently dropped its effect: `isEffectForeignApp` treated any binding with an `MEffect` *reconstructed* signature (ADR-0016) as a host foreign, so it was lowered as a bare producer value — a partial closure built but never applied to the perform-unit — instead of a direct call. The fix excludes `knownFuncs` (anything with a decl body) from the host-foreign check, mirroring the existing intrinsic / `foreignIntrinsic` exclusions, and lowers a performed non-foreign application by appending the perform-unit to the producer's own argument list (ADR-0018) so it saturates to a direct `RCallKnown`. Host foreigns (`log`) are unaffected — the host still performs the returned thunk. Refs ADR-0015 (perform lowering), ADR-0016 (signature reconstruction), ADR-0018 (arity includes the perform-unit).
… Effect fns This bug was invisible: nothing asserted that a discarded `perform (f x)` actually ran, so the dropped effect failed silently with a fully green suite. This adds the guard that would have caught it on day one. Adds fixture E2E.PerformUserEffect + its Cli spec. `bump` is a user-defined Effect function padded past the inline cap and referenced more than once, so its call site survives as `perform (bump k)` — the exact path that regressed; a small/inlined helper would pass and prove nothing. `bump` performs a host foreign (`record`), so it lands in impureKeys and its perform survives the simplifier — pinning any failure to lowering, not purity. The effect is read back as an accumulated total via callI32x1; that total equals the sum of the bumped values iff each perform ran exactly once (catching both drops, which read low, and accidental duplications, which read high). Three cases cover the discard paths that all route through the same isEffectForeignApp fix: do-notation (bindBody) => 11, void (mapBody) => 2, and `*>` (applyBody) => 7. Lives in the routinely-run e2e lane, not test:bin.
|
I ran the benchmarks just now too: |
|
Hi, thank you for this! Both changes are well-reasoned, and the commit messages explain the intent clearly.
Additionally, CI hasn't run on the branch yet (no By the way, the |
|
It's going to be a long time before I get around to all of that. So, you have my full permission to either: A: take what you like and delete the rest B: wait a few weeks + for me to slowly make those cosmetic changes 💀 Thanks again for making this module. |
|
OK, then can I push a few follow-up changes, including some documentation addenda and removing unnecessary project configuration changes (namely Nix and npm)? After that, I'll merge this PR! |
|
Thanks! I'm still not great at formal PR's and I genuinely just want to help. :). If there's anything you need help on, I'll be here, using it in branches of my Prescript projects to try and uncover niceties/necessities that I could add and you can rest assured that my next PR will more closely follow your guidelines. 😊 |
Summary
Three changes, two independent:
Data.Int.Bitsnow lowers to nativei32instructions instead of JS foreigns.performof a non-inlined userEffectfunction no longer silently drops its effect.(1) and (2) are separable into two PRs if you'd rather keep them atomic; (3) belongs with (2).
Checklist
CI passing is enforced by the required
ci-gatestatus check, not by a box here.The items below are the human-judgment gates.
Data.Int.Bitsintrinsic family in the Intrinsics prose, and theknownFuncsexclusion in ADR-0015/0016 (foreignSigscovers all top-level values, so the host-foreign predicate must blacklistknownFuncs). Neither is written yet.E2E.PerformUserEffect, routinely-run lane, nottest:bin). The bit ops are exercised end-to-end by a downstream pure-PureScript SHA-3 implementation whose NIST vectors now pass on wasm; a dedicated compiler-side e2e for the bit ops is not yet added.node bench/run.mjsacross 3 runs. Heavy/stable benchmarks pinned at 1.00x; the two lightest (bintreeDfs, curry) swing ±~10% run-to-run and center on 1.0x (bintreeDfs measured both 1.11x and 0.86x), i.e. noise, not a regression. Baseline unchanged.i32instructions, and the perform fix saturates within the existing eqref / eval-apply convention.