@sproutboat/toolchain 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/PORFFOR_ALPHA16.md +67 -0
- package/PORFFOR_ALPHA17.md +59 -0
- package/PORFFOR_SCORECARD.md +7 -8
- package/package.json +1 -1
- package/scorecards/alpha-16-43087d4.json +72 -0
- package/src/acquire.test.ts +5 -3
- package/src/acquire.ts +1 -1
- package/src/alpha16-native.test.ts +83 -0
- package/src/alpha17-native.test.ts +42 -0
- package/src/patch.test.ts +38 -1
- package/src/patch.ts +46 -70
- package/src/pin.ts +4 -4
- package/vendor/porffor-731f1b2.tar.gz +0 -0
- package/vendor/porffor-72d048d.tar.gz +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @sproutboat/toolchain
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 14546a0: Upgrade the verified vendored Porffor pin to Alpha 17, including correct Math.round ties and signed zero, native number parsing and formatting, full console number precision, and class-field capture fixes. Keep downstream compatibility patches until equivalent upstream changes ship.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 14546a0: Register direct-only local function expressions in their lexical scope so type inference does not borrow an unrelated same-named function's return type. Fixes Better Auth's model-name resolver incorrectly treating an existing user schema as missing.
|
|
12
|
+
|
|
13
|
+
Correct Uint8Array base64 option string tags and complete-chunk truncation, unblocking base64url signatures and fixing padding and decoding.
|
|
14
|
+
|
|
15
|
+
## 0.6.0
|
|
16
|
+
|
|
17
|
+
### Minor Changes
|
|
18
|
+
|
|
19
|
+
- Pin and vendor Porffor alpha-16 (43087d4). Enable Proxy and revocable Proxy in handler validation, add native regression coverage for traps and iterators, and drop three typed-array patches now implemented upstream. See PORFFOR_ALPHA16.md for the complete change audit and remaining package compatibility limits.
|
|
20
|
+
|
|
3
21
|
## 0.5.0
|
|
4
22
|
|
|
5
23
|
### Minor Changes
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Porffor alpha-16 upgrade audit
|
|
2
|
+
|
|
3
|
+
Source: [alpha-16](https://github.com/CanadaHonk/porffor/releases/tag/alpha-16), published October 10, 2026, commit `43087d42f0b90d14f04f3cddac034822560aefad`. The vendored codeload archive has SHA-256 `84398fd214183801d252a78e69a44fac1baeba7cafc7dbbcfd5c6ec3b0372032`. The [complete alpha-15 comparison](https://github.com/CanadaHonk/porffor/compare/alpha-15...alpha-16) contains 32 commits. uWebSockets stays at `360c276d609d59af56ae6932adb95154ace9f15f`.
|
|
4
|
+
|
|
5
|
+
## Supported changes
|
|
6
|
+
|
|
7
|
+
The runtime accepts `new Proxy` and `Proxy.revocable`. Native output matches Bun for get/set/has/deleteProperty, ownKeys/getOwnPropertyDescriptor through Object.keys, apply, construct, revocation, and a frozen-property invariant. This is regression coverage for these operations, not certification of all Proxy invariants or exotic targets.
|
|
8
|
+
|
|
9
|
+
Array and Map iterators and lazy matchAll now expose next/done, and Set spread works. Sproutboat's URLSearchParams and FormData shims now provide live, self-iterable keys/values/entries and default iteration. Appending after creating an iterator is observed on its next step, duplicate fields remain intact, and FormData filenames do not become iterator values.
|
|
10
|
+
|
|
11
|
+
Three local patches are removed because the shared typed-array implementation handles array-like from(), an omitted set() offset and join()/toString(). Builtin precompilation remains necessary for the date, promise, replaceAll and Unicode patches. Existing typed-array bounds and class self-name patches still apply. New native regressions cover clamped ties-to-even, class static this, null-prototype __proto__, binary evaluation order and Unicode console output.
|
|
12
|
+
|
|
13
|
+
## Package probes and opportunities
|
|
14
|
+
|
|
15
|
+
Native standalone HTTP probes on macOS arm64, pinned dependencies:
|
|
16
|
+
|
|
17
|
+
| Package | Operation | Result |
|
|
18
|
+
| --- | --- | --- |
|
|
19
|
+
| qs 6.16.0 | Nested parse and stringify, including array parameters | Passed |
|
|
20
|
+
| @trpc/server 11.19.0 | initTRPC, router and createCaller().hello() | Passed; HTTP adapters, clients, middleware and subscriptions untested |
|
|
21
|
+
| itty-router 5.0.24 Router | Parameter route, POST and fallback handler | Compiler error: labelled break crossing iterator cleanup (`target.brk`) |
|
|
22
|
+
| itty-router 5.0.24 IttyRouter | Parameter route, POST and fallback handler | Passed GET parameters, Unicode POST and 404 fallback after adding live Web API iterators |
|
|
23
|
+
|
|
24
|
+
Proxy-based object facades, method dispatch and server callers are now practical candidates. tRPC's basic caller path is verified, so a fetch-adapter and middleware matrix is a useful next step. Reactive stores and schema libraries must be tested on their own inputs before being listed as supported. Better Auth still needs a separate crypto and framework compatibility evaluation; Proxy alone does not establish auth support. Streams and WebSockets remain separate host API gaps.
|
|
25
|
+
|
|
26
|
+
String iteration still splits astral characters into UTF-16 halves: Array.from("🚤x") produces two surrogate entries and x rather than one emoji and x. Unicode console output itself works. Standard itty-router Router also remains blocked by a separate compiler control-flow bug. No upstream issue was filed.
|
|
27
|
+
|
|
28
|
+
## Complete change review
|
|
29
|
+
|
|
30
|
+
| Commit | Upstream change | Sproutboat impact |
|
|
31
|
+
| --- | --- | --- |
|
|
32
|
+
| [c730268](https://github.com/CanadaHonk/porffor/commit/c730268d087766a87502de0045eb08b1f966d7d2) | codegen: evaluate binary lhs before rhs statements | Fixes operand side-effect order; native regression. |
|
|
33
|
+
| [d24dc0a](https://github.com/CanadaHonk/porffor/commit/d24dc0ad569bfd73a2dd6e3627c0cc21619bffc2) | codegen: balance template literal concats | Balances concatenation trees; compiler improvement, no new API. |
|
|
34
|
+
| [4598bec](https://github.com/CanadaHonk/porffor/commit/4598becfc46865d9ea18ad338a650c98f354af51) | precompile: store builtin table as arrays | Changes builtin table representation; patched builtins still precompile. |
|
|
35
|
+
| [f9eb038](https://github.com/CanadaHonk/porffor/commit/f9eb038c3aa4856d861530beb2423236536beb77) | builtins: bucket auto func keys by prefix | Compiler lookup optimization; no new API. |
|
|
36
|
+
| [fd8f58a](https://github.com/CanadaHonk/porffor/commit/fd8f58a8374f6adf308056b40cae8d59fc31dcb0) | builtins: check builtin existence without decoding | Compiler lookup optimization; no new API. |
|
|
37
|
+
| [b7b8ba2](https://github.com/CanadaHonk/porffor/commit/b7b8ba2fdd4fd12340aba048c578ac2b5c3d3aef) | builtins: add constructor to all builtin prototypes | Builtin prototype constructor identity becomes available; do not infer subclass compatibility. |
|
|
38
|
+
| [95522a4](https://github.com/CanadaHonk/porffor/commit/95522a421ddedb5b6801d4c1f7c6706fab3cebe0) | selfhost: label bench old/new with --compare | Upstream benchmark reporting only. |
|
|
39
|
+
| [860485e](https://github.com/CanadaHonk/porffor/commit/860485e9821894864fb88389f7aba5750c9f09a6) | builtins/console: print strings as utf-8 | Native Unicode console strings; native regression. |
|
|
40
|
+
| [2213f53](https://github.com/CanadaHonk/porffor/commit/2213f531bda917ee3ec865c1dd92b9709dfd0d27) | codegen: include String.prototype methods for bytestrings | String method dispatch on bytestrings; existing handler tests cover common methods. |
|
|
41
|
+
| [3502f8a](https://github.com/CanadaHonk/porffor/commit/3502f8a213b7de2479ba8673a92fc4eb8013fe51) | codegen: generate reassigned hoisted function bodies later | Hoisted function reassignment codegen fix; no new host API. |
|
|
42
|
+
| [77ee767](https://github.com/CanadaHonk/porffor/commit/77ee767b6c8c1ea59ff82e59d7b660c942b75047) | semantic: class static blocks and fields use the class as this | Static fields and blocks see the class as this; native regression. |
|
|
43
|
+
| [2e28260](https://github.com/CanadaHonk/porffor/commit/2e28260bc77048324437e53406c6a7041dba12e0) | fetch: treat null response body as empty | Null Response body becomes empty; native HTTP regression. |
|
|
44
|
+
| [641cd59](https://github.com/CanadaHonk/porffor/commit/641cd5911142d65d7f00d70f39360139349f707c) | builtins: call builtin Symbol directly for well-known symbols | Well-known symbol identity; enables iterator protocols. |
|
|
45
|
+
| [e684407](https://github.com/CanadaHonk/porffor/commit/e68440705a35a2d5e1bee9c67586523f281e80f6) | builtins: add statics to builtin constructors used as values | Builtin constructors used as values retain static methods. |
|
|
46
|
+
| [1e3428e](https://github.com/CanadaHonk/porffor/commit/1e3428e167505f969167f70d1ed0bd3c049256af) | codegen: only type computed string reads with number keys as strings | More accurate computed-string read types; no new API. |
|
|
47
|
+
| [a52e55b](https://github.com/CanadaHonk/porffor/commit/a52e55b59db43170db7831905afb3450c8404d71) | builtins/console: print maps and sets from the hashtable | Console Map/Set formatting follows hashtable representation. |
|
|
48
|
+
| [9579dbb](https://github.com/CanadaHonk/porffor/commit/9579dbb6379617ac8d35dc5177e035b4c6aa7127) | builtins/array: spread sets and maps directly | Map/Set spread; native Set regression. |
|
|
49
|
+
| [fceed9a](https://github.com/CanadaHonk/porffor/commit/fceed9a7f84afc5619da34938f89475f05f7b63d) | builtins: return iterators from array, map, set and string iterator methods | Real collection iterators; native next/done and Map keys regressions. Astral string iteration remains incorrect. |
|
|
50
|
+
| [532d8f8](https://github.com/CanadaHonk/porffor/commit/532d8f85f3961037d2673aaf06fbf008ec57ff07) | builtins/generator: add Symbol.iterator and Symbol.asyncIterator | Generator iterator protocol; infrastructure for iterable libraries. Async generator coverage remains unestablished. |
|
|
51
|
+
| [872edc0](https://github.com/CanadaHonk/porffor/commit/872edc027a220f18baa82edff4166e5e38e9ded3) | builtins/regexp: make matchAll a lazy iterator | Lazy matchAll next/done; native regression. |
|
|
52
|
+
| [64f5299](https://github.com/CanadaHonk/porffor/commit/64f529913c3ea39a162da9516b0371665107347e) | builtins: add Proxy | Proxy implementation; lift source ban and test object/call/construct/revocation behavior. |
|
|
53
|
+
| [2056abb](https://github.com/CanadaHonk/porffor/commit/2056abb1b8c8fad65f50f0ad4b0ccf00f2799150) | repl: force new line | Upstream REPL formatting only. |
|
|
54
|
+
| [905b193](https://github.com/CanadaHonk/porffor/commit/905b193b2d5626fe796dda1290b39f176f4ba191) | builtins/hashtable: allocate the container after its arrays | Allocation ordering fix; exercise Map/Set in native regression. |
|
|
55
|
+
| [1f6959b](https://github.com/CanadaHonk/porffor/commit/1f6959b82c2479a4c003be03d6ac829ccf3f9c7d) | codegen: round half to even in Uint8ClampedArray stores | Uint8ClampedArray ties-to-even stores; native regression. |
|
|
56
|
+
| [8e76e7b](https://github.com/CanadaHonk/porffor/commit/8e76e7b8f0ea64a30974cc116bbe9c9671e7c9f4) | builtins: define builtin $get funcs as accessors | Builtin getters become accessors; typed-array prototype rewrite replaces old generation anchors. |
|
|
57
|
+
| [05199be](https://github.com/CanadaHonk/porffor/commit/05199be91c124f5936c2e40d5164227ddb9b0fb9) | builtins: get builtin constructors via their getters | Builtin constructors are obtained through getters; source patches reviewed. |
|
|
58
|
+
| [79bb578](https://github.com/CanadaHonk/porffor/commit/79bb5789b4fe9d1892ca322be6327984f2d40ca5) | builtins/typedarray: share %TypedArray% and %TypedArray%.prototype | Shared typed-array methods/prototypes; drop from, set-offset and join patches, retain behavioral regressions. |
|
|
59
|
+
| [0a22e14](https://github.com/CanadaHonk/porffor/commit/0a22e1494ddf793fb3bb0c4b0b3740531931f3ef) | codegen: sort builtin proto call cases by type | Codegen case ordering optimization; no new API. |
|
|
60
|
+
| [d5e95d0](https://github.com/CanadaHonk/porffor/commit/d5e95d0a7f91c9cc64b7766980109b8d862b4470) | codegen: only set prototype for plain __proto__ props in object literals | Only plain __proto__ literal keys change prototypes; dictionary semantics improve. |
|
|
61
|
+
| [1b51d58](https://github.com/CanadaHonk/porffor/commit/1b51d58b859741688b87551470d2260606ab9698) | builtins/object: treat __proto__ as a normal key on null prototype objects | Null-prototype __proto__ is an ordinary key; native regression. |
|
|
62
|
+
| [7a6ca5d](https://github.com/CanadaHonk/porffor/commit/7a6ca5d32f56ff7d9b1fbaaf59fafede5a496771) | parser: drop __proto__ scope slot workaround | Removes parser workaround after __proto__ fixes; class self-name patch still needed. |
|
|
63
|
+
| [43087d4](https://github.com/CanadaHonk/porffor/commit/43087d42f0b90d14f04f3cddac034822560aefad) | ci: update expected test262 harness passes | Upstream conformance expectation update; does not establish Sproutboat support. |
|
|
64
|
+
|
|
65
|
+
## Validation
|
|
66
|
+
|
|
67
|
+
The isolated release package suite passed 174 tests, and the CLI release suite passed 103. Both release kitchen-sink harnesses passed: 35 broker checks and 38 standalone checks. All 21 small examples passed on alpha-16. Native crypto, Unicode broker round trips and live Web API iterator checks passed. All 32 compatibility fixtures compile and 30 match Bun; the two existing non-ISO date parsing mismatches remain. The full native feature regression is in src/alpha16-native.test.ts; live Web API iterator coverage is in ../runtime/test/native-iterators.ts. The scorecard ties compatibility results to this exact source pin and patch inventory.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Porffor Alpha 17 evaluation
|
|
2
|
+
|
|
3
|
+
Verified on 2026-10-11. Released 2026-10-10:
|
|
4
|
+
https://github.com/CanadaHonk/porffor/releases/tag/alpha-17
|
|
5
|
+
|
|
6
|
+
Pinned commit: `731f1b2a150d441ed22da3d14f8b1e0daaae8ee4`.
|
|
7
|
+
Vendored archive SHA256:
|
|
8
|
+
`8eb65a3e77d58a23bfd2a1a60328672ad50e1ced7931f8d0ca3108e29d6cf846`.
|
|
9
|
+
|
|
10
|
+
## Changes since Alpha 16
|
|
11
|
+
|
|
12
|
+
- Number conversion moves to C, covering Number, parseInt, parseFloat and
|
|
13
|
+
formatting methods, including nondecimal radix conversion.
|
|
14
|
+
- Math.round implements JavaScript tie behavior and preserves negative zero.
|
|
15
|
+
- Console prints full number precision and signed zero.
|
|
16
|
+
- Class-field initializers ignore captures local to the initializer.
|
|
17
|
+
- Test262 history is updated. No new Proxy or Web Streams implementation ships.
|
|
18
|
+
|
|
19
|
+
The native regression compares dynamic numeric conversion, rounding, signed
|
|
20
|
+
zero, formatting and class-field captures against Bun. Existing stream,
|
|
21
|
+
base64 and lexical model-resolver regressions also run on the new pin.
|
|
22
|
+
|
|
23
|
+
## Downstream fixes retained
|
|
24
|
+
|
|
25
|
+
Web Streams still need the embedded ponyfill and compiler adaptations.
|
|
26
|
+
Better Auth's local model resolver needs lexical function registration and
|
|
27
|
+
correct return-type inference. Uint8Array base64 needs option string tag and
|
|
28
|
+
complete-chunk corrections. Alpha 17 does not contain these fixes.
|
|
29
|
+
|
|
30
|
+
Upstream main is one commit ahead at
|
|
31
|
+
`0342e06d1c893c15eb9d22c5fc2bfe2c5965c81b`:
|
|
32
|
+
https://github.com/CanadaHonk/porffor/commit/0342e06d1c893c15eb9d22c5fc2bfe2c5965c81b
|
|
33
|
+
It fixes TextEncoder astral and lone-surrogate encoding and encodeInto's short
|
|
34
|
+
buffer behavior. This overlaps our Unicode encoder patch and may let us remove
|
|
35
|
+
that patch after the change ships. Its unpatched encoder already passes our
|
|
36
|
+
native Unicode, lone-surrogate and short-buffer regression in an isolated copy. We keep the released pin and our verified patch for now.
|
|
37
|
+
|
|
38
|
+
## Better Auth
|
|
39
|
+
|
|
40
|
+
Version 1.7.1 passes native signup, signed-cookie sessions, wrong-password
|
|
41
|
+
rejection, signin, signout and session revocation using an explicit memory
|
|
42
|
+
adapter and its default password hashing. Optimized credential operations take
|
|
43
|
+
about 6 to 8 seconds on the development machine. The memory adapter is a probe,
|
|
44
|
+
not persistent storage. Production adapters and OAuth/plugins remain untested.
|
|
45
|
+
Database-free sessions select JWE and require AES, which native crypto does not
|
|
46
|
+
support. See the runtime's WEB_STREAMS.md for the stream transport limits.
|
|
47
|
+
|
|
48
|
+
## Verification
|
|
49
|
+
|
|
50
|
+
The development branches pass 183 package tests and 114 CLI tests; broker kitchen
|
|
51
|
+
sink passes 37 checks, standalone kitchen sink passes 40 checks, and all 21
|
|
52
|
+
small examples pass. The isolated package release passes 177 tests. Native
|
|
53
|
+
crypto, Unicode, live iterators, streams, base64,
|
|
54
|
+
model resolution and global timer checks pass. Native timer functions are also
|
|
55
|
+
exposed on globalThis for libraries using host-task yields.
|
|
56
|
+
|
|
57
|
+
The stream ponyfill IIFE still fails with a prototype TypeError on Alpha 17;
|
|
58
|
+
its module-scope adaptation remains necessary. The saved standalone auth probe
|
|
59
|
+
also passes with fresh noble hashes 2.4 and zod 4.6.5 dependencies.
|
package/PORFFOR_SCORECARD.md
CHANGED
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
# Porffor pin scorecard
|
|
2
2
|
|
|
3
|
-
Pin: alpha-
|
|
3
|
+
Pin: alpha-16 (43087d42f0b90d14f04f3cddac034822560aefad). Compatibility report: 2026-10-10T08:51:03.637Z.
|
|
4
4
|
|
|
5
5
|
| Measure | Result |
|
|
6
6
|
| --- | ---: |
|
|
7
7
|
| Fixtures compiled | 32/32 |
|
|
8
8
|
| Behavior matches | 30/32 |
|
|
9
|
-
| Median / p95 compile, ms |
|
|
10
|
-
| Median / p95 binary, bytes |
|
|
11
|
-
| Patch markers |
|
|
12
|
-
| Upstream files written |
|
|
9
|
+
| Median / p95 compile, ms | 13266 / 33619 |
|
|
10
|
+
| Median / p95 binary, bytes | 1082960 / 1099536 |
|
|
11
|
+
| Patch markers | 35 |
|
|
12
|
+
| Upstream files written | 9 |
|
|
13
13
|
| Fresh patch application | pass |
|
|
14
14
|
|
|
15
|
-
Previous snapshot: alpha-
|
|
15
|
+
Previous snapshot: alpha-15-72d048d.json. Matches 30/32 to 30/32; patch markers 38 to 35.
|
|
16
16
|
|
|
17
17
|
## Patch inventory
|
|
18
18
|
|
|
19
19
|
- `compiler/render.js`: PORT_MARKER, CONSOLE_MARKER, BYTESTRING_MARKER, READ_RAW_MARKER
|
|
20
20
|
- `compiler/index.js`: LINK_MARKER, CFLAGS_MARKER, FP_CONTRACT_MARKER, HTTP10_IMPORT_MARKER, HTTP10_CALL_MARKER
|
|
21
|
-
- `compiler/builtins/typedarray.js`: TYPED_ARRAY_FROM_MARKER, TA_SET_OFFSET_MARKER, TA_JOIN_MARKER
|
|
22
21
|
- `compiler/builtins/date.ts`: DATE_PARSER_MARKER
|
|
23
22
|
- `compiler/builtins/promise.ts`: PROMISE_NULL_MARKER
|
|
24
23
|
- `compiler/builtins/string.ts`: REPLACE_ALL_MARKER
|
|
@@ -27,7 +26,7 @@ Previous snapshot: alpha-13-547c781.json. Matches 30/32 to 30/32; patch markers
|
|
|
27
26
|
- `compiler/uwebsockets.js`: HTTP10_SHIM_MARKER, BODY_MARKER, STATUS_SIG_MARKER, STATUS_303_MARKER, STATUS_DEFAULT_MARKER, HDR_SIG_MARKER, HDR_CALL_MARKER, HDR_CAP_MARKER, HDR_SKIP_MARKER, HDR_APPEND_MARKER, READ_RAW_DECL_MARKER, FORBIDDEN_HDR_MARKER, WRITE_RESPONSE_MARKER, R2_TRANSFER_MARKER, R2_TRANSFER_CALL_MARKER, R2_DOWNLOAD_CACHE_MARKER, R2_RANGE_CACHE_MARKER, R2_CONDITIONAL_CACHE_MARKER, R2_STREAMING_CACHE_MARKER, R2_RANGE_HEADER_CACHE_MARKER
|
|
28
27
|
- `compiler/builtins_precompiled.js`: regenerated builtin table
|
|
29
28
|
|
|
30
|
-
Patch source SHA-256: `
|
|
29
|
+
Patch source SHA-256: `65a48b098fb6bd355785684774be43524d31491c89be1f6b0dee17f24b9cdd20`.
|
|
31
30
|
|
|
32
31
|
## Known behavior gaps
|
|
33
32
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"pin": "alpha-16 (43087d42f0b90d14f04f3cddac034822560aefad)",
|
|
3
|
+
"reportGeneratedAt": "2026-10-10T08:51:03.637Z",
|
|
4
|
+
"fixtures": 32,
|
|
5
|
+
"compiled": 32,
|
|
6
|
+
"matched": 30,
|
|
7
|
+
"medianCompileMs": 13266,
|
|
8
|
+
"p95CompileMs": 33619,
|
|
9
|
+
"medianBinaryBytes": 1082960,
|
|
10
|
+
"p95BinaryBytes": 1099536,
|
|
11
|
+
"patchSourceSha256": "65a48b098fb6bd355785684774be43524d31491c89be1f6b0dee17f24b9cdd20",
|
|
12
|
+
"patchMarkerCount": 35,
|
|
13
|
+
"patchTargets": {
|
|
14
|
+
"compiler/render.js": [
|
|
15
|
+
"PORT_MARKER",
|
|
16
|
+
"CONSOLE_MARKER",
|
|
17
|
+
"BYTESTRING_MARKER",
|
|
18
|
+
"READ_RAW_MARKER"
|
|
19
|
+
],
|
|
20
|
+
"compiler/index.js": [
|
|
21
|
+
"LINK_MARKER",
|
|
22
|
+
"CFLAGS_MARKER",
|
|
23
|
+
"FP_CONTRACT_MARKER",
|
|
24
|
+
"HTTP10_IMPORT_MARKER",
|
|
25
|
+
"HTTP10_CALL_MARKER"
|
|
26
|
+
],
|
|
27
|
+
"compiler/builtins/date.ts": [
|
|
28
|
+
"DATE_PARSER_MARKER"
|
|
29
|
+
],
|
|
30
|
+
"compiler/builtins/promise.ts": [
|
|
31
|
+
"PROMISE_NULL_MARKER"
|
|
32
|
+
],
|
|
33
|
+
"compiler/builtins/string.ts": [
|
|
34
|
+
"REPLACE_ALL_MARKER"
|
|
35
|
+
],
|
|
36
|
+
"compiler/codegen.js": [
|
|
37
|
+
"TA_GET_MARKER",
|
|
38
|
+
"TA_SET_BOUNDS_MARKER"
|
|
39
|
+
],
|
|
40
|
+
"compiler/parse.js": [
|
|
41
|
+
"CLASS_SELF_MARKER"
|
|
42
|
+
],
|
|
43
|
+
"compiler/uwebsockets.js": [
|
|
44
|
+
"HTTP10_SHIM_MARKER",
|
|
45
|
+
"BODY_MARKER",
|
|
46
|
+
"STATUS_SIG_MARKER",
|
|
47
|
+
"STATUS_303_MARKER",
|
|
48
|
+
"STATUS_DEFAULT_MARKER",
|
|
49
|
+
"HDR_SIG_MARKER",
|
|
50
|
+
"HDR_CALL_MARKER",
|
|
51
|
+
"HDR_CAP_MARKER",
|
|
52
|
+
"HDR_SKIP_MARKER",
|
|
53
|
+
"HDR_APPEND_MARKER",
|
|
54
|
+
"READ_RAW_DECL_MARKER",
|
|
55
|
+
"FORBIDDEN_HDR_MARKER",
|
|
56
|
+
"WRITE_RESPONSE_MARKER",
|
|
57
|
+
"R2_TRANSFER_MARKER",
|
|
58
|
+
"R2_TRANSFER_CALL_MARKER",
|
|
59
|
+
"R2_DOWNLOAD_CACHE_MARKER",
|
|
60
|
+
"R2_RANGE_CACHE_MARKER",
|
|
61
|
+
"R2_CONDITIONAL_CACHE_MARKER",
|
|
62
|
+
"R2_STREAMING_CACHE_MARKER",
|
|
63
|
+
"R2_RANGE_HEADER_CACHE_MARKER"
|
|
64
|
+
],
|
|
65
|
+
"compiler/builtins_precompiled.js": []
|
|
66
|
+
},
|
|
67
|
+
"patchApplication": "pass",
|
|
68
|
+
"mismatches": [
|
|
69
|
+
"15-date-iso.js: request 3 mismatch: expected {\"status\":200,\"body\":\"2008-03-04T23:00:00.000Z\",\"content-type\":null}, got {\"status\":200,\"body\":\"0003-05-08T00:00:00.000Z\",\"content-type\":null}",
|
|
70
|
+
"16-date-parts.js: request 3 mismatch: expected {\"status\":200,\"body\":\"{\\\"year\\\":2008,\\\"month\\\":3,\\\"day\\\":4}\",\"content-type\":\"application/json;charset=utf-8\"}, got {\"status\":200,\"body\":\"{\\\"year\\\":3,\\\"month\\\":5,\\\"day\\\":8}\",\"content-type\":\"application/json;charset=utf-8\"}"
|
|
71
|
+
]
|
|
72
|
+
}
|
package/src/acquire.test.ts
CHANGED
|
@@ -128,7 +128,9 @@ async function fixture(): Promise<{ archive: string; sha256: string }> {
|
|
|
128
128
|
"}\n",
|
|
129
129
|
"compiler/builtins/json.ts": "// sb_json_utf16_v1\n",
|
|
130
130
|
"runtime/fetch-globals.js": "// sb_text_encoder_scalar_v1\n",
|
|
131
|
+
"compiler/builtins/uint8array_base64.ts": "let alphabet: string = 'base64';\nlet lastChunkHandling: string = 'loose';\nconst fullChunks: i32 = (len / 3) * 3;\n",
|
|
131
132
|
"compiler/codegen.js":
|
|
133
|
+
"if (!hoistRead && directCallOnlyFunctionBinding(scope, kind, name, pattern, func)) {\n return valUndefined();\n}\nconst func = resolveNamedFunction(scope, name) ?? funcByName(name);\n" +
|
|
132
134
|
" stmt(scope, Store(ctype, addr, 4, ctype === 'f64' || ctype === 'f32' ? f : toUint32(scope, f)));\n" +
|
|
133
135
|
" const taGet = (ctype, size, signed = true) => () => {\n" +
|
|
134
136
|
" const loaded = Load(ctype, taAddr(size), 4);\n" +
|
|
@@ -225,11 +227,11 @@ test("the default call (no url/expectedSha256 override) never touches the networ
|
|
|
225
227
|
expect(await readFile(join(dir, "runtime/index.js"), "utf8")).not.toBe("");
|
|
226
228
|
// #242: generated typed-array join/toString keep a plain-array `parts`.
|
|
227
229
|
expect(await readFile(join(dir, "compiler/builtins/typedarray.js"), "utf8")).toContain(
|
|
228
|
-
".
|
|
230
|
+
"return Porffor.callThis(__Array_prototype_join, out, sep);",
|
|
229
231
|
);
|
|
230
232
|
// #238: TypedArray.prototype.set(source) with no offset copies to index 0.
|
|
231
233
|
expect(await readFile(join(dir, "compiler/builtins/typedarray.js"), "utf8")).toContain(
|
|
232
|
-
"
|
|
234
|
+
"const targetOffsetNum: number = ecma262.ToIntegerOrInfinity(offset);",
|
|
233
235
|
);
|
|
234
236
|
// #238: out-of-range typed-array reads are undefined. (The store and `~`
|
|
235
237
|
// conversions #238 also patched are upstream's own code since alpha-14.)
|
|
@@ -255,7 +257,7 @@ test("the default call (no url/expectedSha256 override) never touches the networ
|
|
|
255
257
|
expect(await readFile(join(dir, "compiler/builtins/promise.ts"), "utf8")).toContain(
|
|
256
258
|
"if (Porffor.type(value) == Porffor.TYPES.object && value != null) {",
|
|
257
259
|
);
|
|
258
|
-
});
|
|
260
|
+
}, 60_000);
|
|
259
261
|
|
|
260
262
|
test("a corrupted warm cache is replaced from the verified archive", async () => {
|
|
261
263
|
const { archive, sha256 } = await fixture();
|
package/src/acquire.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { homedir } from "node:os";
|
|
|
5
5
|
import { resolve } from "node:path";
|
|
6
6
|
// @ts-expect-error Bun's file loader supplies a path; see the pinned-commit
|
|
7
7
|
// check below for why this is safe to trust unconditionally.
|
|
8
|
-
import vendoredPorfforArchive from "../vendor/porffor-
|
|
8
|
+
import vendoredPorfforArchive from "../vendor/porffor-731f1b2.tar.gz" with { type: "file" };
|
|
9
9
|
import { ensurePorfforPatched } from "./patch";
|
|
10
10
|
import { PORFFOR_ARCHIVE_SHA256, PORFFOR_ARCHIVE_URL, PORFFOR_COMMIT_FULL } from "./pin";
|
|
11
11
|
// Folded into the cache manifest below so a toolchain release that changes
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
import { ensurePorffor } from "./acquire";
|
|
6
|
+
|
|
7
|
+
// Compare native output with Bun, including trap effects and iterator state.
|
|
8
|
+
const program = `
|
|
9
|
+
const target = { x: 1 };
|
|
10
|
+
let calls = 0;
|
|
11
|
+
const proxy = new Proxy(target, {
|
|
12
|
+
get(t, key, receiver) { calls++; return key === "virtual" ? 7 : Reflect.get(t, key, receiver); },
|
|
13
|
+
set(t, key, value) { t[key] = value * 2; return true; },
|
|
14
|
+
has(t, key) { return key === "virtual" || key in t; },
|
|
15
|
+
deleteProperty(t, key) { delete t[key]; return true; }
|
|
16
|
+
});
|
|
17
|
+
proxy.x = 3;
|
|
18
|
+
console.log(proxy.x, proxy.virtual, calls, "virtual" in proxy);
|
|
19
|
+
delete proxy.x;
|
|
20
|
+
console.log("x" in target);
|
|
21
|
+
const keys = new Proxy({ a: 1 }, {
|
|
22
|
+
ownKeys() { return ["a"]; },
|
|
23
|
+
getOwnPropertyDescriptor(t, key) { return Object.getOwnPropertyDescriptor(t, key); }
|
|
24
|
+
});
|
|
25
|
+
console.log(Object.keys(keys).join(","));
|
|
26
|
+
const fn = new Proxy(function (x) { return x + 1; }, {
|
|
27
|
+
apply(t, self, args) { return t(args[0]) * 2; }
|
|
28
|
+
});
|
|
29
|
+
console.log(fn(3));
|
|
30
|
+
const ctor = new Proxy(function Item(x) { this.x = x; }, {
|
|
31
|
+
construct(t, args) { return { x: args[0] * 3 }; }
|
|
32
|
+
});
|
|
33
|
+
console.log(new ctor(4).x);
|
|
34
|
+
const revocable = Proxy.revocable({ x: 1 }, {});
|
|
35
|
+
console.log(revocable.proxy.x);
|
|
36
|
+
revocable.revoke();
|
|
37
|
+
try { console.log(revocable.proxy.x); } catch (e) { console.log(e instanceof TypeError); }
|
|
38
|
+
const frozen = Object.freeze({ x: 1 });
|
|
39
|
+
try { console.log(new Proxy(frozen, { get() { return 2; } }).x); } catch (e) { console.log(e instanceof TypeError); }
|
|
40
|
+
const iterator = [1, 2].values();
|
|
41
|
+
console.log(iterator.next().value, iterator.next().value, iterator.next().done);
|
|
42
|
+
console.log(Array.from(new Map([["a", 1], ["b", 2]]).keys()).join(","));
|
|
43
|
+
console.log([...new Set([1, 2, 1])].join(","));
|
|
44
|
+
console.log(Array.from("ab").join(","));
|
|
45
|
+
const matches = "a1a2".matchAll(/a(\\d)/g);
|
|
46
|
+
console.log(matches.next().value[1], matches.next().value[1], matches.next().done);
|
|
47
|
+
console.log(Uint8Array.from({ length: 3 }, (_, i) => i + 1).join("-"));
|
|
48
|
+
const bytes = new Uint8Array(3); bytes.set([4, 5]); console.log(bytes.toString());
|
|
49
|
+
console.log(new Uint8ClampedArray([0.5, 1.5, 2.5, 3.5]).join(","));
|
|
50
|
+
class Static { static value = this; static { this.x = 9; } }
|
|
51
|
+
console.log(Static.value === Static, Static.x);
|
|
52
|
+
const dict = Object.create(null); dict.__proto__ = 5;
|
|
53
|
+
console.log(dict.__proto__, Object.getPrototypeOf(dict) === null);
|
|
54
|
+
let order = "";
|
|
55
|
+
function left() { order += "l"; return 1; }
|
|
56
|
+
function right() { order += "r"; return 2; }
|
|
57
|
+
console.log(left() + right(), order);
|
|
58
|
+
console.log("caffè 東京 🚤");
|
|
59
|
+
`;
|
|
60
|
+
|
|
61
|
+
test("alpha-16: native Proxy, iterators and upstream fixes match Bun", async () => {
|
|
62
|
+
const root = await mkdtemp(resolve(tmpdir(), "sb-alpha16-native-"));
|
|
63
|
+
try {
|
|
64
|
+
const porffor = await ensurePorffor();
|
|
65
|
+
const source = resolve(root, "features.js");
|
|
66
|
+
const binary = resolve(root, "features");
|
|
67
|
+
await writeFile(source, program);
|
|
68
|
+
const reference = Bun.spawnSync(["bun", source], { stdout: "pipe", stderr: "pipe" });
|
|
69
|
+
expect(reference.exitCode).toBe(0);
|
|
70
|
+
await rm(binary, { force: true });
|
|
71
|
+
const compile = Bun.spawnSync(["bun", resolve(porffor, "runtime/index.js"), "native", source, "-o", binary, "-s"], {
|
|
72
|
+
cwd: root, stdout: "pipe", stderr: "pipe",
|
|
73
|
+
});
|
|
74
|
+
expect(compile.exitCode, Buffer.from(compile.stderr).toString()).toBe(0);
|
|
75
|
+
const run = Bun.spawnSync([binary], { stdout: "pipe", stderr: "pipe", timeout: 10_000 });
|
|
76
|
+
expect(run.exitCode, Buffer.from(run.stderr).toString()).toBe(0);
|
|
77
|
+
const ansi = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
|
|
78
|
+
const output = Buffer.from(run.stdout).toString().replace(ansi, "");
|
|
79
|
+
expect(output).toBe(Buffer.from(reference.stdout).toString());
|
|
80
|
+
} finally {
|
|
81
|
+
await rm(root, { recursive: true, force: true });
|
|
82
|
+
}
|
|
83
|
+
}, 60_000);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
import { ensurePorffor } from "./acquire";
|
|
6
|
+
|
|
7
|
+
const values = [-2.5, -1.5, -0.5, -0.1, 0, 0.5, 1.5, 2.5];
|
|
8
|
+
const strings = ["", " 42 ", "0xff", "0b101", "0o17", "1.2345678901234567", "1e-7", "-0", "12junk"];
|
|
9
|
+
const expected = {
|
|
10
|
+
rounded: values.map((value) => [Math.round(value), Object.is(Math.round(value), -0)]),
|
|
11
|
+
parsed: strings.map((value) => [Number(value), Object.is(Number(value), -0)]),
|
|
12
|
+
formatted: [1.2345678901234567, 1e-7, 1e21, 255].map((value) => [value.toString(), value.toString(16), value.toFixed(3)]),
|
|
13
|
+
field: [1, 2],
|
|
14
|
+
};
|
|
15
|
+
const source = `const values=${JSON.stringify(values)}, strings=${JSON.stringify(strings)};
|
|
16
|
+
class Field { value = (() => { const data = [1,2]; return () => data; })()(); }
|
|
17
|
+
console.log(JSON.stringify({
|
|
18
|
+
rounded:values.map(value=>[Math.round(value),Object.is(Math.round(value),-0)]),
|
|
19
|
+
parsed:strings.map(value=>[Number(value),Object.is(Number(value),-0)]),
|
|
20
|
+
formatted:[1.2345678901234567,1e-7,1e21,255].map(value=>[value.toString(),value.toString(16),value.toFixed(3)]),
|
|
21
|
+
field:new Field().value
|
|
22
|
+
}));
|
|
23
|
+
console.log(1.2345678901234567);`;
|
|
24
|
+
|
|
25
|
+
test("alpha-17: dynamic number conversion, rounding, precision and class fields match Bun", async () => {
|
|
26
|
+
const dir = await mkdtemp(resolve(tmpdir(), "sb-alpha17-"));
|
|
27
|
+
try {
|
|
28
|
+
const porffor = await ensurePorffor();
|
|
29
|
+
const input = resolve(dir, "numbers.js"), binary = resolve(dir, "numbers");
|
|
30
|
+
await writeFile(input, source);
|
|
31
|
+
await rm(binary, { force: true });
|
|
32
|
+
const compile = Bun.spawnSync(["bun", resolve(porffor, "runtime/index.js"), "native", input, "-o", binary, "-s", "-O0"], { stdout: "pipe", stderr: "pipe" });
|
|
33
|
+
expect(compile.exitCode, new TextDecoder().decode(compile.stderr)).toBe(0);
|
|
34
|
+
const run = Bun.spawnSync([binary], { stdout: "pipe", stderr: "pipe", timeout: 10_000 });
|
|
35
|
+
expect(run.exitCode, new TextDecoder().decode(run.stderr)).toBe(0);
|
|
36
|
+
const lines = new TextDecoder().decode(run.stdout).replaceAll("\x1b[33m", "").replaceAll("\x1b[0m", "").trim().split("\n");
|
|
37
|
+
expect(JSON.parse(lines[0])).toEqual(JSON.parse(JSON.stringify(expected)));
|
|
38
|
+
expect(lines[1]).toBe("1.2345678901234567");
|
|
39
|
+
} finally {
|
|
40
|
+
await rm(dir, { recursive: true, force: true });
|
|
41
|
+
}
|
|
42
|
+
}, 60_000);
|
package/src/patch.test.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { EMPTY_BINDINGS, wrapNativeFetchHandler } from "../../runtime/src/wrap";
|
|
6
|
-
import { patchRenderJs, patchUwebsockets } from "./patch";
|
|
6
|
+
import { patchLocalFunctionTypes, patchUint8ArrayBase64, patchRenderJs, patchUwebsockets } from "./patch";
|
|
7
7
|
|
|
8
8
|
// The parts of Porffor's compiler/render.js the patch anchors to.
|
|
9
9
|
const RENDER = `void porf_native_fetch_runtime_init(void) {
|
|
@@ -299,3 +299,40 @@ test("#165: render.js routes console output to stderr, unbuffered, idempotently"
|
|
|
299
299
|
await rm(root, { recursive: true, force: true });
|
|
300
300
|
}
|
|
301
301
|
});
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
test("local function type patch registers lexical bindings and is idempotent", async () => {
|
|
305
|
+
const root = await mkdtemp(join(tmpdir(), "sb-local-functions-"));
|
|
306
|
+
try {
|
|
307
|
+
await mkdir(join(root, "compiler"));
|
|
308
|
+
const file = join(root, "compiler/codegen.js");
|
|
309
|
+
await writeFile(file, "if (!hoistRead && directCallOnlyFunctionBinding(scope, kind, name, pattern, func)) {\n return valUndefined();\n}\nconst func = resolveNamedFunction(scope, name) ?? funcByName(name);");
|
|
310
|
+
await patchLocalFunctionTypes(root);
|
|
311
|
+
const patched = await readFile(file, "utf8");
|
|
312
|
+
expect(patched).toContain("bindNamedFunction(scope, name, func);");
|
|
313
|
+
expect(patched).not.toContain("?? funcByName(name)");
|
|
314
|
+
await patchLocalFunctionTypes(root);
|
|
315
|
+
expect(await readFile(file, "utf8")).toBe(patched);
|
|
316
|
+
} finally {
|
|
317
|
+
await rm(root, { recursive: true, force: true });
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
test("base64 patch preserves option tags and truncates full chunks, idempotently", async () => {
|
|
323
|
+
const root = await mkdtemp(join(tmpdir(), "sb-base64-patch-"));
|
|
324
|
+
try {
|
|
325
|
+
await mkdir(join(root, "compiler/builtins"), { recursive: true });
|
|
326
|
+
const file = join(root, "compiler/builtins/uint8array_base64.ts");
|
|
327
|
+
await writeFile(file, "let alphabet: string = 'base64';\nlet lastChunkHandling: string = 'loose';\nconst fullChunks: i32 = (len / 3) * 3;");
|
|
328
|
+
await patchUint8ArrayBase64(root);
|
|
329
|
+
const patched = await readFile(file, "utf8");
|
|
330
|
+
expect(patched).toContain("let alphabet: any =");
|
|
331
|
+
expect(patched).toContain("let lastChunkHandling: any =");
|
|
332
|
+
expect(patched).toContain("((len / 3) | 0) * 3");
|
|
333
|
+
await patchUint8ArrayBase64(root);
|
|
334
|
+
expect(await readFile(file, "utf8")).toBe(patched);
|
|
335
|
+
} finally {
|
|
336
|
+
await rm(root, { recursive: true, force: true });
|
|
337
|
+
}
|
|
338
|
+
});
|
package/src/patch.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Idempotent, marker-guarded in-place edits to Porffor's generated C
|
|
3
3
|
* (`compiler/render.js`, `compiler/index.js`, `compiler/uwebsockets.js`,
|
|
4
|
-
* `compiler/builtins/
|
|
4
|
+
* `compiler/builtins/date.ts`). Run
|
|
5
5
|
* from the build path, not a `postinstall` hook: package managers block
|
|
6
6
|
* dependency lifecycle scripts by default, so a published `postinstall` would
|
|
7
7
|
* silently not run.
|
|
@@ -411,50 +411,6 @@ const TA_SET_BOUNDS_INJECT =
|
|
|
411
411
|
" }";
|
|
412
412
|
const TA_SET_BOUNDS_MARKER = "sproutboat #241: a write outside the array is ignored";
|
|
413
413
|
|
|
414
|
-
// baronunread/sproutboat#238: TypedArray.prototype.set(source) with no offset
|
|
415
|
-
// ran `offset = Math.trunc(undefined)`, which is NaN. The same-type fast path
|
|
416
|
-
// then copied to `base + NaN * BYTES_PER_ELEMENT`: nothing landed where it
|
|
417
|
-
// should (noble's HMAC lost its key in `pad.set(key)`), and the write went
|
|
418
|
-
// somewhere else in memory. ECMA-262 uses ToIntegerOrInfinity, which maps
|
|
419
|
-
// undefined and NaN to 0; subarray right below already does.
|
|
420
|
-
const TA_SET_OFFSET_ANCHOR = " offset = Math.trunc(offset);\n if (Porffor.fastOr(offset < 0, offset > len)) throw new RangeError('Offset out of bounds');";
|
|
421
|
-
const TA_SET_OFFSET_INJECT =
|
|
422
|
-
" offset = ecma262.ToIntegerOrInfinity(offset); // sproutboat #238: undefined offset is 0, not NaN\n if (Porffor.fastOr(offset < 0, offset > len)) throw new RangeError('Offset out of bounds');";
|
|
423
|
-
const TA_SET_OFFSET_MARKER = "sproutboat #238: undefined offset is 0";
|
|
424
|
-
|
|
425
|
-
// baronunread/sproutboat#242: typedarray.js generates each TypedArray method
|
|
426
|
-
// from array.ts with `.replaceAll('any[]', name)`. That also retyped join's and
|
|
427
|
-
// toString's internal `const parts: any[]` helper as e.g. a Uint8Array, so the
|
|
428
|
-
// joined strings were stored into a "typed array": Uint8Array join returned
|
|
429
|
-
// garbage and Uint16Array join crashed. Put `parts` back to a plain array after
|
|
430
|
-
// the replacement; map/filter's intentionally typed `out` is untouched.
|
|
431
|
-
const TA_JOIN_ANCHOR = ".replaceAll('Array', name).replaceAll('any[]', name) + '\\n\\n'";
|
|
432
|
-
const TA_JOIN_INJECT =
|
|
433
|
-
".replaceAll('Array', name).replaceAll('any[]', name).replaceAll('const parts: ' + name + ' =', 'const parts: any[] =') /* sproutboat #242 */ + '\\n\\n'";
|
|
434
|
-
const TA_JOIN_MARKER = "sproutboat #242";
|
|
435
|
-
|
|
436
|
-
// Porffor's TypedArray.from only handles iterables. An array-like input such
|
|
437
|
-
// as { length: 16 } silently becomes an empty typed array, including HMAC
|
|
438
|
-
// keys made with Uint8Array.from({ length: 16 }, mapFn). The compiler uses a
|
|
439
|
-
// precompiled builtin table, so the edited source must be precompiled again.
|
|
440
|
-
const TYPED_ARRAY_FROM_ANCHOR =
|
|
441
|
-
" len = i;\n }\n\n arr.length = len;\n\n return new ${name}(arr);\n};";
|
|
442
|
-
const TYPED_ARRAY_FROM_INJECT =
|
|
443
|
-
" len = i;\n" +
|
|
444
|
-
" } else {\n" +
|
|
445
|
-
" // sproutboat: TypedArray.from accepts array-like objects.\n" +
|
|
446
|
-
" let count = ecma262.ToIntegerOrInfinity(Porffor.object.get(arg, 'length'));\n" +
|
|
447
|
-
" if (count < 0) count = 0;\n" +
|
|
448
|
-
" if (count > 2147483643) throw new RangeError('Invalid TypedArray length (over maximum supported length)');\n" +
|
|
449
|
-
" if (Porffor.type(mapFn) != Porffor.TYPES.undefined && Porffor.type(mapFn) != Porffor.TYPES.function)\n" +
|
|
450
|
-
" throw new TypeError('Called TypedArray.from with a non-function mapFn');\n" +
|
|
451
|
-
" for (let i: i32 = 0; i < count; i++) {\n" +
|
|
452
|
-
" arr[i] = Porffor.type(mapFn) == Porffor.TYPES.undefined ? arg[i] : mapFn(arg[i], i);\n" +
|
|
453
|
-
" }\n" +
|
|
454
|
-
" len = count;\n" +
|
|
455
|
-
" }\n\n arr.length = len;\n\n return new ${name}(arr);\n};";
|
|
456
|
-
const TYPED_ARRAY_FROM_MARKER = "sproutboat: TypedArray.from accepts array-like objects";
|
|
457
|
-
|
|
458
414
|
// Porffor alpha 9 treats +02:00 as two milliseconds and adds offset hours
|
|
459
415
|
// instead of subtracting them. Count fields even when they contain zero, and
|
|
460
416
|
// apply the signed offset after parsing the local wall-clock fields.
|
|
@@ -1249,7 +1205,7 @@ export async function patchDateParser(root: string): Promise<void> {
|
|
|
1249
1205
|
await writeFile(file, src.slice(0, start) + parser.replace(DATE_PARSER_ANCHOR, DATE_PARSER_INJECT) + src.slice(end));
|
|
1250
1206
|
}
|
|
1251
1207
|
|
|
1252
|
-
/** #168: keep a promise resolving to null out of the `then` probe.
|
|
1208
|
+
/** #168: keep a promise resolving to null out of the `then` probe. */
|
|
1253
1209
|
export async function patchPromiseResolveNull(root: string): Promise<void> {
|
|
1254
1210
|
const file = resolve(root, "compiler/builtins/promise.ts");
|
|
1255
1211
|
const src = await readFile(file, "utf8");
|
|
@@ -1259,7 +1215,7 @@ export async function patchPromiseResolveNull(root: string): Promise<void> {
|
|
|
1259
1215
|
await writeFile(file, src.replace(PROMISE_NULL_ANCHOR, PROMISE_NULL_INJECT));
|
|
1260
1216
|
}
|
|
1261
1217
|
|
|
1262
|
-
/** #237: linear String.prototype.replaceAll. Precompiled by
|
|
1218
|
+
/** #237: linear String.prototype.replaceAll. Precompiled by precompileBuiltins. */
|
|
1263
1219
|
export async function patchReplaceAll(root: string): Promise<void> {
|
|
1264
1220
|
const file = resolve(root, "compiler/builtins/string.ts");
|
|
1265
1221
|
let src = await readFile(file, "utf8");
|
|
@@ -1293,27 +1249,46 @@ export async function patchTypedArrayStore(root: string): Promise<void> {
|
|
|
1293
1249
|
if (changed) await writeFile(file, src);
|
|
1294
1250
|
}
|
|
1295
1251
|
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
const
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
if (!
|
|
1304
|
-
|
|
1305
|
-
if (!
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1252
|
+
/** Register elided local function expressions so inference uses their lexical binding. */
|
|
1253
|
+
export async function patchLocalFunctionTypes(root: string): Promise<void> {
|
|
1254
|
+
const file = resolve(root, "compiler/codegen.js");
|
|
1255
|
+
let src = await readFile(file, "utf8");
|
|
1256
|
+
const marker = "sproutboat: preserve lexical types for direct-only local functions";
|
|
1257
|
+
if (src.includes(marker)) return;
|
|
1258
|
+
const anchor = "if (!hoistRead && directCallOnlyFunctionBinding(scope, kind, name, pattern, func)) {\n return valUndefined();";
|
|
1259
|
+
if (!src.includes(anchor)) throw new Error(`could not patch Porffor local function types: anchor not found in ${file}`);
|
|
1260
|
+
src = src.replace(anchor,
|
|
1261
|
+
"if (!hoistRead && directCallOnlyFunctionBinding(scope, kind, name, pattern, func)) {\n" +
|
|
1262
|
+
` // ${marker}\n` +
|
|
1263
|
+
" bindNamedFunction(scope, name, func);\n return valUndefined();");
|
|
1264
|
+
// resolveNamedFunction already handles unambiguous global fallbacks. Falling
|
|
1265
|
+
// back again can infer a type from an unrelated same-named function.
|
|
1266
|
+
const fallback = "const func = resolveNamedFunction(scope, name) ?? funcByName(name);";
|
|
1267
|
+
if (!src.includes(fallback)) throw new Error(`could not patch Porffor local function type fallback: anchor not found in ${file}`);
|
|
1268
|
+
src = src.replace(fallback, "const func = resolveNamedFunction(scope, name);");
|
|
1269
|
+
await writeFile(file, src);
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
/** Keep base64 option tags accurate and truncate the complete-chunk count. */
|
|
1273
|
+
export async function patchUint8ArrayBase64(root: string): Promise<void> {
|
|
1274
|
+
const file = resolve(root, "compiler/builtins/uint8array_base64.ts");
|
|
1275
|
+
let src = await readFile(file, "utf8");
|
|
1276
|
+
const marker = "sproutboat: base64 option tags and complete chunks";
|
|
1277
|
+
if (src.includes(marker)) return;
|
|
1278
|
+
const edits = [
|
|
1279
|
+
["let alphabet: string =", "let alphabet: any ="],
|
|
1280
|
+
["let lastChunkHandling: string =", "let lastChunkHandling: any ="],
|
|
1281
|
+
["const fullChunks: i32 = (len / 3) * 3;", "const fullChunks: i32 = ((len / 3) | 0) * 3;"],
|
|
1282
|
+
] as const;
|
|
1283
|
+
for (const [anchor, replacement] of edits) {
|
|
1284
|
+
if (!src.includes(anchor)) throw new Error(`could not patch Porffor base64: anchor not found in ${file}`);
|
|
1285
|
+
src = src.replaceAll(anchor, replacement);
|
|
1313
1286
|
}
|
|
1314
|
-
|
|
1287
|
+
await writeFile(file, `// ${marker}\n${src}`);
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
export async function precompileBuiltins(root: string): Promise<void> {
|
|
1315
1291
|
const table = resolve(root, "compiler/builtins_precompiled.js");
|
|
1316
|
-
const before = await readFile(table);
|
|
1317
1292
|
// Porffor only writes the table when import.meta.url matches argv[1].
|
|
1318
1293
|
// macOS's /var -> /private/var symlink makes a noncanonical argv[1] skip it.
|
|
1319
1294
|
const precompile = await realpath(resolve(root, "compiler/precompile.js"));
|
|
@@ -1327,9 +1302,8 @@ export async function patchTypedArrayFrom(root: string): Promise<void> {
|
|
|
1327
1302
|
new Response(child.stdout).text(),
|
|
1328
1303
|
new Response(child.stderr).text(),
|
|
1329
1304
|
]);
|
|
1330
|
-
if (code !== 0) throw new Error(`could not precompile Porffor's
|
|
1331
|
-
if (
|
|
1332
|
-
throw new Error(`Porffor precompile did not update ${table}`);
|
|
1305
|
+
if (code !== 0) throw new Error(`could not precompile Porffor's patched builtins: ${stderr || stdout}`);
|
|
1306
|
+
if ((await readFile(table)).length === 0) throw new Error(`Porffor precompile produced an empty ${table}`);
|
|
1333
1307
|
}
|
|
1334
1308
|
|
|
1335
1309
|
/** The native-fetch server source: $PORT support + the #165 console sink. Exported for tests. */
|
|
@@ -1383,9 +1357,11 @@ export async function ensurePorfforPatched(root: string): Promise<void> {
|
|
|
1383
1357
|
await patchPromiseResolveNull(root);
|
|
1384
1358
|
await patchReplaceAll(root);
|
|
1385
1359
|
await patchTypedArrayStore(root);
|
|
1360
|
+
await patchLocalFunctionTypes(root);
|
|
1361
|
+
await patchUint8ArrayBase64(root);
|
|
1386
1362
|
// Last of the builtin edits: its precompile picks up the date, promise and
|
|
1387
1363
|
// replaceAll patches above too.
|
|
1388
|
-
await
|
|
1364
|
+
await precompileBuiltins(root);
|
|
1389
1365
|
await patchUnicode(root);
|
|
1390
1366
|
done.add(root);
|
|
1391
1367
|
}
|
package/src/pin.ts
CHANGED
|
@@ -11,14 +11,14 @@
|
|
|
11
11
|
* uWebSockets archive in `sproutboat-cli/vendor/` — then run both repos'
|
|
12
12
|
* test + conformance suites. See sproutboat-cli/MIGRATION.md.
|
|
13
13
|
*/
|
|
14
|
-
export const PORFFOR_CHANNEL = "alpha-
|
|
15
|
-
export const PORFFOR_COMMIT_FULL = "
|
|
14
|
+
export const PORFFOR_CHANNEL = "alpha-17";
|
|
15
|
+
export const PORFFOR_COMMIT_FULL = "731f1b2a150d441ed22da3d14f8b1e0daaae8ee4";
|
|
16
16
|
export const PORFFOR_COMMIT = PORFFOR_COMMIT_FULL.slice(0, 7);
|
|
17
17
|
export const PORFFOR_ARCHIVE_SHA256 =
|
|
18
|
-
"
|
|
18
|
+
"8eb65a3e77d58a23bfd2a1a60328672ad50e1ced7931f8d0ca3108e29d6cf846";
|
|
19
19
|
export const PORFFOR_ARCHIVE_URL = `https://codeload.github.com/CanadaHonk/porffor/tar.gz/${PORFFOR_COMMIT_FULL}`;
|
|
20
20
|
|
|
21
|
-
/** A compact identity string for a manifest / report (`alpha-
|
|
21
|
+
/** A compact identity string for a manifest / report (`alpha-17 (731f1b2)`). */
|
|
22
22
|
export function porfforVersion(): string {
|
|
23
23
|
return process.env.PORFFOR_VERSION || `${PORFFOR_CHANNEL} (${PORFFOR_COMMIT})`;
|
|
24
24
|
}
|
|
Binary file
|
|
Binary file
|