Create multiple PODs where resource limits for a single POD are exceeded (#444)
* Create multiple PODs where resource limits for a single POD are exceeded * HashSet -> BTreeSet determinism fix * Fixed incorrect assignment of input PODs and added test * Ensure only a single output POD * Return error when reveal() called with unknown statement * Use unreachable! for presumed-impossible cases * Use assert_eq! rather than debug_assert_eq * Use FIFO for topological sort * Simplify bounds calculation * Some more simplifications/comments * Enforce dep_idx < idx invariant * Incrementally solve rather than estimating slack * Fix tests to correctly test dependencies between private and public statements * More tidying * Note possible optimisation of MainPodBuilder cloning of input PODs * Fix tracking of total input POD count * Refactor tests * Formatting * Small optimisation: use Vec in place of BTreeSet * Account for automatically-inserted Contains statements * Formatting * Fix possible issue with copied statements * Simplify result type given only a single result MainPod * Remove unnecessary POD count estimate functionality * Simplify dependency ordering and tracking * Remove notion of multiple output PODs from solver * Minor simplifications * Use add_constraint instead of with * Remove unnecessary check following assertion * Fix handling of anchored keys given that Contains statements are not auto-inserted if they already exist * Fix confusing dependency graph test * Remove prove_order * Fix deduplication and possible double-counting of public but not copied statements * Reorder so that the output POD is the final POD * Add more detailed tests * Remove redundant tests * Simplify POD counting * More docs * Flag more branches as unreachable * Formatting * Fix for changed custom batch parsing
This commit is contained in:
parent
d1b7b4d37e
commit
48aa004ae5
6 changed files with 3047 additions and 0 deletions
178
src/frontend/multi_pod/deps.rs
Normal file
178
src/frontend/multi_pod/deps.rs
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
//! Dependency analysis for statements and operations.
|
||||
//!
|
||||
//! This module analyzes dependencies between statements to determine
|
||||
//! which statements must be proved before others.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{
|
||||
frontend::{Operation, OperationArg},
|
||||
middleware::{Hash, Statement},
|
||||
};
|
||||
|
||||
/// Represents a source of a statement dependency.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum StatementSource {
|
||||
/// Statement created within this builder at the given index.
|
||||
Internal(usize),
|
||||
/// Statement from an external input POD (identified by POD hash).
|
||||
External(Hash),
|
||||
}
|
||||
|
||||
/// Dependency graph for all statements in a builder.
|
||||
///
|
||||
/// Each element `statement_deps[i]` is the list of dependencies for statement `i`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DependencyGraph {
|
||||
/// Dependencies for each statement (indexed by statement index).
|
||||
pub statement_deps: Vec<Vec<StatementSource>>,
|
||||
}
|
||||
|
||||
impl DependencyGraph {
|
||||
/// Build a dependency graph from statements and operations.
|
||||
///
|
||||
/// `statements` and `operations` should be parallel arrays where
|
||||
/// `operations[i]` produces `statements[i]`.
|
||||
///
|
||||
/// `external_pod_statements` maps (pod_hash, statement) pairs to enable
|
||||
/// recognizing references to external POD statements.
|
||||
pub fn build(
|
||||
statements: &[Statement],
|
||||
operations: &[Operation],
|
||||
external_pod_statements: &HashMap<Statement, Hash>,
|
||||
) -> Self {
|
||||
let mut statement_deps = Vec::with_capacity(statements.len());
|
||||
|
||||
// Build a map from statement to its index for internal lookup.
|
||||
// Use entry().or_insert() to preserve the FIRST occurrence of each statement.
|
||||
// This is important for CopyStatement: if statements[0] = A and statements[2] = copy(A) = A,
|
||||
// we want statement_to_index[A] = 0 (the original), not 2 (the copy).
|
||||
let mut statement_to_index: HashMap<&Statement, usize> = HashMap::new();
|
||||
for (i, s) in statements.iter().enumerate() {
|
||||
if !s.is_none() {
|
||||
statement_to_index.entry(s).or_insert(i);
|
||||
}
|
||||
}
|
||||
|
||||
for (idx, op) in operations.iter().enumerate() {
|
||||
let mut deps = Vec::new();
|
||||
|
||||
// Examine each argument to the operation
|
||||
for arg in &op.1 {
|
||||
if let OperationArg::Statement(ref dep_stmt) = arg {
|
||||
if dep_stmt.is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this is an internal statement (created earlier in this builder)
|
||||
if let Some(&dep_idx) = statement_to_index.get(dep_stmt) {
|
||||
// Internal dependencies must always be from earlier statements
|
||||
assert!(
|
||||
dep_idx <= idx,
|
||||
"Statement at index {} depends on future statement at index {}",
|
||||
idx,
|
||||
dep_idx
|
||||
);
|
||||
|
||||
if dep_idx < idx {
|
||||
// The statement was created by an earlier operation
|
||||
deps.push(StatementSource::Internal(dep_idx));
|
||||
continue;
|
||||
}
|
||||
// dep_idx == idx: The first occurrence of this statement is at the current index,
|
||||
// meaning this operation both takes and produces this statement (e.g., CopyStatement
|
||||
// copying from an external POD). Fall through to check external PODs for the source.
|
||||
}
|
||||
|
||||
// Check if this is from an external POD
|
||||
if let Some(&pod_hash) = external_pod_statements.get(dep_stmt) {
|
||||
deps.push(StatementSource::External(pod_hash));
|
||||
} else {
|
||||
// Statement arguments should either be internal (created earlier)
|
||||
// or from external PODs. If neither, something is wrong.
|
||||
unreachable!(
|
||||
"Statement argument not found in internal statements or external PODs: {:?}",
|
||||
dep_stmt
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
statement_deps.push(deps);
|
||||
}
|
||||
|
||||
Self { statement_deps }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
frontend::Operation as FrontendOp,
|
||||
middleware::{NativeOperation, OperationAux, OperationType, Value, ValueRef},
|
||||
};
|
||||
|
||||
fn equal_stmt(n: i64) -> Statement {
|
||||
Statement::Equal(
|
||||
ValueRef::Literal(Value::from(n)),
|
||||
ValueRef::Literal(Value::from(n)),
|
||||
)
|
||||
}
|
||||
|
||||
/// None operation produces Statement::None
|
||||
fn none_op() -> FrontendOp {
|
||||
FrontendOp(
|
||||
OperationType::Native(NativeOperation::None),
|
||||
vec![],
|
||||
OperationAux::None,
|
||||
)
|
||||
}
|
||||
|
||||
/// CopyStatement(s) produces s (the same statement)
|
||||
fn copy_op(stmt: Statement) -> FrontendOp {
|
||||
FrontendOp(
|
||||
OperationType::Native(NativeOperation::CopyStatement),
|
||||
vec![OperationArg::Statement(stmt)],
|
||||
OperationAux::None,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_copy_creates_dependency_on_original() {
|
||||
// CopyStatement(s) produces s. When we copy a statement, the copy
|
||||
// depends on where that statement first appears.
|
||||
//
|
||||
// statements[0] = s (produced by none_op - not realistic, but we need a first occurrence)
|
||||
// statements[1] = s (produced by copy_op(s))
|
||||
//
|
||||
// op1's argument s matches statements[0], so statement 1 depends on statement 0.
|
||||
let s = equal_stmt(1);
|
||||
|
||||
let statements = vec![s.clone(), s.clone()];
|
||||
let operations = vec![
|
||||
none_op(), // Placeholder - in reality something else would produce s
|
||||
copy_op(s), // Copies s, producing s. Depends on statements[0].
|
||||
];
|
||||
|
||||
let graph = DependencyGraph::build(&statements, &operations, &HashMap::new());
|
||||
|
||||
assert!(graph.statement_deps[0].is_empty());
|
||||
assert_eq!(graph.statement_deps[1], vec![StatementSource::Internal(0)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_copies_depend_on_original() {
|
||||
// Multiple copies of the same statement all depend on where it first appears.
|
||||
let s = equal_stmt(1);
|
||||
|
||||
let statements = vec![s.clone(), s.clone(), s.clone()];
|
||||
let operations = vec![none_op(), copy_op(s.clone()), copy_op(s)];
|
||||
|
||||
let graph = DependencyGraph::build(&statements, &operations, &HashMap::new());
|
||||
|
||||
assert!(graph.statement_deps[0].is_empty());
|
||||
assert_eq!(graph.statement_deps[1], vec![StatementSource::Internal(0)]);
|
||||
assert_eq!(graph.statement_deps[2], vec![StatementSource::Internal(0)]);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue