Skip to content

A Nat crosses to a JS host as a plain number - #1506

Merged
nicolas-abril merged 2 commits into
bendlang:mainfrom
nicolas-abril:simplify-c2-nat-number
Oct 12, 2026
Merged

nicolas-abril merged 2 commits into
bendlang:mainfrom
nicolas-abril:simplify-c2-nat-number

Conversation

@nicolas-abril

@nicolas-abril nicolas-abril commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator

The JS runtime has held Nats as numbers since 87a1e9c (2.3x), and it kept BigInt only at the host seam. This PR removes that seam conversion. An effect's .js gets and answers a number, and a def exported through the .bend loader or -o x.mjs takes and returns one. js_marshal, js_host, nat_host and the marsh memo are gone. comp.ts goes from 63,788 to 62,883 ttok.

Breaking for JS callers: a host that passed or expected 5n now uses 5. EFFECTS.md, main.ts's header, effs/now.js and the app_win_is_bug_2d demo are updated.

Nat check at the seam

nat_in lets a value through only if it's a whole number from 0 to 2^53. Anything else stops the run with the usual Nat error, as the C lane does. It runs on:

  • each answer of an effect whose type is exactly Nat
  • each Nat argument of an exported def, curried calls included

Nats nested in fields or containers aren't checked. EFFECTS.md says so.

Tests

This also fixes the return-type marshal behind #1150.

Validation (on main c25ee78)

  • Mac gates: repo 54/54, ping 55/55, test 1604/1604, perf 124/124.
  • Linux (x86_64, bun 1.4.2, local run of gates/test.ts): test 1604/1604, the same as main.
  • Generated C: byte-identical to main for all 17 runtime benches.
  • JS export calls: 2M inc calls take 74 ms on main, 21 ms here (20 ms without the check).

@Lorenzobattistela Lorenzobattistela left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The core change looks right: C output is byte-identical, gates pass (repo 54/54, test 1600/1600, ping 49/49, perf 124/124), comp.ts drops to 62,766, JS export calls get 2-5x faster, and #1150 is fixed.

One regression: a bad Nat answer from a JS host is no longer caught. On main it fail-stopped, and C still does. With a top-level IO(Nat) effect:

  • -3 prints - and ., exits 0 (C fail-stops at first use)
  • -1 gives n+1 = 0
  • 1.5 prints 1, 2
  • NaN hangs in Nat.show
  • "5" gives 51 for n+1

So the description's "JS runs a negative host answer raw, the same as C" isn't accurate: C fail-stops on -3, JS prints garbage. Deleting nat_host_neg drops the one test that pinned the two lanes together. Could you keep a small check on effect answers whose type is exactly Nat (one wrapper per effect call, no walk, no export cost), and keep nat_host_neg? Nested fields and exported-def args can stay unchecked.

Also, deleting marshal_tag_unknown means B.kind({$:"Sq"}) across two libraries now silently takes the wrong branch instead of throwing the #1105 error. That's fine if #1488 lands too, since it removes the cause. The two PRs conflict (comp.ts, marshal_tag_unknown), so I'd merge this one first and have #1488 rebase without its marshal parts.

nicolas-abril and others added 2 commits October 12, 2026 16:21
The JS runtime has held Nats as numbers since 87a1e9c; only the host
seam converted them to BigInt and back. js_marshal, js_host and nat_host
are gone: an effect gets and answers a number, and an exported def takes
and returns one, so the C and JS lanes agree on the value model.

Deletes nat_host_neg (a negative host Nat was caught only by the
marshal) and marshal_tag_unknown (the bendlang#1105 tag check ran only for types
holding a Nat). Renames marshal_nat_bigint to marshal_nat_number.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An exported def checks each argument whose type is Nat, and a foreign
def checks an answer whose type is Nat, through nat_in: a negative,
fractional or BigInt value, or one past 2^53, stops with the Nat error
as C does, instead of running on as a bad Nat. A Nat inside a value is
the host's to keep in range, as EFFECTS.md says.

Brings back nat_host_neg on every lane and adds nat_host_export.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nicolas-abril

Copy link
Copy Markdown
Collaborator Author

Rebased on main (c25ee78, after #1509 and #1515) and added a check for Nats coming in from JS.

Check. A runtime nat_in lets a value through only if it is a whole number from 0 to 2^53. Anything else stops the run with the usual Nat error, as C does. It runs in two places:

  • on each argument of an exported def whose type is Nat
  • on a foreign def's answer when the answer's type is Nat (it wraps kont)

So inc(-1), inc(1.5), inc(1n) now throw. Before this, they returned garbage. A Nat inside a value (List<Nat>, a field) is not checked. EFFECTS.md now says that's the host's job. The bound stays at 2^53, as on main, so nat_host_bounds still passes on both lanes.

Tests. nat_host_neg is back on every lane. It now prints only Nat.show, so C and JS both stop with the same message. nat_host_export checks bad and good arguments, called directly and curried. The negative case moved out of marshal_nat_number.

Numbers. comp.ts is 62,883 tokens against 63,788 on main. The check costs 221 of the 1,126 this PR saves. GUIDE.md is unchanged.

Gates (bun gates/_run.ts): repo 54/54, ping 55/55, test 1604/1604, perf 124/124.

Unsupervised AI review.

@nicolas-abril
nicolas-abril force-pushed the simplify-c2-nat-number branch from 485374e to 2ac8896 Compare October 12, 2026 14:22
@nicolas-abril

Copy link
Copy Markdown
Collaborator Author

Linux check (x86_64, bun 1.4.2, gcc 16): gates/test.ts run locally, PASS 1604/1604 on this PR and on main c25ee78. Generated C is identical to main on all runtime benches. The generated JS differs by 12 lines per bench, all from nat_in and the export wrapper, and none of them are in the inner loops. Export calls with the check: 21 ms vs 20 ms without it (2M calls; main 74 ms). Not run: CUDA, cluster perf on Linux.

Unsupervised AI review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants