diff --git a/Cargo.lock b/Cargo.lock index 2f35d129d..7c1473789 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -248,6 +248,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "cpufeatures" version = "0.2.9" @@ -282,6 +288,19 @@ dependencies = [ "typenum", ] +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + [[package]] name = "digest" version = "0.10.7" @@ -642,6 +661,7 @@ name = "miden-frontend-wasm" version = "0.1.0" dependencies = [ "anyhow", + "derive_more", "expect-test", "itertools 0.11.0", "log", @@ -895,6 +915,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.11" diff --git a/frontend-wasm/Cargo.toml b/frontend-wasm/Cargo.toml index 5f49b0dd2..0afc4c83c 100644 --- a/frontend-wasm/Cargo.toml +++ b/frontend-wasm/Cargo.toml @@ -22,6 +22,7 @@ log.workspace = true anyhow.workspace = true wasmparser = "0.107" itertools = "0.11" +derive_more = "0.99" [dev-dependencies] wat = "1.0.69" diff --git a/frontend-wasm/src/environ/module_env.rs b/frontend-wasm/src/environ/module_env.rs index b0aa24709..4db046a18 100644 --- a/frontend-wasm/src/environ/module_env.rs +++ b/frontend-wasm/src/environ/module_env.rs @@ -4,7 +4,8 @@ use crate::error::{WasmError, WasmResult}; use crate::func_translator::FuncTranslator; use crate::translation_utils::sig_from_funct_type; use crate::wasm_types::{ - DefinedFuncIndex, FuncIndex, Global, GlobalIndex, Memory, MemoryIndex, TypeIndex, + DataSegment, DataSegmentIndex, DefinedFuncIndex, FuncIndex, Global, GlobalIndex, Memory, + MemoryIndex, TypeIndex, }; use miden_diagnostics::{DiagnosticsHandler, SourceSpan}; use miden_hir::cranelift_entity::{EntityRef, PrimaryMap, SecondaryMap}; @@ -43,6 +44,12 @@ pub struct ModuleInfo { /// Global names. global_names: SecondaryMap, + /// Data segments declared in the module. + pub data_segments: PrimaryMap, + + /// Data segment names. + pub data_segment_names: SecondaryMap, + /// The start function. pub start_func: Option, } @@ -59,6 +66,8 @@ impl ModuleInfo { globals: PrimaryMap::new(), function_names: SecondaryMap::new(), global_names: SecondaryMap::new(), + data_segments: PrimaryMap::new(), + data_segment_names: SecondaryMap::new(), } } @@ -120,6 +129,7 @@ impl<'a> ModuleEnvironment<'a> { ) -> WasmResult { let mut module_builder = ModuleBuilder::new(self.info.name.as_str()); self.build_globals(&mut module_builder, diagnostics)?; + self.build_data_segments(&mut module_builder, diagnostics)?; let get_num_func_imports = self.get_num_func_imports(); for (def_func_index, body) in &self.function_bodies { let func_index = FuncIndex::new(get_num_func_imports + def_func_index.index()); @@ -152,7 +162,7 @@ impl<'a> ModuleEnvironment<'a> { } fn build_globals( - &mut self, + &self, module_builder: &mut ModuleBuilder, diagnostics: &DiagnosticsHandler, ) -> Result<(), WasmError> { @@ -178,6 +188,30 @@ impl<'a> ModuleEnvironment<'a> { }) } + fn build_data_segments( + &self, + _module_builder: &mut ModuleBuilder, + diagnostics: &DiagnosticsHandler, + ) -> Result<(), WasmError> { + for (data_segment_idx, data_segment) in &self.info.data_segments { + let data_segment_name = self.info.data_segment_names[data_segment_idx].clone(); + let _readonly = data_segment_name == ".rodata"; + let _init = ConstantData::from(data_segment.data.clone()); + let _offset = data_segment + .offset + .as_i32(&self.info.globals, diagnostics)?; + // if let Err(e) = module_builder.declare_data_segment(offset, size, init, readonly) { + // let message = format!("Failed to declare data segment {init} for data segment size {size} at {offset} with error: {:?}", e); + // diagnostics + // .diagnostic(miden_diagnostics::Severity::Error) + // .with_message(message.clone()) + // .emit(); + // return Err(WasmError::Unexpected(message)); + // } + } + Ok(()) + } + /// Declares a function signature to the environment. pub fn declare_type_func(&mut self, func_type: FunctionType) { self.info.func_types.push(func_type); @@ -237,6 +271,14 @@ impl<'a> ModuleEnvironment<'a> { self.info.function_names[func_index] = String::from(name); } + pub fn declare_data_segment(&mut self, segment: DataSegment) { + self.info.data_segments.push(segment); + } + + pub fn declare_data_segment_name(&mut self, segment_index: DataSegmentIndex, name: &'a str) { + self.info.data_segment_names[segment_index] = String::from(name); + } + /// Indicates that a custom section has been found in the wasm file pub fn custom_section(&mut self, _name: &'a str, _data: &'a [u8]) { // Do we need to support custom sections? diff --git a/frontend-wasm/src/sections_translator.rs b/frontend-wasm/src/sections_translator.rs index 751ce78fc..953a273e7 100644 --- a/frontend-wasm/src/sections_translator.rs +++ b/frontend-wasm/src/sections_translator.rs @@ -6,14 +6,15 @@ use crate::{ error::{WasmError, WasmResult}, unsupported_diag, wasm_types::{ - convert_func_type, convert_global_type, FuncIndex, GlobalIndex, GlobalInit, TypeIndex, + convert_func_type, convert_global_type, DataSegment, DataSegmentIndex, DataSegmentOffset, + FuncIndex, GlobalIndex, GlobalInit, TypeIndex, }, }; use miden_diagnostics::DiagnosticsHandler; use wasmparser::{ - DataSectionReader, ElementSectionReader, FunctionSectionReader, GlobalSectionReader, - ImportSectionReader, MemorySectionReader, NameSectionReader, Naming, Operator, Type, TypeRef, - TypeSectionReader, + Data, DataKind, DataSectionReader, ElementSectionReader, FunctionSectionReader, + GlobalSectionReader, ImportSectionReader, MemorySectionReader, NameSectionReader, Naming, + Operator, Type, TypeRef, TypeSectionReader, }; /// Parses the Type section of the wasm module. @@ -139,11 +140,49 @@ pub fn parse_element_section<'a>( /// Parses the Data section of the wasm module. pub fn parse_data_section<'a>( - _data: DataSectionReader<'a>, - _environ: &mut ModuleEnvironment<'a>, - _diagnostics: &DiagnosticsHandler, + data: DataSectionReader<'a>, + environ: &mut ModuleEnvironment<'a>, + diagnostics: &DiagnosticsHandler, ) -> WasmResult<()> { - todo!("Data section are not yet implemented"); + for (_index, entry) in data.into_iter().enumerate() { + let Data { + kind, + data, + range: _, + } = entry?; + match kind { + DataKind::Active { + // ignored, since for Wasm spec v1 it's always 0 + memory_index: _, + offset_expr, + } => { + let mut offset_expr_reader = offset_expr.get_binary_reader(); + let offset = match offset_expr_reader.read_operator()? { + Operator::I32Const { value } => DataSegmentOffset::I32Const(value), + Operator::GlobalGet { global_index } => { + DataSegmentOffset::GetGlobal(GlobalIndex::from_u32(global_index)) + } + ref s => { + unsupported_diag!( + diagnostics, + "unsupported init expr in data section offset: {:?}", + s + ); + } + }; + let segment = DataSegment { + offset, + data: data.to_owned(), + }; + environ.declare_data_segment(segment); + } + DataKind::Passive => { + // Passive data segments type is added in Wasm spec 2.0 + unsupported_diag!(diagnostics, "Passive data segments are not supported"); + } + } + } + Ok(()) } /// Parses the Name section of the wasm module. @@ -185,12 +224,19 @@ pub fn parse_name_section<'a>( } } } + wasmparser::Name::Data(names) => { + for name in names { + let Naming { index, name } = name?; + if index != u32::max_value() { + environ.declare_data_segment_name(DataSegmentIndex::from_u32(index), name); + } + } + } wasmparser::Name::Label(_) | wasmparser::Name::Type(_) | wasmparser::Name::Table(_) | wasmparser::Name::Memory(_) | wasmparser::Name::Element(_) - | wasmparser::Name::Data(_) | wasmparser::Name::Unknown { .. } => {} } } diff --git a/frontend-wasm/src/wasm_types.rs b/frontend-wasm/src/wasm_types.rs index 957c4c155..ef837f7ee 100644 --- a/frontend-wasm/src/wasm_types.rs +++ b/frontend-wasm/src/wasm_types.rs @@ -1,5 +1,6 @@ //! Internal types for parsed WebAssembly. +use miden_diagnostics::DiagnosticsHandler; use miden_hir::cranelift_entity::entity_impl; use miden_hir::cranelift_entity::PrimaryMap; use miden_hir_type::FunctionType; @@ -8,6 +9,7 @@ use miden_hir_type::Type; use crate::environ::ModuleInfo; use crate::error::WasmError; use crate::error::WasmResult; +use crate::unsupported_diag; /// Index type of a function (imported or defined) inside the WebAssembly module. #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] @@ -20,7 +22,7 @@ pub struct DefinedFuncIndex(u32); entity_impl!(DefinedFuncIndex); /// Index type of a global variable (imported or defined) inside the WebAssembly module. -#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, derive_more::Display)] pub struct GlobalIndex(u32); entity_impl!(GlobalIndex); @@ -34,6 +36,11 @@ entity_impl!(MemoryIndex); pub struct TypeIndex(u32); entity_impl!(TypeIndex); +/// Index type of a data segment inside the WebAssembly module. +#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] +pub struct DataSegmentIndex(u32); +entity_impl!(DataSegmentIndex); + /// A WebAssembly global. #[derive(Debug, Clone, Hash, Eq, PartialEq)] pub struct Global { @@ -74,6 +81,23 @@ impl GlobalInit { } } } + + pub fn as_i32( + &self, + globals: &PrimaryMap, + diagnostics: &DiagnosticsHandler, + ) -> WasmResult { + Ok(match self { + GlobalInit::I32Const(x) => *x, + GlobalInit::GetGlobal(global_idx) => { + let global = &globals[*global_idx]; + global.init.as_i32(globals, diagnostics)? + } + g => { + unsupported_diag!(diagnostics, "Expected global init to be i32, got: {:?}", g); + } + }) + } } /// WebAssembly linear memory. @@ -97,6 +121,53 @@ impl From for Memory { } } +/// Offset of a data segment inside a linear memory. +#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] +pub enum DataSegmentOffset { + /// An `i32.const` offset. + I32Const(i32), + /// An offset as a `global.get` of another global. + GetGlobal(GlobalIndex), +} + +impl DataSegmentOffset { + /// Returns the offset as a i32, resolving the global if necessary. + pub fn as_i32( + &self, + globals: &PrimaryMap, + diagnostics: &DiagnosticsHandler, + ) -> WasmResult { + Ok(match self { + DataSegmentOffset::I32Const(x) => *x, + DataSegmentOffset::GetGlobal(global_idx) => { + let global = &globals[*global_idx]; + match global.init.as_i32(globals, diagnostics) { + Err(e) => { + diagnostics + .diagnostic(miden_diagnostics::Severity::Error) + .with_message(format!( + "Failed to get data segment offset from global init {:?} with global index {global_idx}", + global.init, + )) + .emit(); + return Err(e); + } + Ok(v) => v, + } + } + }) + } +} + +/// A WebAssembly data segment. +/// https://www.w3.org/TR/wasm-core-1/#data-segments%E2%91%A0 +pub struct DataSegment { + /// The offset of the data segment inside the linear memory. + pub offset: DataSegmentOffset, + /// The initialization data. + pub data: Vec, +} + #[derive(Debug, Clone, PartialEq, Eq, Hash, Default)] pub struct BlockType { pub params: Vec, diff --git a/frontend-wasm/tests/rust_source/array.rs b/frontend-wasm/tests/rust_source/array.rs new file mode 100644 index 000000000..8dab20c15 --- /dev/null +++ b/frontend-wasm/tests/rust_source/array.rs @@ -0,0 +1,18 @@ +#![no_std] +#![no_main] + +#[panic_handler] +fn my_panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} + +#[inline(never)] +#[no_mangle] +pub fn sum_arr(arr: &[u32]) -> u32 { + arr.iter().sum() +} + +#[no_mangle] +pub extern "C" fn __main() -> u32 { + sum_arr(&[1, 2, 3, 4, 5]) + sum_arr(&[6, 7, 8, 9, 10]) +} diff --git a/frontend-wasm/tests/rust_source/static_mut.rs b/frontend-wasm/tests/rust_source/static_mut.rs new file mode 100644 index 000000000..2d82148f2 --- /dev/null +++ b/frontend-wasm/tests/rust_source/static_mut.rs @@ -0,0 +1,23 @@ +#![no_std] +#![no_main] + +#[panic_handler] +fn my_panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} + +static mut G1: [u8; 9] = [1, 2, 3, 4, 5, 6, 7, 8, 9]; + +#[inline(never)] +#[no_mangle] +fn global_var_update() { + unsafe { + G1[0] = G1[1] + 1; + } +} + +#[no_mangle] +pub extern "C" fn __main() -> u32 { + global_var_update(); + unsafe { G1.into_iter().sum::() as u32 } +} diff --git a/frontend-wasm/tests/test_rust_comp.rs b/frontend-wasm/tests/test_rust_comp.rs index 56a815a52..e9e3d16cf 100644 --- a/frontend-wasm/tests/test_rust_comp.rs +++ b/frontend-wasm/tests/test_rust_comp.rs @@ -339,3 +339,232 @@ fn rust_enum() { "#]], ) } + +#[test] +fn rust_array() { + check_ir( + include_str!("rust_source/array.rs"), + expect![[r#" + (module + (type (;0;) (func (param i32 i32) (result i32))) + (type (;1;) (func (result i32))) + (func $sum_arr (;0;) (type 0) (param i32 i32) (result i32) + (local i32) + i32.const 0 + local.set 2 + block ;; label = @1 + local.get 1 + i32.eqz + br_if 0 (;@1;) + loop ;; label = @2 + local.get 0 + i32.load + local.get 2 + i32.add + local.set 2 + local.get 0 + i32.const 4 + i32.add + local.set 0 + local.get 1 + i32.const -1 + i32.add + local.tee 1 + br_if 0 (;@2;) + end + end + local.get 2 + ) + (func $__main (;1;) (type 1) (result i32) + i32.const 1048576 + i32.const 5 + call $sum_arr + i32.const 1048596 + i32.const 5 + call $sum_arr + i32.add + ) + (memory (;0;) 17) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (global (;1;) i32 i32.const 1048616) + (global (;2;) i32 i32.const 1048624) + (export "memory" (memory 0)) + (export "sum_arr" (func $sum_arr)) + (export "__main" (func $__main)) + (export "__data_end" (global 1)) + (export "__heap_base" (global 2)) + (data $.rodata (;0;) (i32.const 1048576) "\01\00\00\00\02\00\00\00\03\00\00\00\04\00\00\00\05\00\00\00\06\00\00\00\07\00\00\00\08\00\00\00\09\00\00\00\0a\00\00\00") + )"#]], + expect![[r#" + module noname + + pub fn sum_arr(i32, i32) -> i32 { + block0(v0: i32, v1: i32): + v3 = const.i32 0 : i32 + v4 = const.i32 0 : i32 + v5 = const.i32 0 : i32 + v6 = eq v1, v5 : i1 + v7 = const.i32 0 : i32 + v8 = neq v6, v7 : i1 + condbr v8, block2(v4), block3 + + block1(v2: i32): + v22 = ret v2 : () + + block2(v21: i32): + br block1(v21) + + block3: + br block4(v0, v4, v1) + + block4(v9: i32, v12: i32, v16: i32): + v10 = inttoptr v9 : *mut i32 + v11 = load v10 : i32 + v13 = add v11, v12 : i32 + v14 = const.i32 4 : i32 + v15 = add v9, v14 : i32 + v17 = const.i32 -1 : i32 + v18 = add v16, v17 : i32 + v19 = const.i32 0 : i32 + v20 = neq v18, v19 : i1 + condbr v20, block4(v15, v13, v18), block6 + + block5: + br block2(v13) + + block6: + br block5 + } + + pub fn __main() -> i32 { + block0: + v1 = const.i32 1048576 : i32 + v2 = const.i32 5 : i32 + v3 = call noname::sum_arr(v1, v2) : i32 + v4 = const.i32 1048596 : i32 + v5 = const.i32 5 : i32 + v6 = call noname::sum_arr(v4, v5) : i32 + v7 = add v3, v6 : i32 + br block1(v7) + + block1(v0: i32): + v8 = ret v0 : () + } + "#]], + ) +} + +#[test] +fn rust_static_mut() { + check_ir( + include_str!("rust_source/static_mut.rs"), + expect![[r#" + (module + (type (;0;) (func)) + (type (;1;) (func (result i32))) + (func $global_var_update (;0;) (type 0) + i32.const 0 + i32.const 0 + i32.load8_u offset=1048577 + i32.const 1 + i32.add + i32.store8 offset=1048576 + ) + (func $__main (;1;) (type 1) (result i32) + (local i32 i32 i32) + call $global_var_update + i32.const 0 + local.set 0 + i32.const -9 + local.set 1 + loop ;; label = @1 + local.get 1 + i32.const 1048585 + i32.add + i32.load8_u + local.get 0 + i32.add + local.set 0 + local.get 1 + i32.const 1 + i32.add + local.tee 2 + local.set 1 + local.get 2 + br_if 0 (;@1;) + end + local.get 0 + i32.const 255 + i32.and + ) + (memory (;0;) 17) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (global (;1;) i32 i32.const 1048585) + (global (;2;) i32 i32.const 1048592) + (export "memory" (memory 0)) + (export "global_var_update" (func $global_var_update)) + (export "__main" (func $__main)) + (export "__data_end" (global 1)) + (export "__heap_base" (global 2)) + (data $.data (;0;) (i32.const 1048576) "\01\02\03\04\05\06\07\08\09") + )"#]], + expect![[r#" + module noname + + pub fn global_var_update() { + block0: + v0 = const.i32 0 : i32 + v1 = const.i32 0 : i32 + v2 = const.i32 1048577 : i32 + v3 = add v1, v2 : i32 + v4 = inttoptr v3 : *mut i8 + v5 = load v4 : i8 + v6 = zext v5 : i32 + v7 = const.i32 1 : i32 + v8 = add v6, v7 : i32 + v9 = trunc v8 : i8 + v10 = const.i32 1048576 : i32 + v11 = add v0, v10 : i32 + v12 = inttoptr v11 : *mut i8 + store v12, v9 + br block1 + + block1: + v13 = ret : () + } + + pub fn __main() -> i32 { + block0: + v1 = const.i32 0 : i32 + call noname::global_var_update() + v2 = const.i32 0 : i32 + v3 = const.i32 -9 : i32 + br block2(v3, v2) + + block1(v0: i32): + v18 = ret v0 : () + + block2(v4: i32, v10: i32): + v5 = const.i32 1048585 : i32 + v6 = add v4, v5 : i32 + v7 = inttoptr v6 : *mut i8 + v8 = load v7 : i8 + v9 = zext v8 : i32 + v11 = add v9, v10 : i32 + v12 = const.i32 1 : i32 + v13 = add v4, v12 : i32 + v14 = const.i32 0 : i32 + v15 = neq v13, v14 : i1 + condbr v15, block2(v13, v11), block4 + + block3: + v16 = const.i32 255 : i32 + v17 = band v11, v16 : i32 + br block1(v17) + + block4: + br block3 + } + "#]], + ); +}