mirror of
https://github.com/logos-storage/plonky2.git
synced 2026-01-09 17:23:08 +00:00
Merge pull request #646 from mir-protocol/stack_labels
Allow `%stack` to work with labels
This commit is contained in:
commit
8db1ff53d1
@ -17,7 +17,7 @@ use crate::cpu::kernel::{
|
||||
/// The number of bytes to push when pushing an offset within the code (i.e. when assembling jumps).
|
||||
/// Ideally we would automatically use the minimal number of bytes required, but that would be
|
||||
/// nontrivial given the circular dependency between an offset and its size.
|
||||
const BYTES_PER_OFFSET: u8 = 3;
|
||||
pub(crate) const BYTES_PER_OFFSET: u8 = 3;
|
||||
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub struct Kernel {
|
||||
@ -505,8 +505,13 @@ mod tests {
|
||||
#[test]
|
||||
fn stack_manipulation() {
|
||||
let pop = get_opcode("POP");
|
||||
let dup1 = get_opcode("DUP1");
|
||||
let swap1 = get_opcode("SWAP1");
|
||||
let swap2 = get_opcode("SWAP2");
|
||||
let push_label = get_push_opcode(BYTES_PER_OFFSET);
|
||||
|
||||
let kernel = parse_and_assemble(&["%stack (a) -> (a)"]);
|
||||
assert_eq!(kernel.code, vec![]);
|
||||
|
||||
let kernel = parse_and_assemble(&["%stack (a, b, c) -> (c, b, a)"]);
|
||||
assert_eq!(kernel.code, vec![swap2]);
|
||||
@ -518,6 +523,13 @@ mod tests {
|
||||
consts.insert("LIFE".into(), 42.into());
|
||||
parse_and_assemble_with_constants(&["%stack (a, b) -> (b, @LIFE)"], consts);
|
||||
// We won't check the code since there are two equally efficient implementations.
|
||||
|
||||
let kernel = parse_and_assemble(&["start: %stack (a, b) -> (start)"]);
|
||||
assert_eq!(kernel.code, vec![pop, pop, push_label, 0, 0, 0]);
|
||||
|
||||
// The "start" label gets shadowed by the "start" named stack item.
|
||||
let kernel = parse_and_assemble(&["start: %stack (start) -> (start, start)"]);
|
||||
assert_eq!(kernel.code, vec![dup1]);
|
||||
}
|
||||
|
||||
fn parse_and_assemble(files: &[&str]) -> Kernel {
|
||||
|
||||
@ -37,14 +37,15 @@ pub(crate) enum Item {
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) enum StackReplacement {
|
||||
NamedItem(String),
|
||||
/// Can be either a named item or a label.
|
||||
Identifier(String),
|
||||
Literal(Literal),
|
||||
MacroVar(String),
|
||||
Constant(String),
|
||||
}
|
||||
|
||||
/// The target of a `PUSH` operation.
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub(crate) enum PushTarget {
|
||||
Literal(Literal),
|
||||
Label(String),
|
||||
|
||||
@ -112,7 +112,7 @@ fn parse_stack_replacement(target: Pair<Rule>) -> StackReplacement {
|
||||
assert_eq!(target.as_rule(), Rule::stack_replacement);
|
||||
let inner = target.into_inner().next().unwrap();
|
||||
match inner.as_rule() {
|
||||
Rule::identifier => StackReplacement::NamedItem(inner.as_str().into()),
|
||||
Rule::identifier => StackReplacement::Identifier(inner.as_str().into()),
|
||||
Rule::literal => StackReplacement::Literal(parse_literal(inner)),
|
||||
Rule::variable => {
|
||||
StackReplacement::MacroVar(inner.into_inner().next().unwrap().as_str().into())
|
||||
|
||||
@ -5,7 +5,8 @@ use std::collections::{BinaryHeap, HashMap};
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::cpu::columns::NUM_CPU_COLUMNS;
|
||||
use crate::cpu::kernel::ast::{Item, Literal, PushTarget, StackReplacement};
|
||||
use crate::cpu::kernel::assembler::BYTES_PER_OFFSET;
|
||||
use crate::cpu::kernel::ast::{Item, PushTarget, StackReplacement};
|
||||
use crate::cpu::kernel::stack_manipulation::StackOp::Pop;
|
||||
use crate::memory;
|
||||
|
||||
@ -22,22 +23,24 @@ pub(crate) fn expand_stack_manipulation(body: Vec<Item>) -> Vec<Item> {
|
||||
}
|
||||
|
||||
fn expand(names: Vec<String>, replacements: Vec<StackReplacement>) -> Vec<Item> {
|
||||
let mut src = names.into_iter().map(StackItem::NamedItem).collect_vec();
|
||||
|
||||
let unique_literals = replacements
|
||||
let mut src = names
|
||||
.iter()
|
||||
.filter_map(|item| match item {
|
||||
StackReplacement::Literal(n) => Some(n.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.unique()
|
||||
.cloned()
|
||||
.map(StackItem::NamedItem)
|
||||
.collect_vec();
|
||||
|
||||
let mut dst = replacements
|
||||
.into_iter()
|
||||
.map(|item| match item {
|
||||
StackReplacement::NamedItem(name) => StackItem::NamedItem(name),
|
||||
StackReplacement::Literal(n) => StackItem::Literal(n),
|
||||
StackReplacement::Identifier(name) => {
|
||||
// May be either a named item or a label. Named items have precedence.
|
||||
if names.contains(&name) {
|
||||
StackItem::NamedItem(name)
|
||||
} else {
|
||||
StackItem::PushTarget(PushTarget::Label(name))
|
||||
}
|
||||
}
|
||||
StackReplacement::Literal(n) => StackItem::PushTarget(PushTarget::Literal(n)),
|
||||
StackReplacement::MacroVar(_) | StackReplacement::Constant(_) => {
|
||||
panic!("Should have been expanded already: {:?}", item)
|
||||
}
|
||||
@ -49,7 +52,16 @@ fn expand(names: Vec<String>, replacements: Vec<StackReplacement>) -> Vec<Item>
|
||||
src.reverse();
|
||||
dst.reverse();
|
||||
|
||||
let path = shortest_path(src, dst, unique_literals);
|
||||
let unique_push_targets = dst
|
||||
.iter()
|
||||
.filter_map(|item| match item {
|
||||
StackItem::PushTarget(target) => Some(target.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.unique()
|
||||
.collect_vec();
|
||||
|
||||
let path = shortest_path(src, dst, unique_push_targets);
|
||||
path.into_iter().map(StackOp::into_item).collect()
|
||||
}
|
||||
|
||||
@ -58,7 +70,7 @@ fn expand(names: Vec<String>, replacements: Vec<StackReplacement>) -> Vec<Item>
|
||||
fn shortest_path(
|
||||
src: Vec<StackItem>,
|
||||
dst: Vec<StackItem>,
|
||||
unique_literals: Vec<Literal>,
|
||||
unique_push_targets: Vec<PushTarget>,
|
||||
) -> Vec<StackOp> {
|
||||
// Nodes to visit, starting with the lowest-cost node.
|
||||
let mut queue = BinaryHeap::new();
|
||||
@ -93,7 +105,7 @@ fn shortest_path(
|
||||
continue;
|
||||
}
|
||||
|
||||
for op in next_ops(&node.stack, &dst, &unique_literals) {
|
||||
for op in next_ops(&node.stack, &dst, &unique_push_targets) {
|
||||
let neighbor = match op.apply_to(node.stack.clone()) {
|
||||
Some(n) => n,
|
||||
None => continue,
|
||||
@ -151,19 +163,23 @@ impl Ord for Node {
|
||||
#[derive(Eq, PartialEq, Hash, Clone, Debug)]
|
||||
enum StackItem {
|
||||
NamedItem(String),
|
||||
Literal(Literal),
|
||||
PushTarget(PushTarget),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum StackOp {
|
||||
Push(Literal),
|
||||
Push(PushTarget),
|
||||
Pop,
|
||||
Dup(u8),
|
||||
Swap(u8),
|
||||
}
|
||||
|
||||
/// A set of candidate operations to consider for the next step in the path from `src` to `dst`.
|
||||
fn next_ops(src: &[StackItem], dst: &[StackItem], unique_literals: &[Literal]) -> Vec<StackOp> {
|
||||
fn next_ops(
|
||||
src: &[StackItem],
|
||||
dst: &[StackItem],
|
||||
unique_push_targets: &[PushTarget],
|
||||
) -> Vec<StackOp> {
|
||||
if let Some(top) = src.last() && !dst.contains(top) {
|
||||
// If the top of src doesn't appear in dst, don't bother with anything other than a POP.
|
||||
return vec![StackOp::Pop]
|
||||
@ -172,12 +188,12 @@ fn next_ops(src: &[StackItem], dst: &[StackItem], unique_literals: &[Literal]) -
|
||||
let mut ops = vec![StackOp::Pop];
|
||||
|
||||
ops.extend(
|
||||
unique_literals
|
||||
unique_push_targets
|
||||
.iter()
|
||||
// Only consider pushing this literal if we need more occurrences of it, otherwise swaps
|
||||
// Only consider pushing this target if we need more occurrences of it, otherwise swaps
|
||||
// will be a better way to rearrange the existing occurrences as needed.
|
||||
.filter(|lit| {
|
||||
let item = StackItem::Literal((*lit).clone());
|
||||
.filter(|push_target| {
|
||||
let item = StackItem::PushTarget((*push_target).clone());
|
||||
let src_count = src.iter().filter(|x| **x == item).count();
|
||||
let dst_count = dst.iter().filter(|x| **x == item).count();
|
||||
src_count < dst_count
|
||||
@ -209,8 +225,14 @@ fn next_ops(src: &[StackItem], dst: &[StackItem], unique_literals: &[Literal]) -
|
||||
impl StackOp {
|
||||
fn cost(&self) -> u32 {
|
||||
let (cpu_rows, memory_rows) = match self {
|
||||
StackOp::Push(n) => {
|
||||
let bytes = n.to_trimmed_be_bytes().len() as u32;
|
||||
StackOp::Push(target) => {
|
||||
let bytes = match target {
|
||||
PushTarget::Literal(n) => n.to_trimmed_be_bytes().len() as u32,
|
||||
PushTarget::Label(_) => BYTES_PER_OFFSET as u32,
|
||||
PushTarget::MacroVar(_) | PushTarget::Constant(_) => {
|
||||
panic!("Target should have been expanded already: {:?}", target)
|
||||
}
|
||||
};
|
||||
// This is just a rough estimate; we can update it after implementing PUSH.
|
||||
(bytes, bytes)
|
||||
}
|
||||
@ -232,8 +254,8 @@ impl StackOp {
|
||||
fn apply_to(&self, mut stack: Vec<StackItem>) -> Option<Vec<StackItem>> {
|
||||
let len = stack.len();
|
||||
match self {
|
||||
StackOp::Push(n) => {
|
||||
stack.push(StackItem::Literal(n.clone()));
|
||||
StackOp::Push(target) => {
|
||||
stack.push(StackItem::PushTarget(target.clone()));
|
||||
}
|
||||
Pop => {
|
||||
stack.pop()?;
|
||||
@ -253,7 +275,7 @@ impl StackOp {
|
||||
|
||||
fn into_item(self) -> Item {
|
||||
match self {
|
||||
StackOp::Push(n) => Item::Push(PushTarget::Literal(n)),
|
||||
StackOp::Push(target) => Item::Push(target),
|
||||
Pop => Item::StandardOp("POP".into()),
|
||||
StackOp::Dup(n) => Item::StandardOp(format!("DUP{}", n)),
|
||||
StackOp::Swap(n) => Item::StandardOp(format!("SWAP{}", n)),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user