Skip to content

Commit

Permalink
Fix soundness bug in mul instruction of RISC-V machine
Browse files Browse the repository at this point in the history
  • Loading branch information
georgwiese committed Mar 19, 2024
1 parent af97517 commit 06866e4
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 64 deletions.
34 changes: 16 additions & 18 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,13 +476,6 @@ fn run_command(command: Commands) {
just_execute,
continuations,
} => {
let coprocessors = match coprocessors {
Some(list) => {
powdr_riscv::CoProcessors::try_from(list.split(',').collect::<Vec<_>>())
.unwrap()
}
None => powdr_riscv::CoProcessors::base(),
};
call_with_field!(run_rust::<field>(
&file,
split_inputs(&inputs),
Expand Down Expand Up @@ -518,13 +511,6 @@ fn run_command(command: Commands) {
Cow::Borrowed("output")
};

let coprocessors = match coprocessors {
Some(list) => {
powdr_riscv::CoProcessors::try_from(list.split(',').collect::<Vec<_>>())
.unwrap()
}
None => powdr_riscv::CoProcessors::base(),
};
call_with_field!(run_riscv_asm::<field>(
&name,
files.into_iter(),
Expand Down Expand Up @@ -681,11 +667,17 @@ fn run_rust<F: FieldElement>(
prove_with: Option<BackendType>,
export_csv: bool,
csv_mode: CsvRenderModeCLI,
coprocessors: powdr_riscv::CoProcessors,
coprocessors: Option<String>,
just_execute: bool,
continuations: bool,
) -> Result<(), Vec<String>> {
let (asm_file_path, asm_contents) = compile_rust(
let coprocessors = match coprocessors {
Some(list) => {
powdr_riscv::CoProcessors::try_from(list.split(',').collect::<Vec<_>>()).unwrap()
}
None => powdr_riscv::CoProcessors::base::<F>(),
};
let (asm_file_path, asm_contents) = compile_rust::<F>(
file_name,
output_dir,
force_overwrite,
Expand Down Expand Up @@ -724,11 +716,17 @@ fn run_riscv_asm<F: FieldElement>(
prove_with: Option<BackendType>,
export_csv: bool,
csv_mode: CsvRenderModeCLI,
coprocessors: powdr_riscv::CoProcessors,
coprocessors: Option<String>,
just_execute: bool,
continuations: bool,
) -> Result<(), Vec<String>> {
let (asm_file_path, asm_contents) = compile_riscv_asm(
let coprocessors = match coprocessors {
Some(list) => {
powdr_riscv::CoProcessors::try_from(list.split(',').collect::<Vec<_>>()).unwrap()
}
None => powdr_riscv::CoProcessors::base::<F>(),
};
let (asm_file_path, asm_contents) = compile_riscv_asm::<F>(
original_file_name,
file_names,
output_dir,
Expand Down
8 changes: 6 additions & 2 deletions pipeline/benches/executor_benchmark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ fn executor_benchmark(c: &mut Criterion) {
let tmp_dir = Temp::new_dir().unwrap();
let riscv_asm_files =
compile_rust_crate_to_riscv_asm("../riscv/tests/riscv_data/keccak/Cargo.toml", &tmp_dir);
let contents = compiler::compile(riscv_asm_files, &CoProcessors::base(), false);
let contents = compiler::compile::<T>(riscv_asm_files, &CoProcessors::base::<T>(), false);
let mut pipeline = Pipeline::<T>::default().from_asm_string(contents, None);
let pil = pipeline.compute_optimized_pil().unwrap();
let fixed_cols = pipeline.compute_fixed_cols().unwrap();
Expand All @@ -44,7 +44,11 @@ fn executor_benchmark(c: &mut Criterion) {
// The first chunk of `many_chunks`, with Poseidon co-processor & bootloader
let riscv_asm_files =
compile_rust_to_riscv_asm("../riscv/tests/riscv_data/many_chunks.rs", &tmp_dir);
let contents = compiler::compile(riscv_asm_files, &CoProcessors::base().with_poseidon(), true);
let contents = compiler::compile::<T>(
riscv_asm_files,
&CoProcessors::base::<T>().with_poseidon(),
true,
);
let mut pipeline = Pipeline::<T>::default().from_asm_string(contents, None);
let pil = pipeline.compute_optimized_pil().unwrap();
let fixed_cols = pipeline.compute_fixed_cols().unwrap();
Expand Down
34 changes: 31 additions & 3 deletions riscv/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use powdr_asm_utils::{
},
Architecture,
};
use powdr_number::{FieldElement, KnownField};

use crate::continuations::bootloader::{bootloader_and_shutdown_routine, bootloader_preamble};
use crate::coprocessors::*;
Expand Down Expand Up @@ -102,7 +103,7 @@ impl Architecture for RiscvArchitecture {
}

/// Compiles riscv assembly to a powdr assembly file. Adds required library routines.
pub fn compile(
pub fn compile<T: FieldElement>(
mut assemblies: BTreeMap<String, String>,
coprocessors: &CoProcessors,
with_bootloader: bool,
Expand Down Expand Up @@ -258,7 +259,7 @@ pub fn compile(

riscv_machine(
&coprocessors.machine_imports(),
&preamble(degree, coprocessors, with_bootloader),
&preamble::<T>(degree, coprocessors, with_bootloader),
initial_mem,
&coprocessors.declarations(),
program,
Expand Down Expand Up @@ -475,13 +476,19 @@ let initial_memory: (fe, fe)[] = [
)
}

fn preamble(degree: u64, coprocessors: &CoProcessors, with_bootloader: bool) -> String {
fn preamble<T: FieldElement>(
degree: u64,
coprocessors: &CoProcessors,
with_bootloader: bool,
) -> String {
let bootloader_preamble_if_included = if with_bootloader {
bootloader_preamble()
} else {
"".to_string()
};

let mul_instruction = mul_instruction::<T>();

format!("degree {degree};")
+ r#"
reg pc[@pc];
Expand Down Expand Up @@ -643,7 +650,15 @@ fn preamble(degree: u64, coprocessors: &CoProcessors, with_bootloader: bool) ->
// quotient is 32 bits:
Z = X_b1 + X_b2 * 0x100 + X_b3 * 0x10000 + X_b4 * 0x1000000
}
"# + mul_instruction
}

fn mul_instruction<T: FieldElement>() -> &'static str {
match T::known_field().expect("Unknown field!") {
KnownField::Bn254Field => {
// The BN254 field can fit any 64-bit number, so we can naively de-compose
// Z * W into 8 bytes and put them together to get the upper and lower word.
r#"
// Multiply two 32-bits unsigned, return the upper and lower unsigned 32-bit
// halves of the result.
// X is the lower half (least significant bits)
Expand All @@ -654,6 +669,19 @@ fn preamble(degree: u64, coprocessors: &CoProcessors, with_bootloader: bool) ->
Y = Y_b5 + Y_b6 * 0x100 + Y_b7 * 0x10000 + Y_b8 * 0x1000000
}
"#
}
KnownField::GoldilocksField => {
// The Goldilocks field cannot fit some 64-bit numbers, so we have to use
// the split machine. Note that it can fit a product of two 32-bit numbers.
r#"
// Multiply two 32-bits unsigned, return the upper and lower unsigned 32-bit
// halves of the result.
// X is the lower half (least significant bits)
// Y is the higher half (most significant bits)
instr mul Z, W -> X, Y = split_gl.split Z * W -> X, Y;
"#
}
}
}

fn memory(with_bootloader: bool) -> String {
Expand Down
21 changes: 14 additions & 7 deletions riscv/src/coprocessors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use std::{
convert::TryFrom,
};

use powdr_number::{FieldElement, KnownField};

type RuntimeFunctionImpl = (&'static str, fn() -> String);

#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
Expand Down Expand Up @@ -120,14 +122,19 @@ impl TryFrom<Vec<&str>> for CoProcessors {

impl CoProcessors {
/// The base version only adds the commonly used bitwise and shift operations.
pub fn base() -> CoProcessors {
Self {
coprocessors: BTreeMap::from([
(BINARY_COPROCESSOR.name, &BINARY_COPROCESSOR),
(SHIFT_COPROCESSOR.name, &SHIFT_COPROCESSOR),
(INPUT_COPROCESSOR.name, &INPUT_COPROCESSOR),
]),
pub fn base<T: FieldElement>() -> CoProcessors {
let mut coprocessors = BTreeMap::from([
(BINARY_COPROCESSOR.name, &BINARY_COPROCESSOR),
(SHIFT_COPROCESSOR.name, &SHIFT_COPROCESSOR),
(INPUT_COPROCESSOR.name, &INPUT_COPROCESSOR),
]);

if matches!(T::known_field(), Some(KnownField::GoldilocksField)) {
// The mul instructions needs the split machine.
coprocessors.insert(SPLIT_GL_COPROCESSOR.name, &SPLIT_GL_COPROCESSOR);
}

Self { coprocessors }
}

/// Poseidon also uses the Split machine.
Expand Down
13 changes: 7 additions & 6 deletions riscv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use std::{
};

use mktemp::Temp;
use powdr_number::FieldElement;
use serde_json::Value as JsonValue;
use std::fs;

Expand All @@ -28,7 +29,7 @@ type Expression = powdr_asm_utils::ast::Expression<FunctionKind>;
/// Compiles a rust file all the way down to PIL and generates
/// fixed and witness columns.
#[allow(clippy::print_stderr)]
pub fn compile_rust(
pub fn compile_rust<T: FieldElement>(
file_name: &str,
output_dir: &Path,
force_overwrite: bool,
Expand Down Expand Up @@ -69,7 +70,7 @@ pub fn compile_rust(
log::info!("Wrote {}", riscv_asm_file_name.to_str().unwrap());
}

compile_riscv_asm_bundle(
compile_riscv_asm_bundle::<T>(
file_name,
riscv_asm,
output_dir,
Expand All @@ -80,7 +81,7 @@ pub fn compile_rust(
}

#[allow(clippy::print_stderr)]
pub fn compile_riscv_asm_bundle(
pub fn compile_riscv_asm_bundle<T: FieldElement>(
original_file_name: &str,
riscv_asm_files: BTreeMap<String, String>,
output_dir: &Path,
Expand All @@ -104,7 +105,7 @@ pub fn compile_riscv_asm_bundle(
return None;
}

let powdr_asm = compiler::compile(riscv_asm_files, coprocessors, with_bootloader);
let powdr_asm = compiler::compile::<T>(riscv_asm_files, coprocessors, with_bootloader);

fs::write(powdr_asm_file_name.clone(), &powdr_asm).unwrap();
log::info!("Wrote {}", powdr_asm_file_name.to_str().unwrap());
Expand All @@ -114,15 +115,15 @@ pub fn compile_riscv_asm_bundle(

/// Compiles a riscv asm file all the way down to PIL and generates
/// fixed and witness columns.
pub fn compile_riscv_asm(
pub fn compile_riscv_asm<T: FieldElement>(
original_file_name: &str,
file_names: impl Iterator<Item = String>,
output_dir: &Path,
force_overwrite: bool,
coprocessors: &CoProcessors,
with_bootloader: bool,
) -> Option<(PathBuf, String)> {
compile_riscv_asm_bundle(
compile_riscv_asm_bundle::<T>(
original_file_name,
file_names
.map(|name| {
Expand Down
5 changes: 3 additions & 2 deletions riscv/tests/instructions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ mod common;

mod instruction_tests {
use crate::common::verify_riscv_asm_string;
use powdr_number::GoldilocksField;
use powdr_riscv::compiler::compile;
use powdr_riscv::CoProcessors;
use test_log::test;

fn run_instruction_test(assembly: &str, name: &str) {
// TODO Should we create one powdr-asm from all tests or keep them separate?
let powdr_asm = compile(
let powdr_asm = compile::<GoldilocksField>(
[(name.to_string(), assembly.to_string())].into(),
&CoProcessors::base(),
&CoProcessors::base::<GoldilocksField>(),
false,
);

Expand Down
Loading

0 comments on commit 06866e4

Please sign in to comment.