From 1021d479e7898f457184a86328e14c927712ea5a Mon Sep 17 00:00:00 2001 From: Max Pestov Date: Wed, 12 Feb 2025 17:16:08 +0300 Subject: [PATCH 1/3] Working runner with gas metering and compiled pvm-prog-calc You must update the vendors, recompile polkatool, recompile calc pvm blob --- Cargo.toml | 3 +- Makefile | 2 +- pvm-test-runner/Cargo.toml | 15 ++++ pvm-test-runner/src/main.rs | 142 ++++++++++++++++++++++++++++++++++++ 4 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 pvm-test-runner/Cargo.toml create mode 100644 pvm-test-runner/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index e73a0d3..44e4300 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,8 @@ members = [ "runtime", "node", "pallets/qf-polkavm-dev", - "qf-test-runner" + "qf-test-runner", + "pvm-test-runner", ] exclude = [ "old", diff --git a/Makefile b/Makefile index 057ef5b..79bc25d 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ GUEST_RUST_FLAGS="-C relocation-model=pie -C link-arg=--emit-relocs -C link-arg=--unique --remap-path-prefix=$(pwd)= --remap-path-prefix=$HOME=~" vendor-clone: - git clone --depth=1 --branch v0.18.0 https://github.com/paritytech/polkavm.git vendor/polkavm + git clone --depth=1 --branch v0.19.0 https://github.com/paritytech/polkavm.git vendor/polkavm git clone --depth=1 https://github.com/QuantumFusion-network/polkadot-sdk vendor/polkadot-sdk tools: polkatool chain-spec-builder diff --git a/pvm-test-runner/Cargo.toml b/pvm-test-runner/Cargo.toml new file mode 100644 index 0000000..4bd77fd --- /dev/null +++ b/pvm-test-runner/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "pvm-test-runner" +description = "PolkaVM program runner. Only for testing purpose." +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +version = "0.1.0" + +[dependencies] +clap = { workspace = true, features = ["derive"] } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } + +polkavm = { workspace = true } diff --git a/pvm-test-runner/src/main.rs b/pvm-test-runner/src/main.rs new file mode 100644 index 0000000..0489efe --- /dev/null +++ b/pvm-test-runner/src/main.rs @@ -0,0 +1,142 @@ +use clap::Parser; +use tracing_subscriber::prelude::*; + +extern crate alloc; + +use polkavm::{ + Config as PolkaVMConfig, + Engine, GasMeteringKind, InterruptKind, Module, ModuleConfig, + ProgramBlob +}; + +// For debugging purposes +macro_rules! match_interrupt { + ($interrupt:expr, $pattern:pat) => { + let i = $interrupt; + assert!( + matches!(i, $pattern), + "unexpected interrupt: {i:?}, expected: {:?}", + stringify!($pattern) + ); + }; +} + +#[derive(Parser, Debug)] +#[command(version, about)] +struct Cli { + /// Path to the PolkaVM program to execute + #[arg(short, long)] + program: std::path::PathBuf, + /// Entry point (default "add_numbers") + #[arg(short, long, default_value="add_numbers")] + entry_name: String, + /// "a" param (default 1) + #[arg(short, long, default_value="1")] + a: u32, + /// "b" param (default 2) + #[arg(short, long, default_value="2")] + b: u32, + /// Available Gas + #[arg(short, long, default_value="100")] + gas: u32, +} + +fn get_native_page_size() -> usize { + 4096 +} + +fn main() { + let registry = tracing_subscriber::registry(); + + let filter = tracing_subscriber::EnvFilter::builder() + .with_default_directive(tracing::Level::INFO.into()) + .from_env_lossy(); + + registry + .with(tracing_subscriber::fmt::layer().with_filter(filter)) + .try_init() + .expect("Failed to initialize tracing"); + + let cli = Cli::parse(); + + println!("Program: {:?}", cli.program); + let raw_blob = std::fs::read(cli.program).expect("Failed to read program"); + let config = PolkaVMConfig::from_env() + .map_err(|e| { + tracing::debug!("Failed to initialize PolkaVM Config: {}", e); + e + }) + .expect("Failed to initialize PolkaVM Config"); + let engine = Engine::new(&config) + .map_err(|e| { + tracing::debug!("Failed to initialize PolkaVM Engine: {}", e); + e + }) + .expect("Failed to initialize PolkaVM Engine"); + let page_size = get_native_page_size() as u32; + let blob = ProgramBlob::parse(raw_blob.into()) + .map_err(|e| { + tracing::debug!("Failed to parse a blob: {}", e); + e + }) + .expect("Failed to parse a blob"); + + let mut module_config = ModuleConfig::new(); + module_config.set_page_size(page_size); + module_config.set_gas_metering(Some(GasMeteringKind::Sync)); + let module = Module::from_blob(&engine, &module_config, blob) + .map_err(|e| { + tracing::debug!("Failed to initialize PolkaVM Module: {}", e); + e + }) + .expect("Failed to initialize PolkaVM Module"); + + let mut instance = module.instantiate() + .map_err(|e| { + tracing::debug!("Failed to initialize PolkaVM Instance: {}", e); + e + }) + .expect("Failed to initialize PolkaVM Instance"); + + instance.set_gas(cli.gas.into()); + println!("Gas available: {}", instance.gas()); + + let entry: &str = cli.entry_name.as_str(); + + let entry_point = module.exports().find(|export| export.symbol() == entry).expect("Entry point not found"); + + let pc = entry_point.program_counter(); + + let args = (cli.a, cli.b); + + println!("Entry {} with args: {:?}", entry, args); + + instance.prepare_call_typed(pc, args); + let gas_cost = module.calculate_gas_cost_for(pc).unwrap(); + println!("gas_cost: {}", gas_cost); + + loop { + let run_result = instance.run() + .map_err(|e| { + tracing::debug!("Failed to run an Instance: {}", e); + e + }) + .expect("Failed to run an Instance"); + match run_result { + InterruptKind::Step => { + println!("Step pc={:?}", instance.program_counter().unwrap()); + }, + InterruptKind::Finished => { + println!("Finished"); + break; + } + _ => { + println!("Unexpected interrupt: {:?}", run_result); + break; + }, + } + } + let res = instance.get_result_typed::(); + println!("Gas left: {}", instance.gas()); + println!("Result: {}", res); +} From 62b7723f79b39c5521be8405490a7601e0284067 Mon Sep 17 00:00:00 2001 From: Max Pestov Date: Thu, 13 Feb 2025 17:23:18 +0300 Subject: [PATCH 2/3] more info for expecting a gas functionality --- pvm-test-runner/Cargo.toml | 2 + pvm-test-runner/src/main.rs | 77 ++++++++++++++++++++++++++++++++++--- pvm_prog/calc/src/main.rs | 19 ++++++++- 3 files changed, 92 insertions(+), 6 deletions(-) diff --git a/pvm-test-runner/Cargo.toml b/pvm-test-runner/Cargo.toml index 4bd77fd..c96df57 100644 --- a/pvm-test-runner/Cargo.toml +++ b/pvm-test-runner/Cargo.toml @@ -13,3 +13,5 @@ tracing = { workspace = true } tracing-subscriber = { workspace = true } polkavm = { workspace = true } +polkavm-disassembler = { path = "../vendor/polkavm/crates/polkavm-disassembler", default-features = false, package = "polkavm-disassembler" } +polkavm-common = { path = "../vendor/polkavm/crates/polkavm-common", default-features = false, package = "polkavm-common" } diff --git a/pvm-test-runner/src/main.rs b/pvm-test-runner/src/main.rs index 0489efe..17816b6 100644 --- a/pvm-test-runner/src/main.rs +++ b/pvm-test-runner/src/main.rs @@ -1,3 +1,5 @@ +use std::io::Write; + use clap::Parser; use tracing_subscriber::prelude::*; @@ -6,8 +8,10 @@ extern crate alloc; use polkavm::{ Config as PolkaVMConfig, Engine, GasMeteringKind, InterruptKind, Module, ModuleConfig, - ProgramBlob + ProgramBlob, Reg }; +// use polkavm_common::Reg; +use polkavm_disassembler::{Disassembler, DisassemblyFormat}; // For debugging purposes macro_rules! match_interrupt { @@ -39,12 +43,27 @@ struct Cli { /// Available Gas #[arg(short, long, default_value="100")] gas: u32, + /// Additional debug information + #[arg(short, long, action )] + debug: bool, + /// Write debug to the file + #[arg(long)] + debug_file: Option, } fn get_native_page_size() -> usize { 4096 } +fn disassemble_with_gas(blob: &ProgramBlob, format: DisassemblyFormat) -> Vec { + let mut disassembler = Disassembler::new(blob, format).unwrap(); + disassembler.display_gas().unwrap(); + + let mut buffer = Vec::with_capacity(1 << 20); + disassembler.disassemble_into(&mut buffer).unwrap(); + buffer +} + fn main() { let registry = tracing_subscriber::registry(); @@ -74,16 +93,52 @@ fn main() { }) .expect("Failed to initialize PolkaVM Engine"); let page_size = get_native_page_size() as u32; - let blob = ProgramBlob::parse(raw_blob.into()) + let blob = ProgramBlob::parse(raw_blob.clone().into()) .map_err(|e| { tracing::debug!("Failed to parse a blob: {}", e); e }) .expect("Failed to parse a blob"); + let disasm; + if cli.debug { + let dblob = ProgramBlob::parse(raw_blob.into()) + .map_err(|e| { + tracing::debug!("Failed to parse a blob: {}", e); + e + }) + .expect("Failed to parse a blob"); + disasm = disassemble_with_gas(&dblob, DisassemblyFormat::Guest); + let assembly_text = String::from_utf8(disasm).unwrap(); + + println!("\n========={}===========\n", assembly_text); + + let exported_fns = blob.exports().map(|e| e.symbol().clone().to_string()).collect::>(); + let imported_fns = blob.imports().iter().map(|e| e).collect::>(); + println!("Imported functions:"); + for imp in imported_fns { + if imp.is_some() { + println!("- {:?}", String::from_utf8(imp.expect("no import").as_bytes().to_vec()).unwrap()); + } + } + println!("\nExported functions:"); + for exp in exported_fns { + println!("- {:?}", exp); + } + + if cli.debug_file.is_some() { + let mut file = std::fs::File::create(cli.debug_file.as_ref().unwrap()).unwrap(); + let data = assembly_text.as_bytes(); + file.write_all(&data).unwrap(); + } + } + println!("\nRun the blob"); let mut module_config = ModuleConfig::new(); module_config.set_page_size(page_size); module_config.set_gas_metering(Some(GasMeteringKind::Sync)); + if cli.debug { + module_config.set_step_tracing(true); + } let module = Module::from_blob(&engine, &module_config, blob) .map_err(|e| { tracing::debug!("Failed to initialize PolkaVM Module: {}", e); @@ -109,7 +164,7 @@ fn main() { let args = (cli.a, cli.b); - println!("Entry {} with args: {:?}", entry, args); + println!("Entry \"{}\" with args: {:?}", entry, args); instance.prepare_call_typed(pc, args); let gas_cost = module.calculate_gas_cost_for(pc).unwrap(); @@ -124,12 +179,24 @@ fn main() { .expect("Failed to run an Instance"); match run_result { InterruptKind::Step => { - println!("Step pc={:?}", instance.program_counter().unwrap()); + let pc = instance.program_counter().unwrap(); + println!("Step pc={:?}", pc); }, InterruptKind::Finished => { println!("Finished"); break; - } + }, + InterruptKind::Ecalli(num) => { + let Some(name) = module.imports().get(num) else { + panic!("unexpected external call: {num}"); + }; + + if name == "get_third_number" { + instance.set_reg(Reg::A0, 100); + } else { + panic!("unexpected external call: {name} ({num})") + } + }, _ => { println!("Unexpected interrupt: {:?}", run_result); break; diff --git a/pvm_prog/calc/src/main.rs b/pvm_prog/calc/src/main.rs index ffa0542..541b8bd 100644 --- a/pvm_prog/calc/src/main.rs +++ b/pvm_prog/calc/src/main.rs @@ -8,9 +8,26 @@ fn panic(_info: &core::panic::PanicInfo) -> ! { } } +fn pre_mul(a: u32, b: u32) -> u32 { + if a > b { + a * b + } else if b != 0 { + a / b + } else { + a - b + } +} + +// Call the host function +#[polkavm_derive::polkavm_import] +extern "C" { + fn get_third_number() -> u32; +} + #[polkavm_derive::polkavm_export] extern "C" fn add_numbers(a: u32, b: u32) -> u32 { - a + b + let c = pre_mul(a, b); + a + b + c + unsafe { get_third_number() } } #[polkavm_derive::polkavm_export] From 4b75ddedc5e9b7489aaf05b1a3342e52e70941c7 Mon Sep 17 00:00:00 2001 From: Max Pestov Date: Tue, 18 Feb 2025 18:05:51 +0300 Subject: [PATCH 3/3] fixes after review --- Makefile | 2 +- pvm-test-runner/src/main.rs | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 79bc25d..0bbbc79 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ GUEST_RUST_FLAGS="-C relocation-model=pie -C link-arg=--emit-relocs -C link-arg=--unique --remap-path-prefix=$(pwd)= --remap-path-prefix=$HOME=~" vendor-clone: - git clone --depth=1 --branch v0.19.0 https://github.com/paritytech/polkavm.git vendor/polkavm + git clone --depth=1 --branch v0.21.0 https://github.com/paritytech/polkavm.git vendor/polkavm git clone --depth=1 https://github.com/QuantumFusion-network/polkadot-sdk vendor/polkadot-sdk tools: polkatool chain-spec-builder diff --git a/pvm-test-runner/src/main.rs b/pvm-test-runner/src/main.rs index 17816b6..fd93a77 100644 --- a/pvm-test-runner/src/main.rs +++ b/pvm-test-runner/src/main.rs @@ -25,6 +25,8 @@ macro_rules! match_interrupt { }; } +const NATIVE_PAGE_SIZE: u32 = 4096; + #[derive(Parser, Debug)] #[command(version, about)] struct Cli { @@ -51,10 +53,6 @@ struct Cli { debug_file: Option, } -fn get_native_page_size() -> usize { - 4096 -} - fn disassemble_with_gas(blob: &ProgramBlob, format: DisassemblyFormat) -> Vec { let mut disassembler = Disassembler::new(blob, format).unwrap(); disassembler.display_gas().unwrap(); @@ -92,7 +90,7 @@ fn main() { e }) .expect("Failed to initialize PolkaVM Engine"); - let page_size = get_native_page_size() as u32; + let page_size = NATIVE_PAGE_SIZE; let blob = ProgramBlob::parse(raw_blob.clone().into()) .map_err(|e| { tracing::debug!("Failed to parse a blob: {}", e); @@ -167,7 +165,7 @@ fn main() { println!("Entry \"{}\" with args: {:?}", entry, args); instance.prepare_call_typed(pc, args); - let gas_cost = module.calculate_gas_cost_for(pc).unwrap(); + let gas_cost = module.calculate_gas_cost_for(pc).expect(&format!("Failed to calculate gas cost for {:?}", pc)); println!("gas_cost: {}", gas_cost); loop { @@ -179,7 +177,7 @@ fn main() { .expect("Failed to run an Instance"); match run_result { InterruptKind::Step => { - let pc = instance.program_counter().unwrap(); + let pc = instance.program_counter().expect("Failed to get program counter"); println!("Step pc={:?}", pc); }, InterruptKind::Finished => { @@ -197,6 +195,10 @@ fn main() { panic!("unexpected external call: {name} ({num})") } }, + InterruptKind::NotEnoughGas => { + println!("Not enough gas"); + break; + }, _ => { println!("Unexpected interrupt: {:?}", run_result); break;