Skip to main content

ckb_script/
scheduler.rs

1use crate::cost_model::transferred_byte_cycles;
2use crate::syscalls::{
3    EXEC_LOAD_ELF_V2_CYCLES_BASE, INVALID_FD, MAX_FDS_CREATED, MAX_VMS_SPAWNED, OTHER_END_CLOSED,
4    SPAWN_EXTRA_CYCLES_BASE, SUCCESS, WAIT_FAILURE,
5};
6
7use crate::types::{
8    DataLocation, DataPieceId, FIRST_FD_SLOT, FIRST_VM_ID, Fd, FdArgs, IterationResult, Message,
9    ReadState, RunMode, SgData, SyscallGenerator, TerminatedResult, VmArgs, VmContext, VmId,
10    VmState, WriteState,
11};
12use ckb_traits::{CellDataProvider, ExtensionProvider, HeaderProvider};
13use ckb_types::core::Cycle;
14use ckb_vm::snapshot2::Snapshot2Context;
15use ckb_vm::{
16    Error, FlattenedArgsReader, Register,
17    bytes::Bytes,
18    cost_model::estimate_cycles,
19    elf::parse_elf,
20    machine::{CoreMachine, DefaultMachineBuilder, DefaultMachineRunner, Pause, SupportMachine},
21    memory::Memory,
22    registers::A0,
23    snapshot2::Snapshot2,
24};
25use std::collections::{BTreeMap, HashMap};
26use std::sync::{
27    Arc, Mutex,
28    atomic::{AtomicU64, Ordering},
29};
30
31/// Root process's id.
32pub const ROOT_VM_ID: VmId = FIRST_VM_ID;
33/// The maximum number of VMs that can be created at the same time.
34pub const MAX_VMS_COUNT: u64 = 16;
35/// The maximum number of instantiated VMs.
36pub const MAX_INSTANTIATED_VMS: usize = 4;
37/// The maximum number of fds.
38pub const MAX_FDS: u64 = 64;
39
40/// A single Scheduler instance is used to verify a single script
41/// within a CKB transaction.
42///
43/// A scheduler holds & manipulates a core, the scheduler also holds
44/// all CKB-VM machines, each CKB-VM machine also gets a mutable reference
45/// of the core for IO operations.
46pub struct Scheduler<DL, V, M>
47where
48    DL: CellDataProvider,
49    M: DefaultMachineRunner,
50{
51    /// Immutable context data for current running transaction & script.
52    sg_data: SgData<DL>,
53
54    /// Syscall generator
55    syscall_generator: SyscallGenerator<DL, V, M::Inner>,
56    /// Syscall generator context
57    syscall_context: V,
58
59    /// Total cycles. When a scheduler executes, there are 3 variables
60    /// that might all contain charged cycles: +total_cycles+,
61    /// +iteration_cycles+ and +machine.cycles()+ from the current
62    /// executing virtual machine. At any given time, the sum of all 3
63    /// variables here, represent the total consumed cycles by the current
64    /// scheduler.
65    /// But there are also exceptions: at certain period of time, the cycles
66    /// stored in `machine.cycles()` are moved over to +iteration_cycles+,
67    /// the cycles stored in +iteration_cycles+ would also be moved over to
68    /// +total_cycles+:
69    ///
70    /// * The current running virtual machine would contain consumed
71    ///   cycles in its own machine.cycles() structure.
72    /// * +iteration_cycles+ holds the current consumed cycles each time
73    ///   we executed a virtual machine(also named an iteration). It will
74    ///   always be zero before each iteration(i.e., before each VM starts
75    ///   execution). When a virtual machine finishes execution, the cycles
76    ///   stored in `machine.cycles()` will be moved over to +iteration_cycles+.
77    ///   `machine.cycles()` will then be reset to zero.
78    /// * Processing messages in the message box would alao charge cycles
79    ///   for operations, such as suspending/resuming VMs, transferring data
80    ///   etc. Those cycles were added to +iteration_cycles+ directly. When all
81    ///   postprocessing work is completed, the cycles consumed in
82    ///   +iteration_cycles+ will then be moved to +total_cycles+.
83    ///   +iteration_cycles+ will then be reset to zero.
84    ///
85    /// One can consider that +total_cycles+ contains the total cycles
86    /// consumed in current scheduler, when the scheduler is not busy executing.
87    ///
88    /// NOTE: the above workflow describes the optimal case: `iteration_cycles`
89    /// will always be zero after each iteration. However, our initial implementation
90    /// for Meepo hardfork contains a bug: cycles charged by suspending / resuming
91    /// VMs when processing IOs, will not be reflected in `current cycles` syscalls
92    /// of the subsequent running VMs. To preserve this behavior, consumed cycles in
93    /// iteration_cycles cannot be moved at iterate boundaries. Later hardfork versions
94    /// might fix this, but for the Meepo hardfork, we will have to preserve this behavior.
95    total_cycles: Arc<AtomicU64>,
96    /// Iteration cycles, see +total_cycles+ on its usage
97    iteration_cycles: Cycle,
98    /// Next vm id used by spawn.
99    next_vm_id: VmId,
100    /// Next fd used by pipe.
101    next_fd_slot: u64,
102    /// Used to store VM state.
103    states: BTreeMap<VmId, VmState>,
104    /// Used to confirm the owner of fd.
105    fds: BTreeMap<Fd, VmId>,
106    /// Verify the VM's inherited fd list.
107    inherited_fd: BTreeMap<VmId, Vec<Fd>>,
108    /// Instantiated vms.
109    instantiated: BTreeMap<VmId, (VmContext<DL>, M)>,
110    /// Suspended vms.
111    suspended: BTreeMap<VmId, Snapshot2<DataPieceId>>,
112    /// Terminated vms.
113    terminated_vms: BTreeMap<VmId, i8>,
114    /// Root vm's arguments. Provided for compatibility with surrounding tools. You should not
115    /// read it anywhere except when initializing the root vm.
116    /// Note: This field is intentionally not serialized in FullSuspendedState.
117    root_vm_args: Vec<Bytes>,
118
119    /// MessageBox is expected to be empty before returning from `run`
120    /// function, there is no need to persist messages.
121    message_box: Arc<Mutex<Vec<Message>>>,
122}
123
124impl<DL, V, M> Scheduler<DL, V, M>
125where
126    DL: CellDataProvider + HeaderProvider + ExtensionProvider + Clone,
127    V: Clone,
128    M: DefaultMachineRunner,
129{
130    /// Create a new scheduler from empty state
131    pub fn new(
132        sg_data: SgData<DL>,
133        syscall_generator: SyscallGenerator<DL, V, M::Inner>,
134        syscall_context: V,
135    ) -> Self {
136        Self {
137            sg_data,
138            syscall_generator,
139            syscall_context,
140            total_cycles: Arc::new(AtomicU64::new(0)),
141            iteration_cycles: 0,
142            next_vm_id: FIRST_VM_ID,
143            next_fd_slot: FIRST_FD_SLOT,
144            states: BTreeMap::default(),
145            fds: BTreeMap::default(),
146            inherited_fd: BTreeMap::default(),
147            instantiated: BTreeMap::default(),
148            suspended: BTreeMap::default(),
149            message_box: Arc::new(Mutex::new(Vec::new())),
150            terminated_vms: BTreeMap::default(),
151            root_vm_args: Vec::new(),
152        }
153    }
154
155    /// Return total cycles.
156    pub fn consumed_cycles(&self) -> Cycle {
157        self.total_cycles.load(Ordering::Acquire)
158    }
159
160    /// Fetch specified VM state
161    pub fn state(&self, vm_id: &VmId) -> Option<VmState> {
162        self.states.get(vm_id).cloned()
163    }
164
165    /// Access the SgData data structure
166    pub fn sg_data(&self) -> &SgData<DL> {
167        &self.sg_data
168    }
169
170    /// This function provides a peek into one of the current created
171    /// VM. Depending on the actual state, the VM might either be instantiated
172    /// or suspended. As a result, 2 callback functions must be provided to handle
173    /// both cases. The function only provides a *peek*, meaning the caller must
174    /// not make any changes to an instantiated VMs. the VM is passed as a mutable
175    /// reference only because memory load functions in CKB-VM require mutable
176    /// references. It does not mean the caller can modify the VM in any sense.
177    /// Even a slight tampering of the VM can result in non-determinism.
178    pub fn peek<F, G, W>(&mut self, vm_id: &VmId, mut f: F, mut g: G) -> Result<W, Error>
179    where
180        F: FnMut(&mut M) -> Result<W, Error>,
181        G: FnMut(&Snapshot2<DataPieceId>, &SgData<DL>) -> Result<W, Error>,
182    {
183        if let Some((_, machine)) = self.instantiated.get_mut(vm_id) {
184            return f(machine);
185        }
186        if let Some(snapshot) = self.suspended.get(vm_id) {
187            return g(snapshot, &self.sg_data);
188        }
189        Err(Error::Unexpected(format!("VM {} does not exist!", vm_id)))
190    }
191
192    /// Add cycles to total cycles.
193    fn consume_cycles(&mut self, cycles: Cycle) -> Result<(), Error> {
194        match self
195            .total_cycles
196            .fetch_update(Ordering::AcqRel, Ordering::Acquire, |total_cycles| {
197                total_cycles.checked_add(cycles)
198            }) {
199            Ok(_) => Ok(()),
200            Err(_) => Err(Error::CyclesExceeded),
201        }
202    }
203
204    /// This is the only entrypoint for running the scheduler,
205    /// both newly created instance and resumed instance are supported.
206    /// It accepts 2 run mode, one can either limit the cycles to execute,
207    /// or use a pause signal to trigger termination.
208    ///
209    /// Only when the execution terminates without VM errors, will this
210    /// function return an exit code(could still be non-zero) and total
211    /// consumed cycles.
212    ///
213    /// Err would be returned in the following cases:
214    /// * Cycle limit reached, the returned error would be ckb_vm::Error::CyclesExceeded,
215    /// * Pause trigger, the returned error would be ckb_vm::Error::Pause,
216    /// * Other terminating errors
217    pub fn run(&mut self, mode: RunMode) -> Result<TerminatedResult, Error> {
218        self.boot_root_vm_if_needed()?;
219
220        let (pause, mut limit_cycles) = match mode {
221            RunMode::LimitCycles(limit_cycles) => (Pause::new(), limit_cycles),
222            RunMode::Pause(pause, limit_cycles) => (pause, limit_cycles),
223        };
224
225        while !self.terminated() {
226            limit_cycles = self.iterate_outer(&pause, limit_cycles)?.1;
227        }
228        assert_eq!(self.iteration_cycles, 0);
229
230        self.terminated_result()
231    }
232
233    /// Public API that runs a single VM, processes all messages, then returns the
234    /// executed VM ID(so caller can fetch later data). This can be used when more
235    /// finer tweaks are required for a single VM.
236    pub fn iterate(&mut self) -> Result<IterationResult, Error> {
237        self.boot_root_vm_if_needed()?;
238
239        if self.terminated() {
240            return Ok(IterationResult {
241                executed_vm: ROOT_VM_ID,
242                terminated_status: Some(self.terminated_result()?),
243            });
244        }
245
246        let (id, _) = self.iterate_outer(&Pause::new(), u64::MAX)?;
247        let terminated_status = if self.terminated() {
248            assert_eq!(self.iteration_cycles, 0);
249            Some(self.terminated_result()?)
250        } else {
251            None
252        };
253
254        Ok(IterationResult {
255            executed_vm: id,
256            terminated_status,
257        })
258    }
259
260    /// Returns the machine that needs to be executed in the current iterate.
261    fn iterate_prepare_machine(&mut self) -> Result<(u64, &mut M), Error> {
262        // Find a runnable VM that has the largest ID.
263        let vm_id_to_run = self
264            .states
265            .iter()
266            .rev()
267            .filter(|(_, state)| matches!(state, VmState::Runnable))
268            .map(|(id, _)| *id)
269            .next();
270        let vm_id_to_run = vm_id_to_run.ok_or_else(|| {
271            Error::Unexpected("A deadlock situation has been reached!".to_string())
272        })?;
273        let (_context, machine) = self.ensure_get_instantiated(&vm_id_to_run)?;
274        Ok((vm_id_to_run, machine))
275    }
276
277    /// Process machine execution results in the current iterate.
278    fn iterate_process_results(
279        &mut self,
280        vm_id_to_run: u64,
281        result: Result<i8, Error>,
282    ) -> Result<(), Error> {
283        // Process message box, update VM states accordingly
284        self.process_message_box()?;
285        assert!(self.message_box.lock().expect("lock").is_empty());
286        // If the VM terminates, update VMs in join state, also closes its fds
287
288        match result {
289            Ok(code) => {
290                self.terminated_vms.insert(vm_id_to_run, code);
291                // When root VM terminates, the execution stops immediately, we will purge
292                // all non-root VMs, and only keep root VM in states.
293                // When non-root VM terminates, we only purge the VM's own states.
294                if vm_id_to_run == ROOT_VM_ID {
295                    self.ensure_vms_instantiated(&[vm_id_to_run])?;
296                    self.instantiated.retain(|id, _| *id == vm_id_to_run);
297                    self.suspended.clear();
298                    self.states.clear();
299                    self.states.insert(vm_id_to_run, VmState::Terminated);
300                } else {
301                    let joining_vms: Vec<(VmId, u64)> = self
302                        .states
303                        .iter()
304                        .filter_map(|(vm_id, state)| match state {
305                            VmState::Wait {
306                                target_vm_id,
307                                exit_code_addr,
308                            } if *target_vm_id == vm_id_to_run => Some((*vm_id, *exit_code_addr)),
309                            _ => None,
310                        })
311                        .collect();
312                    // For all joining VMs, update exit code, then mark them as
313                    // runnable state.
314                    for (vm_id, exit_code_addr) in joining_vms {
315                        let (_, machine) = self.ensure_get_instantiated(&vm_id)?;
316                        machine
317                            .inner_mut()
318                            .memory_mut()
319                            .store8(&Self::u64_to_reg(exit_code_addr), &Self::i8_to_reg(code))?;
320                        machine
321                            .inner_mut()
322                            .set_register(A0, Self::u8_to_reg(SUCCESS));
323                        self.states.insert(vm_id, VmState::Runnable);
324                    }
325                    // Close fds
326                    self.fds.retain(|_, vm_id| *vm_id != vm_id_to_run);
327                    // Clear terminated VM states
328                    self.states.remove(&vm_id_to_run);
329                    self.instantiated.remove(&vm_id_to_run);
330                    self.suspended.remove(&vm_id_to_run);
331                }
332                Ok(())
333            }
334            Err(Error::Yield) => Ok(()),
335            Err(e) => Err(e),
336        }
337    }
338
339    // This internal function is actually a wrapper over +iterate_inner+,
340    // it is split into a different function, so cycle calculation will be
341    // executed no matter what result +iterate_inner+ returns.
342    #[inline]
343    fn iterate_outer(
344        &mut self,
345        pause: &Pause,
346        limit_cycles: Cycle,
347    ) -> Result<(VmId, Cycle), Error> {
348        let iterate_return = self.iterate_inner(pause.clone(), limit_cycles);
349        self.consume_cycles(self.iteration_cycles)?;
350        let remaining_cycles = limit_cycles
351            .checked_sub(self.iteration_cycles)
352            .ok_or(Error::CyclesExceeded)?;
353        // Clear iteration cycles intentionally after each run
354        self.iteration_cycles = 0;
355        // Process all pending VM reads & writes. Notice ideally, this invocation
356        // should be put at the end of `iterate_inner` function. However, 2 things
357        // prevent this:
358        //
359        // * In earlier implementation of the Meepo hardfork version, `self.process_io`
360        // was put at the very start of +iterate_prepare_machine+ method. Meaning we used
361        // to process IO syscalls at the very start of a new iteration.
362        // * Earlier implementation contains a bug that cycles consumed by suspending / resuming
363        // VMs are not updated in the subsequent VM's `current cycles` syscalls.
364        //
365        // To make ckb-script package suitable for outside usage, we want IOs processed at
366        // the end of each iteration, not at the start of the next iteration. We also need
367        // to replicate the exact same runtime behavior of Meepo hardfork. This means the only
368        // viable change will be:
369        //
370        // * Move `self.process_io` call to the very end of `iterate_outer` method, which is
371        // exactly current location
372        // * For now we have to live with the fact that `iteration_cycles` will not always be
373        // zero at iteration boundaries, and also preserve its value in `FullSuspendedState`.
374        //
375        // One expected change is that +process_io+ is now called once more
376        // after the whole scheduler terminates, and not called at the very beginning
377        // when no VM is executing. But since no VMs will be in IO states at this 2 timeslot,
378        // we should be fine here.
379        self.process_io()?;
380        let id = iterate_return?;
381        Ok((id, remaining_cycles))
382    }
383
384    // This is internal function that does the actual VM execution loop.
385    // Here both pause signal and limit_cycles are provided so as to simplify
386    // branches.
387    fn iterate_inner(&mut self, pause: Pause, limit_cycles: Cycle) -> Result<VmId, Error> {
388        // Execute the VM for real, consumed cycles in the virtual machine is
389        // moved over to +iteration_cycles+, then we reset virtual machine's own
390        // cycle count to zero.
391        let (id, result, cycles) = {
392            let (id, vm) = self.iterate_prepare_machine()?;
393            vm.inner_mut().set_max_cycles(limit_cycles);
394            vm.machine_mut().set_pause(pause);
395            let result = vm.run();
396            let cycles = vm.machine().cycles();
397            vm.inner_mut().set_cycles(0);
398            (id, result, cycles)
399        };
400        self.iteration_cycles = self
401            .iteration_cycles
402            .checked_add(cycles)
403            .ok_or(Error::CyclesExceeded)?;
404        self.iterate_process_results(id, result)?;
405        Ok(id)
406    }
407
408    fn process_message_box(&mut self) -> Result<(), Error> {
409        let messages: Vec<Message> = self.message_box.lock().expect("lock").drain(..).collect();
410        for message in messages {
411            match message {
412                Message::ExecV2(vm_id, args) => {
413                    let (old_context, old_machine) = self
414                        .instantiated
415                        .get_mut(&vm_id)
416                        .ok_or_else(|| Error::Unexpected("Unable to find VM Id".to_string()))?;
417                    old_machine
418                        .inner_mut()
419                        .add_cycles_no_checking(EXEC_LOAD_ELF_V2_CYCLES_BASE)?;
420                    let old_cycles = old_machine.machine().cycles();
421                    let max_cycles = old_machine.machine().max_cycles();
422                    let program = {
423                        let sc = old_context.snapshot2_context.lock().expect("lock");
424                        sc.load_data(
425                            &args.location.data_piece_id,
426                            args.location.offset,
427                            args.location.length,
428                        )?
429                        .0
430                    };
431                    let (context, mut new_machine) = self.create_dummy_vm(&vm_id)?;
432                    new_machine.inner_mut().set_max_cycles(max_cycles);
433                    new_machine.inner_mut().add_cycles_no_checking(old_cycles)?;
434                    self.load_vm_program(
435                        &context,
436                        &mut new_machine,
437                        &args.location,
438                        program,
439                        VmArgs::Reader {
440                            vm_id,
441                            argc: args.argc,
442                            argv: args.argv,
443                        },
444                    )?;
445                    // The insert operation removes the old vm instance and adds the new vm instance.
446                    debug_assert!(self.instantiated.contains_key(&vm_id));
447                    self.instantiated.insert(vm_id, (context, new_machine));
448                }
449                Message::Spawn(vm_id, args) => {
450                    // All fds must belong to the correct owner
451                    if args.fds.iter().any(|fd| self.fds.get(fd) != Some(&vm_id)) {
452                        let (_, machine) = self.ensure_get_instantiated(&vm_id)?;
453                        machine
454                            .inner_mut()
455                            .set_register(A0, Self::u8_to_reg(INVALID_FD));
456                        continue;
457                    }
458                    if self.suspended.len() + self.instantiated.len() > MAX_VMS_COUNT as usize {
459                        let (_, machine) = self.ensure_get_instantiated(&vm_id)?;
460                        machine
461                            .inner_mut()
462                            .set_register(A0, Self::u8_to_reg(MAX_VMS_SPAWNED));
463                        continue;
464                    }
465                    let spawned_vm_id = self.boot_vm(
466                        &args.location,
467                        VmArgs::Reader {
468                            vm_id,
469                            argc: args.argc,
470                            argv: args.argv,
471                        },
472                    )?;
473                    // Move passed fds from spawner to spawnee
474                    for fd in &args.fds {
475                        self.fds.insert(*fd, spawned_vm_id);
476                    }
477                    // Here we keep the original version of file descriptors.
478                    // If one fd is moved afterward, this inherited file descriptors doesn't change.
479                    self.inherited_fd.insert(spawned_vm_id, args.fds.clone());
480
481                    let (_, machine) = self.ensure_get_instantiated(&vm_id)?;
482                    machine.inner_mut().memory_mut().store64(
483                        &Self::u64_to_reg(args.process_id_addr),
484                        &Self::u64_to_reg(spawned_vm_id),
485                    )?;
486                    machine
487                        .inner_mut()
488                        .set_register(A0, Self::u8_to_reg(SUCCESS));
489                }
490                Message::Wait(vm_id, args) => {
491                    if let Some(exit_code) = self.terminated_vms.get(&args.target_id).copied() {
492                        let (_, machine) = self.ensure_get_instantiated(&vm_id)?;
493                        machine.inner_mut().memory_mut().store8(
494                            &Self::u64_to_reg(args.exit_code_addr),
495                            &Self::i8_to_reg(exit_code),
496                        )?;
497                        machine
498                            .inner_mut()
499                            .set_register(A0, Self::u8_to_reg(SUCCESS));
500                        self.states.insert(vm_id, VmState::Runnable);
501                        self.terminated_vms.remove(&args.target_id);
502                        continue;
503                    }
504                    if !self.states.contains_key(&args.target_id) {
505                        let (_, machine) = self.ensure_get_instantiated(&vm_id)?;
506                        machine
507                            .inner_mut()
508                            .set_register(A0, Self::u8_to_reg(WAIT_FAILURE));
509                        continue;
510                    }
511                    // Return code will be updated when the joining VM exits
512                    self.states.insert(
513                        vm_id,
514                        VmState::Wait {
515                            target_vm_id: args.target_id,
516                            exit_code_addr: args.exit_code_addr,
517                        },
518                    );
519                }
520                Message::Pipe(vm_id, args) => {
521                    if self.fds.len() as u64 >= MAX_FDS {
522                        let (_, machine) = self.ensure_get_instantiated(&vm_id)?;
523                        machine
524                            .inner_mut()
525                            .set_register(A0, Self::u8_to_reg(MAX_FDS_CREATED));
526                        continue;
527                    }
528                    let (p1, p2, slot) = Fd::create(self.next_fd_slot);
529                    self.next_fd_slot = slot;
530                    self.fds.insert(p1, vm_id);
531                    self.fds.insert(p2, vm_id);
532                    let (_, machine) = self.ensure_get_instantiated(&vm_id)?;
533                    machine
534                        .inner_mut()
535                        .memory_mut()
536                        .store64(&Self::u64_to_reg(args.fd1_addr), &Self::u64_to_reg(p1.0))?;
537                    machine
538                        .inner_mut()
539                        .memory_mut()
540                        .store64(&Self::u64_to_reg(args.fd2_addr), &Self::u64_to_reg(p2.0))?;
541                    machine
542                        .inner_mut()
543                        .set_register(A0, Self::u8_to_reg(SUCCESS));
544                }
545                Message::FdRead(vm_id, args) => {
546                    if !(self.fds.contains_key(&args.fd) && (self.fds[&args.fd] == vm_id)) {
547                        let (_, machine) = self.ensure_get_instantiated(&vm_id)?;
548                        machine
549                            .inner_mut()
550                            .set_register(A0, Self::u8_to_reg(INVALID_FD));
551                        continue;
552                    }
553                    if !self.fds.contains_key(&args.fd.other_fd()) {
554                        let (_, machine) = self.ensure_get_instantiated(&vm_id)?;
555                        machine
556                            .inner_mut()
557                            .set_register(A0, Self::u8_to_reg(OTHER_END_CLOSED));
558                        continue;
559                    }
560                    // Return code will be updated when the read operation finishes
561                    self.states.insert(
562                        vm_id,
563                        VmState::WaitForRead(ReadState {
564                            fd: args.fd,
565                            length: args.length,
566                            buffer_addr: args.buffer_addr,
567                            length_addr: args.length_addr,
568                        }),
569                    );
570                }
571                Message::FdWrite(vm_id, args) => {
572                    if !(self.fds.contains_key(&args.fd) && (self.fds[&args.fd] == vm_id)) {
573                        let (_, machine) = self.ensure_get_instantiated(&vm_id)?;
574                        machine
575                            .inner_mut()
576                            .set_register(A0, Self::u8_to_reg(INVALID_FD));
577                        continue;
578                    }
579                    if !self.fds.contains_key(&args.fd.other_fd()) {
580                        let (_, machine) = self.ensure_get_instantiated(&vm_id)?;
581                        machine
582                            .inner_mut()
583                            .set_register(A0, Self::u8_to_reg(OTHER_END_CLOSED));
584                        continue;
585                    }
586                    // Return code will be updated when the write operation finishes
587                    self.states.insert(
588                        vm_id,
589                        VmState::WaitForWrite(WriteState {
590                            fd: args.fd,
591                            consumed: 0,
592                            length: args.length,
593                            buffer_addr: args.buffer_addr,
594                            length_addr: args.length_addr,
595                        }),
596                    );
597                }
598                Message::InheritedFileDescriptor(vm_id, args) => {
599                    let inherited_fd = if vm_id == ROOT_VM_ID {
600                        Vec::new()
601                    } else {
602                        self.inherited_fd[&vm_id].clone()
603                    };
604                    let (_, machine) = self.ensure_get_instantiated(&vm_id)?;
605                    let FdArgs {
606                        buffer_addr,
607                        length_addr,
608                        ..
609                    } = args;
610                    let full_length = machine
611                        .inner_mut()
612                        .memory_mut()
613                        .load64(&Self::u64_to_reg(length_addr))?
614                        .to_u64();
615                    let real_length = inherited_fd.len() as u64;
616                    let copy_length = u64::min(full_length, real_length);
617                    for i in 0..copy_length {
618                        let fd = inherited_fd[i as usize].0;
619                        let offset = i.checked_mul(8).ok_or(Error::MemOutOfBound)?;
620                        let addr = buffer_addr
621                            .checked_add(offset)
622                            .ok_or(Error::MemOutOfBound)?;
623                        machine
624                            .inner_mut()
625                            .memory_mut()
626                            .store64(&Self::u64_to_reg(addr), &Self::u64_to_reg(fd))?;
627                    }
628                    machine.inner_mut().memory_mut().store64(
629                        &Self::u64_to_reg(length_addr),
630                        &Self::u64_to_reg(real_length),
631                    )?;
632                    machine
633                        .inner_mut()
634                        .set_register(A0, Self::u8_to_reg(SUCCESS));
635                }
636                Message::Close(vm_id, fd) => {
637                    if self.fds.get(&fd) != Some(&vm_id) {
638                        let (_, machine) = self.ensure_get_instantiated(&vm_id)?;
639                        machine
640                            .inner_mut()
641                            .set_register(A0, Self::u8_to_reg(INVALID_FD));
642                    } else {
643                        self.fds.remove(&fd);
644                        let (_, machine) = self.ensure_get_instantiated(&vm_id)?;
645                        machine
646                            .inner_mut()
647                            .set_register(A0, Self::u8_to_reg(SUCCESS));
648                    }
649                }
650            }
651        }
652        Ok(())
653    }
654
655    fn process_io(&mut self) -> Result<(), Error> {
656        let mut reads: HashMap<Fd, (VmId, ReadState)> = HashMap::default();
657        let mut closed_fds: Vec<VmId> = Vec::new();
658
659        self.states.iter().for_each(|(vm_id, state)| {
660            if let VmState::WaitForRead(inner_state) = state {
661                if self.fds.contains_key(&inner_state.fd.other_fd()) {
662                    reads.insert(inner_state.fd, (*vm_id, inner_state.clone()));
663                } else {
664                    closed_fds.push(*vm_id);
665                }
666            }
667        });
668        let mut pairs: Vec<(VmId, ReadState, VmId, WriteState)> = Vec::new();
669        self.states.iter().for_each(|(vm_id, state)| {
670            if let VmState::WaitForWrite(inner_state) = state {
671                if self.fds.contains_key(&inner_state.fd.other_fd()) {
672                    if let Some((read_vm_id, read_state)) = reads.get(&inner_state.fd.other_fd()) {
673                        pairs.push((*read_vm_id, read_state.clone(), *vm_id, inner_state.clone()));
674                    }
675                } else {
676                    closed_fds.push(*vm_id);
677                }
678            }
679        });
680        // Finish read / write syscalls for fds that are closed on the other end
681        for vm_id in closed_fds {
682            match self.states[&vm_id].clone() {
683                VmState::WaitForRead(ReadState { length_addr, .. }) => {
684                    let (_, machine) = self.ensure_get_instantiated(&vm_id)?;
685                    machine.inner_mut().memory_mut().store64(
686                        &Self::u64_to_reg(length_addr),
687                        &<M::Inner as CoreMachine>::REG::zero(),
688                    )?;
689                    machine
690                        .inner_mut()
691                        .set_register(A0, Self::u8_to_reg(SUCCESS));
692                    self.states.insert(vm_id, VmState::Runnable);
693                }
694                VmState::WaitForWrite(WriteState {
695                    consumed,
696                    length_addr,
697                    ..
698                }) => {
699                    let (_, machine) = self.ensure_get_instantiated(&vm_id)?;
700                    machine
701                        .inner_mut()
702                        .memory_mut()
703                        .store64(&Self::u64_to_reg(length_addr), &Self::u64_to_reg(consumed))?;
704                    machine
705                        .inner_mut()
706                        .set_register(A0, Self::u8_to_reg(SUCCESS));
707                    self.states.insert(vm_id, VmState::Runnable);
708                }
709                _ => (),
710            }
711        }
712        // Transferring data from write fds to read fds
713        for (read_vm_id, read_state, write_vm_id, write_state) in pairs {
714            let ReadState {
715                length: read_length,
716                buffer_addr: read_buffer_addr,
717                length_addr: read_length_addr,
718                ..
719            } = read_state;
720            let WriteState {
721                fd: write_fd,
722                mut consumed,
723                length: write_length,
724                buffer_addr: write_buffer_addr,
725                length_addr: write_length_addr,
726            } = write_state;
727
728            self.ensure_vms_instantiated(&[read_vm_id, write_vm_id])?;
729            {
730                let fillable = read_length;
731                let consumable = write_length - consumed;
732                let copiable = std::cmp::min(fillable, consumable);
733
734                // Actual data copying
735                let (_, write_machine) = self
736                    .instantiated
737                    .get_mut(&write_vm_id)
738                    .ok_or_else(|| Error::Unexpected("Unable to find VM Id".to_string()))?;
739                write_machine
740                    .inner_mut()
741                    .add_cycles_no_checking(transferred_byte_cycles(copiable))?;
742                let data = write_machine.inner_mut().memory_mut().load_bytes(
743                    write_buffer_addr
744                        .checked_add(consumed)
745                        .ok_or(Error::MemOutOfBound)?,
746                    copiable,
747                )?;
748                let (_, read_machine) = self
749                    .instantiated
750                    .get_mut(&read_vm_id)
751                    .ok_or_else(|| Error::Unexpected("Unable to find VM Id".to_string()))?;
752                read_machine
753                    .inner_mut()
754                    .add_cycles_no_checking(transferred_byte_cycles(copiable))?;
755                read_machine
756                    .inner_mut()
757                    .memory_mut()
758                    .store_bytes(read_buffer_addr, &data)?;
759                // Read syscall terminates as soon as some data are filled
760                read_machine.inner_mut().memory_mut().store64(
761                    &Self::u64_to_reg(read_length_addr),
762                    &Self::u64_to_reg(copiable),
763                )?;
764                read_machine
765                    .inner_mut()
766                    .set_register(A0, Self::u8_to_reg(SUCCESS));
767                self.states.insert(read_vm_id, VmState::Runnable);
768
769                // Write syscall, however, terminates only when all the data
770                // have been written, or when the pairing read fd is closed.
771                consumed += copiable;
772                if consumed == write_length {
773                    // Write VM has fulfilled its write request
774                    let (_, write_machine) = self
775                        .instantiated
776                        .get_mut(&write_vm_id)
777                        .ok_or_else(|| Error::Unexpected("Unable to find VM Id".to_string()))?;
778                    write_machine.inner_mut().memory_mut().store64(
779                        &Self::u64_to_reg(write_length_addr),
780                        &Self::u64_to_reg(write_length),
781                    )?;
782                    write_machine
783                        .inner_mut()
784                        .set_register(A0, Self::u8_to_reg(SUCCESS));
785                    self.states.insert(write_vm_id, VmState::Runnable);
786                } else {
787                    // Only update write VM state
788                    self.states.insert(
789                        write_vm_id,
790                        VmState::WaitForWrite(WriteState {
791                            fd: write_fd,
792                            consumed,
793                            length: write_length,
794                            buffer_addr: write_buffer_addr,
795                            length_addr: write_length_addr,
796                        }),
797                    );
798                }
799            }
800        }
801        Ok(())
802    }
803
804    /// If current scheduler is terminated
805    pub fn terminated(&self) -> bool {
806        self.states
807            .get(&ROOT_VM_ID)
808            .map(|state| *state == VmState::Terminated)
809            .unwrap_or(false)
810    }
811
812    fn terminated_result(&mut self) -> Result<TerminatedResult, Error> {
813        assert!(self.terminated());
814
815        let exit_code = {
816            let root_vm = &self.ensure_get_instantiated(&ROOT_VM_ID)?.1;
817            root_vm.machine().exit_code()
818        };
819        Ok(TerminatedResult {
820            exit_code,
821            consumed_cycles: self.consumed_cycles(),
822        })
823    }
824
825    // Ensure VMs are instantiated
826    fn ensure_vms_instantiated(&mut self, ids: &[VmId]) -> Result<(), Error> {
827        if ids.len() > MAX_INSTANTIATED_VMS {
828            return Err(Error::Unexpected(format!(
829                "At most {} VMs can be instantiated but {} are requested!",
830                MAX_INSTANTIATED_VMS,
831                ids.len()
832            )));
833        }
834
835        let mut uninstantiated_ids: Vec<VmId> = ids
836            .iter()
837            .filter(|id| !self.instantiated.contains_key(id))
838            .copied()
839            .collect();
840        while (!uninstantiated_ids.is_empty()) && (self.instantiated.len() < MAX_INSTANTIATED_VMS) {
841            let id = uninstantiated_ids
842                .pop()
843                .ok_or_else(|| Error::Unexpected("Map should not be empty".to_string()))?;
844            self.resume_vm(&id)?;
845        }
846
847        if !uninstantiated_ids.is_empty() {
848            // Instantiated is a BTreeMap, an iterator on it maintains key order to ensure deterministic behavior
849            let suspendable_ids: Vec<VmId> = self
850                .instantiated
851                .keys()
852                .filter(|id| !ids.contains(id))
853                .copied()
854                .collect();
855
856            assert!(suspendable_ids.len() >= uninstantiated_ids.len());
857            for i in 0..uninstantiated_ids.len() {
858                self.suspend_vm(&suspendable_ids[i])?;
859                self.resume_vm(&uninstantiated_ids[i])?;
860            }
861        }
862
863        Ok(())
864    }
865
866    /// Ensure corresponding VM is instantiated and return a mutable reference to it
867    fn ensure_get_instantiated(&mut self, id: &VmId) -> Result<&mut (VmContext<DL>, M), Error> {
868        self.ensure_vms_instantiated(&[*id])?;
869        self.instantiated
870            .get_mut(id)
871            .ok_or_else(|| Error::Unexpected("Unable to find VM Id".to_string()))
872    }
873
874    // Resume a suspended VM
875    fn resume_vm(&mut self, id: &VmId) -> Result<(), Error> {
876        if !self.suspended.contains_key(id) {
877            return Err(Error::Unexpected(format!("VM {:?} is not suspended!", id)));
878        }
879        let snapshot = &self.suspended[id];
880        self.iteration_cycles = self
881            .iteration_cycles
882            .checked_add(SPAWN_EXTRA_CYCLES_BASE)
883            .ok_or(Error::CyclesExceeded)?;
884        let (context, mut machine) = self.create_dummy_vm(id)?;
885        {
886            let mut sc = context.snapshot2_context.lock().expect("lock");
887            sc.resume(machine.inner_mut(), snapshot)?;
888        }
889        self.instantiated.insert(*id, (context, machine));
890        self.suspended.remove(id);
891        Ok(())
892    }
893
894    // Suspend an instantiated VM
895    fn suspend_vm(&mut self, id: &VmId) -> Result<(), Error> {
896        if !self.instantiated.contains_key(id) {
897            return Err(Error::Unexpected(format!(
898                "VM {:?} is not instantiated!",
899                id
900            )));
901        }
902        self.iteration_cycles = self
903            .iteration_cycles
904            .checked_add(SPAWN_EXTRA_CYCLES_BASE)
905            .ok_or(Error::CyclesExceeded)?;
906        let (context, machine) = self
907            .instantiated
908            .get_mut(id)
909            .ok_or_else(|| Error::Unexpected("Unable to find VM Id".to_string()))?;
910        let snapshot = {
911            let sc = context.snapshot2_context.lock().expect("lock");
912            sc.make_snapshot(machine.inner_mut())?
913        };
914        self.suspended.insert(*id, snapshot);
915        self.instantiated.remove(id);
916        Ok(())
917    }
918
919    fn boot_root_vm_if_needed(&mut self) -> Result<(), Error> {
920        if self.states.is_empty() {
921            // Booting phase, we will need to initialize the first VM.
922            let program_id = self.sg_data.sg_info.program_data_piece_id.clone();
923            assert_eq!(
924                self.boot_vm(
925                    &DataLocation {
926                        data_piece_id: program_id,
927                        offset: 0,
928                        length: u64::MAX,
929                    },
930                    VmArgs::Vector(self.root_vm_args.clone()),
931                )?,
932                ROOT_VM_ID
933            );
934        }
935        assert!(self.states.contains_key(&ROOT_VM_ID));
936
937        Ok(())
938    }
939
940    /// Boot a vm by given program and args.
941    fn boot_vm(&mut self, location: &DataLocation, args: VmArgs) -> Result<VmId, Error> {
942        let id = self.next_vm_id;
943        self.next_vm_id += 1;
944        let (context, mut machine) = self.create_dummy_vm(&id)?;
945        let (program, _) = {
946            let sc = context.snapshot2_context.lock().expect("lock");
947            sc.load_data(&location.data_piece_id, location.offset, location.length)?
948        };
949        self.load_vm_program(&context, &mut machine, location, program, args)?;
950        // Newly booted VM will be instantiated by default
951        while self.instantiated.len() >= MAX_INSTANTIATED_VMS {
952            // Instantiated is a BTreeMap, first_entry will maintain key order
953            let id = *self
954                .instantiated
955                .first_entry()
956                .ok_or_else(|| Error::Unexpected("Map should not be empty".to_string()))?
957                .key();
958            self.suspend_vm(&id)?;
959        }
960
961        self.instantiated.insert(id, (context, machine));
962        self.states.insert(id, VmState::Runnable);
963
964        Ok(id)
965    }
966
967    // Load the program into an empty vm.
968    fn load_vm_program(
969        &mut self,
970        context: &VmContext<DL>,
971        machine: &mut M,
972        location: &DataLocation,
973        program: Bytes,
974        args: VmArgs,
975    ) -> Result<u64, Error> {
976        let metadata = parse_elf::<u64>(&program, machine.inner_mut().version())?;
977        let bytes = match args {
978            VmArgs::Reader { vm_id, argc, argv } => {
979                let (_, machine_from) = self.ensure_get_instantiated(&vm_id)?;
980                let argc = Self::u64_to_reg(argc);
981                let argv = Self::u64_to_reg(argv);
982                let argv =
983                    FlattenedArgsReader::new(machine_from.inner_mut().memory_mut(), argc, argv);
984                machine.load_program_with_metadata(&program, &metadata, argv)?
985            }
986            VmArgs::Vector(data) => {
987                machine.load_program_with_metadata(&program, &metadata, data.into_iter().map(Ok))?
988            }
989        };
990        let mut sc = context.snapshot2_context.lock().expect("lock");
991        sc.mark_program(
992            machine.inner_mut(),
993            &metadata,
994            &location.data_piece_id,
995            location.offset,
996        )?;
997        machine
998            .inner_mut()
999            .add_cycles_no_checking(transferred_byte_cycles(bytes))?;
1000        Ok(bytes)
1001    }
1002
1003    // Create a new VM instance with syscalls attached
1004    fn create_dummy_vm(&self, id: &VmId) -> Result<(VmContext<DL>, M), Error> {
1005        let version = &self.sg_data.sg_info.script_version;
1006        let core_machine = M::Inner::new(
1007            version.vm_isa(),
1008            version.vm_version(),
1009            // We will update max_cycles for each machine when it gets a chance to run
1010            u64::MAX,
1011        );
1012        let vm_context = VmContext {
1013            base_cycles: Arc::clone(&self.total_cycles),
1014            message_box: Arc::clone(&self.message_box),
1015            snapshot2_context: Arc::new(Mutex::new(Snapshot2Context::new(self.sg_data.clone()))),
1016        };
1017
1018        let machine_builder = DefaultMachineBuilder::new(core_machine)
1019            .instruction_cycle_func(Box::new(estimate_cycles));
1020        let machine_builder =
1021            (self.syscall_generator)(id, &self.sg_data, &vm_context, &self.syscall_context)
1022                .into_iter()
1023                .fold(machine_builder, |builder, syscall| builder.syscall(syscall));
1024        let default_machine = machine_builder.build();
1025        Ok((vm_context, M::new(default_machine)))
1026    }
1027
1028    /// Provided for compatibility with surrounding tools. Normally, you should not call this
1029    /// function from within the current crate.
1030    pub fn set_root_vm_args(&mut self, args: Vec<Bytes>) {
1031        self.root_vm_args = args;
1032    }
1033
1034    fn i8_to_reg(v: i8) -> <M::Inner as CoreMachine>::REG {
1035        <M::Inner as CoreMachine>::REG::from_i8(v)
1036    }
1037
1038    fn u8_to_reg(v: u8) -> <M::Inner as CoreMachine>::REG {
1039        <M::Inner as CoreMachine>::REG::from_u8(v)
1040    }
1041
1042    fn u64_to_reg(v: u64) -> <M::Inner as CoreMachine>::REG {
1043        <M::Inner as CoreMachine>::REG::from_u64(v)
1044    }
1045}