@sproutboat/toolchain 0.4.19 → 0.4.20
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 +6 -0
- package/package.json +1 -1
- package/src/acquire.test.ts +10 -0
- package/src/patch.ts +41 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @sproutboat/toolchain
|
|
2
2
|
|
|
3
|
+
## 0.4.20
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 0cdd803: `String.prototype.replaceAll` is linear instead of quadratic (baronunread/sproutboat#237). Porffor grew the result with one full-copy `strcat` per match; a patch to `compiler/builtins/string.ts` collects the pieces and joins them once. Replacing in a 2.1 MB string went from about 42 s to 71 ms (Node: 36 ms), with identical output.
|
|
8
|
+
|
|
3
9
|
## 0.4.19
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/package.json
CHANGED
package/src/acquire.test.ts
CHANGED
|
@@ -120,6 +120,12 @@ async function fixture(): Promise<{ archive: string; sha256: string }> {
|
|
|
120
120
|
"}\n",
|
|
121
121
|
"compiler/builtins/json.ts": "// sb_json_utf16_v1\n",
|
|
122
122
|
"runtime/fetch-globals.js": "// sb_text_encoder_scalar_v1\n",
|
|
123
|
+
"compiler/builtins/string.ts":
|
|
124
|
+
"export const __Porffor_string_replaceAll = (str: any, searchValue: any, replaceValue: any) => {\n" +
|
|
125
|
+
" let out: any = __Porffor_string_emptyLike(str);\n let appendIndex: i32 = 0;\n let searchIndex: i32 = 0;\n let matched: boolean = false;\n" +
|
|
126
|
+
" out = __Porffor_strcat(out, __Porffor_string_substringLike(str, appendIndex, matchIndex));\n out = __Porffor_strcat(out, __Porffor_string_applyReplacer(str, match, matchIndex, replaceValue));\n" +
|
|
127
|
+
" if (!matched) return str;\n return __Porffor_strcat(out, __Porffor_string_substringLike(str, appendIndex, thisLen));\n};\n" +
|
|
128
|
+
"export const __String_prototype_replaceAll = function (this: string, searchValue: any, replaceValue: any) {};\n",
|
|
123
129
|
"compiler/builtins/promise.ts":
|
|
124
130
|
"export const __Porffor_promise_resolve = (value: any, promise: any): void => {\n" +
|
|
125
131
|
" if (Porffor.type(value) == Porffor.TYPES.object) {\n" +
|
|
@@ -202,6 +208,10 @@ test("the default call (no url/expectedSha256 override) never touches the networ
|
|
|
202
208
|
},
|
|
203
209
|
});
|
|
204
210
|
expect(await readFile(join(dir, "runtime/index.js"), "utf8")).not.toBe("");
|
|
211
|
+
// #237: replaceAll joins its pieces once instead of a strcat per match.
|
|
212
|
+
expect(await readFile(join(dir, "compiler/builtins/string.ts"), "utf8")).toContain(
|
|
213
|
+
"return Porffor.callThis(__Array_prototype_join, pieces, '');",
|
|
214
|
+
);
|
|
205
215
|
// #236: the native-fetch build edits uWebSockets to accept HTTP/1.0.
|
|
206
216
|
const index = await readFile(join(dir, "compiler/index.js"), "utf8");
|
|
207
217
|
expect(index).toContain("import { sbPatchHttp10 } from './sb-http10.js';");
|
package/src/patch.ts
CHANGED
|
@@ -270,6 +270,27 @@ const PROMISE_NULL_INJECT =
|
|
|
270
270
|
" if (Porffor.type(value) == Porffor.TYPES.object && value != null) {\n // cheap prototype-chain probe for 'then'";
|
|
271
271
|
const PROMISE_NULL_MARKER = "sproutboat #168: null is object-typed";
|
|
272
272
|
|
|
273
|
+
// baronunread/sproutboat#237: String.prototype.replaceAll grew its result with
|
|
274
|
+
// one __Porffor_strcat per match, and each strcat copies everything so far, so
|
|
275
|
+
// a 2.7 MB string with many matches took ~22 s (Node: 17 ms). Collect the
|
|
276
|
+
// pieces and join once: Array.prototype.join sizes the result up front and
|
|
277
|
+
// copies each piece a single time.
|
|
278
|
+
const REPLACE_ALL_EDITS = [
|
|
279
|
+
[
|
|
280
|
+
" let out: any = __Porffor_string_emptyLike(str);\n let appendIndex: i32 = 0;\n let searchIndex: i32 = 0;\n let matched: boolean = false;",
|
|
281
|
+
" // sproutboat #237: pieces joined once, not a strcat (full copy) per match.\n const pieces: any[] = Porffor.array.new(0);\n let appendIndex: i32 = 0;\n let searchIndex: i32 = 0;\n let matched: boolean = false;",
|
|
282
|
+
],
|
|
283
|
+
[
|
|
284
|
+
" out = __Porffor_strcat(out, __Porffor_string_substringLike(str, appendIndex, matchIndex));\n out = __Porffor_strcat(out, __Porffor_string_applyReplacer(str, match, matchIndex, replaceValue));",
|
|
285
|
+
" Porffor.array.fastPush(pieces, __Porffor_string_substringLike(str, appendIndex, matchIndex));\n Porffor.array.fastPush(pieces, __Porffor_string_applyReplacer(str, match, matchIndex, replaceValue));",
|
|
286
|
+
],
|
|
287
|
+
[
|
|
288
|
+
" if (!matched) return str;\n return __Porffor_strcat(out, __Porffor_string_substringLike(str, appendIndex, thisLen));\n};",
|
|
289
|
+
" if (!matched) return str;\n Porffor.array.fastPush(pieces, __Porffor_string_substringLike(str, appendIndex, thisLen));\n return Porffor.callThis(__Array_prototype_join, pieces, '');\n};",
|
|
290
|
+
],
|
|
291
|
+
] as const;
|
|
292
|
+
const REPLACE_ALL_MARKER = "sproutboat #237: pieces joined once";
|
|
293
|
+
|
|
273
294
|
// Porffor's TypedArray.from only handles iterables. An array-like input such
|
|
274
295
|
// as { length: 16 } silently becomes an empty typed array, including HMAC
|
|
275
296
|
// keys made with Uint8Array.from({ length: 16 }, mapFn). The compiler uses a
|
|
@@ -1070,6 +1091,23 @@ export async function patchPromiseResolveNull(root: string): Promise<void> {
|
|
|
1070
1091
|
await writeFile(file, src.replace(PROMISE_NULL_ANCHOR, PROMISE_NULL_INJECT));
|
|
1071
1092
|
}
|
|
1072
1093
|
|
|
1094
|
+
/** #237: linear String.prototype.replaceAll. Precompiled by patchTypedArrayFrom. */
|
|
1095
|
+
export async function patchReplaceAll(root: string): Promise<void> {
|
|
1096
|
+
const file = resolve(root, "compiler/builtins/string.ts");
|
|
1097
|
+
let src = await readFile(file, "utf8");
|
|
1098
|
+
if (src.includes(REPLACE_ALL_MARKER)) return;
|
|
1099
|
+
const start = src.indexOf("export const __Porffor_string_replaceAll = ");
|
|
1100
|
+
const end = src.indexOf("export const __String_prototype_replaceAll = ", start);
|
|
1101
|
+
if (start < 0 || end < 0) throw new Error(`could not find Porffor's replaceAll in ${file}`);
|
|
1102
|
+
let body = src.slice(start, end);
|
|
1103
|
+
for (const [anchor, inject] of REPLACE_ALL_EDITS) {
|
|
1104
|
+
if (!body.includes(anchor)) throw new Error(`could not patch Porffor's replaceAll: anchor not found in ${file}`);
|
|
1105
|
+
body = body.replace(anchor, inject);
|
|
1106
|
+
}
|
|
1107
|
+
src = src.slice(0, start) + body + src.slice(end);
|
|
1108
|
+
await writeFile(file, src);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1073
1111
|
export async function patchTypedArrayFrom(root: string): Promise<void> {
|
|
1074
1112
|
const file = resolve(root, "compiler/builtins/typedarray.js");
|
|
1075
1113
|
const src = await readFile(file, "utf8");
|
|
@@ -1146,8 +1184,9 @@ export async function ensurePorfforPatched(root: string): Promise<void> {
|
|
|
1146
1184
|
await patchRenderJs(root);
|
|
1147
1185
|
await patchDateParser(root);
|
|
1148
1186
|
await patchPromiseResolveNull(root);
|
|
1149
|
-
|
|
1150
|
-
//
|
|
1187
|
+
await patchReplaceAll(root);
|
|
1188
|
+
// Last of the builtin edits: its precompile picks up the date, promise and
|
|
1189
|
+
// replaceAll patches above too.
|
|
1151
1190
|
await patchTypedArrayFrom(root);
|
|
1152
1191
|
await patchUnicode(root);
|
|
1153
1192
|
done.add(root);
|