From 70aebed7fdc5dbcde2edc878b7269f277e5779cb Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Fri, 15 Mar 2024 22:32:43 -0600 Subject: [PATCH 01/20] streaming decompression api with dictionary --- arbitrator/caller-env/src/brotli.rs | 106 ----------- arbitrator/caller-env/src/brotli/mod.rs | 207 ++++++++++++++++++++++ arbitrator/caller-env/src/brotli/types.rs | 52 ++++++ 3 files changed, 259 insertions(+), 106 deletions(-) delete mode 100644 arbitrator/caller-env/src/brotli.rs create mode 100644 arbitrator/caller-env/src/brotli/mod.rs create mode 100644 arbitrator/caller-env/src/brotli/types.rs diff --git a/arbitrator/caller-env/src/brotli.rs b/arbitrator/caller-env/src/brotli.rs deleted file mode 100644 index 9f6f47e7e..000000000 --- a/arbitrator/caller-env/src/brotli.rs +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2021-2024, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE - -#![allow(clippy::too_many_arguments)] - -use crate::{ExecEnv, GuestPtr, MemAccess}; -use alloc::vec; -use num_enum::{IntoPrimitive, TryFromPrimitive}; - -#[derive(PartialEq, IntoPrimitive, TryFromPrimitive)] -#[repr(u32)] -pub enum BrotliStatus { - Failure, - Success, -} - -extern "C" { - pub fn BrotliDecoderDecompress( - encoded_size: usize, - encoded_buffer: *const u8, - decoded_size: *mut usize, - decoded_buffer: *mut u8, - ) -> BrotliStatus; - - pub fn BrotliEncoderCompress( - quality: u32, - lgwin: u32, - mode: u32, - input_size: usize, - input_buffer: *const u8, - encoded_size: *mut usize, - encoded_buffer: *mut u8, - ) -> BrotliStatus; -} - -const BROTLI_MODE_GENERIC: u32 = 0; - -/// Brotli decompresses a go slice. -/// -/// # Safety -/// -/// The output buffer must be sufficiently large enough. -pub fn brotli_decompress( - mem: &mut M, - _env: &mut E, - in_buf_ptr: GuestPtr, - in_buf_len: u32, - out_buf_ptr: GuestPtr, - out_len_ptr: GuestPtr, -) -> BrotliStatus { - let in_slice = mem.read_slice(in_buf_ptr, in_buf_len as usize); - let orig_output_len = mem.read_u32(out_len_ptr) as usize; - let mut output = vec![0; orig_output_len]; - let mut output_len = orig_output_len; - unsafe { - let res = BrotliDecoderDecompress( - in_buf_len as usize, - in_slice.as_ptr(), - &mut output_len, - output.as_mut_ptr(), - ); - if (res != BrotliStatus::Success) || (output_len > orig_output_len) { - return BrotliStatus::Failure; - } - } - mem.write_slice(out_buf_ptr, &output[..output_len]); - mem.write_u32(out_len_ptr, output_len as u32); - BrotliStatus::Success -} - -/// Brotli compresses a go slice -/// -/// The output buffer must be large enough. -pub fn brotli_compress( - mem: &mut M, - _env: &mut E, - in_buf_ptr: GuestPtr, - in_buf_len: u32, - out_buf_ptr: GuestPtr, - out_len_ptr: GuestPtr, - level: u32, - window_size: u32, -) -> BrotliStatus { - let in_slice = mem.read_slice(in_buf_ptr, in_buf_len as usize); - let orig_output_len = mem.read_u32(out_len_ptr) as usize; - let mut output = vec![0; orig_output_len]; - let mut output_len = orig_output_len; - - unsafe { - let res = BrotliEncoderCompress( - level, - window_size, - BROTLI_MODE_GENERIC, - in_buf_len as usize, - in_slice.as_ptr(), - &mut output_len, - output.as_mut_ptr(), - ); - if (res != BrotliStatus::Success) || (output_len > orig_output_len) { - return BrotliStatus::Failure; - } - } - mem.write_slice(out_buf_ptr, &output[..output_len]); - mem.write_u32(out_len_ptr, output_len as u32); - BrotliStatus::Success -} diff --git a/arbitrator/caller-env/src/brotli/mod.rs b/arbitrator/caller-env/src/brotli/mod.rs new file mode 100644 index 000000000..306ff5ad4 --- /dev/null +++ b/arbitrator/caller-env/src/brotli/mod.rs @@ -0,0 +1,207 @@ +// Copyright 2021-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +#![allow(clippy::too_many_arguments)] + +use crate::{ExecEnv, GuestPtr, MemAccess}; +use alloc::vec::Vec; +use core::{ffi::c_void, ptr}; + +mod types; + +pub use types::*; + +type DecoderState = c_void; +type CustomAllocator = c_void; +type HeapItem = c_void; + +// one-shot brotli API +extern "C" { + fn BrotliDecoderDecompress( + encoded_size: usize, + encoded_buffer: *const u8, + decoded_size: *mut usize, + decoded_buffer: *mut u8, + ) -> BrotliStatus; + + fn BrotliEncoderCompress( + quality: u32, + lgwin: u32, + mode: u32, + input_size: usize, + input_buffer: *const u8, + encoded_size: *mut usize, + encoded_buffer: *mut u8, + ) -> BrotliStatus; +} + +// custom dictionary API +extern "C" { + fn BrotliDecoderCreateInstance( + alloc: Option *mut HeapItem>, + free: Option, + opaque: *mut CustomAllocator, + ) -> *mut DecoderState; + + fn BrotliDecoderAttachDictionary( + state: *mut DecoderState, + dict_type: BrotliSharedDictionaryType, + dict_len: usize, + dictionary: *const u8, + ) -> BrotliBool; + + fn BrotliDecoderDecompressStream( + state: *mut DecoderState, + input_len: *mut usize, + input_ptr: *mut *const u8, + out_left: *mut usize, + out_ptr: *mut *mut u8, + out_len: *mut usize, + ) -> BrotliStatus; + + fn BrotliDecoderIsFinished(state: *const DecoderState) -> BrotliBool; + + fn BrotliDecoderDestroyInstance(state: *mut DecoderState); +} + +const BROTLI_MODE_GENERIC: u32 = 0; + +/// Brotli decompresses a go slice using a custom dictionary. +/// +/// # Safety +/// +/// The output buffer must be sufficiently large. +/// The pointers must not be null. +pub fn brotli_decompress_with_dictionary( + mem: &mut M, + _env: &mut E, + in_buf_ptr: GuestPtr, + in_buf_len: u32, + out_buf_ptr: GuestPtr, + out_len_ptr: GuestPtr, + dictionary: Dictionary, +) -> BrotliStatus { + let input = mem.read_slice(in_buf_ptr, in_buf_len as usize); + let prior_out_len = mem.read_u32(out_len_ptr) as usize; + + let mut output = Vec::with_capacity(prior_out_len); + let mut out_len = prior_out_len; + unsafe { + let state = BrotliDecoderCreateInstance(None, None, ptr::null_mut()); + + macro_rules! require { + ($cond:expr) => { + if !$cond { + BrotliDecoderDestroyInstance(state); + return BrotliStatus::Failure; + } + }; + } + + if dictionary != Dictionary::None { + let attatched = BrotliDecoderAttachDictionary( + state, + BrotliSharedDictionaryType::Raw, + dictionary.len(), + dictionary.data(), + ); + require!(attatched == BrotliBool::True); + } + + let mut in_len = input.len(); + let mut in_ptr = input.as_ptr(); + let mut out_left = prior_out_len; + let mut out_ptr = output.as_mut_ptr(); + + let status = BrotliDecoderDecompressStream( + state, + &mut in_len as _, + &mut in_ptr as _, + &mut out_left as _, + &mut out_ptr as _, + &mut out_len as _, + ); + require!(status == BrotliStatus::Success && out_len <= prior_out_len); + require!(BrotliDecoderIsFinished(state) == BrotliBool::True); + + BrotliDecoderDestroyInstance(state); + output.set_len(out_len); + } + mem.write_slice(out_buf_ptr, &output[..out_len]); + mem.write_u32(out_len_ptr, out_len as u32); + BrotliStatus::Success +} + +/// Brotli decompresses a go slice. +/// +/// # Safety +/// +/// The output buffer must be sufficiently large. +/// The pointers must not be null. +pub fn brotli_decompress( + mem: &mut M, + _env: &mut E, + in_buf_ptr: GuestPtr, + in_buf_len: u32, + out_buf_ptr: GuestPtr, + out_len_ptr: GuestPtr, +) -> BrotliStatus { + let in_slice = mem.read_slice(in_buf_ptr, in_buf_len as usize); + let orig_output_len = mem.read_u32(out_len_ptr) as usize; + let mut output = Vec::with_capacity(orig_output_len); + let mut output_len = orig_output_len; + unsafe { + let res = BrotliDecoderDecompress( + in_buf_len as usize, + in_slice.as_ptr(), + &mut output_len, + output.as_mut_ptr(), + ); + if (res != BrotliStatus::Success) || (output_len > orig_output_len) { + return BrotliStatus::Failure; + } + output.set_len(output_len); + } + mem.write_slice(out_buf_ptr, &output[..output_len]); + mem.write_u32(out_len_ptr, output_len as u32); + BrotliStatus::Success +} + +/// Brotli compresses a go slice +/// +/// The output buffer must be sufficiently large. +/// The pointers must not be null. +pub fn brotli_compress( + mem: &mut M, + _env: &mut E, + in_buf_ptr: GuestPtr, + in_buf_len: u32, + out_buf_ptr: GuestPtr, + out_len_ptr: GuestPtr, + level: u32, + window_size: u32, +) -> BrotliStatus { + let in_slice = mem.read_slice(in_buf_ptr, in_buf_len as usize); + let orig_output_len = mem.read_u32(out_len_ptr) as usize; + let mut output = Vec::with_capacity(orig_output_len); + let mut output_len = orig_output_len; + + unsafe { + let res = BrotliEncoderCompress( + level, + window_size, + BROTLI_MODE_GENERIC, + in_buf_len as usize, + in_slice.as_ptr(), + &mut output_len, + output.as_mut_ptr(), + ); + if (res != BrotliStatus::Success) || (output_len > orig_output_len) { + return BrotliStatus::Failure; + } + output.set_len(output_len); + } + mem.write_slice(out_buf_ptr, &output[..output_len]); + mem.write_u32(out_len_ptr, output_len as u32); + BrotliStatus::Success +} diff --git a/arbitrator/caller-env/src/brotli/types.rs b/arbitrator/caller-env/src/brotli/types.rs new file mode 100644 index 000000000..a5f5724fa --- /dev/null +++ b/arbitrator/caller-env/src/brotli/types.rs @@ -0,0 +1,52 @@ +// Copyright 2021-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +#![allow(dead_code, clippy::len_without_is_empty)] + +use core::ptr; +use num_enum::{IntoPrimitive, TryFromPrimitive}; + +#[derive(PartialEq, IntoPrimitive, TryFromPrimitive)] +#[repr(u32)] +pub enum BrotliStatus { + Failure, + Success, +} + +#[derive(PartialEq)] +#[repr(usize)] +pub(super) enum BrotliBool { + True, + False, +} + +#[repr(C)] +pub(super) enum BrotliSharedDictionaryType { + /// LZ77 prefix dictionary + Raw, + /// Serialized dictionary + Serialized, +} + +#[derive(PartialEq)] +#[repr(u32)] +pub enum Dictionary { + None, + StylusProgram, +} + +impl Dictionary { + pub fn len(&self) -> usize { + match self { + Self::None => 0, + Self::StylusProgram => todo!(), + } + } + + pub fn data(&self) -> *const u8 { + match self { + Self::None => ptr::null(), + Self::StylusProgram => todo!(), + } + } +} From 72c2ab158c418e5441ff29789bff79566535d016 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Sat, 16 Mar 2024 02:13:47 -0600 Subject: [PATCH 02/20] propagate dictionaries --- arbcompress/compress_common.go | 7 +++ arbcompress/compress_wasm.go | 8 ++- arbitrator/caller-env/src/brotli/mod.rs | 49 +------------------ arbitrator/caller-env/src/brotli/types.rs | 9 ++-- arbitrator/caller-env/src/lib.rs | 2 +- arbitrator/caller-env/src/wasmer_traits.rs | 14 +++++- arbitrator/jit/src/arbcompress.rs | 5 +- arbitrator/prover/src/machine.rs | 4 +- arbitrator/wasm-libraries/brotli/src/lib.rs | 23 +++++++-- .../wasm-libraries/user-test/src/host.rs | 6 +-- .../wasm-libraries/user-test/src/lib.rs | 2 +- .../wasm-libraries/user-test/src/program.rs | 2 +- 12 files changed, 61 insertions(+), 70 deletions(-) diff --git a/arbcompress/compress_common.go b/arbcompress/compress_common.go index 2e469996e..f45d553a5 100644 --- a/arbcompress/compress_common.go +++ b/arbcompress/compress_common.go @@ -10,6 +10,13 @@ const ( BrotliSuccess ) +type Dictionary uint32 + +const ( + EmptyDictionary Dictionary = iota + StylusProgramDictionary +) + const LEVEL_FAST = 0 const LEVEL_WELL = 11 const WINDOW_SIZE = 22 // BROTLI_DEFAULT_WINDOW diff --git a/arbcompress/compress_wasm.go b/arbcompress/compress_wasm.go index 250b46705..024aa9372 100644 --- a/arbcompress/compress_wasm.go +++ b/arbcompress/compress_wasm.go @@ -17,13 +17,17 @@ import ( func brotliCompress(inBuf unsafe.Pointer, inBufLen uint32, outBuf unsafe.Pointer, outBufLen unsafe.Pointer, level, windowSize uint32) BrotliStatus //go:wasmimport arbcompress brotli_decompress -func brotliDecompress(inBuf unsafe.Pointer, inBufLen uint32, outBuf unsafe.Pointer, outBufLen unsafe.Pointer) BrotliStatus +func brotliDecompress(inBuf unsafe.Pointer, inBufLen uint32, outBuf unsafe.Pointer, outBufLen unsafe.Pointer, dictionary Dictionary) BrotliStatus func Decompress(input []byte, maxSize int) ([]byte, error) { outBuf := make([]byte, maxSize) outLen := uint32(len(outBuf)) status := brotliDecompress( - arbutil.SliceToUnsafePointer(input), uint32(len(input)), arbutil.SliceToUnsafePointer(outBuf), unsafe.Pointer(&outLen), + arbutil.SliceToUnsafePointer(input), + uint32(len(input)), + arbutil.SliceToUnsafePointer(outBuf), + unsafe.Pointer(&outLen), + EmptyDictionary, ) if status != BrotliSuccess { return nil, fmt.Errorf("failed decompression") diff --git a/arbitrator/caller-env/src/brotli/mod.rs b/arbitrator/caller-env/src/brotli/mod.rs index 306ff5ad4..6ec33f430 100644 --- a/arbitrator/caller-env/src/brotli/mod.rs +++ b/arbitrator/caller-env/src/brotli/mod.rs @@ -17,13 +17,6 @@ type HeapItem = c_void; // one-shot brotli API extern "C" { - fn BrotliDecoderDecompress( - encoded_size: usize, - encoded_buffer: *const u8, - decoded_size: *mut usize, - decoded_buffer: *mut u8, - ) -> BrotliStatus; - fn BrotliEncoderCompress( quality: u32, lgwin: u32, @@ -59,8 +52,6 @@ extern "C" { out_len: *mut usize, ) -> BrotliStatus; - fn BrotliDecoderIsFinished(state: *const DecoderState) -> BrotliBool; - fn BrotliDecoderDestroyInstance(state: *mut DecoderState); } @@ -72,7 +63,7 @@ const BROTLI_MODE_GENERIC: u32 = 0; /// /// The output buffer must be sufficiently large. /// The pointers must not be null. -pub fn brotli_decompress_with_dictionary( +pub fn brotli_decompress( mem: &mut M, _env: &mut E, in_buf_ptr: GuestPtr, @@ -98,7 +89,7 @@ pub fn brotli_decompress_with_dictionary( }; } - if dictionary != Dictionary::None { + if dictionary != Dictionary::Empty { let attatched = BrotliDecoderAttachDictionary( state, BrotliSharedDictionaryType::Raw, @@ -122,7 +113,6 @@ pub fn brotli_decompress_with_dictionary( &mut out_len as _, ); require!(status == BrotliStatus::Success && out_len <= prior_out_len); - require!(BrotliDecoderIsFinished(state) == BrotliBool::True); BrotliDecoderDestroyInstance(state); output.set_len(out_len); @@ -132,41 +122,6 @@ pub fn brotli_decompress_with_dictionary( BrotliStatus::Success } -/// Brotli decompresses a go slice. -/// -/// # Safety -/// -/// The output buffer must be sufficiently large. -/// The pointers must not be null. -pub fn brotli_decompress( - mem: &mut M, - _env: &mut E, - in_buf_ptr: GuestPtr, - in_buf_len: u32, - out_buf_ptr: GuestPtr, - out_len_ptr: GuestPtr, -) -> BrotliStatus { - let in_slice = mem.read_slice(in_buf_ptr, in_buf_len as usize); - let orig_output_len = mem.read_u32(out_len_ptr) as usize; - let mut output = Vec::with_capacity(orig_output_len); - let mut output_len = orig_output_len; - unsafe { - let res = BrotliDecoderDecompress( - in_buf_len as usize, - in_slice.as_ptr(), - &mut output_len, - output.as_mut_ptr(), - ); - if (res != BrotliStatus::Success) || (output_len > orig_output_len) { - return BrotliStatus::Failure; - } - output.set_len(output_len); - } - mem.write_slice(out_buf_ptr, &output[..output_len]); - mem.write_u32(out_len_ptr, output_len as u32); - BrotliStatus::Success -} - /// Brotli compresses a go slice /// /// The output buffer must be sufficiently large. diff --git a/arbitrator/caller-env/src/brotli/types.rs b/arbitrator/caller-env/src/brotli/types.rs index a5f5724fa..6a2e5426c 100644 --- a/arbitrator/caller-env/src/brotli/types.rs +++ b/arbitrator/caller-env/src/brotli/types.rs @@ -3,7 +3,6 @@ #![allow(dead_code, clippy::len_without_is_empty)] -use core::ptr; use num_enum::{IntoPrimitive, TryFromPrimitive}; #[derive(PartialEq, IntoPrimitive, TryFromPrimitive)] @@ -28,24 +27,24 @@ pub(super) enum BrotliSharedDictionaryType { Serialized, } -#[derive(PartialEq)] +#[derive(PartialEq, IntoPrimitive, TryFromPrimitive)] #[repr(u32)] pub enum Dictionary { - None, + Empty, StylusProgram, } impl Dictionary { pub fn len(&self) -> usize { match self { - Self::None => 0, + Self::Empty => 0, Self::StylusProgram => todo!(), } } pub fn data(&self) -> *const u8 { match self { - Self::None => ptr::null(), + Self::Empty => [].as_ptr(), Self::StylusProgram => todo!(), } } diff --git a/arbitrator/caller-env/src/lib.rs b/arbitrator/caller-env/src/lib.rs index 39ee65e59..ffe502d71 100644 --- a/arbitrator/caller-env/src/lib.rs +++ b/arbitrator/caller-env/src/lib.rs @@ -1,7 +1,7 @@ // Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE -#![no_std] +#![cfg_attr(target_arch = "wasm32", no_std)] extern crate alloc; diff --git a/arbitrator/caller-env/src/wasmer_traits.rs b/arbitrator/caller-env/src/wasmer_traits.rs index 5cc6f9e67..3b28d2c0b 100644 --- a/arbitrator/caller-env/src/wasmer_traits.rs +++ b/arbitrator/caller-env/src/wasmer_traits.rs @@ -1,7 +1,7 @@ // Copyright 2024, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE -use crate::{BrotliStatus, Errno, GuestPtr}; +use crate::{brotli::Dictionary, BrotliStatus, Errno, GuestPtr}; use wasmer::{FromToNativeWasmType, WasmPtr}; unsafe impl FromToNativeWasmType for GuestPtr { @@ -40,6 +40,18 @@ unsafe impl FromToNativeWasmType for BrotliStatus { } } +unsafe impl FromToNativeWasmType for Dictionary { + type Native = i32; + + fn from_native(native: i32) -> Self { + Self::try_from(u32::from_native(native)).expect("unknown brotli dictionary") + } + + fn to_native(self) -> i32 { + (self as u32).to_native() + } +} + impl From for WasmPtr { fn from(value: GuestPtr) -> Self { WasmPtr::new(value.0) diff --git a/arbitrator/jit/src/arbcompress.rs b/arbitrator/jit/src/arbcompress.rs index 6815a480e..af6068411 100644 --- a/arbitrator/jit/src/arbcompress.rs +++ b/arbitrator/jit/src/arbcompress.rs @@ -4,7 +4,7 @@ use crate::caller_env::{JitEnv, JitExecEnv}; use crate::machine::Escape; use crate::machine::WasmEnvMut; -use caller_env::brotli::BrotliStatus; +use caller_env::brotli::{BrotliStatus, Dictionary}; use caller_env::{self, GuestPtr}; macro_rules! wrap { @@ -24,7 +24,8 @@ wrap! { in_buf_ptr: GuestPtr, in_buf_len: u32, out_buf_ptr: GuestPtr, - out_len_ptr: GuestPtr + out_len_ptr: GuestPtr, + dictionary: Dictionary ) -> BrotliStatus; fn brotli_compress( diff --git a/arbitrator/prover/src/machine.rs b/arbitrator/prover/src/machine.rs index a4601f845..6cb6056ae 100644 --- a/arbitrator/prover/src/machine.rs +++ b/arbitrator/prover/src/machine.rs @@ -364,8 +364,8 @@ impl Module { }; ensure!( &func.ty == have_ty, - "Import {} has different function signature than host function. Expected {} but got {}", - import_name.red(), func.ty.red(), have_ty.red(), + "Import {} for {} has different function signature than export.\nexpected {}\nbut have {}", + import_name.red(), bin.names.module.red(), func.ty.red(), have_ty.red(), ); func_type_idxs.push(import.offset); diff --git a/arbitrator/wasm-libraries/brotli/src/lib.rs b/arbitrator/wasm-libraries/brotli/src/lib.rs index 86456bfec..a831a37e5 100644 --- a/arbitrator/wasm-libraries/brotli/src/lib.rs +++ b/arbitrator/wasm-libraries/brotli/src/lib.rs @@ -3,7 +3,11 @@ #![allow(clippy::missing_safety_doc)] // TODO: add safety docs -use caller_env::{self, brotli::BrotliStatus, GuestPtr}; +use caller_env::{ + self, + brotli::{BrotliStatus, Dictionary}, + GuestPtr, +}; use paste::paste; macro_rules! wrap { @@ -24,7 +28,20 @@ macro_rules! wrap { } wrap! { - fn brotli_decompress(in_buf_ptr: GuestPtr, in_buf_len: u32, out_buf_ptr: GuestPtr, out_len_ptr: GuestPtr) -> BrotliStatus; + fn brotli_decompress( + in_buf_ptr: GuestPtr, + in_buf_len: u32, + out_buf_ptr: GuestPtr, + out_len_ptr: GuestPtr, + dictionary: Dictionary + ) -> BrotliStatus; - fn brotli_compress(in_buf_ptr: GuestPtr, in_buf_len: u32, out_buf_ptr: GuestPtr, out_len_ptr: GuestPtr, level: u32, window_size: u32) -> BrotliStatus + fn brotli_compress( + in_buf_ptr: GuestPtr, + in_buf_len: u32, + out_buf_ptr: GuestPtr, + out_len_ptr: GuestPtr, + level: u32, + window_size: u32 + ) -> BrotliStatus } diff --git a/arbitrator/wasm-libraries/user-test/src/host.rs b/arbitrator/wasm-libraries/user-test/src/host.rs index a4f7912f5..5ae5d9047 100644 --- a/arbitrator/wasm-libraries/user-test/src/host.rs +++ b/arbitrator/wasm-libraries/user-test/src/host.rs @@ -104,11 +104,7 @@ pub unsafe extern "C" fn vm_hooks__create2( } #[no_mangle] -pub unsafe extern "C" fn vm_hooks__read_return_data( - dest: GuestPtr, - offset: u32, - size: u32, -) -> u32 { +pub unsafe extern "C" fn vm_hooks__read_return_data(dest: GuestPtr, offset: u32, size: u32) -> u32 { hostio!(read_return_data(dest, offset, size)) } diff --git a/arbitrator/wasm-libraries/user-test/src/lib.rs b/arbitrator/wasm-libraries/user-test/src/lib.rs index 7fd771cf3..ffb8d4a28 100644 --- a/arbitrator/wasm-libraries/user-test/src/lib.rs +++ b/arbitrator/wasm-libraries/user-test/src/lib.rs @@ -3,7 +3,7 @@ #![allow(clippy::missing_safety_doc)] -use arbutil::{Bytes32, evm::EvmData}; +use arbutil::{evm::EvmData, Bytes32}; use fnv::FnvHashMap as HashMap; use lazy_static::lazy_static; use parking_lot::Mutex; diff --git a/arbitrator/wasm-libraries/user-test/src/program.rs b/arbitrator/wasm-libraries/user-test/src/program.rs index 63afbdfe7..e5911046a 100644 --- a/arbitrator/wasm-libraries/user-test/src/program.rs +++ b/arbitrator/wasm-libraries/user-test/src/program.rs @@ -1,7 +1,7 @@ // Copyright 2022-2024, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE -use crate::{ARGS, EVER_PAGES, KEYS, LOGS, OPEN_PAGES, OUTS, EVM_DATA}; +use crate::{ARGS, EVER_PAGES, EVM_DATA, KEYS, LOGS, OPEN_PAGES, OUTS}; use arbutil::{ evm::{ api::{EvmApi, VecReader}, From 7fe42e837f83856d5bc2b292b5de86f53d089c0a Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Sun, 17 Mar 2024 22:32:56 -0600 Subject: [PATCH 03/20] unified brotli impl with streaming API --- Makefile | 18 +-- arbcompress/compress_cgo.go | 87 ++++++++-- arbcompress/compress_common.go | 8 +- arbcompress/compress_wasm.go | 14 +- arbitrator/Cargo.lock | 24 +-- arbitrator/Cargo.toml | 2 + arbitrator/arbutil/src/format.rs | 2 +- arbitrator/brotli/Cargo.toml | 16 ++ arbitrator/brotli/build.rs | 15 ++ arbitrator/brotli/src/lib.rs | 148 +++++++++++++++++ arbitrator/brotli/src/types.rs | 66 ++++++++ arbitrator/brotli/src/wasmer_traits.rs | 29 ++++ arbitrator/caller-env/Cargo.toml | 7 +- arbitrator/caller-env/src/brotli/mod.rs | 151 +++--------------- arbitrator/caller-env/src/brotli/types.rs | 2 +- arbitrator/caller-env/src/lib.rs | 1 - arbitrator/caller-env/src/wasmer_traits.rs | 26 +-- arbitrator/jit/Cargo.toml | 1 + arbitrator/jit/build.rs | 7 - arbitrator/jit/src/arbcompress.rs | 2 +- arbitrator/prover/Cargo.toml | 4 +- arbitrator/prover/src/machine.rs | 30 ++-- arbitrator/prover/src/test.rs | 22 +++ arbitrator/wasm-libraries/Cargo.lock | 14 +- arbitrator/wasm-libraries/Cargo.toml | 2 +- .../{brotli => arbcompress}/Cargo.toml | 5 +- .../{brotli => arbcompress}/build.rs | 0 .../{brotli => arbcompress}/src/lib.rs | 7 +- 28 files changed, 470 insertions(+), 240 deletions(-) create mode 100644 arbitrator/brotli/Cargo.toml create mode 100644 arbitrator/brotli/build.rs create mode 100644 arbitrator/brotli/src/lib.rs create mode 100644 arbitrator/brotli/src/types.rs create mode 100644 arbitrator/brotli/src/wasmer_traits.rs delete mode 100644 arbitrator/jit/build.rs rename arbitrator/wasm-libraries/{brotli => arbcompress}/Cargo.toml (73%) rename arbitrator/wasm-libraries/{brotli => arbcompress}/build.rs (100%) rename arbitrator/wasm-libraries/{brotli => arbcompress}/src/lib.rs (93%) diff --git a/Makefile b/Makefile index f99318399..2e00a2fd4 100644 --- a/Makefile +++ b/Makefile @@ -52,7 +52,7 @@ replay_deps=arbos wavmio arbstate arbcompress solgen/go/node-interfacegen blsSig replay_wasm=$(output_latest)/replay.wasm arbitrator_generated_header=$(output_root)/include/arbitrator.h -arbitrator_wasm_libs=$(patsubst %, $(output_root)/machines/latest/%.wasm, forward wasi_stub host_io soft-float brotli user_host program_exec) +arbitrator_wasm_libs=$(patsubst %, $(output_root)/machines/latest/%.wasm, forward wasi_stub host_io soft-float arbcompress user_host program_exec) arbitrator_stylus_lib=$(output_root)/lib/libstylus.a prover_bin=$(output_root)/bin/prover arbitrator_jit=$(output_root)/bin/jit @@ -74,11 +74,11 @@ WASI_SYSROOT?=/opt/wasi-sdk/wasi-sysroot arbitrator_wasm_lib_flags=$(patsubst %, -l %, $(arbitrator_wasm_libs)) -rust_arbutil_files = $(wildcard arbitrator/arbutil/src/*.* arbitrator/arbutil/src/*/*.* arbitrator/arbutil/*.toml arbitrator/caller-env/src/*.* arbitrator/caller-env/src/*/*.* arbitrator/caller-env/*.toml) +rust_arbutil_files = $(wildcard arbitrator/arbutil/src/*.* arbitrator/arbutil/src/*/*.* arbitrator/arbutil/*.toml arbitrator/caller-env/src/*.* arbitrator/caller-env/src/*/*.* arbitrator/caller-env/*.toml) .make/cbrotli-lib prover_direct_includes = $(patsubst %,$(output_latest)/%.wasm, forward forward_stub) -prover_src = arbitrator/prover/src -rust_prover_files = $(wildcard $(prover_src)/*.* $(prover_src)/*/*.* arbitrator/prover/*.toml) $(rust_arbutil_files) $(prover_direct_includes) +prover_dir = arbitrator/prover/ +rust_prover_files = $(wildcard $(prover_dir)/src/*.* $(prover_dir)/src/*/*.* $(prover_dir)/*.toml $(prover_dir)/*.rs) $(rust_arbutil_files) $(prover_direct_includes) wasm_lib = arbitrator/wasm-libraries wasm_lib_deps = $(wildcard $(wasm_lib)/$(1)/*.toml $(wasm_lib)/$(1)/src/*.rs $(wasm_lib)/$(1)/*.rs) $(rust_arbutil_files) .make/machines @@ -274,7 +274,7 @@ $(arbitrator_stylus_lib): $(DEP_PREDICATE) $(stylus_files) cargo build --manifest-path arbitrator/Cargo.toml --release --lib -p stylus ${CARGOFLAGS} install arbitrator/target/release/libstylus.a $@ -$(arbitrator_jit): $(DEP_PREDICATE) .make/cbrotli-lib $(jit_files) +$(arbitrator_jit): $(DEP_PREDICATE) $(jit_files) mkdir -p `dirname $(arbitrator_jit)` cargo build --manifest-path arbitrator/Cargo.toml --release -p jit ${CARGOFLAGS} install arbitrator/target/release/jit $@ @@ -349,9 +349,9 @@ $(output_latest)/user_test.wasm: $(DEP_PREDICATE) $(call wasm_lib_deps,user-test cargo build --manifest-path arbitrator/wasm-libraries/Cargo.toml --release --target wasm32-wasi --package user-test install arbitrator/wasm-libraries/$(wasm32_wasi)/user_test.wasm $@ -$(output_latest)/brotli.wasm: $(DEP_PREDICATE) $(call wasm_lib_deps,brotli) $(wasm_lib_go_abi) .make/cbrotli-wasm - cargo build --manifest-path arbitrator/wasm-libraries/Cargo.toml --release --target wasm32-wasi --package brotli - install arbitrator/wasm-libraries/$(wasm32_wasi)/brotli.wasm $@ +$(output_latest)/arbcompress.wasm: $(DEP_PREDICATE) $(call wasm_lib_deps,brotli) $(wasm_lib_go_abi) .make/cbrotli-wasm + cargo build --manifest-path arbitrator/wasm-libraries/Cargo.toml --release --target wasm32-wasi --package arbcompress + install arbitrator/wasm-libraries/$(wasm32_wasi)/arbcompress.wasm $@ $(output_latest)/forward.wasm: $(DEP_PREDICATE) $(wasm_lib_forward) .make/machines cargo run --manifest-path $(forward_dir)/Cargo.toml -- --path $(forward_dir)/forward.wat @@ -363,7 +363,7 @@ $(output_latest)/forward_stub.wasm: $(DEP_PREDICATE) $(wasm_lib_forward) .make/m $(output_latest)/machine.wavm.br: $(DEP_PREDICATE) $(prover_bin) $(arbitrator_wasm_libs) $(replay_wasm) $(prover_bin) $(replay_wasm) --generate-binaries $(output_latest) \ - $(patsubst %,-l $(output_latest)/%.wasm, forward soft-float wasi_stub host_io user_host brotli program_exec) + $(patsubst %,-l $(output_latest)/%.wasm, forward soft-float wasi_stub host_io user_host arbcompress program_exec) $(arbitrator_cases)/%.wasm: $(arbitrator_cases)/%.wat wat2wasm $< -o $@ diff --git a/arbcompress/compress_cgo.go b/arbcompress/compress_cgo.go index 97981dafb..18a83a1ea 100644 --- a/arbcompress/compress_cgo.go +++ b/arbcompress/compress_cgo.go @@ -17,36 +17,82 @@ import ( "fmt" ) +type u8 = C.uint8_t +type usize = C.size_t + +type brotliBool = uint32 + +const ( + brotliFalse brotliBool = iota + brotliTrue +) + +const ( + rawSharedDictionary C.BrotliSharedDictionaryType = iota // LZ77 prefix dictionary + serializedSharedDictionary // Serialized dictionary +) + +func (d Dictionary) data() []byte { + return []byte{} +} + func Decompress(input []byte, maxSize int) ([]byte, error) { - outbuf := make([]byte, maxSize) - outsize := C.size_t(maxSize) - var ptr *C.uint8_t - if len(input) > 0 { - ptr = (*C.uint8_t)(&input[0]) + return DecompressWithDictionary(input, maxSize, EmptyDictionary) +} + +func DecompressWithDictionary(input []byte, maxSize int, dictionary Dictionary) ([]byte, error) { + state := C.BrotliDecoderCreateInstance(nil, nil, nil) + defer C.BrotliDecoderDestroyInstance(state) + + if dictionary != EmptyDictionary { + data := dictionary.data() + attached := C.BrotliDecoderAttachDictionary( + state, + rawSharedDictionary, + usize(len(data)), + sliceToPointer(data), + ) + if uint32(attached) != brotliTrue { + return nil, fmt.Errorf("failed decompression: failed to attach dictionary") + } } - res := C.BrotliDecoderDecompress(C.size_t(len(input)), ptr, &outsize, (*C.uint8_t)(&outbuf[0])) - if uint32(res) != BrotliSuccess { - return nil, fmt.Errorf("failed decompression: %d", res) + + inLen := usize(len(input)) + inPtr := sliceToPointer(input) + output := make([]byte, maxSize) + outLen := usize(maxSize) + outLeft := usize(len(output)) + outPtr := sliceToPointer(output) + + status := C.BrotliDecoderDecompressStream( + state, + &inLen, + &inPtr, + &outLeft, + &outPtr, + &outLen, //nolint:gocritic + ) + if uint32(status) != brotliSuccess { + return nil, fmt.Errorf("failed decompression: failed streaming: %d", status) } - if int(outsize) > maxSize { - return nil, fmt.Errorf("result too large: %d", outsize) + if int(outLen) > maxSize { + return nil, fmt.Errorf("failed decompression: result too large: %d", outLen) } - return outbuf[:outsize], nil + return output[:outLen], nil } func compressLevel(input []byte, level int) ([]byte, error) { maxOutSize := compressedBufferSizeFor(len(input)) outbuf := make([]byte, maxOutSize) outSize := C.size_t(maxOutSize) - var inputPtr *C.uint8_t - if len(input) > 0 { - inputPtr = (*C.uint8_t)(&input[0]) - } + inputPtr := sliceToPointer(input) + outPtr := sliceToPointer(outbuf) + res := C.BrotliEncoderCompress( C.int(level), C.BROTLI_DEFAULT_WINDOW, C.BROTLI_MODE_GENERIC, - C.size_t(len(input)), inputPtr, &outSize, (*C.uint8_t)(&outbuf[0]), + C.size_t(len(input)), inputPtr, &outSize, outPtr, ) - if uint32(res) != BrotliSuccess { + if uint32(res) != brotliSuccess { return nil, fmt.Errorf("failed compression: %d", res) } return outbuf[:outSize], nil @@ -55,3 +101,10 @@ func compressLevel(input []byte, level int) ([]byte, error) { func CompressWell(input []byte) ([]byte, error) { return compressLevel(input, LEVEL_WELL) } + +func sliceToPointer(slice []byte) *u8 { + if len(slice) == 0 { + slice = []byte{0x00} // ensures pointer is not null (shouldn't be necessary, but brotli docs are picky about NULL) + } + return (*u8)(&slice[0]) +} diff --git a/arbcompress/compress_common.go b/arbcompress/compress_common.go index f45d553a5..c841b5180 100644 --- a/arbcompress/compress_common.go +++ b/arbcompress/compress_common.go @@ -1,13 +1,13 @@ -// Copyright 2021-2022, Offchain Labs, Inc. +// Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE package arbcompress -type BrotliStatus = uint32 +type brotliStatus = uint32 const ( - BrotliFailure uint32 = iota - BrotliSuccess + brotliFailure brotliStatus = iota + brotliSuccess ) type Dictionary uint32 diff --git a/arbcompress/compress_wasm.go b/arbcompress/compress_wasm.go index 024aa9372..51f3b36de 100644 --- a/arbcompress/compress_wasm.go +++ b/arbcompress/compress_wasm.go @@ -14,12 +14,16 @@ import ( ) //go:wasmimport arbcompress brotli_compress -func brotliCompress(inBuf unsafe.Pointer, inBufLen uint32, outBuf unsafe.Pointer, outBufLen unsafe.Pointer, level, windowSize uint32) BrotliStatus +func brotliCompress(inBuf unsafe.Pointer, inBufLen uint32, outBuf unsafe.Pointer, outBufLen unsafe.Pointer, level, windowSize uint32) brotliStatus //go:wasmimport arbcompress brotli_decompress -func brotliDecompress(inBuf unsafe.Pointer, inBufLen uint32, outBuf unsafe.Pointer, outBufLen unsafe.Pointer, dictionary Dictionary) BrotliStatus +func brotliDecompress(inBuf unsafe.Pointer, inBufLen uint32, outBuf unsafe.Pointer, outBufLen unsafe.Pointer, dictionary Dictionary) brotliStatus func Decompress(input []byte, maxSize int) ([]byte, error) { + return DecompressWithDictionary(input, maxSize, EmptyDictionary) +} + +func DecompressWithDictionary(input []byte, maxSize int, dictionary Dictionary) ([]byte, error) { outBuf := make([]byte, maxSize) outLen := uint32(len(outBuf)) status := brotliDecompress( @@ -27,9 +31,9 @@ func Decompress(input []byte, maxSize int) ([]byte, error) { uint32(len(input)), arbutil.SliceToUnsafePointer(outBuf), unsafe.Pointer(&outLen), - EmptyDictionary, + dictionary, ) - if status != BrotliSuccess { + if status != brotliSuccess { return nil, fmt.Errorf("failed decompression") } return outBuf[:outLen], nil @@ -45,7 +49,7 @@ func compressLevel(input []byte, level uint32) ([]byte, error) { level, WINDOW_SIZE, ) - if status != BrotliSuccess { + if status != brotliSuccess { return nil, fmt.Errorf("failed compression") } return outBuf[:outLen], nil diff --git a/arbitrator/Cargo.lock b/arbitrator/Cargo.lock index 94aace7a7..58a35ce12 100644 --- a/arbitrator/Cargo.lock +++ b/arbitrator/Cargo.lock @@ -144,23 +144,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] -name = "brotli-sys" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4445dea95f4c2b41cde57cc9fee236ae4dbae88d8fcbdb4750fc1bb5d86aaecd" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "brotli2" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cb036c3eade309815c15ddbacec5b22c4d1f3983a774ab2eac2e3e9ea85568e" +name = "brotli" +version = "0.1.0" dependencies = [ - "brotli-sys", - "libc", + "num_enum", + "wasmer", ] [[package]] @@ -207,6 +195,7 @@ checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" name = "caller-env" version = "0.1.0" dependencies = [ + "brotli", "num_enum", "rand", "rand_pcg", @@ -790,6 +779,7 @@ name = "jit" version = "0.1.0" dependencies = [ "arbutil", + "brotli", "caller-env", "eyre", "hex", @@ -1207,7 +1197,7 @@ version = "0.1.0" dependencies = [ "arbutil", "bincode", - "brotli2", + "brotli", "derivative", "digest", "eyre", diff --git a/arbitrator/Cargo.toml b/arbitrator/Cargo.toml index 2d76c17b5..c889aea90 100644 --- a/arbitrator/Cargo.toml +++ b/arbitrator/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "arbutil", + "brotli", "caller-env", "prover", "stylus", @@ -21,6 +22,7 @@ repository = "https://github.com/OffchainLabs/nitro.git" rust-version = "1.67" [workspace.dependencies] +num_enum = { version = "0.7.2", default-features = false } wasmparser = "0.95" [profile.release] diff --git a/arbitrator/arbutil/src/format.rs b/arbitrator/arbutil/src/format.rs index 99e8b31b5..de4c0968e 100644 --- a/arbitrator/arbutil/src/format.rs +++ b/arbitrator/arbutil/src/format.rs @@ -13,7 +13,7 @@ pub fn time(span: Duration) -> String { let mut span = span.as_nanos() as f64; let mut unit = 0; - let units = vec![ + let units = [ "ns", "μs", "ms", "s", "min", "h", "d", "w", "mo", "yr", "dec", "cent", "mill", "eon", ]; let scale = [ diff --git a/arbitrator/brotli/Cargo.toml b/arbitrator/brotli/Cargo.toml new file mode 100644 index 000000000..513369518 --- /dev/null +++ b/arbitrator/brotli/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "brotli" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] +num_enum.workspace = true +wasmer = { path = "../tools/wasmer/lib/api", optional = true } + +[features] +wasmer_traits = ["dep:wasmer"] diff --git a/arbitrator/brotli/build.rs b/arbitrator/brotli/build.rs new file mode 100644 index 000000000..4bd9fe899 --- /dev/null +++ b/arbitrator/brotli/build.rs @@ -0,0 +1,15 @@ +// Copyright 2021-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +fn main() { + let target_arch = std::env::var("TARGET").unwrap(); + + if target_arch.contains("wasm32") { + println!("cargo:rustc-link-search=../../target/lib-wasm/"); + } else { + println!("cargo:rustc-link-search=../../target/lib/"); + } + println!("cargo:rustc-link-lib=static=brotlienc-static"); + println!("cargo:rustc-link-lib=static=brotlidec-static"); + println!("cargo:rustc-link-lib=static=brotlicommon-static"); +} diff --git a/arbitrator/brotli/src/lib.rs b/arbitrator/brotli/src/lib.rs new file mode 100644 index 000000000..8c67ffb4b --- /dev/null +++ b/arbitrator/brotli/src/lib.rs @@ -0,0 +1,148 @@ +// Copyright 2021-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +#![cfg_attr(target_arch = "wasm32", no_std)] + +extern crate alloc; + +use alloc::vec::Vec; +use core::{ffi::c_void, ptr}; + +mod types; + +#[cfg(feature = "wasmer_traits")] +mod wasmer_traits; + +use types::*; +pub use types::{BrotliStatus, Dictionary, DEFAULT_WINDOW_SIZE}; + +type DecoderState = c_void; +type CustomAllocator = c_void; +type HeapItem = c_void; + +// one-shot brotli API +extern "C" { + fn BrotliEncoderCompress( + quality: u32, + lgwin: u32, + mode: u32, + input_size: usize, + input_buffer: *const u8, + encoded_size: *mut usize, + encoded_buffer: *mut u8, + ) -> BrotliStatus; +} + +// custom dictionary API +extern "C" { + fn BrotliDecoderCreateInstance( + alloc: Option *mut HeapItem>, + free: Option, + opaque: *mut CustomAllocator, + ) -> *mut DecoderState; + + fn BrotliDecoderAttachDictionary( + state: *mut DecoderState, + dict_type: BrotliSharedDictionaryType, + dict_len: usize, + dictionary: *const u8, + ) -> BrotliBool; + + fn BrotliDecoderDecompressStream( + state: *mut DecoderState, + input_len: *mut usize, + input_ptr: *mut *const u8, + out_left: *mut usize, + out_ptr: *mut *mut u8, + out_len: *mut usize, + ) -> BrotliStatus; + + fn BrotliDecoderIsFinished(state: *const DecoderState) -> BrotliBool; + + fn BrotliDecoderDestroyInstance(state: *mut DecoderState); +} + +/// Brotli compresses a slice. +/// The output buffer must be sufficiently large. +pub fn compress(input: &[u8], output: &mut Vec, level: u32, window_size: u32) -> BrotliStatus { + let mut output_len = output.capacity(); + unsafe { + let res = BrotliEncoderCompress( + level, + window_size, + BROTLI_MODE_GENERIC, + input.len(), + input.as_ptr(), + &mut output_len, + output.as_mut_ptr(), + ); + if res != BrotliStatus::Success { + return BrotliStatus::Failure; + } + output.set_len(output_len); + } + BrotliStatus::Success +} + +/// Brotli decompresses a slice. +/// If `grow`, this function will attempt to grow the `output` buffer when needed. +pub fn decompress( + input: &[u8], + output: &mut Vec, + dictionary: Dictionary, + grow: bool, +) -> BrotliStatus { + unsafe { + let state = BrotliDecoderCreateInstance(None, None, ptr::null_mut()); + + macro_rules! require { + ($cond:expr) => { + if !$cond { + BrotliDecoderDestroyInstance(state); + return BrotliStatus::Failure; + } + }; + } + + if dictionary != Dictionary::Empty { + let attatched = BrotliDecoderAttachDictionary( + state, + BrotliSharedDictionaryType::Raw, + dictionary.len(), + dictionary.data(), + ); + require!(attatched == BrotliBool::True); + } + + let mut in_len = input.len(); + let mut in_ptr = input.as_ptr(); + let mut out_left = output.capacity(); + let mut out_ptr = output.as_mut_ptr(); + let mut out_len = out_left; + + loop { + let status = BrotliDecoderDecompressStream( + state, + &mut in_len as _, + &mut in_ptr as _, + &mut out_left as _, + &mut out_ptr as _, + &mut out_len as _, + ); + output.set_len(out_len); + + if grow && status == BrotliStatus::NeedsMoreOutput { + output.reserve(24 * 1024); + out_ptr = output.as_mut_ptr().add(out_len); + out_left = output.capacity() - out_len; + continue; + } + require!(status == BrotliStatus::Success); + require!(BrotliDecoderIsFinished(state) == BrotliBool::True); + break; + } + + BrotliDecoderDestroyInstance(state); + } + BrotliStatus::Success +} diff --git a/arbitrator/brotli/src/types.rs b/arbitrator/brotli/src/types.rs new file mode 100644 index 000000000..8ef78bff2 --- /dev/null +++ b/arbitrator/brotli/src/types.rs @@ -0,0 +1,66 @@ +// Copyright 2021-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +#![allow(dead_code, clippy::len_without_is_empty)] + +use num_enum::{IntoPrimitive, TryFromPrimitive}; + +pub const BROTLI_MODE_GENERIC: u32 = 0; +pub const DEFAULT_WINDOW_SIZE: u32 = 22; + +#[derive(PartialEq, IntoPrimitive, TryFromPrimitive)] +#[repr(u32)] +pub enum BrotliStatus { + Failure, + Success, + NeedsMoreInput, + NeedsMoreOutput, +} + +impl BrotliStatus { + pub fn is_ok(&self) -> bool { + self == &Self::Success + } + + pub fn is_err(&self) -> bool { + !self.is_ok() + } +} + +#[derive(PartialEq)] +#[repr(usize)] +pub(super) enum BrotliBool { + False, + True, +} + +#[repr(C)] +pub(super) enum BrotliSharedDictionaryType { + /// LZ77 prefix dictionary + Raw, + /// Serialized dictionary + Serialized, +} + +#[derive(Clone, Copy, PartialEq, IntoPrimitive, TryFromPrimitive)] +#[repr(u32)] +pub enum Dictionary { + Empty, + StylusProgram, +} + +impl Dictionary { + pub fn len(&self) -> usize { + match self { + Self::Empty => 0, + Self::StylusProgram => todo!(), + } + } + + pub fn data(&self) -> *const u8 { + match self { + Self::Empty => [].as_ptr(), + Self::StylusProgram => todo!(), + } + } +} diff --git a/arbitrator/brotli/src/wasmer_traits.rs b/arbitrator/brotli/src/wasmer_traits.rs new file mode 100644 index 000000000..9ae7f3b96 --- /dev/null +++ b/arbitrator/brotli/src/wasmer_traits.rs @@ -0,0 +1,29 @@ +// Copyright 2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use crate::types::{BrotliStatus, Dictionary}; +use wasmer::FromToNativeWasmType; + +unsafe impl FromToNativeWasmType for BrotliStatus { + type Native = i32; + + fn from_native(native: i32) -> Self { + Self::try_from(u32::from_native(native)).expect("unknown brotli status") + } + + fn to_native(self) -> i32 { + (self as u32).to_native() + } +} + +unsafe impl FromToNativeWasmType for Dictionary { + type Native = i32; + + fn from_native(native: i32) -> Self { + Self::try_from(u32::from_native(native)).expect("unknown brotli dictionary") + } + + fn to_native(self) -> i32 { + (self as u32).to_native() + } +} diff --git a/arbitrator/caller-env/Cargo.toml b/arbitrator/caller-env/Cargo.toml index 8b278f606..100bd7a82 100644 --- a/arbitrator/caller-env/Cargo.toml +++ b/arbitrator/caller-env/Cargo.toml @@ -1,14 +1,15 @@ [package] name = "caller-env" version = "0.1.0" -edition = "2021" +edition.workspace = true [dependencies] -num_enum = { version = "0.7.2", default-features = false } +brotli.path = "../brotli/" +num_enum.workspace = true rand_pcg = { version = "0.3.1", default-features = false } rand = { version = "0.8.4", default-features = false } wasmer = { path = "../tools/wasmer/lib/api", optional = true } [features] static_caller = [] -wasmer_traits = ["dep:wasmer"] +wasmer_traits = ["dep:wasmer", "brotli/wasmer_traits"] diff --git a/arbitrator/caller-env/src/brotli/mod.rs b/arbitrator/caller-env/src/brotli/mod.rs index 6ec33f430..fbd995174 100644 --- a/arbitrator/caller-env/src/brotli/mod.rs +++ b/arbitrator/caller-env/src/brotli/mod.rs @@ -5,158 +5,57 @@ use crate::{ExecEnv, GuestPtr, MemAccess}; use alloc::vec::Vec; -use core::{ffi::c_void, ptr}; +use brotli::{BrotliStatus, Dictionary}; -mod types; - -pub use types::*; - -type DecoderState = c_void; -type CustomAllocator = c_void; -type HeapItem = c_void; - -// one-shot brotli API -extern "C" { - fn BrotliEncoderCompress( - quality: u32, - lgwin: u32, - mode: u32, - input_size: usize, - input_buffer: *const u8, - encoded_size: *mut usize, - encoded_buffer: *mut u8, - ) -> BrotliStatus; -} - -// custom dictionary API -extern "C" { - fn BrotliDecoderCreateInstance( - alloc: Option *mut HeapItem>, - free: Option, - opaque: *mut CustomAllocator, - ) -> *mut DecoderState; - - fn BrotliDecoderAttachDictionary( - state: *mut DecoderState, - dict_type: BrotliSharedDictionaryType, - dict_len: usize, - dictionary: *const u8, - ) -> BrotliBool; - - fn BrotliDecoderDecompressStream( - state: *mut DecoderState, - input_len: *mut usize, - input_ptr: *mut *const u8, - out_left: *mut usize, - out_ptr: *mut *mut u8, - out_len: *mut usize, - ) -> BrotliStatus; - - fn BrotliDecoderDestroyInstance(state: *mut DecoderState); -} - -const BROTLI_MODE_GENERIC: u32 = 0; - -/// Brotli decompresses a go slice using a custom dictionary. -/// -/// # Safety +/// Brotli compresses a go slice /// /// The output buffer must be sufficiently large. /// The pointers must not be null. -pub fn brotli_decompress( +pub fn brotli_compress( mem: &mut M, _env: &mut E, in_buf_ptr: GuestPtr, in_buf_len: u32, out_buf_ptr: GuestPtr, out_len_ptr: GuestPtr, - dictionary: Dictionary, + level: u32, + window_size: u32, ) -> BrotliStatus { let input = mem.read_slice(in_buf_ptr, in_buf_len as usize); - let prior_out_len = mem.read_u32(out_len_ptr) as usize; - - let mut output = Vec::with_capacity(prior_out_len); - let mut out_len = prior_out_len; - unsafe { - let state = BrotliDecoderCreateInstance(None, None, ptr::null_mut()); - - macro_rules! require { - ($cond:expr) => { - if !$cond { - BrotliDecoderDestroyInstance(state); - return BrotliStatus::Failure; - } - }; - } - - if dictionary != Dictionary::Empty { - let attatched = BrotliDecoderAttachDictionary( - state, - BrotliSharedDictionaryType::Raw, - dictionary.len(), - dictionary.data(), - ); - require!(attatched == BrotliBool::True); - } + let mut output = Vec::with_capacity(mem.read_u32(out_len_ptr) as usize); - let mut in_len = input.len(); - let mut in_ptr = input.as_ptr(); - let mut out_left = prior_out_len; - let mut out_ptr = output.as_mut_ptr(); - - let status = BrotliDecoderDecompressStream( - state, - &mut in_len as _, - &mut in_ptr as _, - &mut out_left as _, - &mut out_ptr as _, - &mut out_len as _, - ); - require!(status == BrotliStatus::Success && out_len <= prior_out_len); - - BrotliDecoderDestroyInstance(state); - output.set_len(out_len); + let status = brotli::compress(&input, &mut output, level, window_size); + if status.is_ok() { + let out_len = output.len(); + mem.write_slice(out_buf_ptr, &output[..out_len]); + mem.write_u32(out_len_ptr, out_len as u32); } - mem.write_slice(out_buf_ptr, &output[..out_len]); - mem.write_u32(out_len_ptr, out_len as u32); - BrotliStatus::Success + status } -/// Brotli compresses a go slice +/// Brotli decompresses a go slice using a custom dictionary. +/// +/// # Safety /// /// The output buffer must be sufficiently large. /// The pointers must not be null. -pub fn brotli_compress( +pub fn brotli_decompress( mem: &mut M, _env: &mut E, in_buf_ptr: GuestPtr, in_buf_len: u32, out_buf_ptr: GuestPtr, out_len_ptr: GuestPtr, - level: u32, - window_size: u32, + dictionary: Dictionary, ) -> BrotliStatus { - let in_slice = mem.read_slice(in_buf_ptr, in_buf_len as usize); - let orig_output_len = mem.read_u32(out_len_ptr) as usize; - let mut output = Vec::with_capacity(orig_output_len); - let mut output_len = orig_output_len; + let input = mem.read_slice(in_buf_ptr, in_buf_len as usize); + let mut output = Vec::with_capacity(mem.read_u32(out_len_ptr) as usize); - unsafe { - let res = BrotliEncoderCompress( - level, - window_size, - BROTLI_MODE_GENERIC, - in_buf_len as usize, - in_slice.as_ptr(), - &mut output_len, - output.as_mut_ptr(), - ); - if (res != BrotliStatus::Success) || (output_len > orig_output_len) { - return BrotliStatus::Failure; - } - output.set_len(output_len); + let status = brotli::decompress(&input, &mut output, dictionary, false); + if status.is_ok() { + let out_len = output.len(); + mem.write_slice(out_buf_ptr, &output[..out_len]); + mem.write_u32(out_len_ptr, out_len as u32); } - mem.write_slice(out_buf_ptr, &output[..output_len]); - mem.write_u32(out_len_ptr, output_len as u32); - BrotliStatus::Success + status } diff --git a/arbitrator/caller-env/src/brotli/types.rs b/arbitrator/caller-env/src/brotli/types.rs index 6a2e5426c..7af2e8b87 100644 --- a/arbitrator/caller-env/src/brotli/types.rs +++ b/arbitrator/caller-env/src/brotli/types.rs @@ -15,8 +15,8 @@ pub enum BrotliStatus { #[derive(PartialEq)] #[repr(usize)] pub(super) enum BrotliBool { - True, False, + True, } #[repr(C)] diff --git a/arbitrator/caller-env/src/lib.rs b/arbitrator/caller-env/src/lib.rs index ffe502d71..baff05be0 100644 --- a/arbitrator/caller-env/src/lib.rs +++ b/arbitrator/caller-env/src/lib.rs @@ -8,7 +8,6 @@ extern crate alloc; use alloc::vec::Vec; use rand_pcg::Pcg32; -pub use brotli::BrotliStatus; pub use guest_ptr::GuestPtr; pub use wasip1_stub::Errno; diff --git a/arbitrator/caller-env/src/wasmer_traits.rs b/arbitrator/caller-env/src/wasmer_traits.rs index 3b28d2c0b..babc22c6f 100644 --- a/arbitrator/caller-env/src/wasmer_traits.rs +++ b/arbitrator/caller-env/src/wasmer_traits.rs @@ -1,7 +1,7 @@ // Copyright 2024, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE -use crate::{brotli::Dictionary, BrotliStatus, Errno, GuestPtr}; +use crate::{Errno, GuestPtr}; use wasmer::{FromToNativeWasmType, WasmPtr}; unsafe impl FromToNativeWasmType for GuestPtr { @@ -28,30 +28,6 @@ unsafe impl FromToNativeWasmType for Errno { } } -unsafe impl FromToNativeWasmType for BrotliStatus { - type Native = i32; - - fn from_native(native: i32) -> Self { - Self::try_from(u32::from_native(native)).expect("unknown brotli status") - } - - fn to_native(self) -> i32 { - (self as u32).to_native() - } -} - -unsafe impl FromToNativeWasmType for Dictionary { - type Native = i32; - - fn from_native(native: i32) -> Self { - Self::try_from(u32::from_native(native)).expect("unknown brotli dictionary") - } - - fn to_native(self) -> i32 { - (self as u32).to_native() - } -} - impl From for WasmPtr { fn from(value: GuestPtr) -> Self { WasmPtr::new(value.0) diff --git a/arbitrator/jit/Cargo.toml b/arbitrator/jit/Cargo.toml index 58861e873..2864c92ab 100644 --- a/arbitrator/jit/Cargo.toml +++ b/arbitrator/jit/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] arbutil = { path = "../arbutil/" } +brotli = { path = "../brotli/", features = ["wasmer_traits"] } caller-env = { path = "../caller-env/", features = ["wasmer_traits"] } prover = { path = "../prover/", default-features = false, features = ["native"] } stylus = { path = "../stylus/", default-features = false } diff --git a/arbitrator/jit/build.rs b/arbitrator/jit/build.rs deleted file mode 100644 index e18155017..000000000 --- a/arbitrator/jit/build.rs +++ /dev/null @@ -1,7 +0,0 @@ -fn main() { - // Tell Cargo that if the given file changes, to rerun this build script. - println!("cargo:rustc-link-search=../target/lib/"); - println!("cargo:rustc-link-lib=static=brotlienc-static"); - println!("cargo:rustc-link-lib=static=brotlidec-static"); - println!("cargo:rustc-link-lib=static=brotlicommon-static"); -} diff --git a/arbitrator/jit/src/arbcompress.rs b/arbitrator/jit/src/arbcompress.rs index af6068411..e5a0de36b 100644 --- a/arbitrator/jit/src/arbcompress.rs +++ b/arbitrator/jit/src/arbcompress.rs @@ -4,7 +4,7 @@ use crate::caller_env::{JitEnv, JitExecEnv}; use crate::machine::Escape; use crate::machine::WasmEnvMut; -use caller_env::brotli::{BrotliStatus, Dictionary}; +use brotli::{BrotliStatus, Dictionary}; use caller_env::{self, GuestPtr}; macro_rules! wrap { diff --git a/arbitrator/prover/Cargo.toml b/arbitrator/prover/Cargo.toml index 84c18a83c..15a6dd99e 100644 --- a/arbitrator/prover/Cargo.toml +++ b/arbitrator/prover/Cargo.toml @@ -27,9 +27,9 @@ lazy_static = "1.4.0" itertools = "0.10.5" wat = "1.0.56" smallvec = { version = "1.10.0", features = ["serde"] } -brotli2 = { version = "0.3.2", optional = true } rayon = { version = "1.5.1", optional = true } arbutil = { path = "../arbutil/" } +brotli = { path = "../brotli/" } wasmer = { path = "../tools/wasmer/lib/api", optional = true } wasmer-types = { path = "../tools/wasmer/lib/types" } wasmer-compiler-singlepass = { path = "../tools/wasmer/lib/compiler-singlepass", optional = true, default-features = false, features = ["std", "unwind", "avx"] } @@ -43,6 +43,6 @@ crate-type = ["staticlib", "lib"] [features] default = ["native", "rayon", "singlepass_rayon"] -native = ["dep:wasmer", "dep:wasmer-compiler-singlepass", "dep:brotli2"] +native = ["dep:wasmer", "dep:wasmer-compiler-singlepass", "brotli/wasmer_traits"] singlepass_rayon = ["wasmer-compiler-singlepass?/rayon"] rayon = ["dep:rayon"] diff --git a/arbitrator/prover/src/machine.rs b/arbitrator/prover/src/machine.rs index 6cb6056ae..2481a6362 100644 --- a/arbitrator/prover/src/machine.rs +++ b/arbitrator/prover/src/machine.rs @@ -1396,11 +1396,17 @@ impl Machine { Ok(mach) } - #[cfg(feature = "native")] pub fn new_from_wavm(wavm_binary: &Path) -> Result { - let f = BufReader::new(File::open(wavm_binary)?); - let decompressor = brotli2::read::BrotliDecoder::new(f); - let mut modules: Vec = bincode::deserialize_from(decompressor)?; + let mut modules: Vec = { + use brotli::Dictionary; + let compressed = std::fs::read(wavm_binary)?; + let mut modules = vec![]; + if !brotli::decompress(&compressed, &mut modules, Dictionary::Empty, true).is_ok() { + bail!("failed to decompress wavm binary"); + } + bincode::deserialize(&modules)? + }; + for module in modules.iter_mut() { for table in module.tables.iter_mut() { table.elems_merkle = Merkle::new( @@ -1451,18 +1457,20 @@ impl Machine { Ok(mach) } - #[cfg(feature = "native")] pub fn serialize_binary>(&self, path: P) -> Result<()> { ensure!( self.hash() == self.initial_hash, "serialize_binary can only be called on initial machine", ); - let mut f = File::create(path)?; - let mut compressor = brotli2::write::BrotliEncoder::new(BufWriter::new(&mut f), 9); - bincode::serialize_into(&mut compressor, &self.modules)?; - compressor.flush()?; - drop(compressor); - f.sync_data()?; + let modules = bincode::serialize(&self.modules)?; + let window = brotli::DEFAULT_WINDOW_SIZE; + let mut output = Vec::with_capacity(2 * modules.len()); + if !brotli::compress(&modules, &mut output, 9, window).is_ok() { + bail!("failed to compress binary"); + } + + let mut file = File::create(path)?; + file.write_all(&output)?; Ok(()) } diff --git a/arbitrator/prover/src/test.rs b/arbitrator/prover/src/test.rs index 44a8dff09..ee57281ce 100644 --- a/arbitrator/prover/src/test.rs +++ b/arbitrator/prover/src/test.rs @@ -4,6 +4,8 @@ #![cfg(test)] use crate::binary; +use brotli::Dictionary; +use eyre::Result; use std::path::Path; fn as_wasm(wat: &str) -> Vec { @@ -52,3 +54,23 @@ pub fn reject_ambiguous_imports() { ); let _ = binary::parse(&wasm, Path::new("")).unwrap_err(); } + +#[test] +pub fn test_compress() -> Result<()> { + let data = std::fs::read("test-cases/block.wat")?; + let dict = Dictionary::Empty; + + let deflate = &mut Vec::with_capacity(data.len()); + assert!(brotli::compress(&data, deflate, 0, 22).is_ok()); + assert!(!deflate.is_empty()); + + let inflate = &mut Vec::with_capacity(data.len()); + assert!(brotli::decompress(deflate, inflate, dict, false).is_ok()); + assert_eq!(hex::encode(inflate), hex::encode(&data)); + + let inflate = &mut vec![]; + assert!(brotli::decompress(deflate, inflate, dict, false).is_err()); + assert!(brotli::decompress(deflate, inflate, dict, true).is_ok()); + assert_eq!(hex::encode(inflate), hex::encode(&data)); + Ok(()) +} diff --git a/arbitrator/wasm-libraries/Cargo.lock b/arbitrator/wasm-libraries/Cargo.lock index f7bc33d46..48018bc71 100644 --- a/arbitrator/wasm-libraries/Cargo.lock +++ b/arbitrator/wasm-libraries/Cargo.lock @@ -22,6 +22,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "arbcompress" +version = "0.1.0" +dependencies = [ + "brotli", + "caller-env", + "paste", +] + [[package]] name = "arbutil" version = "0.1.0" @@ -108,8 +117,7 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" name = "brotli" version = "0.1.0" dependencies = [ - "caller-env", - "paste", + "num_enum", ] [[package]] @@ -138,6 +146,7 @@ dependencies = [ name = "caller-env" version = "0.1.0" dependencies = [ + "brotli", "num_enum", "rand", "rand_pcg", @@ -780,6 +789,7 @@ version = "0.1.0" dependencies = [ "arbutil", "bincode", + "brotli", "derivative", "digest", "eyre", diff --git a/arbitrator/wasm-libraries/Cargo.toml b/arbitrator/wasm-libraries/Cargo.toml index e9e5aa4b8..837df8f4d 100644 --- a/arbitrator/wasm-libraries/Cargo.toml +++ b/arbitrator/wasm-libraries/Cargo.toml @@ -1,6 +1,6 @@ [workspace] members = [ - "brotli", + "arbcompress", "wasi-stub", "host-io", "user-host", diff --git a/arbitrator/wasm-libraries/brotli/Cargo.toml b/arbitrator/wasm-libraries/arbcompress/Cargo.toml similarity index 73% rename from arbitrator/wasm-libraries/brotli/Cargo.toml rename to arbitrator/wasm-libraries/arbcompress/Cargo.toml index 640db7230..ec4c32c1e 100644 --- a/arbitrator/wasm-libraries/brotli/Cargo.toml +++ b/arbitrator/wasm-libraries/arbcompress/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "brotli" +name = "arbcompress" version = "0.1.0" edition = "2021" publish = false @@ -8,5 +8,6 @@ publish = false crate-type = ["cdylib"] [dependencies] -paste = { version = "1.0.14" } +brotli.path = "../../brotli" caller-env = { path = "../../caller-env/", features = ["static_caller"] } +paste = "1.0.14" diff --git a/arbitrator/wasm-libraries/brotli/build.rs b/arbitrator/wasm-libraries/arbcompress/build.rs similarity index 100% rename from arbitrator/wasm-libraries/brotli/build.rs rename to arbitrator/wasm-libraries/arbcompress/build.rs diff --git a/arbitrator/wasm-libraries/brotli/src/lib.rs b/arbitrator/wasm-libraries/arbcompress/src/lib.rs similarity index 93% rename from arbitrator/wasm-libraries/brotli/src/lib.rs rename to arbitrator/wasm-libraries/arbcompress/src/lib.rs index a831a37e5..a65331031 100644 --- a/arbitrator/wasm-libraries/brotli/src/lib.rs +++ b/arbitrator/wasm-libraries/arbcompress/src/lib.rs @@ -3,11 +3,8 @@ #![allow(clippy::missing_safety_doc)] // TODO: add safety docs -use caller_env::{ - self, - brotli::{BrotliStatus, Dictionary}, - GuestPtr, -}; +use brotli::{BrotliStatus, Dictionary}; +use caller_env::{self, GuestPtr}; use paste::paste; macro_rules! wrap { From 4c422c5b14cb683cdb5f2ecab0172dd0062e143b Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Mon, 18 Mar 2024 10:12:41 -0600 Subject: [PATCH 04/20] go uses arbbrotli --- Makefile | 20 +++++- arbcompress/compress_cgo.go | 104 +++++++++++--------------------- arbcompress/compress_common.go | 9 +-- arbcompress/compress_wasm.go | 7 +++ arbitrator/brotli/Cargo.toml | 3 + arbitrator/brotli/build.rs | 13 +++- arbitrator/brotli/cbindgen.toml | 10 +++ arbitrator/brotli/src/cgo.rs | 60 ++++++++++++++++++ arbitrator/brotli/src/lib.rs | 87 +++++++++++++++++++++++--- 9 files changed, 224 insertions(+), 89 deletions(-) create mode 100644 arbitrator/brotli/cbindgen.toml create mode 100644 arbitrator/brotli/src/cgo.rs diff --git a/Makefile b/Makefile index 2e00a2fd4..2fa68a1cc 100644 --- a/Makefile +++ b/Makefile @@ -51,6 +51,10 @@ replay_deps=arbos wavmio arbstate arbcompress solgen/go/node-interfacegen blsSig replay_wasm=$(output_latest)/replay.wasm +arb_brotli_lib = $(output_root)/lib/libbrotli.a +brotli_cgo_header = $(output_root)/include/arb_brotli.h +arb_brotli_files = $(wildcard arbitrator/brotli/src/*.* arbitrator/brotli/src/*/*.* arbitrator/brotli/*.toml arbitrator/brotli/*.rs) .make/cbrotli-lib + arbitrator_generated_header=$(output_root)/include/arbitrator.h arbitrator_wasm_libs=$(patsubst %, $(output_root)/machines/latest/%.wasm, forward wasi_stub host_io soft-float arbcompress user_host program_exec) arbitrator_stylus_lib=$(output_root)/lib/libstylus.a @@ -157,12 +161,13 @@ build-node-deps: $(go_source) build-prover-header build-prover-lib build-jit .ma test-go-deps: \ build-replay-env \ $(stylus_test_wasms) \ + $(arb_brotli_lib) \ $(arbitrator_stylus_lib) \ $(patsubst %,$(arbitrator_cases)/%.wasm, global-state read-inboxmsg-10 global-state-wrapper const) -build-prover-header: $(arbitrator_generated_header) +build-prover-header: $(arbitrator_generated_header) $(brotli_cgo_header) -build-prover-lib: $(arbitrator_stylus_lib) +build-prover-lib: $(arbitrator_stylus_lib) $(arb_brotli_lib) build-prover-bin: $(prover_bin) @@ -269,6 +274,11 @@ $(prover_bin): $(DEP_PREDICATE) $(rust_prover_files) cargo build --manifest-path arbitrator/Cargo.toml --release --bin prover ${CARGOFLAGS} install arbitrator/target/release/prover $@ +$(arb_brotli_lib): $(DEP_PREDICATE) $(arb_brotli_files) + mkdir -p `dirname $(arb_brotli_lib)` + cargo build --manifest-path arbitrator/Cargo.toml --release --lib -p brotli ${CARGOFLAGS} + install arbitrator/target/release/libbrotli.a $@ + $(arbitrator_stylus_lib): $(DEP_PREDICATE) $(stylus_files) mkdir -p `dirname $(arbitrator_stylus_lib)` cargo build --manifest-path arbitrator/Cargo.toml --release --lib -p stylus ${CARGOFLAGS} @@ -291,6 +301,12 @@ $(arbitrator_generated_header): $(DEP_PREDICATE) $(stylus_files) cd arbitrator/stylus && cbindgen --config cbindgen.toml --crate stylus --output ../../$(arbitrator_generated_header) @touch -c $@ # cargo might decide to not rebuild the header +$(brotli_cgo_header): $(DEP_PREDICATE) $(arb_brotli_files) + @echo creating ${PWD}/$(brotli_cgo_header) + mkdir -p `dirname $(brotli_cgo_header)` + cd arbitrator/brotli && cbindgen --config cbindgen.toml --crate brotli --output ../../$(brotli_cgo_header) + @touch -c $@ # cargo might decide to not rebuild the header + $(output_latest)/wasi_stub.wasm: $(DEP_PREDICATE) $(call wasm_lib_deps,wasi-stub) cargo build --manifest-path arbitrator/wasm-libraries/Cargo.toml --release --target wasm32-unknown-unknown --package wasi-stub install arbitrator/wasm-libraries/$(wasm32_unknown)/wasi_stub.wasm $@ diff --git a/arbcompress/compress_cgo.go b/arbcompress/compress_cgo.go index 18a83a1ea..2641cbda4 100644 --- a/arbcompress/compress_cgo.go +++ b/arbcompress/compress_cgo.go @@ -8,103 +8,69 @@ package arbcompress /* #cgo CFLAGS: -g -Wall -I${SRCDIR}/../target/include/ -#cgo LDFLAGS: ${SRCDIR}/../target/lib/libbrotlidec-static.a ${SRCDIR}/../target/lib/libbrotlienc-static.a ${SRCDIR}/../target/lib/libbrotlicommon-static.a -lm -#include "brotli/encode.h" -#include "brotli/decode.h" +#cgo LDFLAGS: ${SRCDIR}/../target/lib/libbrotli.a -lm +#include "arb_brotli.h" */ import "C" -import ( - "fmt" -) +import "fmt" type u8 = C.uint8_t +type u32 = C.uint32_t type usize = C.size_t type brotliBool = uint32 +type brotliBuffer = C.BrotliBuffer const ( brotliFalse brotliBool = iota brotliTrue ) -const ( - rawSharedDictionary C.BrotliSharedDictionaryType = iota // LZ77 prefix dictionary - serializedSharedDictionary // Serialized dictionary -) - -func (d Dictionary) data() []byte { - return []byte{} -} - func Decompress(input []byte, maxSize int) ([]byte, error) { return DecompressWithDictionary(input, maxSize, EmptyDictionary) } func DecompressWithDictionary(input []byte, maxSize int, dictionary Dictionary) ([]byte, error) { - state := C.BrotliDecoderCreateInstance(nil, nil, nil) - defer C.BrotliDecoderDestroyInstance(state) - - if dictionary != EmptyDictionary { - data := dictionary.data() - attached := C.BrotliDecoderAttachDictionary( - state, - rawSharedDictionary, - usize(len(data)), - sliceToPointer(data), - ) - if uint32(attached) != brotliTrue { - return nil, fmt.Errorf("failed decompression: failed to attach dictionary") - } - } - - inLen := usize(len(input)) - inPtr := sliceToPointer(input) output := make([]byte, maxSize) - outLen := usize(maxSize) - outLeft := usize(len(output)) - outPtr := sliceToPointer(output) + outbuf := sliceToBuffer(output) + inbuf := sliceToBuffer(input) - status := C.BrotliDecoderDecompressStream( - state, - &inLen, - &inPtr, - &outLeft, - &outPtr, - &outLen, //nolint:gocritic - ) - if uint32(status) != brotliSuccess { - return nil, fmt.Errorf("failed decompression: failed streaming: %d", status) + status := C.brotli_decompress(inbuf, outbuf, C.Dictionary(dictionary)) + if status != C.BrotliStatus_Success { + return nil, fmt.Errorf("failed decompression: %d", status) } - if int(outLen) > maxSize { - return nil, fmt.Errorf("failed decompression: result too large: %d", outLen) + if *outbuf.len > usize(maxSize) { + return nil, fmt.Errorf("failed decompression: result too large: %d", *outbuf.len) } - return output[:outLen], nil + output = output[:*outbuf.len] + return output, nil } -func compressLevel(input []byte, level int) ([]byte, error) { - maxOutSize := compressedBufferSizeFor(len(input)) - outbuf := make([]byte, maxOutSize) - outSize := C.size_t(maxOutSize) - inputPtr := sliceToPointer(input) - outPtr := sliceToPointer(outbuf) - - res := C.BrotliEncoderCompress( - C.int(level), C.BROTLI_DEFAULT_WINDOW, C.BROTLI_MODE_GENERIC, - C.size_t(len(input)), inputPtr, &outSize, outPtr, - ) - if uint32(res) != brotliSuccess { - return nil, fmt.Errorf("failed compression: %d", res) - } - return outbuf[:outSize], nil +func CompressWell(input []byte) ([]byte, error) { + return compressLevel(input, EmptyDictionary, LEVEL_WELL) } -func CompressWell(input []byte) ([]byte, error) { - return compressLevel(input, LEVEL_WELL) +func compressLevel(input []byte, dictionary Dictionary, level int) ([]byte, error) { + maxSize := compressedBufferSizeFor(len(input)) + output := make([]byte, maxSize) + outbuf := sliceToBuffer(output) + inbuf := sliceToBuffer(input) + + status := C.brotli_compress(inbuf, outbuf, C.Dictionary(dictionary), u32(level)) + if status != C.BrotliStatus_Success { + return nil, fmt.Errorf("failed decompression: %d", status) + } + output = output[:*outbuf.len] + return output, nil } -func sliceToPointer(slice []byte) *u8 { - if len(slice) == 0 { +func sliceToBuffer(slice []byte) brotliBuffer { + count := usize(len(slice)) + if count == 0 { slice = []byte{0x00} // ensures pointer is not null (shouldn't be necessary, but brotli docs are picky about NULL) } - return (*u8)(&slice[0]) + return brotliBuffer{ + ptr: (*u8)(&slice[0]), + len: &count, + } } diff --git a/arbcompress/compress_common.go b/arbcompress/compress_common.go index c841b5180..705acf192 100644 --- a/arbcompress/compress_common.go +++ b/arbcompress/compress_common.go @@ -3,13 +3,6 @@ package arbcompress -type brotliStatus = uint32 - -const ( - brotliFailure brotliStatus = iota - brotliSuccess -) - type Dictionary uint32 const ( @@ -26,5 +19,5 @@ func compressedBufferSizeFor(length int) int { } func CompressFast(input []byte) ([]byte, error) { - return compressLevel(input, LEVEL_FAST) + return compressLevel(input, EmptyDictionary, LEVEL_FAST) } diff --git a/arbcompress/compress_wasm.go b/arbcompress/compress_wasm.go index 51f3b36de..91bb8b973 100644 --- a/arbcompress/compress_wasm.go +++ b/arbcompress/compress_wasm.go @@ -13,6 +13,13 @@ import ( "github.com/offchainlabs/nitro/arbutil" ) +type brotliStatus = uint32 + +const ( + brotliFailure brotliStatus = iota + brotliSuccess +) + //go:wasmimport arbcompress brotli_compress func brotliCompress(inBuf unsafe.Pointer, inBufLen uint32, outBuf unsafe.Pointer, outBufLen unsafe.Pointer, level, windowSize uint32) brotliStatus diff --git a/arbitrator/brotli/Cargo.toml b/arbitrator/brotli/Cargo.toml index 513369518..c27e2786b 100644 --- a/arbitrator/brotli/Cargo.toml +++ b/arbitrator/brotli/Cargo.toml @@ -12,5 +12,8 @@ rust-version.workspace = true num_enum.workspace = true wasmer = { path = "../tools/wasmer/lib/api", optional = true } +[lib] +crate-type = ["lib", "staticlib"] + [features] wasmer_traits = ["dep:wasmer"] diff --git a/arbitrator/brotli/build.rs b/arbitrator/brotli/build.rs index 4bd9fe899..dd1a08a5f 100644 --- a/arbitrator/brotli/build.rs +++ b/arbitrator/brotli/build.rs @@ -1,13 +1,22 @@ // Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE +use std::{env, path::Path}; + fn main() { - let target_arch = std::env::var("TARGET").unwrap(); + let target_arch = env::var("TARGET").unwrap(); + let manifest = env::var("CARGO_MANIFEST_DIR").unwrap(); + let manifest = Path::new(&manifest); if target_arch.contains("wasm32") { println!("cargo:rustc-link-search=../../target/lib-wasm/"); } else { - println!("cargo:rustc-link-search=../../target/lib/"); + // search for brotli libs depending on where cargo is invoked + let arbitrator = Some(Path::new("arbitrator").file_name()); + match arbitrator == manifest.parent().map(Path::file_name) { + true => println!("cargo:rustc-link-search=../target/lib/"), + false => println!("cargo:rustc-link-search=../../target/lib/"), + } } println!("cargo:rustc-link-lib=static=brotlienc-static"); println!("cargo:rustc-link-lib=static=brotlidec-static"); diff --git a/arbitrator/brotli/cbindgen.toml b/arbitrator/brotli/cbindgen.toml new file mode 100644 index 000000000..0c28a7ccb --- /dev/null +++ b/arbitrator/brotli/cbindgen.toml @@ -0,0 +1,10 @@ +language = "C" +include_guard = "brotli_bindings" + +[parse] +parse_deps = false + +[enum] +prefix_with_name = true + +[export] diff --git a/arbitrator/brotli/src/cgo.rs b/arbitrator/brotli/src/cgo.rs new file mode 100644 index 000000000..1ff4f421e --- /dev/null +++ b/arbitrator/brotli/src/cgo.rs @@ -0,0 +1,60 @@ +// Copyright 2021-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use crate::{BrotliStatus, Dictionary, DEFAULT_WINDOW_SIZE}; + +#[derive(Clone, Copy)] +#[repr(C)] +pub struct BrotliBuffer { + /// Points to data owned by Go. + ptr: *mut u8, + /// The length in bytes. + len: *mut usize, +} + +impl BrotliBuffer { + fn as_slice(&self) -> &[u8] { + let len = unsafe { *self.len }; + if len == 0 { + return &[]; + } + unsafe { std::slice::from_raw_parts(self.ptr, len) } + } + + fn as_mut_slice(&mut self) -> &mut [u8] { + let len = unsafe { *self.len }; + if len == 0 { + return &mut []; + } + unsafe { std::slice::from_raw_parts_mut(self.ptr, len) } + } +} + +#[no_mangle] +pub extern "C" fn brotli_compress( + input: BrotliBuffer, + mut output: BrotliBuffer, + dictionary: Dictionary, + level: u32, +) -> BrotliStatus { + let window = DEFAULT_WINDOW_SIZE; + let buffer = output.as_mut_slice(); + match crate::compress_fixed(input.as_slice(), buffer, level, window, dictionary) { + Ok(written) => unsafe { *output.len = written }, + Err(status) => return status, + } + BrotliStatus::Success +} + +#[no_mangle] +pub extern "C" fn brotli_decompress( + input: BrotliBuffer, + mut output: BrotliBuffer, + dictionary: Dictionary, +) -> BrotliStatus { + match crate::decompress_fixed(input.as_slice(), output.as_mut_slice(), dictionary) { + Ok(written) => unsafe { *output.len = written }, + Err(status) => return status, + } + BrotliStatus::Success +} diff --git a/arbitrator/brotli/src/lib.rs b/arbitrator/brotli/src/lib.rs index 8c67ffb4b..114b04003 100644 --- a/arbitrator/brotli/src/lib.rs +++ b/arbitrator/brotli/src/lib.rs @@ -8,6 +8,7 @@ extern crate alloc; use alloc::vec::Vec; use core::{ffi::c_void, ptr}; +pub mod cgo; mod types; #[cfg(feature = "wasmer_traits")] @@ -62,7 +63,7 @@ extern "C" { fn BrotliDecoderDestroyInstance(state: *mut DecoderState); } -/// Brotli compresses a slice. +/// Brotli compresses a slice into a vec, growing as needed. /// The output buffer must be sufficiently large. pub fn compress(input: &[u8], output: &mut Vec, level: u32, window_size: u32) -> BrotliStatus { let mut output_len = output.capacity(); @@ -84,14 +85,35 @@ pub fn compress(input: &[u8], output: &mut Vec, level: u32, window_size: u32 BrotliStatus::Success } -/// Brotli decompresses a slice. -/// If `grow`, this function will attempt to grow the `output` buffer when needed. -pub fn decompress( +/// Brotli compresses a slice. +/// The output buffer must be sufficiently large. +pub fn compress_fixed( input: &[u8], - output: &mut Vec, + output: &mut [u8], + level: u32, + window_size: u32, dictionary: Dictionary, - grow: bool, -) -> BrotliStatus { +) -> Result { + let mut output_len = output.len(); + unsafe { + let res = BrotliEncoderCompress( + level, + window_size, + BROTLI_MODE_GENERIC, + input.len(), + input.as_ptr(), + &mut output_len, + output.as_mut_ptr(), + ); + if res != BrotliStatus::Success { + return Err(BrotliStatus::Failure); + } + Ok(output_len) + } +} + +/// Brotli decompresses a slice into a vec, growing as needed. +pub fn decompress(input: &[u8], output: &mut Vec, dictionary: Dictionary) -> BrotliStatus { unsafe { let state = BrotliDecoderCreateInstance(None, None, ptr::null_mut()); @@ -131,7 +153,7 @@ pub fn decompress( ); output.set_len(out_len); - if grow && status == BrotliStatus::NeedsMoreOutput { + if status == BrotliStatus::NeedsMoreOutput { output.reserve(24 * 1024); out_ptr = output.as_mut_ptr().add(out_len); out_left = output.capacity() - out_len; @@ -146,3 +168,52 @@ pub fn decompress( } BrotliStatus::Success } + +/// Brotli decompresses a slice, returning the number of bytes written. +pub fn decompress_fixed( + input: &[u8], + output: &mut [u8], + dictionary: Dictionary, +) -> Result { + unsafe { + let state = BrotliDecoderCreateInstance(None, None, ptr::null_mut()); + + macro_rules! require { + ($cond:expr) => { + if !$cond { + BrotliDecoderDestroyInstance(state); + return Err(BrotliStatus::Failure); + } + }; + } + + if dictionary != Dictionary::Empty { + let attatched = BrotliDecoderAttachDictionary( + state, + BrotliSharedDictionaryType::Raw, + dictionary.len(), + dictionary.data(), + ); + require!(attatched == BrotliBool::True); + } + + let mut in_len = input.len(); + let mut in_ptr = input.as_ptr(); + let mut out_left = output.len(); + let mut out_ptr = output.as_mut_ptr(); + let mut out_len = out_left; + + let status = BrotliDecoderDecompressStream( + state, + &mut in_len as _, + &mut in_ptr as _, + &mut out_left as _, + &mut out_ptr as _, + &mut out_len as _, + ); + require!(status == BrotliStatus::Success); + require!(BrotliDecoderIsFinished(state) == BrotliBool::True); + BrotliDecoderDestroyInstance(state); + Ok(out_len) + } +} From 0497d2de2374ca316e66aa502b932b3c3af63c94 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Mon, 18 Mar 2024 12:23:37 -0600 Subject: [PATCH 05/20] cgo brotli --- Makefile | 22 ++----- arbcompress/compress_common.go | 2 +- arbcompress/{compress_cgo.go => native.go} | 6 +- arbcompress/{compress_wasm.go => wasm.go} | 2 +- arbitrator/Cargo.lock | 58 ++++++++++++++----- arbitrator/Cargo.toml | 2 + arbitrator/brotli/Cargo.toml | 3 +- arbitrator/brotli/cbindgen.toml | 10 ---- arbitrator/brotli/src/cgo.rs | 7 ++- arbitrator/caller-env/Cargo.toml | 2 +- arbitrator/caller-env/src/brotli/mod.rs | 31 ++++++---- arbitrator/jit/src/arbcompress.rs | 3 +- arbitrator/prover/src/machine.rs | 12 ++-- arbitrator/stylus/Cargo.toml | 1 + arbitrator/stylus/cbindgen.toml | 4 +- arbitrator/stylus/src/lib.rs | 1 + arbitrator/wasm-libraries/Cargo.lock | 1 + .../wasm-libraries/arbcompress/src/lib.rs | 3 +- 18 files changed, 95 insertions(+), 75 deletions(-) rename arbcompress/{compress_cgo.go => native.go} (90%) rename arbcompress/{compress_wasm.go => wasm.go} (95%) delete mode 100644 arbitrator/brotli/cbindgen.toml diff --git a/Makefile b/Makefile index 2fa68a1cc..fb376ee37 100644 --- a/Makefile +++ b/Makefile @@ -51,8 +51,6 @@ replay_deps=arbos wavmio arbstate arbcompress solgen/go/node-interfacegen blsSig replay_wasm=$(output_latest)/replay.wasm -arb_brotli_lib = $(output_root)/lib/libbrotli.a -brotli_cgo_header = $(output_root)/include/arb_brotli.h arb_brotli_files = $(wildcard arbitrator/brotli/src/*.* arbitrator/brotli/src/*/*.* arbitrator/brotli/*.toml arbitrator/brotli/*.rs) .make/cbrotli-lib arbitrator_generated_header=$(output_root)/include/arbitrator.h @@ -82,10 +80,10 @@ rust_arbutil_files = $(wildcard arbitrator/arbutil/src/*.* arbitrator/arbutil/sr prover_direct_includes = $(patsubst %,$(output_latest)/%.wasm, forward forward_stub) prover_dir = arbitrator/prover/ -rust_prover_files = $(wildcard $(prover_dir)/src/*.* $(prover_dir)/src/*/*.* $(prover_dir)/*.toml $(prover_dir)/*.rs) $(rust_arbutil_files) $(prover_direct_includes) +rust_prover_files = $(wildcard $(prover_dir)/src/*.* $(prover_dir)/src/*/*.* $(prover_dir)/*.toml $(prover_dir)/*.rs) $(rust_arbutil_files) $(prover_direct_includes) $(arb_brotli_files) wasm_lib = arbitrator/wasm-libraries -wasm_lib_deps = $(wildcard $(wasm_lib)/$(1)/*.toml $(wasm_lib)/$(1)/src/*.rs $(wasm_lib)/$(1)/*.rs) $(rust_arbutil_files) .make/machines +wasm_lib_deps = $(wildcard $(wasm_lib)/$(1)/*.toml $(wasm_lib)/$(1)/src/*.rs $(wasm_lib)/$(1)/*.rs) $(rust_arbutil_files) $(arb_brotli_files) .make/machines wasm_lib_go_abi = $(call wasm_lib_deps,go-abi) wasm_lib_forward = $(call wasm_lib_deps,forward) wasm_lib_user_host_trait = $(call wasm_lib_deps,user-host-trait) @@ -161,13 +159,12 @@ build-node-deps: $(go_source) build-prover-header build-prover-lib build-jit .ma test-go-deps: \ build-replay-env \ $(stylus_test_wasms) \ - $(arb_brotli_lib) \ $(arbitrator_stylus_lib) \ $(patsubst %,$(arbitrator_cases)/%.wasm, global-state read-inboxmsg-10 global-state-wrapper const) -build-prover-header: $(arbitrator_generated_header) $(brotli_cgo_header) +build-prover-header: $(arbitrator_generated_header) -build-prover-lib: $(arbitrator_stylus_lib) $(arb_brotli_lib) +build-prover-lib: $(arbitrator_stylus_lib) build-prover-bin: $(prover_bin) @@ -274,11 +271,6 @@ $(prover_bin): $(DEP_PREDICATE) $(rust_prover_files) cargo build --manifest-path arbitrator/Cargo.toml --release --bin prover ${CARGOFLAGS} install arbitrator/target/release/prover $@ -$(arb_brotli_lib): $(DEP_PREDICATE) $(arb_brotli_files) - mkdir -p `dirname $(arb_brotli_lib)` - cargo build --manifest-path arbitrator/Cargo.toml --release --lib -p brotli ${CARGOFLAGS} - install arbitrator/target/release/libbrotli.a $@ - $(arbitrator_stylus_lib): $(DEP_PREDICATE) $(stylus_files) mkdir -p `dirname $(arbitrator_stylus_lib)` cargo build --manifest-path arbitrator/Cargo.toml --release --lib -p stylus ${CARGOFLAGS} @@ -301,12 +293,6 @@ $(arbitrator_generated_header): $(DEP_PREDICATE) $(stylus_files) cd arbitrator/stylus && cbindgen --config cbindgen.toml --crate stylus --output ../../$(arbitrator_generated_header) @touch -c $@ # cargo might decide to not rebuild the header -$(brotli_cgo_header): $(DEP_PREDICATE) $(arb_brotli_files) - @echo creating ${PWD}/$(brotli_cgo_header) - mkdir -p `dirname $(brotli_cgo_header)` - cd arbitrator/brotli && cbindgen --config cbindgen.toml --crate brotli --output ../../$(brotli_cgo_header) - @touch -c $@ # cargo might decide to not rebuild the header - $(output_latest)/wasi_stub.wasm: $(DEP_PREDICATE) $(call wasm_lib_deps,wasi-stub) cargo build --manifest-path arbitrator/wasm-libraries/Cargo.toml --release --target wasm32-unknown-unknown --package wasi-stub install arbitrator/wasm-libraries/$(wasm32_unknown)/wasi_stub.wasm $@ diff --git a/arbcompress/compress_common.go b/arbcompress/compress_common.go index 705acf192..31d46dbfa 100644 --- a/arbcompress/compress_common.go +++ b/arbcompress/compress_common.go @@ -19,5 +19,5 @@ func compressedBufferSizeFor(length int) int { } func CompressFast(input []byte) ([]byte, error) { - return compressLevel(input, EmptyDictionary, LEVEL_FAST) + return compressLevel(input, LEVEL_FAST, EmptyDictionary) } diff --git a/arbcompress/compress_cgo.go b/arbcompress/native.go similarity index 90% rename from arbcompress/compress_cgo.go rename to arbcompress/native.go index 2641cbda4..a305ddd8a 100644 --- a/arbcompress/compress_cgo.go +++ b/arbcompress/native.go @@ -8,7 +8,7 @@ package arbcompress /* #cgo CFLAGS: -g -Wall -I${SRCDIR}/../target/include/ -#cgo LDFLAGS: ${SRCDIR}/../target/lib/libbrotli.a -lm +#cgo LDFLAGS: ${SRCDIR}/../target/lib/libstylus.a -lm #include "arb_brotli.h" */ import "C" @@ -47,10 +47,10 @@ func DecompressWithDictionary(input []byte, maxSize int, dictionary Dictionary) } func CompressWell(input []byte) ([]byte, error) { - return compressLevel(input, EmptyDictionary, LEVEL_WELL) + return compressLevel(input, LEVEL_WELL, EmptyDictionary) } -func compressLevel(input []byte, dictionary Dictionary, level int) ([]byte, error) { +func compressLevel(input []byte, level int, dictionary Dictionary) ([]byte, error) { maxSize := compressedBufferSizeFor(len(input)) output := make([]byte, maxSize) outbuf := sliceToBuffer(output) diff --git a/arbcompress/compress_wasm.go b/arbcompress/wasm.go similarity index 95% rename from arbcompress/compress_wasm.go rename to arbcompress/wasm.go index 91bb8b973..e8deda021 100644 --- a/arbcompress/compress_wasm.go +++ b/arbcompress/wasm.go @@ -46,7 +46,7 @@ func DecompressWithDictionary(input []byte, maxSize int, dictionary Dictionary) return outBuf[:outLen], nil } -func compressLevel(input []byte, level uint32) ([]byte, error) { +func compressLevel(input []byte, level uint32, dictionary Dictionary) ([]byte, error) { maxOutSize := compressedBufferSizeFor(len(input)) outBuf := make([]byte, maxOutSize) outLen := uint32(len(outBuf)) diff --git a/arbitrator/Cargo.lock b/arbitrator/Cargo.lock index 58a35ce12..3ee6abe16 100644 --- a/arbitrator/Cargo.lock +++ b/arbitrator/Cargo.lock @@ -93,7 +93,7 @@ checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" dependencies = [ "addr2line", "cc", - "cfg-if", + "cfg-if 1.0.0", "libc", "miniz_oxide", "object 0.29.0", @@ -149,6 +149,7 @@ version = "0.1.0" dependencies = [ "num_enum", "wasmer", + "wee_alloc", ] [[package]] @@ -208,6 +209,12 @@ version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "cfg-if" version = "1.0.0" @@ -236,7 +243,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9847f90f32a50b0dcbd68bc23ff242798b13080b97b0569f6ed96a45ce4cf2cd" dependencies = [ "autocfg", - "cfg-if", + "cfg-if 1.0.0", "libc", "scopeguard", "windows-sys", @@ -340,7 +347,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crossbeam-utils", ] @@ -350,7 +357,7 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crossbeam-epoch", "crossbeam-utils", ] @@ -361,7 +368,7 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crossbeam-utils", "lazy_static", "memoffset 0.6.4", @@ -374,7 +381,7 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crossbeam-utils", ] @@ -384,7 +391,7 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "lazy_static", ] @@ -469,7 +476,7 @@ version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "hashbrown 0.14.3", "lock_api", "once_cell", @@ -637,7 +644,7 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "wasi", ] @@ -862,7 +869,7 @@ version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -916,6 +923,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + [[package]] name = "minimal-lexical" version = "0.1.3" @@ -1123,7 +1136,7 @@ version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "redox_syscall", "smallvec", @@ -1634,6 +1647,7 @@ version = "0.1.0" dependencies = [ "arbutil", "bincode", + "brotli", "caller-env", "derivative", "eyre", @@ -1764,7 +1778,7 @@ version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -1880,7 +1894,7 @@ version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "wasm-bindgen-macro", ] @@ -1965,7 +1979,7 @@ name = "wasmer" version = "4.2.3" dependencies = [ "bytes", - "cfg-if", + "cfg-if 1.0.0", "derivative", "indexmap 1.9.3", "js-sys", @@ -1993,7 +2007,7 @@ version = "4.2.3" dependencies = [ "backtrace", "bytes", - "cfg-if", + "cfg-if 1.0.0", "enum-iterator", "enumset", "lazy_static", @@ -2098,7 +2112,7 @@ version = "4.2.3" dependencies = [ "backtrace", "cc", - "cfg-if", + "cfg-if 1.0.0", "corosensei", "crossbeam-queue", "dashmap", @@ -2149,6 +2163,18 @@ dependencies = [ "wast", ] +[[package]] +name = "wee_alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "memory_units", + "winapi", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/arbitrator/Cargo.toml b/arbitrator/Cargo.toml index c889aea90..da1c367fb 100644 --- a/arbitrator/Cargo.toml +++ b/arbitrator/Cargo.toml @@ -22,8 +22,10 @@ repository = "https://github.com/OffchainLabs/nitro.git" rust-version = "1.67" [workspace.dependencies] +cfg-if = "1.0.0" num_enum = { version = "0.7.2", default-features = false } wasmparser = "0.95" +wee_alloc = "0.4.2" [profile.release] debug = true diff --git a/arbitrator/brotli/Cargo.toml b/arbitrator/brotli/Cargo.toml index c27e2786b..e2c9e9376 100644 --- a/arbitrator/brotli/Cargo.toml +++ b/arbitrator/brotli/Cargo.toml @@ -11,9 +11,10 @@ rust-version.workspace = true [dependencies] num_enum.workspace = true wasmer = { path = "../tools/wasmer/lib/api", optional = true } +wee_alloc.workspace = true [lib] -crate-type = ["lib", "staticlib"] +crate-type = ["lib"] [features] wasmer_traits = ["dep:wasmer"] diff --git a/arbitrator/brotli/cbindgen.toml b/arbitrator/brotli/cbindgen.toml deleted file mode 100644 index 0c28a7ccb..000000000 --- a/arbitrator/brotli/cbindgen.toml +++ /dev/null @@ -1,10 +0,0 @@ -language = "C" -include_guard = "brotli_bindings" - -[parse] -parse_deps = false - -[enum] -prefix_with_name = true - -[export] diff --git a/arbitrator/brotli/src/cgo.rs b/arbitrator/brotli/src/cgo.rs index 1ff4f421e..71ebab5dc 100644 --- a/arbitrator/brotli/src/cgo.rs +++ b/arbitrator/brotli/src/cgo.rs @@ -2,13 +2,14 @@ // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE use crate::{BrotliStatus, Dictionary, DEFAULT_WINDOW_SIZE}; +use core::slice; #[derive(Clone, Copy)] #[repr(C)] pub struct BrotliBuffer { /// Points to data owned by Go. ptr: *mut u8, - /// The length in bytes. + /// The length in bytes. len: *mut usize, } @@ -18,7 +19,7 @@ impl BrotliBuffer { if len == 0 { return &[]; } - unsafe { std::slice::from_raw_parts(self.ptr, len) } + unsafe { slice::from_raw_parts(self.ptr, len) } } fn as_mut_slice(&mut self) -> &mut [u8] { @@ -26,7 +27,7 @@ impl BrotliBuffer { if len == 0 { return &mut []; } - unsafe { std::slice::from_raw_parts_mut(self.ptr, len) } + unsafe { slice::from_raw_parts_mut(self.ptr, len) } } } diff --git a/arbitrator/caller-env/Cargo.toml b/arbitrator/caller-env/Cargo.toml index 100bd7a82..f763a02d2 100644 --- a/arbitrator/caller-env/Cargo.toml +++ b/arbitrator/caller-env/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition.workspace = true [dependencies] -brotli.path = "../brotli/" +brotli = { path = "../brotli/", default-features = false } num_enum.workspace = true rand_pcg = { version = "0.3.1", default-features = false } rand = { version = "0.8.4", default-features = false } diff --git a/arbitrator/caller-env/src/brotli/mod.rs b/arbitrator/caller-env/src/brotli/mod.rs index fbd995174..0160368f1 100644 --- a/arbitrator/caller-env/src/brotli/mod.rs +++ b/arbitrator/caller-env/src/brotli/mod.rs @@ -20,17 +20,21 @@ pub fn brotli_compress( out_len_ptr: GuestPtr, level: u32, window_size: u32, + dictionary: Dictionary, ) -> BrotliStatus { let input = mem.read_slice(in_buf_ptr, in_buf_len as usize); let mut output = Vec::with_capacity(mem.read_u32(out_len_ptr) as usize); - let status = brotli::compress(&input, &mut output, level, window_size); - if status.is_ok() { - let out_len = output.len(); - mem.write_slice(out_buf_ptr, &output[..out_len]); - mem.write_u32(out_len_ptr, out_len as u32); + let status = brotli::compress_fixed(&input, &mut output, level, window_size, dictionary); + match status { + Ok(written) => unsafe { + output.set_len(written); + mem.write_slice(out_buf_ptr, &output[..written]); + mem.write_u32(out_len_ptr, written as u32); + BrotliStatus::Success + }, + Err(status) => status, } - status } /// Brotli decompresses a go slice using a custom dictionary. @@ -51,11 +55,14 @@ pub fn brotli_decompress( let input = mem.read_slice(in_buf_ptr, in_buf_len as usize); let mut output = Vec::with_capacity(mem.read_u32(out_len_ptr) as usize); - let status = brotli::decompress(&input, &mut output, dictionary, false); - if status.is_ok() { - let out_len = output.len(); - mem.write_slice(out_buf_ptr, &output[..out_len]); - mem.write_u32(out_len_ptr, out_len as u32); + let status = brotli::decompress_fixed(&input, &mut output, dictionary); + match status { + Ok(written) => unsafe { + output.set_len(written); + mem.write_slice(out_buf_ptr, &output[..written]); + mem.write_u32(out_len_ptr, written as u32); + BrotliStatus::Success + }, + Err(status) => status, } - status } diff --git a/arbitrator/jit/src/arbcompress.rs b/arbitrator/jit/src/arbcompress.rs index e5a0de36b..b977b0927 100644 --- a/arbitrator/jit/src/arbcompress.rs +++ b/arbitrator/jit/src/arbcompress.rs @@ -34,6 +34,7 @@ wrap! { out_buf_ptr: GuestPtr, out_len_ptr: GuestPtr, level: u32, - window_size: u32 + window_size: u32, + dictionary: Dictionary ) -> BrotliStatus } diff --git a/arbitrator/prover/src/machine.rs b/arbitrator/prover/src/machine.rs index 2481a6362..412d4c3fa 100644 --- a/arbitrator/prover/src/machine.rs +++ b/arbitrator/prover/src/machine.rs @@ -322,6 +322,7 @@ impl Module { let mut memory = Memory::default(); let mut tables = Vec::new(); let mut host_call_hooks = Vec::new(); + let bin_name = &bin.names.module; for import in &bin.imports { let module = import.module; let have_ty = &bin.types[import.offset as usize]; @@ -357,15 +358,16 @@ impl Module { hostio } else { bail!( - "No such import {} in {}", + "No such import {} in {} for {}", import_name.red(), - import.module.red() + import.module.red(), + bin_name.red() ) }; ensure!( &func.ty == have_ty, "Import {} for {} has different function signature than export.\nexpected {}\nbut have {}", - import_name.red(), bin.names.module.red(), func.ty.red(), have_ty.red(), + import_name.red(), bin_name.red(), func.ty.red(), have_ty.red(), ); func_type_idxs.push(import.offset); @@ -1401,7 +1403,7 @@ impl Machine { use brotli::Dictionary; let compressed = std::fs::read(wavm_binary)?; let mut modules = vec![]; - if !brotli::decompress(&compressed, &mut modules, Dictionary::Empty, true).is_ok() { + if brotli::decompress(&compressed, &mut modules, Dictionary::Empty).is_err() { bail!("failed to decompress wavm binary"); } bincode::deserialize(&modules)? @@ -1465,7 +1467,7 @@ impl Machine { let modules = bincode::serialize(&self.modules)?; let window = brotli::DEFAULT_WINDOW_SIZE; let mut output = Vec::with_capacity(2 * modules.len()); - if !brotli::compress(&modules, &mut output, 9, window).is_ok() { + if brotli::compress(&modules, &mut output, 9, window).is_err() { bail!("failed to compress binary"); } diff --git a/arbitrator/stylus/Cargo.toml b/arbitrator/stylus/Cargo.toml index 01867e307..c957707aa 100644 --- a/arbitrator/stylus/Cargo.toml +++ b/arbitrator/stylus/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] arbutil = { path = "../arbutil/" } +brotli = { path = "../brotli" } caller-env = { path = "../caller-env", features = ["wasmer_traits"] } prover = { path = "../prover/", default-features = false, features = ["native"] } wasmer = { path = "../tools/wasmer/lib/api" } diff --git a/arbitrator/stylus/cbindgen.toml b/arbitrator/stylus/cbindgen.toml index 466972da7..95adfd462 100644 --- a/arbitrator/stylus/cbindgen.toml +++ b/arbitrator/stylus/cbindgen.toml @@ -3,8 +3,8 @@ include_guard = "arbitrator_bindings" [parse] parse_deps = true -include = ["arbutil", "prover"] -extra_bindings = ["arbutil", "prover"] +include = ["arbutil", "prover", "brotli"] +extra_bindings = ["arbutil", "prover", "brotli"] [enum] prefix_with_name = true diff --git a/arbitrator/stylus/src/lib.rs b/arbitrator/stylus/src/lib.rs index 2e681725f..399a5dd36 100644 --- a/arbitrator/stylus/src/lib.rs +++ b/arbitrator/stylus/src/lib.rs @@ -18,6 +18,7 @@ use prover::programs::{prelude::*, StylusData}; use run::RunProgram; use std::{marker::PhantomData, mem, ptr}; +pub use brotli; pub use prover; pub mod env; diff --git a/arbitrator/wasm-libraries/Cargo.lock b/arbitrator/wasm-libraries/Cargo.lock index 48018bc71..25122cfcd 100644 --- a/arbitrator/wasm-libraries/Cargo.lock +++ b/arbitrator/wasm-libraries/Cargo.lock @@ -118,6 +118,7 @@ name = "brotli" version = "0.1.0" dependencies = [ "num_enum", + "wee_alloc", ] [[package]] diff --git a/arbitrator/wasm-libraries/arbcompress/src/lib.rs b/arbitrator/wasm-libraries/arbcompress/src/lib.rs index a65331031..eb834e545 100644 --- a/arbitrator/wasm-libraries/arbcompress/src/lib.rs +++ b/arbitrator/wasm-libraries/arbcompress/src/lib.rs @@ -39,6 +39,7 @@ wrap! { out_buf_ptr: GuestPtr, out_len_ptr: GuestPtr, level: u32, - window_size: u32 + window_size: u32, + dictionary: Dictionary ) -> BrotliStatus } From e8e6f3887e6805ab0d4785ccd403efda1420f0b5 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Mon, 18 Mar 2024 15:19:54 -0600 Subject: [PATCH 06/20] MaybeUninit API --- Makefile | 8 ++- arbcompress/native.go | 2 +- arbcompress/wasm.go | 5 +- arbitrator/brotli/src/cgo.rs | 14 ++--- arbitrator/brotli/src/lib.rs | 40 +++++++++------ arbitrator/brotli/src/types.rs | 2 +- arbitrator/caller-env/Cargo.toml | 6 ++- arbitrator/caller-env/src/brotli/mod.rs | 32 +++++++----- arbitrator/caller-env/src/brotli/types.rs | 51 ------------------- arbitrator/caller-env/src/lib.rs | 2 + arbitrator/caller-env/src/wasip1_stub.rs | 3 +- arbitrator/jit/src/arbcompress.rs | 8 +-- arbitrator/jit/src/caller_env.rs | 2 +- arbitrator/prover/src/machine.rs | 4 +- .../wasm-libraries/arbcompress/src/lib.rs | 8 +-- .../wasm-libraries/wasi-stub/Cargo.toml | 2 +- 16 files changed, 78 insertions(+), 111 deletions(-) delete mode 100644 arbitrator/caller-env/src/brotli/types.rs diff --git a/Makefile b/Makefile index fb376ee37..afa125176 100644 --- a/Makefile +++ b/Makefile @@ -37,7 +37,7 @@ precompiles = $(patsubst %,./solgen/generated/%.go, $(precompile_names)) output_root=target output_latest=$(output_root)/machines/latest -repo_dirs = arbos arbnode arbutil arbstate cmd das precompiles solgen system_tests util validator wavmio +repo_dirs = arbos arbcompress arbnode arbutil arbstate cmd das precompiles solgen system_tests util validator wavmio go_source.go = $(wildcard $(patsubst %,%/*.go, $(repo_dirs)) $(patsubst %,%/*/*.go, $(repo_dirs))) go_source.s = $(wildcard $(patsubst %,%/*.s, $(repo_dirs)) $(patsubst %,%/*/*.s, $(repo_dirs))) go_source = $(go_source.go) $(go_source.s) @@ -47,11 +47,9 @@ color_reset = "\e[0;0m" done = "%bdone!%b\n" $(color_pink) $(color_reset) -replay_deps=arbos wavmio arbstate arbcompress solgen/go/node-interfacegen blsSignatures cmd/replay - replay_wasm=$(output_latest)/replay.wasm -arb_brotli_files = $(wildcard arbitrator/brotli/src/*.* arbitrator/brotli/src/*/*.* arbitrator/brotli/*.toml arbitrator/brotli/*.rs) .make/cbrotli-lib +arb_brotli_files = $(wildcard arbitrator/brotli/src/*.* arbitrator/brotli/src/*/*.* arbitrator/brotli/*.toml arbitrator/brotli/*.rs) .make/cbrotli-lib .make/cbrotli-wasm arbitrator_generated_header=$(output_root)/include/arbitrator.h arbitrator_wasm_libs=$(patsubst %, $(output_root)/machines/latest/%.wasm, forward wasi_stub host_io soft-float arbcompress user_host program_exec) @@ -351,7 +349,7 @@ $(output_latest)/user_test.wasm: $(DEP_PREDICATE) $(call wasm_lib_deps,user-test cargo build --manifest-path arbitrator/wasm-libraries/Cargo.toml --release --target wasm32-wasi --package user-test install arbitrator/wasm-libraries/$(wasm32_wasi)/user_test.wasm $@ -$(output_latest)/arbcompress.wasm: $(DEP_PREDICATE) $(call wasm_lib_deps,brotli) $(wasm_lib_go_abi) .make/cbrotli-wasm +$(output_latest)/arbcompress.wasm: $(DEP_PREDICATE) $(call wasm_lib_deps,brotli) $(wasm_lib_go_abi) cargo build --manifest-path arbitrator/wasm-libraries/Cargo.toml --release --target wasm32-wasi --package arbcompress install arbitrator/wasm-libraries/$(wasm32_wasi)/arbcompress.wasm $@ diff --git a/arbcompress/native.go b/arbcompress/native.go index a305ddd8a..e6fd49262 100644 --- a/arbcompress/native.go +++ b/arbcompress/native.go @@ -9,7 +9,7 @@ package arbcompress /* #cgo CFLAGS: -g -Wall -I${SRCDIR}/../target/include/ #cgo LDFLAGS: ${SRCDIR}/../target/lib/libstylus.a -lm -#include "arb_brotli.h" +#include "arbitrator.h" */ import "C" import "fmt" diff --git a/arbcompress/wasm.go b/arbcompress/wasm.go index e8deda021..844325690 100644 --- a/arbcompress/wasm.go +++ b/arbcompress/wasm.go @@ -21,10 +21,10 @@ const ( ) //go:wasmimport arbcompress brotli_compress -func brotliCompress(inBuf unsafe.Pointer, inBufLen uint32, outBuf unsafe.Pointer, outBufLen unsafe.Pointer, level, windowSize uint32) brotliStatus +func brotliCompress(inBuf unsafe.Pointer, inLen uint32, outBuf unsafe.Pointer, outLen unsafe.Pointer, level, windowSize uint32, dictionary Dictionary) brotliStatus //go:wasmimport arbcompress brotli_decompress -func brotliDecompress(inBuf unsafe.Pointer, inBufLen uint32, outBuf unsafe.Pointer, outBufLen unsafe.Pointer, dictionary Dictionary) brotliStatus +func brotliDecompress(inBuf unsafe.Pointer, inLen uint32, outBuf unsafe.Pointer, outLen unsafe.Pointer, dictionary Dictionary) brotliStatus func Decompress(input []byte, maxSize int) ([]byte, error) { return DecompressWithDictionary(input, maxSize, EmptyDictionary) @@ -55,6 +55,7 @@ func compressLevel(input []byte, level uint32, dictionary Dictionary) ([]byte, e arbutil.SliceToUnsafePointer(outBuf), unsafe.Pointer(&outLen), level, WINDOW_SIZE, + dictionary, ) if status != brotliSuccess { return nil, fmt.Errorf("failed compression") diff --git a/arbitrator/brotli/src/cgo.rs b/arbitrator/brotli/src/cgo.rs index 71ebab5dc..a12e162d8 100644 --- a/arbitrator/brotli/src/cgo.rs +++ b/arbitrator/brotli/src/cgo.rs @@ -2,7 +2,7 @@ // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE use crate::{BrotliStatus, Dictionary, DEFAULT_WINDOW_SIZE}; -use core::slice; +use core::{mem::MaybeUninit, slice}; #[derive(Clone, Copy)] #[repr(C)] @@ -22,12 +22,12 @@ impl BrotliBuffer { unsafe { slice::from_raw_parts(self.ptr, len) } } - fn as_mut_slice(&mut self) -> &mut [u8] { + fn as_uninit(&mut self) -> &mut [MaybeUninit] { let len = unsafe { *self.len }; if len == 0 { return &mut []; } - unsafe { slice::from_raw_parts_mut(self.ptr, len) } + unsafe { slice::from_raw_parts_mut(self.ptr as _, len) } } } @@ -39,9 +39,9 @@ pub extern "C" fn brotli_compress( level: u32, ) -> BrotliStatus { let window = DEFAULT_WINDOW_SIZE; - let buffer = output.as_mut_slice(); + let buffer = output.as_uninit(); match crate::compress_fixed(input.as_slice(), buffer, level, window, dictionary) { - Ok(written) => unsafe { *output.len = written }, + Ok(slice) => unsafe { *output.len = slice.len() }, Err(status) => return status, } BrotliStatus::Success @@ -53,8 +53,8 @@ pub extern "C" fn brotli_decompress( mut output: BrotliBuffer, dictionary: Dictionary, ) -> BrotliStatus { - match crate::decompress_fixed(input.as_slice(), output.as_mut_slice(), dictionary) { - Ok(written) => unsafe { *output.len = written }, + match crate::decompress_fixed(input.as_slice(), output.as_uninit(), dictionary) { + Ok(slice) => unsafe { *output.len = slice.len() }, Err(status) => return status, } BrotliStatus::Success diff --git a/arbitrator/brotli/src/lib.rs b/arbitrator/brotli/src/lib.rs index 114b04003..5e63af736 100644 --- a/arbitrator/brotli/src/lib.rs +++ b/arbitrator/brotli/src/lib.rs @@ -6,7 +6,11 @@ extern crate alloc; use alloc::vec::Vec; -use core::{ffi::c_void, ptr}; +use core::{ + ffi::c_void, + mem::{self, MaybeUninit}, + ptr, +}; pub mod cgo; mod types; @@ -87,14 +91,14 @@ pub fn compress(input: &[u8], output: &mut Vec, level: u32, window_size: u32 /// Brotli compresses a slice. /// The output buffer must be sufficiently large. -pub fn compress_fixed( - input: &[u8], - output: &mut [u8], +pub fn compress_fixed<'a>( + input: &'a [u8], + output: &'a mut [MaybeUninit], level: u32, window_size: u32, dictionary: Dictionary, -) -> Result { - let mut output_len = output.len(); +) -> Result<&'a [u8], BrotliStatus> { + let mut out_len = output.len(); unsafe { let res = BrotliEncoderCompress( level, @@ -102,14 +106,17 @@ pub fn compress_fixed( BROTLI_MODE_GENERIC, input.len(), input.as_ptr(), - &mut output_len, - output.as_mut_ptr(), + &mut out_len, + output.as_mut_ptr() as *mut u8, ); if res != BrotliStatus::Success { return Err(BrotliStatus::Failure); } - Ok(output_len) } + + // SAFETY: brotli initialized this span of bytes + let output = unsafe { mem::transmute(&output[..out_len]) }; + Ok(output) } /// Brotli decompresses a slice into a vec, growing as needed. @@ -170,11 +177,11 @@ pub fn decompress(input: &[u8], output: &mut Vec, dictionary: Dictionary) -> } /// Brotli decompresses a slice, returning the number of bytes written. -pub fn decompress_fixed( - input: &[u8], - output: &mut [u8], +pub fn decompress_fixed<'a>( + input: &'a [u8], + output: &'a mut [MaybeUninit], dictionary: Dictionary, -) -> Result { +) -> Result<&'a [u8], BrotliStatus> { unsafe { let state = BrotliDecoderCreateInstance(None, None, ptr::null_mut()); @@ -200,7 +207,7 @@ pub fn decompress_fixed( let mut in_len = input.len(); let mut in_ptr = input.as_ptr(); let mut out_left = output.len(); - let mut out_ptr = output.as_mut_ptr(); + let mut out_ptr = output.as_mut_ptr() as *mut u8; let mut out_len = out_left; let status = BrotliDecoderDecompressStream( @@ -214,6 +221,9 @@ pub fn decompress_fixed( require!(status == BrotliStatus::Success); require!(BrotliDecoderIsFinished(state) == BrotliBool::True); BrotliDecoderDestroyInstance(state); - Ok(out_len) + + // SAFETY: brotli initialized this span of bytes + let output = mem::transmute(&output[..out_len]); + Ok(output) } } diff --git a/arbitrator/brotli/src/types.rs b/arbitrator/brotli/src/types.rs index 8ef78bff2..48697a54a 100644 --- a/arbitrator/brotli/src/types.rs +++ b/arbitrator/brotli/src/types.rs @@ -8,7 +8,7 @@ use num_enum::{IntoPrimitive, TryFromPrimitive}; pub const BROTLI_MODE_GENERIC: u32 = 0; pub const DEFAULT_WINDOW_SIZE: u32 = 22; -#[derive(PartialEq, IntoPrimitive, TryFromPrimitive)] +#[derive(Debug, PartialEq, IntoPrimitive, TryFromPrimitive)] #[repr(u32)] pub enum BrotliStatus { Failure, diff --git a/arbitrator/caller-env/Cargo.toml b/arbitrator/caller-env/Cargo.toml index f763a02d2..ad4d07cca 100644 --- a/arbitrator/caller-env/Cargo.toml +++ b/arbitrator/caller-env/Cargo.toml @@ -4,12 +4,14 @@ version = "0.1.0" edition.workspace = true [dependencies] -brotli = { path = "../brotli/", default-features = false } +brotli = { path = "../brotli/", optional = true } num_enum.workspace = true rand_pcg = { version = "0.3.1", default-features = false } rand = { version = "0.8.4", default-features = false } wasmer = { path = "../tools/wasmer/lib/api", optional = true } [features] +default = ["brotli"] +brotli = ["dep:brotli"] static_caller = [] -wasmer_traits = ["dep:wasmer", "brotli/wasmer_traits"] +wasmer_traits = ["dep:wasmer", "brotli?/wasmer_traits"] diff --git a/arbitrator/caller-env/src/brotli/mod.rs b/arbitrator/caller-env/src/brotli/mod.rs index 0160368f1..2ba8c6e6f 100644 --- a/arbitrator/caller-env/src/brotli/mod.rs +++ b/arbitrator/caller-env/src/brotli/mod.rs @@ -25,14 +25,19 @@ pub fn brotli_compress( let input = mem.read_slice(in_buf_ptr, in_buf_len as usize); let mut output = Vec::with_capacity(mem.read_u32(out_len_ptr) as usize); - let status = brotli::compress_fixed(&input, &mut output, level, window_size, dictionary); - match status { - Ok(written) => unsafe { - output.set_len(written); - mem.write_slice(out_buf_ptr, &output[..written]); - mem.write_u32(out_len_ptr, written as u32); + let result = brotli::compress_fixed( + &input, + output.spare_capacity_mut(), + level, + window_size, + dictionary, + ); + match result { + Ok(slice) => { + mem.write_slice(out_buf_ptr, slice); + mem.write_u32(out_len_ptr, slice.len() as u32); BrotliStatus::Success - }, + } Err(status) => status, } } @@ -55,14 +60,13 @@ pub fn brotli_decompress( let input = mem.read_slice(in_buf_ptr, in_buf_len as usize); let mut output = Vec::with_capacity(mem.read_u32(out_len_ptr) as usize); - let status = brotli::decompress_fixed(&input, &mut output, dictionary); - match status { - Ok(written) => unsafe { - output.set_len(written); - mem.write_slice(out_buf_ptr, &output[..written]); - mem.write_u32(out_len_ptr, written as u32); + let result = brotli::decompress_fixed(&input, output.spare_capacity_mut(), dictionary); + match result { + Ok(slice) => { + mem.write_slice(out_buf_ptr, slice); + mem.write_u32(out_len_ptr, slice.len() as u32); BrotliStatus::Success - }, + } Err(status) => status, } } diff --git a/arbitrator/caller-env/src/brotli/types.rs b/arbitrator/caller-env/src/brotli/types.rs deleted file mode 100644 index 7af2e8b87..000000000 --- a/arbitrator/caller-env/src/brotli/types.rs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2021-2024, Offchain Labs, Inc. -// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE - -#![allow(dead_code, clippy::len_without_is_empty)] - -use num_enum::{IntoPrimitive, TryFromPrimitive}; - -#[derive(PartialEq, IntoPrimitive, TryFromPrimitive)] -#[repr(u32)] -pub enum BrotliStatus { - Failure, - Success, -} - -#[derive(PartialEq)] -#[repr(usize)] -pub(super) enum BrotliBool { - False, - True, -} - -#[repr(C)] -pub(super) enum BrotliSharedDictionaryType { - /// LZ77 prefix dictionary - Raw, - /// Serialized dictionary - Serialized, -} - -#[derive(PartialEq, IntoPrimitive, TryFromPrimitive)] -#[repr(u32)] -pub enum Dictionary { - Empty, - StylusProgram, -} - -impl Dictionary { - pub fn len(&self) -> usize { - match self { - Self::Empty => 0, - Self::StylusProgram => todo!(), - } - } - - pub fn data(&self) -> *const u8 { - match self { - Self::Empty => [].as_ptr(), - Self::StylusProgram => todo!(), - } - } -} diff --git a/arbitrator/caller-env/src/lib.rs b/arbitrator/caller-env/src/lib.rs index baff05be0..ba3874919 100644 --- a/arbitrator/caller-env/src/lib.rs +++ b/arbitrator/caller-env/src/lib.rs @@ -17,7 +17,9 @@ pub mod static_caller; #[cfg(feature = "wasmer_traits")] pub mod wasmer_traits; +#[cfg(feature = "brotli")] pub mod brotli; + mod guest_ptr; pub mod wasip1_stub; diff --git a/arbitrator/caller-env/src/wasip1_stub.rs b/arbitrator/caller-env/src/wasip1_stub.rs index 75fc03e73..2f07cd7e5 100644 --- a/arbitrator/caller-env/src/wasip1_stub.rs +++ b/arbitrator/caller-env/src/wasip1_stub.rs @@ -83,7 +83,8 @@ pub fn fd_write( for i in 0..iovecs_len { let ptr = iovecs_ptr + i * 8; let len = mem.read_u32(ptr + 4); - let data = mem.read_slice(ptr, len as usize); + let ptr = mem.read_u32(ptr); // TODO: string might be split across utf-8 character boundary + let data = mem.read_slice(GuestPtr(ptr), len as usize); env.print_string(&data); size += len; } diff --git a/arbitrator/jit/src/arbcompress.rs b/arbitrator/jit/src/arbcompress.rs index b977b0927..0d8d14bc7 100644 --- a/arbitrator/jit/src/arbcompress.rs +++ b/arbitrator/jit/src/arbcompress.rs @@ -20,21 +20,21 @@ macro_rules! wrap { } wrap! { - fn brotli_decompress( + fn brotli_compress( in_buf_ptr: GuestPtr, in_buf_len: u32, out_buf_ptr: GuestPtr, out_len_ptr: GuestPtr, + level: u32, + window_size: u32, dictionary: Dictionary ) -> BrotliStatus; - fn brotli_compress( + fn brotli_decompress( in_buf_ptr: GuestPtr, in_buf_len: u32, out_buf_ptr: GuestPtr, out_len_ptr: GuestPtr, - level: u32, - window_size: u32, dictionary: Dictionary ) -> BrotliStatus } diff --git a/arbitrator/jit/src/caller_env.rs b/arbitrator/jit/src/caller_env.rs index ac1b8d70d..f4fbff10a 100644 --- a/arbitrator/jit/src/caller_env.rs +++ b/arbitrator/jit/src/caller_env.rs @@ -130,7 +130,7 @@ impl ExecEnv for JitExecEnv<'_> { fn print_string(&mut self, bytes: &[u8]) { match String::from_utf8(bytes.to_vec()) { - Ok(s) => eprintln!("JIT: WASM says: {s}"), + Ok(s) => eprintln!("JIT: WASM says: {s}"), // TODO: this adds too many newlines since go calls this in chunks Err(e) => { let bytes = e.as_bytes(); eprintln!("Go string {} is not valid utf8: {e:?}", hex::encode(bytes)); diff --git a/arbitrator/prover/src/machine.rs b/arbitrator/prover/src/machine.rs index 412d4c3fa..056432ec9 100644 --- a/arbitrator/prover/src/machine.rs +++ b/arbitrator/prover/src/machine.rs @@ -366,8 +366,8 @@ impl Module { }; ensure!( &func.ty == have_ty, - "Import {} for {} has different function signature than export.\nexpected {}\nbut have {}", - import_name.red(), bin_name.red(), func.ty.red(), have_ty.red(), + "Import {} for {} has different function signature than export.\nexpected {} in {}\nbut have {}", + import_name.red(), bin_name.red(), func.ty.red(), module.red(), have_ty.red(), ); func_type_idxs.push(import.offset); diff --git a/arbitrator/wasm-libraries/arbcompress/src/lib.rs b/arbitrator/wasm-libraries/arbcompress/src/lib.rs index eb834e545..fe54e667d 100644 --- a/arbitrator/wasm-libraries/arbcompress/src/lib.rs +++ b/arbitrator/wasm-libraries/arbcompress/src/lib.rs @@ -25,21 +25,21 @@ macro_rules! wrap { } wrap! { - fn brotli_decompress( + fn brotli_compress( in_buf_ptr: GuestPtr, in_buf_len: u32, out_buf_ptr: GuestPtr, out_len_ptr: GuestPtr, + level: u32, + window_size: u32, dictionary: Dictionary ) -> BrotliStatus; - fn brotli_compress( + fn brotli_decompress( in_buf_ptr: GuestPtr, in_buf_len: u32, out_buf_ptr: GuestPtr, out_len_ptr: GuestPtr, - level: u32, - window_size: u32, dictionary: Dictionary ) -> BrotliStatus } diff --git a/arbitrator/wasm-libraries/wasi-stub/Cargo.toml b/arbitrator/wasm-libraries/wasi-stub/Cargo.toml index f6079ce2f..698c1e0f2 100644 --- a/arbitrator/wasm-libraries/wasi-stub/Cargo.toml +++ b/arbitrator/wasm-libraries/wasi-stub/Cargo.toml @@ -9,5 +9,5 @@ crate-type = ["cdylib"] [dependencies] paste = { version = "1.0.14" } -caller-env = { path = "../../caller-env/", features = ["static_caller"] } +caller-env = { path = "../../caller-env/", default-features = false, features = ["static_caller"] } wee_alloc = "0.4.2" From faa52f78f424863f8be3a7e629ee8d05feb5a150 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Tue, 19 Mar 2024 02:09:48 -0600 Subject: [PATCH 07/20] full brotli api with stylus adoption --- arbcompress/compress_common.go | 2 +- arbcompress/native.go | 24 +- arbcompress/wasm.go | 34 +-- arbitrator/Cargo.lock | 1 + arbitrator/Cargo.toml | 1 + arbitrator/brotli/Cargo.toml | 1 + arbitrator/brotli/src/cgo.rs | 5 + arbitrator/brotli/src/dicts/mod.rs | 77 ++++++ .../brotli/src/dicts/stylus-program-11.lz | Bin 0 -> 112640 bytes arbitrator/brotli/src/lib.rs | 249 +++++++++++++----- arbitrator/brotli/src/types.rs | 86 ++++-- arbitrator/brotli/src/wasmer_traits.rs | 2 +- arbitrator/prover/Cargo.toml | 2 +- arbitrator/prover/dict | Bin 0 -> 429 bytes arbitrator/prover/no-dict | Bin 0 -> 483 bytes arbitrator/prover/src/machine.rs | 12 +- arbitrator/prover/src/test.rs | 25 +- arbitrator/wasm-libraries/Cargo.lock | 1 + arbos/programs/programs.go | 2 +- system_tests/program_test.go | 2 +- 20 files changed, 375 insertions(+), 151 deletions(-) create mode 100644 arbitrator/brotli/src/dicts/mod.rs create mode 100644 arbitrator/brotli/src/dicts/stylus-program-11.lz create mode 100644 arbitrator/prover/dict create mode 100644 arbitrator/prover/no-dict diff --git a/arbcompress/compress_common.go b/arbcompress/compress_common.go index 31d46dbfa..6c9492dec 100644 --- a/arbcompress/compress_common.go +++ b/arbcompress/compress_common.go @@ -19,5 +19,5 @@ func compressedBufferSizeFor(length int) int { } func CompressFast(input []byte) ([]byte, error) { - return compressLevel(input, LEVEL_FAST, EmptyDictionary) + return Compress(input, LEVEL_FAST, EmptyDictionary) } diff --git a/arbcompress/native.go b/arbcompress/native.go index e6fd49262..c81601932 100644 --- a/arbcompress/native.go +++ b/arbcompress/native.go @@ -26,40 +26,40 @@ const ( brotliTrue ) -func Decompress(input []byte, maxSize int) ([]byte, error) { - return DecompressWithDictionary(input, maxSize, EmptyDictionary) +func CompressWell(input []byte) ([]byte, error) { + return Compress(input, LEVEL_WELL, EmptyDictionary) } -func DecompressWithDictionary(input []byte, maxSize int, dictionary Dictionary) ([]byte, error) { +func Compress(input []byte, level int, dictionary Dictionary) ([]byte, error) { + maxSize := compressedBufferSizeFor(len(input)) output := make([]byte, maxSize) outbuf := sliceToBuffer(output) inbuf := sliceToBuffer(input) - status := C.brotli_decompress(inbuf, outbuf, C.Dictionary(dictionary)) + status := C.brotli_compress(inbuf, outbuf, C.Dictionary(dictionary), u32(level)) if status != C.BrotliStatus_Success { return nil, fmt.Errorf("failed decompression: %d", status) } - if *outbuf.len > usize(maxSize) { - return nil, fmt.Errorf("failed decompression: result too large: %d", *outbuf.len) - } output = output[:*outbuf.len] return output, nil } -func CompressWell(input []byte) ([]byte, error) { - return compressLevel(input, LEVEL_WELL, EmptyDictionary) +func Decompress(input []byte, maxSize int) ([]byte, error) { + return DecompressWithDictionary(input, maxSize, EmptyDictionary) } -func compressLevel(input []byte, level int, dictionary Dictionary) ([]byte, error) { - maxSize := compressedBufferSizeFor(len(input)) +func DecompressWithDictionary(input []byte, maxSize int, dictionary Dictionary) ([]byte, error) { output := make([]byte, maxSize) outbuf := sliceToBuffer(output) inbuf := sliceToBuffer(input) - status := C.brotli_compress(inbuf, outbuf, C.Dictionary(dictionary), u32(level)) + status := C.brotli_decompress(inbuf, outbuf, C.Dictionary(dictionary)) if status != C.BrotliStatus_Success { return nil, fmt.Errorf("failed decompression: %d", status) } + if *outbuf.len > usize(maxSize) { + return nil, fmt.Errorf("failed decompression: result too large: %d", *outbuf.len) + } output = output[:*outbuf.len] return output, nil } diff --git a/arbcompress/wasm.go b/arbcompress/wasm.go index 844325690..e3ba17baf 100644 --- a/arbcompress/wasm.go +++ b/arbcompress/wasm.go @@ -26,6 +26,23 @@ func brotliCompress(inBuf unsafe.Pointer, inLen uint32, outBuf unsafe.Pointer, o //go:wasmimport arbcompress brotli_decompress func brotliDecompress(inBuf unsafe.Pointer, inLen uint32, outBuf unsafe.Pointer, outLen unsafe.Pointer, dictionary Dictionary) brotliStatus +func Compress(input []byte, level uint32, dictionary Dictionary) ([]byte, error) { + maxOutSize := compressedBufferSizeFor(len(input)) + outBuf := make([]byte, maxOutSize) + outLen := uint32(len(outBuf)) + status := brotliCompress( + arbutil.SliceToUnsafePointer(input), uint32(len(input)), + arbutil.SliceToUnsafePointer(outBuf), unsafe.Pointer(&outLen), + level, + WINDOW_SIZE, + dictionary, + ) + if status != brotliSuccess { + return nil, fmt.Errorf("failed compression") + } + return outBuf[:outLen], nil +} + func Decompress(input []byte, maxSize int) ([]byte, error) { return DecompressWithDictionary(input, maxSize, EmptyDictionary) } @@ -45,20 +62,3 @@ func DecompressWithDictionary(input []byte, maxSize int, dictionary Dictionary) } return outBuf[:outLen], nil } - -func compressLevel(input []byte, level uint32, dictionary Dictionary) ([]byte, error) { - maxOutSize := compressedBufferSizeFor(len(input)) - outBuf := make([]byte, maxOutSize) - outLen := uint32(len(outBuf)) - status := brotliCompress( - arbutil.SliceToUnsafePointer(input), uint32(len(input)), - arbutil.SliceToUnsafePointer(outBuf), unsafe.Pointer(&outLen), - level, - WINDOW_SIZE, - dictionary, - ) - if status != brotliSuccess { - return nil, fmt.Errorf("failed compression") - } - return outBuf[:outLen], nil -} diff --git a/arbitrator/Cargo.lock b/arbitrator/Cargo.lock index 3ee6abe16..a296ba111 100644 --- a/arbitrator/Cargo.lock +++ b/arbitrator/Cargo.lock @@ -147,6 +147,7 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" name = "brotli" version = "0.1.0" dependencies = [ + "lazy_static", "num_enum", "wasmer", "wee_alloc", diff --git a/arbitrator/Cargo.toml b/arbitrator/Cargo.toml index da1c367fb..09268377e 100644 --- a/arbitrator/Cargo.toml +++ b/arbitrator/Cargo.toml @@ -23,6 +23,7 @@ rust-version = "1.67" [workspace.dependencies] cfg-if = "1.0.0" +lazy_static = "1.4.0" num_enum = { version = "0.7.2", default-features = false } wasmparser = "0.95" wee_alloc = "0.4.2" diff --git a/arbitrator/brotli/Cargo.toml b/arbitrator/brotli/Cargo.toml index e2c9e9376..7dba0ffdd 100644 --- a/arbitrator/brotli/Cargo.toml +++ b/arbitrator/brotli/Cargo.toml @@ -9,6 +9,7 @@ repository.workspace = true rust-version.workspace = true [dependencies] +lazy_static.workspace = true num_enum.workspace = true wasmer = { path = "../tools/wasmer/lib/api", optional = true } wee_alloc.workspace = true diff --git a/arbitrator/brotli/src/cgo.rs b/arbitrator/brotli/src/cgo.rs index a12e162d8..220ebdca0 100644 --- a/arbitrator/brotli/src/cgo.rs +++ b/arbitrator/brotli/src/cgo.rs @@ -4,6 +4,7 @@ use crate::{BrotliStatus, Dictionary, DEFAULT_WINDOW_SIZE}; use core::{mem::MaybeUninit, slice}; +/// Mechanism for passing data between Go and Rust where Rust can specify the initialized length. #[derive(Clone, Copy)] #[repr(C)] pub struct BrotliBuffer { @@ -14,6 +15,7 @@ pub struct BrotliBuffer { } impl BrotliBuffer { + /// Interprets the underlying Go data as a Rust slice. fn as_slice(&self) -> &[u8] { let len = unsafe { *self.len }; if len == 0 { @@ -22,6 +24,7 @@ impl BrotliBuffer { unsafe { slice::from_raw_parts(self.ptr, len) } } + /// Interprets the underlying Go data as a Rust slice of uninitialized data. fn as_uninit(&mut self) -> &mut [MaybeUninit] { let len = unsafe { *self.len }; if len == 0 { @@ -31,6 +34,7 @@ impl BrotliBuffer { } } +/// Brotli compresses the given Go data into a buffer of limited capacity. #[no_mangle] pub extern "C" fn brotli_compress( input: BrotliBuffer, @@ -47,6 +51,7 @@ pub extern "C" fn brotli_compress( BrotliStatus::Success } +/// Brotli decompresses the given Go data into a buffer of limited capacity. #[no_mangle] pub extern "C" fn brotli_decompress( input: BrotliBuffer, diff --git a/arbitrator/brotli/src/dicts/mod.rs b/arbitrator/brotli/src/dicts/mod.rs new file mode 100644 index 000000000..f43f71b88 --- /dev/null +++ b/arbitrator/brotli/src/dicts/mod.rs @@ -0,0 +1,77 @@ +// Copyright 2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use crate::{ + types::BrotliSharedDictionaryType, CustomAllocator, EncoderPreparedDictionary, HeapItem, +}; +use core::{ffi::c_int, ptr}; +use lazy_static::lazy_static; +use num_enum::{IntoPrimitive, TryFromPrimitive}; + +extern "C" { + /// Prepares an LZ77 dictionary for use during compression. + fn BrotliEncoderPrepareDictionary( + dict_type: BrotliSharedDictionaryType, + dict_len: c_int, + dictionary: *const u8, + quality: c_int, + alloc: Option *mut HeapItem>, + free: Option, + opaque: *mut CustomAllocator, + ) -> *mut EncoderPreparedDictionary; + + /// Nonzero when valid. + fn BrotliEncoderGetPreparedDictionarySize( + dictionary: *const EncoderPreparedDictionary, + ) -> usize; +} + +/// Forces a type to implement [`Sync`]. +struct ForceSync(T); + +unsafe impl Sync for ForceSync {} + +lazy_static! { + /// Memoizes dictionary preperation. + static ref STYLUS_PROGRAM_DICT: ForceSync<*const EncoderPreparedDictionary> = + ForceSync(unsafe { + let data = Dictionary::StylusProgram.slice().unwrap(); + let dict = BrotliEncoderPrepareDictionary( + BrotliSharedDictionaryType::Raw, + data.len() as c_int, + data.as_ptr(), + 11, + None, + None, + ptr::null_mut(), + ); + assert!(BrotliEncoderGetPreparedDictionarySize(dict) > 0); // check integrity + dict as _ + }); +} + +/// Brotli dictionary selection. +#[derive(Clone, Copy, Debug, PartialEq, IntoPrimitive, TryFromPrimitive)] +#[repr(u32)] +pub enum Dictionary { + Empty, + StylusProgram, +} + +impl Dictionary { + /// Gets the raw bytes of the underlying LZ77 dictionary. + pub fn slice(&self) -> Option<&[u8]> { + match self { + Self::StylusProgram => Some(include_bytes!("stylus-program-11.lz")), + _ => None, + } + } + + /// Returns a pointer to a compression-ready instance of the given dictionary. + pub fn ptr(&self, level: u32) -> Option<*const EncoderPreparedDictionary> { + match self { + Self::StylusProgram if level == 11 => Some(STYLUS_PROGRAM_DICT.0), + _ => None, + } + } +} diff --git a/arbitrator/brotli/src/dicts/stylus-program-11.lz b/arbitrator/brotli/src/dicts/stylus-program-11.lz new file mode 100644 index 0000000000000000000000000000000000000000..073a29abf767f77b30301f25762981ba01f7f8d2 GIT binary patch literal 112640 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 BrotliStatus; + fn BrotliEncoderCreateInstance( + alloc: Option *mut HeapItem>, + free: Option, + opaque: *mut CustomAllocator, + ) -> *mut EncoderState; + + /// Quality must be at least 2 for this bound to be correct. + fn BrotliEncoderMaxCompressedSize(input_size: usize) -> usize; + + fn BrotliEncoderSetParameter( + state: *mut EncoderState, + param: BrotliEncoderParameter, + value: u32, + ) -> BrotliBool; + + fn BrotliEncoderAttachPreparedDictionary( + state: *mut EncoderState, + dictionary: *const EncoderPreparedDictionary, + ) -> BrotliBool; + + fn BrotliEncoderCompressStream( + state: *mut EncoderState, + op: BrotliEncoderOperation, + input_len: *mut usize, + input_ptr: *mut *const u8, + out_left: *mut usize, + out_ptr: *mut *mut u8, + out_len: *mut usize, + ) -> BrotliBool; + + fn BrotliEncoderIsFinished(state: *mut EncoderState) -> BrotliBool; + + fn BrotliEncoderDestroyInstance(state: *mut EncoderState); } -// custom dictionary API +// decompression API extern "C" { fn BrotliDecoderCreateInstance( alloc: Option *mut HeapItem>, @@ -48,8 +76,8 @@ extern "C" { fn BrotliDecoderAttachDictionary( state: *mut DecoderState, - dict_type: BrotliSharedDictionaryType, - dict_len: usize, + kind: BrotliSharedDictionaryType, + len: usize, dictionary: *const u8, ) -> BrotliBool; @@ -67,30 +95,77 @@ extern "C" { fn BrotliDecoderDestroyInstance(state: *mut DecoderState); } -/// Brotli compresses a slice into a vec, growing as needed. -/// The output buffer must be sufficiently large. -pub fn compress(input: &[u8], output: &mut Vec, level: u32, window_size: u32) -> BrotliStatus { - let mut output_len = output.capacity(); +/// Determines the maximum size a brotli compression could be. +/// Note: assumes the user never calls "flush" except during "finish" at the end. +pub fn compression_bound(len: usize, level: u32) -> usize { + let mut bound = unsafe { BrotliEncoderMaxCompressedSize(len) }; + if level <= 2 { + bound = bound.max(len + (len >> 10) * 8 + 64); + } + bound +} + +/// Brotli compresses a slice into a vec. +pub fn compress( + input: &[u8], + level: u32, + window_size: u32, + dictionary: Dictionary, +) -> Result, BrotliStatus> { + let max_size = compression_bound(input.len(), level); + let mut output = Vec::with_capacity(max_size); unsafe { - let res = BrotliEncoderCompress( - level, - window_size, - BROTLI_MODE_GENERIC, - input.len(), - input.as_ptr(), - &mut output_len, - output.as_mut_ptr(), - ); - if res != BrotliStatus::Success { - return BrotliStatus::Failure; + let state = BrotliEncoderCreateInstance(None, None, ptr::null_mut()); + + macro_rules! check { + ($ret:expr) => { + if $ret.is_err() { + BrotliEncoderDestroyInstance(state); + return Err(BrotliStatus::Failure); + } + }; } - output.set_len(output_len); + + check!(BrotliEncoderSetParameter( + state, + BrotliEncoderParameter::Quality, + level + )); + check!(BrotliEncoderSetParameter( + state, + BrotliEncoderParameter::WindowSize, + window_size + )); + + if let Some(dict) = dictionary.ptr(level) { + check!(BrotliEncoderAttachPreparedDictionary(state, dict)); + } + + let mut in_len = input.len(); + let mut in_ptr = input.as_ptr(); + let mut out_left = output.capacity(); + let mut out_ptr = output.as_mut_ptr(); + let mut out_len = out_left; + + let status = BrotliEncoderCompressStream( + state, + BrotliEncoderOperation::Finish, + &mut in_len as _, + &mut in_ptr as _, + &mut out_left as _, + &mut out_ptr as _, + &mut out_len as _, + ); + check!(status); + check!(BrotliEncoderIsFinished(state)); + BrotliEncoderDestroyInstance(state); + + output.set_len(out_len); + Ok(output) } - BrotliStatus::Success } -/// Brotli compresses a slice. -/// The output buffer must be sufficiently large. +/// Brotli compresses a slice into a buffer of limited capacity. pub fn compress_fixed<'a>( input: &'a [u8], output: &'a mut [MaybeUninit], @@ -98,49 +173,83 @@ pub fn compress_fixed<'a>( window_size: u32, dictionary: Dictionary, ) -> Result<&'a [u8], BrotliStatus> { - let mut out_len = output.len(); unsafe { - let res = BrotliEncoderCompress( - level, - window_size, - BROTLI_MODE_GENERIC, - input.len(), - input.as_ptr(), - &mut out_len, - output.as_mut_ptr() as *mut u8, - ); - if res != BrotliStatus::Success { - return Err(BrotliStatus::Failure); + let state = BrotliEncoderCreateInstance(None, None, ptr::null_mut()); + + macro_rules! check { + ($ret:expr) => { + if $ret.is_err() { + BrotliEncoderDestroyInstance(state); + return Err(BrotliStatus::Failure); + } + }; + } + + check!(BrotliEncoderSetParameter( + state, + BrotliEncoderParameter::Quality, + level + )); + check!(BrotliEncoderSetParameter( + state, + BrotliEncoderParameter::WindowSize, + window_size + )); + + if let Some(dict) = dictionary.ptr(level) { + check!(BrotliEncoderAttachPreparedDictionary(state, dict)); } - } - // SAFETY: brotli initialized this span of bytes - let output = unsafe { mem::transmute(&output[..out_len]) }; - Ok(output) + let mut in_len = input.len(); + let mut in_ptr = input.as_ptr(); + let mut out_left = output.len(); + let mut out_ptr = output.as_mut_ptr() as *mut u8; + let mut out_len = out_left; + + let status = BrotliEncoderCompressStream( + state, + BrotliEncoderOperation::Finish, + &mut in_len as _, + &mut in_ptr as _, + &mut out_left as _, + &mut out_ptr as _, + &mut out_len as _, + ); + check!(status); + check!(BrotliEncoderIsFinished(state)); + BrotliEncoderDestroyInstance(state); + + // SAFETY: brotli initialized this span of bytes + let output = mem::transmute(&output[..out_len]); + Ok(output) + } } -/// Brotli decompresses a slice into a vec, growing as needed. -pub fn decompress(input: &[u8], output: &mut Vec, dictionary: Dictionary) -> BrotliStatus { +/// Brotli compresses a slice into a buffer of limited capacity. +pub fn decompress(input: &[u8], dictionary: Dictionary) -> Result, BrotliStatus> { unsafe { let state = BrotliDecoderCreateInstance(None, None, ptr::null_mut()); + let mut output: Vec = Vec::with_capacity(4 * input.len()); - macro_rules! require { - ($cond:expr) => { - if !$cond { + macro_rules! check { + ($ret:expr) => { + if $ret.is_err() { BrotliDecoderDestroyInstance(state); - return BrotliStatus::Failure; + return Err(BrotliStatus::Failure); } }; } - if dictionary != Dictionary::Empty { + // TODO: consider window and quality check? + // TODO: fuzz + if let Some(dict) = dictionary.slice() { let attatched = BrotliDecoderAttachDictionary( state, BrotliSharedDictionaryType::Raw, - dictionary.len(), - dictionary.data(), + dict.len(), + dict.as_ptr(), ); - require!(attatched == BrotliBool::True); + check!(attatched); } let mut in_len = input.len(); @@ -166,17 +275,17 @@ pub fn decompress(input: &[u8], output: &mut Vec, dictionary: Dictionary) -> out_left = output.capacity() - out_len; continue; } - require!(status == BrotliStatus::Success); - require!(BrotliDecoderIsFinished(state) == BrotliBool::True); + check!(status); + check!(BrotliDecoderIsFinished(state)); break; } BrotliDecoderDestroyInstance(state); + Ok(output) } - BrotliStatus::Success } -/// Brotli decompresses a slice, returning the number of bytes written. +/// Brotli decompresses a slice into pub fn decompress_fixed<'a>( input: &'a [u8], output: &'a mut [MaybeUninit], @@ -185,7 +294,7 @@ pub fn decompress_fixed<'a>( unsafe { let state = BrotliDecoderCreateInstance(None, None, ptr::null_mut()); - macro_rules! require { + macro_rules! check { ($cond:expr) => { if !$cond { BrotliDecoderDestroyInstance(state); @@ -194,14 +303,14 @@ pub fn decompress_fixed<'a>( }; } - if dictionary != Dictionary::Empty { + if let Some(dict) = dictionary.slice() { let attatched = BrotliDecoderAttachDictionary( state, BrotliSharedDictionaryType::Raw, - dictionary.len(), - dictionary.data(), + dict.len(), + dict.as_ptr(), ); - require!(attatched == BrotliBool::True); + check!(attatched == BrotliBool::True); } let mut in_len = input.len(); @@ -218,8 +327,8 @@ pub fn decompress_fixed<'a>( &mut out_ptr as _, &mut out_len as _, ); - require!(status == BrotliStatus::Success); - require!(BrotliDecoderIsFinished(state) == BrotliBool::True); + check!(status == BrotliStatus::Success); + check!(BrotliDecoderIsFinished(state) == BrotliBool::True); BrotliDecoderDestroyInstance(state); // SAFETY: brotli initialized this span of bytes diff --git a/arbitrator/brotli/src/types.rs b/arbitrator/brotli/src/types.rs index 48697a54a..ace44f389 100644 --- a/arbitrator/brotli/src/types.rs +++ b/arbitrator/brotli/src/types.rs @@ -1,13 +1,14 @@ // Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE -#![allow(dead_code, clippy::len_without_is_empty)] +#![allow(dead_code)] use num_enum::{IntoPrimitive, TryFromPrimitive}; -pub const BROTLI_MODE_GENERIC: u32 = 0; +/// The default window size used during compression. pub const DEFAULT_WINDOW_SIZE: u32 = 22; +/// Represents the outcome of a brotli operation. #[derive(Debug, PartialEq, IntoPrimitive, TryFromPrimitive)] #[repr(u32)] pub enum BrotliStatus { @@ -18,15 +19,18 @@ pub enum BrotliStatus { } impl BrotliStatus { + /// Whether the outcome of the operation was successful. pub fn is_ok(&self) -> bool { self == &Self::Success } + /// Whether the outcome of the operation was an error of any kind. pub fn is_err(&self) -> bool { !self.is_ok() } } +/// A portable `bool`. #[derive(PartialEq)] #[repr(usize)] pub(super) enum BrotliBool { @@ -34,33 +38,65 @@ pub(super) enum BrotliBool { True, } +impl BrotliBool { + /// Whether the type is `True`. This function exists since the API conflates `BrotliBool` and `BrotliStatus` at times. + pub fn is_ok(&self) -> bool { + self == &Self::True + } + + /// Whether the type is `False`. This function exists since the API conflates `BrotliBool` and `BrotliStatus` at times. + pub fn is_err(&self) -> bool { + !self.is_ok() + } +} + +/// The dictionary policy. #[repr(C)] -pub(super) enum BrotliSharedDictionaryType { - /// LZ77 prefix dictionary - Raw, - /// Serialized dictionary - Serialized, +pub(super) enum BrotliEncoderMode { + /// Start with an empty dictionary. + Generic, + /// Use the pre-built dictionary for text. + Text, + /// Use the pre-built dictionary for fonts. + Font, } -#[derive(Clone, Copy, PartialEq, IntoPrimitive, TryFromPrimitive)] -#[repr(u32)] -pub enum Dictionary { - Empty, - StylusProgram, +/// Configuration options for brotli compression. +#[repr(C)] +pub(super) enum BrotliEncoderParameter { + /// The dictionary policy. + Mode, + /// The brotli level. Ranges from 0 to 11. + Quality, + /// The size of the window. Defaults to 22. + WindowSize, + BlockSize, + DisableContextModeling, + SizeHint, + LargeWindowMode, + PostfixBits, + DirectDistanceCodes, + StreamOffset, } -impl Dictionary { - pub fn len(&self) -> usize { - match self { - Self::Empty => 0, - Self::StylusProgram => todo!(), - } - } +/// Streaming operations for use when encoding. +#[repr(C)] +pub(super) enum BrotliEncoderOperation { + /// Produce as much output as possible. + Process, + /// Flush the contents of the encoder. + Flush, + /// Flush and finalize the contents of the encoder. + Finish, + /// Emit metadata info. + Metadata, +} - pub fn data(&self) -> *const u8 { - match self { - Self::Empty => [].as_ptr(), - Self::StylusProgram => todo!(), - } - } +/// Type of custom dictionary. +#[repr(C)] +pub(super) enum BrotliSharedDictionaryType { + /// LZ77 prefix dictionary + Raw, + /// Serialized dictionary + Serialized, } diff --git a/arbitrator/brotli/src/wasmer_traits.rs b/arbitrator/brotli/src/wasmer_traits.rs index 9ae7f3b96..169b2862a 100644 --- a/arbitrator/brotli/src/wasmer_traits.rs +++ b/arbitrator/brotli/src/wasmer_traits.rs @@ -1,7 +1,7 @@ // Copyright 2024, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE -use crate::types::{BrotliStatus, Dictionary}; +use crate::{dicts::Dictionary, types::BrotliStatus}; use wasmer::FromToNativeWasmType; unsafe impl FromToNativeWasmType for BrotliStatus { diff --git a/arbitrator/prover/Cargo.toml b/arbitrator/prover/Cargo.toml index 15a6dd99e..6ff6b5173 100644 --- a/arbitrator/prover/Cargo.toml +++ b/arbitrator/prover/Cargo.toml @@ -23,7 +23,7 @@ static_assertions = "1.1.0" structopt = "0.3.23" serde_with = "1.12.1" parking_lot = "0.12.1" -lazy_static = "1.4.0" +lazy_static.workspace = true itertools = "0.10.5" wat = "1.0.56" smallvec = { version = "1.10.0", features = ["serde"] } diff --git a/arbitrator/prover/dict b/arbitrator/prover/dict new file mode 100644 index 0000000000000000000000000000000000000000..90117d6df53a95210825dc97e031d5dbd8e6f5d1 GIT binary patch literal 429 zcmV;e0aE_KFn9oPabTYQDS}2QX;_#mFO{|6li$6w**-`3IcuHLgsK*U^M>{(Dx?Z) zMG^%6ZSIY0jmt;$tiw`C16v9L|Kn7!Y)lx|d4%W7q}ghyOJK}z-GizHua;CxQf+Ja z@Rv}?&h)ZAF*tOfdP>&`k|y(`0vL4AzC=)j!mI=D@;}V@k|YokOGK{KL)$JJW1EIS z!*Lq*E%;oeN1^*~Q_H$9(y*!dgpR}UY-c34?~W@%y=BMaQO;H*Mec6!O>8jb_efw_ z+3MdR>x4!pU+*}LZ-}I$&%$?iVjCW2@w_A%=-{%GGRaZ>?#M<4n09it|5AHNVbKkS z(iZQAl1&I=yHhEMD=1!~-BWr(EgIBfOQ?ZHbNV}>RZT3B`R3?u9ayzG!jOb{3+YkW zYazo#*y`xnL0(_78ROHkCFpz86O-Jgip^`glG$(I_9$w8duA3Gxc_RFur`GJl#B|RKlJ!jQ0r43mB&LXYY1y@IvN5(v=<$cSr+&x4SVf*z zJzRZLbOn_=(l(HOte`)bRW_LZb~?vQGg%rs?Il*3^HX-X+n4tE?mHGjM+%@I;3niq zSyqYGjfL^rFgsGsx}5kDsV(GyQEPUf?3Ld(L+t`+F%s>;wq0Z{_YyEA9V@m|KQ{aZ z(nd#uc~+Y-_7xdW&bDG&HKKB!u+}E#I;aig2og(Vn00mnYemzSCAM!i{;e#8&fLvUx5tN(wJ3Nn>RJxvFWC4b ZK4R3C{Hc2L Result { let mut modules: Vec = { - use brotli::Dictionary; let compressed = std::fs::read(wavm_binary)?; - let mut modules = vec![]; - if brotli::decompress(&compressed, &mut modules, Dictionary::Empty).is_err() { + let Ok(modules) = brotli::decompress(&compressed, Dictionary::Empty) else { bail!("failed to decompress wavm binary"); - } + }; bincode::deserialize(&modules)? }; @@ -1466,10 +1465,9 @@ impl Machine { ); let modules = bincode::serialize(&self.modules)?; let window = brotli::DEFAULT_WINDOW_SIZE; - let mut output = Vec::with_capacity(2 * modules.len()); - if brotli::compress(&modules, &mut output, 9, window).is_err() { + let Ok(output) = brotli::compress(&modules, 9, window, Dictionary::Empty) else { bail!("failed to compress binary"); - } + }; let mut file = File::create(path)?; file.write_all(&output)?; diff --git a/arbitrator/prover/src/test.rs b/arbitrator/prover/src/test.rs index ee57281ce..97170441f 100644 --- a/arbitrator/prover/src/test.rs +++ b/arbitrator/prover/src/test.rs @@ -1,4 +1,4 @@ -// Copyright 2022, Offchain Labs, Inc. +// Copyright 2022-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE #![cfg(test)] @@ -57,20 +57,15 @@ pub fn reject_ambiguous_imports() { #[test] pub fn test_compress() -> Result<()> { - let data = std::fs::read("test-cases/block.wat")?; - let dict = Dictionary::Empty; + let data = include_bytes!("../../../target/machines/latest/forward_stub.wasm"); + let mut last = vec![]; - let deflate = &mut Vec::with_capacity(data.len()); - assert!(brotli::compress(&data, deflate, 0, 22).is_ok()); - assert!(!deflate.is_empty()); - - let inflate = &mut Vec::with_capacity(data.len()); - assert!(brotli::decompress(deflate, inflate, dict, false).is_ok()); - assert_eq!(hex::encode(inflate), hex::encode(&data)); - - let inflate = &mut vec![]; - assert!(brotli::decompress(deflate, inflate, dict, false).is_err()); - assert!(brotli::decompress(deflate, inflate, dict, true).is_ok()); - assert_eq!(hex::encode(inflate), hex::encode(&data)); + for dict in [Dictionary::Empty, Dictionary::StylusProgram] { + let deflate = brotli::compress(data, 11, 22, dict).unwrap(); + let inflate = brotli::decompress(&deflate, dict).unwrap(); + assert_eq!(hex::encode(inflate), hex::encode(data)); + assert!(&deflate != &last); + last = deflate; + } Ok(()) } diff --git a/arbitrator/wasm-libraries/Cargo.lock b/arbitrator/wasm-libraries/Cargo.lock index 25122cfcd..717d68387 100644 --- a/arbitrator/wasm-libraries/Cargo.lock +++ b/arbitrator/wasm-libraries/Cargo.lock @@ -117,6 +117,7 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" name = "brotli" version = "0.1.0" dependencies = [ + "lazy_static", "num_enum", "wee_alloc", ] diff --git a/arbos/programs/programs.go b/arbos/programs/programs.go index db9a98426..e9174b897 100644 --- a/arbos/programs/programs.go +++ b/arbos/programs/programs.go @@ -358,7 +358,7 @@ func getWasm(statedb vm.StateDB, program common.Address) ([]byte, error) { if err != nil { return nil, err } - return arbcompress.Decompress(wasm, MaxWasmSize) + return arbcompress.DecompressWithDictionary(wasm, MaxWasmSize, arbcompress.StylusProgramDictionary) } func (p Programs) getProgram(codeHash common.Hash, time uint64) (Program, error) { diff --git a/system_tests/program_test.go b/system_tests/program_test.go index 0a570a8db..2fdab70bd 100644 --- a/system_tests/program_test.go +++ b/system_tests/program_test.go @@ -1145,7 +1145,7 @@ func readWasmFile(t *testing.T, file string) ([]byte, []byte) { wasmSource, err := wasmer.Wat2Wasm(string(source)) Require(t, err) - wasm, err := arbcompress.CompressWell(wasmSource) + wasm, err := arbcompress.Compress(wasmSource, arbcompress.LEVEL_WELL, arbcompress.StylusProgramDictionary) Require(t, err) toKb := func(data []byte) float64 { return float64(len(data)) / 1024.0 } From 49f7b8cade192d3843c5482ce123f6e58618c985 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Tue, 19 Mar 2024 02:33:15 -0600 Subject: [PATCH 08/20] fix dockerfile + inclusion --- Dockerfile | 4 ++++ arbitrator/prover/dict | Bin 429 -> 0 bytes arbitrator/prover/no-dict | Bin 483 -> 0 bytes 3 files changed, 4 insertions(+) delete mode 100644 arbitrator/prover/dict delete mode 100644 arbitrator/prover/no-dict diff --git a/Dockerfile b/Dockerfile index bca9a7cc3..48b413376 100644 --- a/Dockerfile +++ b/Dockerfile @@ -46,6 +46,7 @@ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --de COPY ./Makefile ./ COPY arbitrator/Cargo.* arbitrator/ COPY arbitrator/arbutil arbitrator/arbutil +COPY arbitrator/brotli arbitrator/brotli COPY arbitrator/caller-env arbitrator/caller-env COPY arbitrator/prover arbitrator/prover COPY arbitrator/wasm-libraries arbitrator/wasm-libraries @@ -93,6 +94,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ COPY arbitrator/Cargo.* arbitrator/ COPY ./Makefile ./ COPY arbitrator/arbutil arbitrator/arbutil +COPY arbitrator/brotli arbitrator/brotli COPY arbitrator/caller-env arbitrator/caller-env COPY arbitrator/prover arbitrator/prover COPY arbitrator/wasm-libraries arbitrator/wasm-libraries @@ -115,6 +117,7 @@ RUN wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - && \ apt-get install -y llvm-15-dev libclang-common-15-dev libpolly-15-dev COPY arbitrator/Cargo.* arbitrator/ COPY arbitrator/arbutil arbitrator/arbutil +COPY arbitrator/brotli arbitrator/brotli COPY arbitrator/caller-env arbitrator/caller-env COPY arbitrator/prover/Cargo.toml arbitrator/prover/ COPY arbitrator/jit/Cargo.toml arbitrator/jit/ @@ -156,6 +159,7 @@ COPY --from=wasm-libs-builder /workspace/arbitrator/prover/ arbitrator/prover/ COPY --from=wasm-libs-builder /workspace/arbitrator/tools/wasmer/ arbitrator/tools/wasmer/ COPY --from=wasm-libs-builder /workspace/arbitrator/wasm-libraries/ arbitrator/wasm-libraries/ COPY --from=wasm-libs-builder /workspace/arbitrator/arbutil arbitrator/arbutil +COPY --from=wasm-libs-builder /workspace/arbitrator/brotli arbitrator/brotli COPY --from=wasm-libs-builder /workspace/arbitrator/caller-env arbitrator/caller-env COPY --from=wasm-libs-builder /workspace/.make/ .make/ COPY ./Makefile ./ diff --git a/arbitrator/prover/dict b/arbitrator/prover/dict deleted file mode 100644 index 90117d6df53a95210825dc97e031d5dbd8e6f5d1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 429 zcmV;e0aE_KFn9oPabTYQDS}2QX;_#mFO{|6li$6w**-`3IcuHLgsK*U^M>{(Dx?Z) zMG^%6ZSIY0jmt;$tiw`C16v9L|Kn7!Y)lx|d4%W7q}ghyOJK}z-GizHua;CxQf+Ja z@Rv}?&h)ZAF*tOfdP>&`k|y(`0vL4AzC=)j!mI=D@;}V@k|YokOGK{KL)$JJW1EIS z!*Lq*E%;oeN1^*~Q_H$9(y*!dgpR}UY-c34?~W@%y=BMaQO;H*Mec6!O>8jb_efw_ z+3MdR>x4!pU+*}LZ-}I$&%$?iVjCW2@w_A%=-{%GGRaZ>?#M<4n09it|5AHNVbKkS z(iZQAl1&I=yHhEMD=1!~-BWr(EgIBfOQ?ZHbNV}>RZT3B`R3?u9ayzG!jOb{3+YkW zYazo#*y`xnL0(_78ROHkCFpz86O-Jgip^`glG$(I_9$w8duA3Gxc_RFur`GJl#B|RKlJ!jQ0r43mB&LXYY1y@IvN5(v=<$cSr+&x4SVf*z zJzRZLbOn_=(l(HOte`)bRW_LZb~?vQGg%rs?Il*3^HX-X+n4tE?mHGjM+%@I;3niq zSyqYGjfL^rFgsGsx}5kDsV(GyQEPUf?3Ld(L+t`+F%s>;wq0Z{_YyEA9V@m|KQ{aZ z(nd#uc~+Y-_7xdW&bDG&HKKB!u+}E#I;aig2og(Vn00mnYemzSCAM!i{;e#8&fLvUx5tN(wJ3Nn>RJxvFWC4b ZK4R3C{Hc2L Date: Tue, 19 Mar 2024 14:17:57 -0700 Subject: [PATCH 09/20] fuzzing for brotli --- arbitrator/Cargo.lock | 37 +++++++++++++++++++ arbitrator/Cargo.toml | 1 + arbitrator/brotli/fuzz/.gitignore | 4 ++ arbitrator/brotli/fuzz/Cargo.toml | 28 ++++++++++++++ arbitrator/brotli/fuzz/README | 9 +++++ .../brotli/fuzz/fuzz_targets/compress.rs | 13 +++++++ .../brotli/fuzz/fuzz_targets/decompress.rs | 10 +++++ 7 files changed, 102 insertions(+) create mode 100644 arbitrator/brotli/fuzz/.gitignore create mode 100644 arbitrator/brotli/fuzz/Cargo.toml create mode 100644 arbitrator/brotli/fuzz/README create mode 100644 arbitrator/brotli/fuzz/fuzz_targets/compress.rs create mode 100644 arbitrator/brotli/fuzz/fuzz_targets/decompress.rs diff --git a/arbitrator/Cargo.lock b/arbitrator/Cargo.lock index a296ba111..28607969f 100644 --- a/arbitrator/Cargo.lock +++ b/arbitrator/Cargo.lock @@ -46,6 +46,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" + [[package]] name = "arbutil" version = "0.1.0" @@ -153,6 +159,14 @@ dependencies = [ "wee_alloc", ] +[[package]] +name = "brotli-fuzz" +version = "0.0.0" +dependencies = [ + "brotli", + "libfuzzer-sys", +] + [[package]] name = "bumpalo" version = "3.12.0" @@ -209,6 +223,9 @@ name = "cc" version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +dependencies = [ + "jobserver", +] [[package]] name = "cfg-if" @@ -805,6 +822,15 @@ dependencies = [ "wasmer-compiler-llvm", ] +[[package]] +name = "jobserver" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.60" @@ -841,6 +867,17 @@ version = "0.2.146" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" +[[package]] +name = "libfuzzer-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + [[package]] name = "llvm-sys" version = "150.1.3" diff --git a/arbitrator/Cargo.toml b/arbitrator/Cargo.toml index 09268377e..ebaab96bc 100644 --- a/arbitrator/Cargo.toml +++ b/arbitrator/Cargo.toml @@ -2,6 +2,7 @@ members = [ "arbutil", "brotli", + "brotli/fuzz", "caller-env", "prover", "stylus", diff --git a/arbitrator/brotli/fuzz/.gitignore b/arbitrator/brotli/fuzz/.gitignore new file mode 100644 index 000000000..1a45eee77 --- /dev/null +++ b/arbitrator/brotli/fuzz/.gitignore @@ -0,0 +1,4 @@ +target +corpus +artifacts +coverage diff --git a/arbitrator/brotli/fuzz/Cargo.toml b/arbitrator/brotli/fuzz/Cargo.toml new file mode 100644 index 000000000..965d1b2e2 --- /dev/null +++ b/arbitrator/brotli/fuzz/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "brotli-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" + +[dependencies.brotli] +path = ".." + +[[bin]] +name = "compress" +path = "fuzz_targets/compress.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "decompress" +path = "fuzz_targets/decompress.rs" +test = false +doc = false +bench = false diff --git a/arbitrator/brotli/fuzz/README b/arbitrator/brotli/fuzz/README new file mode 100644 index 000000000..b3642cd38 --- /dev/null +++ b/arbitrator/brotli/fuzz/README @@ -0,0 +1,9 @@ + +Fuzzing for brotli. You'll need `cargo-fuzz`. Install it with `cargo install +cargo-fuzz`. You'll also need to use the Rust nightly compiler - `rustup +default nightly`. + +Then you can fuzz with +`cargo fuzz compress -- -max_len 65536` +or +`cargo fuzz decompress -- -max_len 65536`. diff --git a/arbitrator/brotli/fuzz/fuzz_targets/compress.rs b/arbitrator/brotli/fuzz/fuzz_targets/compress.rs new file mode 100644 index 000000000..cafb07d5b --- /dev/null +++ b/arbitrator/brotli/fuzz/fuzz_targets/compress.rs @@ -0,0 +1,13 @@ +// Copyright 2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +#![no_main] + +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|arg: (&[u8], u32, u32)| { + let data = arg.0; + let quality = arg.1; + let window = arg.2; + let _ = brotli::compress(data, 1 + quality % 12, 10 + window % 15, brotli::Dictionary::StylusProgram); +}); diff --git a/arbitrator/brotli/fuzz/fuzz_targets/decompress.rs b/arbitrator/brotli/fuzz/fuzz_targets/decompress.rs new file mode 100644 index 000000000..28dccf7c9 --- /dev/null +++ b/arbitrator/brotli/fuzz/fuzz_targets/decompress.rs @@ -0,0 +1,10 @@ +// Copyright 2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +#![no_main] + +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + let _ = brotli::decompress(data, brotli::Dictionary::StylusProgram); +}); From 50828a64d9e83e8eef765a781023b8a1f03b6f03 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Tue, 19 Mar 2024 21:34:39 -0600 Subject: [PATCH 10/20] check more invariants --- arbitrator/brotli/fuzz/README | 8 ++++++-- .../brotli/fuzz/fuzz_targets/compress.rs | 7 ++++++- .../brotli/fuzz/fuzz_targets/decompress.rs | 20 ++++++++++++++++++- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/arbitrator/brotli/fuzz/README b/arbitrator/brotli/fuzz/README index b3642cd38..e00f4c343 100644 --- a/arbitrator/brotli/fuzz/README +++ b/arbitrator/brotli/fuzz/README @@ -4,6 +4,10 @@ cargo-fuzz`. You'll also need to use the Rust nightly compiler - `rustup default nightly`. Then you can fuzz with -`cargo fuzz compress -- -max_len 65536` +```bash +cargo +nightly fuzz run compress -- -max_len=262144 +``` or -`cargo fuzz decompress -- -max_len 65536`. +```bash +cargo +nightly fuzz run decompress -- -max_len=262144 +``` diff --git a/arbitrator/brotli/fuzz/fuzz_targets/compress.rs b/arbitrator/brotli/fuzz/fuzz_targets/compress.rs index cafb07d5b..6141ede76 100644 --- a/arbitrator/brotli/fuzz/fuzz_targets/compress.rs +++ b/arbitrator/brotli/fuzz/fuzz_targets/compress.rs @@ -9,5 +9,10 @@ fuzz_target!(|arg: (&[u8], u32, u32)| { let data = arg.0; let quality = arg.1; let window = arg.2; - let _ = brotli::compress(data, 1 + quality % 12, 10 + window % 15, brotli::Dictionary::StylusProgram); + let _ = brotli::compress( + data, + 1 + quality % 12, + 10 + window % 15, + brotli::Dictionary::StylusProgram, + ); }); diff --git a/arbitrator/brotli/fuzz/fuzz_targets/decompress.rs b/arbitrator/brotli/fuzz/fuzz_targets/decompress.rs index 28dccf7c9..1ccabca1d 100644 --- a/arbitrator/brotli/fuzz/fuzz_targets/decompress.rs +++ b/arbitrator/brotli/fuzz/fuzz_targets/decompress.rs @@ -3,8 +3,26 @@ #![no_main] +use brotli::Dictionary; use libfuzzer_sys::fuzz_target; fuzz_target!(|data: &[u8]| { - let _ = brotli::decompress(data, brotli::Dictionary::StylusProgram); + let mut data = data; + let dict = Dictionary::StylusProgram; + + let mut space = 0_u32; + if data.len() >= 8 { + space = u32::from_le_bytes(data[..4].try_into().unwrap()); + data = &data[4..]; + } + + let mut array = Vec::with_capacity(space as usize % 65536); + let array = &mut array.spare_capacity_mut(); + + let plain = brotli::decompress(data, dict); + let fixed = brotli::decompress_fixed(data, array, dict); + + if let Ok(fixed) = fixed { + assert_eq!(fixed.len(), plain.unwrap().len()); // fixed succeeding implies both do + } }); From dec3ad1acdac8874bcae73b4399d04f35e9d9410 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Tue, 19 Mar 2024 22:40:48 -0600 Subject: [PATCH 11/20] cleanup --- arbitrator/brotli/fuzz/fuzz_targets/decompress.rs | 2 +- arbitrator/brotli/src/lib.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/arbitrator/brotli/fuzz/fuzz_targets/decompress.rs b/arbitrator/brotli/fuzz/fuzz_targets/decompress.rs index 1ccabca1d..dd36d6483 100644 --- a/arbitrator/brotli/fuzz/fuzz_targets/decompress.rs +++ b/arbitrator/brotli/fuzz/fuzz_targets/decompress.rs @@ -11,7 +11,7 @@ fuzz_target!(|data: &[u8]| { let dict = Dictionary::StylusProgram; let mut space = 0_u32; - if data.len() >= 8 { + if data.len() >= 4 { space = u32::from_le_bytes(data[..4].try_into().unwrap()); data = &data[4..]; } diff --git a/arbitrator/brotli/src/lib.rs b/arbitrator/brotli/src/lib.rs index a14a2a2c3..416ba2914 100644 --- a/arbitrator/brotli/src/lib.rs +++ b/arbitrator/brotli/src/lib.rs @@ -5,7 +5,9 @@ extern crate alloc; +#[cfg(target_arch = "wasm32")] use alloc::vec::Vec; + use core::{ ffi::c_void, mem::{self, MaybeUninit}, From 5dd1e8e5aef81e45ebffece265a43a503649f242 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Tue, 19 Mar 2024 23:07:39 -0600 Subject: [PATCH 12/20] fix test-gen-proofs --- arbitrator/prover/src/host.rs | 1 + arbitrator/prover/src/machine.rs | 1 + arbitrator/prover/src/wavm.rs | 5 +++-- arbitrator/wasm-libraries/host-io/Cargo.toml | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/arbitrator/prover/src/host.rs b/arbitrator/prover/src/host.rs index d5ec9154a..f793bbee5 100644 --- a/arbitrator/prover/src/host.rs +++ b/arbitrator/prover/src/host.rs @@ -496,6 +496,7 @@ lazy_static! { &[ty.clone()], // only type needed is the func itself 0, // ----------------------------------- 0, // impls don't use other internals + &bin.names.module, ), ty.clone(), &[] // impls don't make calls diff --git a/arbitrator/prover/src/machine.rs b/arbitrator/prover/src/machine.rs index d91015e19..f4cc3cfcd 100644 --- a/arbitrator/prover/src/machine.rs +++ b/arbitrator/prover/src/machine.rs @@ -411,6 +411,7 @@ impl Module { &types, func_type_idxs[idx], internals_offset, + bin_name, ) }, func_ty.clone(), diff --git a/arbitrator/prover/src/wavm.rs b/arbitrator/prover/src/wavm.rs index 2507ff403..2103d4c5a 100644 --- a/arbitrator/prover/src/wavm.rs +++ b/arbitrator/prover/src/wavm.rs @@ -6,7 +6,7 @@ use crate::{ host::InternalFunc, value::{ArbValueType, FunctionType, IntegerValType}, }; -use arbutil::Bytes32; +use arbutil::{Bytes32, Color, DebugColor}; use digest::Digest; use eyre::{bail, ensure, Result}; use fnv::FnvHashMap as HashMap; @@ -469,6 +469,7 @@ pub fn wasm_to_wavm( all_types: &[FunctionType], all_types_func_idx: u32, internals_offset: u32, + name: &str, ) -> Result<()> { use Operator::*; @@ -598,7 +599,7 @@ pub fn wasm_to_wavm( let func = $func; let sig = func.signature(); let Some((module, func)) = fp_impls.get(&func) else { - bail!("No implementation for floating point operation {:?}", &func) + bail!("No implementation for floating point operation {} in {}", func.debug_red(), name.red()); }; // Reinterpret float args into ints diff --git a/arbitrator/wasm-libraries/host-io/Cargo.toml b/arbitrator/wasm-libraries/host-io/Cargo.toml index 1743b1017..45f9f8483 100644 --- a/arbitrator/wasm-libraries/host-io/Cargo.toml +++ b/arbitrator/wasm-libraries/host-io/Cargo.toml @@ -8,4 +8,4 @@ publish = false crate-type = ["cdylib"] [dependencies] -caller-env = { path = "../../caller-env/", features = ["static_caller"] } +caller-env = { path = "../../caller-env/", default-features = false, features = ["static_caller"] } From 661fc11fb495c26ad2ec432e70af207680ce272a Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Wed, 20 Mar 2024 01:48:36 -0600 Subject: [PATCH 13/20] fix dockerfile --- Dockerfile | 9 ++++++++- Makefile | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 48b413376..550442e87 100644 --- a/Dockerfile +++ b/Dockerfile @@ -101,6 +101,10 @@ COPY arbitrator/wasm-libraries arbitrator/wasm-libraries COPY arbitrator/jit arbitrator/jit COPY arbitrator/stylus arbitrator/stylus COPY arbitrator/tools/wasmer arbitrator/tools/wasmer +COPY --from=brotli-wasm-export / target/ +COPY scripts/build-brotli.sh scripts/ +COPY brotli brotli +RUN apt-get update && apt-get install -y cmake RUN NITRO_BUILD_IGNORE_TIMESTAMPS=1 make build-prover-header FROM scratch as prover-header-export @@ -115,6 +119,7 @@ RUN wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - && \ add-apt-repository 'deb http://apt.llvm.org/bullseye/ llvm-toolchain-bullseye-15 main' && \ apt-get update && \ apt-get install -y llvm-15-dev libclang-common-15-dev libpolly-15-dev +COPY --from=brotli-library-export / target/ COPY arbitrator/Cargo.* arbitrator/ COPY arbitrator/arbutil arbitrator/arbutil COPY arbitrator/brotli arbitrator/brotli @@ -137,7 +142,9 @@ COPY arbitrator/prover arbitrator/prover COPY arbitrator/wasm-libraries arbitrator/wasm-libraries COPY arbitrator/jit arbitrator/jit COPY arbitrator/stylus arbitrator/stylus -COPY --from=brotli-library-export / target/ +COPY --from=brotli-wasm-export / target/ +COPY scripts/build-brotli.sh scripts/ +COPY brotli brotli RUN touch -a -m arbitrator/prover/src/lib.rs RUN NITRO_BUILD_IGNORE_TIMESTAMPS=1 make build-prover-lib RUN NITRO_BUILD_IGNORE_TIMESTAMPS=1 make build-prover-bin diff --git a/Makefile b/Makefile index afa125176..521b03e40 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ ifneq ($(origin NITRO_MODIFIED),undefined) endif ifneq ($(origin GOLANG_LDFLAGS),undefined) - GOLANG_PARAMS = -ldflags="$(GOLANG_LDFLAGS)" + GOLANG_PARAMS = -ldflags="-extldflags '-ldl' $(GOLANG_LDFLAGS)" endif precompile_names = AddressTable Aggregator BLS Debug FunctionTable GasInfo Info osTest Owner RetryableTx Statistics Sys From e34567d9c4b50f5f4a0491274ca0d814d41decb0 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Wed, 20 Mar 2024 02:29:45 -0600 Subject: [PATCH 14/20] make custom dictionaries configurable --- Dockerfile | 3 +++ arbos/programs/programs.go | 14 ++++++++++++-- go-ethereum | 2 +- system_tests/program_test.go | 7 +++++-- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 550442e87..572df90e2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -51,7 +51,10 @@ COPY arbitrator/caller-env arbitrator/caller-env COPY arbitrator/prover arbitrator/prover COPY arbitrator/wasm-libraries arbitrator/wasm-libraries COPY arbitrator/tools/wasmer arbitrator/tools/wasmer +COPY brotli brotli +COPY scripts/build-brotli.sh scripts/ COPY --from=brotli-wasm-export / target/ +RUN apt-get update && apt-get install -y cmake RUN . ~/.cargo/env && NITRO_BUILD_IGNORE_TIMESTAMPS=1 RUSTFLAGS='-C symbol-mangling-version=v0' make build-wasm-libs FROM scratch as wasm-libs-export diff --git a/arbos/programs/programs.go b/arbos/programs/programs.go index e9174b897..d7e8822ad 100644 --- a/arbos/programs/programs.go +++ b/arbos/programs/programs.go @@ -354,11 +354,21 @@ func getWasm(statedb vm.StateDB, program common.Address) ([]byte, error) { if prefixedWasm == nil { return nil, fmt.Errorf("missing wasm at address %v", program) } - wasm, err := state.StripStylusPrefix(prefixedWasm) + wasm, dictByte, err := state.StripStylusPrefix(prefixedWasm) if err != nil { return nil, err } - return arbcompress.DecompressWithDictionary(wasm, MaxWasmSize, arbcompress.StylusProgramDictionary) + + var dict arbcompress.Dictionary + switch dictByte { + case 0: + dict = arbcompress.EmptyDictionary + case 1: + dict = arbcompress.StylusProgramDictionary + default: + return nil, fmt.Errorf("unsupported dictionary %v", dictByte) + } + return arbcompress.DecompressWithDictionary(wasm, MaxWasmSize, dict) } func (p Programs) getProgram(codeHash common.Hash, time uint64) (Program, error) { diff --git a/go-ethereum b/go-ethereum index 3dd61fe44..3c95595ec 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit 3dd61fe44e248311e8ba6a6ad52434f5303b9a3e +Subproject commit 3c95595ecc6b4f4d69f1b0b94decc25486d399ea diff --git a/system_tests/program_test.go b/system_tests/program_test.go index 2fdab70bd..8dc63d1f9 100644 --- a/system_tests/program_test.go +++ b/system_tests/program_test.go @@ -1143,15 +1143,18 @@ func readWasmFile(t *testing.T, file string) ([]byte, []byte) { source, err := os.ReadFile(file) Require(t, err) + // chose a random dictionary for testing, but keep the same files consistent + randDict := arbcompress.Dictionary((len(file) + len(t.Name())) % 2) + wasmSource, err := wasmer.Wat2Wasm(string(source)) Require(t, err) - wasm, err := arbcompress.Compress(wasmSource, arbcompress.LEVEL_WELL, arbcompress.StylusProgramDictionary) + wasm, err := arbcompress.Compress(wasmSource, arbcompress.LEVEL_WELL, randDict) Require(t, err) toKb := func(data []byte) float64 { return float64(len(data)) / 1024.0 } colors.PrintGrey(fmt.Sprintf("%v: len %.2fK vs %.2fK", name, toKb(wasm), toKb(wasmSource))) - wasm = append(state.StylusPrefix, wasm...) + wasm = append(state.NewStylusPrefix(byte(randDict)), wasm...) return wasm, wasmSource } From 5aed139e061142ced278e5f08b7a1672c7dff22d Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Wed, 20 Mar 2024 21:04:20 -0600 Subject: [PATCH 15/20] compress validator modules --- arbitrator/brotli/src/dicts/mod.rs | 14 ++++++++++++++ arbitrator/brotli/src/lib.rs | 14 +++++++++++++- arbitrator/prover/src/machine.rs | 12 +++++++----- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/arbitrator/brotli/src/dicts/mod.rs b/arbitrator/brotli/src/dicts/mod.rs index f43f71b88..50c8a918f 100644 --- a/arbitrator/brotli/src/dicts/mod.rs +++ b/arbitrator/brotli/src/dicts/mod.rs @@ -75,3 +75,17 @@ impl Dictionary { } } } + +impl From for u8 { + fn from(value: Dictionary) -> Self { + value as u32 as u8 + } +} + +impl TryFrom for Dictionary { + type Error = >::Error; + + fn try_from(value: u8) -> Result { + (value as u32).try_into() + } +} diff --git a/arbitrator/brotli/src/lib.rs b/arbitrator/brotli/src/lib.rs index 416ba2914..7c9da6675 100644 --- a/arbitrator/brotli/src/lib.rs +++ b/arbitrator/brotli/src/lib.rs @@ -113,9 +113,21 @@ pub fn compress( level: u32, window_size: u32, dictionary: Dictionary, +) -> Result, BrotliStatus> { + compress_into(input, Vec::new(), level, window_size, dictionary) +} + +/// Brotli compresses a slice, extending the `output` specified. +pub fn compress_into( + input: &[u8], + mut output: Vec, + level: u32, + window_size: u32, + dictionary: Dictionary, ) -> Result, BrotliStatus> { let max_size = compression_bound(input.len(), level); - let mut output = Vec::with_capacity(max_size); + let needed = max_size.saturating_sub(output.spare_capacity_mut().len()); + output.reserve(needed); unsafe { let state = BrotliEncoderCreateInstance(None, None, ptr::null_mut()); diff --git a/arbitrator/prover/src/machine.rs b/arbitrator/prover/src/machine.rs index f4cc3cfcd..bec938b7c 100644 --- a/arbitrator/prover/src/machine.rs +++ b/arbitrator/prover/src/machine.rs @@ -623,18 +623,20 @@ impl Module { data.extend(self.tables_merkle.root()); data.extend(self.funcs_merkle.root()); - data.extend(self.internals_offset.to_be_bytes()); - data } - pub fn into_bytes(&self) -> Box<[u8]> { - bincode::serialize(self).unwrap().into_boxed_slice() + pub fn into_bytes(&self) -> Vec { + let data = bincode::serialize(self).unwrap(); + let header = vec![Dictionary::Empty.into()]; + brotli::compress_into(&data, header, 0, 22, Dictionary::Empty).expect("failed to compress") } pub unsafe fn from_bytes(bytes: &[u8]) -> Self { - bincode::deserialize(bytes).unwrap() + let dict = Dictionary::try_from(bytes[0]).expect("unknown dictionary"); + let data = brotli::decompress(&bytes[1..], dict).expect("failed to decompress"); + bincode::deserialize(&data).unwrap() } } From ffca5e26eec605eafeed62a4aafd7706c0f35191 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Wed, 20 Mar 2024 21:16:27 -0600 Subject: [PATCH 16/20] docstrings and optional uncompressed variant --- arbitrator/prover/src/machine.rs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/arbitrator/prover/src/machine.rs b/arbitrator/prover/src/machine.rs index bec938b7c..6dc022128 100644 --- a/arbitrator/prover/src/machine.rs +++ b/arbitrator/prover/src/machine.rs @@ -627,16 +627,27 @@ impl Module { data } + /// Serializes the `Module` into bytes that can be stored in the db. + /// The format employed is forward-compatible with future brotli dictionary and caching policies. pub fn into_bytes(&self) -> Vec { let data = bincode::serialize(self).unwrap(); - let header = vec![Dictionary::Empty.into()]; + let header = vec![1 + Into::::into(Dictionary::Empty)]; brotli::compress_into(&data, header, 0, 22, Dictionary::Empty).expect("failed to compress") } - pub unsafe fn from_bytes(bytes: &[u8]) -> Self { - let dict = Dictionary::try_from(bytes[0]).expect("unknown dictionary"); - let data = brotli::decompress(&bytes[1..], dict).expect("failed to decompress"); - bincode::deserialize(&data).unwrap() + /// Deserializes a `Module` from db bytes. + /// + /// # Safety + /// + /// The bytes must have been produced by `into_bytes` and represent a valid `Module`. + pub unsafe fn from_bytes(data: &[u8]) -> Self { + if data[0] > 0 { + let dict = Dictionary::try_from(data[0] - 1).expect("unknown dictionary"); + let data = brotli::decompress(&data[1..], dict).expect("failed to inflate"); + bincode::deserialize(&data).unwrap() + } else { + bincode::deserialize(&data[1..]).unwrap() + } } } From 9dedefe62712bd470c1d141e9f6666b44fbdcc25 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Thu, 21 Mar 2024 00:37:23 -0600 Subject: [PATCH 17/20] deduplicate compression impl --- arbitrator/brotli/src/lib.rs | 54 ++++-------------------------------- 1 file changed, 5 insertions(+), 49 deletions(-) diff --git a/arbitrator/brotli/src/lib.rs b/arbitrator/brotli/src/lib.rs index 7c9da6675..e03190ee2 100644 --- a/arbitrator/brotli/src/lib.rs +++ b/arbitrator/brotli/src/lib.rs @@ -127,56 +127,12 @@ pub fn compress_into( ) -> Result, BrotliStatus> { let max_size = compression_bound(input.len(), level); let needed = max_size.saturating_sub(output.spare_capacity_mut().len()); - output.reserve(needed); - unsafe { - let state = BrotliEncoderCreateInstance(None, None, ptr::null_mut()); - - macro_rules! check { - ($ret:expr) => { - if $ret.is_err() { - BrotliEncoderDestroyInstance(state); - return Err(BrotliStatus::Failure); - } - }; - } - - check!(BrotliEncoderSetParameter( - state, - BrotliEncoderParameter::Quality, - level - )); - check!(BrotliEncoderSetParameter( - state, - BrotliEncoderParameter::WindowSize, - window_size - )); + output.reserve_exact(needed); - if let Some(dict) = dictionary.ptr(level) { - check!(BrotliEncoderAttachPreparedDictionary(state, dict)); - } - - let mut in_len = input.len(); - let mut in_ptr = input.as_ptr(); - let mut out_left = output.capacity(); - let mut out_ptr = output.as_mut_ptr(); - let mut out_len = out_left; - - let status = BrotliEncoderCompressStream( - state, - BrotliEncoderOperation::Finish, - &mut in_len as _, - &mut in_ptr as _, - &mut out_left as _, - &mut out_ptr as _, - &mut out_len as _, - ); - check!(status); - check!(BrotliEncoderIsFinished(state)); - BrotliEncoderDestroyInstance(state); - - output.set_len(out_len); - Ok(output) - } + let space = output.spare_capacity_mut(); + let count = compress_fixed(input, space, level, window_size, dictionary)?.len(); + unsafe { output.set_len(output.len() + count) } + Ok(output) } /// Brotli compresses a slice into a buffer of limited capacity. From d7fdff15490c73c8207a5c8ce260e2a1c83980af Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Thu, 21 Mar 2024 00:40:54 -0600 Subject: [PATCH 18/20] minor tweak --- arbitrator/brotli/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arbitrator/brotli/src/lib.rs b/arbitrator/brotli/src/lib.rs index e03190ee2..d2ebfbc76 100644 --- a/arbitrator/brotli/src/lib.rs +++ b/arbitrator/brotli/src/lib.rs @@ -126,7 +126,7 @@ pub fn compress_into( dictionary: Dictionary, ) -> Result, BrotliStatus> { let max_size = compression_bound(input.len(), level); - let needed = max_size.saturating_sub(output.spare_capacity_mut().len()); + let needed = max_size.saturating_sub(output.capacity() - output.len()); output.reserve_exact(needed); let space = output.spare_capacity_mut(); From 73d48d8370a88be313515f1831c516c15d9668ea Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Thu, 21 Mar 2024 16:32:32 -0600 Subject: [PATCH 19/20] address review comments --- arbitrator/brotli/src/cgo.rs | 2 +- go-ethereum | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/arbitrator/brotli/src/cgo.rs b/arbitrator/brotli/src/cgo.rs index 220ebdca0..3581d024f 100644 --- a/arbitrator/brotli/src/cgo.rs +++ b/arbitrator/brotli/src/cgo.rs @@ -10,7 +10,7 @@ use core::{mem::MaybeUninit, slice}; pub struct BrotliBuffer { /// Points to data owned by Go. ptr: *mut u8, - /// The length in bytes. + /// The length in bytes. Rust may mutate this value to indicate the number of bytes initialized. len: *mut usize, } diff --git a/go-ethereum b/go-ethereum index 3c95595ec..9102492c7 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit 3c95595ecc6b4f4d69f1b0b94decc25486d399ea +Subproject commit 9102492c7e198157b1e5677fd65b731aba8aeaf8 From 1e48f0383d4b2bebda3686d543bb8ab0d100c58e Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Thu, 21 Mar 2024 17:08:41 -0600 Subject: [PATCH 20/20] fallible dictionary.ptr --- arbitrator/brotli/src/dicts/mod.rs | 14 ++++++++++---- arbitrator/brotli/src/lib.rs | 7 +++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/arbitrator/brotli/src/dicts/mod.rs b/arbitrator/brotli/src/dicts/mod.rs index 50c8a918f..40d6c1696 100644 --- a/arbitrator/brotli/src/dicts/mod.rs +++ b/arbitrator/brotli/src/dicts/mod.rs @@ -2,7 +2,8 @@ // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE use crate::{ - types::BrotliSharedDictionaryType, CustomAllocator, EncoderPreparedDictionary, HeapItem, + types::BrotliSharedDictionaryType, BrotliStatus, CustomAllocator, EncoderPreparedDictionary, + HeapItem, }; use core::{ffi::c_int, ptr}; use lazy_static::lazy_static; @@ -68,11 +69,16 @@ impl Dictionary { } /// Returns a pointer to a compression-ready instance of the given dictionary. - pub fn ptr(&self, level: u32) -> Option<*const EncoderPreparedDictionary> { - match self { + /// Note: this function fails when the specified level doesn't match. + pub fn ptr( + &self, + level: u32, + ) -> Result, BrotliStatus> { + Ok(match self { Self::StylusProgram if level == 11 => Some(STYLUS_PROGRAM_DICT.0), + Self::StylusProgram => return Err(BrotliStatus::Failure), _ => None, - } + }) } } diff --git a/arbitrator/brotli/src/lib.rs b/arbitrator/brotli/src/lib.rs index d2ebfbc76..9072d99f7 100644 --- a/arbitrator/brotli/src/lib.rs +++ b/arbitrator/brotli/src/lib.rs @@ -166,8 +166,11 @@ pub fn compress_fixed<'a>( window_size )); - if let Some(dict) = dictionary.ptr(level) { - check!(BrotliEncoderAttachPreparedDictionary(state, dict)); + // attach a custom dictionary if requested + match dictionary.ptr(level) { + Ok(Some(dict)) => check!(BrotliEncoderAttachPreparedDictionary(state, dict)), + Err(status) => check!(status), + _ => {} } let mut in_len = input.len();