Skip to content

Commit

Permalink
feat(core, wasm-instrument): add gr_system_break syscall (#3512)
Browse files Browse the repository at this point in the history
  • Loading branch information
StackOverflowExcept1on authored Dec 25, 2023
1 parent f424256 commit d285152
Show file tree
Hide file tree
Showing 27 changed files with 455 additions and 154 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ jobs:
./target/release/gear benchmark pallet --chain=dev --pallet=pallet_gear --steps=20 --extrinsic="*" --execution=wasm --wasm-execution=compiled --heap-pages=4096
# check that read_big_state benchmarks works
./target/release/gear benchmark pallet --chain=dev --pallet=pallet_gear --extrinsic="read_big_state" --execution=wasm --wasm-execution=compiled --heap-pages=4096 --extra
# check that signal_stack_limit_exceeded_works benchmarks works
./target/release/gear benchmark pallet --chain=dev --pallet=pallet_gear --extrinsic="signal_stack_limit_exceeded_works" --execution=wasm --wasm-execution=compiled --heap-pages=4096 --extra
# check that check/test benchmarks works
./target/release/gear benchmark pallet --chain=dev --pallet=pallet_gear --extrinsic="check_all" --execution=wasm --wasm-execution=compiled --heap-pages=4096 --extra
# check also lazy-pages benchmarks tests for native runtime
Expand Down
7 changes: 5 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,8 @@ tempfile = "3.8.1"
#
# https://github.com/gear-tech/dlmalloc-rust/tree/v0.1.4
dlmalloc = { package = "gear-dlmalloc", version = "0.1.4" }
# https://github.com/gear-tech/wasm-instrument/tree/v0.2.3-sign-ext
gwasm-instrument = { version = "0.2.3", default-features = false }
# https://github.com/gear-tech/wasm-instrument/tree/gear-stable-v0.3.0
gwasm-instrument = { version = "0.3.0", default-features = false }
# https://github.com/gear-tech/wasm-utils/tree/v0.19.0
pwasm-utils = { version = "0.19.0", package = "gear-pwasm-utils" }

Expand Down Expand Up @@ -400,7 +400,7 @@ demo-reservation-manager = { path = "examples/reservation-manager" }
demo-reserve-gas = { path = "examples/reserve-gas", default-features = false }
demo-rwlock = { path = "examples/rwlock" }
demo-send-from-reservation = { path = "examples/send-from-reservation" }
demo-signal-entry = { path = "examples/signal-entry" }
demo-signal-entry = { path = "examples/signal-entry", default-features = false }
demo-state-rollback = { path = "examples/state-rollback" }
demo-sync-duplicate = { path = "examples/sync-duplicate" }
demo-vec = { path = "examples/vec" }
Expand Down
2 changes: 1 addition & 1 deletion core-backend/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ where
builder.add_func(ReservationReplyCommit, wrap_syscall!(reservation_reply_commit));
builder.add_func(ReservationSend, wrap_syscall!(reservation_send));
builder.add_func(ReservationSendCommit, wrap_syscall!(reservation_send_commit));
builder.add_func(OutOfGas, wrap_syscall!(out_of_gas));
builder.add_func(SystemBreak, wrap_syscall!(system_break));

builder.add_func(Alloc, wrap_syscall!(alloc));
builder.add_func(Free, wrap_syscall!(free));
Expand Down
2 changes: 2 additions & 0 deletions core-backend/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ pub enum TrapExplanation {
UnrecoverableExt(UnrecoverableExtError),
#[display(fmt = "{_0}")]
Panic(LimitedStr<'static>),
#[display(fmt = "Stack limit exceeded")]
StackLimitExceeded,
#[display(fmt = "Reason is unknown. Possibly `unreachable` instruction is occurred")]
Unknown,
}
Expand Down
39 changes: 27 additions & 12 deletions core-backend/src/funcs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ use gear_core::{
use gear_core_errors::{MessageError, ReplyCode, SignalCode};
use gear_sandbox::{default_executor::Caller, ReturnValue, Value};
use gear_sandbox_env::{HostError, WasmReturnValue};
use gear_wasm_instrument::SystemBreakCode;
use gsys::{
BlockNumberWithHash, ErrorBytes, ErrorWithBlockNumberAndValue, ErrorWithGas, ErrorWithHandle,
ErrorWithHash, ErrorWithReplyCode, ErrorWithSignalCode, ErrorWithTwoHashes, Gas, Hash,
Expand Down Expand Up @@ -1317,21 +1318,35 @@ where
})
}

pub fn out_of_gas(_gas: Gas) -> impl Syscall<Ext> {
RawSyscall::new(|ctx: &mut CallerWrap<Ext>| {
let ext = ctx.ext_mut();
let current_counter = ext.current_counter_type();
log::trace!(target: "syscalls", "[out_of_gas] Current counter in global represents {current_counter:?}");
fn out_of_gas(ctx: &mut CallerWrap<Ext>) -> UndefinedTerminationReason {
let ext = ctx.ext_mut();
let current_counter = ext.current_counter_type();
log::trace!(target: "syscalls", "system_break(OutOfGas): Current counter in global represents {current_counter:?}");

if current_counter == CounterType::GasAllowance {
// We manually decrease it to 0 because global won't be affected
// since it didn't pass comparison to argument of `gas_charge()`
ext.decrease_current_counter_to(0);
}
if current_counter == CounterType::GasAllowance {
// We manually decrease it to 0 because global won't be affected
// since it didn't pass comparison to argument of `gas_charge()`
ext.decrease_current_counter_to(0);
}

ActorTerminationReason::from(current_counter).into()
}

let termination_reason: ActorTerminationReason = current_counter.into();
fn stack_limit_exceeded() -> UndefinedTerminationReason {
TrapExplanation::StackLimitExceeded.into()
}

ctx.set_termination_reason(termination_reason.into());
pub fn system_break(_gas: Gas, code: u32) -> impl Syscall<Ext> {
RawSyscall::new(move |ctx: &mut CallerWrap<Ext>| {
// At the instrumentation level, we can only use variants of the `SystemBreakCode`,
// so we should never reach `unreachable!("{e}")`.
let termination_reason = SystemBreakCode::try_from(code)
.map(|system_break_code| match system_break_code {
SystemBreakCode::OutOfGas => Self::out_of_gas(ctx),
SystemBreakCode::StackLimitExceeded => Self::stack_limit_exceeded(),
})
.unwrap_or_else(|e| unreachable!("{e}"));
ctx.set_termination_reason(termination_reason);
Err(HostError)
})
}
Expand Down
8 changes: 5 additions & 3 deletions core-backend/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -607,9 +607,8 @@ fn test_syscalls_table() {
use gear_core::message::DispatchKind;
use gear_wasm_instrument::{
gas_metering::ConstantCostRules,
inject,
parity_wasm::{self, builder},
SyscallName,
InstrumentationBuilder, SyscallName,
};

// Make module with one empty function.
Expand Down Expand Up @@ -637,7 +636,10 @@ fn test_syscalls_table() {
.build();
}

let module = inject(module, &ConstantCostRules::default(), "env").unwrap();
let module = InstrumentationBuilder::new("env")
.with_gas_limiter(|_| ConstantCostRules::default())
.instrument(module)
.unwrap();
let code = module.into_bytes().unwrap();

// Execute wasm and check success.
Expand Down
9 changes: 9 additions & 0 deletions core-errors/src/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,10 @@ pub enum SimpleExecutionError {
#[display(fmt = "Program called WASM `unreachable` instruction")]
UnreachableInstruction = 4,

/// Program has reached stack limit while executing.
#[display(fmt = "Program reached stack limit")]
StackLimitExceeded = 5,

/// Unsupported reason of execution error.
/// Variant exists for backward compatibility.
#[default]
Expand All @@ -273,6 +277,7 @@ impl SimpleExecutionError {
b if Self::BackendError as u8 == b => Self::BackendError,
b if Self::UserspacePanic as u8 == b => Self::UserspacePanic,
b if Self::UnreachableInstruction as u8 == b => Self::UnreachableInstruction,
b if Self::StackLimitExceeded as u8 == b => Self::StackLimitExceeded,
_ => Self::Unsupported,
}
}
Expand Down Expand Up @@ -354,6 +359,7 @@ impl SignalCode {
Self::Execution(SimpleExecutionError::BackendError) => 102,
Self::Execution(SimpleExecutionError::MemoryOverflow) => 103,
Self::Execution(SimpleExecutionError::UnreachableInstruction) => 104,
Self::Execution(SimpleExecutionError::StackLimitExceeded) => 105,
Self::RemovedFromWaitlist => 200,
// Must be unreachable.
Self::Execution(SimpleExecutionError::Unsupported) => u32::MAX,
Expand All @@ -378,6 +384,9 @@ impl SignalCode {
v if Self::Execution(SimpleExecutionError::UnreachableInstruction).to_u32() == v => {
Self::Execution(SimpleExecutionError::UnreachableInstruction)
}
v if Self::Execution(SimpleExecutionError::StackLimitExceeded).to_u32() == v => {
Self::Execution(SimpleExecutionError::StackLimitExceeded)
}
v if Self::RemovedFromWaitlist.to_u32() == v => Self::RemovedFromWaitlist,
_ => return None,
};
Expand Down
1 change: 1 addition & 0 deletions core-processor/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,7 @@ impl ActorExecutionErrorReplyReason {
}
TrapExplanation::ProgramAllocOutOfBounds => SimpleExecutionError::MemoryOverflow,
TrapExplanation::Panic(_) => SimpleExecutionError::UserspacePanic,
TrapExplanation::StackLimitExceeded => SimpleExecutionError::StackLimitExceeded,
TrapExplanation::Unknown => SimpleExecutionError::UnreachableInstruction,
},
}
Expand Down
67 changes: 16 additions & 51 deletions core/src/code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,10 @@ use alloc::{collections::BTreeSet, vec, vec::Vec};
use gear_wasm_instrument::{
parity_wasm::{
self,
builder::ModuleBuilder,
elements::{ExportEntry, GlobalEntry, GlobalType, InitExpr, Instruction, Internal, Module},
},
wasm_instrument::{
self,
gas_metering::{ConstantCostRules, Rules},
},
STACK_END_EXPORT_NAME,
wasm_instrument::gas_metering::{ConstantCostRules, Rules},
InstrumentationBuilder, STACK_END_EXPORT_NAME,
};
use scale_info::{
scale::{Decode, Encode},
Expand Down Expand Up @@ -204,16 +200,9 @@ pub enum CodeError {
/// Error occurred during decoding original program code.
#[display(fmt = "The wasm bytecode is failed to be decoded")]
Decode,
/// Error occurred during injecting gas metering instructions.
///
/// This might be due to program contained unsupported/non-deterministic instructions
/// (floats, memory grow, etc.).
#[display(fmt = "Failed to inject instructions for gas metrics: may be in case \
program contains unsupported instructions (floats, memory grow, etc.)")]
GasInjection,
/// Error occurred during stack height instrumentation.
#[display(fmt = "Failed to set stack height limits")]
StackLimitInjection,
/// Error occurred during instrumentation WASM module.
#[display(fmt = "Failed to instrument WASM module")]
Instrumentation,
/// Error occurred during encoding instrumented program.
#[display(fmt = "Failed to encode instrumented program")]
Encode,
Expand Down Expand Up @@ -296,30 +285,14 @@ fn check_start_section(module: &Module) -> Result<(), CodeError> {
}
}

fn export_stack_height(module: Module) -> Module {
let globals = module
.global_section()
.expect("Global section must be create by `inject_stack_limiter` before")
.entries()
.len();
ModuleBuilder::new()
.with_module(module)
.export()
.field("__gear_stack_height")
.internal()
.global(globals as u32 - 1)
.build()
.build()
}

/// Configuration for `Code::try_new_mock_`.
/// By default all checks enabled.
pub struct TryNewCodeConfig {
/// Instrumentation version
pub version: u32,
/// Stack height limit
pub stack_height: Option<u32>,
/// Export `__gear_stack_height` global
/// Export `STACK_HEIGHT_EXPORT_NAME` global
pub export_stack_height: bool,
/// Check exports (wasm contains init or handle exports)
pub check_exports: bool,
Expand Down Expand Up @@ -417,29 +390,21 @@ impl Code {
return Err(CodeError::RequiredExportFnNotFound);
}

if let Some(stack_limit) = config.stack_height {
let globals = config.export_stack_height.then(|| module.globals_space());

module = wasm_instrument::inject_stack_limiter(module, stack_limit).map_err(|err| {
log::trace!("Failed to inject stack height limits: {err}");
CodeError::StackLimitInjection
})?;

if let Some(globals_before) = globals {
// ensure stack limiter injector has created global
let globals_after = module.globals_space();
assert_eq!(globals_after, globals_before + 1);
let mut instrumentation_builder = InstrumentationBuilder::new("env");

module = export_stack_height(module);
}
if let Some(stack_limit) = config.stack_height {
instrumentation_builder.with_stack_limiter(stack_limit, config.export_stack_height);
}

if let Some(mut get_gas_rules) = get_gas_rules {
let gas_rules = get_gas_rules(&module);
module = gear_wasm_instrument::inject(module, &gas_rules, "env")
.map_err(|_| CodeError::GasInjection)?;
if let Some(get_gas_rules) = get_gas_rules {
instrumentation_builder.with_gas_limiter(get_gas_rules);
}

module = instrumentation_builder.instrument(module).map_err(|err| {
log::trace!("Failed to instrument program: {err}");
CodeError::Instrumentation
})?;

let code = parity_wasm::elements::serialize(module).map_err(|err| {
log::trace!("Failed to encode instrumented program: {err}");
CodeError::Encode
Expand Down
8 changes: 4 additions & 4 deletions examples/signal-entry/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ homepage.workspace = true
repository.workspace = true

[dependencies]
gstd = { workspace = true, features = [
"debug",
] } # debug is used here, because `signal_backend_error_invalid_debug_works` test in `pallet-gear` requires it to be working correctly in release mode
gcore.workspace = true
gstd.workspace = true
parity-scale-codec.workspace = true
gear-core.workspace = true

Expand All @@ -22,5 +21,6 @@ gtest.workspace = true

[features]
debug = ["gstd/debug"]
std = []
wasm-wrapper = []
std = ["wasm-wrapper", "parity-scale-codec/std"]
default = ["std"]
6 changes: 5 additions & 1 deletion examples/signal-entry/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

use gear_wasm_builder::WasmBuilder;

fn main() {
gear_wasm_builder::build();
WasmBuilder::new()
.exclude_features(vec!["std", "wasm-wrapper"])
.build();
}
10 changes: 6 additions & 4 deletions examples/signal-entry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,16 @@

#![cfg_attr(not(feature = "std"), no_std)]

use gstd::errors::SignalCode;
// We can't depend on gstd because it declares panic handler, so we just use gcore.
use gcore::errors::SignalCode;
use parity_scale_codec::{Decode, Encode};

#[cfg(feature = "std")]
#[cfg(feature = "wasm-wrapper")]
mod code {
include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
}

#[cfg(feature = "std")]
#[cfg(feature = "wasm-wrapper")]
pub use code::WASM_BINARY_OPT as WASM_BINARY;

#[derive(Debug, Encode, Decode)]
Expand All @@ -48,6 +49,7 @@ pub enum HandleAction {
ForbiddenAction,
SaveSignal(SignalCode),
ExceedMemory,
ExceedStackLimit,
UnreachableInstruction,
InvalidDebugCall,
UnrecoverableExt,
Expand All @@ -58,7 +60,7 @@ pub enum HandleAction {

pub const WAIT_AND_RESERVE_WITH_PANIC_GAS: u64 = 10_000_000_000;

#[cfg(not(feature = "std"))]
#[cfg(not(feature = "wasm-wrapper"))]
mod wasm;

#[cfg(test)]
Expand Down
Loading

0 comments on commit d285152

Please sign in to comment.