Skip to content

Commit bd4e01b

Browse files
committed
refactor: send (kernel & syscall)
This combines two changes that are somewhat interlinked. Really, they're just hard to factor out into two different patches without a bunch of work. Motivation: 1. Remove copying costs from send/return. 2. Avoid calling "syscall" kernel methods in the call manager. 3. I _really_ hated that `Kernel::block_get` method. 4. Remove an extra call on send to "stat" the returned object. ---- 1. In the kernel, the `send` function now takes/returns a block ID. This moves all the parameter-relates logic into the kernel itself. Otherwise, the syscall has to make three kernel calls, all of which charge gas. 2. In the call manager, instead of passing return values & parameters as raw bytes, we pass them as `Option<kernel::Block>` where `kernel::Block` is a reference counted IPLD block (with a codec). This, let's us pass them around without copying, and lets us be very clear about codecs, empty parameters, etc. 3. In the syscalls, return the block length/codec from send to avoid a second syscall _just_ to look those up.
1 parent 90c35bb commit bd4e01b

File tree

13 files changed

+174
-124
lines changed

13 files changed

+174
-124
lines changed

fvm/src/call_manager/default.rs

Lines changed: 45 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
use anyhow::Context;
22
use derive_more::{Deref, DerefMut};
3-
use fvm_ipld_encoding::{RawBytes, DAG_CBOR};
3+
use fvm_ipld_encoding::{to_vec, DAG_CBOR};
44
use fvm_shared::actor::builtin::Type;
55
use fvm_shared::address::{Address, Protocol};
66
use fvm_shared::econ::TokenAmount;
77
use fvm_shared::error::{ErrorNumber, ExitCode};
8+
use fvm_shared::sys::BlockId;
89
use fvm_shared::{ActorID, MethodNum, METHOD_SEND};
910
use num_traits::Zero;
1011

1112
use super::{Backtrace, CallManager, InvocationResult, NO_DATA_BLOCK_ID};
1213
use crate::call_manager::backtrace::Frame;
1314
use crate::call_manager::FinishRet;
1415
use crate::gas::GasTracker;
15-
use crate::kernel::{ExecutionError, Kernel, Result, SyscallError};
16+
use crate::kernel::{Block, BlockRegistry, ExecutionError, Kernel, Result, SyscallError};
1617
use crate::machine::Machine;
1718
use crate::syscalls::error::Abort;
1819
use crate::syscalls::{charge_for_exec, update_gas_available};
@@ -86,7 +87,7 @@ where
8687
from: ActorID,
8788
to: Address,
8889
method: MethodNum,
89-
params: &RawBytes,
90+
params: Option<Block>,
9091
value: &TokenAmount,
9192
) -> Result<InvocationResult>
9293
where
@@ -97,7 +98,10 @@ where
9798
from,
9899
to,
99100
method,
100-
params: params.clone(),
101+
params: params
102+
.as_ref()
103+
.map(|blk| blk.data().to_owned().into())
104+
.unwrap_or_default(),
101105
value: value.clone(),
102106
}));
103107
}
@@ -233,15 +237,15 @@ where
233237

234238
// Now invoke the constructor; first create the parameters, then
235239
// instantiate a new kernel to invoke the constructor.
236-
let params = RawBytes::serialize(&addr)
240+
let params = to_vec(&addr)
237241
// TODO(#198) this should be a Sys actor error, but we're copying lotus here.
238242
.map_err(|e| syscall_error!(Serialization; "failed to serialize params: {}", e))?;
239243

240244
self.send_resolved::<K>(
241245
account_actor::SYSTEM_ACTOR_ID,
242246
id,
243247
fvm_shared::METHOD_CONSTRUCTOR,
244-
&params,
248+
Some(Block::new(DAG_CBOR, params)),
245249
&TokenAmount::from(0u32),
246250
)?;
247251

@@ -254,7 +258,7 @@ where
254258
from: ActorID,
255259
to: Address,
256260
method: MethodNum,
257-
params: &RawBytes,
261+
params: Option<Block>,
258262
value: &TokenAmount,
259263
) -> Result<InvocationResult>
260264
where
@@ -284,7 +288,7 @@ where
284288
from: ActorID,
285289
to: ActorID,
286290
method: MethodNum,
287-
params: &RawBytes,
291+
params: Option<Block>,
288292
value: &TokenAmount,
289293
) -> Result<InvocationResult>
290294
where
@@ -310,31 +314,29 @@ where
310314
return Ok(InvocationResult::Return(Default::default()));
311315
}
312316

317+
// Store the parametrs, and initialize the block registry for the target actor.
318+
// TODO: In M2, the block registry may have some form of shared block limit.
319+
let mut block_registry = BlockRegistry::new();
320+
let params_id = if let Some(blk) = params {
321+
block_registry.put(blk)?
322+
} else {
323+
NO_DATA_BLOCK_ID
324+
};
325+
313326
// This is a cheap operation as it doesn't actually clone the struct,
314327
// it returns a referenced copy.
315328
let engine = self.engine().clone();
316329

317330
log::trace!("calling {} -> {}::{}", from, to, method);
318331
self.map_mut(|cm| {
319332
// Make the kernel.
320-
let mut kernel = K::new(cm, from, to, method, value.clone());
321-
322-
// Store parameters, if any.
323-
let param_id = if params.len() > 0 {
324-
match kernel.block_create(DAG_CBOR, params) {
325-
Ok(id) => id,
326-
// This could fail if we pass some global memory limit.
327-
Err(err) => return (Err(err), kernel.into_call_manager()),
328-
}
329-
} else {
330-
super::NO_DATA_BLOCK_ID
331-
};
333+
let kernel = K::new(cm, block_registry, from, to, method, value.clone());
332334

333335
// Make a store.
334336
let mut store = engine.new_store(kernel);
335337

336338
// From this point on, there are no more syscall errors, only aborts.
337-
let result: std::result::Result<RawBytes, Abort> = (|| {
339+
let result: std::result::Result<BlockId, Abort> = (|| {
338340
// Instantiate the module.
339341
let instance = engine
340342
.get_instance(&mut store, &state.code)
@@ -358,39 +360,40 @@ where
358360
update_gas_available(&mut store)?;
359361

360362
// Invoke it.
361-
let res = invoke.call(&mut store, (param_id,));
363+
let res = invoke.call(&mut store, (params_id,));
362364

363365
// Charge for any remaining uncharged execution gas, returning an error if we run
364366
// out.
365367
charge_for_exec(&mut store)?;
366368

367-
// If the invocation failed due to running out of exec_units, we have already detected it and returned OutOfGas above.
368-
// Any other invocation failure is returned here as an Abort
369-
let return_block_id = res?;
370-
371-
// Extract the return value, if there is one.
372-
let return_value: RawBytes = if return_block_id > NO_DATA_BLOCK_ID {
373-
let (code, ret) = store
374-
.data_mut()
375-
.kernel
376-
.block_get(return_block_id)
377-
.map_err(|e| Abort::from_error(ExitCode::SYS_MISSING_RETURN, e))?;
378-
debug_assert_eq!(code, DAG_CBOR);
379-
RawBytes::new(ret)
380-
} else {
381-
RawBytes::default()
382-
};
383-
384-
Ok(return_value)
369+
// If the invocation failed due to running out of exec_units, we have already
370+
// detected it and returned OutOfGas above. Any other invocation failure is returned
371+
// here as an Abort
372+
Ok(res?)
385373
})();
386374

387375
let invocation_data = store.into_data();
388376
let last_error = invocation_data.last_error;
389-
let mut cm = invocation_data.kernel.into_call_manager();
377+
let (mut cm, block_registry) = invocation_data.kernel.into_inner();
378+
379+
// Resolve the return block's ID into an actual block, converting to an abort if it
380+
// doesn't exist.
381+
let result = result.and_then(|ret_id| {
382+
Ok(if ret_id == NO_DATA_BLOCK_ID {
383+
None
384+
} else {
385+
Some(block_registry.get(ret_id).map_err(|_| {
386+
Abort::Exit(
387+
ExitCode::SYS_MISSING_RETURN,
388+
String::from("returned block does not exist"),
389+
)
390+
})?)
391+
})
392+
});
390393

391394
// Process the result, updating the backtrace if necessary.
392395
let ret = match result {
393-
Ok(value) => Ok(InvocationResult::Return(value)),
396+
Ok(ret) => Ok(InvocationResult::Return(ret.cloned())),
394397
Err(abort) => {
395398
if let Some(err) = last_error {
396399
cm.backtrace.begin(err);

fvm/src/call_manager/mod.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
use fvm_ipld_encoding::RawBytes;
21
use fvm_shared::address::Address;
32
use fvm_shared::econ::TokenAmount;
43
use fvm_shared::error::ExitCode;
54
use fvm_shared::{ActorID, MethodNum};
65

76
use crate::gas::{GasCharge, GasTracker, PriceList};
8-
use crate::kernel::Result;
7+
use crate::kernel::{self, Result};
98
use crate::machine::{Machine, MachineContext};
109
use crate::state_tree::StateTree;
1110
use crate::Kernel;
@@ -52,7 +51,7 @@ pub trait CallManager: 'static {
5251
from: ActorID,
5352
to: Address,
5453
method: MethodNum,
55-
params: &RawBytes,
54+
params: Option<kernel::Block>,
5655
value: &TokenAmount,
5756
) -> Result<InvocationResult>;
5857

@@ -125,7 +124,7 @@ pub trait CallManager: 'static {
125124
#[derive(Clone, Debug)]
126125
pub enum InvocationResult {
127126
/// Indicates that the actor successfully returned. The value may be empty.
128-
Return(RawBytes),
127+
Return(Option<kernel::Block>),
129128
/// Indicates that the actor aborted with the given exit code.
130129
Failure(ExitCode),
131130
}

fvm/src/executor/default.rs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::result::Result as StdResult;
33

44
use anyhow::{anyhow, Result};
55
use cid::Cid;
6+
use fvm_ipld_encoding::{RawBytes, DAG_CBOR};
67
use fvm_shared::actor::builtin::Type;
78
use fvm_shared::address::Address;
89
use fvm_shared::bigint::{BigInt, Sign};
@@ -16,7 +17,7 @@ use num_traits::Zero;
1617
use super::{ApplyFailure, ApplyKind, ApplyRet, Executor};
1718
use crate::call_manager::{backtrace, CallManager, InvocationResult};
1819
use crate::gas::{milligas_to_gas, GasCharge, GasOutputs};
19-
use crate::kernel::{ClassifyResult, Context as _, ExecutionError, Kernel};
20+
use crate::kernel::{Block, ClassifyResult, Context as _, ExecutionError, Kernel};
2021
use crate::machine::{Machine, BURNT_FUNDS_ACTOR_ADDR, REWARD_ACTOR_ADDR};
2122

2223
/// The default [`Executor`].
@@ -67,14 +68,21 @@ where
6768
return (Err(e), cm.finish().1);
6869
}
6970

71+
let params = if msg.params.is_empty() {
72+
None
73+
} else {
74+
Some(Block::new(DAG_CBOR, msg.params.bytes()))
75+
};
76+
7077
let result = cm.with_transaction(|cm| {
7178
// Invoke the message.
72-
let ret =
73-
cm.send::<K>(sender_id, msg.to, msg.method_num, &msg.params, &msg.value)?;
79+
let ret = cm.send::<K>(sender_id, msg.to, msg.method_num, params, &msg.value)?;
7480

7581
// Charge for including the result (before we end the transaction).
76-
if let InvocationResult::Return(data) = &ret {
77-
cm.charge_gas(cm.context().price_list.on_chain_return_value(data.len()))?;
82+
if let InvocationResult::Return(value) = &ret {
83+
cm.charge_gas(cm.context().price_list.on_chain_return_value(
84+
value.as_ref().map(|v| v.size() as usize).unwrap_or(0),
85+
))?;
7886
}
7987

8088
Ok(ret)
@@ -88,7 +96,13 @@ where
8896

8997
// Extract the exit code and build the result of the message application.
9098
let receipt = match res {
91-
Ok(InvocationResult::Return(return_data)) => {
99+
Ok(InvocationResult::Return(return_value)) => {
100+
// Convert back into a top-level return "value". We throw away the codec here,
101+
// unfortunately.
102+
let return_data = return_value
103+
.map(|blk| RawBytes::from(blk.data().to_vec()))
104+
.unwrap_or_default();
105+
92106
backtrace.clear();
93107
Receipt {
94108
exit_code: ExitCode::OK,

fvm/src/kernel/blocks.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::convert::TryInto;
2+
use std::rc::Rc;
23

34
use fvm_ipld_encoding::DAG_CBOR;
45
use thiserror::Error;
@@ -7,7 +8,7 @@ use super::{ExecutionError, SyscallError};
78
use crate::syscall_error;
89

910
#[derive(Default)]
10-
pub(crate) struct BlockRegistry {
11+
pub struct BlockRegistry {
1112
blocks: Vec<Block>,
1213
}
1314

@@ -25,18 +26,22 @@ pub struct BlockStat {
2526
pub size: u32,
2627
}
2728

28-
#[derive(Clone)]
29+
#[derive(Debug, Clone)]
2930
pub struct Block {
3031
codec: u64,
31-
data: Box<[u8]>,
32+
// Unfortunately, we usually start with a vector/boxed buffer. If we used Rc<[u8]>, we'd have to
33+
// copy the bytes. So we accept some indirection for reliable performance.
34+
#[allow(clippy::redundant_allocation)]
35+
data: Rc<Box<[u8]>>,
3236
}
3337

3438
impl Block {
3539
pub fn new(codec: u64, data: impl Into<Box<[u8]>>) -> Self {
36-
// TODO: check size on the way in?
40+
// This requires an extra allocation (ew) but no extra copy on send.
41+
// The extra allocation is basically nothing.
3742
Self {
3843
codec,
39-
data: data.into(),
44+
data: Rc::new(data.into()),
4045
}
4146
}
4247

0 commit comments

Comments
 (0)