ProgramMemory refactor - eliminate copies of memory (#5273)

* refactor program memory for encapsulation and remove special treatment of return values

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Refactor ProgramMemory to isolate mutation

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Refactor ProgramMemory

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Generated output

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Cache the result of executing modules for their items

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Compress envs when popped

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Remove the last traces of geometry from the memory module

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* docs

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Fixups

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Frontend

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Improve Environment GC

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Fixup mock execution frontend and interpreter, misc bug fixes

Signed-off-by: Nick Cameron <nrc@ncameron.org>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
Nick Cameron
2025-02-12 10:22:56 +13:00
committed by GitHub
parent bc4d254297
commit 322f398049
139 changed files with 4825 additions and 8154 deletions

View File

@ -9,7 +9,7 @@ use crate::{
errors::{KclError, KclErrorDetails},
execution::{
annotations, kcl_value, Artifact, ArtifactCommand, ArtifactGraph, ArtifactId, ExecOutcome, ExecutorSettings,
KclValue, Operation, ProgramMemory, SolidLazyIds, UnitAngle, UnitLen,
KclValue, Operation, ProgramMemory, UnitAngle, UnitLen,
},
modules::{ModuleId, ModuleInfo, ModuleLoader, ModulePath, ModuleRepr},
parsing::ast::types::NonCodeValue,
@ -27,6 +27,8 @@ pub struct ExecState {
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GlobalState {
/// Program variable bindings.
pub memory: ProgramMemory,
/// The stable artifact ID generator.
pub id_generator: IdGenerator,
/// Map from source file absolute path to module ID.
@ -50,13 +52,9 @@ pub struct GlobalState {
pub mod_loader: ModuleLoader,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ModuleState {
/// Program variable bindings.
pub memory: ProgramMemory,
/// Dynamic state that follows dynamic flow of the program.
pub dynamic_state: DynamicState,
/// The current value of the pipe operator returned from the previous
/// expression. If we're not currently in a pipeline, this will be None.
pub pipe_value: Option<KclValue>,
@ -99,7 +97,11 @@ impl ExecState {
// Fields are opt-in so that we don't accidentally leak private internal
// state when we add more to ExecState.
ExecOutcome {
memory: self.mod_local.memory,
variables: self
.memory()
.find_all_in_current_env(|_| true)
.map(|(k, v)| (k.clone(), v.clone()))
.collect(),
operations: self.mod_local.operations,
artifacts: self.global.artifacts,
artifact_commands: self.global.artifact_commands,
@ -107,12 +109,28 @@ impl ExecState {
}
}
pub fn to_mock_wasm_outcome(self) -> ExecOutcome {
// Fields are opt-in so that we don't accidentally leak private internal
// state when we add more to ExecState.
ExecOutcome {
variables: self
.memory()
.find_all_in_current_env(|_| true)
.map(|(k, v)| (k.clone(), v.clone()))
.collect(),
operations: Default::default(),
artifacts: Default::default(),
artifact_commands: Default::default(),
artifact_graph: Default::default(),
}
}
pub fn memory(&self) -> &ProgramMemory {
&self.mod_local.memory
&self.global.memory
}
pub fn mut_memory(&mut self) -> &mut ProgramMemory {
&mut self.mod_local.memory
&mut self.global.memory
}
pub fn next_uuid(&mut self) -> Uuid {
@ -148,11 +166,29 @@ impl ExecState {
pub fn angle_unit(&self) -> UnitAngle {
self.mod_local.settings.default_angle_units
}
pub(super) fn circular_import_error(&self, path: &ModulePath, source_range: SourceRange) -> KclError {
KclError::ImportCycle(KclErrorDetails {
message: format!(
"circular import of modules is not allowed: {} -> {}",
self.global
.mod_loader
.import_stack
.iter()
.map(|p| p.as_path().to_string_lossy())
.collect::<Vec<_>>()
.join(" -> "),
path,
),
source_ranges: vec![source_range],
})
}
}
impl GlobalState {
fn new(settings: &ExecutorSettings) -> Self {
let mut global = GlobalState {
memory: ProgramMemory::new(),
id_generator: Default::default(),
path_to_source_id: Default::default(),
module_infos: Default::default(),
@ -181,8 +217,6 @@ impl GlobalState {
impl ModuleState {
pub(super) fn new(exec_settings: &ExecutorSettings) -> Self {
ModuleState {
memory: Default::default(),
dynamic_state: Default::default(),
pipe_value: Default::default(),
module_exports: Default::default(),
operations: Default::default(),
@ -239,46 +273,6 @@ impl MetaSettings {
}
}
/// Dynamic state that depends on the dynamic flow of the program, like the call
/// stack. If the language had exceptions, for example, you could store the
/// stack of exception handlers here.
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct DynamicState {
pub solid_ids: Vec<SolidLazyIds>,
}
impl DynamicState {
#[must_use]
pub(super) fn merge(&self, memory: &ProgramMemory) -> Self {
let mut merged = self.clone();
merged.append(memory);
merged
}
fn append(&mut self, memory: &ProgramMemory) {
for env in &memory.environments {
for item in env.bindings.values() {
if let KclValue::Solid { value } = item {
self.solid_ids.push(SolidLazyIds::from(value.as_ref()));
}
}
}
}
pub(crate) fn edge_cut_ids_on_sketch(&self, sketch_id: uuid::Uuid) -> Vec<uuid::Uuid> {
self.solid_ids
.iter()
.flat_map(|eg| {
if eg.sketch_id == sketch_id {
eg.edge_cuts.clone()
} else {
Vec::new()
}
})
.collect::<Vec<_>>()
}
}
/// A generator for ArtifactIds that can be stable across executions.
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]