-
Notifications
You must be signed in to change notification settings - Fork 236
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improving spawn and exec syscalls #4785
base: develop
Are you sure you want to change the base?
Changes from all commits
87f41c9
ed787ce
34676ba
872809a
98c41ab
007704d
41a154e
6429889
ac0c2b0
74fd84b
cf2d2ef
5136613
9136fbc
b8a6620
4672878
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,15 @@ | ||
use crate::cost_model::transferred_byte_cycles; | ||
use crate::syscalls::{ | ||
INVALID_FD, MAX_FDS_CREATED, MAX_VMS_SPAWNED, OTHER_END_CLOSED, SPAWN_EXTRA_CYCLES_BASE, | ||
SUCCESS, WAIT_FAILURE, | ||
EXEC_LOAD_ELF_V2_CYCLES_BASE, INVALID_FD, MAX_FDS_CREATED, MAX_VMS_SPAWNED, OTHER_END_CLOSED, | ||
SPAWN_EXTRA_CYCLES_BASE, SUCCESS, WAIT_FAILURE, | ||
}; | ||
use crate::types::MachineContext; | ||
use crate::verify::TransactionScriptsSyscallsGenerator; | ||
use crate::ScriptVersion; | ||
|
||
use crate::types::{ | ||
CoreMachineType, DataPieceId, Fd, FdArgs, FullSuspendedState, Machine, Message, ReadState, | ||
RunMode, TxData, VmId, VmState, WriteState, FIRST_FD_SLOT, FIRST_VM_ID, | ||
CoreMachineType, DataLocation, DataPieceId, Fd, FdArgs, FullSuspendedState, Machine, Message, | ||
ReadState, RunMode, TxData, VmId, VmState, WriteState, FIRST_FD_SLOT, FIRST_VM_ID, | ||
}; | ||
use ckb_traits::{CellDataProvider, ExtensionProvider, HeaderProvider}; | ||
use ckb_types::core::Cycle; | ||
|
@@ -22,7 +22,7 @@ use ckb_vm::{ | |
memory::Memory, | ||
registers::A0, | ||
snapshot2::Snapshot2, | ||
Error, Register, | ||
Error, FlattenedArgsReader, Register, | ||
}; | ||
use std::collections::{BTreeMap, HashMap}; | ||
use std::sync::{Arc, Mutex}; | ||
|
@@ -208,7 +208,14 @@ where | |
if self.states.is_empty() { | ||
// Booting phase, we will need to initialize the first VM. | ||
assert_eq!( | ||
self.boot_vm(&DataPieceId::Program, 0, u64::MAX, &[])?, | ||
self.boot_vm( | ||
&DataLocation { | ||
data_piece_id: DataPieceId::Program, | ||
offset: 0, | ||
length: u64::MAX, | ||
}, | ||
None | ||
)?, | ||
ROOT_VM_ID | ||
); | ||
} | ||
|
@@ -340,6 +347,39 @@ where | |
let messages: Vec<Message> = self.message_box.lock().expect("lock").drain(..).collect(); | ||
for message in messages { | ||
match message { | ||
Message::ExecV2(vm_id, args) => { | ||
let (old_context, old_machine) = self | ||
.instantiated | ||
.get_mut(&vm_id) | ||
.ok_or_else(|| Error::Unexpected("Unable to find VM Id".to_string()))?; | ||
old_machine | ||
.machine | ||
.add_cycles_no_checking(EXEC_LOAD_ELF_V2_CYCLES_BASE)?; | ||
let old_cycles = old_machine.machine.cycles(); | ||
let max_cycles = old_machine.machine.max_cycles(); | ||
let program = { | ||
let mut sc = old_context.snapshot2_context().lock().expect("lock"); | ||
sc.load_data( | ||
&args.location.data_piece_id, | ||
args.location.offset, | ||
args.location.length, | ||
)? | ||
.0 | ||
}; | ||
let (context, mut new_machine) = self.create_dummy_vm(&vm_id)?; | ||
new_machine.set_max_cycles(max_cycles); | ||
new_machine.machine.add_cycles_no_checking(old_cycles)?; | ||
self.load_vm_program( | ||
&context, | ||
&mut new_machine, | ||
&args.location, | ||
program, | ||
Some((vm_id, args.argc, args.argv)), | ||
)?; | ||
// The insert operation removes the old vm instance and adds the new vm instance. | ||
debug_assert!(self.instantiated.contains_key(&vm_id)); | ||
self.instantiated.insert(vm_id, (context, new_machine)); | ||
} | ||
Message::Spawn(vm_id, args) => { | ||
// All fds must belong to the correct owner | ||
if args.fds.iter().any(|fd| self.fds.get(fd) != Some(&vm_id)) { | ||
|
@@ -353,7 +393,7 @@ where | |
continue; | ||
} | ||
let spawned_vm_id = | ||
self.boot_vm(&args.data_piece_id, args.offset, args.length, &args.argv)?; | ||
self.boot_vm(&args.location, Some((vm_id, args.argc, args.argv)))?; | ||
// Move passed fds from spawner to spawnee | ||
for fd in &args.fds { | ||
self.fds.insert(*fd, spawned_vm_id); | ||
|
@@ -746,11 +786,17 @@ where | |
/// Boot a vm by given program and args. | ||
pub fn boot_vm( | ||
&mut self, | ||
data_piece_id: &DataPieceId, | ||
offset: u64, | ||
length: u64, | ||
args: &[Bytes], | ||
location: &DataLocation, | ||
args: Option<(u64, u64, u64)>, | ||
) -> Result<VmId, Error> { | ||
let id = self.next_vm_id; | ||
self.next_vm_id += 1; | ||
let (context, mut machine) = self.create_dummy_vm(&id)?; | ||
let (program, _) = { | ||
let mut sc = context.snapshot2_context().lock().expect("lock"); | ||
sc.load_data(&location.data_piece_id, location.offset, location.length)? | ||
}; | ||
self.load_vm_program(&context, &mut machine, location, program, args)?; | ||
// Newly booted VM will be instantiated by default | ||
while self.instantiated.len() >= MAX_INSTANTIATED_VMS { | ||
// Instantiated is a BTreeMap, first_entry will maintain key order | ||
|
@@ -761,26 +807,43 @@ where | |
.key(); | ||
self.suspend_vm(&id)?; | ||
} | ||
|
||
let id = self.next_vm_id; | ||
self.next_vm_id += 1; | ||
let (context, mut machine) = self.create_dummy_vm(&id)?; | ||
{ | ||
let mut sc = context.snapshot2_context().lock().expect("lock"); | ||
let (program, _) = sc.load_data(data_piece_id, offset, length)?; | ||
let metadata = parse_elf::<u64>(&program, machine.machine.version())?; | ||
let bytes = machine.load_program_with_metadata(&program, &metadata, args)?; | ||
sc.mark_program(&mut machine.machine, &metadata, data_piece_id, offset)?; | ||
machine | ||
.machine | ||
.add_cycles_no_checking(transferred_byte_cycles(bytes))?; | ||
} | ||
self.instantiated.insert(id, (context, machine)); | ||
self.states.insert(id, VmState::Runnable); | ||
|
||
Ok(id) | ||
} | ||
|
||
// Load the program into an empty vm. | ||
fn load_vm_program( | ||
&mut self, | ||
context: &MachineContext<DL>, | ||
machine: &mut Machine, | ||
location: &DataLocation, | ||
program: Bytes, | ||
args: Option<(u64, u64, u64)>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about creating a new struct: struct VmArgs {
vm_id: u64,
argc: u64,
argv: u64,
} to replace the anonymouse turple There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the previous internal review, Xuejie suggested that I use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure either. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did re-check offline communication logs, previously this is represented using the following structure:
Since the enum only had 2 variant: one that has 3 arguments, while the other is empty. I just believe that this 2-value enum, could in fact simply be an I have no opinion whether it is |
||
) -> Result<u64, Error> { | ||
let metadata = parse_elf::<u64>(&program, machine.machine.version())?; | ||
let bytes = match args { | ||
Some((vm_id, argc, argv)) => { | ||
let (_, machine_from) = self.ensure_get_instantiated(&vm_id)?; | ||
let argv = FlattenedArgsReader::new(machine_from.machine.memory_mut(), argc, argv); | ||
machine.load_program_with_metadata(&program, &metadata, argv)? | ||
} | ||
None => machine.load_program_with_metadata(&program, &metadata, vec![].into_iter())?, | ||
}; | ||
let mut sc = context.snapshot2_context().lock().expect("lock"); | ||
sc.mark_program( | ||
&mut machine.machine, | ||
&metadata, | ||
&location.data_piece_id, | ||
location.offset, | ||
)?; | ||
machine | ||
.machine | ||
.add_cycles_no_checking(transferred_byte_cycles(bytes))?; | ||
Ok(bytes) | ||
} | ||
|
||
// Create a new VM instance with syscalls attached | ||
fn create_dummy_vm(&self, id: &VmId) -> Result<(MachineContext<DL>, Machine), Error> { | ||
// The code here looks slightly weird, since I don't want to copy over all syscall | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
use crate::syscalls::{EXEC, INDEX_OUT_OF_BOUND}; | ||
use crate::types::{DataLocation, DataPieceId, ExecV2Args, Message, VmId}; | ||
use ckb_vm::{ | ||
registers::{A0, A1, A2, A3, A4, A5, A7}, | ||
Error as VMError, Register, SupportMachine, Syscalls, | ||
}; | ||
use std::sync::{Arc, Mutex}; | ||
|
||
pub struct ExecV2 { | ||
id: VmId, | ||
message_box: Arc<Mutex<Vec<Message>>>, | ||
} | ||
|
||
impl ExecV2 { | ||
pub fn new(id: VmId, message_box: Arc<Mutex<Vec<Message>>>) -> ExecV2 { | ||
ExecV2 { id, message_box } | ||
} | ||
} | ||
|
||
impl<Mac> Syscalls<Mac> for ExecV2 | ||
where | ||
Mac: SupportMachine, | ||
{ | ||
fn initialize(&mut self, _machine: &mut Mac) -> Result<(), VMError> { | ||
Ok(()) | ||
} | ||
|
||
fn ecall(&mut self, machine: &mut Mac) -> Result<bool, VMError> { | ||
if machine.registers()[A7].to_u64() != EXEC { | ||
return Ok(false); | ||
} | ||
let index = machine.registers()[A0].to_u64(); | ||
let source = machine.registers()[A1].to_u64(); | ||
let place = machine.registers()[A2].to_u64(); | ||
let data_piece_id = match DataPieceId::try_from((source, index, place)) { | ||
Ok(id) => id, | ||
Err(_) => { | ||
machine.set_register(A0, Mac::REG::from_u8(INDEX_OUT_OF_BOUND)); | ||
return Ok(true); | ||
} | ||
}; | ||
let bounds = machine.registers()[A3].to_u64(); | ||
let offset = bounds >> 32; | ||
let length = bounds as u32 as u64; | ||
|
||
let argc = machine.registers()[A4].to_u64(); | ||
let argv = machine.registers()[A5].to_u64(); | ||
self.message_box | ||
.lock() | ||
.map_err(|e| VMError::Unexpected(e.to_string()))? | ||
.push(Message::ExecV2( | ||
self.id, | ||
ExecV2Args { | ||
location: DataLocation { | ||
data_piece_id, | ||
offset, | ||
length, | ||
}, | ||
argc, | ||
argv, | ||
}, | ||
)); | ||
Err(VMError::Yield) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please cap the number of maximum instantiated virtual machines here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exec will always remove an old running vm instance and then join a new one, so the maximum number of instantiations will not be triggered.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I see, in that sense this code is correct, but can we change the code to the following flow to better illustrate this fact:
The latter insert line can be quite confusing and maybe one day we forgot about this underlying logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this way of writing,
machine_n_context
borrowsself
as mutable, This will causeself.create_dummy_vm
andself.load_vm_program
to no longer be usableThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, you are right here. In this sence I recommend the following line as a hint:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK. I'll add a comment as well.