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

trying reuse filetests #211

Merged
merged 11 commits into from
Feb 22, 2024
Merged
18 changes: 18 additions & 0 deletions cranelift/filetests/filetests/runtests/simple.clif

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use existing arithmetic.clif or is there some reason it wouldn't work?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arithmetic.clif panics for now, and changes to fix it worth separate PR

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
test run-zkasm


function %add_i64(i64, i64) -> i64 {
block0(v0: i64,v1: i64):
v2 = iadd v0, v1
return v2
}
; run: %add_i64(0, 0) == 0
; run: %add_i64(0, 1) == 1

function %sub_i64(i64, i64) -> i64 {
block0(v0: i64,v1: i64):
v2 = isub v0, v1
return v2
}
; run: %sub_i64(0, 0) == 0
; run: %sub_i64(0, 1) == -1
2 changes: 2 additions & 0 deletions cranelift/filetests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ mod test_legalizer;
mod test_optimize;
mod test_print_cfg;
mod test_run;
mod test_run_zkasm;
mod test_safepoint;
mod test_unwind;
mod test_verifier;
Expand Down Expand Up @@ -103,6 +104,7 @@ fn new_subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn subtest::SubTest>
"safepoint" => test_safepoint::subtest(parsed),
"unwind" => test_unwind::subtest(parsed),
"verifier" => test_verifier::subtest(parsed),
"run-zkasm" => test_run_zkasm::subtest(parsed),
_ => anyhow::bail!("unknown test command '{}'", parsed.command),
}
}
Expand Down
85 changes: 85 additions & 0 deletions cranelift/filetests/src/test_run_zkasm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//! Test command for compiling CLIF files to .zkasm, running it, and verifying their results
//!
//! using [RunCommand](cranelift_reader::RunCommand)s.

use crate::runone::FileUpdate;
use crate::subtest::SubTest;
use cranelift_codegen::isa::TargetIsa;
use cranelift_codegen::settings::Flags;
use cranelift_codegen::{self, ir};
use cranelift_reader::{parse_run_command, RunCommand, TestCommand, TestFile};
use std::borrow::Cow;

use crate::zkasm_codegen;

struct TestRunZkasm;

pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
assert_eq!(parsed.command, "run-zkasm");
if !parsed.options.is_empty() {
anyhow::bail!("No options allowed on {}", parsed);
}
Ok(Box::new(TestRunZkasm))
}

impl SubTest for TestRunZkasm {
fn name(&self) -> &'static str {
"run-zkasm"
}

fn is_mutating(&self) -> bool {
false
}

fn needs_isa(&self) -> bool {
false
}

/// Runs the entire subtest for a given target
/// TODO: in zkasm we don't really run test for given target, we run for zkasm target
fn run_target<'a>(
&self,
testfile: &TestFile,
_: &mut FileUpdate,
_: &'a str,
_: &'a Flags,
_: Option<&'a dyn TargetIsa>,
) -> anyhow::Result<()> {
let mut zkasm_functions: Vec<Vec<String>> = Vec::new();
let mut invocations: Vec<Vec<String>> = Vec::new();
for (func, details) in &testfile.functions {
zkasm_functions.push(zkasm_codegen::compile_clif_function(func));
for comment in details.comments.iter() {
if let Some(command) = parse_run_command(comment.text, &func.signature)? {
match command {
RunCommand::Print(_) => {
todo!()
}
RunCommand::Run(invoke, compare, expected) => {
invocations
.push(zkasm_codegen::compile_invocation(invoke, compare, expected));
}
}
}
}
}
let zkasm_program = zkasm_codegen::build_test_zkasm(zkasm_functions, invocations);
println!("{}", zkasm_program);
// TODO: instead of printing run program, using something like this:
// match zkasm_runner::run_zkasm(&zkasm_program) {
// // TODO: Probably here is a good place to collect info generated by assert-hostfunction
// // and somehow show it
// Ok(_) => Ok(()),
// Err(e) => Err(e),
// }
Ok(())
}

fn run(
&self,
_func: Cow<ir::Function>,
_context: &crate::subtest::Context,
) -> anyhow::Result<()> {
unreachable!()
}
}
90 changes: 90 additions & 0 deletions cranelift/filetests/src/zkasm_codegen.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
use cranelift_codegen::data_value::DataValue;
use cranelift_codegen::entity::EntityRef;
use cranelift_codegen::ir::function::FunctionParameters;
use cranelift_codegen::ir::ExternalName;
use cranelift_codegen::ir::Function;
use cranelift_codegen::isa::zkasm;
use cranelift_codegen::{settings, FinalizedMachReloc, FinalizedRelocTarget};
use cranelift_reader::Comparison;
use cranelift_reader::Invocation;
use cranelift_wasm::{translate_module, ZkasmEnvironment};
use std::collections::HashMap;

Expand Down Expand Up @@ -231,3 +235,89 @@ fn optimize_labels(code: &[&str], func_index: usize) -> Vec<String> {
}
lines
}

// TODO: fix same label names in different functions
pub fn compile_clif_function(func: &Function) -> Vec<String> {
let flag_builder = settings::builder();
let isa_builder = zkasm::isa_builder("zkasm-unknown-unknown".parse().unwrap());
let isa = isa_builder
.finish(settings::Flags::new(flag_builder))
.unwrap();
let mut context = cranelift_codegen::Context::for_function(func.clone());
let compiled_code = context
.compile(isa.as_ref(), &mut Default::default())
.unwrap();
let mut code_buffer = compiled_code.code_buffer().to_vec();
fix_relocs(
&mut code_buffer,
&func.params,
compiled_code.buffer.relocs(),
);
let code = std::str::from_utf8(&code_buffer).unwrap();
let mut lines: Vec<String> = code.lines().map(|s| s.to_string()).collect();
// TODO: I believe it can be done more beautiful way
let mut funcname = func.name.to_string();
funcname.remove(0);
funcname.push(':');
let mut res = vec![funcname];
res.append(&mut lines);
res
}

// TODO: this function should be much rewrited,
// now it works for one very basic case:
// Simple progam which don't contain globals or some other speciefic preamble\postamble
// Program don't need helper functions (for example 2-exp.zkasm)
// How to fix it? Use generate_preamble and provide correct inputs for it.
pub fn build_test_zkasm(functions: Vec<Vec<String>>, invocations: Vec<Vec<String>>) -> String {
// TODO: use generate_preamble to get preamble
let preamble = "\
start:
zkPC + 2 => RR
:JMP(main)
:JMP(finalizeExecution)";
let mut main = vec![
"main:".to_string(),
" SP - 1 => SP".to_string(),
" RR :MSTORE(SP)".to_string(),
];
for invocation in invocations {
main.extend(invocation);
}
main.push(" SP - 1 => SP".to_string());
main.push(" :JMP(RR)".to_string());
let mut postamble = generate_postamble();
let mut program = vec![preamble.to_string()];
program.append(&mut main);
for foo in functions {
program.extend(foo);
}
program.append(&mut postamble);
program.join("\n")
}

pub fn compile_invocation(
invoke: Invocation,
compare: Comparison,
expected: Vec<DataValue>,
) -> Vec<String> {
// Here I assume that each "function" in zkasm gets it's arguments from first N registers
// and put result in A.
// TODO: should be more robust way to do it, we need somehow define inputs and outputs
let mut res: Vec<String> = Default::default();
let registers = vec!["A", "B", "C", "D", "E"];

let args = invoke.args;
let funcname = invoke.func;

// TODO: here we should pay attention to type of DataValue (I64 or I32)
for (idx, arg) in args.iter().enumerate() {
res.push(format!(" {} => {}", arg, registers[idx]))
}
res.push(format!(" :JMP({})", funcname));
// TODO: handle functions with multiple outputs
res.push(format!(" {} => B", expected[0]));
// TODO: replace with call to host function
res.push(format!(" CALL AWESOME ASSERT ({})", compare));
res
}
Loading