Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MassaLabs: Implement a Intermediate Representation to improve the compilation process #359

Draft
wants to merge 54 commits into
base: next
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
24e687a
feat - move AlgebraicGraph from ir to codegen/air
Leo-Besancon Sep 23, 2024
92952bd
Base IR graph, to improve
Leo-Besancon Sep 26, 2024
f4880ab
Make base IR structure (passes / translation from AST..)
Leo-Besancon Sep 27, 2024
33da4a3
Additional structure (MIR > AIR lowering), added TODO MIR comments
Leo-Besancon Sep 27, 2024
c036336
Add comments to TODOs
Leo-Besancon Sep 30, 2024
d018fd6
Add typed nodes in the Mir
Leo-Besancon Oct 9, 2024
5047968
Update MirValues to account for all case of binding in the AST
Leo-Besancon Oct 11, 2024
f34d16c
Add type for new elements
Leo-Besancon Oct 11, 2024
a615a8b
Add function variable node type
Leo-Besancon Oct 11, 2024
1af3508
Update value.rs
Leo-Besancon Oct 11, 2024
e4ce487
Implement some elements for lowering from AST to MIR
Leo-Besancon Oct 15, 2024
7b554ba
Inlining unit test structure
Soulthym Oct 11, 2024
18c1870
MirType:
Soulthym Oct 11, 2024
21dc897
Add human readable serialization to MirGraph
Soulthym Oct 15, 2024
7c180c9
Make IR test pass
Leo-Besancon Oct 16, 2024
6e58c2a
Merge remote-tracking branch 'origin/thy-ir-inlining' into leo_loweri…
Leo-Besancon Oct 16, 2024
e8fa708
Fix conflict Option<>
Leo-Besancon Oct 16, 2024
b0df63d
fix Migraph pretty printer counters not being shared between recursions
Soulthym Oct 17, 2024
6e1ddc4
Merge branch 'thy-ir-inlining' into feat-implement-ir
Soulthym Oct 17, 2024
6ca0349
Reworked use_list, reworked placeholder mechanic + fmt
Leo-Besancon Oct 17, 2024
ee7f04d
Add placeholder type to avoid issues with Constant(0)
Leo-Besancon Oct 17, 2024
9a2f4f8
MirGraph pretty printer: add call support + track func indexes
Soulthym Oct 17, 2024
1d7ca53
Inlining test: complete Input
Soulthym Oct 17, 2024
33e022d
cargo fmt + clippy
Leo-Besancon Oct 17, 2024
7cadc6f
Fix double Enf in cas of Bindary::Eq
Leo-Besancon Oct 18, 2024
65db04c
improve MirGraph pretty printer
Soulthym Oct 18, 2024
b7834cb
Add root management to differentiate between dead nodes and root nodes
Leo-Besancon Oct 22, 2024
65e0bc6
Add functions tests in MIR
Leo-Besancon Oct 22, 2024
04ff191
Inlining 1/2: replace call(func, args) site by func.body
Soulthym Oct 17, 2024
2876cd8
Inlining 2/2: swap variables for arg values in inlined body
Soulthym Oct 22, 2024
a57a3b0
cargo fmt + clippy
Soulthym Oct 22, 2024
4804834
Add helpers to insert nodes for each operation
Leo-Besancon Nov 5, 2024
35dd907
Generic visitor
Soulthym Nov 6, 2024
03744ad
expose roots and graph to visitor
Soulthym Nov 6, 2024
64cf047
inlining with generic visitor
Soulthym Nov 6, 2024
a999882
implement post order visitor
Soulthym Nov 7, 2024
9176471
fix missing visit of stored nodes
Soulthym Nov 8, 2024
ac51b88
Rename VisitOrder::DepthFirst to Manual
Soulthym Nov 8, 2024
f60e664
Add unrolling and lowering
Leo-Besancon Nov 14, 2024
e9f1bd7
rework Operation: base structure
Soulthym Oct 31, 2024
4910417
fix ir2 module structure
Soulthym Nov 4, 2024
9e43103
implement Sub and Mul
Soulthym Nov 4, 2024
78ab06d
make Operands dyn Value
Soulthym Nov 5, 2024
f066d6a
blanket migration for degree and trace
Soulthym Nov 6, 2024
103f453
added Eq, Clone and Debug support to Value and Operations
Soulthym Nov 13, 2024
d130f97
convert MirValue to the new structure
Soulthym Nov 13, 2024
987aa35
ir2: new graph structure based on enums
Soulthym Nov 19, 2024
a76e357
Split graph module, and several improvements to the graph datastructure
Soulthym Nov 21, 2024
cbd6620
fix builder pattern duplication and split+rename NodeType
Soulthym Nov 26, 2024
858d012
replace asserts by expects
Soulthym Nov 27, 2024
750ecf6
Scope with unique insertions
Soulthym Nov 27, 2024
345cbe7
IsNode derive macro
Soulthym Nov 29, 2024
002dfa5
fix module name
Soulthym Nov 29, 2024
2f1bc62
Merge pull request #3 from massalabs/thy-rework-ir
Soulthym Nov 29, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ members = [
"parser",
"pass",
"ir",
"codegen/air",
"codegen/masm",
"codegen/winterfell",
]
Expand Down
2 changes: 1 addition & 1 deletion air-script/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ name = "airc"
path = "src/main.rs"

[dependencies]
air-ir = { package = "air-ir", path = "../ir", version = "0.4" }
air-ir = { package = "air-ir", path = "../codegen/air", version = "0.4" }
air-parser = { package = "air-parser", path = "../parser", version = "0.4" }
air-pass = { package = "air-pass", path = "../pass", version = "0.1" }
air-codegen-masm = { package = "air-codegen-masm", path = "../codegen/masm", version = "0.4" }
Expand Down
20 changes: 20 additions & 0 deletions codegen/air/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "air-ir"
version = "0.4.0"
description = "Intermediate representation for the AirScript language"
authors = ["miden contributors"]
readme = "README.md"
license = "MIT"
repository = "https://github.com/0xPolygonMiden/air-script"
categories = ["compilers", "cryptography"]
keywords = ["air", "stark", "zero-knowledge", "zkp"]
rust-version.workspace = true
edition.workspace = true

[dependencies]
air-parser = { package = "air-parser", path = "../../parser", version = "0.4" }
air-pass = { package = "air-pass", path = "../../pass", version = "0.1" }
mir = { package = "mir", path = "../../ir", version = "0.4" }
anyhow = "1.0"
miden-diagnostics = "0.1"
thiserror = "1.0"
35 changes: 35 additions & 0 deletions codegen/air/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Intermediate Representation (IR)

This crate contains the intermediate representation for AirScript, `AirIR`.

The purpose of the `AirIR` is to provide a simple and accurate representation of an AIR that allows for optimization and translation to constraint evaluator code in a variety of target languages.

## Generating the AirIR

Generate an `AirIR` from an AirScript AST (the output of the AirScript parser) using the `new` method. The `new` method will return a new `AirIR` or an `Error` of type `SemanticError` if it encounters any errors while processing the AST.

The `new` method will first iterate through the source sections that contain declarations to build a symbol table with constants, trace columns, public inputs, periodic columns and random values. It will return a `SemanticError` if it encounters a duplicate, incorrect, or missing declaration. Once the symbol table is built, the constraints and intermediate variables in the `boundary_constraints` and `integrity_constraints` sections of the AST are processed. Finally, `new` returns a Result containing the `AirIR` or a `SemanticError`.

Example usage:

```Rust
// parse the source string to a Result containing the AST or an Error
let ast = parse(source.as_str()).expect("Parsing failed");

// process the AST to get a Result containing the AirIR or an Error
let ir = AirIR::new(&ast)
```

## AirIR

Although generation of an `AirIR` uses a symbol table while processing the source AST, the internal representation only consists of the following:

- **Name** of the AIR definition represented by the `AirIR`.
- **Segment Widths**, represented by a vector that contains the width of each trace segment (currently `main` and `auxiliary`).
- **Constants**, represented by a vector that maps an identifier to a constant value.
- **Public inputs**, represented by a vector that maps an identifier to a size for each public input that was declared. (Currently, public inputs can only be declared as fixed-size arrays.)
- **Periodic columns**, represented by an ordered vector that contains each periodic column's repeating pattern (as a vector).
- **Constraints**, represented by the combination of:
- a directed acyclic graph (DAG) without duplicate nodes.
- a vector of `ConstraintRoot` for each trace segment (e.g. main or auxiliary), where `ConstraintRoot` contains the node index in the graph where each of the constraint starts and the constraint domain which specifies the row(s) accessed by each of the constraints.
- contains both boundary and integrity constraints.
8 changes: 8 additions & 0 deletions codegen/air/src/codegen.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/// This trait should be implemented on types which handle generating code from AirScript IR
pub trait CodeGenerator {
/// The type of the artifact produced by this codegen backend
type Output;

/// Generates code using this generator, consuming it in the process
fn generate(&self, ir: &crate::Air) -> anyhow::Result<Self::Output>;
}
187 changes: 187 additions & 0 deletions codegen/air/src/graph/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
use std::collections::BTreeMap;

use crate::ir::*;

/// A unique identifier for a node in an [AlgebraicGraph]
///
/// The raw value of this identifier is an index in the `nodes` vector
/// of the [AlgebraicGraph] struct.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub struct NodeIndex(usize);
impl core::ops::Add<usize> for NodeIndex {
type Output = NodeIndex;

fn add(self, rhs: usize) -> Self::Output {
Self(self.0 + rhs)
}
}
impl core::ops::Add<usize> for &NodeIndex {
type Output = NodeIndex;

fn add(self, rhs: usize) -> Self::Output {
NodeIndex(self.0 + rhs)
}
}

/// A node in the [AlgebraicGraph]
#[derive(Debug, Clone)]
pub struct Node {
/// The operation represented by this node
op: Operation,
}
impl Node {
/// Get the underlying [Operation] represented by this node
#[inline]
pub const fn op(&self) -> &Operation {
&self.op
}
}

/// The AlgebraicGraph is a directed acyclic graph used to represent integrity constraints. To
/// store it compactly, it is represented as a vector of nodes where each node references other
/// nodes by their index in the vector.
///
/// Within the graph, constraint expressions can overlap and share subgraphs, since new expressions
/// reuse matching existing nodes when they are added, rather than creating new nodes.
///
/// - Leaf nodes (with no outgoing edges) are constants or references to trace cells (i.e. column 0
/// in the current row or column 5 in the next row).
/// - Tip nodes with no incoming edges (no parent nodes) always represent constraints, although they
/// do not necessarily represent all constraints. There could be constraints which are also
/// subgraphs of other constraints.
#[derive(Default, Debug, Clone)]
pub struct AlgebraicGraph {
/// All nodes in the graph.
nodes: Vec<Node>,
}
impl AlgebraicGraph {
/// Creates a new graph from a list of nodes.
pub const fn new(nodes: Vec<Node>) -> Self {
Self { nodes }
}

/// Returns the node with the specified index.
pub fn node(&self, index: &NodeIndex) -> &Node {
&self.nodes[index.0]
}

/// Returns the number of nodes in the graph.
pub fn num_nodes(&self) -> usize {
self.nodes.len()
}

/// Returns the degree of the subgraph which has the specified node as its tip.
pub fn degree(&self, index: &NodeIndex) -> IntegrityConstraintDegree {
let mut cycles = BTreeMap::default();
let base = self.accumulate_degree(&mut cycles, index);

if cycles.is_empty() {
IntegrityConstraintDegree::new(base)
} else {
IntegrityConstraintDegree::with_cycles(base, cycles.values().copied().collect())
}
}

/// TODO: docs
pub fn node_details(
&self,
index: &NodeIndex,
default_domain: ConstraintDomain,
) -> Result<(TraceSegmentId, ConstraintDomain), ConstraintError> {
// recursively walk the subgraph and infer the trace segment and domain
match self.node(index).op() {
Operation::Value(value) => match value {
Value::Constant(_) => Ok((DEFAULT_SEGMENT, default_domain)),
Value::PeriodicColumn(_) => {
assert!(
!default_domain.is_boundary(),
"unexpected access to periodic column in boundary constraint"
);
// the default domain for [IntegrityConstraints] is `EveryRow`
Ok((DEFAULT_SEGMENT, ConstraintDomain::EveryRow))
}
Value::PublicInput(_) => {
assert!(
!default_domain.is_integrity(),
"unexpected access to public input in integrity constraint"
);
Ok((DEFAULT_SEGMENT, default_domain))
}
Value::RandomValue(_) => Ok((AUX_SEGMENT, default_domain)),
Value::TraceAccess(trace_access) => {
let domain = if default_domain.is_boundary() {
assert_eq!(
trace_access.row_offset, 0,
"unexpected trace offset in boundary constraint"
);
default_domain
} else {
ConstraintDomain::from_offset(trace_access.row_offset)
};

Ok((trace_access.segment, domain))
}
},
Operation::Add(lhs, rhs) | Operation::Sub(lhs, rhs) | Operation::Mul(lhs, rhs) => {
let (lhs_segment, lhs_domain) = self.node_details(lhs, default_domain)?;
let (rhs_segment, rhs_domain) = self.node_details(rhs, default_domain)?;

let trace_segment = lhs_segment.max(rhs_segment);
let domain = lhs_domain.merge(rhs_domain)?;

Ok((trace_segment, domain))
}
}
}

/// Insert the operation and return its node index. If an identical node already exists, return
/// that index instead.
pub(crate) fn insert_node(&mut self, op: Operation) -> NodeIndex {
self.nodes.iter().position(|n| *n.op() == op).map_or_else(
|| {
// create a new node.
let index = self.nodes.len();
self.nodes.push(Node { op });
NodeIndex(index)
},
|index| {
// return the existing node's index.
NodeIndex(index)
},
)
}

/// Recursively accumulates the base degree and the cycle lengths of the periodic columns.
fn accumulate_degree(
&self,
cycles: &mut BTreeMap<QualifiedIdentifier, usize>,
index: &NodeIndex,
) -> usize {
// recursively walk the subgraph and compute the degree from the operation and child nodes
match self.node(index).op() {
Operation::Value(value) => match value {
Value::Constant(_) | Value::RandomValue(_) | Value::PublicInput(_) => 0,
Value::TraceAccess(_) => 1,
Value::PeriodicColumn(pc) => {
cycles.insert(pc.name, pc.cycle);
0
}
},
Operation::Add(lhs, rhs) => {
let lhs_base = self.accumulate_degree(cycles, lhs);
let rhs_base = self.accumulate_degree(cycles, rhs);
lhs_base.max(rhs_base)
}
Operation::Sub(lhs, rhs) => {
let lhs_base = self.accumulate_degree(cycles, lhs);
let rhs_base = self.accumulate_degree(cycles, rhs);
lhs_base.max(rhs_base)
}
Operation::Mul(lhs, rhs) => {
let lhs_base = self.accumulate_degree(cycles, lhs);
let rhs_base = self.accumulate_degree(cycles, rhs);
lhs_base + rhs_base
}
}
}
}
Loading
Loading