@sproutboat/toolchain 0.4.17 → 0.4.18

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @sproutboat/toolchain
2
2
 
3
+ ## 0.4.18
4
+
5
+ ### Patch Changes
6
+
7
+ - e0d0e17: Compile every unit with `-ffp-contract=off` (baronunread/sproutboat#235). On arm64, clang fused `a * b + c` into a single FMA that rounds once, where JavaScript rounds after the multiply and again after the add, so native sprouts could compute different doubles from V8 for the same code. A seeded LCG diverged at step 23.
8
+
3
9
  ## 0.4.17
4
10
 
5
11
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sproutboat/toolchain",
3
- "version": "0.4.17",
3
+ "version": "0.4.18",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {
@@ -29,7 +29,8 @@ async function fixture(): Promise<{ archive: string; sha256: string }> {
29
29
  " return -1;\n" +
30
30
  "}\n" +
31
31
  "#define PORF_CORO_STACK_SIZE (256u * 1024u)\n",
32
- "compiler/index.js": " ...darwinReleaseCompileArgs,\n uSocketsArchive,\n '-lm'\n",
32
+ "compiler/index.js":
33
+ " '-fno-ident', '-ffunction-sections', '-fdata-sections',\n ...darwinReleaseCompileArgs,\n uSocketsArchive,\n '-lm'\n",
33
34
  "compiler/precompile.js":
34
35
  "const fs = require('node:fs');\n" +
35
36
  "const path = require('node:path');\n" +
@@ -198,6 +199,8 @@ test("the default call (no url/expectedSha256 override) never touches the networ
198
199
  },
199
200
  });
200
201
  expect(await readFile(join(dir, "runtime/index.js"), "utf8")).not.toBe("");
202
+ // #235: every compiled unit keeps JavaScript's separate float roundings.
203
+ expect(await readFile(join(dir, "compiler/index.js"), "utf8")).toContain("'-ffp-contract=off',");
201
204
  // #168: the real vendored source takes the null guard on the `then` probe.
202
205
  expect(await readFile(join(dir, "compiler/builtins/promise.ts"), "utf8")).toContain(
203
206
  "if (Porffor.type(value) == Porffor.TYPES.object && value != null) {",
package/src/patch.ts CHANGED
@@ -192,6 +192,14 @@ const CFLAGS_INJECT =
192
192
  " ...(process.env.SB_EXTRA_CFLAGS ? process.env.SB_EXTRA_CFLAGS.split(' ').filter(Boolean) : []),\n";
193
193
  const CFLAGS_MARKER = "SB_EXTRA_CFLAGS";
194
194
 
195
+ // baronunread/sproutboat#235: clang's default -ffp-contract=on fuses `a * b + c`
196
+ // into one FMA on arm64, rounding once where JavaScript rounds twice. A seeded
197
+ // LCG (seed * 1103515245 + 12345, past 2^53) diverged from V8 at step 23.
198
+ // Every compiled unit keeps JS's separate roundings with contraction off.
199
+ const FP_CONTRACT_ANCHOR = " '-fno-ident', '-ffunction-sections', '-fdata-sections',\n";
200
+ const FP_CONTRACT_INJECT = " '-ffp-contract=off', // sproutboat #235: JS rounds a * b + c twice\n";
201
+ const FP_CONTRACT_MARKER = "sproutboat #235";
202
+
195
203
  // baronunread/sproutboat#168: Porffor represents null as an object-typed value
196
204
  // with pointer 0, so __Porffor_promise_resolve's cheap `then` probe treated a
197
205
  // promise resolving to null as an object and looked `then` up at memory offset
@@ -959,6 +967,7 @@ async function patchCompilerArgs(root: string): Promise<void> {
959
967
  for (const [marker, anchor, inject, what] of [
960
968
  [LINK_MARKER, LINK_ANCHOR, LINK_INJECT, "extra link args"],
961
969
  [CFLAGS_MARKER, CFLAGS_ANCHOR, CFLAGS_INJECT, "extra compiler flags"],
970
+ [FP_CONTRACT_MARKER, FP_CONTRACT_ANCHOR, FP_CONTRACT_INJECT, "floating-point contraction"],
962
971
  ] as const) {
963
972
  if (src.includes(marker)) continue;
964
973
  const at = src.indexOf(anchor);
@@ -971,7 +980,7 @@ async function patchCompilerArgs(root: string): Promise<void> {
971
980
  // Add flags to compileOnlyArgs so every generated C unit sees them and
972
981
  // Porffor's module-build cache includes them in its compiler command.
973
982
  src =
974
- marker === CFLAGS_MARKER
983
+ marker !== LINK_MARKER
975
984
  ? src.slice(0, at + anchor.length) + inject + src.slice(at + anchor.length)
976
985
  : src.slice(0, at) + inject + src.slice(at);
977
986
  changed = true;