-
-
Notifications
You must be signed in to change notification settings - Fork 158
perf(ecs): guarded store follows forwarding edge, inline typeof/typed-array/subclass fast paths (wolf-ecs -16.5% / -20.9%) #8876
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
proggeramlug
wants to merge
15
commits into
PerryTS:main
from
proggeramlug:codex/array-subclass-tail-mutation
Closed
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
e969427
perf: cache owning Uint32Array admissions
15d7673
perf: fast-path Array subclass length misses
bbe2b4c
perf(array): accumulated ECS optimization work through v74
12d4cba
perf(codegen): follow one growth-forwarding edge in the guarded array…
6de5299
perf(array): gate the raw-f64 downgrade note inline; typed-array pre-…
45be16c
perf(codegen): exact inline typeof-number compare; header-branded typ…
ec3b4d8
perf(array): route object receivers to the subclass fast read before …
4bf2c84
perf(codegen): give integer-valued dynamic keys the inline numeric re…
0c00774
test(codegen): update the proven-number strict-eq rooting test to the…
7898bf9
Merge origin/main into codex/array-subclass-tail-mutation; land CI gates
c41343c
ci: ratchet baselines for the file splits, census gate for cache carr…
9a09adf
Merge origin/main (#8878 batch) into codex/array-subclass-tail-mutation
a34c580
perf(runtime): array-read fallback serves object-backed Array subclas…
08b7e02
runtime(array): keyed index paths reload the receiver via across_* (r…
1dd37f9
review: keep the fused u31 push non-reentrant, bound ECS columns, lif…
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| Array and ECS performance. Guarded property-receiver element stores follow one | ||
| growth-forwarding edge inline (the read tier already did), so a field that | ||
| kept a pre-grow forwarding stub (`this.ents[id] = arch`) no longer pays the | ||
| out-of-line extend helper and allocator resolver on every store; the raw-f64 | ||
| downgrade note is gated on the header word already loaded. `typeof x === | ||
| "number"` decides the definitely-Number cases inline, deferring INT32/class | ||
| refs, raw typed-array pointers and the Web Streams id band to the classifier. | ||
| Inline dynamic typed-array reads brand off the `GC_TYPE_TYPED_ARRAY` header | ||
| instead of the evictable 64-slot kind cache. `js_array_get_f64` and the | ||
| typed-feedback array-read fallback route object-backed Array-subclass | ||
| receivers to their dense fast read before the tracked resolver and the by-name | ||
| key path, and an `Any`-typed key that is an integer array index takes the | ||
| inline numeric read tiers (`a[b[i]]`). Object-backed Array subclasses keep | ||
| validated prototype-override reads, and compact guarded specializations are | ||
| pre-statepoint inlined by lowered IR size. | ||
|
|
||
| wolf-ecs (noctjs/ecs-benchmark) on the Mac mini reference box, versus the | ||
| previous retained build: add/remove -18.5% (0.556 → 0.453 ms/op), entity-cycle | ||
| -23.1% (0.499 → 0.384 ms/op); each step 11/11 paired wins, semantics probes | ||
| byte-identical to Node. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
176 changes: 176 additions & 0 deletions
176
crates/perry-codegen/src/codegen/guarded_falsy_default_method_tests.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,176 @@ | ||
| //! Guarded omitted-argument/false-field indexed method versioning. | ||
|
|
||
| use crate::{compile_module, CompileOptions}; | ||
| use perry_hir::types::Type; | ||
| use perry_hir::{Class, ClassField, CompareOp, Expr, Function, Module, Param, Stmt}; | ||
|
|
||
| const ROWS: u32 = 30; | ||
| const INDEX: u32 = 31; | ||
| const DEFER: u32 = 32; | ||
|
|
||
| fn param(id: u32, name: &str) -> Param { | ||
| Param { | ||
| id, | ||
| name: name.to_string(), | ||
| ty: Type::Any, | ||
| default: None, | ||
| decorators: Vec::new(), | ||
| is_rest: false, | ||
| arguments_object: None, | ||
| } | ||
| } | ||
|
|
||
| fn default_get() -> Expr { | ||
| Expr::PropertyGet { | ||
| object: Box::new(Expr::This), | ||
| property: "DEFAULT_DEFER".to_string(), | ||
| byte_offset: 0, | ||
| } | ||
| } | ||
|
|
||
| fn candidate_method() -> Function { | ||
| let mut defer = param(DEFER, "defer"); | ||
| defer.default = Some(default_get()); | ||
| Function { | ||
| id: 40, | ||
| name: "update".to_string(), | ||
| type_params: Vec::new(), | ||
| params: vec![param(ROWS, "rows"), param(INDEX, "index"), defer], | ||
| return_type: Type::Any, | ||
| body: vec![ | ||
| Stmt::If { | ||
| condition: Expr::Compare { | ||
| op: CompareOp::Eq, | ||
| left: Box::new(Expr::LocalGet(DEFER)), | ||
| right: Box::new(Expr::Undefined), | ||
| }, | ||
| then_branch: vec![Stmt::Expr(Expr::LocalSet(DEFER, Box::new(default_get())))], | ||
| else_branch: None, | ||
| }, | ||
| // Nominates the existing nonnegative-index family. | ||
| Stmt::Expr(Expr::IndexGet { | ||
| object: Box::new(Expr::LocalGet(ROWS)), | ||
| index: Box::new(Expr::LocalGet(INDEX)), | ||
| }), | ||
| Stmt::If { | ||
| condition: Expr::LocalGet(DEFER), | ||
| then_branch: vec![Stmt::Return(Some(Expr::Integer(1)))], | ||
| else_branch: Some(vec![Stmt::Return(Some(Expr::Integer(2)))]), | ||
| }, | ||
| ], | ||
| is_async: false, | ||
| is_generator: false, | ||
| is_strict: true, | ||
| is_exported: false, | ||
| captures: Vec::new(), | ||
| decorators: Vec::new(), | ||
| was_plain_async: false, | ||
| was_unrolled: false, | ||
| } | ||
| } | ||
|
|
||
| fn fixture(method: Function) -> Module { | ||
| let class = Class { | ||
| id: 41, | ||
| name: "Store".to_string(), | ||
| type_params: Vec::new(), | ||
| extends: None, | ||
| extends_name: None, | ||
| native_extends: None, | ||
| extends_expr: None, | ||
| heritage_lexically_shadowed: false, | ||
| fields: vec![ClassField { | ||
| name: "DEFAULT_DEFER".to_string(), | ||
| key_expr: None, | ||
| ty: Type::Any, | ||
| init: Some(Expr::Bool(false)), | ||
| is_private: false, | ||
| is_readonly: false, | ||
| decorators: Vec::new(), | ||
| }], | ||
| constructor: None, | ||
| methods: vec![method], | ||
| getters: Vec::new(), | ||
| setters: Vec::new(), | ||
| static_accessor_names: Vec::new(), | ||
| static_accessor_fn_ids: Vec::new(), | ||
| computed_members: Vec::new(), | ||
| static_fields: Vec::new(), | ||
| static_methods: Vec::new(), | ||
| decorators: Vec::new(), | ||
| is_exported: false, | ||
| aliases: Vec::new(), | ||
| is_nested: false, | ||
| alloc_width_hint: 0, | ||
| specialized_from: None, | ||
| }; | ||
| let mut module = Module::new("guarded_falsy_default_method.ts"); | ||
| module.classes = vec![class]; | ||
| module | ||
| } | ||
|
|
||
| fn emit(method: Function) -> String { | ||
| let opts = CompileOptions { | ||
| emit_ir_only: true, | ||
| output_type: "executable".to_string(), | ||
| ..Default::default() | ||
| }; | ||
| String::from_utf8(compile_module(&fixture(method), opts).expect("fixture compiles")) | ||
| .expect("LLVM IR is UTF-8") | ||
| } | ||
|
|
||
| fn function_body<'a>(ir: &'a str, marker: &str) -> &'a str { | ||
| let start = ir | ||
| .match_indices("define ") | ||
| .find(|(index, _)| { | ||
| let end = ir[*index..] | ||
| .find('\n') | ||
| .map(|offset| index + offset) | ||
| .unwrap_or(ir.len()); | ||
| ir[*index..end].contains(marker) | ||
| }) | ||
| .map(|(index, _)| index) | ||
| .unwrap_or_else(|| panic!("missing function containing {marker}:\n{ir}")); | ||
| let end = ir[start..] | ||
| .find("\n}") | ||
| .map(|offset| start + offset) | ||
| .expect("function terminator"); | ||
| &ir[start..end] | ||
| } | ||
|
|
||
| #[test] | ||
| fn wrapper_proves_live_false_field_and_clone_erases_default_and_branch() { | ||
| let ir = emit(candidate_method()); | ||
| let base = "perry_method_guarded_falsy_default_method_ts__Store__update"; | ||
| let index = format!("{base}$idx_u31_{INDEX}"); | ||
| let specialized = format!("{index}$default_false2"); | ||
| let wrapper = function_body(&ir, &format!("@{base}(")); | ||
| let ordinary = function_body(&ir, &format!("@{index}(")); | ||
| let false_default = function_body(&ir, &format!("@{specialized}(")); | ||
|
|
||
| assert!(wrapper.contains(&crate::nanbox::TAG_UNDEFINED_I64.to_string())); | ||
| assert!(wrapper.contains(&crate::nanbox::TAG_FALSE_I64.to_string())); | ||
| assert!(wrapper.contains("load i32, ptr @perry_class_shape_id_")); | ||
| assert!(wrapper.contains(&format!("@{specialized}("))); | ||
| assert!(wrapper.contains(&format!("@{index}("))); | ||
| assert!(ordinary.contains("@js_is_truthy("), "{ordinary}"); | ||
| assert!( | ||
| !false_default.contains("@js_is_truthy("), | ||
| "the guarded clone retained the known-false condition:\n{false_default}" | ||
| ); | ||
| assert!( | ||
| !false_default.contains("class_field_get"), | ||
| "the guarded clone reevaluated its already-proved default field:\n{false_default}" | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn arbitrary_parameter_use_rejects_the_clone() { | ||
| let mut method = candidate_method(); | ||
| method.body.push(Stmt::Return(Some(Expr::LocalGet(DEFER)))); | ||
| let ir = emit(method); | ||
| assert!( | ||
| !ir.contains("$default_false"), | ||
| "a parameter whose actual false value remains observable was specialized" | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Do not publish the semantics-pass claim yet.
The fragment says “semantics probes byte-identical to Node”, but the PR objectives record a reproducible failure in
strict_eq_reuses_a_non_pointer_left_operand_across_an_allocating_right_operandon this branch. Update the claim after the test passes on the rebased branch, or report the verified result.🤖 Prompt for AI Agents