@sproutboat/toolchain 0.4.15 → 0.4.17
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 +12 -0
- package/PORFFOR_ALPHA13.md +9 -0
- package/PORFFOR_SCORECARD.md +7 -7
- package/package.json +1 -1
- package/scorecards/alpha-13-547c781.json +59 -0
- package/src/acquire.test.ts +5 -0
- package/src/acquire.ts +1 -1
- package/src/patch.ts +33 -4
- package/src/pin.ts +4 -4
- package/vendor/porffor-547c781.tar.gz +0 -0
- package/vendor/porffor-08ac7ee.tar.gz +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @sproutboat/toolchain
|
|
2
2
|
|
|
3
|
+
## 0.4.17
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 2cda218: Fix async handlers failing permanently with `TypeError: Cannot get property of null` after a few requests (baronunread/sproutboat#168). Porffor represents `null` as an object-typed value with pointer 0, so a promise resolving to `null` went through the `then` probe and looked `then` up at memory offset 0. Once `Date.prototype.toISOString` had written there, the probe matched, read `null.then` and rejected, and every later async function returning `null` failed the same way. A patch to `compiler/builtins/promise.ts` keeps `null` out of the probe.
|
|
8
|
+
|
|
9
|
+
## 0.4.16
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 7bab275: Pin and vendor Porffor alpha-13 (`547c781`). Alpha-11 to alpha-13 bring array, typed array and module codegen fixes, a basic `RegExp` search, a `String.prototype.normalize` stub and lowercase JSON hex escapes. Every Sproutboat patch applies unchanged, uWebSockets stays on the same commit, and the compatibility suite is unchanged at 30/32.
|
|
14
|
+
|
|
3
15
|
## 0.4.15
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Porffor alpha-13 upgrade audit
|
|
2
|
+
|
|
3
|
+
The source pin moves from alpha-10 (`08ac7ee`) to the [alpha-13 release](https://github.com/CanadaHonk/porffor/releases/tag/alpha-13), commit `547c7815125b6f02474591950f8b0dd7031a03f0`, published September 30, 2026. The vendored codeload archive has SHA-256 `d0ff4bf7778f85445ff99bb8f6b2e07f2b83a33a109612f9eba6f2e5cba291b5`.
|
|
4
|
+
|
|
5
|
+
Alpha-11 brings array and typed array fast paths (merge sort, `shift`, same-type `set`) and codegen fixes for closures in loops and function literals. Alpha-12 fixes module handling (reassigned exports, strict-mode analysis, module-init closures). Alpha-13 adds a basic `RegExp` search, a `String.prototype.normalize` stub, more reserved C keywords and lowercase JSON hex escapes. Its `compiler/uwebsockets.js` still pins `360c276d609d59af56ae6932adb95154ace9f15f`, so the CLI's vendored uWebSockets archive does not change. All 28 Sproutboat patches apply without changing their anchors or markers. `PORF_CORO_STACK_SIZE` is still 256 KiB upstream, so the #178 patch still applies and still matters.
|
|
6
|
+
|
|
7
|
+
The native package suite passed 152 tests, plus the native crypto vectors. Linked to this toolchain, the CLI passed 103 tests (`SURFACE.md` only needs the new provenance stamp), 28 broker checks, 30 standalone checks and all 21 examples. The platform's 32-handler comparison compiled all 32 and matched Bun on 30, unchanged from alpha-10. The remaining two failures are non-ISO date parsing in `15-date-iso.js` and `16-date-parts.js`. See [the scorecard](PORFFOR_SCORECARD.md) for the exact results.
|
|
8
|
+
|
|
9
|
+
The open runtime bugs reproduce unchanged: baronunread/sproutboat#168 (an async chain using `new Date().toISOString()` flips to permanent 500s after 5 to 12 requests) and the read half of #189 (non-ASCII D1 and KV values come back as Latin-1 bytes).
|
package/PORFFOR_SCORECARD.md
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
# Porffor pin scorecard
|
|
2
2
|
|
|
3
|
-
Pin: alpha-
|
|
3
|
+
Pin: alpha-13 (547c7815125b6f02474591950f8b0dd7031a03f0). Compatibility report: 2026-10-01T10:57:31.463Z.
|
|
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 |
|
|
9
|
+
| Median / p95 compile, ms | 3212 / 4703 |
|
|
10
|
+
| Median / p95 binary, bytes | 966864 / 999936 |
|
|
11
|
+
| Patch markers | 28 |
|
|
12
12
|
| Upstream files written | 6 |
|
|
13
13
|
| Fresh patch application | pass |
|
|
14
14
|
|
|
15
|
-
Previous snapshot: alpha-
|
|
15
|
+
Previous snapshot: alpha-10-08ac7ee.json. Matches 30/32 to 30/32; patch markers 27 to 28.
|
|
16
16
|
|
|
17
17
|
## Patch inventory
|
|
18
18
|
|
|
19
|
-
- `compiler/render.js`: PORT_MARKER, CONSOLE_MARKER, BYTESTRING_MARKER, READ_RAW_MARKER
|
|
19
|
+
- `compiler/render.js`: PORT_MARKER, CONSOLE_MARKER, BYTESTRING_MARKER, READ_RAW_MARKER, CORO_STACK_MARKER
|
|
20
20
|
- `compiler/index.js`: LINK_MARKER, CFLAGS_MARKER
|
|
21
21
|
- `compiler/builtins/typedarray.js`: TYPED_ARRAY_FROM_MARKER
|
|
22
22
|
- `compiler/builtins/date.ts`: DATE_PARSER_MARKER
|
|
23
23
|
- `compiler/uwebsockets.js`: 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
|
|
24
24
|
- `compiler/builtins_precompiled.js`: regenerated builtin table
|
|
25
25
|
|
|
26
|
-
Patch source SHA-256: `
|
|
26
|
+
Patch source SHA-256: `6f607ac6b66fc4a51c2bf9e9525d0ab7f4e9b9dd3f942a820bbad46b7e3dbf56`.
|
|
27
27
|
|
|
28
28
|
## Known behavior gaps
|
|
29
29
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"pin": "alpha-13 (547c7815125b6f02474591950f8b0dd7031a03f0)",
|
|
3
|
+
"reportGeneratedAt": "2026-10-01T10:57:31.463Z",
|
|
4
|
+
"fixtures": 32,
|
|
5
|
+
"compiled": 32,
|
|
6
|
+
"matched": 30,
|
|
7
|
+
"medianCompileMs": 3212,
|
|
8
|
+
"p95CompileMs": 4703,
|
|
9
|
+
"medianBinaryBytes": 966864,
|
|
10
|
+
"p95BinaryBytes": 999936,
|
|
11
|
+
"patchSourceSha256": "6f607ac6b66fc4a51c2bf9e9525d0ab7f4e9b9dd3f942a820bbad46b7e3dbf56",
|
|
12
|
+
"patchMarkerCount": 28,
|
|
13
|
+
"patchTargets": {
|
|
14
|
+
"compiler/render.js": [
|
|
15
|
+
"PORT_MARKER",
|
|
16
|
+
"CONSOLE_MARKER",
|
|
17
|
+
"BYTESTRING_MARKER",
|
|
18
|
+
"READ_RAW_MARKER",
|
|
19
|
+
"CORO_STACK_MARKER"
|
|
20
|
+
],
|
|
21
|
+
"compiler/index.js": [
|
|
22
|
+
"LINK_MARKER",
|
|
23
|
+
"CFLAGS_MARKER"
|
|
24
|
+
],
|
|
25
|
+
"compiler/builtins/typedarray.js": [
|
|
26
|
+
"TYPED_ARRAY_FROM_MARKER"
|
|
27
|
+
],
|
|
28
|
+
"compiler/builtins/date.ts": [
|
|
29
|
+
"DATE_PARSER_MARKER"
|
|
30
|
+
],
|
|
31
|
+
"compiler/uwebsockets.js": [
|
|
32
|
+
"BODY_MARKER",
|
|
33
|
+
"STATUS_SIG_MARKER",
|
|
34
|
+
"STATUS_303_MARKER",
|
|
35
|
+
"STATUS_DEFAULT_MARKER",
|
|
36
|
+
"HDR_SIG_MARKER",
|
|
37
|
+
"HDR_CALL_MARKER",
|
|
38
|
+
"HDR_CAP_MARKER",
|
|
39
|
+
"HDR_SKIP_MARKER",
|
|
40
|
+
"HDR_APPEND_MARKER",
|
|
41
|
+
"READ_RAW_DECL_MARKER",
|
|
42
|
+
"FORBIDDEN_HDR_MARKER",
|
|
43
|
+
"WRITE_RESPONSE_MARKER",
|
|
44
|
+
"R2_TRANSFER_MARKER",
|
|
45
|
+
"R2_TRANSFER_CALL_MARKER",
|
|
46
|
+
"R2_DOWNLOAD_CACHE_MARKER",
|
|
47
|
+
"R2_RANGE_CACHE_MARKER",
|
|
48
|
+
"R2_CONDITIONAL_CACHE_MARKER",
|
|
49
|
+
"R2_STREAMING_CACHE_MARKER",
|
|
50
|
+
"R2_RANGE_HEADER_CACHE_MARKER"
|
|
51
|
+
],
|
|
52
|
+
"compiler/builtins_precompiled.js": []
|
|
53
|
+
},
|
|
54
|
+
"patchApplication": "pass",
|
|
55
|
+
"mismatches": [
|
|
56
|
+
"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}",
|
|
57
|
+
"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\"}"
|
|
58
|
+
]
|
|
59
|
+
}
|
package/src/acquire.test.ts
CHANGED
|
@@ -119,6 +119,7 @@ async function fixture(): Promise<{ archive: string; sha256: string }> {
|
|
|
119
119
|
"compiler/builtins/promise.ts":
|
|
120
120
|
"export const __Porffor_promise_resolve = (value: any, promise: any): void => {\n" +
|
|
121
121
|
" if (Porffor.type(value) == Porffor.TYPES.object) {\n" +
|
|
122
|
+
" // cheap prototype-chain probe for 'then' before the expensive Get below, does not invoke getters\n" +
|
|
122
123
|
" const thenHash: i32 = __Porffor_object_hash('then');\n" +
|
|
123
124
|
" let probe: any = value;\n" +
|
|
124
125
|
" while (Porffor.type(probe) == Porffor.TYPES.object) {\n" +
|
|
@@ -197,6 +198,10 @@ test("the default call (no url/expectedSha256 override) never touches the networ
|
|
|
197
198
|
},
|
|
198
199
|
});
|
|
199
200
|
expect(await readFile(join(dir, "runtime/index.js"), "utf8")).not.toBe("");
|
|
201
|
+
// #168: the real vendored source takes the null guard on the `then` probe.
|
|
202
|
+
expect(await readFile(join(dir, "compiler/builtins/promise.ts"), "utf8")).toContain(
|
|
203
|
+
"if (Porffor.type(value) == Porffor.TYPES.object && value != null) {",
|
|
204
|
+
);
|
|
200
205
|
});
|
|
201
206
|
|
|
202
207
|
test("a corrupted warm cache is replaced from the verified archive", async () => {
|
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-547c781.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
|
package/src/patch.ts
CHANGED
|
@@ -133,10 +133,11 @@ const READ_RAW_MARKER = "porf_native_fetch_read_raw_bytes(jsval value";
|
|
|
133
133
|
|
|
134
134
|
/**
|
|
135
135
|
* baronunread/sproutboat#178: every async function runs on its own fiber
|
|
136
|
-
* stack, and alpha-10 fixes those at 256 KiB. A
|
|
137
|
-
* (what `sproutboat dev` builds) spends ~40 KiB
|
|
138
|
-
* biggest generated functions, so a few nested
|
|
139
|
-
* the guard page and the sprout dies with SIGBUS
|
|
136
|
+
* stack, and Porffor (alpha-10 through alpha-13) fixes those at 256 KiB. A
|
|
137
|
+
* large handler compiled at -O0 (what `sproutboat dev` builds) spends ~40 KiB
|
|
138
|
+
* of stack per call into the biggest generated functions, so a few nested
|
|
139
|
+
* awaits run off the end into the guard page and the sprout dies with SIGBUS
|
|
140
|
+
* on its first request, no log.
|
|
140
141
|
* -O3 frames are smaller, which only raises the threshold: a deep enough
|
|
141
142
|
* async chain hits it in a release build too.
|
|
142
143
|
*
|
|
@@ -191,6 +192,21 @@ const CFLAGS_INJECT =
|
|
|
191
192
|
" ...(process.env.SB_EXTRA_CFLAGS ? process.env.SB_EXTRA_CFLAGS.split(' ').filter(Boolean) : []),\n";
|
|
192
193
|
const CFLAGS_MARKER = "SB_EXTRA_CFLAGS";
|
|
193
194
|
|
|
195
|
+
// baronunread/sproutboat#168: Porffor represents null as an object-typed value
|
|
196
|
+
// with pointer 0, so __Porffor_promise_resolve's cheap `then` probe treated a
|
|
197
|
+
// promise resolving to null as an object and looked `then` up at memory offset
|
|
198
|
+
// 0. Once something (Date.prototype.toISOString's scratch writes, in practice)
|
|
199
|
+
// left bytes there that hash-match, the probe "found" a then, read null.then,
|
|
200
|
+
// and rejected with "Cannot get property of null". Every later async function
|
|
201
|
+
// resolving to null then failed, permanently. Only non-null objects can be
|
|
202
|
+
// thenables (ECMA-262 Promise Resolve Functions step 8), so exclude null.
|
|
203
|
+
const PROMISE_NULL_ANCHOR =
|
|
204
|
+
" if (Porffor.type(value) == Porffor.TYPES.object) {\n // cheap prototype-chain probe for 'then'";
|
|
205
|
+
const PROMISE_NULL_INJECT =
|
|
206
|
+
" // sproutboat #168: null is object-typed with pointer 0, never a thenable.\n" +
|
|
207
|
+
" if (Porffor.type(value) == Porffor.TYPES.object && value != null) {\n // cheap prototype-chain probe for 'then'";
|
|
208
|
+
const PROMISE_NULL_MARKER = "sproutboat #168: null is object-typed";
|
|
209
|
+
|
|
194
210
|
// Porffor's TypedArray.from only handles iterables. An array-like input such
|
|
195
211
|
// as { length: 16 } silently becomes an empty typed array, including HMAC
|
|
196
212
|
// keys made with Uint8Array.from({ length: 16 }, mapFn). The compiler uses a
|
|
@@ -976,6 +992,16 @@ export async function patchDateParser(root: string): Promise<void> {
|
|
|
976
992
|
await writeFile(file, src.slice(0, start) + parser.replace(DATE_PARSER_ANCHOR, DATE_PARSER_INJECT) + src.slice(end));
|
|
977
993
|
}
|
|
978
994
|
|
|
995
|
+
/** #168: keep a promise resolving to null out of the `then` probe. Precompiled by patchTypedArrayFrom. */
|
|
996
|
+
export async function patchPromiseResolveNull(root: string): Promise<void> {
|
|
997
|
+
const file = resolve(root, "compiler/builtins/promise.ts");
|
|
998
|
+
const src = await readFile(file, "utf8");
|
|
999
|
+
if (src.includes(PROMISE_NULL_MARKER)) return;
|
|
1000
|
+
if (!src.includes(PROMISE_NULL_ANCHOR))
|
|
1001
|
+
throw new Error(`could not patch Porffor's promise resolve for null: anchor not found in ${file}`);
|
|
1002
|
+
await writeFile(file, src.replace(PROMISE_NULL_ANCHOR, PROMISE_NULL_INJECT));
|
|
1003
|
+
}
|
|
1004
|
+
|
|
979
1005
|
export async function patchTypedArrayFrom(root: string): Promise<void> {
|
|
980
1006
|
const file = resolve(root, "compiler/builtins/typedarray.js");
|
|
981
1007
|
const src = await readFile(file, "utf8");
|
|
@@ -1051,6 +1077,9 @@ export async function ensurePorfforPatched(root: string): Promise<void> {
|
|
|
1051
1077
|
await patchUwebsockets(root);
|
|
1052
1078
|
await patchRenderJs(root);
|
|
1053
1079
|
await patchDateParser(root);
|
|
1080
|
+
await patchPromiseResolveNull(root);
|
|
1081
|
+
// Last of the builtin edits: its precompile picks up the date and promise
|
|
1082
|
+
// patches above too.
|
|
1054
1083
|
await patchTypedArrayFrom(root);
|
|
1055
1084
|
await patchUnicode(root);
|
|
1056
1085
|
done.add(root);
|
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-13";
|
|
15
|
+
export const PORFFOR_COMMIT_FULL = "547c7815125b6f02474591950f8b0dd7031a03f0";
|
|
16
16
|
export const PORFFOR_COMMIT = PORFFOR_COMMIT_FULL.slice(0, 7);
|
|
17
17
|
export const PORFFOR_ARCHIVE_SHA256 =
|
|
18
|
-
"
|
|
18
|
+
"d0ff4bf7778f85445ff99bb8f6b2e07f2b83a33a109612f9eba6f2e5cba291b5";
|
|
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-13 (547c781)`). */
|
|
22
22
|
export function porfforVersion(): string {
|
|
23
23
|
return process.env.PORFFOR_VERSION || `${PORFFOR_CHANNEL} (${PORFFOR_COMMIT})`;
|
|
24
24
|
}
|
|
Binary file
|
|
Binary file
|