Skip to content

fix: address #945 scalar method allocation regression (rebased from #980)#1030

Merged
proggeramlug merged 10 commits into
mainfrom
fix-945-scalar-method-allocation
May 18, 2026
Merged

fix: address #945 scalar method allocation regression (rebased from #980)#1030
proggeramlug merged 10 commits into
mainfrom
fix-945-scalar-method-allocation

Conversation

@proggeramlug
Copy link
Copy Markdown
Contributor

Rebased version of @andrewtdiz's #980 onto current main. Co-authored
content is unchanged; conflicts in `type_analysis.rs` and
`lower_call.rs` resolved by keeping the new `is_global_constructor_expr`
helper (functionally identical to the existing `is_global_builtin_named`).

Validation:

  • `cargo build --release -p perry-runtime -p perry-stdlib -p perry` clean.
  • `scripts/run_native_no_fallback_tests.sh` — 35 passed, 0 failed.

Closes #945. Supersedes #980. Credits @andrewtdiz.

codex added 10 commits May 18, 2026 13:33
Remove the fallback codegen method-call scalar summary so method-call scalar replacement only happens after HIR exact-receiver inlining has proven the replacement safe. Extend the #945 IR guard to prove the positive hot-loop shape still scalarizes while unsafe own/prototype mutation shapes still allocate and dispatch.
Add the native extension symbol for zlib.createBrotliDecompress so optimized zlib links resolve the same feature-check shim already exposed by stdlib.
test_ramda_user_import exercises a user-installed ramda namespace import through the V8 fallback. The compile-smoke runner does not install optional package fixtures, so the strict unresolved-namespace diagnostic is expected in that job.
@proggeramlug proggeramlug merged commit 9a9a233 into main May 18, 2026
3 of 9 checks passed
@proggeramlug proggeramlug deleted the fix-945-scalar-method-allocation branch May 18, 2026 11:51
proggeramlug added a commit that referenced this pull request May 18, 2026
Same drive-by fix shipping in #1031 and #1032: origin/main contains
unresolved git conflict markers in perry-codegen/src/{lower_call.rs,
type_analysis.rs} from the #1030 merge over #1008's resolution. Picks
the canonical is_global_constructor_expr helper per maintainer pref.
proggeramlug added a commit that referenced this pull request May 18, 2026
…Page/pdfSave)

Adds a new `perry-ext-pdf` crate exposing five FFI entry points
that let Perry programs build and save PDF documents at runtime.
This is the producer half of issue #516; the consumer half
(PdfView widget) already shipped in perry-ui-{ios,visionos,macos}
+ stubs on the other platform crates.

TypeScript surface (`@perryts/pdf`):
  createPdf(opts) -> number     // handle
  pdfAddText(pdf, text, x, y, fontSize?)
  pdfAddLine(pdf, x1, y1, x2, y2)
  pdfNewPage(pdf)
  pdfSave(pdf)

Coordinates: PDF points (1/72"), origin bottom-left, default
US Letter (612 x 792 pt).

Implementation:
  - Wraps the pure-Rust `printpdf` 0.9 crate with
    `default-features = false` (drops the heavy `html` /
    azul-layout feature set; we only need text + lines).
  - Text uses Helvetica (one of the 14 PDF built-in fonts), so no
    TTF embedding / font subsetting is required.
  - Process-global Mutex<HashMap<i64, OpenDoc>> for handle state;
    1-based handle counter, freed on pdfSave, warn-once on stale.
  - Hand-rolled tiny JSON reader for createPdf's options object so
    the crate doesn't have to pull in serde_json / serde_derive.

Wiring (cookie-cutter mirror of PR #1028 / perry-ext-google-auth):
  - new crate: crates/perry-ext-pdf/{Cargo.toml,src/lib.rs}
  - workspace members + dep entries in Cargo.toml
  - well_known_bindings.toml: @perryts/pdf -> libperry_ext_pdf.a
  - perry-api-manifest: module listed, 5 manifest entries
  - perry-codegen: 5 NATIVE_MODULE_TABLE rows + 5 runtime_decls
    (createPdf NR_PTR, four mutators NR_VOID)
  - types/perry/pdf/{index.d.ts,package.json}
  - test-files/test_pdf_create_smoke.ts smoke test

Out-of-scope follow-ups (deliberate v1 limits): image embedding,
custom font loading, encryption, forms, annotations beyond
text+lines.

Also resolves two stray HEAD/incoming merge-conflict markers that
landed in main commit 9a9a233 — one in lower_call.rs and two in
type_analysis.rs (Promise.* dispatch). Both are resolved by
keeping the richer HEAD-side comment and switching the call to
the post-#1030 canonical `is_global_constructor_expr` helper, so
the build is green on this branch. The legacy
`is_global_builtin_named` is left in place with #[allow(dead_code)]
because cleaning it up is outside #516's scope.

Validation:
  - `cargo build --release -p perry-runtime -p perry-stdlib -p perry
     -p perry-ext-pdf` succeeds.
  - `cargo test -p perry-ext-pdf` -> 3 unit tests pass.
  - `cargo test -p perry-codegen --test manifest_consistency` ->
     4/4 pass (every dispatch row has a manifest entry; manifest
     param shapes match dispatch; every well-known binding has a
     manifest entry; every native module has at least one entry).
  - `cargo test -p perry --bins well_known` -> 6/6 pass
    (every_entry_references_a_workspace_crate confirms the new
     bindings entry resolves).
  - Smoke test compiles + runs end-to-end; `file` reports a valid
    2-page PDF document at /tmp/perry_pdf_smoke.pdf (1.7 KB).

Closes #516.
proggeramlug added a commit that referenced this pull request May 18, 2026
…ary` archives

Adds a supply-chain lockfile (`<project_root>/perry.lock`) that pins
the SHA-256 of every prebuilt archive Perry consumes through
`perry.nativeLibrary.targets.<key>.prebuilt`. A swapped or tampered
`.a` / `.lib` / `.dylib` file is otherwise undetectable — Perry would
hand whatever's on disk to the linker.

Three operating modes:
- Default: write-missing, verify-present. Mismatch fails the build
  with an actionable diagnostic naming the package and suggesting
  `perry lock --update <pkg>`.
- `perry lock --update <pkg>`: deliberately refresh the named
  package(s)' hashes after an intentional dep upgrade.
- `perry lock --frozen`: CI verification only. Refuses to write to
  `perry.lock`; missing AND mismatched entries both fail.

Per-target archives accumulate into `sha256_per_target` so a single
package can lock its `macos-arm64`, `linux-x86_64`, `ios`, … archives
all at once.

Wired into `build_and_run_link` so every backend (LLVM / WASM / ArkTS
/ HarmonyOS / Glance / SwiftUI / JS) hits the same gate before any
object touches the linker. Standalone `perry lock` discovers archives
by scanning `node_modules/*` for `perry.nativeLibrary` blocks.

24 new tests cover the verify-write-update-frozen matrix, the
known-vector SHA-256, deterministic on-disk output, per-target
accumulation, and the helpers that derive lockfile target keys.

Also resolves pre-existing unresolved-conflict markers in
`perry-codegen/src/{lower_call.rs,type_analysis.rs}` (introduced by
merge of #1030 / #1008) — the build wouldn't compile against main
without them; resolution matches the maintainer's in-flight fix
(switch to the `is_global_constructor_expr` helper that the rest of
the codegen uses post-#1030).

Closes #498.
proggeramlug added a commit that referenced this pull request May 18, 2026
…Page/pdfSave)

Adds a new `perry-ext-pdf` crate exposing five FFI entry points
that let Perry programs build and save PDF documents at runtime.
This is the producer half of issue #516; the consumer half
(PdfView widget) already shipped in perry-ui-{ios,visionos,macos}
+ stubs on the other platform crates.

TypeScript surface (`@perryts/pdf`):
  createPdf(opts) -> number     // handle
  pdfAddText(pdf, text, x, y, fontSize?)
  pdfAddLine(pdf, x1, y1, x2, y2)
  pdfNewPage(pdf)
  pdfSave(pdf)

Coordinates: PDF points (1/72"), origin bottom-left, default
US Letter (612 x 792 pt).

Implementation:
  - Wraps the pure-Rust `printpdf` 0.9 crate with
    `default-features = false` (drops the heavy `html` /
    azul-layout feature set; we only need text + lines).
  - Text uses Helvetica (one of the 14 PDF built-in fonts), so no
    TTF embedding / font subsetting is required.
  - Process-global Mutex<HashMap<i64, OpenDoc>> for handle state;
    1-based handle counter, freed on pdfSave, warn-once on stale.
  - Hand-rolled tiny JSON reader for createPdf's options object so
    the crate doesn't have to pull in serde_json / serde_derive.

Wiring (cookie-cutter mirror of PR #1028 / perry-ext-google-auth):
  - new crate: crates/perry-ext-pdf/{Cargo.toml,src/lib.rs}
  - workspace members + dep entries in Cargo.toml
  - well_known_bindings.toml: @perryts/pdf -> libperry_ext_pdf.a
  - perry-api-manifest: module listed, 5 manifest entries
  - perry-codegen: 5 NATIVE_MODULE_TABLE rows + 5 runtime_decls
    (createPdf NR_PTR, four mutators NR_VOID)
  - types/perry/pdf/{index.d.ts,package.json}
  - test-files/test_pdf_create_smoke.ts smoke test

Out-of-scope follow-ups (deliberate v1 limits): image embedding,
custom font loading, encryption, forms, annotations beyond
text+lines.

Also resolves two stray HEAD/incoming merge-conflict markers that
landed in main commit 9a9a233 — one in lower_call.rs and two in
type_analysis.rs (Promise.* dispatch). Both are resolved by
keeping the richer HEAD-side comment and switching the call to
the post-#1030 canonical `is_global_constructor_expr` helper, so
the build is green on this branch. The legacy
`is_global_builtin_named` is left in place with #[allow(dead_code)]
because cleaning it up is outside #516's scope.

Validation:
  - `cargo build --release -p perry-runtime -p perry-stdlib -p perry
     -p perry-ext-pdf` succeeds.
  - `cargo test -p perry-ext-pdf` -> 3 unit tests pass.
  - `cargo test -p perry-codegen --test manifest_consistency` ->
     4/4 pass (every dispatch row has a manifest entry; manifest
     param shapes match dispatch; every well-known binding has a
     manifest entry; every native module has at least one entry).
  - `cargo test -p perry --bins well_known` -> 6/6 pass
    (every_entry_references_a_workspace_crate confirms the new
     bindings entry resolves).
  - Smoke test compiles + runs end-to-end; `file` reports a valid
    2-page PDF document at /tmp/perry_pdf_smoke.pdf (1.7 KB).

Closes #516.
proggeramlug added a commit that referenced this pull request May 18, 2026
…Page/pdfSave)

Adds a new `perry-ext-pdf` crate exposing five FFI entry points
that let Perry programs build and save PDF documents at runtime.
This is the producer half of issue #516; the consumer half
(PdfView widget) already shipped in perry-ui-{ios,visionos,macos}
+ stubs on the other platform crates.

TypeScript surface (`@perryts/pdf`):
  createPdf(opts) -> number     // handle
  pdfAddText(pdf, text, x, y, fontSize?)
  pdfAddLine(pdf, x1, y1, x2, y2)
  pdfNewPage(pdf)
  pdfSave(pdf)

Coordinates: PDF points (1/72"), origin bottom-left, default
US Letter (612 x 792 pt).

Implementation:
  - Wraps the pure-Rust `printpdf` 0.9 crate with
    `default-features = false` (drops the heavy `html` /
    azul-layout feature set; we only need text + lines).
  - Text uses Helvetica (one of the 14 PDF built-in fonts), so no
    TTF embedding / font subsetting is required.
  - Process-global Mutex<HashMap<i64, OpenDoc>> for handle state;
    1-based handle counter, freed on pdfSave, warn-once on stale.
  - Hand-rolled tiny JSON reader for createPdf's options object so
    the crate doesn't have to pull in serde_json / serde_derive.

Wiring (cookie-cutter mirror of PR #1028 / perry-ext-google-auth):
  - new crate: crates/perry-ext-pdf/{Cargo.toml,src/lib.rs}
  - workspace members + dep entries in Cargo.toml
  - well_known_bindings.toml: @perryts/pdf -> libperry_ext_pdf.a
  - perry-api-manifest: module listed, 5 manifest entries
  - perry-codegen: 5 NATIVE_MODULE_TABLE rows + 5 runtime_decls
    (createPdf NR_PTR, four mutators NR_VOID)
  - types/perry/pdf/{index.d.ts,package.json}
  - test-files/test_pdf_create_smoke.ts smoke test

Out-of-scope follow-ups (deliberate v1 limits): image embedding,
custom font loading, encryption, forms, annotations beyond
text+lines.

Also resolves two stray HEAD/incoming merge-conflict markers that
landed in main commit 9a9a233 — one in lower_call.rs and two in
type_analysis.rs (Promise.* dispatch). Both are resolved by
keeping the richer HEAD-side comment and switching the call to
the post-#1030 canonical `is_global_constructor_expr` helper, so
the build is green on this branch. The legacy
`is_global_builtin_named` is left in place with #[allow(dead_code)]
because cleaning it up is outside #516's scope.

Validation:
  - `cargo build --release -p perry-runtime -p perry-stdlib -p perry
     -p perry-ext-pdf` succeeds.
  - `cargo test -p perry-ext-pdf` -> 3 unit tests pass.
  - `cargo test -p perry-codegen --test manifest_consistency` ->
     4/4 pass (every dispatch row has a manifest entry; manifest
     param shapes match dispatch; every well-known binding has a
     manifest entry; every native module has at least one entry).
  - `cargo test -p perry --bins well_known` -> 6/6 pass
    (every_entry_references_a_workspace_crate confirms the new
     bindings entry resolves).
  - Smoke test compiles + runs end-to-end; `file` reports a valid
    2-page PDF document at /tmp/perry_pdf_smoke.pdf (1.7 KB).

Closes #516.
proggeramlug added a commit that referenced this pull request May 18, 2026
…Page/pdfSave) (#1031)

Adds a new `perry-ext-pdf` crate exposing five FFI entry points
that let Perry programs build and save PDF documents at runtime.
This is the producer half of issue #516; the consumer half
(PdfView widget) already shipped in perry-ui-{ios,visionos,macos}
+ stubs on the other platform crates.

TypeScript surface (`@perryts/pdf`):
  createPdf(opts) -> number     // handle
  pdfAddText(pdf, text, x, y, fontSize?)
  pdfAddLine(pdf, x1, y1, x2, y2)
  pdfNewPage(pdf)
  pdfSave(pdf)

Coordinates: PDF points (1/72"), origin bottom-left, default
US Letter (612 x 792 pt).

Implementation:
  - Wraps the pure-Rust `printpdf` 0.9 crate with
    `default-features = false` (drops the heavy `html` /
    azul-layout feature set; we only need text + lines).
  - Text uses Helvetica (one of the 14 PDF built-in fonts), so no
    TTF embedding / font subsetting is required.
  - Process-global Mutex<HashMap<i64, OpenDoc>> for handle state;
    1-based handle counter, freed on pdfSave, warn-once on stale.
  - Hand-rolled tiny JSON reader for createPdf's options object so
    the crate doesn't have to pull in serde_json / serde_derive.

Wiring (cookie-cutter mirror of PR #1028 / perry-ext-google-auth):
  - new crate: crates/perry-ext-pdf/{Cargo.toml,src/lib.rs}
  - workspace members + dep entries in Cargo.toml
  - well_known_bindings.toml: @perryts/pdf -> libperry_ext_pdf.a
  - perry-api-manifest: module listed, 5 manifest entries
  - perry-codegen: 5 NATIVE_MODULE_TABLE rows + 5 runtime_decls
    (createPdf NR_PTR, four mutators NR_VOID)
  - types/perry/pdf/{index.d.ts,package.json}
  - test-files/test_pdf_create_smoke.ts smoke test

Out-of-scope follow-ups (deliberate v1 limits): image embedding,
custom font loading, encryption, forms, annotations beyond
text+lines.

Also resolves two stray HEAD/incoming merge-conflict markers that
landed in main commit 9a9a233 — one in lower_call.rs and two in
type_analysis.rs (Promise.* dispatch). Both are resolved by
keeping the richer HEAD-side comment and switching the call to
the post-#1030 canonical `is_global_constructor_expr` helper, so
the build is green on this branch. The legacy
`is_global_builtin_named` is left in place with #[allow(dead_code)]
because cleaning it up is outside #516's scope.

Validation:
  - `cargo build --release -p perry-runtime -p perry-stdlib -p perry
     -p perry-ext-pdf` succeeds.
  - `cargo test -p perry-ext-pdf` -> 3 unit tests pass.
  - `cargo test -p perry-codegen --test manifest_consistency` ->
     4/4 pass (every dispatch row has a manifest entry; manifest
     param shapes match dispatch; every well-known binding has a
     manifest entry; every native module has at least one entry).
  - `cargo test -p perry --bins well_known` -> 6/6 pass
    (every_entry_references_a_workspace_crate confirms the new
     bindings entry resolves).
  - Smoke test compiles + runs end-to-end; `file` reports a valid
    2-page PDF document at /tmp/perry_pdf_smoke.pdf (1.7 KB).

Closes #516.
proggeramlug added a commit that referenced this pull request May 18, 2026
…ary` archives

Adds a supply-chain lockfile (`<project_root>/perry.lock`) that pins
the SHA-256 of every prebuilt archive Perry consumes through
`perry.nativeLibrary.targets.<key>.prebuilt`. A swapped or tampered
`.a` / `.lib` / `.dylib` file is otherwise undetectable — Perry would
hand whatever's on disk to the linker.

Three operating modes:
- Default: write-missing, verify-present. Mismatch fails the build
  with an actionable diagnostic naming the package and suggesting
  `perry lock --update <pkg>`.
- `perry lock --update <pkg>`: deliberately refresh the named
  package(s)' hashes after an intentional dep upgrade.
- `perry lock --frozen`: CI verification only. Refuses to write to
  `perry.lock`; missing AND mismatched entries both fail.

Per-target archives accumulate into `sha256_per_target` so a single
package can lock its `macos-arm64`, `linux-x86_64`, `ios`, … archives
all at once.

Wired into `build_and_run_link` so every backend (LLVM / WASM / ArkTS
/ HarmonyOS / Glance / SwiftUI / JS) hits the same gate before any
object touches the linker. Standalone `perry lock` discovers archives
by scanning `node_modules/*` for `perry.nativeLibrary` blocks.

24 new tests cover the verify-write-update-frozen matrix, the
known-vector SHA-256, deterministic on-disk output, per-target
accumulation, and the helpers that derive lockfile target keys.

Also resolves pre-existing unresolved-conflict markers in
`perry-codegen/src/{lower_call.rs,type_analysis.rs}` (introduced by
merge of #1030 / #1008) — the build wouldn't compile against main
without them; resolution matches the maintainer's in-flight fix
(switch to the `is_global_constructor_expr` helper that the rest of
the codegen uses post-#1030).

Closes #498.
proggeramlug added a commit that referenced this pull request May 18, 2026
…ary` archives (#1032)

Adds a supply-chain lockfile (`<project_root>/perry.lock`) that pins
the SHA-256 of every prebuilt archive Perry consumes through
`perry.nativeLibrary.targets.<key>.prebuilt`. A swapped or tampered
`.a` / `.lib` / `.dylib` file is otherwise undetectable — Perry would
hand whatever's on disk to the linker.

Three operating modes:
- Default: write-missing, verify-present. Mismatch fails the build
  with an actionable diagnostic naming the package and suggesting
  `perry lock --update <pkg>`.
- `perry lock --update <pkg>`: deliberately refresh the named
  package(s)' hashes after an intentional dep upgrade.
- `perry lock --frozen`: CI verification only. Refuses to write to
  `perry.lock`; missing AND mismatched entries both fail.

Per-target archives accumulate into `sha256_per_target` so a single
package can lock its `macos-arm64`, `linux-x86_64`, `ios`, … archives
all at once.

Wired into `build_and_run_link` so every backend (LLVM / WASM / ArkTS
/ HarmonyOS / Glance / SwiftUI / JS) hits the same gate before any
object touches the linker. Standalone `perry lock` discovers archives
by scanning `node_modules/*` for `perry.nativeLibrary` blocks.

24 new tests cover the verify-write-update-frozen matrix, the
known-vector SHA-256, deterministic on-disk output, per-target
accumulation, and the helpers that derive lockfile target keys.

Also resolves pre-existing unresolved-conflict markers in
`perry-codegen/src/{lower_call.rs,type_analysis.rs}` (introduced by
merge of #1030 / #1008) — the build wouldn't compile against main
without them; resolution matches the maintainer's in-flight fix
(switch to the `is_global_constructor_expr` helper that the rest of
the codegen uses post-#1030).

Closes #498.
proggeramlug added a commit that referenced this pull request May 19, 2026
…HIR collapse (#1064)

`const [a, b] = await Promise.all([fn1(), fn2()])` returned undefined
under Perry HEAD=13dc587a (v0.5.1004, pre-#1007). Bisected: the bug
disappears at f78361f (#1007 — restore built-in static identity in
member position), which collapses `Promise.all` from
`PropertyGet { PropertyGet { GlobalGet(0), "Promise" }, "all" }` down
to `PropertyGet { GlobalGet(0), "all" }`. The codegen Promise-static
dispatch at that commit only matched the bare `GlobalGet(_)` receiver,
so the deeper shape fell through to `js_native_call_method("all", …)`
against the Promise constructor — returning 0.0 (no such method on a
function value) and breaking the destructure.

Current main is doubly-protected: (1) the #1007 HIR collapse keeps
the shallow shape, and (2) PR #1030 introduced `is_global_constructor_expr`,
which accepts both shapes at the codegen dispatch. No code change is
required — the bug is fixed in current main as a side effect of
#1007 + #1030. This test pins the byte-for-byte node match so a
future regression of either piece (the HIR collapse or the helper)
fails immediately. Closes #1013.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Performance Issue: Object allocation via new is 100x slower than Node.js in PerryTS

2 participants