Skip to content

Commit 1dd7254

Browse files
committed
plan
1 parent 97f83bc commit 1dd7254

File tree

4 files changed

+176
-441
lines changed

4 files changed

+176
-441
lines changed

module/move/unilang/task/phase4.md

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
2+
# Task Plan: Phase 4 - Zero-Overhead Static Command Registry (Revised & Elaborated)
3+
4+
### Goal
5+
* To implement Phase 4 of the `unilang` roadmap, focusing on the mandatory performance non-functional requirement for a zero-overhead static command system. This will be achieved by creating a hybrid command registry that uses a Perfect Hash Function (PHF) map for all compile-time commands, ensuring instantaneous startup and sub-millisecond command resolution.
6+
7+
### Ubiquitous Language (Vocabulary)
8+
* **Static Command:** A command whose definition is known at compile-time.
9+
* **`StaticCommandDefinition`:** A `const`-compatible representation of a command, using `&'static str` and `&'static [...]` instead of `String` and `Vec`.
10+
* **Runtime Command:** A command registered dynamically after the application has started.
11+
* **PHF (Perfect Hash Function):** A hash function that maps a static set of keys to a set of integers with no collisions.
12+
* **Static Registry:** The part of the `CommandRegistry` that stores static commands in a PHF map, generated at compile-time.
13+
* **Dynamic Registry:** The part of the `CommandRegistry` that stores runtime commands in a standard `HashMap`.
14+
* **Hybrid Registry:** The final `CommandRegistry` design that combines the static PHF and the dynamic `HashMap`.
15+
16+
### Progress
17+
* **Roadmap Milestone:** Phase 4: Zero-Overhead Static Command Registry
18+
* **Primary Editable Crate:** `module/move/unilang`
19+
* **Overall Progress:** 0/6 increments complete
20+
* **Increment Status:**
21+
* ⚫ Increment 1: Project Setup and `StaticCommandDefinition`
22+
* ⚫ Increment 2: Implement PHF Generation Logic in `build.rs`
23+
* ⚫ Increment 3: Refactor `CommandRegistry` to a Hybrid Model
24+
* ⚫ Increment 4: Create Performance Stress Test Harness
25+
* ⚫ Increment 5: Implement and Run Performance Assertions
26+
* ⚫ Increment 6: Finalization
27+
28+
### Permissions & Boundaries
29+
* **Mode:** code
30+
* **Run workspace-wise commands:** false
31+
* **Add transient comments:** true
32+
* **Additional Editable Crates:** None
33+
34+
### Relevant Context
35+
* Control Files to Reference:
36+
* `module/move/unilang/spec.md`
37+
* `module/move/unilang/roadmap.md`
38+
* Files to Include (for AI's reference):
39+
* `module/move/unilang/src/registry.rs`
40+
* `module/move/unilang/src/data.rs`
41+
* `module/move/unilang/src/semantic.rs`
42+
* `module/move/unilang/Cargo.toml`
43+
44+
### Expected Behavior Rules / Specifications
45+
* **NFR-Performance:** For an application with 1,000+ static commands, the framework must introduce zero runtime overhead for command registration. Startup time must not be impacted by the number of static commands. The p99 latency for resolving a command `FullName` must be less than 1 millisecond.
46+
* The `CommandRegistry` must function as a hybrid, seamlessly resolving both compile-time (static) and run-time (dynamic) commands, with static lookups taking precedence.
47+
48+
### Tests
49+
| Test ID | Status | Notes |
50+
|---|---|---|
51+
| | | |
52+
53+
### Crate Conformance Check Procedure
54+
* Run `timeout 180 cargo test -p unilang -- --nocapture` and verify it passes with no warnings.
55+
* Run `timeout 180 cargo clippy -p unilang -- -D warnings -A clippy::too-many-lines` and verify it passes with no warnings.
56+
57+
### Increments
58+
59+
##### Increment 1: Project Setup and `StaticCommandDefinition`
60+
* **Goal:** To prepare the `unilang` crate for build-time code generation by adding dependencies, creating the `build.rs` script, and defining the necessary `const`-compatible static data structures.
61+
* **Specification Reference:** `roadmap.md` M4.1
62+
* **Steps:**
63+
1. **Read `Cargo.toml`:** Use `read_file` to load `module/move/unilang/Cargo.toml`.
64+
2. **Add Dependencies:** Use `insert_content` to add `phf = { version = "0.11", features = ["macros"] }` to the `[dependencies]` section.
65+
3. **Add Build Dependencies:** Use `insert_content` to add a `[build-dependencies]` section with `phf_codegen = "0.11"`, `serde = "1.0"`, and `serde_yaml = "0.9"`.
66+
4. **Create `build.rs`:** Use `write_to_file` to create `module/move/unilang/build.rs` with the initial content:
67+
```rust
68+
fn main() {
69+
println!("cargo:rerun-if-changed=build.rs");
70+
}
71+
```
72+
5. **Create Static Data Models:** Use `write_to_file` to create a new file `module/move/unilang/src/static_data.rs`. This file will contain `const`-compatible versions of the data models.
73+
```rust
74+
// module/move/unilang/src/static_data.rs
75+
//! Contains `const`-compatible data structures for static command definitions.
76+
77+
// Note: These structs will be expanded in the build script and here.
78+
// For now, we just create the file.
79+
```
80+
6. **Declare Module:** Use `insert_content` in `module/move/unilang/src/lib.rs` to add `pub mod static_data;`.
81+
7. **Perform Increment Verification.**
82+
* **Increment Verification:**
83+
1. Execute `timeout 180 cargo build -p unilang`. The build must complete successfully, confirming the `build.rs` script is recognized and dependencies are resolved.
84+
* **Commit Message:** "chore(unilang): Set up build script and static data models for PHF generation"
85+
86+
##### Increment 2: Implement PHF Generation Logic in `build.rs`
87+
* **Goal:** To implement the core logic in `build.rs` that reads a manifest of static commands and generates a Rust source file containing a PHF map and all associated `const` data.
88+
* **Specification Reference:** `roadmap.md` M4.2
89+
* **Steps:**
90+
1. **Create Manifest:** Use `write_to_file` to create `module/move/unilang/unilang.commands.yaml` with a few static command definitions.
91+
2. **Define Static Structs:** In `build.rs`, define the `StaticCommandDefinition` and related structs. These need to be `serde::Deserialize` for parsing the YAML and must be `const`-compatible for code generation. This is a known challenge; the approach will be to deserialize into temporary structs and then generate code for the `const` static structs.
92+
3. **Implement Build Logic:** Update `build.rs` to:
93+
a. Read and parse `unilang.commands.yaml` into `Vec<CommandDefinition>` (the existing, dynamic struct).
94+
b. Determine the output path: `let path = Path::new(&env::var("OUT_DIR").unwrap()).join("static_commands.rs");`.
95+
c. Open this path for writing.
96+
d. Write `use` statements for `phf` and the static data models.
97+
e. Iterate through the parsed definitions and generate `const` data as a string (e.g., `const CMD_GREET_NAME: &'static str = "greet";`).
98+
f. Generate `const` instances of the `StaticCommandDefinition` structs.
99+
g. Generate the `phf_codegen::Map` builder code, mapping full command names to the `const` structs.
100+
h. Write the final `phf::Map` to the file.
101+
i. Add `println!("cargo:rerun-if-changed=unilang.commands.yaml");`.
102+
4. **Perform Increment Verification.**
103+
* **Increment Verification:**
104+
1. Execute `timeout 180 cargo build -p unilang`.
105+
2. Use `read_file` to inspect the generated `target/debug/build/unilang-*/out/static_commands.rs`. It must contain valid Rust code defining `const` data and a `phf::Map`.
106+
* **Commit Message:** "feat(unilang): Implement build-time generation of PHF for static commands"
107+
108+
##### Increment 3: Refactor `CommandRegistry` to a Hybrid Model
109+
* **Goal:** To integrate the generated static PHF map into the runtime `CommandRegistry` and adapt all lookup logic to use this new hybrid structure.
110+
* **Specification Reference:** `roadmap.md` M4.3
111+
* **Steps:**
112+
1. **Update `static_data.rs`:** Populate `module/move/unilang/src/static_data.rs` with the final `StaticCommandDefinition` and related structs, making them public. Add an implementation of `From<&'static StaticCommandDefinition>` for `CommandDefinition` to convert from the static to the dynamic version.
113+
2. **Modify `registry.rs`:**
114+
a. Use `include!(concat!(env!("OUT_DIR"), "/static_commands.rs"));` at the top level.
115+
b. Change the `CommandRegistry` struct: rename `commands` to `dynamic_commands`.
116+
c. Create a new public method `command(&self, name: &str) -> Option<CommandDefinition>`.
117+
d. Implement the hybrid lookup logic in `command()`: check `STATIC_COMMANDS` first, convert the result to `CommandDefinition`, and if not found, fall back to `dynamic_commands`.
118+
3. **Update `SemanticAnalyzer`:** In `semantic.rs`, change the lookup logic to use the new `registry.command()` method.
119+
4. **Update Tests:** Modify all tests that interact with the registry (e.g., `full_pipeline_test.rs`, `command_loader_test.rs`) to account for the new hybrid lookup. Some tests might need to register commands dynamically to test that part of the registry.
120+
5. **Perform Increment Verification.**
121+
* **Increment Verification:**
122+
1. Perform the Crate Conformance Check. All existing tests must pass.
123+
* **Commit Message:** "refactor(unilang): Integrate static PHF map into a hybrid CommandRegistry"
124+
125+
##### Increment 4: Create Performance Stress Test Harness
126+
* **Goal:** To create the necessary infrastructure for a performance stress test, including a mechanism to generate a large number of static commands and a dedicated binary to test them.
127+
* **Specification Reference:** `roadmap.md` M4.4.1, M4.4.2
128+
* **Steps:**
129+
1. **Create Test File:** Use `write_to_file` to create `module/move/unilang/tests/inc/phase4/performance_stress_test.rs`.
130+
2. **Create Test Binary:** Use `write_to_file` to create `module/move/unilang/tests/stress_test_bin.rs`.
131+
3. **Implement YAML Generator:** In `performance_stress_test.rs`, write a function `generate_stress_yaml(count: usize) -> String` that creates a YAML string with `count` unique command definitions.
132+
4. **Implement Test Binary Logic:** In `stress_test_bin.rs`, write a `main` function that initializes the `CommandRegistry`, performs a large number of random lookups against the static commands, measures the p99 latency using a library like `hdrhistogram`, and prints the result to stdout before printing "Ready".
133+
5. **Orchestrate the Test:** In `performance_stress_test.rs`, the main test function will:
134+
a. Set an environment variable `UNILANG_STATIC_COMMANDS_PATH` to a path in the `target` directory.
135+
b. Call `generate_stress_yaml(1000)` and write the result to that path.
136+
c. Modify `build.rs` to read from `UNILANG_STATIC_COMMANDS_PATH` if it is set.
137+
6. **Perform Increment Verification.**
138+
* **Increment Verification:**
139+
1. The `performance_stress_test.rs` test should successfully generate the large YAML file.
140+
2. Execute `cargo test --test stress_test_bin --no-run`. The binary must compile successfully against the large generated PHF.
141+
* **Commit Message:** "test(unilang): Create harness for performance stress testing"
142+
143+
##### Increment 5: Implement and Run Performance Assertions
144+
* **Goal:** To execute the performance stress test and assert that the startup time and command resolution latency meet the non-functional requirements.
145+
* **Specification Reference:** `roadmap.md` M4.4.3, M4.4.4; `spec.md` NFR-Performance
146+
* **Steps:**
147+
1. **Expand Test Logic:** In `performance_stress_test.rs`, use `assert_cmd::Command::cargo_bin("stress_test_bin")` to run the compiled test binary.
148+
2. **Measure Startup:** The test will measure the total execution time of the binary as a proxy for startup time + lookup time.
149+
3. **Parse Output:** The test will capture the stdout from the binary, parse the p99 latency value.
150+
4. **Assert Performance:** Assert that the total time is within a reasonable bound (e.g., < 200ms) and that the parsed p99 latency is below the required threshold (< 1ms).
151+
5. **Perform Increment Verification.**
152+
* **Increment Verification:**
153+
1. Execute `timeout 300 cargo test -p unilang --test performance_stress_test -- --nocapture`. The test must pass all performance assertions.
154+
* **Commit Message:** "test(unilang): Implement and pass performance stress test for static registry"
155+
156+
##### Increment 6: Finalization
157+
* **Goal:** To perform a final review, remove any temporary test artifacts, and verify the entire task's output.
158+
* **Steps:**
159+
1. Review all changes made during this phase.
160+
2. Ensure all new code is documented.
161+
3. Clean up the `unilang.commands.yaml` file, leaving only a few representative examples.
162+
4. Unset the `UNILANG_STATIC_COMMANDS_PATH` environment variable logic or make it test-only.
163+
5. Perform the full Crate Conformance Check procedure one last time.
164+
6. Perform the `Finalization Increment Verification` procedure from the design rules.
165+
* **Increment Verification:**
166+
1. All checks must pass.
167+
* **Commit Message:** "feat(unilang): Complete and finalize zero-overhead static command registry"
168+
169+
### Notes & Insights
170+
* **`const` Compatibility is Key:** The core of this phase is the `StaticCommandDefinition` struct. It's crucial that this struct and all its nested types are `const`-compatible, which means no heap allocations (`String`, `Vec`).
171+
* **Routine Registration Compromise:** This plan explicitly acknowledges that `CommandRoutine`s cannot be stored statically. The performance gain comes from offloading the parsing and storage of command *definitions* to compile time. Routines for all commands (static and dynamic) will still need to be registered at runtime into a `HashMap`. This is a pragmatic approach that meets the performance NFR for command *resolution*.
172+
173+
### Changelog
174+
* [Initial] Created a new development plan for Phase 4.
175+
* [Critique] Revised the plan to address a critical flaw regarding Rust's `const` rules by introducing `StaticCommandDefinition` and refining the build process. Clarified the hybrid nature of routine handling.
176+
* [Elaboration] Provided a full, detailed version of the revised plan with explicit steps for each increment.

module/move/unilang/test_project/Cargo.toml

Lines changed: 0 additions & 9 deletions
This file was deleted.

module/move/unilang/test_project/src/lib.rs

Lines changed: 0 additions & 69 deletions
This file was deleted.

0 commit comments

Comments
 (0)