@scriptc/compiler 0.0.34 → 0.0.36
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/ambient/scriptc-node-fallback.d.ts +7 -6
- package/dist/backend/build-cache.d.ts +44 -0
- package/dist/backend/build-cache.js +241 -0
- package/dist/backend/build-cache.js.map +1 -0
- package/dist/backend/{emission/emit-async.d.ts → c/async.d.ts} +21 -30
- package/dist/backend/{emission/emit-async.js → c/async.js} +270 -286
- package/dist/backend/c/async.js.map +1 -0
- package/dist/backend/{emission/emitter.d.ts → c/c-emitter.d.ts} +6 -23
- package/dist/backend/{emission/emitter.js → c/c-emitter.js} +53 -107
- package/dist/backend/c/c-emitter.js.map +1 -0
- package/dist/backend/c/exprs.d.ts +8 -0
- package/dist/backend/c/exprs.js +7913 -0
- package/dist/backend/c/exprs.js.map +1 -0
- package/dist/backend/{emission/emit-island.d.ts → c/island.d.ts} +5 -5
- package/dist/backend/{emission/emit-island.js → c/island.js} +24 -22
- package/dist/backend/c/island.js.map +1 -0
- package/dist/backend/{emission → c}/may-throw.d.ts +1 -1
- package/dist/backend/{emission → c}/may-throw.js +1 -1
- package/dist/backend/c/may-throw.js.map +1 -0
- package/dist/backend/{emission/emit-shapes.d.ts → c/shapes.d.ts} +19 -19
- package/dist/backend/{emission/emit-shapes.js → c/shapes.js} +78 -87
- package/dist/backend/c/shapes.js.map +1 -0
- package/dist/backend/{emission/emit-stmts.d.ts → c/stmts.d.ts} +12 -12
- package/dist/backend/{emission/emit-stmts.js → c/stmts.js} +377 -343
- package/dist/backend/c/stmts.js.map +1 -0
- package/dist/backend/{emission/emit-types.d.ts → c/types.d.ts} +1 -1
- package/dist/backend/{emission/emit-types.js → c/types.js} +42 -286
- package/dist/backend/c/types.js.map +1 -0
- package/dist/backend/c/types.test.js +25 -0
- package/dist/backend/c/types.test.js.map +1 -0
- package/dist/backend/{emission/emit-walkers.d.ts → c/walkers.d.ts} +19 -41
- package/dist/backend/{emission/emit-walkers.js → c/walkers.js} +238 -298
- package/dist/backend/c/walkers.js.map +1 -0
- package/dist/backend/ffi-callbacks.d.ts +1 -1
- package/dist/backend/ffi-callbacks.js +4 -3
- package/dist/backend/ffi-callbacks.js.map +1 -1
- package/dist/backend/{library-identity.d.ts → library-identity-markers.d.ts} +0 -4
- package/dist/backend/{library-identity.js → library-identity-markers.js} +10 -9
- package/dist/backend/library-identity-markers.js.map +1 -0
- package/dist/backend/llvm/blocks.d.ts +4 -0
- package/dist/backend/llvm/blocks.js +29 -0
- package/dist/backend/llvm/blocks.js.map +1 -1
- package/dist/backend/llvm/classes.d.ts +2 -19
- package/dist/backend/llvm/classes.js +20 -52
- package/dist/backend/llvm/classes.js.map +1 -1
- package/dist/backend/llvm/common.d.ts +4 -0
- package/dist/backend/llvm/common.js +27 -0
- package/dist/backend/llvm/common.js.map +1 -0
- package/dist/backend/llvm/dyn.d.ts +3 -8
- package/dist/backend/llvm/dyn.js +152 -224
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.d.ts +4 -1
- package/dist/backend/llvm/emitter.js +514 -9981
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/expr-async.d.ts +4 -0
- package/dist/backend/llvm/expr-async.js +726 -0
- package/dist/backend/llvm/expr-async.js.map +1 -0
- package/dist/backend/llvm/expr-bytes.d.ts +13 -0
- package/dist/backend/llvm/expr-bytes.js +456 -0
- package/dist/backend/llvm/expr-bytes.js.map +1 -0
- package/dist/backend/llvm/expr-callbacks.d.ts +22 -0
- package/dist/backend/llvm/expr-callbacks.js +520 -0
- package/dist/backend/llvm/expr-callbacks.js.map +1 -0
- package/dist/backend/llvm/expr-calls.d.ts +2 -0
- package/dist/backend/llvm/expr-calls.js +618 -0
- package/dist/backend/llvm/expr-calls.js.map +1 -0
- package/dist/backend/llvm/expr-containers.d.ts +25 -0
- package/dist/backend/llvm/expr-containers.js +623 -0
- package/dist/backend/llvm/expr-containers.js.map +1 -0
- package/dist/backend/llvm/expr-context.d.ts +255 -0
- package/dist/backend/llvm/expr-context.js +2 -0
- package/dist/backend/llvm/expr-context.js.map +1 -0
- package/dist/backend/llvm/expr-control.d.ts +2 -0
- package/dist/backend/llvm/expr-control.js +473 -0
- package/dist/backend/llvm/expr-control.js.map +1 -0
- package/dist/backend/llvm/expr-dispatch.d.ts +4 -0
- package/dist/backend/llvm/expr-dispatch.js +151 -0
- package/dist/backend/llvm/expr-dispatch.js.map +1 -0
- package/dist/backend/llvm/expr-dynamic.d.ts +2 -0
- package/dist/backend/llvm/expr-dynamic.js +810 -0
- package/dist/backend/llvm/expr-dynamic.js.map +1 -0
- package/dist/backend/llvm/expr-island.d.ts +15 -0
- package/dist/backend/llvm/expr-island.js +690 -0
- package/dist/backend/llvm/expr-island.js.map +1 -0
- package/dist/backend/llvm/expr-primitives.d.ts +6 -0
- package/dist/backend/llvm/expr-primitives.js +622 -0
- package/dist/backend/llvm/expr-primitives.js.map +1 -0
- package/dist/backend/llvm/expr-records.d.ts +9 -0
- package/dist/backend/llvm/expr-records.js +299 -0
- package/dist/backend/llvm/expr-records.js.map +1 -0
- package/dist/backend/llvm/expr-stream-bridges.d.ts +13 -0
- package/dist/backend/llvm/expr-stream-bridges.js +396 -0
- package/dist/backend/llvm/expr-stream-bridges.js.map +1 -0
- package/dist/backend/llvm/expr-stream-callbacks.d.ts +12 -0
- package/dist/backend/llvm/expr-stream-callbacks.js +360 -0
- package/dist/backend/llvm/expr-stream-callbacks.js.map +1 -0
- package/dist/backend/llvm/lib-dispatch.d.ts +5 -0
- package/dist/backend/llvm/lib-dispatch.js +267 -0
- package/dist/backend/llvm/lib-dispatch.js.map +1 -0
- package/dist/backend/llvm/lib-filesystem.d.ts +6 -0
- package/dist/backend/llvm/lib-filesystem.js +594 -0
- package/dist/backend/llvm/lib-filesystem.js.map +1 -0
- package/dist/backend/llvm/lib-network.d.ts +2 -0
- package/dist/backend/llvm/lib-network.js +429 -0
- package/dist/backend/llvm/lib-network.js.map +1 -0
- package/dist/backend/llvm/lib-process.d.ts +5 -0
- package/dist/backend/llvm/lib-process.js +585 -0
- package/dist/backend/llvm/lib-process.js.map +1 -0
- package/dist/backend/llvm/lib-shared.d.ts +6 -0
- package/dist/backend/llvm/lib-shared.js +687 -0
- package/dist/backend/llvm/lib-shared.js.map +1 -0
- package/dist/backend/llvm/lib-stream.d.ts +2 -0
- package/dist/backend/llvm/lib-stream.js +470 -0
- package/dist/backend/llvm/lib-stream.js.map +1 -0
- package/dist/backend/llvm/shapes.d.ts +12 -2
- package/dist/backend/llvm/shapes.js +66 -307
- package/dist/backend/llvm/shapes.js.map +1 -1
- package/dist/backend/llvm/shapes.test.js +52 -0
- package/dist/backend/llvm/shapes.test.js.map +1 -0
- package/dist/backend/llvm/split.d.ts +43 -0
- package/dist/backend/llvm/split.js +227 -0
- package/dist/backend/llvm/split.js.map +1 -0
- package/dist/backend/llvm/split.test.d.ts +1 -0
- package/dist/backend/llvm/split.test.js +102 -0
- package/dist/backend/llvm/split.test.js.map +1 -0
- package/dist/backend/llvm/unsupported.d.ts +1 -1
- package/dist/backend/llvm/walkers.d.ts +2 -3
- package/dist/backend/llvm/walkers.js +164 -218
- package/dist/backend/llvm/walkers.js.map +1 -1
- package/dist/backend/native-codegen.d.ts +38 -0
- package/dist/backend/native-codegen.js +261 -0
- package/dist/backend/native-codegen.js.map +1 -0
- package/dist/backend/native-codegen.test.d.ts +1 -0
- package/dist/backend/native-codegen.test.js +188 -0
- package/dist/backend/native-codegen.test.js.map +1 -0
- package/dist/backend/native-link-info.d.ts +96 -0
- package/dist/backend/native-link-info.js +219 -0
- package/dist/backend/native-link-info.js.map +1 -0
- package/dist/backend/native-link-info.test.d.ts +1 -0
- package/dist/backend/native-link-info.test.js +134 -0
- package/dist/backend/native-link-info.test.js.map +1 -0
- package/dist/backend/{cc.d.ts → native-toolchain.d.ts} +139 -47
- package/dist/backend/{cc.js → native-toolchain.js} +1008 -910
- package/dist/backend/native-toolchain.js.map +1 -0
- package/dist/backend/native-toolchain.test.d.ts +1 -0
- package/dist/backend/{cc-cache.test.js → native-toolchain.test.js} +1297 -127
- package/dist/backend/native-toolchain.test.js.map +1 -0
- package/dist/backend/object-localize.js +1 -1
- package/dist/backend/object-localize.test.js +20 -0
- package/dist/backend/object-localize.test.js.map +1 -1
- package/dist/backend/runtime-abi.d.ts +10 -0
- package/dist/backend/runtime-abi.js +11 -0
- package/dist/backend/runtime-abi.js.map +1 -0
- package/dist/backend/runtime-pack.d.ts +137 -0
- package/dist/backend/runtime-pack.js +400 -0
- package/dist/backend/runtime-pack.js.map +1 -0
- package/dist/backend/runtime-pack.test.d.ts +1 -0
- package/dist/backend/runtime-pack.test.js +438 -0
- package/dist/backend/runtime-pack.test.js.map +1 -0
- package/dist/backend/targets.d.ts +30 -0
- package/dist/backend/targets.js +46 -0
- package/dist/backend/targets.js.map +1 -0
- package/dist/backend/targets.test.d.ts +1 -0
- package/dist/backend/targets.test.js +19 -0
- package/dist/backend/targets.test.js.map +1 -0
- package/dist/backend/vendor-archives.d.ts +41 -0
- package/dist/backend/vendor-archives.js +462 -0
- package/dist/backend/vendor-archives.js.map +1 -0
- package/dist/compat/fetch-profile.d.ts +0 -1
- package/dist/compat/fetch-profile.js +0 -1
- package/dist/compat/fetch-profile.js.map +1 -1
- package/dist/coverage/report.js +3 -3
- package/dist/coverage/report.js.map +1 -1
- package/dist/coverage/surface-manifest.js +25 -32
- package/dist/coverage/surface-manifest.js.map +1 -1
- package/dist/diagnostics/diagnostic.d.ts +11 -12
- package/dist/diagnostics/diagnostic.js +19 -17
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/diagnostics/render.d.ts +1 -1
- package/dist/diagnostics/render.js +1 -1
- package/dist/diagnostics/render.js.map +1 -1
- package/dist/errors.d.ts +5 -0
- package/dist/errors.js +9 -0
- package/dist/errors.js.map +1 -0
- package/dist/executable/executable-cache.d.ts +86 -0
- package/dist/executable/executable-cache.js +398 -0
- package/dist/executable/executable-cache.js.map +1 -0
- package/dist/executable/executable-cache.test.d.ts +1 -0
- package/dist/executable/executable-cache.test.js +300 -0
- package/dist/executable/executable-cache.test.js.map +1 -0
- package/dist/ffi/{profile.d.ts → ffi-manifest.d.ts} +7 -2
- package/dist/ffi/{profile.js → ffi-manifest.js} +2 -1
- package/dist/ffi/ffi-manifest.js.map +1 -0
- package/dist/frontend/builtin-modules.d.ts +21 -0
- package/dist/frontend/builtin-modules.js +82 -0
- package/dist/frontend/builtin-modules.js.map +1 -0
- package/dist/frontend/cjs-lexer.d.ts +7 -0
- package/dist/frontend/cjs-lexer.js +15 -17
- package/dist/frontend/cjs-lexer.js.map +1 -1
- package/dist/frontend/dts-paths.d.ts +26 -0
- package/dist/frontend/dts-paths.js +41 -0
- package/dist/frontend/dts-paths.js.map +1 -0
- package/dist/frontend/input-tracker.d.ts +0 -2
- package/dist/frontend/input-tracker.js +1 -4
- package/dist/frontend/input-tracker.js.map +1 -1
- package/dist/frontend/lib-contract.d.ts +1 -5
- package/dist/frontend/lib-contract.js +1 -1
- package/dist/frontend/lib-contract.js.map +1 -1
- package/dist/frontend/lib-exports.d.ts +1 -1
- package/dist/frontend/lowering/call-position.d.ts +4 -0
- package/dist/frontend/lowering/call-position.js +7 -0
- package/dist/frontend/lowering/call-position.js.map +1 -0
- package/dist/frontend/lowering/callback-arg.d.ts +20 -0
- package/dist/frontend/lowering/callback-arg.js +44 -0
- package/dist/frontend/lowering/callback-arg.js.map +1 -0
- package/dist/frontend/lowering/lib-boundary.d.ts +1 -1
- package/dist/frontend/lowering/lib-boundary.js +34 -34
- package/dist/frontend/lowering/lib-boundary.js.map +1 -1
- package/dist/frontend/lowering/lower-assert.d.ts +3 -3
- package/dist/frontend/lowering/lower-assert.js +214 -241
- package/dist/frontend/lowering/lower-assert.js.map +1 -1
- package/dist/frontend/lowering/lower-builtins.d.ts +74 -146
- package/dist/frontend/lowering/lower-builtins.js +1126 -1234
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.d.ts +56 -188
- package/dist/frontend/lowering/lower-calls.js +1409 -1494
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.d.ts +62 -125
- package/dist/frontend/lowering/lower-classes.js +832 -866
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-comptime.d.ts +5 -12
- package/dist/frontend/lowering/lower-comptime.js +32 -31
- package/dist/frontend/lowering/lower-comptime.js.map +1 -1
- package/dist/frontend/lowering/lower-containers.d.ts +35 -135
- package/dist/frontend/lowering/lower-containers.js +1394 -1715
- package/dist/frontend/lowering/lower-containers.js.map +1 -1
- package/dist/frontend/lowering/lower-dgram.d.ts +3 -7
- package/dist/frontend/lowering/lower-dgram.js +97 -111
- package/dist/frontend/lowering/lower-dgram.js.map +1 -1
- package/dist/frontend/lowering/lower-enums.d.ts +4 -4
- package/dist/frontend/lowering/lower-enums.js +30 -30
- package/dist/frontend/lowering/lower-enums.js.map +1 -1
- package/dist/frontend/lowering/{lower-emitter.d.ts → lower-event-emitter.d.ts} +7 -18
- package/dist/frontend/lowering/{lower-emitter.js → lower-event-emitter.js} +172 -174
- package/dist/frontend/lowering/lower-event-emitter.js.map +1 -0
- package/dist/frontend/lowering/lower-expando.d.ts +5 -5
- package/dist/frontend/lowering/lower-expando.js +40 -40
- package/dist/frontend/lowering/lower-expando.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +56 -264
- package/dist/frontend/lowering/lower-exprs.js +1644 -1704
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-generators.d.ts +6 -6
- package/dist/frontend/lowering/lower-generators.js +73 -72
- package/dist/frontend/lowering/lower-generators.js.map +1 -1
- package/dist/frontend/lowering/lower-inspect.d.ts +5 -17
- package/dist/frontend/lowering/lower-inspect.js +236 -238
- package/dist/frontend/lowering/lower-inspect.js.map +1 -1
- package/dist/frontend/lowering/lower-island.d.ts +34 -45
- package/dist/frontend/lowering/lower-island.js +440 -511
- package/dist/frontend/lowering/lower-island.js.map +1 -1
- package/dist/frontend/lowering/lower-mixins.d.ts +7 -10
- package/dist/frontend/lowering/lower-mixins.js +86 -86
- package/dist/frontend/lowering/lower-mixins.js.map +1 -1
- package/dist/frontend/lowering/lower-modules.d.ts +12 -18
- package/dist/frontend/lowering/lower-modules.js +284 -283
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-namespaces.d.ts +23 -49
- package/dist/frontend/lowering/lower-namespaces.js +123 -115
- package/dist/frontend/lowering/lower-namespaces.js.map +1 -1
- package/dist/frontend/lowering/lower-server.d.ts +18 -25
- package/dist/frontend/lowering/lower-server.js +899 -899
- package/dist/frontend/lowering/lower-server.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.d.ts +30 -149
- package/dist/frontend/lowering/lower-stmts.js +1176 -1229
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lower-stream.d.ts +12 -12
- package/dist/frontend/lowering/lower-stream.js +300 -301
- package/dist/frontend/lowering/lower-stream.js.map +1 -1
- package/dist/frontend/lowering/lower-test.d.ts +5 -5
- package/dist/frontend/lowering/lower-test.js +82 -82
- package/dist/frontend/lowering/lower-test.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +66 -20
- package/dist/frontend/lowering/lowerer.js +421 -198
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/pairs-snapshot.d.ts +11 -0
- package/dist/frontend/lowering/pairs-snapshot.js +85 -0
- package/dist/frontend/lowering/pairs-snapshot.js.map +1 -0
- package/dist/frontend/lowering/static-this.d.ts +6 -0
- package/dist/frontend/lowering/static-this.js +24 -0
- package/dist/frontend/lowering/static-this.js.map +1 -0
- package/dist/frontend/lowering/surfaces.d.ts +14 -33
- package/dist/frontend/lowering/surfaces.js +64 -64
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/frontend/npm-static-rewrite.js +6 -28
- package/dist/frontend/npm-static-rewrite.js.map +1 -1
- package/dist/frontend/npm-static.d.ts +0 -10
- package/dist/frontend/npm-static.js +4 -7
- package/dist/frontend/npm-static.js.map +1 -1
- package/dist/frontend/npm.d.ts +0 -24
- package/dist/frontend/npm.js +11 -85
- package/dist/frontend/npm.js.map +1 -1
- package/dist/frontend/program.d.ts +3 -1
- package/dist/frontend/program.js +51 -17
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/provenance-registry.d.ts +0 -2
- package/dist/frontend/provenance-registry.js +0 -10
- package/dist/frontend/provenance-registry.js.map +1 -1
- package/dist/frontend/provenance.js +4 -7
- package/dist/frontend/provenance.js.map +1 -1
- package/dist/frontend/resolve.d.ts +7 -2
- package/dist/frontend/resolve.js +12 -17
- package/dist/frontend/resolve.js.map +1 -1
- package/dist/frontend/resolve.test.d.ts +1 -0
- package/dist/frontend/resolve.test.js +29 -0
- package/dist/frontend/resolve.test.js.map +1 -0
- package/dist/frontend/ts7/adapter.d.ts +2 -2
- package/dist/frontend/ts7/adapter.js +1 -1
- package/dist/frontend/ts7/adapter.js.map +1 -1
- package/dist/frontend/ts7/checker.d.ts +40 -2
- package/dist/frontend/ts7/checker.js +182 -54
- package/dist/frontend/ts7/checker.js.map +1 -1
- package/dist/frontend/ts7/enums.js +2 -1
- package/dist/frontend/ts7/enums.js.map +1 -1
- package/dist/frontend/ts7/{program.js → program-adapter.js} +7 -6
- package/dist/frontend/ts7/program-adapter.js.map +1 -0
- package/dist/frontend/ts7/world-check.d.ts +1 -3
- package/dist/frontend/ts7/world-check.js +3 -1
- package/dist/frontend/ts7/world-check.js.map +1 -1
- package/dist/frontend/tsc-codes.d.ts +8 -0
- package/dist/frontend/tsc-codes.js +109 -0
- package/dist/frontend/tsc-codes.js.map +1 -0
- package/dist/frontend/{types.d.ts → type-mapper.d.ts} +2 -25
- package/dist/frontend/{types.js → type-mapper.js} +31 -52
- package/dist/frontend/type-mapper.js.map +1 -0
- package/dist/frontend/workspace-registry.d.ts +26 -0
- package/dist/frontend/workspace-registry.js +71 -0
- package/dist/frontend/workspace-registry.js.map +1 -0
- package/dist/index.d.ts +115 -30
- package/dist/index.js +650 -146
- package/dist/index.js.map +1 -1
- package/dist/ir/analysis.d.ts +37 -0
- package/dist/ir/analysis.js +118 -0
- package/dist/ir/analysis.js.map +1 -0
- package/dist/ir/analysis.test.d.ts +1 -0
- package/dist/ir/analysis.test.js +21 -0
- package/dist/ir/analysis.test.js.map +1 -0
- package/dist/ir/build.d.ts +9 -0
- package/dist/ir/build.js +33 -0
- package/dist/ir/build.js.map +1 -0
- package/dist/ir/integer-loops.d.ts +1 -1
- package/dist/ir/{nodes.d.ts → ir.d.ts} +60 -26
- package/dist/ir/{nodes.js → ir.js} +173 -141
- package/dist/ir/ir.js.map +1 -0
- package/dist/ir/ir.test.d.ts +1 -0
- package/dist/ir/ir.test.js +17 -0
- package/dist/ir/ir.test.js.map +1 -0
- package/dist/ir/serialize.d.ts +1 -1
- package/dist/ir/serialize.js +2 -1
- package/dist/ir/serialize.js.map +1 -1
- package/dist/ir/validate.d.ts +1 -1
- package/dist/ir/validate.js +16 -5
- package/dist/ir/validate.js.map +1 -1
- package/dist/library/cache-primitives.d.ts +24 -0
- package/dist/library/cache-primitives.js +83 -0
- package/dist/library/cache-primitives.js.map +1 -0
- package/dist/library/cache-reset.test.d.ts +1 -0
- package/dist/library/cache-reset.test.js +47 -0
- package/dist/library/cache-reset.test.js.map +1 -0
- package/dist/library/compiler-self-identity.d.ts +23 -0
- package/dist/library/compiler-self-identity.js +84 -0
- package/dist/library/compiler-self-identity.js.map +1 -0
- package/dist/library/fence-eval.d.ts +3 -1
- package/dist/library/fence-eval.js +5 -1
- package/dist/library/fence-eval.js.map +1 -1
- package/dist/library/int-infer.d.ts +1 -36
- package/dist/library/int-infer.js +17 -17
- package/dist/library/int-infer.js.map +1 -1
- package/dist/library/{early-cache.d.ts → library-cache.d.ts} +1 -1
- package/dist/library/{early-cache.js → library-cache.js} +37 -111
- package/dist/library/library-cache.js.map +1 -0
- package/dist/library/library-cache.test.d.ts +1 -0
- package/dist/library/{early-cache.test.js → library-cache.test.js} +2 -2
- package/dist/library/library-cache.test.js.map +1 -0
- package/dist/library/{profile.d.ts → library-profile.d.ts} +4 -7
- package/dist/library/{profile.js → library-profile.js} +18 -11
- package/dist/library/library-profile.js.map +1 -0
- package/dist/library/semantic-source.js +2 -1
- package/dist/library/semantic-source.js.map +1 -1
- package/dist/library/sidecar.d.ts +5 -2
- package/dist/library/sidecar.js +11 -5
- package/dist/library/sidecar.js.map +1 -1
- package/dist/startup-cache.d.ts +6 -0
- package/dist/startup-cache.js +6 -0
- package/dist/startup-cache.js.map +1 -0
- package/package.json +7 -2
- package/surface-manifest.json +1 -1
- package/dist/backend/cc-cache.test.js.map +0 -1
- package/dist/backend/cc.js.map +0 -1
- package/dist/backend/emission/emit-async.js.map +0 -1
- package/dist/backend/emission/emit-exprs.d.ts +0 -13
- package/dist/backend/emission/emit-exprs.js +0 -7407
- package/dist/backend/emission/emit-exprs.js.map +0 -1
- package/dist/backend/emission/emit-island.js.map +0 -1
- package/dist/backend/emission/emit-shapes.js.map +0 -1
- package/dist/backend/emission/emit-stmts.js.map +0 -1
- package/dist/backend/emission/emit-types.js.map +0 -1
- package/dist/backend/emission/emit-walkers.js.map +0 -1
- package/dist/backend/emission/emitter.js.map +0 -1
- package/dist/backend/emission/may-throw.js.map +0 -1
- package/dist/backend/library-identity.js.map +0 -1
- package/dist/ffi/profile.js.map +0 -1
- package/dist/frontend/lowering/lower-emitter.js.map +0 -1
- package/dist/frontend/shared.d.ts +0 -82
- package/dist/frontend/shared.js +0 -308
- package/dist/frontend/shared.js.map +0 -1
- package/dist/frontend/ts7/program.js.map +0 -1
- package/dist/frontend/types.js.map +0 -1
- package/dist/ir/nodes.js.map +0 -1
- package/dist/library/early-cache.js.map +0 -1
- package/dist/library/early-cache.test.js.map +0 -1
- package/dist/library/profile.js.map +0 -1
- /package/dist/backend/{cc-cache.test.d.ts → c/types.test.d.ts} +0 -0
- /package/dist/{library/early-cache.test.d.ts → backend/llvm/shapes.test.d.ts} +0 -0
- /package/dist/frontend/ts7/{program.d.ts → program-adapter.d.ts} +0 -0
|
@@ -1,13 +1,25 @@
|
|
|
1
|
+
import { InternalCompilerError } from "../errors.js";
|
|
1
2
|
import { execFile, spawnSync } from "node:child_process";
|
|
2
|
-
import { createHash } from "node:crypto";
|
|
3
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
4
|
import { constants as fsConstants, existsSync, readdirSync } from "node:fs";
|
|
4
5
|
import { createRequire } from "node:module";
|
|
5
|
-
import { access, chmod, copyFile, link, lstat, mkdir, mkdtemp, readdir, readFile, realpath, rename, rm, stat,
|
|
6
|
+
import { access, chmod, copyFile, link, lstat, mkdir, mkdtemp, readdir, readFile, realpath, rename, rm, stat, utimes, writeFile } from "node:fs/promises";
|
|
6
7
|
import { availableParallelism, homedir, tmpdir } from "node:os";
|
|
7
8
|
import { basename, delimiter, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
9
|
+
import { performance } from "node:perf_hooks";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
8
11
|
import { promisify } from "node:util";
|
|
9
12
|
import { localizeElfObject, mergeAndLocalizeCoffObjects } from "./object-localize.js";
|
|
13
|
+
import { createVendorArchives, MBEDTLS_VERSION, QJS_COMMIT, ZLIB_VERSION, } from "./vendor-archives.js";
|
|
14
|
+
import { cacheDigestPath, cacheRootDir, copyValidCachedFile, ensurePrivateCacheRoot, fileDigest, fileExists, installArtifact, privateSiblingPath, protectCachedArtifact, pruneCache, publishCachedFile, validCachedFile, } from "./build-cache.js";
|
|
15
|
+
export { buildCacheRoot, prepareBuildCacheRoot, pruneBuildCache, resolveBuildCacheRoot, } from "./build-cache.js";
|
|
10
16
|
const execFileAsync = promisify(execFile);
|
|
17
|
+
const NATIVE_TOOLCHAIN_IMPLEMENTATION_PATH = fileURLToPath(import.meta.url);
|
|
18
|
+
const NATIVE_RECIPE_IMPLEMENTATION_PATHS = [
|
|
19
|
+
NATIVE_TOOLCHAIN_IMPLEMENTATION_PATH,
|
|
20
|
+
join(dirname(NATIVE_TOOLCHAIN_IMPLEMENTATION_PATH), `build-cache${extname(NATIVE_TOOLCHAIN_IMPLEMENTATION_PATH)}`),
|
|
21
|
+
join(dirname(NATIVE_TOOLCHAIN_IMPLEMENTATION_PATH), `vendor-archives${extname(NATIVE_TOOLCHAIN_IMPLEMENTATION_PATH)}`),
|
|
22
|
+
];
|
|
11
23
|
/** Test lanes run against one immutable checkout and one immutable toolchain
|
|
12
24
|
* for the lifetime of each Vitest worker. Production deliberately rediscovers
|
|
13
25
|
* compiler/linker inputs on every invocation, but doing that thousands of
|
|
@@ -32,22 +44,43 @@ function stableTestMemo(cache, key, probe) {
|
|
|
32
44
|
});
|
|
33
45
|
return pending;
|
|
34
46
|
}
|
|
35
|
-
const
|
|
36
|
-
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
export const EXECUTABLE_RUNTIME_SOURCES = ["scr_number.c", "scr_string.c", "scr_array.c", "scr_bytes.c", "scr_bytes_io.c", "scr_map.c", "scr_closure.c", "scr_ffi.c", "scr_object.c", "scr_union.c", "scr_exception.c", "scr_error.c", "scr_console.c", "scr_lib.c", "scr_path.c", "scr_url.c", "scr_json.c", "scr_async.c", "scr_child.c", "scr_cycle.c"];
|
|
48
|
+
/**
|
|
49
|
+
* Per-executable section-elimination recipe. This belongs beside the native
|
|
50
|
+
* driver rather than a particular build path: one-shot compilation, cached
|
|
51
|
+
* runtime objects, external object recipes, and runtime packs must all agree
|
|
52
|
+
* on what their final executable linker is allowed to discard.
|
|
53
|
+
*
|
|
54
|
+
* Archives and compile-only object/library recipes deliberately do not use
|
|
55
|
+
* the link half. In particular, `--lib` preserves its established object and
|
|
56
|
+
* archive contract; section GC is an executable-link optimization only.
|
|
57
|
+
*/
|
|
58
|
+
export function executableSectionEliminationFlags(platform) {
|
|
59
|
+
switch (platform) {
|
|
60
|
+
case "darwin":
|
|
61
|
+
// ld64's symbol subsections make this sufficient for ordinary C/LLVM
|
|
62
|
+
// objects. Do not attach it only to --dynamic: static programs have the
|
|
63
|
+
// same unreachable runtime sections.
|
|
64
|
+
return { compile: [], link: ["-Wl,-dead_strip"] };
|
|
65
|
+
case "linux":
|
|
66
|
+
return {
|
|
67
|
+
compile: ["-ffunction-sections", "-fdata-sections"],
|
|
68
|
+
link: ["-Wl,--gc-sections"],
|
|
69
|
+
};
|
|
70
|
+
case "win32":
|
|
71
|
+
// clang's MinGW driver forwards this to GNU-flavor ld/lld. A direct
|
|
72
|
+
// local lld-link invocation instead uses /OPT:REF, but scriptc only
|
|
73
|
+
// drives the compiler's GNU-flavor route here.
|
|
74
|
+
return {
|
|
75
|
+
compile: ["-ffunction-sections", "-fdata-sections"],
|
|
76
|
+
link: ["-Wl,--gc-sections"],
|
|
77
|
+
};
|
|
78
|
+
// WASI has a distinct linker/runtime contract. Keep its existing object
|
|
79
|
+
// layout until its linker invocation is validated separately.
|
|
80
|
+
default:
|
|
81
|
+
return { compile: [], link: [] };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
51
84
|
/** Environment variables consumed by clang, its linker/subtools, or the
|
|
52
85
|
* platform SDK selection. They are implicit command-line inputs: changing one
|
|
53
86
|
* must never reuse an artifact produced under the old toolchain posture. */
|
|
@@ -118,17 +151,6 @@ const MUTABLE_COMPILE_ENV_KEYS = [
|
|
|
118
151
|
"CCC_ADD_ARGS",
|
|
119
152
|
"CLANG_CONFIG_FILE_SYSTEM_DIR",
|
|
120
153
|
"CLANG_CONFIG_FILE_USER_DIR",
|
|
121
|
-
// The direct runtime build does not consume the conventional Make/CMake
|
|
122
|
-
// variables, but the lazily-built QuickJS prerequisite does. Their values
|
|
123
|
-
// can name mutable tools, headers, libraries, and toolchain files, so a
|
|
124
|
-
// stable spelling is not a safe persistent-cache identity.
|
|
125
|
-
"CC",
|
|
126
|
-
"CFLAGS",
|
|
127
|
-
"CPPFLAGS",
|
|
128
|
-
"LDFLAGS",
|
|
129
|
-
"AR",
|
|
130
|
-
"RANLIB",
|
|
131
|
-
"CMAKE_TOOLCHAIN_FILE",
|
|
132
154
|
// `zig cc` resolves its bundled headers/runtime through ZIG_LIB_DIR and a
|
|
133
155
|
// caller-selected native libc description through ZIG_LIBC. Both values name
|
|
134
156
|
// mutable compiler inputs whose contents can change behind a stable path.
|
|
@@ -155,6 +177,41 @@ export function toolchainEnvironmentFingerprint(env = process.env) {
|
|
|
155
177
|
}
|
|
156
178
|
return hash.digest("hex");
|
|
157
179
|
}
|
|
180
|
+
/** Inputs that can change which native tool/runtime implementation an
|
|
181
|
+
* executable build selects before compileC has a chance to rediscover it.
|
|
182
|
+
* The early whole-program cache keys this exact posture before restoring a
|
|
183
|
+
* final binary; compileC retains its deeper inode/content validation. */
|
|
184
|
+
export async function executableNativeEnvironmentFingerprint(env = process.env) {
|
|
185
|
+
const configuredCompiler = env["SCRIPTC_CC"] ?? "";
|
|
186
|
+
let compilerIdentity;
|
|
187
|
+
try {
|
|
188
|
+
compilerIdentity = await effectiveCompilerEnvironmentIdentity(resolveCc(env), env);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
// A failed trace cannot safely describe a reusable native posture. Keep
|
|
192
|
+
// the build working, but make this invocation miss every persistent early
|
|
193
|
+
// entry so compileC performs its full discovery and validation.
|
|
194
|
+
compilerIdentity = `<unavailable:${configuredCompiler}:${randomUUID()}>`;
|
|
195
|
+
}
|
|
196
|
+
const hash = createHash("sha256")
|
|
197
|
+
.update("executable-native-environment-v2\0")
|
|
198
|
+
.update(toolchainEnvironmentFingerprint(env)).update("\0")
|
|
199
|
+
// PATH text alone is not a resolution proof, and on Darwin /usr/bin/clang
|
|
200
|
+
// is a stable shim whose selected Xcode compiler can change underneath it.
|
|
201
|
+
// Re-resolve and trace the effective driver on every early lookup.
|
|
202
|
+
.update(compilerIdentity).update("\0");
|
|
203
|
+
for (const name of [
|
|
204
|
+
"PATH",
|
|
205
|
+
"SCRIPTC_FETCH_CURL",
|
|
206
|
+
"SCRIPTC_TEST_RUNTIME_SRC_DIR",
|
|
207
|
+
"SCRIPTC_TEST_VENDOR_CACHE_DIR",
|
|
208
|
+
"SCRIPTC_TEST_TRUST_COMPILER_WRAPPER",
|
|
209
|
+
]) {
|
|
210
|
+
const value = env[name];
|
|
211
|
+
hash.update(name).update(value === undefined ? "\0unset\0" : "\0set\0").update(value ?? "").update("\0");
|
|
212
|
+
}
|
|
213
|
+
return hash.digest("hex");
|
|
214
|
+
}
|
|
158
215
|
/** Structured compiler-driver failure. Most callers still let this surface
|
|
159
216
|
* as an internal build error; the TypeScript compiler pipeline recognizes it
|
|
160
217
|
* when an outbound FFI profile is active and turns user-controlled native
|
|
@@ -200,6 +257,12 @@ export function runtimeSrcDir() {
|
|
|
200
257
|
const require = createRequire(import.meta.url);
|
|
201
258
|
return join(dirname(require.resolve("@scriptc/runtime/package.json")), "src");
|
|
202
259
|
}
|
|
260
|
+
/** Whether the selected compiler driver is Zig. This is deliberately based on
|
|
261
|
+
* the executable prefix rather than target presence: targetless Zig is still
|
|
262
|
+
* a Zig build, while target presence is only the platform/target contract. */
|
|
263
|
+
export function isZigDriver(driver) {
|
|
264
|
+
return driver.argv[0] === "zig";
|
|
265
|
+
}
|
|
203
266
|
/* ── mobile targets (library mode) ─────────────────────────────────────────
|
|
204
267
|
* Three mobile triples are admitted, and only for LIBRARY-MODE archive
|
|
205
268
|
* builds — the consuming pattern is an embedding app linking the archive,
|
|
@@ -397,9 +460,9 @@ export function resolveCc(env = process.env, hostPlatform = process.platform) {
|
|
|
397
460
|
}
|
|
398
461
|
if (target === "")
|
|
399
462
|
return { argv: ["zig", "cc"], target: null, zigTarget: null, ...hostArgs };
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
463
|
+
// Validate the target spelling before any SDK/sysroot discovery. Source
|
|
464
|
+
// emission uses the same pure classifier without resolving this driver.
|
|
465
|
+
configuredTargetPlatform(env, hostPlatform);
|
|
403
466
|
const mobileRefusal = mobileTargetRefusal(target, hostPlatform);
|
|
404
467
|
if (mobileRefusal !== null)
|
|
405
468
|
throw new Error(mobileRefusal);
|
|
@@ -470,7 +533,7 @@ export function resolveCc(env = process.env, hostPlatform = process.platform) {
|
|
|
470
533
|
}
|
|
471
534
|
/** Musl intentionally has no predefined libc macro. The explicit Zig target
|
|
472
535
|
* is therefore the source of truth for selecting its small runtime shim. */
|
|
473
|
-
|
|
536
|
+
function isMuslTarget(driver) {
|
|
474
537
|
return driver.target?.includes("linux-musl") ?? false;
|
|
475
538
|
}
|
|
476
539
|
/** The OS the produced binary runs on: the triple's OS under SCRIPTC_TARGET,
|
|
@@ -479,21 +542,37 @@ export function isMuslTarget(driver) {
|
|
|
479
542
|
* analyze(): the FRONTEND consults it too (path.sep / os.EOL literals and
|
|
480
543
|
* the path-module binding follow the target — a win32 triple compiles
|
|
481
544
|
* Node-on-Windows semantics, path.win32 backing the bare module). */
|
|
482
|
-
export function
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
545
|
+
export function configuredTargetPlatform(env = process.env, hostPlatform = process.platform) {
|
|
546
|
+
const target = env["SCRIPTC_TARGET"] ?? "";
|
|
547
|
+
if (target === "")
|
|
548
|
+
return hostPlatform;
|
|
549
|
+
if (target === "wasm32-wasi")
|
|
486
550
|
return "wasi";
|
|
551
|
+
if (target.includes("wasi")) {
|
|
552
|
+
throw new Error(`unsupported WASI target '${target}' (supported: wasm32-wasi)`);
|
|
553
|
+
}
|
|
487
554
|
// iOS is a darwin-family target: Mach-O objects, ld64 localization,
|
|
488
555
|
// POSIX path/EOL semantics. Android falls to the linux arm below —
|
|
489
556
|
// bionic is a linux libc and its archives are ordinary ELF.
|
|
490
|
-
if (isIosTarget(
|
|
557
|
+
if (isIosTarget(target))
|
|
491
558
|
return "darwin";
|
|
492
|
-
if (
|
|
559
|
+
if (isAndroidTarget(target))
|
|
493
560
|
return "linux";
|
|
494
|
-
if (
|
|
561
|
+
if (/(?:^|-)(?:ios|tvos|watchos|visionos|android)/.test(target)) {
|
|
562
|
+
throw new Error(`unsupported mobile target '${target}' (supported: ${MOBILE_LIBRARY_TARGETS.join(", ")})`);
|
|
563
|
+
}
|
|
564
|
+
if (target.includes("linux"))
|
|
565
|
+
return "linux";
|
|
566
|
+
if (target.includes("windows"))
|
|
495
567
|
return "win32";
|
|
496
|
-
|
|
568
|
+
if (target.includes("macos") || target.includes("darwin"))
|
|
569
|
+
return "darwin";
|
|
570
|
+
throw new Error(`unsupported target '${target}' (supported OS families: linux, windows, macos/darwin, wasm32-wasi)`);
|
|
571
|
+
}
|
|
572
|
+
export function targetPlatform(driver) {
|
|
573
|
+
if (driver.target === null)
|
|
574
|
+
return process.platform;
|
|
575
|
+
return configuredTargetPlatform({ SCRIPTC_TARGET: driver.target });
|
|
497
576
|
}
|
|
498
577
|
/** Architecture identity for host-native cache entries. Explicit cross targets
|
|
499
578
|
* already name their complete target triple; native builds need the process
|
|
@@ -502,455 +581,6 @@ export function targetPlatform(driver) {
|
|
|
502
581
|
export function cacheTargetIdentity(driver, hostPlatform = process.platform, hostArch = process.arch) {
|
|
503
582
|
return driver.target === null ? `native:${hostPlatform}:${hostArch}` : `cross:${driver.target}`;
|
|
504
583
|
}
|
|
505
|
-
function vendorEngineDir() {
|
|
506
|
-
return join(runtimeSrcDir(), "..", "vendor", "quickjs-ng");
|
|
507
|
-
}
|
|
508
|
-
/** Vendor prerequisites have historically lived beside their source trees.
|
|
509
|
-
* The test-only override gives cache-ordering tests a disposable root without
|
|
510
|
-
* renaming package-local artifacts that parallel workers may be using. */
|
|
511
|
-
function vendorBuildCacheRoot() {
|
|
512
|
-
const testRoot = process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"];
|
|
513
|
-
return testRoot === undefined
|
|
514
|
-
? join(runtimeSrcDir(), "..", "vendor", ".cache")
|
|
515
|
-
: resolve(testRoot);
|
|
516
|
-
}
|
|
517
|
-
/** Cross targets already carry their architecture in the explicit triple.
|
|
518
|
-
* Host-native prerequisites need the same separation: a checkout/package can
|
|
519
|
-
* be used by both arm64 Node and Rosetta/x64 Node on macOS. */
|
|
520
|
-
export function vendorCacheTargetFlavor(driver, hostPlatform = process.platform, hostArch = process.arch) {
|
|
521
|
-
return driver.target === null ? `native-${hostPlatform}-${hostArch}` : driver.target;
|
|
522
|
-
}
|
|
523
|
-
/** Vendor prerequisites live outside the content-addressed build root, so
|
|
524
|
-
* their directory name must carry the same environment and resolved compiler
|
|
525
|
-
* identity as the artifacts that consume them. The short digest keeps the
|
|
526
|
-
* package-local path readable while preventing build-order contamination. */
|
|
527
|
-
export function vendorCacheBuildIdentity(environmentFingerprint, compilerIdentity) {
|
|
528
|
-
return createHash("sha256")
|
|
529
|
-
.update("vendor-toolchain-v1\0")
|
|
530
|
-
.update(environmentFingerprint)
|
|
531
|
-
.update("\0")
|
|
532
|
-
.update(compilerIdentity)
|
|
533
|
-
.digest("hex")
|
|
534
|
-
.slice(0, 20);
|
|
535
|
-
}
|
|
536
|
-
const vendorCompilerIdentityFallbacks = new Map();
|
|
537
|
-
async function currentVendorCacheBuildIdentity(driver, environmentFingerprint) {
|
|
538
|
-
// Native vendor recipes additionally use bare clang/ar and CMake; cross
|
|
539
|
-
// recipes use the zig driver for compilation and `zig ar`. Include every
|
|
540
|
-
// executable that can affect the package-local prerequisite, not just the
|
|
541
|
-
// final program's driver.
|
|
542
|
-
const commands = [
|
|
543
|
-
driver.argv[0] ?? "clang",
|
|
544
|
-
...(driver.target === null ? ["clang", "cc", "ar", "ranlib", "cmake"] : []),
|
|
545
|
-
].filter((command, index, all) => all.indexOf(command) === index);
|
|
546
|
-
const identities = await Promise.all(commands.map(async (command) => {
|
|
547
|
-
const spellingKey = `${environmentFingerprint}\0${command}`;
|
|
548
|
-
const resolved = await resolvedToolIdentity(command);
|
|
549
|
-
if (resolved !== null)
|
|
550
|
-
vendorCompilerIdentityFallbacks.set(spellingKey, resolved);
|
|
551
|
-
return [
|
|
552
|
-
command,
|
|
553
|
-
resolved ??
|
|
554
|
-
vendorCompilerIdentityFallbacks.get(spellingKey) ??
|
|
555
|
-
`<unresolved>\0${command}`,
|
|
556
|
-
].join("\0");
|
|
557
|
-
}));
|
|
558
|
-
return vendorCacheBuildIdentity(environmentFingerprint, identities.join("\x1f"));
|
|
559
|
-
}
|
|
560
|
-
function engineArchivePath(sanitize, driver, buildIdentity, cacheRoot = vendorBuildCacheRoot()) {
|
|
561
|
-
const flavor = `${sanitize ? "asan" : "plain"}-${vendorCacheTargetFlavor(driver)}-${buildIdentity}`;
|
|
562
|
-
return join(cacheRoot, `${QJS_COMMIT.slice(0, 12)}-${flavor}`, "libqjs.a");
|
|
563
|
-
}
|
|
564
|
-
async function fileExists(path) {
|
|
565
|
-
return stat(path).then((s) => s.isFile(), () => false);
|
|
566
|
-
}
|
|
567
|
-
function privateSiblingPath(destination, label) {
|
|
568
|
-
// Keep the temporary component independent of the caller's basename. A
|
|
569
|
-
// destination can validly consume the filesystem's entire NAME_MAX budget;
|
|
570
|
-
// appending or prepending that basename would make the atomic install fail.
|
|
571
|
-
return join(dirname(destination), `.scriptc-${label}-${process.pid}-${Math.random().toString(36).slice(2)}`);
|
|
572
|
-
}
|
|
573
|
-
/** Copy an artifact through a private sibling name, then atomically install it.
|
|
574
|
-
* The source can live on another filesystem (cache/temp roots commonly do), so
|
|
575
|
-
* a direct rename is not portable. */
|
|
576
|
-
async function installArtifact(source, destination, mode) {
|
|
577
|
-
await mkdir(dirname(destination), { recursive: true });
|
|
578
|
-
const tmp = privateSiblingPath(destination, "install");
|
|
579
|
-
try {
|
|
580
|
-
await copyFile(source, tmp);
|
|
581
|
-
if (mode !== undefined)
|
|
582
|
-
await chmod(tmp, mode);
|
|
583
|
-
await rename(tmp, destination);
|
|
584
|
-
}
|
|
585
|
-
finally {
|
|
586
|
-
await rm(tmp, { force: true }).catch(() => undefined);
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
/** The engine archive for one flavor, built lazily on the first --dynamic
|
|
590
|
-
* compile (~10s) and cached under vendor/.cache/<commit>-<flavor>-<target>-<toolchain>/ — unlike
|
|
591
|
-
* the runtime's own sources (recompiled every build, ~100ms), the engine is
|
|
592
|
-
* far too big to rebuild per compile. The plain flavor is ALWAYS MinSizeRel
|
|
593
|
-
* regardless of the program's optimization level: measured binary delta is
|
|
594
|
-
* ~620KB vs ~900KB at -O2, and eval-in-an-island workloads don't earn the
|
|
595
|
-
* difference. The asan flavor (Debug + QJS_ENABLE_ASAN) exists so the
|
|
596
|
-
* sanitized test lane checks engine interop under instrumentation.
|
|
597
|
-
*
|
|
598
|
-
* Concurrency: parallel first builds (e.g. test workers) each build in a
|
|
599
|
-
* private temp dir and publish with an atomic rename — first one wins,
|
|
600
|
-
* losers discard their work and use the winner's archive.
|
|
601
|
-
*
|
|
602
|
-
* SCRIPTC_TARGET adds a per-target cache flavor built WITHOUT CMake (the
|
|
603
|
-
* mbedTLS recipe): the qjs library target is just four TUs
|
|
604
|
-
* (QJS_ENGINE_SOURCES) compiled with `zig cc -target <triple>` under the
|
|
605
|
-
* same flags CMake's MinSizeRel/Debug+ASan configurations apply, packed
|
|
606
|
-
* with `zig ar`. Host builds keep the exact historical CMake recipe. */
|
|
607
|
-
async function ensureEngineArchive(sanitize, driver, buildIdentity, cacheRoot = vendorBuildCacheRoot()) {
|
|
608
|
-
const flavor = `${sanitize ? "asan" : "plain"}-${vendorCacheTargetFlavor(driver)}-${buildIdentity}`;
|
|
609
|
-
const vendor = vendorEngineDir();
|
|
610
|
-
const archive = engineArchivePath(sanitize, driver, buildIdentity, cacheRoot);
|
|
611
|
-
const cacheDir = dirname(archive);
|
|
612
|
-
if (await fileExists(archive))
|
|
613
|
-
return archive;
|
|
614
|
-
if (driver.target !== null)
|
|
615
|
-
return buildEngineArchiveCross(sanitize, driver, cacheRoot, cacheDir);
|
|
616
|
-
await mkdir(cacheRoot, { recursive: true });
|
|
617
|
-
const buildDir = await mkdtemp(join(cacheRoot, `build-${flavor}-`));
|
|
618
|
-
const stageDir = `${buildDir}.stage`;
|
|
619
|
-
try {
|
|
620
|
-
const configureArgs = [
|
|
621
|
-
"-S", vendor,
|
|
622
|
-
"-B", buildDir,
|
|
623
|
-
...(sanitize
|
|
624
|
-
? ["-DCMAKE_BUILD_TYPE=Debug", "-DQJS_ENABLE_ASAN=ON"]
|
|
625
|
-
: ["-DCMAKE_BUILD_TYPE=MinSizeRel"]),
|
|
626
|
-
"-DQJS_BUILD_EXAMPLES=OFF",
|
|
627
|
-
"-DQJS_ENABLE_INSTALL=OFF",
|
|
628
|
-
];
|
|
629
|
-
try {
|
|
630
|
-
await execFileAsync("cmake", configureArgs);
|
|
631
|
-
}
|
|
632
|
-
catch (err) {
|
|
633
|
-
if (err.code === "ENOENT") {
|
|
634
|
-
throw new Error("--dynamic builds the embedded engine once with CMake, which was not found on PATH.\n" +
|
|
635
|
-
"Install cmake (e.g. `brew install cmake`) and retry. Static builds do not need it.");
|
|
636
|
-
}
|
|
637
|
-
throw new Error(`cmake failed configuring the embedded engine (${vendor}).\n\n` +
|
|
638
|
-
subprocessFailureDetail(err));
|
|
639
|
-
}
|
|
640
|
-
await execFileAsync("cmake", [
|
|
641
|
-
"--build", buildDir,
|
|
642
|
-
"--target", "qjs",
|
|
643
|
-
`-j${availableParallelism()}`,
|
|
644
|
-
]);
|
|
645
|
-
// Publish only the archive, atomically: rename fails if a concurrent
|
|
646
|
-
// build won the race, and the winner's archive is just as good.
|
|
647
|
-
await mkdir(stageDir);
|
|
648
|
-
await copyFile(join(buildDir, "libqjs.a"), join(stageDir, "libqjs.a"));
|
|
649
|
-
await rename(stageDir, cacheDir).catch(() => undefined);
|
|
650
|
-
}
|
|
651
|
-
finally {
|
|
652
|
-
await rm(buildDir, { recursive: true, force: true });
|
|
653
|
-
await rm(stageDir, { recursive: true, force: true });
|
|
654
|
-
}
|
|
655
|
-
return archive;
|
|
656
|
-
}
|
|
657
|
-
/** The qjs library target's source list (CMakeLists.txt `qjs_sources` with
|
|
658
|
-
* QJS_BUILD_LIBC off — cutils is header-only): the cross-build recipe
|
|
659
|
-
* compiles exactly these. */
|
|
660
|
-
const QJS_ENGINE_SOURCES = ["dtoa.c", "libregexp.c", "libunicode.c", "quickjs.c"];
|
|
661
|
-
/** The engine archive for a CROSS target, per-TU `zig cc` compiles + `zig
|
|
662
|
-
* ar` (no CMake — the mbedTLS recipe). The flags mirror what the CMake
|
|
663
|
-
* configurations apply to the qjs library target: gnu11 (CMAKE_C_EXTENSIONS
|
|
664
|
-
* ON), hidden visibility, -funsigned-char, -DQUICKJS_NG_BUILD and
|
|
665
|
-
* -D_GNU_SOURCE (qjs_defines; linux targetArgs already carry the latter),
|
|
666
|
-
* then MinSizeRel (-Os -DNDEBUG) for plain or Debug+QJS_ENABLE_ASAN
|
|
667
|
-
* (-O0 -ggdb -fno-omit-frame-pointer -fsanitize=address
|
|
668
|
-
* -fno-sanitize-recover=all) for asan. Same atomic-rename publish as every
|
|
669
|
-
* vendor cache. */
|
|
670
|
-
async function buildEngineArchiveCross(sanitize, driver, cacheRoot, cacheDir) {
|
|
671
|
-
const vendor = vendorEngineDir();
|
|
672
|
-
const archive = join(cacheDir, "libqjs.a");
|
|
673
|
-
const arArgv = [...driver.argv.slice(0, 1), "ar"];
|
|
674
|
-
const cflags = [
|
|
675
|
-
"-std=gnu11",
|
|
676
|
-
...driver.targetArgs,
|
|
677
|
-
"-fvisibility=hidden",
|
|
678
|
-
"-funsigned-char",
|
|
679
|
-
"-DQUICKJS_NG_BUILD",
|
|
680
|
-
"-D_GNU_SOURCE",
|
|
681
|
-
// CMake's qjs_defines on WIN32 (quickjs.c's own _WIN32 arms cover the
|
|
682
|
-
// rest — timezoneapi, intrin, cutils.h's _msize usable-size probe).
|
|
683
|
-
...(targetPlatform(driver) === "win32" ? ["-DWIN32_LEAN_AND_MEAN", "-D_WIN32_WINNT=0x0601"] : []),
|
|
684
|
-
...(sanitize
|
|
685
|
-
? ["-O0", "-ggdb", "-fno-omit-frame-pointer", "-fsanitize=address", "-fno-sanitize-recover=all", "-DQJS_ENABLE_ASAN"]
|
|
686
|
-
: ["-Os", "-DNDEBUG"]),
|
|
687
|
-
"-I", vendor,
|
|
688
|
-
];
|
|
689
|
-
await mkdir(cacheRoot, { recursive: true });
|
|
690
|
-
const buildDir = await mkdtemp(join(cacheRoot, `build-qjs-${driver.target ?? "host"}-`));
|
|
691
|
-
try {
|
|
692
|
-
const width = Math.min(QJS_ENGINE_SOURCES.length, availableParallelism());
|
|
693
|
-
for (let i = 0; i < QJS_ENGINE_SOURCES.length; i += width) {
|
|
694
|
-
await Promise.all(QJS_ENGINE_SOURCES.slice(i, i + width).map((src) => execFileAsync(driver.argv[0] ?? "clang", [...driver.argv.slice(1), ...cflags, "-c", join(vendor, src), "-o", join(buildDir, `${basename(src, ".c")}.o`)], { cwd: buildDir })));
|
|
695
|
-
}
|
|
696
|
-
await execFileAsync(arArgv[0] ?? "ar", [...arArgv.slice(1), "rcs", join(buildDir, "libqjs.a"), ...QJS_ENGINE_SOURCES.map((s) => join(buildDir, `${basename(s, ".c")}.o`))]);
|
|
697
|
-
// Atomic publish: rename fails if a concurrent build won the race, and
|
|
698
|
-
// the winner's archive is just as good.
|
|
699
|
-
const stageDir = `${buildDir}.stage`;
|
|
700
|
-
await mkdir(stageDir);
|
|
701
|
-
await copyFile(join(buildDir, "libqjs.a"), join(stageDir, "libqjs.a"));
|
|
702
|
-
await rename(stageDir, cacheDir).catch(async () => {
|
|
703
|
-
await rm(stageDir, { recursive: true, force: true });
|
|
704
|
-
});
|
|
705
|
-
}
|
|
706
|
-
finally {
|
|
707
|
-
await rm(buildDir, { recursive: true, force: true });
|
|
708
|
-
}
|
|
709
|
-
return archive;
|
|
710
|
-
}
|
|
711
|
-
/** The vendor sources behind static-build regex support: quickjs-ng's
|
|
712
|
-
* libregexp and its unicode tables (cutils is header-only). Deliberately
|
|
713
|
-
* NOT the engine — a regex-using static binary links ~110KB of matcher,
|
|
714
|
-
* never the ~620KB island. */
|
|
715
|
-
const LRE_SOURCES = ["libregexp.c", "libunicode.c"];
|
|
716
|
-
function lreObjectPaths(sanitize, driver, buildIdentity, cacheRoot = vendorBuildCacheRoot()) {
|
|
717
|
-
const flavor = (sanitize ? "asan" : "plain") +
|
|
718
|
-
(driver.argv.length === 1 && driver.argv[0] === "clang" ? "" : "-zigcc") +
|
|
719
|
-
`-${vendorCacheTargetFlavor(driver)}-${buildIdentity}`;
|
|
720
|
-
const cacheDir = join(cacheRoot, `${QJS_COMMIT.slice(0, 12)}-lre-${flavor}`);
|
|
721
|
-
return LRE_SOURCES.map((f) => join(cacheDir, f.replace(/\.c$/, ".o")));
|
|
722
|
-
}
|
|
723
|
-
/** The libregexp objects for one flavor, compiled lazily on the first
|
|
724
|
-
* regex-using static build (~1s) and cached like the engine archive —
|
|
725
|
-
* vendor/.cache/<commit>-lre-<flavor>-<target>-<toolchain>/*.o — with the same atomic-rename
|
|
726
|
-
* publish (parallel first builds race safely; losers discard their work).
|
|
727
|
-
* Plain is -Os (size class matters: the regex fence pins regex-using
|
|
728
|
-
* binaries well under the engine class); asan matches the final link so
|
|
729
|
-
* the sanitized lane instruments the matcher too. Cross targets get their
|
|
730
|
-
* own flavor directory, compiled with the SCRIPTC_CC driver and its target
|
|
731
|
-
* args — libregexp/libunicode are plain C11, so the cross story is exactly
|
|
732
|
-
* the runtime sources' (no host-built inputs); vendored SOURCES are
|
|
733
|
-
* untouched, only the build wiring knows about targets. */
|
|
734
|
-
async function ensureLreObjects(sanitize, driver, buildIdentity, cacheRoot = vendorBuildCacheRoot()) {
|
|
735
|
-
// The flavor keys the DRIVER as well as the target: a zig-cc-built object
|
|
736
|
-
// set must never be handed to a clang link (or vice versa) off a shared
|
|
737
|
-
// cache directory. Native targets include host platform + architecture so
|
|
738
|
-
// arm64 and Rosetta processes never exchange Mach-O objects.
|
|
739
|
-
const flavor = (sanitize ? "asan" : "plain") +
|
|
740
|
-
(driver.argv.length === 1 && driver.argv[0] === "clang" ? "" : "-zigcc") +
|
|
741
|
-
`-${vendorCacheTargetFlavor(driver)}-${buildIdentity}`;
|
|
742
|
-
const vendor = vendorEngineDir();
|
|
743
|
-
const objects = lreObjectPaths(sanitize, driver, buildIdentity, cacheRoot);
|
|
744
|
-
const cacheDir = dirname(objects[0]);
|
|
745
|
-
if ((await Promise.all(objects.map(fileExists))).every(Boolean))
|
|
746
|
-
return objects;
|
|
747
|
-
await mkdir(cacheRoot, { recursive: true });
|
|
748
|
-
const buildDir = await mkdtemp(join(cacheRoot, `build-lre-${flavor}-`));
|
|
749
|
-
try {
|
|
750
|
-
// One -c per invocation: zig's COFF driver rejects multiple -c inputs
|
|
751
|
-
// in a single command ("coff does not support linking multiple
|
|
752
|
-
// objects"); per-file compiles produce the identical objects on every
|
|
753
|
-
// target.
|
|
754
|
-
for (const f of LRE_SOURCES) {
|
|
755
|
-
await execFileAsync(driver.argv[0] ?? "clang", [
|
|
756
|
-
...driver.argv.slice(1),
|
|
757
|
-
"-std=c11",
|
|
758
|
-
...driver.targetArgs,
|
|
759
|
-
...(sanitize ? ["-O1", "-fsanitize=address"] : ["-Os"]),
|
|
760
|
-
"-I", vendor,
|
|
761
|
-
"-c", join(vendor, f),
|
|
762
|
-
"-o", join(buildDir, f.replace(/\.c$/, ".o")),
|
|
763
|
-
], { cwd: buildDir });
|
|
764
|
-
}
|
|
765
|
-
// Atomic publish: rename fails if a concurrent build won the race, and
|
|
766
|
-
// the winner's objects are just as good.
|
|
767
|
-
await rename(buildDir, cacheDir).catch(() => undefined);
|
|
768
|
-
}
|
|
769
|
-
finally {
|
|
770
|
-
await rm(buildDir, { recursive: true, force: true });
|
|
771
|
-
}
|
|
772
|
-
return objects;
|
|
773
|
-
}
|
|
774
|
-
function vendorZlibDir() {
|
|
775
|
-
return join(runtimeSrcDir(), "..", "vendor", "zlib");
|
|
776
|
-
}
|
|
777
|
-
/** The vendored zlib TUs behind CROSS-target zlib support: every root *.c
|
|
778
|
-
* except the gzFile file-I/O units (gz*.c — nothing in scr_zlib.c
|
|
779
|
-
* references the gzFile API, and those TUs alone want unistd/io headers).
|
|
780
|
-
* Host builds never touch this list — they link the system libz. */
|
|
781
|
-
const ZLIB_SOURCES = ["adler32.c", "compress.c", "crc32.c", "deflate.c", "infback.c", "inffast.c", "inflate.c", "inftrees.c", "trees.c", "uncompr.c", "zutil.c"];
|
|
782
|
-
function zlibObjectPaths(sanitize, driver, buildIdentity, cacheRoot = vendorBuildCacheRoot()) {
|
|
783
|
-
const flavor = (sanitize ? "asan" : "plain") +
|
|
784
|
-
(driver.argv.length === 1 && driver.argv[0] === "clang" ? "" : "-zigcc") +
|
|
785
|
-
`-${vendorCacheTargetFlavor(driver)}-${buildIdentity}`;
|
|
786
|
-
const cacheDir = join(cacheRoot, `zlib-${ZLIB_VERSION}-${flavor}`);
|
|
787
|
-
return ZLIB_SOURCES.map((f) => join(cacheDir, f.replace(/\.c$/, ".o")));
|
|
788
|
-
}
|
|
789
|
-
/** The zlib objects for one flavor, compiled lazily on the first zlib-using
|
|
790
|
-
* CROSS build (~1s) and cached like the lre objects —
|
|
791
|
-
* vendor/.cache/zlib-<version>-<flavor>-<target>-<toolchain>/*.o — with the same atomic-rename
|
|
792
|
-
* publish (parallel first builds race safely; losers discard their work).
|
|
793
|
-
* Plain is -Os, asan matches the final link so the sanitized lane
|
|
794
|
-
* instruments the codec too. The flavor keys the driver and target exactly
|
|
795
|
-
* like the lre flavor: only cross builds call this today, but the keying
|
|
796
|
-
* must never hand a zig-built object set to a clang link off a shared
|
|
797
|
-
* directory. */
|
|
798
|
-
async function ensureZlibObjects(sanitize, driver, buildIdentity, cacheRoot = vendorBuildCacheRoot()) {
|
|
799
|
-
const flavor = (sanitize ? "asan" : "plain") +
|
|
800
|
-
(driver.argv.length === 1 && driver.argv[0] === "clang" ? "" : "-zigcc") +
|
|
801
|
-
`-${vendorCacheTargetFlavor(driver)}-${buildIdentity}`;
|
|
802
|
-
const vendor = vendorZlibDir();
|
|
803
|
-
const objects = zlibObjectPaths(sanitize, driver, buildIdentity, cacheRoot);
|
|
804
|
-
const cacheDir = dirname(objects[0]);
|
|
805
|
-
if ((await Promise.all(objects.map(fileExists))).every(Boolean))
|
|
806
|
-
return objects;
|
|
807
|
-
await mkdir(cacheRoot, { recursive: true });
|
|
808
|
-
const buildDir = await mkdtemp(join(cacheRoot, `build-zlib-${flavor}-`));
|
|
809
|
-
try {
|
|
810
|
-
// One -c per invocation, the lre recipe (zig's COFF driver rejects
|
|
811
|
-
// multiple -c inputs in a single command).
|
|
812
|
-
for (const f of ZLIB_SOURCES) {
|
|
813
|
-
await execFileAsync(driver.argv[0] ?? "clang", [
|
|
814
|
-
...driver.argv.slice(1),
|
|
815
|
-
"-std=c11",
|
|
816
|
-
...driver.targetArgs,
|
|
817
|
-
...(sanitize ? ["-O1", "-fsanitize=address"] : ["-Os"]),
|
|
818
|
-
"-I", vendor,
|
|
819
|
-
"-c", join(vendor, f),
|
|
820
|
-
"-o", join(buildDir, f.replace(/\.c$/, ".o")),
|
|
821
|
-
], { cwd: buildDir });
|
|
822
|
-
}
|
|
823
|
-
// Atomic publish: rename fails if a concurrent build won the race, and
|
|
824
|
-
// the winner's objects are just as good.
|
|
825
|
-
await rename(buildDir, cacheDir).catch(() => undefined);
|
|
826
|
-
}
|
|
827
|
-
finally {
|
|
828
|
-
await rm(buildDir, { recursive: true, force: true });
|
|
829
|
-
}
|
|
830
|
-
return objects;
|
|
831
|
-
}
|
|
832
|
-
function vendorCurlDir() {
|
|
833
|
-
return join(runtimeSrcDir(), "..", "vendor", "curl");
|
|
834
|
-
}
|
|
835
|
-
/** Every libcurl function scr_fetch_curl.c references — the generated import
|
|
836
|
-
* stub defines exactly these. A new curl call in scr_fetch_curl.c that is
|
|
837
|
-
* missing here fails the CROSS link immediately with the symbol's name
|
|
838
|
-
* (host builds resolve it from the real system libcurl and never look). */
|
|
839
|
-
const CURL_STUB_SYMBOLS = [
|
|
840
|
-
"curl_easy_cleanup", "curl_easy_getinfo", "curl_easy_init", "curl_easy_setopt", "curl_easy_strerror",
|
|
841
|
-
"curl_free", "curl_global_cleanup", "curl_global_init",
|
|
842
|
-
"curl_multi_add_handle", "curl_multi_cleanup", "curl_multi_info_read", "curl_multi_init",
|
|
843
|
-
"curl_multi_perform", "curl_multi_poll", "curl_multi_remove_handle",
|
|
844
|
-
"curl_slist_append", "curl_slist_free_all",
|
|
845
|
-
"curl_url", "curl_url_cleanup", "curl_url_get", "curl_url_set",
|
|
846
|
-
];
|
|
847
|
-
function curlStubDirPath(driver, buildIdentity, cacheRoot = vendorBuildCacheRoot()) {
|
|
848
|
-
return join(cacheRoot, `curl-stub-${driver.target}-${buildIdentity}`);
|
|
849
|
-
}
|
|
850
|
-
/** The libcurl import stub for one CROSS target, generated lazily on the
|
|
851
|
-
* first fetch-using cross build and cached like the zlib objects —
|
|
852
|
-
* vendor/.cache/curl-stub-<target>-<toolchain>/libcurl.so, atomic-rename publish. The
|
|
853
|
-
* stub is empty definitions of CURL_STUB_SYMBOLS compiled `-shared` with
|
|
854
|
-
* `-soname libcurl.so.4`: linking against it satisfies scr_fetch_curl.c's
|
|
855
|
-
* references and records a plain `DT_NEEDED libcurl.so.4`, which the
|
|
856
|
-
* TARGET system's real libcurl satisfies at load time (curl's unversioned
|
|
857
|
-
* exports make the classic import-stub technique sound here — nothing
|
|
858
|
-
* from the stub's bodies ever ships in the binary). One stub per target,
|
|
859
|
-
* no sanitize flavor: the stub's code is never executed or instrumented,
|
|
860
|
-
* and asan links against plain shared libraries freely. */
|
|
861
|
-
async function ensureCurlStub(driver, buildIdentity, cacheRoot = vendorBuildCacheRoot()) {
|
|
862
|
-
const cacheDir = curlStubDirPath(driver, buildIdentity, cacheRoot);
|
|
863
|
-
const lib = join(cacheDir, "libcurl.so");
|
|
864
|
-
if (await fileExists(lib))
|
|
865
|
-
return cacheDir;
|
|
866
|
-
await mkdir(cacheRoot, { recursive: true });
|
|
867
|
-
const buildDir = await mkdtemp(join(cacheRoot, `build-curl-stub-`));
|
|
868
|
-
try {
|
|
869
|
-
const src = join(buildDir, "stub.c");
|
|
870
|
-
await writeFile(src, CURL_STUB_SYMBOLS.map((s) => `void ${s}(void) {}\n`).join(""));
|
|
871
|
-
await execFileAsync(driver.argv[0] ?? "clang", [
|
|
872
|
-
...driver.argv.slice(1),
|
|
873
|
-
...driver.targetArgs,
|
|
874
|
-
"-shared", "-fPIC",
|
|
875
|
-
"-Wl,-soname,libcurl.so.4",
|
|
876
|
-
src,
|
|
877
|
-
"-o", join(buildDir, "libcurl.so"),
|
|
878
|
-
]);
|
|
879
|
-
// Atomic publish: rename fails if a concurrent build won the race, and
|
|
880
|
-
// the winner's stub is just as good.
|
|
881
|
-
const stageDir = `${buildDir}.stage`;
|
|
882
|
-
await mkdir(stageDir);
|
|
883
|
-
await copyFile(join(buildDir, "libcurl.so"), join(stageDir, "libcurl.so"));
|
|
884
|
-
await rename(stageDir, cacheDir).catch(async () => {
|
|
885
|
-
await rm(stageDir, { recursive: true, force: true });
|
|
886
|
-
});
|
|
887
|
-
}
|
|
888
|
-
finally {
|
|
889
|
-
await rm(buildDir, { recursive: true, force: true });
|
|
890
|
-
}
|
|
891
|
-
return cacheDir;
|
|
892
|
-
}
|
|
893
|
-
function vendorTlsDir() {
|
|
894
|
-
return join(runtimeSrcDir(), "..", "vendor", "mbedtls");
|
|
895
|
-
}
|
|
896
|
-
function tlsArchivePath(sanitize, driver, buildIdentity, cacheRoot = vendorBuildCacheRoot()) {
|
|
897
|
-
const flavor = `${sanitize ? "asan" : "plain"}-${vendorCacheTargetFlavor(driver)}-${buildIdentity}`;
|
|
898
|
-
return join(cacheRoot, `mbedtls-${MBEDTLS_VERSION}-${flavor}`, "libmbedtls.a");
|
|
899
|
-
}
|
|
900
|
-
/** The mbedTLS archive for one flavor, compiled lazily on the first
|
|
901
|
-
* TLS-using build (~15s over ~110 TUs, parallelized) and cached like the
|
|
902
|
-
* engine archive — vendor/.cache/mbedtls-<version>-<flavor>-<target>-<toolchain>/libmbedtls.a —
|
|
903
|
-
* with the same atomic-rename publish (parallel first builds race safely;
|
|
904
|
-
* losers discard their work). Plain is -Os (the TLS stack is link-gated
|
|
905
|
-
* but still wants its size class small); asan matches the final link so
|
|
906
|
-
* the sanitized lane instruments every TLS path too. No CMake: the stock
|
|
907
|
-
* config builds every library/*.c standalone with clang, so the recipe is
|
|
908
|
-
* per-TU `-c` compiles plus one `ar rcs` — vendored-build machinery the
|
|
909
|
-
* lre-objects cache already established.
|
|
910
|
-
*
|
|
911
|
-
* SCRIPTC_TARGET adds a per-target cache flavor (the lre-objects story):
|
|
912
|
-
* TUs compile with `zig cc -target <triple>` and the archive is packed
|
|
913
|
-
* with `zig ar` (llvm-ar — the host BSD ar has no business indexing ELF
|
|
914
|
-
* objects). Host builds keep the exact historical clang + ar recipe. */
|
|
915
|
-
async function ensureTlsArchive(sanitize, driver, buildIdentity, cacheRoot = vendorBuildCacheRoot()) {
|
|
916
|
-
const flavor = `${sanitize ? "asan" : "plain"}-${vendorCacheTargetFlavor(driver)}-${buildIdentity}`;
|
|
917
|
-
const compileArgv = driver.target !== null ? driver.argv : ["clang"];
|
|
918
|
-
const arArgv = driver.target !== null ? [...driver.argv.slice(0, 1), "ar"] : ["ar"];
|
|
919
|
-
const vendor = vendorTlsDir();
|
|
920
|
-
const archive = tlsArchivePath(sanitize, driver, buildIdentity, cacheRoot);
|
|
921
|
-
const cacheDir = dirname(archive);
|
|
922
|
-
if (await fileExists(archive))
|
|
923
|
-
return archive;
|
|
924
|
-
await mkdir(cacheRoot, { recursive: true });
|
|
925
|
-
const buildDir = await mkdtemp(join(cacheRoot, `build-mbedtls-${flavor}-`));
|
|
926
|
-
try {
|
|
927
|
-
const sources = (await readdir(join(vendor, "library"))).filter((n) => n.endsWith(".c")).sort();
|
|
928
|
-
const cflags = [
|
|
929
|
-
"-std=c11",
|
|
930
|
-
...driver.targetArgs,
|
|
931
|
-
...(sanitize ? ["-O1", "-fsanitize=address"] : ["-Os"]),
|
|
932
|
-
"-I", join(vendor, "include"),
|
|
933
|
-
"-I", join(vendor, "library"),
|
|
934
|
-
];
|
|
935
|
-
const width = availableParallelism();
|
|
936
|
-
for (let i = 0; i < sources.length; i += width) {
|
|
937
|
-
await Promise.all(sources.slice(i, i + width).map((src) => execFileAsync(compileArgv[0] ?? "clang", [...compileArgv.slice(1), ...cflags, "-c", join(vendor, "library", src), "-o", join(buildDir, `${basename(src, ".c")}.o`)])));
|
|
938
|
-
}
|
|
939
|
-
await execFileAsync(arArgv[0] ?? "ar", [...arArgv.slice(1), "rcs", join(buildDir, "libmbedtls.a"), ...sources.map((s) => join(buildDir, `${basename(s, ".c")}.o`))]);
|
|
940
|
-
// Atomic publish: rename fails if a concurrent build won the race, and
|
|
941
|
-
// the winner's archive is just as good.
|
|
942
|
-
const stageDir = `${buildDir}.stage`;
|
|
943
|
-
await mkdir(stageDir);
|
|
944
|
-
await copyFile(join(buildDir, "libmbedtls.a"), join(stageDir, "libmbedtls.a"));
|
|
945
|
-
await rename(stageDir, cacheDir).catch(async () => {
|
|
946
|
-
await rm(stageDir, { recursive: true, force: true });
|
|
947
|
-
});
|
|
948
|
-
}
|
|
949
|
-
finally {
|
|
950
|
-
await rm(buildDir, { recursive: true, force: true });
|
|
951
|
-
}
|
|
952
|
-
return archive;
|
|
953
|
-
}
|
|
954
584
|
/* ── library mode: the static-archive artifact ────────────────────────────
|
|
955
585
|
* One `scriptc build --lib` invocation produces <name>.lib.a: the program TU
|
|
956
586
|
* object plus exactly the runtime objects the program's IR gates in, every
|
|
@@ -958,6 +588,11 @@ async function ensureTlsArchive(sanitize, driver, buildIdentity, cacheRoot = ven
|
|
|
958
588
|
* objects apart from executable-lane objects). Persistent builds cache the
|
|
959
589
|
* completed archive by program-TU content and cache runtime objects separately,
|
|
960
590
|
* so an edit recompiles only the changed program object before re-archiving.
|
|
591
|
+
* Large LLVM dev TUs split further into stable symbol-hash shards: compile
|
|
592
|
+
* those in parallel, cache each object independently, then relocatably merge
|
|
593
|
+
* them back into the archive's one canonical program member. A localized edit
|
|
594
|
+
* recompiles only the changed buckets; exact repeats use the merged-object or
|
|
595
|
+
* completed-archive tiers and never repeat the merge.
|
|
961
596
|
* The base set narrows from the executable lane's unconditional sources:
|
|
962
597
|
* scr_async.c (fibers, timers, the loop) and scr_child.c drop — the
|
|
963
598
|
* async_free refusal already guarantees nothing references them — and
|
|
@@ -974,12 +609,98 @@ async function ensureTlsArchive(sanitize, driver, buildIdentity, cacheRoot = ven
|
|
|
974
609
|
/** The library base: the executable lane's unconditional sources minus the
|
|
975
610
|
* fiber/loop and child-process units, plus the library-mode TU. */
|
|
976
611
|
const LIB_RUNTIME_SOURCES = [
|
|
977
|
-
...
|
|
612
|
+
...EXECUTABLE_RUNTIME_SOURCES.filter((f) => f !== "scr_async.c" && f !== "scr_child.c" && f !== "scr_ffi.c"),
|
|
978
613
|
"scr_library.c",
|
|
979
614
|
];
|
|
615
|
+
function updateProgramShardCacheIdentity(hash, shards, publicSymbols, mergeIdentity) {
|
|
616
|
+
hash.update("\0program-shards\0");
|
|
617
|
+
if (shards === undefined) {
|
|
618
|
+
hash.update("<none>\0");
|
|
619
|
+
}
|
|
620
|
+
else {
|
|
621
|
+
hash.update("<present>\0");
|
|
622
|
+
for (const shard of shards) {
|
|
623
|
+
hash.update(shard.name).update("\0").update(shard.source).update("\0");
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
hash.update("\0program-public-symbols\0");
|
|
627
|
+
if (publicSymbols === undefined) {
|
|
628
|
+
hash.update("<none>\0");
|
|
629
|
+
}
|
|
630
|
+
else {
|
|
631
|
+
hash.update("<present>\0");
|
|
632
|
+
for (const symbol of publicSymbols)
|
|
633
|
+
hash.update(symbol).update("\0");
|
|
634
|
+
}
|
|
635
|
+
hash.update("\0program-shard-merge\0");
|
|
636
|
+
hash.update(mergeIdentity === undefined ? "<none>\0" : `<present>\0${mergeIdentity}\0`);
|
|
637
|
+
}
|
|
638
|
+
/** Identity of the machinery that turns LLVM shard objects back into the
|
|
639
|
+
* canonical program member, or null when the target cannot do so. Sharding is
|
|
640
|
+
* only a build optimization: a missing host tool or unsupported object class
|
|
641
|
+
* retains the ordinary single-TU compile. The identity joins every cache tier
|
|
642
|
+
* that contains merged bytes; raw shard-object keys deliberately omit it. */
|
|
643
|
+
async function resolveProgramShardMergeIdentity(driver) {
|
|
644
|
+
const platform = targetPlatform(driver);
|
|
645
|
+
// Mach-O merging uses the host's ld64. A Darwin target produced from a
|
|
646
|
+
// non-Darwin host can still compile as one canonical Zig TU, but the host's
|
|
647
|
+
// ELF/COFF linker cannot combine those objects.
|
|
648
|
+
if (platform === "darwin") {
|
|
649
|
+
if (process.platform !== "darwin")
|
|
650
|
+
return null;
|
|
651
|
+
const ld = await resolvedTool("ld");
|
|
652
|
+
return ld === null
|
|
653
|
+
? null
|
|
654
|
+
: rememberFingerprintDependencies(`program-shard-merge-darwin-v1\0${ld.cacheIdentity}`, [ld.canonicalPath]);
|
|
655
|
+
}
|
|
656
|
+
if (platform === "linux") {
|
|
657
|
+
if (driver.target === null) {
|
|
658
|
+
const [ld, objcopy] = await Promise.all([
|
|
659
|
+
resolvedTool("ld"),
|
|
660
|
+
resolvedTool("objcopy"),
|
|
661
|
+
]);
|
|
662
|
+
return ld === null || objcopy === null
|
|
663
|
+
? null
|
|
664
|
+
: rememberFingerprintDependencies(`program-shard-merge-linux-v1\0${ld.cacheIdentity}\0${objcopy.cacheIdentity}`, [ld.canonicalPath, objcopy.canonicalPath]);
|
|
665
|
+
}
|
|
666
|
+
const arch = driver.target.split("-", 1)[0];
|
|
667
|
+
if (arch !== "x86_64" && arch !== "aarch64")
|
|
668
|
+
return null;
|
|
669
|
+
const compiler = await resolvedTool(driver.argv[0] ?? "zig");
|
|
670
|
+
return compiler === null
|
|
671
|
+
? null
|
|
672
|
+
: rememberFingerprintDependencies(`program-shard-merge-cross-elf-v1\0${arch}\0${compiler.cacheIdentity}`, [compiler.canonicalPath]);
|
|
673
|
+
}
|
|
674
|
+
if (platform === "win32") {
|
|
675
|
+
const arch = driver.target?.split("-", 1)[0] ?? process.arch;
|
|
676
|
+
return arch === "x86_64" || arch === "x64"
|
|
677
|
+
? `program-shard-merge-coff-v1\0${arch}`
|
|
678
|
+
: null;
|
|
679
|
+
}
|
|
680
|
+
return null;
|
|
681
|
+
}
|
|
980
682
|
export async function compileLibArchive(opts) {
|
|
683
|
+
clearCcCaches();
|
|
981
684
|
const rtDir = runtimeSrcDir();
|
|
982
685
|
const driver = resolveCc();
|
|
686
|
+
const shardNames = new Set();
|
|
687
|
+
const programShardsValid = opts.programShards?.every((shard) => {
|
|
688
|
+
if (basename(shard.name) !== shard.name || !shard.name.endsWith(".ll") ||
|
|
689
|
+
shardNames.has(shard.name))
|
|
690
|
+
return false;
|
|
691
|
+
shardNames.add(shard.name);
|
|
692
|
+
return true;
|
|
693
|
+
}) === true;
|
|
694
|
+
const programShardsRequested = opts.cPath.endsWith(".ll") &&
|
|
695
|
+
programShardsValid && opts.programShards !== undefined && opts.programShards.length > 1 &&
|
|
696
|
+
opts.programPublicSymbols !== undefined
|
|
697
|
+
? opts.programShards
|
|
698
|
+
: null;
|
|
699
|
+
const programShardMergeIdentity = programShardsRequested === null
|
|
700
|
+
? null
|
|
701
|
+
: await resolveProgramShardMergeIdentity(driver);
|
|
702
|
+
const programShards = programShardMergeIdentity === null ? null : programShardsRequested;
|
|
703
|
+
const programPublicSymbols = programShards === null ? undefined : opts.programPublicSymbols;
|
|
983
704
|
const sanitize = opts.sanitize ?? false;
|
|
984
705
|
const optimization = opts.optimization ?? "release";
|
|
985
706
|
const regex = opts.regex ?? false;
|
|
@@ -1026,7 +747,7 @@ export async function compileLibArchive(opts) {
|
|
|
1026
747
|
? [...cflags, "-Wno-override-module"]
|
|
1027
748
|
: cflags;
|
|
1028
749
|
const programSourceExtension = opts.cPath.endsWith(".ll") ? ".ll" : ".c";
|
|
1029
|
-
const arArgv = driver
|
|
750
|
+
const arArgv = isZigDriver(driver) ? [driver.argv[0], "ar"] : ["ar"];
|
|
1030
751
|
const cachePolicy = toolchainEnvironmentCachePolicy();
|
|
1031
752
|
const configuredCacheRoot = cacheRootDir();
|
|
1032
753
|
const toolchainEnv = toolchainEnvironmentFingerprint();
|
|
@@ -1037,31 +758,35 @@ export async function compileLibArchive(opts) {
|
|
|
1037
758
|
// search variables cannot affect it, but any mutable compilation input or
|
|
1038
759
|
// opaque compiler wrapper makes every persistent tier unsafe to reuse. An
|
|
1039
760
|
// opaque archiver narrows only the completed-archive tier below.
|
|
1040
|
-
|
|
761
|
+
const cacheIdentity = opts.cacheIdentity;
|
|
762
|
+
let persistentCache = cacheIdentity === undefined || configuredCacheRoot === null || !persistentDriverCache
|
|
1041
763
|
? null
|
|
1042
|
-
: configuredCacheRoot;
|
|
1043
|
-
if (
|
|
764
|
+
: { root: configuredCacheRoot, identity: cacheIdentity };
|
|
765
|
+
if (persistentCache !== null) {
|
|
1044
766
|
try {
|
|
1045
|
-
await ensurePrivateCacheRoot(root, process.env["SCRIPTC_CACHE_DIR"] === undefined);
|
|
767
|
+
await ensurePrivateCacheRoot(persistentCache.root, process.env["SCRIPTC_CACHE_DIR"] === undefined);
|
|
1046
768
|
}
|
|
1047
769
|
catch {
|
|
1048
|
-
|
|
770
|
+
persistentCache = null;
|
|
1049
771
|
}
|
|
1050
772
|
}
|
|
1051
773
|
let implicitToolchain = null;
|
|
774
|
+
let implicitCompileToolchain = null;
|
|
1052
775
|
let runtimeCompilerInvocation = null;
|
|
1053
776
|
let programCompilerInvocation = null;
|
|
1054
777
|
if (persistentDriverCache) {
|
|
1055
778
|
try {
|
|
1056
|
-
|
|
779
|
+
const fingerprints = await implicitToolchainFingerprints(driver, toolchainEnv);
|
|
780
|
+
implicitToolchain = fingerprints.complete;
|
|
781
|
+
implicitCompileToolchain = fingerprints.compile;
|
|
1057
782
|
}
|
|
1058
783
|
catch {
|
|
1059
784
|
// An identity probe is cache machinery, never a reason a valid native
|
|
1060
785
|
// compile should fail. Disable every persistent tier for this invocation.
|
|
1061
|
-
|
|
786
|
+
persistentCache = null;
|
|
1062
787
|
}
|
|
1063
788
|
}
|
|
1064
|
-
if (
|
|
789
|
+
if (persistentCache !== null) {
|
|
1065
790
|
try {
|
|
1066
791
|
runtimeCompilerInvocation = await effectiveCompilerInvocationFingerprint(driver, toolchainEnv, cflags);
|
|
1067
792
|
programCompilerInvocation = programSourceExtension === ".ll"
|
|
@@ -1071,7 +796,7 @@ export async function compileLibArchive(opts) {
|
|
|
1071
796
|
catch {
|
|
1072
797
|
// A wrapper that cannot expose its effective invocation can still build,
|
|
1073
798
|
// but its outputs cannot safely participate in a persistent cache.
|
|
1074
|
-
|
|
799
|
+
persistentCache = null;
|
|
1075
800
|
}
|
|
1076
801
|
}
|
|
1077
802
|
const vendorBuildIdentity = await currentVendorCacheBuildIdentity(driver, `${toolchainEnv}\0${implicitToolchain ?? "<uncached>"}`);
|
|
@@ -1081,7 +806,7 @@ export async function compileLibArchive(opts) {
|
|
|
1081
806
|
// fingerprint. The runtime-object tier still serves them (localization
|
|
1082
807
|
// consumes the same per-flavor objects).
|
|
1083
808
|
const cacheCompleteArchive = opts.localizeSymbols === undefined &&
|
|
1084
|
-
|
|
809
|
+
persistentCache !== null && await archiverSupportsPersistentCache(arArgv, driver);
|
|
1085
810
|
let cachedArchive = null;
|
|
1086
811
|
let compilerVersion = "";
|
|
1087
812
|
let archiverVersion = "";
|
|
@@ -1093,10 +818,10 @@ export async function compileLibArchive(opts) {
|
|
|
1093
818
|
const identityBytes = opts.identityCSource === undefined
|
|
1094
819
|
? null
|
|
1095
820
|
: Buffer.from(opts.identityCSource, "utf8");
|
|
1096
|
-
if (
|
|
821
|
+
if (persistentCache !== null) {
|
|
1097
822
|
try {
|
|
1098
823
|
const [cv, fingerprint, programBytes] = await Promise.all([
|
|
1099
|
-
|
|
824
|
+
ccVersion(driver.argv, toolchainEnv, true),
|
|
1100
825
|
runtimeFingerprint(rtDir),
|
|
1101
826
|
cachedProgramBytes === null ? readFile(opts.cPath) : Promise.resolve(cachedProgramBytes),
|
|
1102
827
|
]);
|
|
@@ -1108,16 +833,18 @@ export async function compileLibArchive(opts) {
|
|
|
1108
833
|
const av = await toolVersionOnce(arArgv, toolchainEnv, true);
|
|
1109
834
|
archiverVersion = av;
|
|
1110
835
|
const key = createHash("sha256")
|
|
1111
|
-
//
|
|
1112
|
-
//
|
|
1113
|
-
|
|
836
|
+
// v10 adds the shard-merge implementation/tool identity. The
|
|
837
|
+
// canonical TU still keys source semantics; shard, keep, and merge
|
|
838
|
+
// bytes key the exact merged program object so ABI projections,
|
|
839
|
+
// tool replacements, and single-/multi-TU producers never collide.
|
|
840
|
+
.update("lib-v10\0")
|
|
1114
841
|
.update(cacheTargetIdentity(driver)).update("\0")
|
|
1115
842
|
.update(toolchainEnv).update("\0")
|
|
1116
843
|
.update(implicitToolchain).update("\0")
|
|
1117
844
|
.update(runtimeCompilerInvocation).update("\0")
|
|
1118
845
|
.update(programCompilerInvocation).update("\0")
|
|
1119
846
|
.update(programDependencyHash).update("\0")
|
|
1120
|
-
.update(
|
|
847
|
+
.update(persistentCache.identity).update("\0")
|
|
1121
848
|
.update(driver.argv.join("\x1f")).update("\0")
|
|
1122
849
|
.update(cv).update("\0")
|
|
1123
850
|
.update(fingerprint).update("\0")
|
|
@@ -1130,12 +857,14 @@ export async function compileLibArchive(opts) {
|
|
|
1130
857
|
// latter. Archive members also inherit the TU's basename.
|
|
1131
858
|
.update(opts.cPath).update("\0")
|
|
1132
859
|
.update(resolve(opts.cPath)).update("\0")
|
|
1133
|
-
.update(programBytes)
|
|
860
|
+
.update(programBytes);
|
|
861
|
+
updateProgramShardCacheIdentity(key, programShards ?? undefined, programPublicSymbols, programShardMergeIdentity ?? undefined);
|
|
862
|
+
const keyHex = key
|
|
1134
863
|
.update("\0identity\0")
|
|
1135
864
|
.update(identityBytes === null ? "<none>" : "<generated>").update("\0")
|
|
1136
865
|
.update(identityBytes ?? Buffer.alloc(0))
|
|
1137
866
|
.digest("hex");
|
|
1138
|
-
cachedArchive = join(root, "lib",
|
|
867
|
+
cachedArchive = join(persistentCache.root, "lib", keyHex);
|
|
1139
868
|
const tmpOut = privateSiblingPath(opts.outPath, "lib-hit");
|
|
1140
869
|
try {
|
|
1141
870
|
await mkdir(dirname(opts.outPath), { recursive: true });
|
|
@@ -1160,19 +889,19 @@ export async function compileLibArchive(opts) {
|
|
|
1160
889
|
cachedArchive = null;
|
|
1161
890
|
}
|
|
1162
891
|
}
|
|
1163
|
-
const transientVendorRoot =
|
|
892
|
+
const transientVendorRoot = persistentCache !== null && implicitToolchain !== null
|
|
1164
893
|
? null
|
|
1165
894
|
: join(tmpdir(), `scriptc-lib-vendor-${process.pid}-${Math.random().toString(36).slice(2)}`);
|
|
1166
|
-
const vendorCacheRoot = transientVendorRoot ?? vendorBuildCacheRoot();
|
|
895
|
+
const vendorCacheRoot = transientVendorRoot ?? vendorBuildCacheRoot(persistentCache?.root);
|
|
1167
896
|
try {
|
|
1168
|
-
const lreObjects = regex
|
|
1169
|
-
? await ensureLreObjects(sanitize, driver, vendorBuildIdentity, vendorCacheRoot)
|
|
1170
|
-
: [];
|
|
1171
|
-
const zlibObjects = opts.zlib
|
|
1172
|
-
? await ensureZlibObjects(sanitize, driver, vendorBuildIdentity, vendorCacheRoot)
|
|
1173
|
-
: [];
|
|
1174
897
|
const buildDir = await mkdtemp(join(tmpdir(), "scriptc-lib-"));
|
|
1175
898
|
try {
|
|
899
|
+
const lreObjects = regex
|
|
900
|
+
? await stageVendorInputs(() => ensureLreObjects(sanitize, driver, vendorBuildIdentity, vendorCacheRoot), join(buildDir, "vendor-lre"))
|
|
901
|
+
: [];
|
|
902
|
+
const zlibObjects = opts.zlib
|
|
903
|
+
? await stageVendorInputs(() => ensureZlibObjects(sanitize, driver, vendorBuildIdentity, vendorCacheRoot), join(buildDir, "vendor-zlib"))
|
|
904
|
+
: [];
|
|
1176
905
|
const compileOne = async (src, objName, compilerVisibleSource) => {
|
|
1177
906
|
const obj = join(buildDir, objName);
|
|
1178
907
|
const args = [
|
|
@@ -1203,18 +932,19 @@ export async function compileLibArchive(opts) {
|
|
|
1203
932
|
const programSource = cachedProgramBytes === null
|
|
1204
933
|
? opts.cPath
|
|
1205
934
|
: join(buildDir, `program${opts.cPath.endsWith(".ll") ? ".ll" : ".c"}`);
|
|
1206
|
-
if (cachedProgramBytes !== null)
|
|
935
|
+
if (cachedProgramBytes !== null && programShards === null) {
|
|
1207
936
|
await writeFile(programSource, cachedProgramBytes);
|
|
937
|
+
}
|
|
1208
938
|
let cachedProgramObject = null;
|
|
1209
|
-
if (
|
|
939
|
+
if (persistentCache !== null && cachedProgramBytes !== null && compilerVersion !== "" &&
|
|
1210
940
|
implicitToolchain !== null && programCompilerInvocation !== null) {
|
|
1211
941
|
const programKey = createHash("sha256")
|
|
1212
|
-
.update("lib-program-obj-
|
|
942
|
+
.update("lib-program-obj-v3\0")
|
|
1213
943
|
.update(cacheTargetIdentity(driver)).update("\0")
|
|
1214
944
|
.update(toolchainEnv).update("\0")
|
|
1215
945
|
.update(implicitToolchain).update("\0")
|
|
1216
946
|
.update(programCompilerInvocation).update("\0")
|
|
1217
|
-
.update(
|
|
947
|
+
.update(persistentCache.identity).update("\0")
|
|
1218
948
|
.update(driver.argv.join("\x1f")).update("\0")
|
|
1219
949
|
.update(compilerVersion).update("\0")
|
|
1220
950
|
.update(runtimeHash).update("\0")
|
|
@@ -1222,16 +952,120 @@ export async function compileLibArchive(opts) {
|
|
|
1222
952
|
.update(programCompilerArgs.join("\x1f")).update("\0")
|
|
1223
953
|
.update(opts.cPath).update("\0")
|
|
1224
954
|
.update(resolve(opts.cPath)).update("\0")
|
|
1225
|
-
.update(cachedProgramBytes)
|
|
1226
|
-
|
|
1227
|
-
|
|
955
|
+
.update(cachedProgramBytes);
|
|
956
|
+
updateProgramShardCacheIdentity(programKey, programShards ?? undefined, programPublicSymbols, programShardMergeIdentity ?? undefined);
|
|
957
|
+
const programKeyHex = programKey.digest("hex");
|
|
958
|
+
cachedProgramObject = join(persistentCache.root, "program-obj", programKeyHex);
|
|
1228
959
|
}
|
|
1229
960
|
const stagedProgramObject = join(buildDir, `${stem}.program.o`);
|
|
1230
961
|
let programObject;
|
|
962
|
+
let programShardFallback = false;
|
|
1231
963
|
if (cachedProgramObject !== null &&
|
|
1232
964
|
await copyValidCachedFile(cachedProgramObject, stagedProgramObject)) {
|
|
1233
965
|
programObject = stagedProgramObject;
|
|
1234
966
|
}
|
|
967
|
+
else if (programShards !== null) {
|
|
968
|
+
try {
|
|
969
|
+
const shardEntries = programShards.map((shard, index) => {
|
|
970
|
+
const sourcePath = join(buildDir, shard.name);
|
|
971
|
+
const staged = join(buildDir, `${stem}.program-${index.toString().padStart(3, "0")}.o`);
|
|
972
|
+
let cachePath = null;
|
|
973
|
+
if (persistentCache !== null && compilerVersion !== "" &&
|
|
974
|
+
implicitCompileToolchain !== null && programCompilerInvocation !== null) {
|
|
975
|
+
const key = createHash("sha256")
|
|
976
|
+
// v2 removes the broad implicit-toolchain fingerprint: it
|
|
977
|
+
// includes the linker selected by the driver, but raw shard
|
|
978
|
+
// objects are compile-only outputs. The compile-only toolchain
|
|
979
|
+
// identity retains compiler/config/header/assembler inputs;
|
|
980
|
+
// the effective program invocation pins this exact flag lane.
|
|
981
|
+
// Merge-tool identity belongs only to the merged-object and
|
|
982
|
+
// completed-archive tiers.
|
|
983
|
+
.update("lib-program-shard-v2\0")
|
|
984
|
+
.update(cacheTargetIdentity(driver)).update("\0")
|
|
985
|
+
.update(toolchainEnv).update("\0")
|
|
986
|
+
.update(implicitCompileToolchain).update("\0")
|
|
987
|
+
.update(programCompilerInvocation).update("\0")
|
|
988
|
+
.update(persistentCache.identity).update("\0")
|
|
989
|
+
.update(driver.argv.join("\x1f")).update("\0")
|
|
990
|
+
.update(compilerVersion).update("\0")
|
|
991
|
+
.update(runtimeHash).update("\0")
|
|
992
|
+
.update(programDependencyHash).update("\0")
|
|
993
|
+
.update(programCompilerArgs.join("\x1f")).update("\0")
|
|
994
|
+
.update(opts.cPath).update("\0")
|
|
995
|
+
.update(resolve(opts.cPath)).update("\0")
|
|
996
|
+
.update(shard.name).update("\0")
|
|
997
|
+
.update(shard.source)
|
|
998
|
+
.digest("hex");
|
|
999
|
+
cachePath = join(persistentCache.root, "program-shard", key);
|
|
1000
|
+
}
|
|
1001
|
+
return { ...shard, sourcePath, staged, cachePath, missed: false };
|
|
1002
|
+
});
|
|
1003
|
+
const shardWidth = Math.min(8, availableParallelism());
|
|
1004
|
+
for (let i = 0; i < shardEntries.length; i += shardWidth) {
|
|
1005
|
+
await Promise.all(shardEntries.slice(i, i + shardWidth).map(async (entry) => {
|
|
1006
|
+
await writeFile(entry.sourcePath, entry.source);
|
|
1007
|
+
if (entry.cachePath !== null &&
|
|
1008
|
+
await copyValidCachedFile(entry.cachePath, entry.staged))
|
|
1009
|
+
return;
|
|
1010
|
+
entry.missed = true;
|
|
1011
|
+
await compileOne(entry.sourcePath, basename(entry.staged), opts.cPath);
|
|
1012
|
+
}));
|
|
1013
|
+
}
|
|
1014
|
+
const publishable = shardEntries.filter((entry) => entry.missed && entry.cachePath !== null);
|
|
1015
|
+
const mergedProgramObject = await localizeLibraryObjects(driver, arArgv, buildDir, shardEntries.map((entry) => entry.staged), [], programPublicSymbols, `${stem}.program`);
|
|
1016
|
+
// Keep the canonical archive member spelling. The fact that native
|
|
1017
|
+
// compilation used shards is an implementation detail; consumers and
|
|
1018
|
+
// deterministic cache tests continue to see `<stem>.program.o`.
|
|
1019
|
+
await rename(mergedProgramObject, stagedProgramObject);
|
|
1020
|
+
programObject = stagedProgramObject;
|
|
1021
|
+
if (cachedProgramObject !== null || publishable.length > 0) {
|
|
1022
|
+
try {
|
|
1023
|
+
const [currentRuntime, currentInvocation, currentDependencies, currentCompiler] = await Promise.all([
|
|
1024
|
+
runtimeFingerprint(rtDir),
|
|
1025
|
+
effectiveCompilerInvocationFingerprint(driver, toolchainEnv, programCompilerArgs, programSourceExtension),
|
|
1026
|
+
translationUnitDependencyFingerprint(driver, cflags, opts.cPath, cachedProgramBytes, toolchainEnv),
|
|
1027
|
+
ccVersion(driver.argv, toolchainEnv, true),
|
|
1028
|
+
]);
|
|
1029
|
+
const shardInputsStillMatch = currentRuntime === runtimeHash &&
|
|
1030
|
+
currentInvocation === programCompilerInvocation &&
|
|
1031
|
+
currentDependencies === programDependencyHash && currentCompiler === compilerVersion;
|
|
1032
|
+
const currentFingerprints = shardInputsStillMatch
|
|
1033
|
+
? await implicitToolchainFingerprints(driver, toolchainEnv)
|
|
1034
|
+
: null;
|
|
1035
|
+
const compileInputsStillMatch = shardInputsStillMatch &&
|
|
1036
|
+
currentFingerprints?.compile === implicitCompileToolchain;
|
|
1037
|
+
let mergedInputsStillMatch = false;
|
|
1038
|
+
if (compileInputsStillMatch && cachedProgramObject !== null) {
|
|
1039
|
+
const currentMerge = await resolveProgramShardMergeIdentity(driver).catch(() => null);
|
|
1040
|
+
mergedInputsStillMatch =
|
|
1041
|
+
currentFingerprints?.complete === implicitToolchain &&
|
|
1042
|
+
currentMerge === programShardMergeIdentity;
|
|
1043
|
+
}
|
|
1044
|
+
if (compileInputsStillMatch) {
|
|
1045
|
+
await Promise.all([
|
|
1046
|
+
...(mergedInputsStillMatch
|
|
1047
|
+
? [publishCachedFile(programObject, cachedProgramObject)]
|
|
1048
|
+
: []),
|
|
1049
|
+
...publishable.map((entry) => publishCachedFile(entry.staged, entry.cachePath)),
|
|
1050
|
+
]);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
catch {
|
|
1054
|
+
// Best-effort: the merged program object is already valid.
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
catch {
|
|
1059
|
+
// Sharding is only an optimization. A present but incompatible or
|
|
1060
|
+
// failing merge tool (or a shard-only compiler failure) must not
|
|
1061
|
+
// turn a valid canonical LLVM TU into a failed library build. Do not
|
|
1062
|
+
// publish this retry under shard-derived object/archive cache keys.
|
|
1063
|
+
programShardFallback = true;
|
|
1064
|
+
if (cachedProgramBytes !== null)
|
|
1065
|
+
await writeFile(programSource, cachedProgramBytes);
|
|
1066
|
+
programObject = await compileOne(programSource, `${stem}.program.o`, cachedProgramBytes === null ? undefined : opts.cPath);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1235
1069
|
else {
|
|
1236
1070
|
programObject = await compileOne(programSource, `${stem}.program.o`, cachedProgramBytes === null ? undefined : opts.cPath);
|
|
1237
1071
|
if (cachedProgramObject !== null) {
|
|
@@ -1241,7 +1075,7 @@ export async function compileLibArchive(opts) {
|
|
|
1241
1075
|
implicitToolchainFingerprint(driver, toolchainEnv),
|
|
1242
1076
|
effectiveCompilerInvocationFingerprint(driver, toolchainEnv, programCompilerArgs, programSourceExtension),
|
|
1243
1077
|
translationUnitDependencyFingerprint(driver, cflags, opts.cPath, cachedProgramBytes, toolchainEnv),
|
|
1244
|
-
|
|
1078
|
+
ccVersion(driver.argv, toolchainEnv, true),
|
|
1245
1079
|
]);
|
|
1246
1080
|
if (currentRuntime === runtimeHash &&
|
|
1247
1081
|
currentImplicit === implicitToolchain &&
|
|
@@ -1274,10 +1108,10 @@ export async function compileLibArchive(opts) {
|
|
|
1274
1108
|
currentInvocation === runtimeCompilerInvocation, () => false);
|
|
1275
1109
|
return objectImplicitVerification;
|
|
1276
1110
|
};
|
|
1277
|
-
if (
|
|
1111
|
+
if (persistentCache !== null && compilerVersion !== "" && runtimeHash !== "") {
|
|
1278
1112
|
try {
|
|
1279
1113
|
const sourcePaths = sources.map((f) => join(rtDir, f));
|
|
1280
|
-
const cached = await ensureRuntimeObjects(root, driver.argv, cflags, sourcePaths, `lib-obj-v5\0${cacheTargetIdentity(driver)}\0${toolchainEnv}\0${implicitToolchain}\0${runtimeCompilerInvocation}\0${driver.argv.join(" ")}\0${compilerVersion}\0${runtimeHash}\0`, async () => (await runtimeFingerprint(rtDir)) === runtimeHash &&
|
|
1114
|
+
const cached = await ensureRuntimeObjects(persistentCache.root, driver.argv, cflags, sourcePaths, `lib-obj-v5\0${cacheTargetIdentity(driver)}\0${toolchainEnv}\0${implicitToolchain}\0${runtimeCompilerInvocation}\0${driver.argv.join(" ")}\0${compilerVersion}\0${runtimeHash}\0`, async () => (await runtimeFingerprint(rtDir)) === runtimeHash &&
|
|
1281
1115
|
(await objectImplicitToolchainStillMatches()));
|
|
1282
1116
|
const staged = await stageRuntimeObjects(cached, join(buildDir, "cached-runtime"));
|
|
1283
1117
|
runtimeObjects = sourcePaths.map((path) => staged.get(path));
|
|
@@ -1324,31 +1158,36 @@ export async function compileLibArchive(opts) {
|
|
|
1324
1158
|
await installArtifact(archiveOutput, opts.outPath);
|
|
1325
1159
|
let runtimeStillMatchesKey = false;
|
|
1326
1160
|
if (cachedArchive !== null &&
|
|
1327
|
-
|
|
1161
|
+
persistentCache !== null &&
|
|
1328
1162
|
runtimeHash !== "" &&
|
|
1329
1163
|
programDependencyHash !== "" &&
|
|
1330
1164
|
compilerVersion !== "" &&
|
|
1331
1165
|
archiverVersion !== "") {
|
|
1332
|
-
const [currentRuntime, currentImplicit, currentRuntimeInvocation, currentProgramInvocation, currentProgramDependencies, currentCompiler, currentArchiver] = await Promise.all([
|
|
1166
|
+
const [currentRuntime, currentImplicit, currentRuntimeInvocation, currentProgramInvocation, currentProgramDependencies, currentCompiler, currentArchiver, currentProgramShardMerge] = await Promise.all([
|
|
1333
1167
|
runtimeFingerprint(rtDir).catch(() => null),
|
|
1334
1168
|
implicitToolchainFingerprint(driver, toolchainEnv).catch(() => null),
|
|
1335
1169
|
effectiveCompilerInvocationFingerprint(driver, toolchainEnv, cflags).catch(() => null),
|
|
1336
1170
|
effectiveCompilerInvocationFingerprint(driver, toolchainEnv, programCompilerArgs, programSourceExtension).catch(() => null),
|
|
1337
1171
|
translationUnitDependencyFingerprint(driver, cflags, opts.cPath, cachedProgramBytes, toolchainEnv).catch(() => null),
|
|
1338
|
-
|
|
1172
|
+
ccVersion(driver.argv, toolchainEnv, true).catch(() => null),
|
|
1339
1173
|
toolVersionOnce(arArgv, toolchainEnv, true).catch(() => null),
|
|
1174
|
+
programShards === null
|
|
1175
|
+
? Promise.resolve(null)
|
|
1176
|
+
: resolveProgramShardMergeIdentity(driver).catch(() => null),
|
|
1340
1177
|
]);
|
|
1341
1178
|
runtimeStillMatchesKey =
|
|
1342
1179
|
cacheInputsStable &&
|
|
1180
|
+
!programShardFallback &&
|
|
1343
1181
|
currentRuntime === runtimeHash &&
|
|
1344
1182
|
currentImplicit === implicitToolchain &&
|
|
1345
1183
|
currentRuntimeInvocation === runtimeCompilerInvocation &&
|
|
1346
1184
|
currentProgramInvocation === programCompilerInvocation &&
|
|
1347
1185
|
currentProgramDependencies === programDependencyHash &&
|
|
1348
1186
|
currentCompiler === compilerVersion &&
|
|
1349
|
-
currentArchiver === archiverVersion
|
|
1187
|
+
currentArchiver === archiverVersion &&
|
|
1188
|
+
currentProgramShardMerge === programShardMergeIdentity;
|
|
1350
1189
|
}
|
|
1351
|
-
if (cachedArchive !== null &&
|
|
1190
|
+
if (cachedArchive !== null && persistentCache !== null && runtimeStillMatchesKey) {
|
|
1352
1191
|
try {
|
|
1353
1192
|
await publishCachedFile(archiveOutput, cachedArchive);
|
|
1354
1193
|
}
|
|
@@ -1366,8 +1205,9 @@ export async function compileLibArchive(opts) {
|
|
|
1366
1205
|
await rm(transientVendorRoot, { recursive: true, force: true }).catch(() => undefined);
|
|
1367
1206
|
}
|
|
1368
1207
|
}
|
|
1369
|
-
if (
|
|
1370
|
-
await pruneCache(root).catch(() => undefined);
|
|
1208
|
+
if (persistentCache !== null) {
|
|
1209
|
+
await pruneCache(persistentCache.root).catch(() => undefined);
|
|
1210
|
+
}
|
|
1371
1211
|
}
|
|
1372
1212
|
/* Multi-instance library mode's localization step: combine the program
|
|
1373
1213
|
* object with exactly the runtime/vendor members it reaches into ONE
|
|
@@ -1456,14 +1296,17 @@ async function localizeLibraryObjects(driver, arArgv, buildDir, rootObjects, sup
|
|
|
1456
1296
|
}
|
|
1457
1297
|
return combined;
|
|
1458
1298
|
}
|
|
1459
|
-
|
|
1299
|
+
const supportArgs = supportObjects.length === 0 ? [] : [staging];
|
|
1300
|
+
if (supportObjects.length > 0) {
|
|
1301
|
+
await run([arArgv[0] ?? "ar", ...arArgv.slice(1), "rcs", staging, ...supportObjects]);
|
|
1302
|
+
}
|
|
1460
1303
|
if (platform === "darwin") {
|
|
1461
1304
|
await writeFile(keepFile, keepSymbols.map((s) => `_${s}\n`).join(""));
|
|
1462
|
-
await run(["ld", "-r", ...rootObjects,
|
|
1305
|
+
await run(["ld", "-r", ...rootObjects, ...supportArgs, "-o", combined, "-exported_symbols_list", keepFile]);
|
|
1463
1306
|
}
|
|
1464
1307
|
else if (platform === "linux" && driver.target === null) {
|
|
1465
1308
|
await writeFile(keepFile, keepSymbols.map((s) => `${s}\n`).join(""));
|
|
1466
|
-
await run(["ld", "-r", "--force-group-allocation", ...rootObjects,
|
|
1309
|
+
await run(["ld", "-r", "--force-group-allocation", ...rootObjects, ...supportArgs, "-o", combined]);
|
|
1467
1310
|
await run(["objcopy", `--keep-global-symbols=${keepFile}`, combined]);
|
|
1468
1311
|
}
|
|
1469
1312
|
else if (platform === "linux") {
|
|
@@ -1478,7 +1321,7 @@ async function localizeLibraryObjects(driver, arArgv, buildDir, rootObjects, sup
|
|
|
1478
1321
|
...driver.argv.slice(1),
|
|
1479
1322
|
"-target", driver.zigTarget ?? driver.target,
|
|
1480
1323
|
"-nostdlib",
|
|
1481
|
-
"-r", ...rootObjects,
|
|
1324
|
+
"-r", ...rootObjects, ...supportArgs,
|
|
1482
1325
|
"-o", combined,
|
|
1483
1326
|
]);
|
|
1484
1327
|
try {
|
|
@@ -1489,7 +1332,7 @@ async function localizeLibraryObjects(driver, arArgv, buildDir, rootObjects, sup
|
|
|
1489
1332
|
}
|
|
1490
1333
|
}
|
|
1491
1334
|
else {
|
|
1492
|
-
throw new
|
|
1335
|
+
throw new InternalCompilerError(`runtime symbol localization (abi.localize_runtime) has no ${platform} arm; compileLibrary admits darwin, linux, and win32 builds only`);
|
|
1493
1336
|
}
|
|
1494
1337
|
return combined;
|
|
1495
1338
|
}
|
|
@@ -1497,7 +1340,7 @@ async function localizeLibraryObjects(driver, arArgv, buildDir, rootObjects, sup
|
|
|
1497
1340
|
* Content-addressed caches that let repeat builds of unchanged programs skip
|
|
1498
1341
|
* payload code generation/linking — the test lanes' dominant cost. Executable
|
|
1499
1342
|
* lookups still run lightweight dependency and link-input metadata probes.
|
|
1500
|
-
*
|
|
1343
|
+
* Principal keyspaces under the cache root:
|
|
1501
1344
|
*
|
|
1502
1345
|
* bin/<key> — whole program binaries. key = sha256(resolved clang
|
|
1503
1346
|
* identity/version, target + compiler/linker environment,
|
|
@@ -1527,6 +1370,14 @@ async function localizeLibraryObjects(driver, arArgv, buildDir, rootObjects, sup
|
|
|
1527
1370
|
* identity, TU path, and program-TU bytes.
|
|
1528
1371
|
* Checksum-verified hits skip native code generation and ar.
|
|
1529
1372
|
*
|
|
1373
|
+
* program-obj/<key> — the canonical library program object, after any LLVM
|
|
1374
|
+
* shard merge. Exact TU repeats reuse it directly.
|
|
1375
|
+
*
|
|
1376
|
+
* program-shard/<key> — stable LLVM dev-library buckets keyed by their IR
|
|
1377
|
+
* text and complete toolchain/TU identity. Local edits
|
|
1378
|
+
* retain every bucket whose definitions/declarations did
|
|
1379
|
+
* not change.
|
|
1380
|
+
*
|
|
1530
1381
|
* obj/<set>/<f>.o — per-flavor runtime objects for cache-miss builds. The
|
|
1531
1382
|
* historical single invocation recompiles every runtime
|
|
1532
1383
|
* TU per program (~1.3s at -O2); with cached objects a
|
|
@@ -1567,81 +1418,53 @@ async function localizeLibraryObjects(driver, arArgv, buildDir, rootObjects, sup
|
|
|
1567
1418
|
* oracle cache lives under the same root and is swept by the same pass. Cache
|
|
1568
1419
|
* trouble is never a build failure — every cache error falls back to a real
|
|
1569
1420
|
* compile. */
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
const
|
|
1579
|
-
if (
|
|
1580
|
-
return
|
|
1581
|
-
|
|
1582
|
-
const local = env["LOCALAPPDATA"];
|
|
1583
|
-
if (local !== undefined && local !== "")
|
|
1584
|
-
return resolve(local, "scriptc", "cache", "build");
|
|
1585
|
-
}
|
|
1586
|
-
return platform === "darwin"
|
|
1587
|
-
? resolve(userHome, "Library", "Caches", "scriptc", "build")
|
|
1588
|
-
: resolve(userHome, ".cache", "scriptc", "build");
|
|
1589
|
-
}
|
|
1590
|
-
/** Shared persistent-cache root for compiler-level tiers. The early library
|
|
1591
|
-
* cache deliberately follows the native cache's activation and hard-disable
|
|
1592
|
-
* contract, while native compilation retains ownership of toolchain safety. */
|
|
1593
|
-
export function buildCacheRoot() {
|
|
1594
|
-
return resolveBuildCacheRoot();
|
|
1595
|
-
}
|
|
1596
|
-
/** Harden/create a compiler-level cache root using the same privacy policy as
|
|
1597
|
-
* the artifact caches. Failure disables only the optional caller's tier. */
|
|
1598
|
-
export async function prepareBuildCacheRoot(root) {
|
|
1599
|
-
if (root === null)
|
|
1600
|
-
return null;
|
|
1421
|
+
const ccVersionMemos = new Map();
|
|
1422
|
+
/** Identity of the compiler implementation and configuration selected by a
|
|
1423
|
+
* fresh driver invocation. In particular, Darwin's stable /usr/bin/clang shim
|
|
1424
|
+
* exposes the currently selected Xcode clang only in its `-###` trace. The
|
|
1425
|
+
* normalized trace also covers output-affecting default driver configuration
|
|
1426
|
+
* that can change while argv[0] and PATH retain the same spelling. */
|
|
1427
|
+
async function effectiveCompilerEnvironmentIdentity(driver, env) {
|
|
1428
|
+
const compiler = driver.argv[0] ?? "clang";
|
|
1429
|
+
const resolvedDriver = await resolvedTool(compiler, env);
|
|
1430
|
+
if (resolvedDriver === null)
|
|
1431
|
+
return `<unresolved:${compiler}>`;
|
|
1432
|
+
const probeDir = await mkdtemp(join(tmpdir(), "scriptc-early-cc-probe-"));
|
|
1601
1433
|
try {
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
}
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
throw new Error("native cache root is not a directory");
|
|
1632
|
-
}
|
|
1633
|
-
if (process.platform !== "win32" &&
|
|
1634
|
-
existing !== null &&
|
|
1635
|
-
!hardenExisting &&
|
|
1636
|
-
(existing.mode & 0o077) !== 0) {
|
|
1637
|
-
throw new Error("existing native cache override is not private");
|
|
1434
|
+
const source = join(probeDir, "empty.c");
|
|
1435
|
+
const object = join(probeDir, "empty.o");
|
|
1436
|
+
await writeFile(source, "int scriptc_early_driver_probe;\n");
|
|
1437
|
+
const trace = await execFileAsync(compiler, [
|
|
1438
|
+
...driver.argv.slice(1),
|
|
1439
|
+
...driver.targetArgs,
|
|
1440
|
+
"-###",
|
|
1441
|
+
"-std=c11",
|
|
1442
|
+
"-c",
|
|
1443
|
+
source,
|
|
1444
|
+
"-o",
|
|
1445
|
+
object,
|
|
1446
|
+
], { cwd: probeDir, env, maxBuffer: 16 * 1024 * 1024 });
|
|
1447
|
+
const effectiveSpellings = [];
|
|
1448
|
+
for (const line of `${trace.stdout}\n${trace.stderr}`.split(/\r?\n/)) {
|
|
1449
|
+
const tokens = driverTraceCandidates(line);
|
|
1450
|
+
const cc1 = tokens.indexOf("-cc1");
|
|
1451
|
+
if (cc1 > 0)
|
|
1452
|
+
effectiveSpellings.push(tokens[cc1 - 1]);
|
|
1453
|
+
}
|
|
1454
|
+
const effective = effectiveSpellings.length === 1
|
|
1455
|
+
? await resolvedTool(effectiveSpellings[0], env)
|
|
1456
|
+
: null;
|
|
1457
|
+
return createHash("sha256")
|
|
1458
|
+
.update("effective-compiler-environment-v1\0")
|
|
1459
|
+
.update(resolvedDriver.cacheIdentity).update("\0")
|
|
1460
|
+
.update(normalizedProbeInvocation(trace, probeDir)).update("\0")
|
|
1461
|
+
.update(effective?.cacheIdentity ?? `<unresolved-effective:${effectiveSpellings.join("\x1f")}>`)
|
|
1462
|
+
.digest("hex");
|
|
1638
1463
|
}
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
await chmod(root, 0o700);
|
|
1464
|
+
finally {
|
|
1465
|
+
await rm(probeDir, { recursive: true, force: true }).catch(() => undefined);
|
|
1642
1466
|
}
|
|
1643
1467
|
}
|
|
1644
|
-
const ccVersionMemos = new Map();
|
|
1645
1468
|
/** Resolve the executable the OS will select for an argv[0] spelling. The
|
|
1646
1469
|
* path and inode metadata join the version output below: two PATH postures
|
|
1647
1470
|
* must not share cache entries merely because both drivers call themselves
|
|
@@ -1721,7 +1544,7 @@ function isAppleSystemClangHandoff(driver, effectiveCompiler) {
|
|
|
1721
1544
|
* can safely represent that behavior. Accept direct Clang/Zig drivers (plus
|
|
1722
1545
|
* Apple's system shim) and conservatively keep wrapper-driven builds on the
|
|
1723
1546
|
* uncached path. */
|
|
1724
|
-
async function compilerDriverSupportsPersistentCache(driver, environmentFingerprint) {
|
|
1547
|
+
export async function compilerDriverSupportsPersistentCache(driver, environmentFingerprint) {
|
|
1725
1548
|
// Cache-race tests exercise publication below an intentionally instrumented
|
|
1726
1549
|
// wrapper. This is deliberately undocumented and test-scoped.
|
|
1727
1550
|
if (process.env["SCRIPTC_TEST_TRUST_COMPILER_WRAPPER"] === "1")
|
|
@@ -1807,7 +1630,7 @@ async function archiverSupportsPersistentCache(arArgv, driver) {
|
|
|
1807
1630
|
return process.platform === "darwin" &&
|
|
1808
1631
|
/^\/Applications\/Xcode[^/]*\.app\/Contents\/Developer\/Toolchains\/[^/]+\.xctoolchain\/usr\/bin\/[^/]*ar$/.test(archiver.canonicalPath);
|
|
1809
1632
|
}
|
|
1810
|
-
export async function
|
|
1633
|
+
export async function ccVersion(argv, environmentFingerprint = toolchainEnvironmentFingerprint(), fresh = false) {
|
|
1811
1634
|
const spellingKey = `${environmentFingerprint}\0${argv.join("\x1f")}`;
|
|
1812
1635
|
const executableIdentity = await resolvedToolIdentity(argv[0] ?? "clang");
|
|
1813
1636
|
const key = `${spellingKey}\0${executableIdentity ?? "<unresolved>"}`;
|
|
@@ -2174,7 +1997,7 @@ function translationUnitDependencyFingerprint(driver, cflags, sourcePath, source
|
|
|
2174
1997
|
* change that redirects includes cannot hide behind an unchanged old path
|
|
2175
1998
|
* list. Dependency discovery must succeed on every cache-enabled invocation;
|
|
2176
1999
|
* a prior path list cannot reveal a new higher-priority header. */
|
|
2177
|
-
async function
|
|
2000
|
+
async function implicitToolchainFingerprintsFresh(driver, environmentFingerprint) {
|
|
2178
2001
|
let probe;
|
|
2179
2002
|
const compiler = driver.argv[0] ?? "clang";
|
|
2180
2003
|
const compilerIdentity = await resolvedToolIdentity(compiler);
|
|
@@ -2227,7 +2050,8 @@ async function implicitToolchainFingerprintFresh(driver, environmentFingerprint)
|
|
|
2227
2050
|
// executable/version and dependency set.
|
|
2228
2051
|
execFileAsync(compiler, [...prefix, "-std=c11", "-###", "-c", driverSource, "-o", driverOutput], { cwd: probeDir, maxBuffer: 16 * 1024 * 1024 }),
|
|
2229
2052
|
]);
|
|
2230
|
-
const
|
|
2053
|
+
const assemblerSpelling = assembler.stdout.trim();
|
|
2054
|
+
const toolSpellings = [linker.stdout.trim(), assemblerSpelling].filter((value, index, all) => value !== "" && all.indexOf(value) === index);
|
|
2231
2055
|
const sourceSet = new Set(sources);
|
|
2232
2056
|
const dependencyPaths = [
|
|
2233
2057
|
...new Set(dependencyResults
|
|
@@ -2248,44 +2072,56 @@ async function implicitToolchainFingerprintFresh(driver, environmentFingerprint)
|
|
|
2248
2072
|
path: resolved?.canonicalPath ?? null,
|
|
2249
2073
|
};
|
|
2250
2074
|
})),
|
|
2075
|
+
compileToolSpellings: assemblerSpelling === "" ? [] : [assemblerSpelling],
|
|
2251
2076
|
};
|
|
2252
2077
|
}
|
|
2253
2078
|
finally {
|
|
2254
2079
|
await rm(probeDir, { recursive: true, force: true }).catch(() => undefined);
|
|
2255
2080
|
}
|
|
2256
2081
|
}
|
|
2257
|
-
const
|
|
2258
|
-
|
|
2259
|
-
.
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
.update(probe.dependencies.join("\x1f"))
|
|
2270
|
-
.update("\0")
|
|
2271
|
-
.update(probe.dependencyFingerprint)
|
|
2272
|
-
.update("\0");
|
|
2273
|
-
for (const tool of probe.tools) {
|
|
2274
|
-
const currentIdentity = await resolvedToolIdentity(tool.spelling);
|
|
2275
|
-
hash
|
|
2276
|
-
.update(tool.spelling)
|
|
2082
|
+
const tools = await Promise.all(probe.tools.map(async (tool) => ({
|
|
2083
|
+
...tool,
|
|
2084
|
+
currentIdentity: await resolvedToolIdentity(tool.spelling),
|
|
2085
|
+
})));
|
|
2086
|
+
const fingerprint = (domain, selectedTools) => {
|
|
2087
|
+
const hash = createHash("sha256")
|
|
2088
|
+
.update(domain)
|
|
2089
|
+
.update(environmentFingerprint)
|
|
2090
|
+
.update("\0")
|
|
2091
|
+
.update(probe.compilerIdentity)
|
|
2092
|
+
.update("\0")
|
|
2093
|
+
.update(probe.compilerInvocation)
|
|
2277
2094
|
.update("\0")
|
|
2278
|
-
.update(
|
|
2095
|
+
.update(driver.argv.join("\x1f"))
|
|
2096
|
+
.update("\0")
|
|
2097
|
+
.update(driver.targetArgs.join("\x1f"))
|
|
2098
|
+
.update("\0")
|
|
2099
|
+
.update(probe.dependencies.join("\x1f"))
|
|
2100
|
+
.update("\0")
|
|
2101
|
+
.update(probe.dependencyFingerprint)
|
|
2279
2102
|
.update("\0");
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2103
|
+
for (const tool of selectedTools) {
|
|
2104
|
+
hash
|
|
2105
|
+
.update(tool.spelling)
|
|
2106
|
+
.update("\0")
|
|
2107
|
+
.update(tool.currentIdentity ?? tool.identity ?? "<unresolved>")
|
|
2108
|
+
.update("\0");
|
|
2109
|
+
}
|
|
2110
|
+
return rememberFingerprintDependencies(hash.digest("hex"), [
|
|
2111
|
+
...probe.dependencies,
|
|
2112
|
+
...probe.invocationPaths,
|
|
2113
|
+
...selectedTools.flatMap((tool) => tool.path === null ? [] : [tool.path]),
|
|
2114
|
+
], probe.dependencies, probe.dependencyFingerprint);
|
|
2115
|
+
};
|
|
2116
|
+
const compileSpellingSet = new Set(probe.compileToolSpellings);
|
|
2117
|
+
return {
|
|
2118
|
+
// Preserve the established complete fingerprint domain and byte stream.
|
|
2119
|
+
complete: fingerprint("implicit-toolchain-v2\0", tools),
|
|
2120
|
+
compile: fingerprint("implicit-compile-toolchain-v1\0", tools.filter((tool) => compileSpellingSet.has(tool.spelling))),
|
|
2121
|
+
};
|
|
2286
2122
|
}
|
|
2287
2123
|
const stableImplicitToolchainMemos = new Map();
|
|
2288
|
-
function
|
|
2124
|
+
function implicitToolchainFingerprints(driver, environmentFingerprint) {
|
|
2289
2125
|
const key = [
|
|
2290
2126
|
environmentFingerprint,
|
|
2291
2127
|
driver.argv.join("\x1f"),
|
|
@@ -2293,7 +2129,10 @@ function implicitToolchainFingerprint(driver, environmentFingerprint) {
|
|
|
2293
2129
|
driver.targetArgs.join("\x1f"),
|
|
2294
2130
|
runtimeSrcDir(),
|
|
2295
2131
|
].join("\0");
|
|
2296
|
-
return stableTestMemo(stableImplicitToolchainMemos, key, () =>
|
|
2132
|
+
return stableTestMemo(stableImplicitToolchainMemos, key, () => implicitToolchainFingerprintsFresh(driver, environmentFingerprint));
|
|
2133
|
+
}
|
|
2134
|
+
function implicitToolchainFingerprint(driver, environmentFingerprint) {
|
|
2135
|
+
return implicitToolchainFingerprints(driver, environmentFingerprint).then((fingerprints) => fingerprints.complete);
|
|
2297
2136
|
}
|
|
2298
2137
|
function linkTraceCandidate(line) {
|
|
2299
2138
|
const trimmed = line.trim().replace(/^(?:LOAD|load)\s+/, "");
|
|
@@ -2514,24 +2353,41 @@ function implicitLinkerFingerprint(driver, environmentFingerprint, linkArgs, eff
|
|
|
2514
2353
|
].join("\0");
|
|
2515
2354
|
return stableTestMemo(stableImplicitLinkerMemos, key, () => implicitLinkerFingerprintFresh(driver, environmentFingerprint, linkArgs, effectiveInvocationArgs, traceInvocationArgs));
|
|
2516
2355
|
}
|
|
2356
|
+
/** Resolve the exact files consumed by one compiler-driver link invocation.
|
|
2357
|
+
* Runtime packs reuse the native toolchain's strict dry-run plus real linker
|
|
2358
|
+
* trace so a PATH-selected Clang carries its own linker, SDK, compiler
|
|
2359
|
+
* runtime, and injected inputs into the executable cache proof. */
|
|
2360
|
+
export async function nativeLinkerDependencyPaths(linker, linkArgs) {
|
|
2361
|
+
const fingerprint = await implicitLinkerFingerprint({ argv: [linker], targetArgs: [], target: null }, toolchainEnvironmentFingerprint(), linkArgs);
|
|
2362
|
+
return fingerprintDependencyPaths(fingerprint);
|
|
2363
|
+
}
|
|
2517
2364
|
let ccacheMemo = null;
|
|
2365
|
+
/** Reset process observations whose validity is bounded to one public native
|
|
2366
|
+
* build. A long-lived caller may change PATH or install/remove ccache between
|
|
2367
|
+
* invocations; the next build must probe that environment again. */
|
|
2368
|
+
export function clearCcCaches() {
|
|
2369
|
+
ccacheMemo = null;
|
|
2370
|
+
}
|
|
2518
2371
|
function ccacheAvailable() {
|
|
2519
2372
|
ccacheMemo ??= execFileAsync("ccache", ["--version"]).then(() => true, () => false);
|
|
2520
2373
|
return ccacheMemo;
|
|
2521
2374
|
}
|
|
2522
|
-
/** Content hash of every
|
|
2523
|
-
*
|
|
2524
|
-
*
|
|
2525
|
-
*
|
|
2526
|
-
*
|
|
2527
|
-
*
|
|
2528
|
-
*
|
|
2529
|
-
* include without changing any previously selected dependency, while content
|
|
2530
|
-
* hashing catches same-size edits whose timestamp was preserved by a copy/sync
|
|
2531
|
-
* tool. */
|
|
2375
|
+
/** Content hash of every owned native source/header plus this backend's build
|
|
2376
|
+
* recipe implementation. It keys complete artifacts, runtime objects, and the
|
|
2377
|
+
* separately built vendor prerequisites, so two installed scriptc versions or
|
|
2378
|
+
* worktrees can share a user cache without exchanging outputs produced from
|
|
2379
|
+
* different vendored bytes or compile/archive recipes. Recursive enumeration
|
|
2380
|
+
* also catches a newly added nested header that begins shadowing a system
|
|
2381
|
+
* include, while content hashing catches same-size timestamp-preserving edits. */
|
|
2532
2382
|
async function runtimeFingerprintFresh(rtDir) {
|
|
2533
2383
|
const groups = await runtimeFingerprintInputGroups(rtDir);
|
|
2534
|
-
const h = createHash("sha256")
|
|
2384
|
+
const h = createHash("sha256")
|
|
2385
|
+
.update("native-owned-inputs-v2\0")
|
|
2386
|
+
.update(QJS_COMMIT).update(MBEDTLS_VERSION).update(ZLIB_VERSION)
|
|
2387
|
+
.update("\0backend-recipe\0");
|
|
2388
|
+
for (const path of NATIVE_RECIPE_IMPLEMENTATION_PATHS) {
|
|
2389
|
+
h.update(basename(path)).update("\0").update(await readFile(path)).update("\0");
|
|
2390
|
+
}
|
|
2535
2391
|
for (const group of groups) {
|
|
2536
2392
|
for (const n of group.names) {
|
|
2537
2393
|
h.update(group.label).update("/").update(n).update("\0").update(await readFile(join(group.dir, n))).update("\0");
|
|
@@ -2543,6 +2399,10 @@ async function runtimeFingerprintInputGroups(rtDir) {
|
|
|
2543
2399
|
return Promise.all([
|
|
2544
2400
|
{ label: "runtime", dir: rtDir },
|
|
2545
2401
|
{ label: "ryu", dir: join(rtDir, "..", "vendor", "ryu") },
|
|
2402
|
+
{ label: "quickjs-ng", dir: join(rtDir, "..", "vendor", "quickjs-ng") },
|
|
2403
|
+
{ label: "mbedtls", dir: join(rtDir, "..", "vendor", "mbedtls") },
|
|
2404
|
+
{ label: "zlib", dir: join(rtDir, "..", "vendor", "zlib") },
|
|
2405
|
+
{ label: "curl", dir: join(rtDir, "..", "vendor", "curl") },
|
|
2546
2406
|
].map(async (group) => {
|
|
2547
2407
|
const names = (await nativeSourceFiles(group.dir, true))
|
|
2548
2408
|
.map((path) => relative(group.dir, path))
|
|
@@ -2551,96 +2411,30 @@ async function runtimeFingerprintInputGroups(rtDir) {
|
|
|
2551
2411
|
}));
|
|
2552
2412
|
}
|
|
2553
2413
|
async function runtimeFingerprintInputPaths(rtDir) {
|
|
2554
|
-
return
|
|
2414
|
+
return [
|
|
2415
|
+
...(await runtimeFingerprintInputGroups(rtDir)).flatMap((group) => group.names.map((name) => join(group.dir, name))),
|
|
2416
|
+
...NATIVE_RECIPE_IMPLEMENTATION_PATHS,
|
|
2417
|
+
];
|
|
2555
2418
|
}
|
|
2556
2419
|
const stableRuntimeFingerprintMemos = new Map();
|
|
2557
2420
|
export function runtimeFingerprint(rtDir) {
|
|
2558
2421
|
const key = resolve(rtDir);
|
|
2559
2422
|
return stableTestMemo(stableRuntimeFingerprintMemos, key, () => runtimeFingerprintFresh(rtDir));
|
|
2560
2423
|
}
|
|
2424
|
+
const { vendorEngineDir, vendorTlsDir, vendorCurlDir, vendorBuildCacheRoot, vendorCacheTargetFlavor, vendorCacheBuildIdentity, currentVendorCacheBuildIdentity, engineArchivePath, stageVendorInputs, ensureEngineArchive, lreObjectPaths, ensureLreObjects, vendorZlibDir, zlibObjectPaths, ensureZlibObjects, curlStubDirPath, ensureCurlStub, tlsArchivePath, ensureTlsArchive, QJS_ENGINE_SOURCES, LRE_SOURCES, ZLIB_SOURCES, } = createVendorArchives({
|
|
2425
|
+
runtimeSrcDir,
|
|
2426
|
+
targetPlatform,
|
|
2427
|
+
isZigDriver,
|
|
2428
|
+
resolvedToolIdentity,
|
|
2429
|
+
runtimeFingerprint,
|
|
2430
|
+
});
|
|
2431
|
+
export { vendorCacheBuildIdentity, vendorCacheTargetFlavor };
|
|
2561
2432
|
class CacheInputsChangedError extends Error {
|
|
2562
2433
|
constructor() {
|
|
2563
2434
|
super("runtime inputs changed while populating the native object cache");
|
|
2564
2435
|
this.name = "CacheInputsChangedError";
|
|
2565
2436
|
}
|
|
2566
2437
|
}
|
|
2567
|
-
function cacheDigestPath(path) {
|
|
2568
|
-
return `${path}.sha256`;
|
|
2569
|
-
}
|
|
2570
|
-
async function fileDigest(path) {
|
|
2571
|
-
return createHash("sha256").update(await readFile(path)).digest("hex");
|
|
2572
|
-
}
|
|
2573
|
-
/** Cache entries are disposable data, not trusted compiler output. Atomic
|
|
2574
|
-
* rename prevents ordinary partial writes, while the adjacent digest catches
|
|
2575
|
-
* disk damage, manual truncation, and entries left by an interrupted/older
|
|
2576
|
-
* publisher before they reach clang, the linker, or `ar`. */
|
|
2577
|
-
async function validCachedFile(object) {
|
|
2578
|
-
try {
|
|
2579
|
-
const digestPath = cacheDigestPath(object);
|
|
2580
|
-
const expected = (await readFile(digestPath, "utf8")).trim();
|
|
2581
|
-
const valid = /^[0-9a-f]{64}$/.test(expected) && (await fileDigest(object)) === expected;
|
|
2582
|
-
if (valid) {
|
|
2583
|
-
const now = new Date();
|
|
2584
|
-
await Promise.all([
|
|
2585
|
-
utimes(object, now, now).catch(() => undefined),
|
|
2586
|
-
utimes(digestPath, now, now).catch(() => undefined),
|
|
2587
|
-
]);
|
|
2588
|
-
}
|
|
2589
|
-
return valid;
|
|
2590
|
-
}
|
|
2591
|
-
catch {
|
|
2592
|
-
return false;
|
|
2593
|
-
}
|
|
2594
|
-
}
|
|
2595
|
-
/** Copy a cache entry and verify the private copy before it can become a
|
|
2596
|
-
* caller-visible artifact. Verifying after the copy closes the gap between a
|
|
2597
|
-
* source-side digest check and a concurrent replacement/truncation. */
|
|
2598
|
-
async function copyValidCachedFile(source, destination) {
|
|
2599
|
-
try {
|
|
2600
|
-
const digestPath = cacheDigestPath(source);
|
|
2601
|
-
const expected = (await readFile(digestPath, "utf8")).trim();
|
|
2602
|
-
if (!/^[0-9a-f]{64}$/.test(expected))
|
|
2603
|
-
return false;
|
|
2604
|
-
await copyFile(source, destination);
|
|
2605
|
-
if ((await fileDigest(destination)) !== expected) {
|
|
2606
|
-
await rm(destination, { force: true }).catch(() => undefined);
|
|
2607
|
-
return false;
|
|
2608
|
-
}
|
|
2609
|
-
const now = new Date();
|
|
2610
|
-
await Promise.all([
|
|
2611
|
-
utimes(source, now, now).catch(() => undefined),
|
|
2612
|
-
utimes(digestPath, now, now).catch(() => undefined),
|
|
2613
|
-
]);
|
|
2614
|
-
return true;
|
|
2615
|
-
}
|
|
2616
|
-
catch {
|
|
2617
|
-
await rm(destination, { force: true }).catch(() => undefined);
|
|
2618
|
-
return false;
|
|
2619
|
-
}
|
|
2620
|
-
}
|
|
2621
|
-
/** Publish a complete executable/archive and its digest through private names.
|
|
2622
|
-
* Data is installed before its verifier, so a racing reader can only observe
|
|
2623
|
-
* an invalid/missing digest and take the fresh-build path. */
|
|
2624
|
-
async function publishCachedFile(source, destination) {
|
|
2625
|
-
const directory = dirname(destination);
|
|
2626
|
-
await mkdir(directory, { recursive: true });
|
|
2627
|
-
const nonce = `${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
2628
|
-
const tmp = join(directory, `.tmp-${basename(destination).slice(0, 8)}-${nonce}`);
|
|
2629
|
-
const tmpDigest = `${tmp}.sha256`;
|
|
2630
|
-
try {
|
|
2631
|
-
await copyFile(source, tmp);
|
|
2632
|
-
await chmod(tmp, 0o600);
|
|
2633
|
-
await writeFile(tmpDigest, `${await fileDigest(tmp)}\n`, { mode: 0o600 });
|
|
2634
|
-
await rename(tmp, destination);
|
|
2635
|
-
await rename(tmpDigest, cacheDigestPath(destination));
|
|
2636
|
-
}
|
|
2637
|
-
finally {
|
|
2638
|
-
await Promise.all([
|
|
2639
|
-
rm(tmp, { force: true }).catch(() => undefined),
|
|
2640
|
-
rm(tmpDigest, { force: true }).catch(() => undefined),
|
|
2641
|
-
]);
|
|
2642
|
-
}
|
|
2643
|
-
}
|
|
2644
2438
|
async function directoryTreeDigest(root, excludedPaths = []) {
|
|
2645
2439
|
const hash = createHash("sha256").update("native-dependency-tree-v1\0");
|
|
2646
2440
|
const visited = new Set();
|
|
@@ -2737,7 +2531,28 @@ async function snapshotLocalArtifactDependencies(dependencyPaths, recursiveDirec
|
|
|
2737
2531
|
const recursive = new Set(recursiveDirectories.map((path) => resolve(path)));
|
|
2738
2532
|
return Promise.all([...new Set(dependencyPaths)].sort().map((path) => snapshotLocalArtifactDependency(path, recursive.has(resolve(path)) ? recursiveExclusions : null)));
|
|
2739
2533
|
}
|
|
2740
|
-
|
|
2534
|
+
/** Capture exact filesystem identities for inputs produced outside the C
|
|
2535
|
+
* toolchain but consumed by its cache proofs. Callers carry this snapshot
|
|
2536
|
+
* forward so later stages can prove the same inputs remained installed. */
|
|
2537
|
+
export async function snapshotNativeArtifactDependencies(dependencyPaths) {
|
|
2538
|
+
return snapshotLocalArtifactDependencies(dependencyPaths);
|
|
2539
|
+
}
|
|
2540
|
+
export async function nativeArtifactDependenciesStillMatch(dependencies) {
|
|
2541
|
+
if (!dependencies.every((dependency) => dependency !== null && typeof dependency === "object" &&
|
|
2542
|
+
typeof dependency.path === "string" &&
|
|
2543
|
+
(dependency.kind === "file" || dependency.kind === "directory" || dependency.kind === "symlink") &&
|
|
2544
|
+
typeof dependency.dev === "number" && typeof dependency.ino === "number" &&
|
|
2545
|
+
typeof dependency.size === "number" && typeof dependency.mtimeMs === "number" &&
|
|
2546
|
+
typeof dependency.ctimeMs === "number" &&
|
|
2547
|
+
(dependency.treeDigest === undefined || typeof dependency.treeDigest === "string") &&
|
|
2548
|
+
(dependency.treeExclusions === undefined || (Array.isArray(dependency.treeExclusions) &&
|
|
2549
|
+
dependency.treeExclusions.every((path) => typeof path === "string"))) &&
|
|
2550
|
+
(dependency.kind !== "symlink" || (typeof dependency.targetPath === "string" &&
|
|
2551
|
+
(dependency.targetKind === "file" || dependency.targetKind === "directory") &&
|
|
2552
|
+
typeof dependency.targetDev === "number" && typeof dependency.targetIno === "number" &&
|
|
2553
|
+
typeof dependency.targetSize === "number" && typeof dependency.targetMtimeMs === "number" &&
|
|
2554
|
+
typeof dependency.targetCtimeMs === "number"))))
|
|
2555
|
+
return false;
|
|
2741
2556
|
return (await Promise.all(dependencies.map(async (dependency) => {
|
|
2742
2557
|
const current = await snapshotLocalArtifactDependency(dependency.path, dependency.treeDigest === undefined ? null : dependency.treeExclusions ?? []).catch(() => null);
|
|
2743
2558
|
return current !== null && JSON.stringify(current) === JSON.stringify(dependency);
|
|
@@ -2767,7 +2582,7 @@ async function readNativeMetadataStamp(root, key) {
|
|
|
2767
2582
|
values: stamp.values,
|
|
2768
2583
|
dependencies: stamp.dependencies,
|
|
2769
2584
|
}) !== stamp.integrity ||
|
|
2770
|
-
!(await
|
|
2585
|
+
!(await nativeArtifactDependenciesStillMatch(stamp.dependencies))) {
|
|
2771
2586
|
return null;
|
|
2772
2587
|
}
|
|
2773
2588
|
return stamp;
|
|
@@ -2813,12 +2628,19 @@ function nativeMetadataKey(kind, parts) {
|
|
|
2813
2628
|
* The generated TU bytes, every scriptc runtime source, the selected direct
|
|
2814
2629
|
* compiler inode, target/options/environment, and the output path all join the
|
|
2815
2630
|
* key. A digest rejects a modified/truncated output before the no-op hit. */
|
|
2816
|
-
function localArtifactIdentity(opts, driver, environmentFingerprint, compilerIdentity, runtimeHash, programBytes) {
|
|
2631
|
+
function localArtifactIdentity(opts, driver, environmentFingerprint, compilerIdentity, runtimeHash, programBytes, programShardMerge) {
|
|
2817
2632
|
const normalizedOptions = Object.fromEntries(Object.entries(opts)
|
|
2818
|
-
.
|
|
2633
|
+
// Shard source can be tens of megabytes. Hash it incrementally below
|
|
2634
|
+
// instead of materializing a second giant JSON string solely for this
|
|
2635
|
+
// output-local fast-path identity.
|
|
2636
|
+
.filter(([key, value]) => value !== undefined && key !== "programShards" && key !== "programPublicSymbols")
|
|
2819
2637
|
.sort(([a], [b]) => a.localeCompare(b)));
|
|
2820
|
-
|
|
2821
|
-
|
|
2638
|
+
const executableSectionFlags = executableSectionEliminationFlags(targetPlatform(driver));
|
|
2639
|
+
const hash = createHash("sha256")
|
|
2640
|
+
// v2 adds the executable section-GC recipe. A v1 output-local stamp can
|
|
2641
|
+
// name a same-source binary from before unused runtime sections were
|
|
2642
|
+
// eliminated, so it must never short-circuit the new linker invocation.
|
|
2643
|
+
.update("local-artifact-v2\0")
|
|
2822
2644
|
.update(cacheTargetIdentity(driver)).update("\0")
|
|
2823
2645
|
.update(environmentFingerprint).update("\0")
|
|
2824
2646
|
.update(compilerIdentity).update("\0")
|
|
@@ -2826,6 +2648,12 @@ function localArtifactIdentity(opts, driver, environmentFingerprint, compilerIde
|
|
|
2826
2648
|
.update(driver.argv.join("\x1f")).update("\0")
|
|
2827
2649
|
.update(driver.targetArgs.join("\x1f")).update("\0")
|
|
2828
2650
|
.update(driver.linkArgs.join("\x1f")).update("\0")
|
|
2651
|
+
.update(executableSectionFlags.compile.join("\x1f")).update("\0")
|
|
2652
|
+
.update(executableSectionFlags.link.join("\x1f")).update("\0");
|
|
2653
|
+
if (programShardMerge !== null) {
|
|
2654
|
+
updateProgramShardCacheIdentity(hash, opts.programShards, opts.programPublicSymbols, programShardMerge);
|
|
2655
|
+
}
|
|
2656
|
+
return hash
|
|
2829
2657
|
.update(process.env["SCRIPTC_FETCH_CURL"] === "1" ? "fetch-curl" : "fetch-native").update("\0")
|
|
2830
2658
|
.update(JSON.stringify(normalizedOptions)).update("\0")
|
|
2831
2659
|
.update(resolve(opts.cPath)).update("\0")
|
|
@@ -2880,16 +2708,16 @@ async function localArtifactHit(stampPath, outPath, key) {
|
|
|
2880
2708
|
typeof dependency.targetSize !== "number" ||
|
|
2881
2709
|
typeof dependency.targetMtimeMs !== "number" ||
|
|
2882
2710
|
typeof dependency.targetCtimeMs !== "number")) ||
|
|
2883
|
-
!(await
|
|
2711
|
+
!(await nativeArtifactDependenciesStillMatch(stamp.dependencies)) ||
|
|
2884
2712
|
await fileDigest(outPath) !== stamp.digest) {
|
|
2885
|
-
return
|
|
2713
|
+
return null;
|
|
2886
2714
|
}
|
|
2887
2715
|
const now = new Date();
|
|
2888
2716
|
await utimes(stampPath, now, now).catch(() => undefined);
|
|
2889
|
-
return
|
|
2717
|
+
return stamp;
|
|
2890
2718
|
}
|
|
2891
2719
|
catch {
|
|
2892
|
-
return
|
|
2720
|
+
return null;
|
|
2893
2721
|
}
|
|
2894
2722
|
}
|
|
2895
2723
|
async function publishLocalArtifactStamp(stampPath, outPath, key, dependencyPaths, recursiveDirectories = [], recursiveExclusions = []) {
|
|
@@ -2909,6 +2737,7 @@ async function publishLocalArtifactStamp(stampPath, outPath, key, dependencyPath
|
|
|
2909
2737
|
};
|
|
2910
2738
|
await writeFile(tmp, `${JSON.stringify(stamp)}\n`, { mode: 0o600 });
|
|
2911
2739
|
await rename(tmp, stampPath);
|
|
2740
|
+
return stamp;
|
|
2912
2741
|
}
|
|
2913
2742
|
finally {
|
|
2914
2743
|
await rm(tmp, { force: true }).catch(() => undefined);
|
|
@@ -2919,7 +2748,7 @@ async function publishLocalArtifactStamp(stampPath, outPath, key, dependencyPath
|
|
|
2919
2748
|
* per-file atomic renames make every winner equivalent. Publication is held
|
|
2920
2749
|
* until verifyInputs confirms that the source/header fingerprint used by the
|
|
2921
2750
|
* key still describes the bytes clang just read. */
|
|
2922
|
-
async function ensureRuntimeObjects(root, ccArgv, cflags, sources, keyPrefix, verifyInputs) {
|
|
2751
|
+
async function ensureRuntimeObjects(root, ccArgv, cflags, sources, keyPrefix, verifyInputs, protectedPaths) {
|
|
2923
2752
|
const setKey = createHash("sha256")
|
|
2924
2753
|
.update(keyPrefix)
|
|
2925
2754
|
.update(cflags.join("\x1f"))
|
|
@@ -2927,6 +2756,13 @@ async function ensureRuntimeObjects(root, ccArgv, cflags, sources, keyPrefix, ve
|
|
|
2927
2756
|
.slice(0, 24);
|
|
2928
2757
|
const objDir = join(root, "obj", setKey);
|
|
2929
2758
|
const objOf = (src) => join(objDir, `${basename(src, ".c")}.o`);
|
|
2759
|
+
if (protectedPaths !== undefined) {
|
|
2760
|
+
for (const source of sources) {
|
|
2761
|
+
const object = objOf(source);
|
|
2762
|
+
protectedPaths.add(object);
|
|
2763
|
+
protectedPaths.add(cacheDigestPath(object));
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2930
2766
|
const present = await Promise.all(sources.map((s) => validCachedFile(objOf(s))));
|
|
2931
2767
|
const missing = sources.filter((_, i) => !present[i]);
|
|
2932
2768
|
if (missing.length > 0) {
|
|
@@ -2937,7 +2773,7 @@ async function ensureRuntimeObjects(root, ccArgv, cflags, sources, keyPrefix, ve
|
|
|
2937
2773
|
ccArgv[0] === "clang" &&
|
|
2938
2774
|
(await ccacheAvailable());
|
|
2939
2775
|
await mkdir(objDir, { recursive: true });
|
|
2940
|
-
const tmpDir = await mkdtemp(join(
|
|
2776
|
+
const tmpDir = await mkdtemp(join(tmpdir(), "scriptc-cache-obj-"));
|
|
2941
2777
|
try {
|
|
2942
2778
|
const compiled = new Map();
|
|
2943
2779
|
// Modest parallelism: a flavor's objects build once, but several cold
|
|
@@ -2963,7 +2799,7 @@ async function ensureRuntimeObjects(root, ccArgv, cflags, sources, keyPrefix, ve
|
|
|
2963
2799
|
},
|
|
2964
2800
|
}
|
|
2965
2801
|
: undefined);
|
|
2966
|
-
compiled.set(src,
|
|
2802
|
+
compiled.set(src, tmpObj);
|
|
2967
2803
|
}));
|
|
2968
2804
|
}
|
|
2969
2805
|
// The fingerprint was computed before these subprocesses started. Do
|
|
@@ -2973,12 +2809,7 @@ async function ensureRuntimeObjects(root, ccArgv, cflags, sources, keyPrefix, ve
|
|
|
2973
2809
|
throw new CacheInputsChangedError();
|
|
2974
2810
|
for (const [src, built] of compiled) {
|
|
2975
2811
|
const destination = objOf(src);
|
|
2976
|
-
|
|
2977
|
-
await writeFile(tmpDigest, `${built.digest}\n`);
|
|
2978
|
-
// Publish data first and its verifier second. A concurrent reader in
|
|
2979
|
-
// the tiny gap sees a missing/mismatched digest and falls back safely.
|
|
2980
|
-
await rename(built.object, destination);
|
|
2981
|
-
await rename(tmpDigest, cacheDigestPath(destination));
|
|
2812
|
+
await publishCachedFile(built, destination);
|
|
2982
2813
|
}
|
|
2983
2814
|
}
|
|
2984
2815
|
finally {
|
|
@@ -3017,71 +2848,24 @@ export async function stageRuntimeObjects(objects, stageDir) {
|
|
|
3017
2848
|
}));
|
|
3018
2849
|
return new Map(staged);
|
|
3019
2850
|
}
|
|
3020
|
-
/** Size-capped LRU sweep of the whole cache root. A caller-configured cap is
|
|
3021
|
-
* enforced after every successful cache write. The 4 GiB default is checked on
|
|
3022
|
-
* the first and every 64th write in a long-lived process: a full tree walk per
|
|
3023
|
-
* corpus program would otherwise become quadratic as the cache grows. Oldest-
|
|
3024
|
-
* mtime files go first until the tree is back under 75% of the cap; reads bump
|
|
3025
|
-
* mtimes. Active links use private staged names/hard links, so cache names can
|
|
3026
|
-
* be unlinked safely. */
|
|
3027
|
-
const rootWriteCounts = new Map();
|
|
3028
|
-
async function pruneCache(root) {
|
|
3029
|
-
const configuredCap = process.env["SCRIPTC_CACHE_MAX_MB"];
|
|
3030
|
-
const writes = (rootWriteCounts.get(root) ?? 0) + 1;
|
|
3031
|
-
rootWriteCounts.set(root, writes);
|
|
3032
|
-
if (configuredCap === undefined && writes !== 1 && writes % 64 !== 0)
|
|
3033
|
-
return;
|
|
3034
|
-
const capBytes = Number(configuredCap ?? "4096") * 1024 * 1024;
|
|
3035
|
-
if (!Number.isFinite(capBytes) || capBytes <= 0)
|
|
3036
|
-
return;
|
|
3037
|
-
const files = [];
|
|
3038
|
-
const walk = async (dir) => {
|
|
3039
|
-
const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
3040
|
-
for (const ent of entries) {
|
|
3041
|
-
const p = join(dir, ent.name);
|
|
3042
|
-
if (ent.isDirectory())
|
|
3043
|
-
await walk(p);
|
|
3044
|
-
else if (ent.isFile()) {
|
|
3045
|
-
const s = await stat(p).catch(() => null);
|
|
3046
|
-
if (s !== null)
|
|
3047
|
-
files.push({ path: p, size: s.size, mtimeMs: s.mtimeMs });
|
|
3048
|
-
}
|
|
3049
|
-
}
|
|
3050
|
-
};
|
|
3051
|
-
await walk(root);
|
|
3052
|
-
let total = files.reduce((n, f) => n + f.size, 0);
|
|
3053
|
-
if (total <= capBytes)
|
|
3054
|
-
return;
|
|
3055
|
-
files.sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
3056
|
-
for (const f of files) {
|
|
3057
|
-
if (total <= capBytes * 0.75)
|
|
3058
|
-
break;
|
|
3059
|
-
try {
|
|
3060
|
-
await unlink(f.path);
|
|
3061
|
-
total -= f.size;
|
|
3062
|
-
}
|
|
3063
|
-
catch {
|
|
3064
|
-
// A concurrent reader/publisher may already have moved the name.
|
|
3065
|
-
}
|
|
3066
|
-
}
|
|
3067
|
-
}
|
|
3068
2851
|
/** Compiles one C program together with the runtime sources.
|
|
3069
2852
|
* With caching disabled, the runtime (a dozen small files) is recompiled on
|
|
3070
|
-
* every build in one
|
|
2853
|
+
* every build in one clang invocation — no cached-archive
|
|
3071
2854
|
* staleness bugs. --dynamic additionally compiles
|
|
3072
2855
|
* scr_island.c under SCR_DYNAMIC and links the cached engine archive (built
|
|
3073
2856
|
* lazily, see above); regex-using programs additionally compile scr_regex.c
|
|
3074
2857
|
* and link libregexp (the cached objects, or the archive's own copy under
|
|
3075
|
-
* --dynamic).
|
|
3076
|
-
*
|
|
2858
|
+
* --dynamic). Executable links use the target's section-elimination recipe
|
|
2859
|
+
* independently of those feature gates.
|
|
3077
2860
|
*
|
|
3078
2861
|
* With a caller-supplied dependency identity and an enabled cache root,
|
|
3079
2862
|
* unchanged programs skip payload code generation/linking via the binary
|
|
3080
2863
|
* cache after lightweight metadata probes, and misses link the program's own
|
|
3081
2864
|
* TU against cached per-flavor runtime objects. */
|
|
3082
|
-
|
|
2865
|
+
async function compileCInternal(opts, cacheWarmOnly, cacheWarmPaths) {
|
|
3083
2866
|
const rtDir = runtimeSrcDir();
|
|
3084
2867
|
const sanitize = opts.sanitize ?? false;
|
|
2868
|
+
const optimization = opts.optimization ?? "release";
|
|
3085
2869
|
const dynamic = opts.dynamic ?? false;
|
|
3086
2870
|
const regex = opts.regex ?? false;
|
|
3087
2871
|
// fetch's implementation switch: the default is the NATIVE bridge
|
|
@@ -3105,6 +2889,24 @@ export async function compileC(opts) {
|
|
|
3105
2889
|
const tls = (opts.tls ?? false) || nativeFetch || netIsland;
|
|
3106
2890
|
const tlsCa = (opts.tlsCa ?? false) || tls;
|
|
3107
2891
|
const driver = resolveCc();
|
|
2892
|
+
const shardNames = new Set();
|
|
2893
|
+
const programShardsValid = opts.programShards?.every((shard) => {
|
|
2894
|
+
if (basename(shard.name) !== shard.name || !shard.name.endsWith(".ll") ||
|
|
2895
|
+
shardNames.has(shard.name))
|
|
2896
|
+
return false;
|
|
2897
|
+
shardNames.add(shard.name);
|
|
2898
|
+
return true;
|
|
2899
|
+
}) === true;
|
|
2900
|
+
const programShardsRequested = optimization === "dev" && !sanitize && opts.cPath.endsWith(".ll") &&
|
|
2901
|
+
programShardsValid && opts.programShards !== undefined && opts.programShards.length > 1 &&
|
|
2902
|
+
opts.programPublicSymbols !== undefined
|
|
2903
|
+
? opts.programShards
|
|
2904
|
+
: null;
|
|
2905
|
+
const programShardMergeIdentity = programShardsRequested === null
|
|
2906
|
+
? null
|
|
2907
|
+
: await resolveProgramShardMergeIdentity(driver);
|
|
2908
|
+
const programShards = programShardMergeIdentity === null ? null : programShardsRequested;
|
|
2909
|
+
const programPublicSymbols = programShards === null ? undefined : opts.programPublicSymbols;
|
|
3108
2910
|
// Mobile triples produce library archives, never standalone executables:
|
|
3109
2911
|
// the executable-lane runtime (event loop, sockets, child processes) is
|
|
3110
2912
|
// not verified on those device classes. compile() reports the SC3002
|
|
@@ -3114,8 +2916,9 @@ export async function compileC(opts) {
|
|
|
3114
2916
|
`compile with a library profile (SCRIPTC_CC=zigcc scriptc build --lib --profile <profile.json>) and link the archive from the app project.`);
|
|
3115
2917
|
}
|
|
3116
2918
|
const runtimeSources = targetPlatform(driver) === "wasi"
|
|
3117
|
-
?
|
|
3118
|
-
:
|
|
2919
|
+
? EXECUTABLE_RUNTIME_SOURCES.filter((source) => source !== "scr_child.c")
|
|
2920
|
+
: EXECUTABLE_RUNTIME_SOURCES;
|
|
2921
|
+
const executableSectionFlags = executableSectionEliminationFlags(targetPlatform(driver));
|
|
3119
2922
|
// scr_async.c submits callback-style filesystem work to a native worker.
|
|
3120
2923
|
// POSIX drivers need the thread compile/link mode; win32 uses CreateThread.
|
|
3121
2924
|
const threadArgs = targetPlatform(driver) === "win32" || targetPlatform(driver) === "wasi"
|
|
@@ -3125,7 +2928,7 @@ export async function compileC(opts) {
|
|
|
3125
2928
|
// See the resolveCc block: these inputs are built on and for the HOST
|
|
3126
2929
|
// (vendored archives, system libs). Regex, zlib, and the engine archive
|
|
3127
2930
|
// are NOT here: their vendored sources are plain C that ensureLreObjects
|
|
3128
|
-
// / ensureZlibObjects /
|
|
2931
|
+
// / ensureZlibObjects / buildEngineArchiveDirect compile per target with
|
|
3129
2932
|
// the driver itself — win32 included (the Windows lane runs the
|
|
3130
2933
|
// @dynamic corpus and the zlib program against the box's Node). The
|
|
3131
2934
|
// NATIVE fetch rides the socket units and cross-compiles with them —
|
|
@@ -3163,15 +2966,24 @@ export async function compileC(opts) {
|
|
|
3163
2966
|
// inputs may include caller-owned headers whose contents are not otherwise
|
|
3164
2967
|
// represented in this key, so they retain the fully uncached historical
|
|
3165
2968
|
// path unless the caller supplies its own complete dependency identity.
|
|
3166
|
-
|
|
2969
|
+
const cacheIdentity = opts.cacheIdentity;
|
|
2970
|
+
let persistentCache = cacheIdentity === undefined || configuredCacheRoot === null || !persistentDriverCache
|
|
3167
2971
|
? null
|
|
3168
|
-
: configuredCacheRoot;
|
|
3169
|
-
if (
|
|
2972
|
+
: { root: configuredCacheRoot, identity: cacheIdentity };
|
|
2973
|
+
if (cacheWarmOnly && persistentCache === null) {
|
|
2974
|
+
throw new Error("native cache warming requires a persistently cacheable compiler environment");
|
|
2975
|
+
}
|
|
2976
|
+
if (persistentCache !== null) {
|
|
3170
2977
|
try {
|
|
3171
|
-
await ensurePrivateCacheRoot(root, process.env["SCRIPTC_CACHE_DIR"] === undefined);
|
|
2978
|
+
await ensurePrivateCacheRoot(persistentCache.root, process.env["SCRIPTC_CACHE_DIR"] === undefined);
|
|
3172
2979
|
}
|
|
3173
|
-
catch {
|
|
3174
|
-
|
|
2980
|
+
catch (error) {
|
|
2981
|
+
if (cacheWarmOnly) {
|
|
2982
|
+
throw new Error("native cache warming could not prepare the persistent cache root", {
|
|
2983
|
+
cause: error,
|
|
2984
|
+
});
|
|
2985
|
+
}
|
|
2986
|
+
persistentCache = null;
|
|
3175
2987
|
}
|
|
3176
2988
|
}
|
|
3177
2989
|
let localArtifact = null;
|
|
@@ -3180,9 +2992,10 @@ export async function compileC(opts) {
|
|
|
3180
2992
|
// broader cross-output CAS performs its compiler/SDK/linker rediscovery.
|
|
3181
2993
|
// FFI/native-input builds and the public arbitrary-C cache API stay on the
|
|
3182
2994
|
// strict path because their dependency graphs are caller-owned.
|
|
3183
|
-
if (
|
|
2995
|
+
if (!cacheWarmOnly &&
|
|
2996
|
+
persistentCache !== null &&
|
|
3184
2997
|
cachePolicy.completeArtifacts &&
|
|
3185
|
-
|
|
2998
|
+
persistentCache.identity === "scriptc-generated-v1" &&
|
|
3186
2999
|
(opts.linkInputs?.length ?? 0) === 0 &&
|
|
3187
3000
|
(opts.systemLibraries?.length ?? 0) === 0 &&
|
|
3188
3001
|
process.env["SCRIPTC_TEST_TRUST_COMPILER_WRAPPER"] !== "1") {
|
|
@@ -3194,8 +3007,8 @@ export async function compileC(opts) {
|
|
|
3194
3007
|
]);
|
|
3195
3008
|
if (compiler !== null) {
|
|
3196
3009
|
const effectiveCompiler = directCompilerSelections.get(compilerDriverProbeKey(driver, toolchainEnv)) ?? compiler;
|
|
3197
|
-
const key = localArtifactIdentity(opts, driver, toolchainEnv, `${compiler.cacheIdentity}\0${effectiveCompiler.cacheIdentity}`, runtimeHash, programBytes);
|
|
3198
|
-
const stampPath = localArtifactStampPath(root, opts.outPath);
|
|
3010
|
+
const key = localArtifactIdentity(opts, driver, toolchainEnv, `${compiler.cacheIdentity}\0${effectiveCompiler.cacheIdentity}`, runtimeHash, programBytes, programShardMergeIdentity);
|
|
3011
|
+
const stampPath = localArtifactStampPath(persistentCache.root, opts.outPath);
|
|
3199
3012
|
localArtifact = {
|
|
3200
3013
|
stampPath,
|
|
3201
3014
|
key,
|
|
@@ -3203,8 +3016,11 @@ export async function compileC(opts) {
|
|
|
3203
3016
|
programBytes,
|
|
3204
3017
|
compilerPath: effectiveCompiler.canonicalPath,
|
|
3205
3018
|
};
|
|
3206
|
-
|
|
3019
|
+
const hit = await localArtifactHit(stampPath, opts.outPath, key);
|
|
3020
|
+
if (hit !== null) {
|
|
3021
|
+
await opts.onArtifactReady?.({ dependencies: hit.dependencies }).catch(() => undefined);
|
|
3207
3022
|
return;
|
|
3023
|
+
}
|
|
3208
3024
|
}
|
|
3209
3025
|
}
|
|
3210
3026
|
catch {
|
|
@@ -3214,12 +3030,13 @@ export async function compileC(opts) {
|
|
|
3214
3030
|
}
|
|
3215
3031
|
}
|
|
3216
3032
|
let implicitToolchain = null;
|
|
3033
|
+
let implicitCompileToolchain = null;
|
|
3217
3034
|
let toolchainMetadataStamp = null;
|
|
3218
|
-
const metadataCompiler =
|
|
3035
|
+
const metadataCompiler = persistentCache === null
|
|
3219
3036
|
? null
|
|
3220
3037
|
: await resolvedTool(driver.argv[0] ?? "clang");
|
|
3221
3038
|
const metadataEffectiveCompiler = directCompilerSelections.get(compilerDriverProbeKey(driver, toolchainEnv)) ?? metadataCompiler;
|
|
3222
|
-
const toolchainMetadataKey =
|
|
3039
|
+
const toolchainMetadataKey = persistentCache === null ||
|
|
3223
3040
|
metadataCompiler === null ||
|
|
3224
3041
|
metadataEffectiveCompiler === null ||
|
|
3225
3042
|
process.env["SCRIPTC_TEST_TRUST_COMPILER_WRAPPER"] === "1"
|
|
@@ -3236,86 +3053,143 @@ export async function compileC(opts) {
|
|
|
3236
3053
|
if (persistentDriverCache) {
|
|
3237
3054
|
try {
|
|
3238
3055
|
toolchainMetadataStamp =
|
|
3239
|
-
|
|
3056
|
+
persistentCache === null || toolchainMetadataKey === null
|
|
3240
3057
|
? null
|
|
3241
|
-
: await readNativeMetadataStamp(root, toolchainMetadataKey);
|
|
3242
|
-
implicitToolchain = toolchainMetadataStamp?.values["implicitToolchain"] ??
|
|
3243
|
-
|
|
3058
|
+
: await readNativeMetadataStamp(persistentCache.root, toolchainMetadataKey);
|
|
3059
|
+
implicitToolchain = toolchainMetadataStamp?.values["implicitToolchain"] ?? null;
|
|
3060
|
+
implicitCompileToolchain =
|
|
3061
|
+
toolchainMetadataStamp?.values["implicitCompileToolchain"] ?? null;
|
|
3062
|
+
if (implicitToolchain === null ||
|
|
3063
|
+
(programShards !== null && implicitCompileToolchain === null)) {
|
|
3064
|
+
const fingerprints = await implicitToolchainFingerprints(driver, toolchainEnv);
|
|
3065
|
+
implicitToolchain = fingerprints.complete;
|
|
3066
|
+
implicitCompileToolchain = fingerprints.compile;
|
|
3067
|
+
toolchainMetadataStamp = null;
|
|
3068
|
+
}
|
|
3244
3069
|
let compilerVersion = toolchainMetadataStamp?.values["compilerVersion"];
|
|
3245
3070
|
if (compilerVersion === undefined) {
|
|
3246
|
-
compilerVersion = await
|
|
3071
|
+
compilerVersion = await ccVersion(driver.argv, toolchainEnv, true);
|
|
3247
3072
|
}
|
|
3248
3073
|
if (toolchainMetadataStamp === null &&
|
|
3249
|
-
|
|
3074
|
+
persistentCache !== null &&
|
|
3250
3075
|
toolchainMetadataKey !== null) {
|
|
3251
3076
|
if (metadataCompiler !== null && metadataEffectiveCompiler !== null) {
|
|
3252
|
-
toolchainMetadataStamp = await publishNativeMetadataStamp(root, toolchainMetadataKey, {
|
|
3077
|
+
toolchainMetadataStamp = await publishNativeMetadataStamp(persistentCache.root, toolchainMetadataKey, {
|
|
3078
|
+
implicitToolchain,
|
|
3079
|
+
...(implicitCompileToolchain === null ? {} : { implicitCompileToolchain }),
|
|
3080
|
+
compilerVersion,
|
|
3081
|
+
}, [
|
|
3253
3082
|
metadataCompiler.canonicalPath,
|
|
3254
3083
|
metadataEffectiveCompiler.canonicalPath,
|
|
3255
3084
|
...fingerprintDependencyPaths(implicitToolchain),
|
|
3256
|
-
|
|
3085
|
+
...(implicitCompileToolchain === null
|
|
3086
|
+
? []
|
|
3087
|
+
: fingerprintDependencyPaths(implicitCompileToolchain)),
|
|
3088
|
+
], [
|
|
3089
|
+
implicitToolchain,
|
|
3090
|
+
...(implicitCompileToolchain === null ? [] : [implicitCompileToolchain]),
|
|
3091
|
+
]);
|
|
3257
3092
|
}
|
|
3258
3093
|
}
|
|
3259
3094
|
}
|
|
3260
|
-
catch {
|
|
3095
|
+
catch (error) {
|
|
3261
3096
|
// Cache discovery is best-effort. In particular, a compiler wrapper can
|
|
3262
3097
|
// compile successfully without implementing the metadata probes.
|
|
3263
|
-
|
|
3098
|
+
if (cacheWarmOnly) {
|
|
3099
|
+
throw new Error("native cache warming could not validate the compiler toolchain", {
|
|
3100
|
+
cause: error,
|
|
3101
|
+
});
|
|
3102
|
+
}
|
|
3103
|
+
persistentCache = null;
|
|
3264
3104
|
}
|
|
3265
3105
|
}
|
|
3266
3106
|
const vendorBuildIdentity = await currentVendorCacheBuildIdentity(driver, `${toolchainEnv}\0${implicitToolchain ?? "<uncached>"}`);
|
|
3267
3107
|
// Mutable include/SDK/config inputs can change behind a stable environment
|
|
3268
3108
|
// spelling. The complete/runtime cache is disabled below; vendor objects
|
|
3269
|
-
// must follow the same rule instead of silently surviving in
|
|
3270
|
-
//
|
|
3109
|
+
// must follow the same rule instead of silently surviving in the user cache.
|
|
3110
|
+
// A private root gives this invocation the usual vendor build recipe
|
|
3271
3111
|
// without publishing or reusing those prerequisites.
|
|
3272
|
-
const transientVendorRoot =
|
|
3112
|
+
const transientVendorRoot = persistentCache !== null && implicitToolchain !== null
|
|
3273
3113
|
? null
|
|
3274
3114
|
: join(tmpdir(), `scriptc-vendor-${process.pid}-${Math.random().toString(36).slice(2)}`);
|
|
3275
|
-
const vendorCacheRoot = transientVendorRoot ?? vendorBuildCacheRoot();
|
|
3115
|
+
const vendorCacheRoot = transientVendorRoot ?? vendorBuildCacheRoot(persistentCache?.root);
|
|
3276
3116
|
// Every vendor output path is deterministic from pins, flags, driver, and
|
|
3277
3117
|
// target. Build the command/key from those paths now, but do not materialize
|
|
3278
3118
|
// them until a complete-binary lookup has missed.
|
|
3279
|
-
|
|
3119
|
+
let engineArchive = dynamic
|
|
3280
3120
|
? engineArchivePath(sanitize, driver, vendorBuildIdentity, vendorCacheRoot)
|
|
3281
3121
|
: null;
|
|
3282
|
-
|
|
3122
|
+
let tlsArchive = tls
|
|
3283
3123
|
? tlsArchivePath(sanitize, driver, vendorBuildIdentity, vendorCacheRoot)
|
|
3284
3124
|
: null;
|
|
3285
3125
|
// --dynamic + regex shares the archive's libregexp (its host hooks and
|
|
3286
3126
|
// ours would collide; see scr_regex.c) — the standalone objects are for
|
|
3287
3127
|
// static builds only.
|
|
3288
|
-
|
|
3128
|
+
let lreObjects = regex && !dynamic
|
|
3289
3129
|
? lreObjectPaths(sanitize, driver, vendorBuildIdentity, vendorCacheRoot)
|
|
3290
3130
|
: [];
|
|
3291
|
-
// Vendored zlib is the
|
|
3131
|
+
// Vendored zlib is the Zig story — default host-clang builds keep the exact
|
|
3292
3132
|
// historical `-lz` system link (see CcOptions.zlib). The native fetch's
|
|
3293
3133
|
// gzip decoder rides the same objects/link.
|
|
3294
|
-
|
|
3134
|
+
let zlibObjects = ((opts.zlib ?? false) || nativeFetch) && isZigDriver(driver)
|
|
3295
3135
|
? zlibObjectPaths(sanitize, driver, vendorBuildIdentity, vendorCacheRoot)
|
|
3296
3136
|
: [];
|
|
3297
3137
|
// The libcurl import stub is likewise CROSS-only — host builds keep the
|
|
3298
3138
|
// exact historical system `-lcurl` link (see CcOptions.fetch). Curl
|
|
3299
3139
|
// reference builds only.
|
|
3300
|
-
|
|
3140
|
+
let curlStubDir = curlFetch && driver.target !== null
|
|
3301
3141
|
? curlStubDirPath(driver, vendorBuildIdentity, vendorCacheRoot)
|
|
3302
3142
|
: null;
|
|
3303
|
-
const materializeVendorPrerequisites = async () => {
|
|
3304
|
-
// Preserve
|
|
3143
|
+
const materializeVendorPrerequisites = async (stageRoot, materializeCacheRoot = vendorCacheRoot) => {
|
|
3144
|
+
// Preserve source order to avoid multiplying first-build resource
|
|
3305
3145
|
// pressure when several large vendor sets are cold simultaneously.
|
|
3306
3146
|
if (dynamic) {
|
|
3307
|
-
|
|
3147
|
+
const cachedArchive = engineArchivePath(sanitize, driver, vendorBuildIdentity, materializeCacheRoot);
|
|
3148
|
+
protectCachedArtifact(cacheWarmPaths, cachedArchive);
|
|
3149
|
+
const materialize = async () => [
|
|
3150
|
+
await ensureEngineArchive(sanitize, driver, vendorBuildIdentity, materializeCacheRoot),
|
|
3151
|
+
];
|
|
3152
|
+
const paths = stageRoot === undefined
|
|
3153
|
+
? await materialize()
|
|
3154
|
+
: await stageVendorInputs(materialize, join(stageRoot, "engine"));
|
|
3155
|
+
engineArchive = paths[0];
|
|
3156
|
+
}
|
|
3157
|
+
if (tls) {
|
|
3158
|
+
const cachedArchive = tlsArchivePath(sanitize, driver, vendorBuildIdentity, materializeCacheRoot);
|
|
3159
|
+
protectCachedArtifact(cacheWarmPaths, cachedArchive);
|
|
3160
|
+
const materialize = async () => [
|
|
3161
|
+
await ensureTlsArchive(sanitize, driver, vendorBuildIdentity, materializeCacheRoot),
|
|
3162
|
+
];
|
|
3163
|
+
const paths = stageRoot === undefined
|
|
3164
|
+
? await materialize()
|
|
3165
|
+
: await stageVendorInputs(materialize, join(stageRoot, "tls"));
|
|
3166
|
+
tlsArchive = paths[0];
|
|
3308
3167
|
}
|
|
3309
|
-
if (tls)
|
|
3310
|
-
await ensureTlsArchive(sanitize, driver, vendorBuildIdentity, vendorCacheRoot);
|
|
3311
3168
|
if (regex && !dynamic) {
|
|
3312
|
-
|
|
3169
|
+
for (const object of lreObjectPaths(sanitize, driver, vendorBuildIdentity, materializeCacheRoot))
|
|
3170
|
+
protectCachedArtifact(cacheWarmPaths, object);
|
|
3171
|
+
const materialize = async () => await ensureLreObjects(sanitize, driver, vendorBuildIdentity, materializeCacheRoot);
|
|
3172
|
+
lreObjects = stageRoot === undefined
|
|
3173
|
+
? await materialize()
|
|
3174
|
+
: await stageVendorInputs(materialize, join(stageRoot, "lre"));
|
|
3313
3175
|
}
|
|
3314
3176
|
if (zlibObjects.length > 0) {
|
|
3315
|
-
|
|
3177
|
+
for (const object of zlibObjectPaths(sanitize, driver, vendorBuildIdentity, materializeCacheRoot))
|
|
3178
|
+
protectCachedArtifact(cacheWarmPaths, object);
|
|
3179
|
+
const materialize = async () => await ensureZlibObjects(sanitize, driver, vendorBuildIdentity, materializeCacheRoot);
|
|
3180
|
+
zlibObjects = stageRoot === undefined
|
|
3181
|
+
? await materialize()
|
|
3182
|
+
: await stageVendorInputs(materialize, join(stageRoot, "zlib"));
|
|
3316
3183
|
}
|
|
3317
3184
|
if (curlStubDir !== null) {
|
|
3318
|
-
|
|
3185
|
+
protectCachedArtifact(cacheWarmPaths, join(curlStubDirPath(driver, vendorBuildIdentity, materializeCacheRoot), "libcurl.so"));
|
|
3186
|
+
const materialize = async () => [
|
|
3187
|
+
join(await ensureCurlStub(driver, vendorBuildIdentity, materializeCacheRoot), "libcurl.so"),
|
|
3188
|
+
];
|
|
3189
|
+
const paths = stageRoot === undefined
|
|
3190
|
+
? await materialize()
|
|
3191
|
+
: await stageVendorInputs(materialize, join(stageRoot, "curl"));
|
|
3192
|
+
curlStubDir = dirname(paths[0]);
|
|
3319
3193
|
}
|
|
3320
3194
|
};
|
|
3321
3195
|
// rt() maps each runtime source's path on the command line: identity for
|
|
@@ -3326,7 +3200,8 @@ export async function compileC(opts) {
|
|
|
3326
3200
|
...threadArgs,
|
|
3327
3201
|
...(sanitize
|
|
3328
3202
|
? ["-O1", "-fsanitize=address", "-DSCR_RC_AUDIT"]
|
|
3329
|
-
: ["-O2"]),
|
|
3203
|
+
: [optimization === "dev" ? "-O0" : "-O2"]),
|
|
3204
|
+
...executableSectionFlags.compile,
|
|
3330
3205
|
...(opts.textDecoderLegacy ? ["-DSCR_TEXT_DECODER_LEGACY"] : []),
|
|
3331
3206
|
"-fno-math-errno",
|
|
3332
3207
|
// The emitted object model is deliberately type-punned C: a hierarchy
|
|
@@ -3368,14 +3243,14 @@ export async function compileC(opts) {
|
|
|
3368
3243
|
...(opts.dc ? [rt(join(rtDir, "scr_dc.c"))] : []),
|
|
3369
3244
|
...(opts.dynAsync || opts.dynInvoke || opts.dc || nativeFetch ? [rt(join(rtDir, "scr_async_dyn.c"))] : []),
|
|
3370
3245
|
// The zlib UNIT (scr_zlib.c) gates on zlib.* IR use; the LINK (system
|
|
3371
|
-
// libz on
|
|
3246
|
+
// libz on the default host-clang build, vendored objects on Zig builds)
|
|
3372
3247
|
// also serves the native fetch's gzip decoder — spread exactly once.
|
|
3373
3248
|
...(opts.zlib
|
|
3374
|
-
? driver
|
|
3249
|
+
? isZigDriver(driver)
|
|
3375
3250
|
? ["-I", vendorZlibDir(), rt(join(rtDir, "scr_zlib.c")), ...zlibObjects]
|
|
3376
3251
|
: [rt(join(rtDir, "scr_zlib.c"))]
|
|
3377
3252
|
: nativeFetch
|
|
3378
|
-
? driver
|
|
3253
|
+
? isZigDriver(driver)
|
|
3379
3254
|
? ["-I", vendorZlibDir(), ...zlibObjects]
|
|
3380
3255
|
: []
|
|
3381
3256
|
: []),
|
|
@@ -3467,11 +3342,6 @@ export async function compileC(opts) {
|
|
|
3467
3342
|
// left-to-right archive resolution. Other dynamic targets keep
|
|
3468
3343
|
// the historical engine-adjacent spelling.
|
|
3469
3344
|
...(driver.linkArgs.includes("-lm") ? [] : ["-lm"]),
|
|
3470
|
-
// ld64 dead-stripping claws back a chunk of the engine archive;
|
|
3471
|
-
// harmless elsewhere but only spelled this way on macOS. Keyed on
|
|
3472
|
-
// the TARGET platform (= the host on the default path, where this
|
|
3473
|
-
// expression is byte-identical to the historical one).
|
|
3474
|
-
...(targetPlatform(driver) === "darwin" ? ["-Wl,-dead_strip"] : []),
|
|
3475
3345
|
// The PE stack reserve, pinned to the 8MB POSIX main-stack
|
|
3476
3346
|
// geometry ISL_MAIN_STACK_BUDGET is sized against (4MB engine
|
|
3477
3347
|
// budget + 4MB excursion margin) — quickjs-ng's own CMake makes
|
|
@@ -3499,16 +3369,17 @@ export async function compileC(opts) {
|
|
|
3499
3369
|
...(opts.linkInputs ?? []),
|
|
3500
3370
|
...(opts.systemLibraries ?? []).map((name) => `-l${name}`),
|
|
3501
3371
|
// GNU ld resolves libraries from left to right and commonly enables
|
|
3502
|
-
// --as-needed: host libz must follow scr_zlib.c/scr_fetch.c and every
|
|
3372
|
+
// --as-needed: host-clang libz must follow scr_zlib.c/scr_fetch.c and every
|
|
3503
3373
|
// generated/native input that references inflate symbols. Cross
|
|
3504
|
-
// builds use vendored zlib objects in the input section above.
|
|
3505
|
-
...(((opts.zlib ?? false) || nativeFetch) && driver
|
|
3374
|
+
// and targetless Zig builds use vendored zlib objects in the input section above.
|
|
3375
|
+
...(((opts.zlib ?? false) || nativeFetch) && !isZigDriver(driver)
|
|
3506
3376
|
? ["-lz"]
|
|
3507
3377
|
: []),
|
|
3508
3378
|
// glibc keeps libm separate from libc. This must trail the generated
|
|
3509
3379
|
// program and every native FFI input because GNU ld resolves archives
|
|
3510
3380
|
// from left to right.
|
|
3511
3381
|
...driver.linkArgs,
|
|
3382
|
+
...executableSectionFlags.link,
|
|
3512
3383
|
"-o", build.outPath ?? opts.outPath,
|
|
3513
3384
|
];
|
|
3514
3385
|
// Compile-only flags shared by runtime-object population and the caller-TU
|
|
@@ -3520,7 +3391,8 @@ export async function compileC(opts) {
|
|
|
3520
3391
|
...threadArgs,
|
|
3521
3392
|
...(sanitize
|
|
3522
3393
|
? ["-O1", "-fsanitize=address", "-DSCR_RC_AUDIT"]
|
|
3523
|
-
: ["-O2"]),
|
|
3394
|
+
: [optimization === "dev" ? "-O0" : "-O2"]),
|
|
3395
|
+
...executableSectionFlags.compile,
|
|
3524
3396
|
...(opts.textDecoderLegacy ? ["-DSCR_TEXT_DECODER_LEGACY"] : []),
|
|
3525
3397
|
"-fno-math-errno",
|
|
3526
3398
|
"-fno-strict-aliasing", // the emitted object model type-puns — see buildArgs
|
|
@@ -3553,11 +3425,21 @@ export async function compileC(opts) {
|
|
|
3553
3425
|
`${guidance}\n\n${stderr}`);
|
|
3554
3426
|
}
|
|
3555
3427
|
};
|
|
3428
|
+
const runUncachedBuild = async () => {
|
|
3429
|
+
const privateVendorRoot = transientVendorRoot ?? join(tmpdir(), `scriptc-vendor-fallback-${process.pid}-${Math.random().toString(36).slice(2)}`);
|
|
3430
|
+
try {
|
|
3431
|
+
await materializeVendorPrerequisites(undefined, privateVendorRoot);
|
|
3432
|
+
await runClang(buildArgs((p) => p));
|
|
3433
|
+
}
|
|
3434
|
+
finally {
|
|
3435
|
+
await rm(privateVendorRoot, { recursive: true, force: true }).catch(() => undefined);
|
|
3436
|
+
}
|
|
3437
|
+
};
|
|
3556
3438
|
let runtimeCompilerInvocation = null;
|
|
3557
3439
|
let programCompilerInvocation = null;
|
|
3558
3440
|
let payloadMetadata = null;
|
|
3559
3441
|
let compileMetadataStamp = null;
|
|
3560
|
-
const compileMetadataKey =
|
|
3442
|
+
const compileMetadataKey = persistentCache === null || process.env["SCRIPTC_TEST_TRUST_COMPILER_WRAPPER"] === "1"
|
|
3561
3443
|
? null
|
|
3562
3444
|
: nativeMetadataKey("compile", [
|
|
3563
3445
|
cacheTargetIdentity(driver),
|
|
@@ -3568,14 +3450,14 @@ export async function compileC(opts) {
|
|
|
3568
3450
|
programCompilerArgs,
|
|
3569
3451
|
programSourceExtension,
|
|
3570
3452
|
]);
|
|
3571
|
-
if (
|
|
3453
|
+
if (persistentCache !== null) {
|
|
3572
3454
|
try {
|
|
3573
3455
|
// These probes inspect disjoint inputs. Start the payload reads here as
|
|
3574
3456
|
// well so runtime hashing and clang's dry-run traces overlap instead of
|
|
3575
3457
|
// forming a serial prelude before every cache lookup.
|
|
3576
3458
|
payloadMetadata = Promise.all([
|
|
3577
3459
|
Promise.resolve(toolchainMetadataStamp?.values["compilerVersion"] ??
|
|
3578
|
-
|
|
3460
|
+
ccVersion(driver.argv, toolchainEnv, true)),
|
|
3579
3461
|
localArtifact === null
|
|
3580
3462
|
? runtimeFingerprint(rtDir)
|
|
3581
3463
|
: Promise.resolve(localArtifact.runtimeHash),
|
|
@@ -3585,7 +3467,7 @@ export async function compileC(opts) {
|
|
|
3585
3467
|
]);
|
|
3586
3468
|
compileMetadataStamp = compileMetadataKey === null
|
|
3587
3469
|
? null
|
|
3588
|
-
: await readNativeMetadataStamp(root, compileMetadataKey);
|
|
3470
|
+
: await readNativeMetadataStamp(persistentCache.root, compileMetadataKey);
|
|
3589
3471
|
if (compileMetadataStamp !== null) {
|
|
3590
3472
|
runtimeCompilerInvocation = compileMetadataStamp.values["runtimeInvocation"] ?? null;
|
|
3591
3473
|
programCompilerInvocation = compileMetadataStamp.values["programInvocation"] ?? null;
|
|
@@ -3603,7 +3485,7 @@ export async function compileC(opts) {
|
|
|
3603
3485
|
runtimeCompilerInvocation = runtimeInvocation;
|
|
3604
3486
|
programCompilerInvocation = programInvocation ?? runtimeInvocation;
|
|
3605
3487
|
if (compileMetadataKey !== null) {
|
|
3606
|
-
compileMetadataStamp = await publishNativeMetadataStamp(root, compileMetadataKey, {
|
|
3488
|
+
compileMetadataStamp = await publishNativeMetadataStamp(persistentCache.root, compileMetadataKey, {
|
|
3607
3489
|
runtimeInvocation: runtimeCompilerInvocation,
|
|
3608
3490
|
programInvocation: programCompilerInvocation,
|
|
3609
3491
|
}, [
|
|
@@ -3613,23 +3495,20 @@ export async function compileC(opts) {
|
|
|
3613
3495
|
}
|
|
3614
3496
|
}
|
|
3615
3497
|
}
|
|
3616
|
-
catch {
|
|
3498
|
+
catch (error) {
|
|
3617
3499
|
// Preserve the uncached build for wrappers that compile successfully but
|
|
3618
3500
|
// cannot provide a dry-run trace for the real build flavor.
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
// The exact historical command line, byte for byte.
|
|
3624
|
-
try {
|
|
3625
|
-
await materializeVendorPrerequisites();
|
|
3626
|
-
await runClang(buildArgs((p) => p));
|
|
3627
|
-
}
|
|
3628
|
-
finally {
|
|
3629
|
-
if (transientVendorRoot !== null) {
|
|
3630
|
-
await rm(transientVendorRoot, { recursive: true, force: true }).catch(() => undefined);
|
|
3501
|
+
if (cacheWarmOnly) {
|
|
3502
|
+
throw new Error("native cache warming could not validate the compiler invocation", {
|
|
3503
|
+
cause: error,
|
|
3504
|
+
});
|
|
3631
3505
|
}
|
|
3506
|
+
persistentCache = null;
|
|
3632
3507
|
}
|
|
3508
|
+
}
|
|
3509
|
+
if (persistentCache === null) {
|
|
3510
|
+
// The direct uncached command is the source-of-truth executable recipe.
|
|
3511
|
+
await runUncachedBuild();
|
|
3633
3512
|
return;
|
|
3634
3513
|
}
|
|
3635
3514
|
let cv;
|
|
@@ -3637,17 +3516,21 @@ export async function compileC(opts) {
|
|
|
3637
3516
|
let cBytes;
|
|
3638
3517
|
try {
|
|
3639
3518
|
[cv, fingerprint, cBytes] = await (payloadMetadata ?? Promise.all([
|
|
3640
|
-
|
|
3519
|
+
ccVersion(driver.argv, toolchainEnv, true),
|
|
3641
3520
|
runtimeFingerprint(rtDir),
|
|
3642
3521
|
readFile(opts.cPath),
|
|
3643
3522
|
]));
|
|
3644
3523
|
}
|
|
3645
|
-
catch {
|
|
3524
|
+
catch (error) {
|
|
3646
3525
|
// A version/fingerprint probe is an optimization boundary. If the compiler
|
|
3647
3526
|
// itself can still compile, preserve the pre-cache behavior instead of
|
|
3648
3527
|
// surfacing a metadata command's failure as the build result.
|
|
3649
|
-
|
|
3650
|
-
|
|
3528
|
+
if (cacheWarmOnly) {
|
|
3529
|
+
throw new Error("native cache warming could not validate cache inputs", {
|
|
3530
|
+
cause: error,
|
|
3531
|
+
});
|
|
3532
|
+
}
|
|
3533
|
+
await runUncachedBuild();
|
|
3651
3534
|
return;
|
|
3652
3535
|
}
|
|
3653
3536
|
// Caller-supplied native inputs can all hide mutable dependencies: `-l`
|
|
@@ -3655,7 +3538,8 @@ export async function compileC(opts) {
|
|
|
3655
3538
|
// script can retain identical top-level bytes as its referenced files are
|
|
3656
3539
|
// rebuilt. Keep the safe runtime-object cache, but force a fresh final link
|
|
3657
3540
|
// whenever the caller supplies either form.
|
|
3658
|
-
let cacheCompleteArtifact =
|
|
3541
|
+
let cacheCompleteArtifact = !cacheWarmOnly &&
|
|
3542
|
+
cachePolicy.completeArtifacts &&
|
|
3659
3543
|
(opts.linkInputs?.length ?? 0) === 0 &&
|
|
3660
3544
|
(opts.systemLibraries?.length ?? 0) === 0;
|
|
3661
3545
|
let programDependencies = null;
|
|
@@ -3669,8 +3553,9 @@ export async function compileC(opts) {
|
|
|
3669
3553
|
...(tlsCa && targetPlatform(driver) === "win32" ? ["-lcrypt32"] : []),
|
|
3670
3554
|
...(curlFetch && driver.target === null ? ["-lcurl"] : []),
|
|
3671
3555
|
...(dynamic && !driver.linkArgs.includes("-lm") ? ["-lm"] : []),
|
|
3672
|
-
...(((opts.zlib ?? false) || nativeFetch) && driver
|
|
3556
|
+
...(((opts.zlib ?? false) || nativeFetch) && !isZigDriver(driver) ? ["-lz"] : []),
|
|
3673
3557
|
...driver.linkArgs,
|
|
3558
|
+
...executableSectionFlags.link,
|
|
3674
3559
|
];
|
|
3675
3560
|
// Both the wrapper dry run and dependency trace need the real build's
|
|
3676
3561
|
// compile/link flag shape: wrappers commonly inject flags or native inputs
|
|
@@ -3685,12 +3570,12 @@ export async function compileC(opts) {
|
|
|
3685
3570
|
...(curlStubDir !== null ? [`-L${curlStubDir}`] : []),
|
|
3686
3571
|
...(curlFetch && driver.target === null ? ["-lcurl"] : []),
|
|
3687
3572
|
...(dynamic && !driver.linkArgs.includes("-lm") ? ["-lm"] : []),
|
|
3688
|
-
...(dynamic && targetPlatform(driver) === "darwin" ? ["-Wl,-dead_strip"] : []),
|
|
3689
3573
|
...(dynamic && targetPlatform(driver) === "win32"
|
|
3690
3574
|
? ["-Wl,--stack,8388608"]
|
|
3691
3575
|
: []),
|
|
3692
|
-
...(((opts.zlib ?? false) || nativeFetch) && driver
|
|
3576
|
+
...(((opts.zlib ?? false) || nativeFetch) && !isZigDriver(driver) ? ["-lz"] : []),
|
|
3693
3577
|
...driver.linkArgs,
|
|
3578
|
+
...executableSectionFlags.link,
|
|
3694
3579
|
];
|
|
3695
3580
|
// A complete hit is checked before cross-target curl's generated import stub
|
|
3696
3581
|
// is materialized. Its -L spelling still joins the dry-run identity, while
|
|
@@ -3702,7 +3587,7 @@ export async function compileC(opts) {
|
|
|
3702
3587
|
let preBuildDependencies = null;
|
|
3703
3588
|
let localArtifactDependencyPaths = null;
|
|
3704
3589
|
let linkMetadataStamp = null;
|
|
3705
|
-
const linkMetadataKey =
|
|
3590
|
+
const linkMetadataKey = process.env["SCRIPTC_TEST_TRUST_COMPILER_WRAPPER"] === "1"
|
|
3706
3591
|
? null
|
|
3707
3592
|
: nativeMetadataKey("link", [
|
|
3708
3593
|
cacheTargetIdentity(driver),
|
|
@@ -3714,6 +3599,7 @@ export async function compileC(opts) {
|
|
|
3714
3599
|
linkProbeArgs,
|
|
3715
3600
|
effectiveLinkInvocationArgs,
|
|
3716
3601
|
linkTraceInvocationArgs,
|
|
3602
|
+
...(programShardMergeIdentity === null ? [] : [programShardMergeIdentity]),
|
|
3717
3603
|
]);
|
|
3718
3604
|
if (cacheCompleteArtifact) {
|
|
3719
3605
|
try {
|
|
@@ -3722,7 +3608,7 @@ export async function compileC(opts) {
|
|
|
3722
3608
|
// cache hits and ordinary edit/build misses without changing either key.
|
|
3723
3609
|
linkMetadataStamp = linkMetadataKey === null
|
|
3724
3610
|
? null
|
|
3725
|
-
: await readNativeMetadataStamp(root, linkMetadataKey);
|
|
3611
|
+
: await readNativeMetadataStamp(persistentCache.root, linkMetadataKey);
|
|
3726
3612
|
if (linkMetadataStamp !== null) {
|
|
3727
3613
|
implicitLinker = linkMetadataStamp.values["implicitLinker"] ?? null;
|
|
3728
3614
|
if (implicitLinker === null)
|
|
@@ -3735,7 +3621,7 @@ export async function compileC(opts) {
|
|
|
3735
3621
|
: Promise.resolve(implicitLinker),
|
|
3736
3622
|
]);
|
|
3737
3623
|
if (linkMetadataStamp === null && linkMetadataKey !== null) {
|
|
3738
|
-
linkMetadataStamp = await publishNativeMetadataStamp(root, linkMetadataKey, { implicitLinker }, fingerprintDependencyPaths(implicitLinker), [implicitLinker]);
|
|
3624
|
+
linkMetadataStamp = await publishNativeMetadataStamp(persistentCache.root, linkMetadataKey, { implicitLinker }, fingerprintDependencyPaths(implicitLinker), [implicitLinker]);
|
|
3739
3625
|
}
|
|
3740
3626
|
preBuildDependencies = await snapshotLocalArtifactDependencies([
|
|
3741
3627
|
...(await runtimeFingerprintInputPaths(rtDir)),
|
|
@@ -3745,6 +3631,9 @@ export async function compileC(opts) {
|
|
|
3745
3631
|
...(linkMetadataStamp?.dependencies.map((dependency) => dependency.path) ??
|
|
3746
3632
|
fingerprintDependencyPaths(implicitLinker)),
|
|
3747
3633
|
...fingerprintDependencyPaths(programDependencies),
|
|
3634
|
+
...(programShardMergeIdentity === null
|
|
3635
|
+
? []
|
|
3636
|
+
: fingerprintDependencyPaths(programShardMergeIdentity)),
|
|
3748
3637
|
]);
|
|
3749
3638
|
// Every content-bearing fingerprint was computed before this metadata
|
|
3750
3639
|
// snapshot. Re-read those exact files now so the snapshot cannot certify
|
|
@@ -3755,11 +3644,12 @@ export async function compileC(opts) {
|
|
|
3755
3644
|
programCompilerInvocation,
|
|
3756
3645
|
implicitLinker,
|
|
3757
3646
|
programDependencies,
|
|
3647
|
+
...(programShardMergeIdentity === null ? [] : [programShardMergeIdentity]),
|
|
3758
3648
|
])) ||
|
|
3759
3649
|
await runtimeFingerprint(rtDir).catch(() => null) !== fingerprint ||
|
|
3760
3650
|
!(await Promise.all([toolchainMetadataStamp, compileMetadataStamp, linkMetadataStamp]
|
|
3761
3651
|
.filter((stamp) => stamp !== null)
|
|
3762
|
-
.map((stamp) =>
|
|
3652
|
+
.map((stamp) => nativeArtifactDependenciesStillMatch(stamp.dependencies)))).every(Boolean)) {
|
|
3763
3653
|
throw new CacheInputsChangedError();
|
|
3764
3654
|
}
|
|
3765
3655
|
if (localArtifact !== null) {
|
|
@@ -3784,7 +3674,26 @@ export async function compileC(opts) {
|
|
|
3784
3674
|
localArtifactDependencyPaths = null;
|
|
3785
3675
|
}
|
|
3786
3676
|
}
|
|
3787
|
-
|
|
3677
|
+
// Warm-only builds deliberately do not need linker/header identity for a
|
|
3678
|
+
// complete executable, but they still need a stable runtime-object key.
|
|
3679
|
+
// The ordinary complete-artifact path computes these values above.
|
|
3680
|
+
if (cacheWarmOnly && preBuildDependencies === null) {
|
|
3681
|
+
try {
|
|
3682
|
+
const [dependencies, invocation] = await Promise.all([
|
|
3683
|
+
translationUnitDependencyFingerprint(driver, cflags, opts.cPath, cBytes, toolchainEnv),
|
|
3684
|
+
effectiveCompilerInvocationFingerprint(driver, toolchainEnv, cflags),
|
|
3685
|
+
]);
|
|
3686
|
+
programDependencies = dependencies;
|
|
3687
|
+
if (runtimeCompilerInvocation === null)
|
|
3688
|
+
runtimeCompilerInvocation = invocation;
|
|
3689
|
+
}
|
|
3690
|
+
catch (error) {
|
|
3691
|
+
throw new Error("native cache warming could not validate runtime-object inputs", {
|
|
3692
|
+
cause: error,
|
|
3693
|
+
});
|
|
3694
|
+
}
|
|
3695
|
+
}
|
|
3696
|
+
const binDir = join(persistentCache.root, "bin");
|
|
3788
3697
|
let keyHex = null;
|
|
3789
3698
|
let cachedBin = null;
|
|
3790
3699
|
if (cacheCompleteArtifact) {
|
|
@@ -3795,9 +3704,10 @@ export async function compileC(opts) {
|
|
|
3795
3704
|
// the fingerprint and the pin.
|
|
3796
3705
|
const identityArgs = buildArgs((p) => p).map((a) => a === opts.cPath ? "<program.c>" : a === opts.outPath ? "<out>" : a);
|
|
3797
3706
|
const key = createHash("sha256")
|
|
3798
|
-
//
|
|
3799
|
-
//
|
|
3800
|
-
.
|
|
3707
|
+
// Sharded dev builds need the v10 identity below. Canonical single-TU
|
|
3708
|
+
// builds retain v9 so adding the opt-in mode does not evict release
|
|
3709
|
+
// binaries compiled by an earlier scriptc.
|
|
3710
|
+
.update(programShards === null ? "bin-v9\0" : "bin-v10\0")
|
|
3801
3711
|
.update(cacheTargetIdentity(driver)).update("\0")
|
|
3802
3712
|
.update(toolchainEnv).update("\0")
|
|
3803
3713
|
.update(implicitToolchain).update("\0")
|
|
@@ -3805,7 +3715,7 @@ export async function compileC(opts) {
|
|
|
3805
3715
|
.update(programCompilerInvocation).update("\0")
|
|
3806
3716
|
.update(implicitLinker).update("\0")
|
|
3807
3717
|
.update(programDependencies).update("\0")
|
|
3808
|
-
.update(
|
|
3718
|
+
.update(persistentCache.identity).update("\0")
|
|
3809
3719
|
// Preserve both the spelling clang sees (__FILE__) and the location used
|
|
3810
3720
|
// to resolve relative includes. The top-level bytes are not sufficient.
|
|
3811
3721
|
.update(opts.cPath).update("\0")
|
|
@@ -3819,6 +3729,9 @@ export async function compileC(opts) {
|
|
|
3819
3729
|
.update(fingerprint).update("\0")
|
|
3820
3730
|
.update(identityArgs.join("\x1f")).update("\0")
|
|
3821
3731
|
.update(cBytes);
|
|
3732
|
+
if (programShards !== null) {
|
|
3733
|
+
updateProgramShardCacheIdentity(key, programShards, programPublicSymbols, programShardMergeIdentity ?? undefined);
|
|
3734
|
+
}
|
|
3822
3735
|
keyHex = key.digest("hex");
|
|
3823
3736
|
cachedBin = join(binDir, keyHex);
|
|
3824
3737
|
const tmpOut = privateSiblingPath(opts.outPath, "bin-hit");
|
|
@@ -3835,7 +3748,10 @@ export async function compileC(opts) {
|
|
|
3835
3748
|
await chmod(tmpOut, 0o777 & ~process.umask());
|
|
3836
3749
|
await rename(tmpOut, opts.outPath);
|
|
3837
3750
|
if (localArtifact !== null && localArtifactDependencyPaths !== null) {
|
|
3838
|
-
await publishLocalArtifactStamp(localArtifact.stampPath, opts.outPath, localArtifact.key, localArtifactDependencyPaths, [dirname(resolve(opts.cPath))], [root]).catch(() =>
|
|
3751
|
+
const stamp = await publishLocalArtifactStamp(localArtifact.stampPath, opts.outPath, localArtifact.key, localArtifactDependencyPaths, [dirname(resolve(opts.cPath))], [persistentCache.root]).catch(() => null);
|
|
3752
|
+
if (stamp !== null) {
|
|
3753
|
+
await opts.onArtifactReady?.({ dependencies: stamp.dependencies }).catch(() => undefined);
|
|
3754
|
+
}
|
|
3839
3755
|
}
|
|
3840
3756
|
return; // hit: the program/runtime payload compile and link were skipped
|
|
3841
3757
|
}
|
|
@@ -3844,7 +3760,6 @@ export async function compileC(opts) {
|
|
|
3844
3760
|
/* miss — build below, then publish */
|
|
3845
3761
|
}
|
|
3846
3762
|
}
|
|
3847
|
-
await materializeVendorPrerequisites();
|
|
3848
3763
|
// Miss: link the program's own TU against cached per-flavor runtime objects.
|
|
3849
3764
|
// Collect the runtime sources this build actually compiles (the same
|
|
3850
3765
|
// conditionals as the command line, by construction).
|
|
@@ -3855,12 +3770,13 @@ export async function compileC(opts) {
|
|
|
3855
3770
|
});
|
|
3856
3771
|
const buildDir = await mkdtemp(join(tmpdir(), "scriptc-cache-build-"));
|
|
3857
3772
|
try {
|
|
3773
|
+
await materializeVendorPrerequisites(join(buildDir, "vendor-inputs"));
|
|
3858
3774
|
// Freeze the exact bytes used for the key. Generated TUs live at stable
|
|
3859
3775
|
// paths under .scriptc/, so two builds can otherwise overwrite that path
|
|
3860
3776
|
// between hashing and clang and publish one invocation's code under the
|
|
3861
3777
|
// other's key. The prefix map preserves the original __FILE__/debug-file
|
|
3862
3778
|
// spelling while clang reads this invocation-private snapshot.
|
|
3863
|
-
const programPath = join(buildDir, `program${
|
|
3779
|
+
const programPath = join(buildDir, `program${programSourceExtension}`);
|
|
3864
3780
|
// Preserve the caller-visible basename while keeping Darwin builds on a
|
|
3865
3781
|
// private inode: ld uses this spelling as the embedded ad-hoc signing
|
|
3866
3782
|
// identifier. Other targets retain the basename-independent cache key.
|
|
@@ -3875,7 +3791,7 @@ export async function compileC(opts) {
|
|
|
3875
3791
|
let strictObjectVerification = null;
|
|
3876
3792
|
const objectImplicitToolchainStillMatches = () => {
|
|
3877
3793
|
if (preBuildDependencies !== null) {
|
|
3878
|
-
return
|
|
3794
|
+
return nativeArtifactDependenciesStillMatch(preBuildDependencies);
|
|
3879
3795
|
}
|
|
3880
3796
|
// Complete-artifact caching can be disabled by caller-owned native
|
|
3881
3797
|
// inputs while the safe runtime-object tier remains active. Preserve its
|
|
@@ -3890,15 +3806,116 @@ export async function compileC(opts) {
|
|
|
3890
3806
|
return strictObjectVerification;
|
|
3891
3807
|
};
|
|
3892
3808
|
try {
|
|
3893
|
-
const cached = await ensureRuntimeObjects(root, driver.argv, cflags, rtInputs, `obj-v5\0${cacheTargetIdentity(driver)}\0${toolchainEnv}\0${implicitToolchain}\0${runtimeCompilerInvocation}\0${ccName}\0${cv}\0${fingerprint}\0`, async () => await objectImplicitToolchainStillMatches());
|
|
3809
|
+
const cached = await ensureRuntimeObjects(persistentCache.root, driver.argv, cflags, rtInputs, `obj-v5\0${cacheTargetIdentity(driver)}\0${toolchainEnv}\0${implicitToolchain}\0${runtimeCompilerInvocation}\0${ccName}\0${cv}\0${fingerprint}\0`, async () => await objectImplicitToolchainStillMatches(), cacheWarmPaths);
|
|
3894
3810
|
objects = await stageRuntimeObjects(cached, join(buildDir, "runtime-objects"));
|
|
3895
3811
|
}
|
|
3896
3812
|
catch (err) {
|
|
3897
3813
|
if (err instanceof CacheInputsChangedError)
|
|
3898
3814
|
cacheInputsStable = false;
|
|
3815
|
+
if (cacheWarmOnly) {
|
|
3816
|
+
throw new Error("native cache warming could not persist runtime objects", {
|
|
3817
|
+
cause: err,
|
|
3818
|
+
});
|
|
3819
|
+
}
|
|
3899
3820
|
objects = null; // cache trouble is never a build failure
|
|
3900
3821
|
}
|
|
3901
|
-
|
|
3822
|
+
const compileShardedProgram = async () => {
|
|
3823
|
+
if (programShards === null || programPublicSymbols === undefined ||
|
|
3824
|
+
programCompilerInvocation === null || implicitCompileToolchain === null)
|
|
3825
|
+
return null;
|
|
3826
|
+
try {
|
|
3827
|
+
const programDependencyHash = programDependencies ??
|
|
3828
|
+
await translationUnitDependencyFingerprint(driver, cflags, opts.cPath, cBytes, toolchainEnv);
|
|
3829
|
+
const stem = basename(opts.cPath, ".ll");
|
|
3830
|
+
const entries = programShards.map((shard, index) => {
|
|
3831
|
+
const sourcePath = join(buildDir, shard.name);
|
|
3832
|
+
const staged = join(buildDir, `${stem}.program-${index.toString().padStart(3, "0")}.o`);
|
|
3833
|
+
const key = createHash("sha256")
|
|
3834
|
+
.update("exe-program-shard-v1\0")
|
|
3835
|
+
.update(cacheTargetIdentity(driver)).update("\0")
|
|
3836
|
+
.update(toolchainEnv).update("\0")
|
|
3837
|
+
.update(implicitCompileToolchain).update("\0")
|
|
3838
|
+
.update(programCompilerInvocation).update("\0")
|
|
3839
|
+
.update(persistentCache.identity).update("\0")
|
|
3840
|
+
.update(driver.argv.join("\x1f")).update("\0")
|
|
3841
|
+
.update(cv).update("\0")
|
|
3842
|
+
.update(fingerprint).update("\0")
|
|
3843
|
+
.update(programDependencyHash).update("\0")
|
|
3844
|
+
.update(programCompilerArgs.join("\x1f")).update("\0")
|
|
3845
|
+
.update(opts.cPath).update("\0")
|
|
3846
|
+
.update(resolve(opts.cPath)).update("\0")
|
|
3847
|
+
.update(shard.name).update("\0")
|
|
3848
|
+
.update(shard.source)
|
|
3849
|
+
.digest("hex");
|
|
3850
|
+
return {
|
|
3851
|
+
...shard,
|
|
3852
|
+
sourcePath,
|
|
3853
|
+
staged,
|
|
3854
|
+
cachePath: join(persistentCache.root, "program-shard", key),
|
|
3855
|
+
missed: false,
|
|
3856
|
+
};
|
|
3857
|
+
});
|
|
3858
|
+
const shardWidth = Math.min(8, availableParallelism());
|
|
3859
|
+
for (let i = 0; i < entries.length; i += shardWidth) {
|
|
3860
|
+
await Promise.all(entries.slice(i, i + shardWidth).map(async (entry) => {
|
|
3861
|
+
await writeFile(entry.sourcePath, entry.source);
|
|
3862
|
+
if (await copyValidCachedFile(entry.cachePath, entry.staged))
|
|
3863
|
+
return;
|
|
3864
|
+
entry.missed = true;
|
|
3865
|
+
await runClang([
|
|
3866
|
+
...programCompilerArgs,
|
|
3867
|
+
`-ffile-prefix-map=${entry.sourcePath}=${opts.cPath}`,
|
|
3868
|
+
"-c",
|
|
3869
|
+
entry.sourcePath,
|
|
3870
|
+
"-o",
|
|
3871
|
+
entry.staged,
|
|
3872
|
+
]);
|
|
3873
|
+
}));
|
|
3874
|
+
}
|
|
3875
|
+
const arArgv = isZigDriver(driver) ? [driver.argv[0], "ar"] : ["ar"];
|
|
3876
|
+
const merged = await localizeLibraryObjects(driver, arArgv, buildDir, entries.map((entry) => entry.staged), [], programPublicSymbols, `${stem}.program`);
|
|
3877
|
+
const publishable = entries.filter((entry) => entry.missed);
|
|
3878
|
+
if (publishable.length > 0) {
|
|
3879
|
+
try {
|
|
3880
|
+
const [currentRuntime, currentFingerprints, currentInvocation, currentDependencies, currentCompiler, currentMerge] = await Promise.all([
|
|
3881
|
+
runtimeFingerprint(rtDir),
|
|
3882
|
+
implicitToolchainFingerprints(driver, toolchainEnv),
|
|
3883
|
+
effectiveCompilerInvocationFingerprint(driver, toolchainEnv, programCompilerArgs, ".ll"),
|
|
3884
|
+
translationUnitDependencyFingerprint(driver, cflags, opts.cPath, cBytes, toolchainEnv),
|
|
3885
|
+
ccVersion(driver.argv, toolchainEnv, true),
|
|
3886
|
+
resolveProgramShardMergeIdentity(driver),
|
|
3887
|
+
]);
|
|
3888
|
+
if (currentRuntime === fingerprint &&
|
|
3889
|
+
currentFingerprints.compile === implicitCompileToolchain &&
|
|
3890
|
+
currentInvocation === programCompilerInvocation &&
|
|
3891
|
+
currentDependencies === programDependencyHash &&
|
|
3892
|
+
currentCompiler === cv &&
|
|
3893
|
+
currentMerge === programShardMergeIdentity) {
|
|
3894
|
+
await Promise.all(publishable.map((entry) => publishCachedFile(entry.staged, entry.cachePath)));
|
|
3895
|
+
}
|
|
3896
|
+
}
|
|
3897
|
+
catch {
|
|
3898
|
+
// The merged object is valid for this invocation; publication is
|
|
3899
|
+
// only an optimization for later edits.
|
|
3900
|
+
}
|
|
3901
|
+
}
|
|
3902
|
+
return merged;
|
|
3903
|
+
}
|
|
3904
|
+
catch {
|
|
3905
|
+
// Sharding is an optimization. Compile/link the canonical TU below if
|
|
3906
|
+
// a target tool, one shard, or relocatable merge is unavailable.
|
|
3907
|
+
return null;
|
|
3908
|
+
}
|
|
3909
|
+
};
|
|
3910
|
+
const shardedProgramObject = await compileShardedProgram();
|
|
3911
|
+
if (programShards !== null && shardedProgramObject === null) {
|
|
3912
|
+
// The canonical TU fallback is valid output, but it must not populate a
|
|
3913
|
+
// cache key describing a successful shard projection/merge.
|
|
3914
|
+
cacheInputsStable = false;
|
|
3915
|
+
}
|
|
3916
|
+
await runClang(buildArgs((p) => objects?.get(p) ?? p, shardedProgramObject === null
|
|
3917
|
+
? { programPath, outPath: privateOut, compilerVisibleSource: opts.cPath }
|
|
3918
|
+
: { programPath: shardedProgramObject, outPath: privateOut }));
|
|
3902
3919
|
await installArtifact(privateOut, opts.outPath);
|
|
3903
3920
|
if (cachedBin !== null && keyHex !== null) {
|
|
3904
3921
|
// Metadata comparison catches ordinary changes cheaply, but cannot by
|
|
@@ -3906,26 +3923,30 @@ export async function compileC(opts) {
|
|
|
3906
3923
|
// the key. Recompute every content-bearing identity after the final link
|
|
3907
3924
|
// so a header/SDK/compiler change in either pre-build gap cannot publish
|
|
3908
3925
|
// new output under an old key.
|
|
3909
|
-
const [currentRuntime, currentImplicit, currentRuntimeInvocation, currentProgramInvocation, currentProgramDependencies, currentLinker, currentCompiler] = await Promise.all([
|
|
3926
|
+
const [currentRuntime, currentImplicit, currentRuntimeInvocation, currentProgramInvocation, currentProgramDependencies, currentLinker, currentCompiler, currentProgramShardMerge] = await Promise.all([
|
|
3910
3927
|
runtimeFingerprint(rtDir).catch(() => null),
|
|
3911
3928
|
implicitToolchainFingerprint(driver, toolchainEnv).catch(() => null),
|
|
3912
3929
|
effectiveCompilerInvocationFingerprint(driver, toolchainEnv, cflags).catch(() => null),
|
|
3913
3930
|
effectiveCompilerInvocationFingerprint(driver, toolchainEnv, programCompilerArgs, programSourceExtension).catch(() => null),
|
|
3914
3931
|
translationUnitDependencyFingerprint(driver, cflags, opts.cPath, cBytes, toolchainEnv).catch(() => null),
|
|
3915
3932
|
implicitLinkerFingerprint(driver, toolchainEnv, linkProbeArgs, effectiveLinkInvocationArgs, linkTraceInvocationArgs).catch(() => null),
|
|
3916
|
-
|
|
3933
|
+
ccVersion(driver.argv, toolchainEnv, true).catch(() => null),
|
|
3934
|
+
programShards === null
|
|
3935
|
+
? Promise.resolve(null)
|
|
3936
|
+
: resolveProgramShardMergeIdentity(driver).catch(() => null),
|
|
3917
3937
|
]);
|
|
3918
3938
|
cacheInputsStable =
|
|
3919
3939
|
cacheInputsStable &&
|
|
3920
3940
|
preBuildDependencies !== null &&
|
|
3921
|
-
await
|
|
3941
|
+
await nativeArtifactDependenciesStillMatch(preBuildDependencies) &&
|
|
3922
3942
|
currentRuntime === fingerprint &&
|
|
3923
3943
|
currentImplicit === implicitToolchain &&
|
|
3924
3944
|
currentRuntimeInvocation === runtimeCompilerInvocation &&
|
|
3925
3945
|
currentProgramInvocation === programCompilerInvocation &&
|
|
3926
3946
|
currentProgramDependencies === programDependencies &&
|
|
3927
3947
|
currentLinker === implicitLinker &&
|
|
3928
|
-
currentCompiler === cv
|
|
3948
|
+
currentCompiler === cv &&
|
|
3949
|
+
currentProgramShardMerge === programShardMergeIdentity;
|
|
3929
3950
|
}
|
|
3930
3951
|
if (cachedBin !== null && keyHex !== null && cacheInputsStable) {
|
|
3931
3952
|
try {
|
|
@@ -3942,7 +3963,10 @@ export async function compileC(opts) {
|
|
|
3942
3963
|
localArtifactDependencyPaths !== null &&
|
|
3943
3964
|
cacheCompleteArtifact &&
|
|
3944
3965
|
cacheInputsStable) {
|
|
3945
|
-
await publishLocalArtifactStamp(localArtifact.stampPath, opts.outPath, localArtifact.key, localArtifactDependencyPaths, [dirname(resolve(opts.cPath))], [root]).catch(() =>
|
|
3966
|
+
const stamp = await publishLocalArtifactStamp(localArtifact.stampPath, opts.outPath, localArtifact.key, localArtifactDependencyPaths, [dirname(resolve(opts.cPath))], [persistentCache.root]).catch(() => null);
|
|
3967
|
+
if (stamp !== null) {
|
|
3968
|
+
await opts.onArtifactReady?.({ dependencies: stamp.dependencies }).catch(() => undefined);
|
|
3969
|
+
}
|
|
3946
3970
|
}
|
|
3947
3971
|
}
|
|
3948
3972
|
finally {
|
|
@@ -3950,6 +3974,80 @@ export async function compileC(opts) {
|
|
|
3950
3974
|
}
|
|
3951
3975
|
// Runtime-object population is itself a cache write, including on builds
|
|
3952
3976
|
// whose native link inputs disable complete-artifact publication.
|
|
3953
|
-
await pruneCache(root).catch(() => undefined);
|
|
3977
|
+
await pruneCache(persistentCache.root, cacheWarmPaths).catch(() => undefined);
|
|
3978
|
+
}
|
|
3979
|
+
export async function compileC(opts) {
|
|
3980
|
+
clearCcCaches();
|
|
3981
|
+
await compileCInternal(opts, false);
|
|
3982
|
+
}
|
|
3983
|
+
export function supportedNativeCacheWarmProfiles(driver) {
|
|
3984
|
+
if (isMobileTarget(driver.target) || targetPlatform(driver) === "wasi")
|
|
3985
|
+
return [];
|
|
3986
|
+
return ["runtime", "tls", "dynamic"];
|
|
3987
|
+
}
|
|
3988
|
+
/** Populate expensive native prerequisites against the current compiler,
|
|
3989
|
+
* target, SDK, and environment. The resulting entries use the exact same
|
|
3990
|
+
* identities and validators as ordinary builds; this merely pays their cost
|
|
3991
|
+
* before a developer's first program asks for them. Synthetic link products
|
|
3992
|
+
* are discarded and never enter the complete-executable cache. */
|
|
3993
|
+
export async function warmNativeCaches(options = {}) {
|
|
3994
|
+
clearCcCaches();
|
|
3995
|
+
const cacheRoot = cacheRootDir();
|
|
3996
|
+
if (cacheRoot === null) {
|
|
3997
|
+
throw new Error("the native build cache is disabled (unset SCRIPTC_NO_CACHE and use a non-empty SCRIPTC_CACHE_DIR)");
|
|
3998
|
+
}
|
|
3999
|
+
await ensurePrivateCacheRoot(cacheRoot, process.env["SCRIPTC_CACHE_DIR"] === undefined);
|
|
4000
|
+
const known = new Set(["runtime", "tls", "dynamic"]);
|
|
4001
|
+
for (const profile of options.profiles ?? []) {
|
|
4002
|
+
if (!known.has(profile))
|
|
4003
|
+
throw new Error(`unknown native cache warm profile '${profile}'`);
|
|
4004
|
+
}
|
|
4005
|
+
if (options.profiles?.length === 0)
|
|
4006
|
+
return { cacheRoot, profiles: [] };
|
|
4007
|
+
const mobileTarget = mobileLibraryTarget();
|
|
4008
|
+
if (mobileTarget !== null) {
|
|
4009
|
+
throw new Error(`native cache warming targets executable builds and is unsupported for SCRIPTC_TARGET=${mobileTarget}`);
|
|
4010
|
+
}
|
|
4011
|
+
const driver = resolveCc();
|
|
4012
|
+
const supported = supportedNativeCacheWarmProfiles(driver);
|
|
4013
|
+
if (supported.length === 0) {
|
|
4014
|
+
throw new Error(`native cache warming targets persistently cached native executables and is unsupported for SCRIPTC_TARGET=${driver.target}`);
|
|
4015
|
+
}
|
|
4016
|
+
const profiles = [...new Set(options.profiles ?? supported)];
|
|
4017
|
+
for (const profile of profiles) {
|
|
4018
|
+
if (!supported.includes(profile)) {
|
|
4019
|
+
throw new Error(`native cache warm profile '${profile}' is unsupported for SCRIPTC_TARGET=${driver.target}`);
|
|
4020
|
+
}
|
|
4021
|
+
}
|
|
4022
|
+
const workDir = await mkdtemp(join(tmpdir(), "scriptc-cache-warm-"));
|
|
4023
|
+
const cPath = join(workDir, "warm.c");
|
|
4024
|
+
await writeFile(cPath, "int main(void) { return 0; }\n");
|
|
4025
|
+
const protectedPaths = new Set();
|
|
4026
|
+
try {
|
|
4027
|
+
const results = await Promise.all(profiles.map(async (profile) => {
|
|
4028
|
+
const started = performance.now();
|
|
4029
|
+
await compileCInternal({
|
|
4030
|
+
cPath,
|
|
4031
|
+
outPath: join(workDir, process.platform === "win32" ? `${profile}.exe` : profile),
|
|
4032
|
+
cacheIdentity: "scriptc-native-cache-warm-v1",
|
|
4033
|
+
optimization: options.optimization ?? "release",
|
|
4034
|
+
sanitize: options.sanitize ?? false,
|
|
4035
|
+
...(profile === "tls" ? { fetch: true } : {}),
|
|
4036
|
+
...(profile === "dynamic" ? { dynamic: true } : {}),
|
|
4037
|
+
}, true, protectedPaths);
|
|
4038
|
+
return {
|
|
4039
|
+
profile,
|
|
4040
|
+
elapsedMs: Math.round((performance.now() - started) * 10) / 10,
|
|
4041
|
+
};
|
|
4042
|
+
}));
|
|
4043
|
+
await pruneCache(cacheRoot).catch(() => undefined);
|
|
4044
|
+
if (!(await Promise.all([...protectedPaths].map(fileExists))).every(Boolean)) {
|
|
4045
|
+
throw new Error(`SCRIPTC_CACHE_MAX_MB is too small to retain the requested native cache warm profiles (${profiles.join(", ")})`);
|
|
4046
|
+
}
|
|
4047
|
+
return { cacheRoot, profiles: results };
|
|
4048
|
+
}
|
|
4049
|
+
finally {
|
|
4050
|
+
await rm(workDir, { recursive: true, force: true }).catch(() => undefined);
|
|
4051
|
+
}
|
|
3954
4052
|
}
|
|
3955
|
-
//# sourceMappingURL=
|
|
4053
|
+
//# sourceMappingURL=native-toolchain.js.map
|