Properly process multiple asm files.

This commit is contained in:
chriseth
2023-04-20 21:55:42 +02:00
parent f5dd2fa110
commit 2678b03a93
4 changed files with 410 additions and 212 deletions

View File

@@ -1,32 +1,39 @@
use std::collections::BTreeMap;
use std::collections::{BTreeMap, BTreeSet, HashSet};
use lazy_static::lazy_static;
use regex::Regex;
use itertools::Itertools;
use crate::riscv::parser::{self, Argument, Register, Statement};
use super::parser::Constant;
/// Compiles riscv assembly to POWDR assembly. Adds required library routines.
pub fn compile_riscv_asm(data: &str) -> String {
pub fn compile_riscv_asm(mut assemblies: BTreeMap<String, String>) -> String {
// stack grows towards zero
let stack_start = 0x10000;
// data grows away from zero
let data_start = 0x20000;
let statements = parser::parse_asm(data);
let labels = parser::extract_labels(&statements);
let label_references = parser::extract_label_references(&statements);
let missing_labels = label_references.difference(&labels);
assert!(assemblies
.insert("__runtime".to_string(), runtime().to_string())
.is_none());
let data = data.to_string()
+ &missing_labels
.into_iter()
.map(|label| library_routine(label))
.collect::<Vec<_>>()
.join("\n");
let mut statements = parser::parse_asm(&data);
let objects = parser::extract_data_objects(&statements);
let parsed_assemblies = assemblies
.into_iter()
.map(|(name, contents)| (name, parser::parse_asm(&contents)))
.collect::<Vec<_>>();
let globals = parsed_assemblies
.iter()
.flat_map(|(_, statements)| extract_globals(statements))
.collect::<HashSet<_>>();
let mut statements = parsed_assemblies
.into_iter()
.map(|(name, statements)| disambiguate(&name, statements, &globals))
.concat();
let mut objects = parser::extract_data_objects(&statements);
// Reduce to the code that is actually reachable from main
// (and the objects that are referred from there)
filter_reachable_from("main", &mut statements, &mut objects);
// Replace dynamic references to code labels
replace_dynamic_label_references(&mut statements, &objects);
@@ -45,6 +52,191 @@ pub fn compile_riscv_asm(data: &str) -> String {
output
}
fn disambiguate(
file_name: &str,
statements: Vec<Statement>,
globals: &HashSet<String>,
) -> Vec<Statement> {
let prefix = file_name.replace('-', "_dash_");
statements
.into_iter()
.map(|s| match s {
Statement::Label(l) => {
Statement::Label(disambiguate_symbol_if_needed(l, &prefix, globals))
}
Statement::Directive(dir, args) => Statement::Directive(
dir,
disambiguate_arguments_if_needed(args, &prefix, globals),
),
Statement::Instruction(instr, args) => Statement::Instruction(
instr,
disambiguate_arguments_if_needed(args, &prefix, globals),
),
})
.collect()
}
fn disambiguate_arguments_if_needed(
args: Vec<Argument>,
prefix: &str,
globals: &HashSet<String>,
) -> Vec<Argument> {
args.into_iter()
.map(|a| disambiguate_argument_if_needed(a, prefix, globals))
.collect()
}
fn disambiguate_argument_if_needed(
arg: Argument,
prefix: &str,
globals: &HashSet<String>,
) -> Argument {
match arg {
Argument::Register(_) | Argument::StringLiteral(_) => arg,
Argument::RegOffset(reg, constant) => Argument::RegOffset(
reg,
disambiguate_constant_if_needed(constant, prefix, globals),
),
Argument::Constant(c) => {
Argument::Constant(disambiguate_constant_if_needed(c, prefix, globals))
}
Argument::Symbol(s) => Argument::Symbol(disambiguate_symbol_if_needed(s, prefix, globals)),
Argument::Difference(l, r) => Argument::Difference(
disambiguate_symbol_if_needed(l, prefix, globals),
disambiguate_symbol_if_needed(r, prefix, globals),
),
}
}
fn disambiguate_constant_if_needed(
c: Constant,
prefix: &str,
globals: &HashSet<String>,
) -> Constant {
match c {
Constant::Number(_) => c,
Constant::HiDataRef(s) => {
Constant::HiDataRef(disambiguate_symbol_if_needed(s, prefix, globals))
}
Constant::LoDataRef(s) => {
Constant::LoDataRef(disambiguate_symbol_if_needed(s, prefix, globals))
}
}
}
fn disambiguate_symbol_if_needed(s: String, prefix: &str, globals: &HashSet<String>) -> String {
if globals.contains(s.as_str()) || s.starts_with('@') {
s
} else {
format!("{prefix}__{s}")
}
}
fn extract_globals(statements: &[Statement]) -> HashSet<String> {
statements
.iter()
.flat_map(|s| {
if let Statement::Directive(name, args) = s {
if name == ".globl" {
return args
.iter()
.map(|a| {
if let Argument::Symbol(s) = a {
s.clone()
} else {
panic!("Invalid .globl directive: {s}");
}
})
// TODO possible wihtout collect?
.collect();
}
}
vec![]
})
.collect()
}
fn filter_reachable_from(
label: &str,
statements: &mut Vec<Statement>,
objects: &mut BTreeMap<String, Vec<u8>>,
) {
let label_offsets = parser::extract_label_offsets(statements);
let mut queued_labels: BTreeSet<&str> = vec![label].into_iter().collect();
let mut referenced_labels: BTreeSet<&str> = vec![label].into_iter().collect();
let mut processed_labels = BTreeSet::<&str>::new();
// Labels that are included in a basic block that starts with a different label.
let mut secondary_labels = BTreeSet::<&str>::new();
let mut label_queue = vec![label];
while let Some(l) = label_queue.pop() {
if objects.contains_key(l) {
// We record but do not process references to objects
continue;
}
if !processed_labels.insert(l) {
continue;
}
let offset = *label_offsets.get(l).unwrap_or_else(|| {
eprintln!("The RISCV assembly code references an external routine / label that is not available:");
eprintln!("{l}");
panic!();
});
let (referenced_labels_in_block, seen_labels_in_block) =
basic_block_references_starting_from(&statements[offset..]);
assert!(!secondary_labels.contains(l));
secondary_labels.extend(seen_labels_in_block.clone());
secondary_labels.remove(l);
processed_labels.extend(seen_labels_in_block);
for referenced in &referenced_labels_in_block {
if !queued_labels.contains(referenced) && !processed_labels.contains(referenced) {
label_queue.push(referenced);
queued_labels.insert(referenced);
}
}
referenced_labels.extend(referenced_labels_in_block);
}
objects.retain(|name, _value| referenced_labels.contains(name.as_str()));
let code = processed_labels
.difference(&secondary_labels)
.flat_map(|l| {
let offset = *label_offsets.get(l).unwrap();
basic_block_code_starting_from(&statements[offset..])
})
.collect();
*statements = code;
}
fn basic_block_references_starting_from(statements: &[Statement]) -> (Vec<&str>, Vec<&str>) {
let mut seen_labels = vec![];
let mut referenced_labels = BTreeSet::<&str>::new();
for s in statements {
if let Statement::Label(l) = s {
seen_labels.push(l.as_str());
} else {
referenced_labels.extend(parser::referenced_labels(s))
}
if parser::ends_control_flow(s) {
break;
}
}
(referenced_labels.into_iter().collect(), seen_labels)
}
fn basic_block_code_starting_from(statements: &[Statement]) -> Vec<Statement> {
let mut code = vec![];
for s in statements {
if let Statement::Directive(_, _) = s {
panic!("Included directive in code block: {s}");
}
code.push(s.clone());
if parser::ends_control_flow(s) {
break;
}
}
code
}
/// Replace certain patterns of references to code labels by
/// special instructions. We ignore any references to data objects
/// because they will be handled differently.
@@ -448,127 +640,96 @@ pil{
"#
}
lazy_static! {
static ref LIBRARY_ROUTINES: Vec<(Regex, &'static str)> = vec![
(
Regex::new(r"^_ZN4core9panicking18panic_bounds_check17h[0-9a-f]{16}E$").unwrap(),
"unimp"
),
(
Regex::new(r"^_ZN4core9panicking5panic17h[0-9a-f]{16}E$").unwrap(),
"unimp"
),
(
Regex::new(r"^_ZN4core5slice5index24slice_end_index_len_fail17h[0-9a-f]{16}E$")
.unwrap(),
"unimp"
),
(
Regex::new(r"^_ZN5alloc5alloc18handle_alloc_error17h[0-9a-f]{16}E$").unwrap(),
"unimp"
),
(
Regex::new(r"^_ZN5alloc7raw_vec17capacity_overflow17h[0-9a-f]{16}E$").unwrap(),
"unimp"
),
(
Regex::new(r"^_ZN5alloc7raw_vec17capacity_overflow17h[0-9a-f]{16}E$").unwrap(),
"unimp"
),
(
Regex::new(r"^_ZN5alloc5alloc18handle_alloc_error17h[0-9a-f]{16}E$").unwrap(),
"unimp"
),
(
Regex::new(r"^_ZN4core5slice5index26slice_start_index_len_fail17h[0-9a-f]{16}E$")
.unwrap(),
"unimp"
),
// TODO rust alloc calls the global allocator - not sure why this is not automatic.
(Regex::new(r"^__rust_alloc$").unwrap(), "j __rg_alloc"),
(Regex::new(r"^__rust_realloc$").unwrap(), "j __rg_realloc"),
(Regex::new(r"^__rust_dealloc$").unwrap(), "j __rg_dealloc"),
(
Regex::new(r"^memset@plt$").unwrap(),
/* Source cod efor memset:
// TODO c is ussualy a "c int"
pub unsafe extern "C" fn memset(s: *mut u8, c: u8, n: usize) -> *mut u8 {
// We only access u32 because then we do not have to deal with
// un-aligned memory access.
// TODO this does not really enforce that the pointers are u32-aligned.
let mut value = c as u32;
value = value | (value << 8) | (value << 16) | (value << 24);
let mut i: isize = 0;
while i + 3 < n as isize {
*((s.offset(i)) as *mut u32) = value;
i += 4;
fn runtime() -> &'static str {
// // TODO rust alloc calls the global allocator - not sure why this is not automatic.
// (Regex::new(r"^__rust_alloc$").unwrap(), "j __rg_alloc"),
// (Regex::new(r"^__rust_realloc$").unwrap(), "j __rg_realloc"),
// (Regex::new(r"^__rust_dealloc$").unwrap(), "j __rg_dealloc"),
// (
/* Source code:
// TODO c is usually a "c int"
pub unsafe extern "C" fn memset(s: *mut u8, c: u8, n: usize) -> *mut u8 {
// We only access u32 because then we do not have to deal with
// un-aligned memory access.
// TODO this does not really enforce that the pointers are u32-aligned.
let mut value = c as u32;
value = value | (value << 8) | (value << 16) | (value << 24);
let mut i: isize = 0;
while i + 3 < n as isize {
*((s.offset(i)) as *mut u32) = value;
i += 4;
}
if i < n {
let dest_value = (s.offset(i)) as *mut u32;
let mask = (1 << ((((n as isize) - i) * 8) as u32)) - 1;
*dest_value = (*dest_value & !mask) | (value & mask);
}
s
}
if i < n {
let dest_value = (s.offset(i)) as *mut u32;
let mask = (1 << ((((n as isize) - i) * 8) as u32)) - 1;
*dest_value = (*dest_value & !mask) | (value & mask);
pub unsafe extern "C" fn memcpy(dest: *mut u8, src: *const u8, n: usize) -> *mut u8 {
// We only access u32 because then we do not have to deal with
// un-aligned memory access.
// TODO this does not really enforce that the pointers are u32-aligned.
let mut i: isize = 0;
while i + 3 < n as isize {
*((dest.offset(i)) as *mut u32) = *((src.offset(i)) as *mut u32);
i += 4;
}
if i < n as isize {
let value = *((src.offset(i)) as *mut u32);
let dest_value = (dest.offset(i)) as *mut u32;
let mask = (1 << (((n as isize - i) * 8) as u32)) - 1;
*dest_value = (*dest_value & !mask) | (value & mask);
}
dest
}
s
}
*/
r#"
li a3, 4
blt a2, a3, __memset_LBB5_5
li a5, 0
lui a3, 4112
addi a3, a3, 257
mul a6, a1, a3
*/
r#"
.globl rust_begin_unwind
rust_begin_unwind:
unimp
.globl memset@plt
memset@plt:
li a3, 4
blt a2, a3, __memset_LBB5_5
li a5, 0
lui a3, 4112
addi a3, a3, 257
mul a6, a1, a3
__memset_LBB5_2:
add a7, a0, a5
addi a3, a5, 4
addi a4, a5, 7
sw a6, 0(a7)
mv a5, a3
blt a4, a2, __memset_LBB5_2
bge a3, a2, __memset_LBB5_6
add a7, a0, a5
addi a3, a5, 4
addi a4, a5, 7
sw a6, 0(a7)
mv a5, a3
blt a4, a2, __memset_LBB5_2
bge a3, a2, __memset_LBB5_6
__memset_LBB5_4:
lui a4, 16
addi a4, a4, 257
mul a1, a1, a4
add a3, a3, a0
slli a2, a2, 3
lw a4, 0(a3)
li a5, -1
sll a2, a5, a2
not a5, a2
and a2, a2, a4
and a1, a1, a5
or a1, a1, a2
sw a1, 0(a3)
ret
lui a4, 16
addi a4, a4, 257
mul a1, a1, a4
add a3, a3, a0
slli a2, a2, 3
lw a4, 0(a3)
li a5, -1
sll a2, a5, a2
not a5, a2
and a2, a2, a4
and a1, a1, a5
or a1, a1, a2
sw a1, 0(a3)
ret
__memset_LBB5_5:
li a3, 0
blt a3, a2, __memset_LBB5_4
li a3, 0
blt a3, a2, __memset_LBB5_4
__memset_LBB5_6:
ret"#
),
(
Regex::new(r"^memcpy@plt$").unwrap(),
/* Source code for memcpy:
pub unsafe extern "C" fn memcpy(dest: *mut u8, src: *const u8, n: usize) -> *mut u8 {
// We only access u32 because then we do not have to deal with
// un-aligned memory access.
// TODO this does not really enforce that the pointers are u32-aligned.
let mut i: isize = 0;
while i + 3 < n as isize {
*((dest.offset(i)) as *mut u32) = *((src.offset(i)) as *mut u32);
i += 4;
}
if i < n as isize {
let value = *((src.offset(i)) as *mut u32);
let dest_value = (dest.offset(i)) as *mut u32;
let mask = (1 << (((n as isize - i) * 8) as u32)) - 1;
*dest_value = (*dest_value & !mask) | (value & mask);
}
dest
}
*/
r#"
ret
.globl memcpy@plt
memcpy@plt:
li a3, 4
blt a2, a3, __memcpy_LBB2_5
li a4, 0
@@ -602,19 +763,6 @@ __memcpy_LBB2_5:
__memcpy_LBB2_6:
ret
"#
),
];
}
fn library_routine(label: &str) -> String {
for (pattern, routine) in LIBRARY_ROUTINES.iter() {
if pattern.is_match(label) {
return format!("{label}:\n{routine}");
}
}
eprintln!("The RISCV assembly code references an external routine / label that has not been implemented yet:");
eprintln!("{label}");
panic!();
}
fn process_statement(s: Statement) -> String {

View File

@@ -1,4 +1,4 @@
use std::{path::Path, process::Command};
use std::{collections::BTreeMap, path::Path, process::Command};
use mktemp::Temp;
use std::fs;
@@ -28,41 +28,33 @@ pub fn compile_rust(
} else {
compile_rust_to_riscv_asm(file_name)
};
let riscv_asm_file_name = output_dir.join(format!(
"{}_riscv.asm",
Path::new(file_name).file_stem().unwrap().to_str().unwrap()
));
if riscv_asm_file_name.exists() && !force_overwrite {
eprint!(
"Target file {} already exists. Not overwriting.",
riscv_asm_file_name.to_str().unwrap()
);
return;
}
fs::write(riscv_asm_file_name.clone(), riscv_asm).unwrap();
log::info!("Wrote {}", riscv_asm_file_name.to_str().unwrap());
for (asm_file_name, contents) in &riscv_asm {
let riscv_asm_file_name = output_dir.join(format!(
"{}_riscv_{asm_file_name}.asm",
Path::new(file_name).file_stem().unwrap().to_str().unwrap(),
));
if riscv_asm_file_name.exists() && !force_overwrite {
eprint!(
"Target file {} already exists. Not overwriting.",
riscv_asm_file_name.to_str().unwrap()
);
return;
}
compile_riscv_asm(
file_name,
riscv_asm_file_name.to_str().unwrap(),
inputs,
output_dir,
force_overwrite,
)
fs::write(riscv_asm_file_name.clone(), contents).unwrap();
log::info!("Wrote {}", riscv_asm_file_name.to_str().unwrap());
}
compile_riscv_asm_bundle(file_name, riscv_asm, inputs, output_dir, force_overwrite)
}
/// Compiles a riscv asm file all the way down to PIL and generates
/// fixed and witness columns.
/// Adds required library routines automatically.
pub fn compile_riscv_asm(
pub fn compile_riscv_asm_bundle(
original_file_name: &str,
file_name: &str,
riscv_asm_files: BTreeMap<String, String>,
inputs: Vec<FieldElement>,
output_dir: &Path,
force_overwrite: bool,
) {
let contents = fs::read_to_string(file_name).unwrap();
let powdr_asm = compiler::compile_riscv_asm(&contents);
let powdr_asm_file_name = output_dir.join(format!(
"{}.asm",
Path::new(original_file_name)
@@ -78,6 +70,9 @@ pub fn compile_riscv_asm(
);
return;
}
let powdr_asm = compiler::compile_riscv_asm(riscv_asm_files);
fs::write(powdr_asm_file_name.clone(), &powdr_asm).unwrap();
log::info!("Wrote {}", powdr_asm_file_name.to_str().unwrap());
@@ -90,28 +85,56 @@ pub fn compile_riscv_asm(
)
}
pub fn compile_rust_to_riscv_asm(input_file: &str) -> String {
let temp_file = Temp::new_file().unwrap();
let rustc_status = Command::new("rustc")
.args([
"--target",
"riscv32imc-unknown-none-elf",
"--crate-type",
"lib",
"--emit=asm",
"-C",
"opt-level=3",
"-o",
temp_file.to_str().unwrap(),
input_file,
])
.status()
.unwrap();
assert!(rustc_status.success());
fs::read_to_string(temp_file.to_str().unwrap()).unwrap()
/// Compiles a riscv asm file all the way down to PIL and generates
/// fixed and witness columns.
pub fn compile_riscv_asm(
original_file_name: &str,
file_name: &str,
inputs: Vec<FieldElement>,
output_dir: &Path,
force_overwrite: bool,
) {
let contents = fs::read_to_string(file_name).unwrap();
compile_riscv_asm_bundle(
original_file_name,
vec![(file_name.to_string(), contents)]
.into_iter()
.collect(),
inputs,
output_dir,
force_overwrite,
)
}
pub fn compile_rust_crate_to_riscv_asm(input_dir: &str) -> String {
pub fn compile_rust_to_riscv_asm(input_file: &str) -> BTreeMap<String, String> {
let crate_dir = Temp::new_dir().unwrap();
// TODO is there no easier way?
let mut cargo_file = crate_dir.clone();
cargo_file.push("Cargo.toml");
fs::write(
&cargo_file,
format!(
r#"[package]
name = "{}"
version = "0.1.0"
edition = "2021"
"#,
Path::new(input_file).file_stem().unwrap().to_str().unwrap()
),
)
.unwrap();
let mut src_file = crate_dir.clone();
src_file.push("src");
fs::create_dir(&src_file).unwrap();
src_file.push("lib.rs");
fs::write(src_file, fs::read_to_string(input_file).unwrap()).unwrap();
compile_rust_crate_to_riscv_asm(cargo_file.to_str().unwrap())
}
pub fn compile_rust_crate_to_riscv_asm(input_dir: &str) -> BTreeMap<String, String> {
let temp_dir = Temp::new_dir().unwrap();
let cargo_status = Command::new("cargo")
@@ -119,6 +142,8 @@ pub fn compile_rust_crate_to_riscv_asm(input_dir: &str) -> String {
.args([
"build",
"--release",
"-Z",
"build-std=core",
"--target",
"riscv32imc-unknown-none-elf",
"--lib",
@@ -131,13 +156,19 @@ pub fn compile_rust_crate_to_riscv_asm(input_dir: &str) -> String {
.unwrap();
assert!(cargo_status.success());
let mut combined_assembly = String::new();
let mut assemblies = BTreeMap::new();
for entry in WalkDir::new(&temp_dir) {
let entry = entry.unwrap();
// TODO search only in certain subdir?
if entry.file_name().to_str().unwrap().ends_with(".s") {
combined_assembly += &fs::read_to_string(entry.path()).unwrap();
let file_name = entry.file_name().to_str().unwrap();
if let Some(name) = file_name.strip_suffix(".s") {
assert!(
assemblies
.insert(name.to_string(), fs::read_to_string(entry.path()).unwrap())
.is_none(),
"Duplicate assembly file name: {name}"
);
}
}
combined_assembly
assemblies
}

View File

@@ -42,7 +42,7 @@ impl Display for Statement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Statement::Label(l) => writeln!(f, "{l}:"),
Statement::Directive(d, args) => writeln!(f, " .{d} {}", format_arguments(args)),
Statement::Directive(d, args) => writeln!(f, " {d} {}", format_arguments(args)),
Statement::Instruction(i, args) => writeln!(f, " {i} {}", format_arguments(args)),
}
}
@@ -102,22 +102,23 @@ pub fn parse_asm(input: &str) -> Vec<Statement> {
.collect()
}
pub fn extract_labels(statements: &[Statement]) -> BTreeSet<&str> {
pub fn extract_label_offsets(statements: &[Statement]) -> BTreeMap<&str, usize> {
statements
.iter()
.filter_map(|s| match s {
Statement::Label(l) => Some(l.as_str()),
.enumerate()
.filter_map(|(i, s)| match s {
Statement::Label(l) => Some((l.as_str(), i)),
Statement::Directive(_, _) | Statement::Instruction(_, _) => None,
})
.collect()
}
pub fn extract_label_references(statements: &[Statement]) -> BTreeSet<&str> {
statements
.iter()
.flat_map(|s| match s {
Statement::Label(_) | Statement::Directive(_, _) => None,
Statement::Instruction(_, args) => Some(args.iter().filter_map(|arg| match arg {
pub fn referenced_labels(statement: &Statement) -> BTreeSet<&str> {
match statement {
Statement::Label(_) | Statement::Directive(_, _) => Default::default(),
Statement::Instruction(_, args) => args
.iter()
.filter_map(|arg| match arg {
Argument::Register(_) | Argument::StringLiteral(_) => None,
Argument::Symbol(s) => Some(s.as_str()),
Argument::RegOffset(_, c) | Argument::Constant(c) => match c {
@@ -125,10 +126,28 @@ pub fn extract_label_references(statements: &[Statement]) -> BTreeSet<&str> {
Constant::HiDataRef(s) | Constant::LoDataRef(s) => Some(s.as_str()),
},
Argument::Difference(_, _) => todo!(),
})),
})
.flatten()
.collect()
})
.collect(),
}
}
pub fn ends_control_flow(s: &Statement) -> bool {
match s {
Statement::Instruction(instruction, _) => match instruction.as_str() {
"li" | "lui" | "mv" | "add" | "addi" | "sub" | "neg" | "mul" | "mulhu" | "xor"
| "xori" | "and" | "andi" | "or" | "ori" | "not" | "slli" | "sll" | "srli" | "srl"
| "seqz" | "snez" | "slti" | "sltu" | "sltiu" | "beq" | "beqz" | "bgeu" | "bltu"
| "blt" | "bge" | "bltz" | "blez" | "bgtz" | "bgez" | "bne" | "bnez" | "jal"
| "jalr" | "call" | "ecall" | "ebreak" | "lw" | "lb" | "lbu" | "sw" | "sh" | "sb" => {
false
}
"j" | "jr" | "tail" | "ret" | "unimp" => true,
_ => {
panic!("Unknown instruction: {instruction}");
}
},
_ => false,
}
}
pub fn extract_data_objects(statements: &[Statement]) -> BTreeMap<String, Vec<u8>> {

View File

@@ -61,7 +61,7 @@ fn test_keccak() {
fn verify_file(case: &str, inputs: Vec<FieldElement>) {
let riscv_asm = powdr::riscv::compile_rust_to_riscv_asm(&format!("tests/riscv_data/{case}"));
let powdr_asm = powdr::riscv::compiler::compile_riscv_asm(&riscv_asm);
let powdr_asm = powdr::riscv::compiler::compile_riscv_asm(riscv_asm);
verify_asm_string(&format!("{case}.asm"), &powdr_asm, inputs);
}
@@ -70,7 +70,7 @@ fn verify_crate(case: &str, inputs: Vec<FieldElement>) {
let riscv_asm = powdr::riscv::compile_rust_crate_to_riscv_asm(&format!(
"tests/riscv_data/{case}/Cargo.toml"
));
let powdr_asm = powdr::riscv::compiler::compile_riscv_asm(&riscv_asm);
let powdr_asm = powdr::riscv::compiler::compile_riscv_asm(riscv_asm);
verify_asm_string(&format!("{case}.asm"), &powdr_asm, inputs);
}