refactor(aura-core): own FieldSpec.name to drop the derive_signature heap leak
FieldSpec.name was &'static str, forcing derive_signature to Box::leak every re-exported composite OutField name. Because compile_with_params reruns derive_signature per sweep point, an N-point sweep over an M-output-field composite leaked N×M boundary names unbounded — a scaling wall the moment the World/sweep layer compiles families per point. Pull FieldSpec.name to String, mirroring the already-owned PortSpec.name and ParamSpec.name: a composite re-exports interior fields under runtime boundary names, so ownership is the correct model, not a workaround. FieldSpec loses Copy; the only fallout is one borrow at the render site. leak_name/Box::leak deleted. Behaviour-preserving: signature contents unchanged, all 231 tests green.
This commit is contained in:
@@ -33,8 +33,9 @@ pub enum Firing {
|
||||
/// window = its `length`). The `name` is a **non-load-bearing** debug symbol (C23):
|
||||
/// identity is the positional slot, the name is for tracing / graph rendering
|
||||
/// (#13), never read by bootstrap or the run loop (which wire by index). A
|
||||
/// `String`, not `&'static str` like `FieldSpec.name`: a variadic node generates
|
||||
/// per-slot names (`term[0]`), exactly as `ParamSpec.name` does (`weights[0]`).
|
||||
/// `String` (as `FieldSpec.name` and `ParamSpec.name` are): a variadic node
|
||||
/// generates per-slot names (`term[0]`), needing ownership, exactly as
|
||||
/// `ParamSpec.name` does (`weights[0]`).
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct PortSpec {
|
||||
pub kind: ScalarKind,
|
||||
@@ -45,10 +46,13 @@ pub struct PortSpec {
|
||||
/// One declared output column of a node's record: its name (metadata for sinks /
|
||||
/// the playground, C18) and its scalar kind. The position of a `FieldSpec` in
|
||||
/// `NodeSchema.output` is what an `Edge` binds (`Edge::from_field`); the name is
|
||||
/// not load-bearing for wiring.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
/// not load-bearing for wiring. The `name` is a `String` (not `Copy`): a composite
|
||||
/// re-exports an interior field under a runtime boundary name (`OutField.name`,
|
||||
/// derived in `derive_signature`), so ownership is required — the same reason
|
||||
/// `PortSpec.name` and `ParamSpec.name` are owned.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct FieldSpec {
|
||||
pub name: &'static str,
|
||||
pub name: String,
|
||||
pub kind: ScalarKind,
|
||||
}
|
||||
|
||||
@@ -56,8 +60,8 @@ pub struct FieldSpec {
|
||||
/// kind. The name is a **non-load-bearing** debug symbol (path-qualified at
|
||||
/// aggregation, as `FieldSpec.name` already is); a param's identity is its
|
||||
/// positional slot in the blueprint's aggregated param-space (C23 — by index, not
|
||||
/// by name). Unlike `FieldSpec.name` (`&'static str`), the name is a `String`: a
|
||||
/// vector knob carries a runtime index (`weights[0]`) and aggregation prefixes the
|
||||
/// by name). The name is a `String` (as `FieldSpec.name` and `PortSpec.name` are):
|
||||
/// a vector knob carries a runtime index (`weights[0]`) and aggregation prefixes the
|
||||
/// composite path (`strategy.weights[0]`). Permitted kinds: `I64`/`F64`/`Bool`;
|
||||
/// `Timestamp` is a structural axis (C20), never a numeric knob.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
|
||||
@@ -100,7 +100,7 @@ fn derive_signature(c: &Composite) -> NodeSchema {
|
||||
.get(of.node)
|
||||
.and_then(|n| n.signature().output.get(of.field).map(|f| f.kind))
|
||||
.unwrap_or(ScalarKind::F64);
|
||||
FieldSpec { name: leak_name(&of.name), kind }
|
||||
FieldSpec { name: of.name.clone(), kind }
|
||||
})
|
||||
.collect();
|
||||
let mut params = Vec::new();
|
||||
@@ -120,13 +120,6 @@ fn interior_slot_kind(nodes: &[BlueprintNode], _edges: &[Edge], t: &Target) -> S
|
||||
.unwrap_or(ScalarKind::F64)
|
||||
}
|
||||
|
||||
/// Leak a boundary name into a `&'static str` for a derived `FieldSpec`. Acceptable
|
||||
/// because `derive_signature` is a cold, pre-build, render/validation path (never the
|
||||
/// hot loop), and the leaked names are bounded by the static blueprint.
|
||||
fn leak_name(s: &str) -> &'static str {
|
||||
Box::leak(s.to_string().into_boxed_str())
|
||||
}
|
||||
|
||||
/// One named input role: role `r` (by position) fans the source value into
|
||||
/// `targets`. The `name` is a non-load-bearing render symbol (C23); identity is
|
||||
/// the role index, which survives lowering — the name does not.
|
||||
@@ -1048,7 +1041,7 @@ mod tests {
|
||||
}
|
||||
/// A one-f64-field output record under name `v`.
|
||||
fn out_v() -> Vec<FieldSpec> {
|
||||
vec![FieldSpec { name: "v", kind: ScalarKind::F64 }]
|
||||
vec![FieldSpec { name: "v".into(), kind: ScalarKind::F64 }]
|
||||
}
|
||||
/// A bound root role of f64 kind, fanning into `targets`.
|
||||
fn root_role(name: &str, targets: Vec<Target>) -> Role {
|
||||
@@ -2181,7 +2174,7 @@ mod tests {
|
||||
"Boom",
|
||||
NodeSchema {
|
||||
inputs: vec![PortSpec { kind: ScalarKind::I64, firing: Firing::Any, name: "in".into() }],
|
||||
output: vec![FieldSpec { name: "v", kind: ScalarKind::I64 }],
|
||||
output: vec![FieldSpec { name: "v".into(), kind: ScalarKind::I64 }],
|
||||
params: vec![],
|
||||
},
|
||||
|_| panic!("build must not run when validation fails pre-build"),
|
||||
|
||||
@@ -88,7 +88,7 @@ fn prim_record(b: &aura_core::PrimitiveBuilder) -> String {
|
||||
let role = if s.output.is_empty() { "sink" } else { "node" };
|
||||
let params = join(s.params.iter().map(|p| named_kind(&p.name, p.kind)));
|
||||
let ins = join(s.inputs.iter().map(port_json));
|
||||
let outs = join(s.output.iter().map(|f| named_kind(f.name, f.kind)));
|
||||
let outs = join(s.output.iter().map(|f| named_kind(&f.name, f.kind)));
|
||||
// explicit instance name only — an unnamed node carries no "name" field
|
||||
// (node_name()'s lowercased-type default would be a redundant type-duplicate)
|
||||
let name = match b.instance_name() {
|
||||
@@ -298,7 +298,7 @@ mod tests {
|
||||
PortSpec { kind: ScalarKind::F64, firing: Firing::Any, name: "lhs".into() },
|
||||
PortSpec { kind: ScalarKind::F64, firing: Firing::Barrier(0), name: "rhs".into() },
|
||||
],
|
||||
output: vec![FieldSpec { name: "diff", kind: ScalarKind::F64 }],
|
||||
output: vec![FieldSpec { name: "diff".into(), kind: ScalarKind::F64 }],
|
||||
params: vec![],
|
||||
},
|
||||
|_| Box::new(Bare),
|
||||
|
||||
@@ -395,7 +395,7 @@ mod tests {
|
||||
fn sig() -> NodeSchema {
|
||||
NodeSchema {
|
||||
inputs: vec![f64_port(Firing::Any), f64_port(Firing::Any)],
|
||||
output: vec![FieldSpec { name: "value", kind: ScalarKind::F64 }],
|
||||
output: vec![FieldSpec { name: "value".into(), kind: ScalarKind::F64 }],
|
||||
params: vec![],
|
||||
}
|
||||
}
|
||||
@@ -424,7 +424,7 @@ mod tests {
|
||||
fn sig() -> NodeSchema {
|
||||
NodeSchema {
|
||||
inputs: vec![f64_port(Firing::Barrier(0)), f64_port(Firing::Barrier(0))],
|
||||
output: vec![FieldSpec { name: "value", kind: ScalarKind::F64 }],
|
||||
output: vec![FieldSpec { name: "value".into(), kind: ScalarKind::F64 }],
|
||||
params: vec![],
|
||||
}
|
||||
}
|
||||
@@ -453,7 +453,7 @@ mod tests {
|
||||
f64_port(Firing::Barrier(0)),
|
||||
f64_port(Firing::Any),
|
||||
],
|
||||
output: vec![FieldSpec { name: "value", kind: ScalarKind::F64 }],
|
||||
output: vec![FieldSpec { name: "value".into(), kind: ScalarKind::F64 }],
|
||||
params: vec![],
|
||||
}
|
||||
}
|
||||
@@ -486,11 +486,11 @@ mod tests {
|
||||
NodeSchema {
|
||||
inputs: vec![f64_port(Firing::Barrier(0)); 5],
|
||||
output: vec![
|
||||
FieldSpec { name: "open", kind: ScalarKind::F64 },
|
||||
FieldSpec { name: "high", kind: ScalarKind::F64 },
|
||||
FieldSpec { name: "low", kind: ScalarKind::F64 },
|
||||
FieldSpec { name: "close", kind: ScalarKind::F64 },
|
||||
FieldSpec { name: "volume", kind: ScalarKind::F64 },
|
||||
FieldSpec { name: "open".into(), kind: ScalarKind::F64 },
|
||||
FieldSpec { name: "high".into(), kind: ScalarKind::F64 },
|
||||
FieldSpec { name: "low".into(), kind: ScalarKind::F64 },
|
||||
FieldSpec { name: "close".into(), kind: ScalarKind::F64 },
|
||||
FieldSpec { name: "volume".into(), kind: ScalarKind::F64 },
|
||||
],
|
||||
params: vec![],
|
||||
}
|
||||
@@ -524,8 +524,8 @@ mod tests {
|
||||
NodeSchema {
|
||||
inputs: vec![f64_port(Firing::Any)],
|
||||
output: vec![
|
||||
FieldSpec { name: "f", kind: ScalarKind::F64 },
|
||||
FieldSpec { name: "i", kind: ScalarKind::I64 },
|
||||
FieldSpec { name: "f".into(), kind: ScalarKind::F64 },
|
||||
FieldSpec { name: "i".into(), kind: ScalarKind::I64 },
|
||||
],
|
||||
params: vec![],
|
||||
}
|
||||
@@ -553,7 +553,7 @@ mod tests {
|
||||
fn sig() -> NodeSchema {
|
||||
NodeSchema {
|
||||
inputs: vec![f64_port(Firing::Any)],
|
||||
output: vec![FieldSpec { name: "value", kind: ScalarKind::F64 }],
|
||||
output: vec![FieldSpec { name: "value".into(), kind: ScalarKind::F64 }],
|
||||
params: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ impl Add {
|
||||
PortSpec { kind: ScalarKind::F64, firing: Firing::Any, name: "lhs".into() },
|
||||
PortSpec { kind: ScalarKind::F64, firing: Firing::Any, name: "rhs".into() },
|
||||
],
|
||||
output: vec![FieldSpec { name: "value", kind: ScalarKind::F64 }],
|
||||
output: vec![FieldSpec { name: "value".into(), kind: ScalarKind::F64 }],
|
||||
params: vec![],
|
||||
},
|
||||
|_| Box::new(Add::new()),
|
||||
|
||||
@@ -61,7 +61,7 @@ impl Ema {
|
||||
"EMA",
|
||||
NodeSchema {
|
||||
inputs: vec![PortSpec { kind: ScalarKind::F64, firing: Firing::Any, name: "series".into() }],
|
||||
output: vec![FieldSpec { name: "value", kind: ScalarKind::F64 }],
|
||||
output: vec![FieldSpec { name: "value".into(), kind: ScalarKind::F64 }],
|
||||
params: vec![ParamSpec { name: "length".into(), kind: ScalarKind::I64 }],
|
||||
},
|
||||
|p| Box::new(Ema::new(p[0].as_i64().expect("length slot is I64") as usize)),
|
||||
|
||||
@@ -30,7 +30,7 @@ impl Exposure {
|
||||
"Exposure",
|
||||
NodeSchema {
|
||||
inputs: vec![PortSpec { kind: ScalarKind::F64, firing: Firing::Any, name: "signal".into() }],
|
||||
output: vec![FieldSpec { name: "exposure", kind: ScalarKind::F64 }],
|
||||
output: vec![FieldSpec { name: "exposure".into(), kind: ScalarKind::F64 }],
|
||||
params: vec![ParamSpec { name: "scale".into(), kind: ScalarKind::F64 }],
|
||||
},
|
||||
|p| Box::new(Exposure::new(p[0].as_f64().expect("scale slot is F64"))),
|
||||
|
||||
@@ -52,7 +52,7 @@ impl LinComb {
|
||||
.collect();
|
||||
PrimitiveBuilder::new(
|
||||
"LinComb",
|
||||
NodeSchema { inputs, output: vec![FieldSpec { name: "value", kind: ScalarKind::F64 }], params },
|
||||
NodeSchema { inputs, output: vec![FieldSpec { name: "value".into(), kind: ScalarKind::F64 }], params },
|
||||
|p| Box::new(LinComb::new(
|
||||
p.iter().map(|s| s.as_f64().expect("weight slot is F64")).collect(),
|
||||
)),
|
||||
|
||||
@@ -66,7 +66,7 @@ impl SimBroker {
|
||||
PortSpec { kind: ScalarKind::F64, firing: Firing::Any, name: "exposure".into() },
|
||||
PortSpec { kind: ScalarKind::F64, firing: Firing::Any, name: "price".into() },
|
||||
],
|
||||
output: vec![FieldSpec { name: "equity", kind: ScalarKind::F64 }],
|
||||
output: vec![FieldSpec { name: "equity".into(), kind: ScalarKind::F64 }],
|
||||
params: vec![],
|
||||
},
|
||||
move |_| Box::new(SimBroker::new(pip_size)),
|
||||
|
||||
@@ -29,7 +29,7 @@ impl Sma {
|
||||
"SMA",
|
||||
NodeSchema {
|
||||
inputs: vec![PortSpec { kind: ScalarKind::F64, firing: Firing::Any, name: "series".into() }],
|
||||
output: vec![FieldSpec { name: "value", kind: ScalarKind::F64 }],
|
||||
output: vec![FieldSpec { name: "value".into(), kind: ScalarKind::F64 }],
|
||||
params: vec![ParamSpec { name: "length".into(), kind: ScalarKind::I64 }],
|
||||
},
|
||||
|p| Box::new(Sma::new(p[0].as_i64().expect("length slot is I64") as usize)),
|
||||
|
||||
@@ -27,7 +27,7 @@ impl Sub {
|
||||
PortSpec { kind: ScalarKind::F64, firing: Firing::Any, name: "lhs".into() },
|
||||
PortSpec { kind: ScalarKind::F64, firing: Firing::Any, name: "rhs".into() },
|
||||
],
|
||||
output: vec![FieldSpec { name: "value", kind: ScalarKind::F64 }],
|
||||
output: vec![FieldSpec { name: "value".into(), kind: ScalarKind::F64 }],
|
||||
params: vec![],
|
||||
},
|
||||
|_| Box::new(Sub::new()),
|
||||
|
||||
Reference in New Issue
Block a user