Skip to content

Commit

Permalink
feat: implement constant propagation pass
Browse files Browse the repository at this point in the history
This commit implements a pass which performs constant propagation on a
Program, elimintating any variables which are constants by converting
any references to constant variables into constant expressions.
Likewise, expressions which are constant in all operands are folded into
constant values and propagated as well.

The resulting Program is simpler and easier to perform local
analysis/optimizations on.
  • Loading branch information
bitwalker committed May 22, 2023
1 parent 5be7508 commit d0d4416
Show file tree
Hide file tree
Showing 6 changed files with 614 additions and 2 deletions.
20 changes: 20 additions & 0 deletions parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod lexer;
mod parser;
mod sema;
pub mod symbols;
pub mod transforms;

pub use self::parser::{ParseError, Parser};
pub use self::symbols::Symbol;
Expand Down Expand Up @@ -50,3 +51,22 @@ pub(crate) fn parse_module_from_file<P: AsRef<Path>>(
err @ Err(_) => err,
}
}

/// Parses a [Module] from a file already in the codemap
///
/// This is primarily intended for use in the import resolution phase.
pub(crate) fn parse_module(
diagnostics: &DiagnosticsHandler,
codemap: Arc<CodeMap>,
source: Arc<miden_diagnostics::SourceFile>,
) -> Result<ast::Module, ParseError> {
let parser = Parser::new((), codemap);
match parser.parse::<ast::Module, _>(diagnostics, source) {
ok @ Ok(_) => ok,
Err(ParseError::Lexer(err)) => {
diagnostics.emit(err);
Err(ParseError::Failed)
}
err @ Err(_) => err,
}
}
116 changes: 116 additions & 0 deletions parser/src/parser/tests/constant_propagation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
use miden_diagnostics::SourceSpan;

use pretty_assertions::assert_eq;

use crate::{ast::*, transforms::ConstantPropagator};

use super::ParseTest;

#[test]
fn test_constant_propagation() {
let root = r#"
def root
use lib::*
trace_columns:
main: [clk, a, b[2], c]
public_inputs:
inputs: [0]
const A = [2, 4, 6, 8]
const B = [[1, 1], [2, 2]]
integrity_constraints:
enf test_constraint(b)
let x = 2^EXP
let y = A[0..2]
enf a + y[1] = c + (x + 1)
boundary_constraints:
let x = B[0]
enf a.first = x[0]
"#;
let lib = r#"
mod lib
const EXP = 2
ev test_constraint([b0, b1]):
let x = EXP
let y = 2^x
enf b0 + x = b1 + y
"#;

let test = ParseTest::new();
let path = std::env::current_dir().unwrap().join("lib.air");
test.add_virtual_file(path, lib.to_string());

let mut program = match test.parse_program(root) {
Err(err) => {
test.diagnostics.emit(err);
panic!("expected parsing to succeed, see diagnostics for details");
}
Ok(ast) => ast,
};

let pass = ConstantPropagator::new();
pass.run(&mut program).unwrap();

let mut expected = Program::new(ident!(root));
expected.trace_columns.push(trace_segment!(
0,
"$main",
[(clk, 1), (a, 1), (b, 2), (c, 1)]
));
expected.public_inputs.insert(
ident!(inputs),
PublicInput::new(SourceSpan::UNKNOWN, ident!(inputs), 0),
);
expected
.constants
.insert(ident!(root, A), constant!(A = [2, 4, 6, 8]));
expected
.constants
.insert(ident!(root, B), constant!(B = [[1, 1], [2, 2]]));
expected
.constants
.insert(ident!(lib, EXP), constant!(EXP = 2));
// When constant propagation is done, the boundary constraints should look like:
// enf a.first = 1
expected.boundary_constraints.push(enforce!(eq!(
bounded_access!(a, Boundary::First, Type::Felt),
int!(1)
)));
// When constant propagation is done, the integrity constraints should look like:
// enf test_constraint(b)
// enf a + 4 = c + 5
expected
.integrity_constraints
.push(enforce!(call!(lib::test_constraint(
access!(b, Type::Vector(2)).into()
))));
expected.integrity_constraints.push(enforce!(eq!(
add!(access!(a, Type::Felt), int!(4)),
add!(access!(c, Type::Felt), int!(5))
)));
// The test_constraint function should look like:
// enf b0 + 2 = b1 + 4
let body = vec![enforce!(eq!(
add!(access!(b0, Type::Felt), int!(2)),
add!(access!(b1, Type::Felt), int!(4))
))];
expected.evaluators.insert(
function_ident!(lib, test_constraint),
EvaluatorFunction::new(
SourceSpan::UNKNOWN,
ident!(test_constraint),
vec![trace_segment!(0, "%0", [(b0, 1), (b1, 1)])],
body,
),
);

assert_eq!(program, expected);
}
1 change: 1 addition & 0 deletions parser/src/parser/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,7 @@ macro_rules! import {
mod arithmetic_ops;
mod boundary_constraints;
mod calls;
mod constant_propagation;
mod constants;
mod evaluators;
mod identifiers;
Expand Down
7 changes: 5 additions & 2 deletions parser/src/parser/tests/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ impl Emitter for SplitEmitter {
/// - ParseError test: check that the parsed values are valid.
/// * InvalidInt: This error is returned if the parsed number is not a valid u64.
pub struct ParseTest {
diagnostics: Arc<DiagnosticsHandler>,
pub diagnostics: Arc<DiagnosticsHandler>,
emitter: Arc<SplitEmitter>,
parser: Parser,
}
Expand Down Expand Up @@ -87,7 +87,10 @@ impl ParseTest {
}
}

#[allow(unused)]
pub fn add_virtual_file<P: AsRef<std::path::Path>>(&self, name: P, content: String) {
self.parser.codemap.add(name.as_ref(), content);
}

pub fn parse_module_from_file(&self, path: &str) -> Result<Module, ParseError> {
self.parser
.parse_file::<Module, _, _>(&self.diagnostics, path)
Expand Down
Loading

0 comments on commit d0d4416

Please sign in to comment.