1use javm_cap::{
49 Blake2b256, CacheDirectory, Cap, CapHashOrRef, DataCap, DataContent, Hash, SlotIdx, TypeCap,
50};
51use javm_exec::{EcallHandler, EcallKind, EcallResult, ExitReason, Memory, Regs};
52
53use crate::callstack::Entry;
54use crate::error::VmError;
55use crate::kernel_assist::KernelAssist;
56use crate::vm::Vm;
57
58pub mod mgmt_op {
60 pub const COPY: u32 = 1;
61 pub const MOVE: u32 = 2;
62 pub const DROP: u32 = 3;
63 pub const CNODE_SWAP: u32 = 4;
64 pub const CNODE_MINT: u32 = 5;
65 pub const MAX: u32 = 15;
67}
68
69pub mod host_op {
73 pub const HOST_YIELD: u32 = 16;
74 pub const SET_IMAGE: u32 = 17;
77 pub const DERIVE_SPAWN: u32 = 18;
79 pub const MAKE_IMAGE: u32 = 19;
81 pub const HOST_SAME_TYPE: u32 = 20;
83 pub const HOST_TYPE_OF: u32 = 21;
86 pub const HOST_READ_DATA_CAP: u32 = 22;
88 pub const HOST_MINT_DATA_CAP: u32 = 23;
90 pub const HOST_OPEN: u32 = 24;
92 pub const HOST_SAVE: u32 = 25;
94 pub const HOST_CALL: u32 = 26;
103 pub const MAX: u32 = 63;
105}
106
107impl<K: KernelAssist> EcallHandler for Vm<K> {
108 fn handle(&mut self, kind: EcallKind, regs: &mut Regs, mem: &mut dyn Memory) -> EcallResult {
109 match kind {
110 EcallKind::Ecalli(op) => self.dispatch_ecalli(op, regs, mem, None),
111 EcallKind::Ecall => self.dispatch_ecall(regs, mem, None),
112 }
113 }
114}
115
116pub(crate) struct CachedEcallHandler<'a, K: KernelAssist> {
117 pub(crate) vm: &'a mut Vm<K>,
118 pub(crate) cache: &'a mut CacheDirectory,
119}
120
121impl<K: KernelAssist> EcallHandler for CachedEcallHandler<'_, K> {
122 fn handle(&mut self, kind: EcallKind, regs: &mut Regs, mem: &mut dyn Memory) -> EcallResult {
123 match kind {
124 EcallKind::Ecalli(op) => self
125 .vm
126 .dispatch_ecalli(op, regs, mem, Some(&mut *self.cache)),
127 EcallKind::Ecall => self.vm.dispatch_ecall(regs, mem, Some(&mut *self.cache)),
128 }
129 }
130}
131
132impl<K: KernelAssist> Vm<K> {
133 fn dispatch_ecalli(
134 &mut self,
135 op: u32,
136 regs: &mut Regs,
137 mem: &mut dyn Memory,
138 cache: Option<&mut CacheDirectory>,
139 ) -> EcallResult {
140 match op {
141 0 => {
142 EcallResult::Exit(ExitReason::Halt)
144 }
145 o if o <= mgmt_op::MAX => match self.dispatch_mgmt(o, regs, cache) {
146 Ok(()) => EcallResult::Continue,
147 Err(_) => EcallResult::Exit(ExitReason::Trap),
148 },
149 o if o <= host_op::MAX => self.dispatch_host_call(o, regs, mem, cache),
150 _ => {
151 EcallResult::Continue
155 }
156 }
157 }
158
159 fn dispatch_host_call(
161 &mut self,
162 op: u32,
163 regs: &mut Regs,
164 mem: &mut dyn Memory,
165 cache: Option<&mut CacheDirectory>,
166 ) -> EcallResult {
167 fn trap_on_err<T>(r: Result<T, VmError>, ok: impl FnOnce(T) -> EcallResult) -> EcallResult {
168 match r {
169 Ok(v) => ok(v),
170 Err(_) => EcallResult::Exit(ExitReason::Trap),
171 }
172 }
173 match op {
174 host_op::HOST_YIELD => trap_on_err(self.dispatch_host_yield(regs), |r| r),
175 host_op::SET_IMAGE => match cache {
176 Some(cache) => trap_on_err(self.dispatch_set_image_cached(regs, cache), |()| {
177 EcallResult::Exit(ExitReason::HostCall(host_op::SET_IMAGE))
178 }),
179 None => trap_on_err(self.dispatch_set_image(regs), |()| EcallResult::Continue),
180 },
181 host_op::DERIVE_SPAWN => match cache {
182 Some(cache) => trap_on_err(self.dispatch_derive_spawn_cached(regs, cache), |()| {
183 EcallResult::Continue
184 }),
185 None => trap_on_err(self.dispatch_derive_spawn(regs), |()| EcallResult::Continue),
186 },
187 host_op::MAKE_IMAGE => {
188 EcallResult::Exit(ExitReason::Trap)
190 }
191 host_op::HOST_SAME_TYPE => trap_on_err(self.dispatch_host_same_type(regs), |()| {
192 EcallResult::Continue
193 }),
194 host_op::HOST_TYPE_OF => match cache {
195 Some(cache) => trap_on_err(self.dispatch_host_type_of(regs, cache), |()| {
196 EcallResult::Continue
197 }),
198 None => EcallResult::Exit(ExitReason::Trap),
199 },
200 host_op::HOST_READ_DATA_CAP => match cache {
201 Some(cache) => {
202 trap_on_err(self.dispatch_host_read_data_cap(regs, mem, cache), |()| {
203 EcallResult::Continue
204 })
205 }
206 None => EcallResult::Exit(ExitReason::Trap),
207 },
208 host_op::HOST_MINT_DATA_CAP => match cache {
209 Some(cache) => {
210 trap_on_err(self.dispatch_host_mint_data_cap(regs, mem, cache), |()| {
211 EcallResult::Continue
212 })
213 }
214 None => EcallResult::Exit(ExitReason::Trap),
215 },
216 host_op::HOST_OPEN => match cache {
217 Some(cache) => trap_on_err(self.dispatch_host_open(regs, cache), |()| {
218 EcallResult::Continue
219 }),
220 None => EcallResult::Exit(ExitReason::Trap),
221 },
222 host_op::HOST_SAVE => match cache {
223 Some(cache) => trap_on_err(self.dispatch_host_save(regs, cache), |()| {
224 EcallResult::Continue
225 }),
226 None => EcallResult::Exit(ExitReason::Trap),
227 },
228 host_op::HOST_CALL => match cache {
229 Some(cache) => trap_on_err(self.dispatch_host_call_cached(regs, cache), |()| {
230 EcallResult::Exit(ExitReason::HostCall(host_op::HOST_CALL))
231 }),
232 None => EcallResult::Exit(ExitReason::Trap),
233 },
234 _ => {
235 EcallResult::Continue
238 }
239 }
240 }
241
242 fn dispatch_host_yield(&mut self, regs: &mut Regs) -> Result<EcallResult, VmError> {
244 let marker_slot = SlotIdx((regs.gpr[7] & 0xFF) as u32);
245
246 let marker_hash = {
251 let running = self
252 .stack
253 .running_instance()
254 .ok_or(VmError::CallStackEmpty)?;
255 match running.root_cnode.get(marker_slot) {
256 Some(CapHashOrRef::Hash(h)) => h,
257 Some(CapHashOrRef::Ref(_)) => {
258 return Err(VmError::SlotKindMismatch(marker_slot.get()));
259 }
260 None => return Err(VmError::SlotEmpty(marker_slot.get())),
261 }
262 };
263
264 let stack_len = self.stack.entries().len();
269 let mut target_pos: Option<usize> = None;
270 for pos in (0..stack_len.saturating_sub(1)).rev() {
271 let ie = match &self.stack.entries()[pos] {
272 Entry::Instance(ie) => ie.as_ref(),
273 Entry::Reference(_) => continue,
274 };
275 let Some(catcher_slot) = ie.yield_marker_slot else {
276 continue;
277 };
278 let catcher_hash = match ie.root_cnode.get(catcher_slot) {
279 Some(CapHashOrRef::Hash(h)) => h,
280 _ => continue,
281 };
282 let markers = self.kernel_assist.yield_catcher_markers(catcher_hash);
283 if markers.contains(&marker_hash) {
284 target_pos = Some(pos);
285 break;
286 }
287 }
288
289 let pos = target_pos.ok_or(VmError::UnhandledMarker)?;
290
291 self.stack.push_reference(pos)?;
294
295 Ok(EcallResult::Exit(ExitReason::HostCall(host_op::HOST_YIELD)))
296 }
297
298 fn dispatch_set_image(&mut self, regs: &mut Regs) -> Result<(), VmError> {
307 let image_slot = SlotIdx((regs.gpr[7] & 0xFF) as u32);
308
309 let new_image_hash = {
311 let running = self
312 .stack
313 .running_instance()
314 .ok_or(VmError::CallStackEmpty)?;
315 match running.root_cnode.get(image_slot) {
316 Some(CapHashOrRef::Hash(h)) => h,
317 Some(CapHashOrRef::Ref(_)) => {
318 return Err(VmError::SlotKindMismatch(image_slot.get()));
319 }
320 None => return Err(VmError::SlotEmpty(image_slot.get())),
321 }
322 };
323
324 let extended_chain = {
326 let running = self
327 .stack
328 .running_instance()
329 .ok_or(VmError::CallStackEmpty)?;
330 Blake2b256::hash_pair(&running.image_hash_chain, &new_image_hash)
331 };
332
333 let running = self
336 .stack
337 .running_instance_mut()
338 .ok_or(VmError::CallStackEmpty)?;
339 running.image_hash_chain = extended_chain;
340 running.image_hash = new_image_hash;
341 Ok(())
342 }
343
344 fn dispatch_set_image_cached(
345 &mut self,
346 regs: &mut Regs,
347 cache: &CacheDirectory,
348 ) -> Result<(), VmError> {
349 let image_slot = SlotIdx((regs.gpr[7] & 0xFF) as u32);
350 let (new_image_hash, extended_chain) = {
351 let running = self
352 .stack
353 .running_instance()
354 .ok_or(VmError::CallStackEmpty)?;
355 let new_image_hash = match running.root_cnode.get(image_slot) {
356 Some(CapHashOrRef::Hash(h)) => h,
357 Some(CapHashOrRef::Ref(_)) => {
358 return Err(VmError::SlotKindMismatch(image_slot.get()));
359 }
360 None => return Err(VmError::SlotEmpty(image_slot.get())),
361 };
362 (
363 new_image_hash,
364 Blake2b256::hash_pair(&running.image_hash_chain, &new_image_hash),
365 )
366 };
367
368 let img = match &*cache
369 .get(CapHashOrRef::Hash(new_image_hash))
370 .ok_or(VmError::ImageNotFound)?
371 {
372 Cap::Image(i) => i.clone(),
373 _ => return Err(VmError::ImageNotFound),
374 };
375 let unpacked_bitmask = javm_exec::unpack_bitmask(img.bitmask.as_slice(), img.code.len());
376 let program = self.image_cache.get_or_decode(
377 new_image_hash,
378 img.code.as_slice().to_vec(),
379 unpacked_bitmask,
380 img.jump_table.as_slice().to_vec(),
381 )?;
382 let pinned_slots: Vec<SlotIdx> = img.pinned.iter().map(|e| e.slot).collect();
383
384 let running = self
385 .stack
386 .running_instance_mut()
387 .ok_or(VmError::CallStackEmpty)?;
388 running.image_hash_chain = extended_chain;
389 running.image_hash = new_image_hash;
390 running.program = program;
391 running.pinned_slots = pinned_slots;
392 running.yield_marker_slot = img.yield_marker_slot;
393 Ok(())
394 }
395
396 fn dispatch_derive_spawn(&mut self, regs: &mut Regs) -> Result<(), VmError> {
402 let image_slot = SlotIdx((regs.gpr[7] & 0xFF) as u32);
403 let dst_slot = SlotIdx((regs.gpr[8] & 0xFF) as u32);
404
405 let new_image_hash = {
407 let running = self
408 .stack
409 .running_instance()
410 .ok_or(VmError::CallStackEmpty)?;
411 match running.root_cnode.get(image_slot) {
412 Some(CapHashOrRef::Hash(h)) => h,
413 Some(CapHashOrRef::Ref(_)) => {
414 return Err(VmError::SlotKindMismatch(image_slot.get()));
415 }
416 None => return Err(VmError::SlotEmpty(image_slot.get())),
417 }
418 };
419
420 let extended = {
422 let running = self
423 .stack
424 .running_instance()
425 .ok_or(VmError::CallStackEmpty)?;
426 Blake2b256::hash_pair(&running.image_hash_chain, &new_image_hash)
427 };
428
429 let running = self
431 .stack
432 .running_instance_mut()
433 .ok_or(VmError::CallStackEmpty)?;
434 if running.pinned_slots.binary_search(&dst_slot).is_ok() {
435 return Err(javm_cap::OpError::SlotPinned(dst_slot.get()).into());
436 }
437 running
438 .root_cnode
439 .set(dst_slot, Some(CapHashOrRef::Hash(extended)))?;
440 Ok(())
441 }
442
443 fn dispatch_derive_spawn_cached(
460 &mut self,
461 regs: &mut Regs,
462 cache: &mut CacheDirectory,
463 ) -> Result<(), VmError> {
464 let image_slot = SlotIdx((regs.gpr[7] & 0xFF) as u32);
465 let cnode_slot = SlotIdx((regs.gpr[8] & 0xFF) as u32);
466 let dst_slot = SlotIdx((regs.gpr[9] & 0xFF) as u32);
467
468 let (image_hash, cnode_hash, parent_chain) = {
470 let running = self
471 .stack
472 .running_instance()
473 .ok_or(VmError::CallStackEmpty)?;
474 let img_h = match running.root_cnode.get(image_slot) {
475 Some(CapHashOrRef::Hash(h)) => h,
476 Some(CapHashOrRef::Ref(_)) => {
477 return Err(VmError::SlotKindMismatch(image_slot.get()));
478 }
479 None => return Err(VmError::SlotEmpty(image_slot.get())),
480 };
481 let cn_h = match running.root_cnode.get(cnode_slot) {
482 Some(CapHashOrRef::Hash(h)) => h,
483 Some(CapHashOrRef::Ref(_)) => {
484 return Err(VmError::SlotKindMismatch(cnode_slot.get()));
485 }
486 None => return Err(VmError::SlotEmpty(cnode_slot.get())),
487 };
488 (img_h, cn_h, running.image_hash_chain)
489 };
490
491 let child_chain = Blake2b256::hash_pair(&parent_chain, &image_hash);
493
494 let img_cap = match &*cache
502 .get(CapHashOrRef::Hash(image_hash))
503 .ok_or(VmError::ImageNotFound)?
504 {
505 Cap::Image(i) => i.clone(),
506 _ => return Err(VmError::ImageNotFound),
507 };
508 let mut child_cn = match &*cache
509 .get(CapHashOrRef::Hash(cnode_hash))
510 .ok_or(VmError::Invariant("derive_spawn: prepared cnode missing"))?
511 {
512 Cap::CNode(c) => c.clone(),
513 _ => {
514 return Err(VmError::Invariant(
515 "derive_spawn: cnode_slot does not hold Cap::CNode",
516 ));
517 }
518 };
519 for e in img_cap.pinned.iter() {
520 child_cn.set(e.slot, Some(CapHashOrRef::Hash(e.cap_hash)))?;
521 }
522 for e in img_cap.initial.iter() {
523 if child_cn.get(e.slot).is_none() {
524 child_cn.set(e.slot, Some(CapHashOrRef::Hash(e.cap_hash)))?;
525 }
526 }
527 let new_cnode_hash = cache.put_cap(&Cap::CNode(child_cn))?;
528
529 let new_cnode_cap = cache
536 .get(CapHashOrRef::Hash(new_cnode_hash))
537 .ok_or(VmError::Invariant("derive_spawn: new cnode missing"))?;
538 let new_cnode = match &*new_cnode_cap {
539 Cap::CNode(c) => c.clone(),
540 _ => return Err(VmError::Invariant("derive_spawn: cnode hash misroutes")),
541 };
542 let mut overlay_bufs: Vec<(u32, Vec<u8>)> = Vec::new();
543 let mut mem_size: u32 = 0;
544 for m in img_cap.mappings.iter() {
545 let end = (m.start + m.size) as u32;
546 if end > mem_size {
547 mem_size = end;
548 }
549 if m.source_path_len == 0 {
550 continue;
551 }
552 let src_slot = m.source_path[0];
554 let target = match new_cnode.get(src_slot) {
555 Some(t) => t,
556 None => continue,
557 };
558 let data_arc = cache.get(target);
559 let bytes_vec = match data_arc.as_deref() {
560 Some(Cap::Data(d)) => match &d.content {
561 javm_cap::DataContent::Inline(v) => v.as_slice().to_vec(),
562 javm_cap::DataContent::Paged { .. } => continue,
563 },
564 _ => continue,
565 };
566 if !bytes_vec.is_empty() {
567 overlay_bufs.push((m.start as u32, bytes_vec));
568 }
569 }
570 let overlay_slices: Vec<(u32, &[u8])> = overlay_bufs
571 .iter()
572 .map(|(s, b)| (*s, b.as_slice()))
573 .collect();
574
575 let inst_cap = Cap::instance_with_overlays(
576 child_chain,
577 image_hash,
578 new_cnode_hash,
579 &overlay_slices,
580 mem_size,
581 [0u64; javm_cap::NUM_REGS],
582 0,
583 0,
584 );
585 let new_instance_hash = cache.put_cap(&inst_cap)?;
586
587 let running = self
590 .stack
591 .running_instance_mut()
592 .ok_or(VmError::CallStackEmpty)?;
593 if running.pinned_slots.binary_search(&dst_slot).is_ok() {
594 return Err(javm_cap::OpError::SlotPinned(dst_slot.get()).into());
595 }
596 running.root_cnode.take(cnode_slot)?;
597 running
598 .root_cnode
599 .set(dst_slot, Some(CapHashOrRef::Hash(new_instance_hash)))?;
600 Ok(())
601 }
602
603 fn dispatch_host_same_type(&mut self, regs: &mut Regs) -> Result<(), VmError> {
609 let a = SlotIdx((regs.gpr[7] & 0xFF) as u32);
610 let b = SlotIdx((regs.gpr[8] & 0xFF) as u32);
611 let running = self
612 .stack
613 .running_instance()
614 .ok_or(VmError::CallStackEmpty)?;
615 let ha = match running.root_cnode.get(a) {
616 Some(CapHashOrRef::Hash(h)) => h,
617 _ => return Err(VmError::SlotEmpty(a.get())),
618 };
619 let hb = match running.root_cnode.get(b) {
620 Some(CapHashOrRef::Hash(h)) => h,
621 _ => return Err(VmError::SlotEmpty(b.get())),
622 };
623 regs.gpr[7] = if ha == hb { 1 } else { 0 };
624 Ok(())
625 }
626
627 fn dispatch_host_type_of(
628 &mut self,
629 regs: &mut Regs,
630 cache: &mut CacheDirectory,
631 ) -> Result<(), VmError> {
632 let src = SlotIdx((regs.gpr[7] & 0xFF) as u32);
633 let dst = SlotIdx((regs.gpr[8] & 0xFF) as u32);
634 let target = self
635 .stack
636 .running_instance()
637 .ok_or(VmError::CallStackEmpty)?
638 .root_cnode
639 .get(src)
640 .ok_or(VmError::SlotEmpty(src.get()))?;
641 let image_hash_chain = match &*cache.get(target).ok_or(VmError::InstanceNotFound)? {
642 Cap::Instance(i) => i.image_hash_chain,
643 _ => return Err(VmError::InstanceNotFound),
644 };
645 let cap = Cap::Type(TypeCap { image_hash_chain });
646 let h = javm_cap::cap_hash(&cap);
647 cache.put_cap_with_hash(h, &cap)?;
648
649 let running = self
650 .stack
651 .running_instance_mut()
652 .ok_or(VmError::CallStackEmpty)?;
653 if running.pinned_slots.binary_search(&dst).is_ok() {
654 return Err(javm_cap::OpError::SlotPinned(dst.get()).into());
655 }
656 running.root_cnode.set(dst, Some(CapHashOrRef::Hash(h)))?;
657 Ok(())
658 }
659
660 fn dispatch_host_read_data_cap(
661 &mut self,
662 regs: &mut Regs,
663 mem: &mut dyn Memory,
664 cache: &CacheDirectory,
665 ) -> Result<(), VmError> {
666 let src = SlotIdx((regs.gpr[7] & 0xFF) as u32);
667 let dst_offset = regs.gpr[8] as u32;
668 let len = regs.gpr[9] as usize;
669 let target = self
670 .stack
671 .running_instance()
672 .ok_or(VmError::CallStackEmpty)?
673 .root_cnode
674 .get(src)
675 .ok_or(VmError::SlotEmpty(src.get()))?;
676 let data_arc = cache
677 .get(target)
678 .ok_or(VmError::Invariant("data cap missing"))?;
679 let data = match &*data_arc {
680 Cap::Data(d) => d,
681 _ => return Err(VmError::Invariant("slot does not hold Cap::Data")),
682 };
683 let bytes = data_cap_prefix(data, len);
684 mem.write(dst_offset, &bytes)
685 .map_err(|_| VmError::Invariant("host_read_data_cap memory write failed"))?;
686 regs.gpr[7] = bytes.len() as u64;
687 Ok(())
688 }
689
690 fn dispatch_host_mint_data_cap(
691 &mut self,
692 regs: &mut Regs,
693 mem: &mut dyn Memory,
694 cache: &mut CacheDirectory,
695 ) -> Result<(), VmError> {
696 let src_offset = regs.gpr[7] as u32;
697 let len = regs.gpr[8] as usize;
698 let quota_id = regs.gpr[9];
699 let dst = SlotIdx((regs.gpr[10] & 0xFF) as u32);
700 let bytes = mem
701 .read(src_offset, len)
702 .map_err(|_| VmError::Invariant("host_mint_data_cap memory read failed"))?;
703 let mut inline = javm_cap::data::alloc_page_aligned_zeroed(bytes.len());
709 inline[..bytes.len()].copy_from_slice(&bytes);
710 let debit = inline.len() as u64;
711 let quota = self.kernel_assist.storage_quota_get(quota_id);
712 if quota < debit {
713 return Err(VmError::Invariant("storage quota exhausted"));
714 }
715 self.kernel_assist
716 .storage_quota_set(quota_id, quota - debit);
717 let cap = Cap::Data(DataCap {
718 content: DataContent::Inline(inline),
719 });
720 let h = javm_cap::cap_hash(&cap);
721 cache.put_cap_with_hash(h, &cap)?;
722
723 let running = self
724 .stack
725 .running_instance_mut()
726 .ok_or(VmError::CallStackEmpty)?;
727 if running.pinned_slots.binary_search(&dst).is_ok() {
728 return Err(javm_cap::OpError::SlotPinned(dst.get()).into());
729 }
730 running.root_cnode.set(dst, Some(CapHashOrRef::Hash(h)))?;
731 Ok(())
732 }
733
734 fn dispatch_host_open(
735 &mut self,
736 regs: &mut Regs,
737 cache: &mut CacheDirectory,
738 ) -> Result<(), VmError> {
739 let file_id = regs.gpr[7];
740 let dst = SlotIdx((regs.gpr[8] & 0xFF) as u32);
741 let data_ref = self
742 .kernel_assist
743 .host_open(file_id)
744 .ok_or(VmError::Invariant("unknown file id"))?;
745 match &*cache
746 .get(data_ref.clone())
747 .ok_or(VmError::Invariant("file data missing"))?
748 {
749 Cap::Data(_) => {}
750 _ => return Err(VmError::Invariant("file target is not Cap::Data")),
751 }
752 let h = cache.settle(data_ref)?;
753
754 let running = self
755 .stack
756 .running_instance_mut()
757 .ok_or(VmError::CallStackEmpty)?;
758 if running.pinned_slots.binary_search(&dst).is_ok() {
759 return Err(javm_cap::OpError::SlotPinned(dst.get()).into());
760 }
761 running.root_cnode.set(dst, Some(CapHashOrRef::Hash(h)))?;
762 Ok(())
763 }
764
765 fn dispatch_host_save(
766 &mut self,
767 regs: &mut Regs,
768 cache: &CacheDirectory,
769 ) -> Result<(), VmError> {
770 let src = SlotIdx((regs.gpr[7] & 0xFF) as u32);
771 let quota_id = regs.gpr[8];
772 let target = self
773 .stack
774 .running_instance()
775 .ok_or(VmError::CallStackEmpty)?
776 .root_cnode
777 .get(src)
778 .ok_or(VmError::SlotEmpty(src.get()))?;
779 let size = match &*cache
780 .get(target.clone())
781 .ok_or(VmError::Invariant("host_save data missing"))?
782 {
783 Cap::Data(d) => d.content_len(),
784 _ => return Err(VmError::Invariant("host_save source is not Cap::Data")),
785 };
786 let file_id = self
787 .kernel_assist
788 .host_save(target, quota_id, size)
789 .ok_or(VmError::Invariant("host_save failed"))?;
790 regs.gpr[7] = file_id;
791 Ok(())
792 }
793
794 fn dispatch_host_call_cached(
810 &mut self,
811 regs: &mut Regs,
812 cache: &mut CacheDirectory,
813 ) -> Result<(), VmError> {
814 let inst_slot = SlotIdx((regs.gpr[7] & 0xFF) as u32);
815 let endpoint_idx = (regs.gpr[8] & 0xFF) as u8;
816
817 let target_ref = self
819 .stack
820 .running_instance()
821 .ok_or(VmError::CallStackEmpty)?
822 .root_cnode
823 .get(inst_slot)
824 .ok_or(VmError::SlotEmpty(inst_slot.get()))?;
825 let target_arc = cache.get(target_ref.clone());
826 match target_arc.as_deref() {
827 Some(Cap::Instance(_)) => {}
828 _ => return Err(VmError::InstanceNotFound),
829 }
830
831 let scratch = self
833 .stack
834 .running_instance_mut()
835 .ok_or(VmError::CallStackEmpty)?
836 .root_cnode
837 .take(SlotIdx(0))?;
838
839 let (mut child, child_mem, child_regs, _child_gas, _) =
843 self.build_entry(cache, target_ref, endpoint_idx, [0u64; 4], 0)?;
844
845 if let Some(cap) = scratch {
847 child.root_cnode.set(SlotIdx(0), Some(cap))?;
848 }
849
850 child.regs = child_regs;
855 child.mem = child_mem;
856
857 self.stack.push_instance(child)?;
860 Ok(())
861 }
862}
863
864fn data_cap_prefix(data: &DataCap, len: usize) -> Vec<u8> {
865 let actual_len = len.min(data.content_len() as usize);
866 let mut out = vec![0u8; actual_len];
867 match &data.content {
868 DataContent::Inline(bytes) => {
869 let copy_len = actual_len.min(bytes.len());
870 out[..copy_len].copy_from_slice(&bytes[..copy_len]);
871 }
872 DataContent::Paged { page_size, pages } => {
873 let page_size = *page_size as usize;
874 for (page_idx, page) in pages.iter().enumerate() {
875 let start = page_idx * page_size;
876 if start >= actual_len {
877 break;
878 }
879 let end = (start + page_size).min(actual_len);
880 if let javm_cap::page::PageSlot::Loaded(page_ref) = page {
881 let page_bytes = &page_ref.bytes;
882 out[start..end].copy_from_slice(&page_bytes[..end - start]);
883 }
884 }
885 }
886 }
887 out
888}
889
890impl<K: KernelAssist> Vm<K> {
891 fn dispatch_ecall(
896 &mut self,
897 regs: &mut Regs,
898 mem: &mut dyn Memory,
899 cache: Option<&mut CacheDirectory>,
900 ) -> EcallResult {
901 let op = regs.gpr[11] as u32;
902 self.dispatch_ecalli(op, regs, mem, cache)
903 }
904
905 fn dispatch_mgmt(
906 &mut self,
907 op: u32,
908 regs: &mut Regs,
909 cache: Option<&mut CacheDirectory>,
910 ) -> Result<(), VmError> {
911 let a = SlotIdx((regs.gpr[7] & 0xFF) as u32);
912 let b = SlotIdx((regs.gpr[8] & 0xFF) as u32);
913 match op {
914 mgmt_op::COPY => self.mgmt_copy(a, b),
915 mgmt_op::MOVE => self.mgmt_move(a, b),
916 mgmt_op::DROP => self.mgmt_drop(a),
917 mgmt_op::CNODE_SWAP => self.mgmt_cnode_swap(a, b),
918 mgmt_op::CNODE_MINT => {
919 let size_log = (regs.gpr[8] & 0xFF) as u8;
920 self.mgmt_cnode_mint(a, size_log, cache)
921 }
922 _ => Err(VmError::Invariant("unknown MGMT op")),
923 }
924 }
925
926 fn mgmt_copy(&mut self, a: SlotIdx, b: SlotIdx) -> Result<(), VmError> {
927 let running = self
928 .stack
929 .running_instance_mut()
930 .ok_or(VmError::CallStackEmpty)?;
931 if running.pinned_slots.binary_search(&b).is_ok() {
932 return Err(javm_cap::OpError::SlotPinned(b.get()).into());
933 }
934 let src = running
935 .root_cnode
936 .get(a)
937 .ok_or(javm_cap::OpError::SourceEmpty)?;
938 running.root_cnode.set(b, Some(src))?;
939 Ok(())
940 }
941
942 fn mgmt_move(&mut self, a: SlotIdx, b: SlotIdx) -> Result<(), VmError> {
943 let running = self
944 .stack
945 .running_instance_mut()
946 .ok_or(VmError::CallStackEmpty)?;
947 if running.pinned_slots.binary_search(&a).is_ok() {
948 return Err(javm_cap::OpError::SlotPinned(a.get()).into());
949 }
950 if running.pinned_slots.binary_search(&b).is_ok() {
951 return Err(javm_cap::OpError::SlotPinned(b.get()).into());
952 }
953 let src = running
954 .root_cnode
955 .take(a)?
956 .ok_or(javm_cap::OpError::SourceEmpty)?;
957 running.root_cnode.set(b, Some(src))?;
958 Ok(())
959 }
960
961 fn mgmt_drop(&mut self, a: SlotIdx) -> Result<(), VmError> {
962 let running = self
963 .stack
964 .running_instance_mut()
965 .ok_or(VmError::CallStackEmpty)?;
966 if running.pinned_slots.binary_search(&a).is_ok() {
967 return Err(javm_cap::OpError::SlotPinned(a.get()).into());
968 }
969 running.root_cnode.take(a)?;
970 Ok(())
971 }
972
973 fn mgmt_cnode_swap(&mut self, a: SlotIdx, b: SlotIdx) -> Result<(), VmError> {
974 let running = self
975 .stack
976 .running_instance_mut()
977 .ok_or(VmError::CallStackEmpty)?;
978 if running.pinned_slots.binary_search(&a).is_ok() {
979 return Err(javm_cap::OpError::SlotPinned(a.get()).into());
980 }
981 if running.pinned_slots.binary_search(&b).is_ok() {
982 return Err(javm_cap::OpError::SlotPinned(b.get()).into());
983 }
984 let av = running.root_cnode.take(a)?;
985 let bv = running.root_cnode.take(b)?;
986 if let Some(t) = bv {
987 running.root_cnode.set(a, Some(t))?;
988 }
989 if let Some(t) = av {
990 running.root_cnode.set(b, Some(t))?;
991 }
992 Ok(())
993 }
994
995 fn mgmt_cnode_mint(
996 &mut self,
997 dst: SlotIdx,
998 size_log: u8,
999 cache: Option<&mut CacheDirectory>,
1000 ) -> Result<(), VmError> {
1001 let cap = Cap::CNode(javm_cap::CNodeCap::new(size_log)?);
1002 let cap_hash = javm_cap::cap_hash(&cap);
1003 let h = match cache {
1004 Some(cache) => {
1005 cache.put_cap_with_hash(cap_hash, &cap)?;
1006 cap_hash
1007 }
1008 None => cap_hash,
1009 };
1010 let running = self
1011 .stack
1012 .running_instance_mut()
1013 .ok_or(VmError::CallStackEmpty)?;
1014 if running.pinned_slots.binary_search(&dst).is_ok() {
1015 return Err(javm_cap::OpError::SlotPinned(dst.get()).into());
1016 }
1017 running.root_cnode.set(dst, Some(CapHashOrRef::Hash(h)))?;
1018 Ok(())
1019 }
1020}
1021
1022#[cfg(test)]
1023mod tests {
1024 use super::*;
1025 use crate::callstack::{EntryStatus, InstanceEntry};
1026 use crate::kernel_assist::InProcessKernelAssist;
1027 use javm_cap::image::Image;
1028 use javm_cap::{CNodeCap, NUM_REGS};
1029 use javm_exec::{Access, GasCounter, Mem, PAGE_SIZE, PvmProgram, Regs};
1030 use std::sync::Arc;
1031
1032 fn fixture_vm() -> Vm<InProcessKernelAssist> {
1033 let mut vm = Vm::new(InProcessKernelAssist::new());
1034 let mut cnode = CNodeCap::new(4).unwrap();
1035 cnode
1037 .set(SlotIdx(2), Some(CapHashOrRef::Hash([0xAA; 32])))
1038 .unwrap();
1039 let prog = Arc::new(PvmProgram::new(vec![0u8], vec![1u8], vec![], 25).unwrap());
1040 let entry = InstanceEntry {
1041 instance_ref: CapHashOrRef::Hash([1u8; 32]),
1042 image_hash_chain: [1u8; 32],
1043 image_hash: [2u8; 32],
1044 program: prog,
1045 root_cnode: cnode,
1046 yield_marker_slot: None,
1047 pinned_slots: Vec::new(),
1048 regs: Regs::new(),
1049 mem: Mem::new(),
1050 gas: GasCounter::new(1000),
1051 status: EntryStatus::Waiting,
1052 };
1053 vm.stack.push_instance(entry).unwrap();
1054 vm
1055 }
1056
1057 fn handle_cached(
1058 vm: &mut Vm<InProcessKernelAssist>,
1059 cache: &mut CacheDirectory,
1060 op: u32,
1061 regs: &mut Regs,
1062 mem: &mut Mem,
1063 ) -> EcallResult {
1064 let mut handler = CachedEcallHandler { vm, cache };
1065 handler.handle(EcallKind::Ecalli(op), regs, mem)
1066 }
1067
1068 fn publish_data_inline(cache: &mut CacheDirectory, bytes: &[u8]) -> javm_cap::CapHash {
1069 cache.put_cap(&Cap::data_inline(bytes)).unwrap()
1070 }
1071
1072 #[test]
1073 fn mgmt_copy_dispatch_via_ecalli() {
1074 let mut vm = fixture_vm();
1075 let mut regs = Regs::new();
1076 regs.gpr[7] = 2; regs.gpr[8] = 7; let mut mem = Mem::new();
1079 let r = vm.handle(EcallKind::Ecalli(mgmt_op::COPY), &mut regs, &mut mem);
1080 assert!(matches!(r, EcallResult::Continue));
1081 let cnode = &vm.stack.running_instance().unwrap().root_cnode;
1082 assert!(cnode.get(SlotIdx(2)).is_some());
1083 assert!(cnode.get(SlotIdx(7)).is_some());
1084 }
1085
1086 #[test]
1087 fn mgmt_move_dispatch_via_ecalli() {
1088 let mut vm = fixture_vm();
1089 let mut regs = Regs::new();
1090 regs.gpr[7] = 2;
1091 regs.gpr[8] = 9;
1092 let mut mem = Mem::new();
1093 let r = vm.handle(EcallKind::Ecalli(mgmt_op::MOVE), &mut regs, &mut mem);
1094 assert!(matches!(r, EcallResult::Continue));
1095 let cnode = &vm.stack.running_instance().unwrap().root_cnode;
1096 assert!(cnode.get(SlotIdx(2)).is_none()); assert!(cnode.get(SlotIdx(9)).is_some()); }
1099
1100 #[test]
1101 fn mgmt_drop_dispatch_via_ecalli() {
1102 let mut vm = fixture_vm();
1103 let mut regs = Regs::new();
1104 regs.gpr[7] = 2;
1105 let mut mem = Mem::new();
1106 let r = vm.handle(EcallKind::Ecalli(mgmt_op::DROP), &mut regs, &mut mem);
1107 assert!(matches!(r, EcallResult::Continue));
1108 let cnode = &vm.stack.running_instance().unwrap().root_cnode;
1109 assert!(cnode.get(SlotIdx(2)).is_none());
1110 }
1111
1112 #[test]
1113 fn mgmt_cnode_mint_places_hash_at_dst() {
1114 let mut vm = fixture_vm();
1115 let mut regs = Regs::new();
1116 regs.gpr[7] = 5; regs.gpr[8] = 3; let mut mem = Mem::new();
1119 let r = vm.handle(EcallKind::Ecalli(mgmt_op::CNODE_MINT), &mut regs, &mut mem);
1120 assert!(matches!(r, EcallResult::Continue));
1121 let cnode = &vm.stack.running_instance().unwrap().root_cnode;
1122 assert!(matches!(cnode.get(SlotIdx(5)), Some(CapHashOrRef::Hash(_))));
1123 }
1124
1125 #[test]
1126 fn mgmt_cnode_mint_publishes_cnode_when_cache_threaded() {
1127 let mut vm = fixture_vm();
1128 let mut cache = CacheDirectory::new();
1129 let mut regs = Regs::new();
1130 regs.gpr[7] = 5;
1131 regs.gpr[8] = 3;
1132 let mut mem = Mem::new();
1133 let r = handle_cached(
1134 &mut vm,
1135 &mut cache,
1136 mgmt_op::CNODE_MINT,
1137 &mut regs,
1138 &mut mem,
1139 );
1140 assert!(matches!(r, EcallResult::Continue));
1141 let cnode = &vm.stack.running_instance().unwrap().root_cnode;
1142 let target = cnode.get(SlotIdx(5)).unwrap();
1143 assert!(matches!(cache.get(target).as_deref(), Some(Cap::CNode(_))));
1144 }
1145
1146 #[test]
1147 fn mgmt_cnode_swap_swaps_slots() {
1148 let mut vm = fixture_vm();
1149 vm.stack
1150 .running_instance_mut()
1151 .unwrap()
1152 .root_cnode
1153 .set(SlotIdx(3), Some(CapHashOrRef::Hash([0xBB; 32])))
1154 .unwrap();
1155 let mut regs = Regs::new();
1156 regs.gpr[7] = 2;
1157 regs.gpr[8] = 3;
1158 let mut mem = Mem::new();
1159 let r = vm.handle(EcallKind::Ecalli(mgmt_op::CNODE_SWAP), &mut regs, &mut mem);
1160 assert!(matches!(r, EcallResult::Continue));
1161 let cnode = &vm.stack.running_instance().unwrap().root_cnode;
1162 let s2 = cnode.get(SlotIdx(2)).unwrap();
1163 let s3 = cnode.get(SlotIdx(3)).unwrap();
1164 assert_eq!(s2, CapHashOrRef::Hash([0xBB; 32]));
1165 assert_eq!(s3, CapHashOrRef::Hash([0xAA; 32]));
1166 }
1167
1168 #[test]
1169 fn mgmt_op_on_empty_stack_traps() {
1170 let mut vm: Vm<InProcessKernelAssist> = Vm::new(InProcessKernelAssist::new());
1171 let mut regs = Regs::new();
1172 let mut mem = Mem::new();
1173 let r = vm.handle(EcallKind::Ecalli(mgmt_op::DROP), &mut regs, &mut mem);
1174 assert!(matches!(r, EcallResult::Exit(ExitReason::Trap)));
1175 }
1176
1177 #[test]
1178 fn ecalli_reply_zero_exits_halt() {
1179 let mut vm = fixture_vm();
1180 let mut regs = Regs::new();
1181 let mut mem = Mem::new();
1182 let r = vm.handle(EcallKind::Ecalli(0), &mut regs, &mut mem);
1183 assert!(matches!(r, EcallResult::Exit(ExitReason::Halt)));
1184 }
1185
1186 #[test]
1187 fn plain_ecall_reads_op_from_phi11() {
1188 let mut vm = fixture_vm();
1189 let mut regs = Regs::new();
1190 regs.gpr[11] = mgmt_op::DROP as u64;
1191 regs.gpr[7] = 2;
1192 let mut mem = Mem::new();
1193 let r = vm.handle(EcallKind::Ecall, &mut regs, &mut mem);
1194 assert!(matches!(r, EcallResult::Continue));
1195 assert!(
1196 vm.stack
1197 .running_instance()
1198 .unwrap()
1199 .root_cnode
1200 .get(SlotIdx(2))
1201 .is_none()
1202 );
1203 }
1204
1205 #[test]
1206 fn unknown_op_continues_silently() {
1207 let mut vm = fixture_vm();
1208 let mut regs = Regs::new();
1209 let mut mem = Mem::new();
1210 let r = vm.handle(EcallKind::Ecalli(999), &mut regs, &mut mem);
1211 assert!(matches!(r, EcallResult::Continue));
1212 }
1213
1214 #[test]
1215 fn set_image_extends_chain_hash() {
1216 let mut vm = fixture_vm();
1217 let original_chain = vm.stack.running_instance().unwrap().image_hash_chain;
1218 let mut regs = Regs::new();
1219 regs.gpr[7] = 2;
1220 let mut mem = Mem::new();
1221 let r = vm.handle(EcallKind::Ecalli(host_op::SET_IMAGE), &mut regs, &mut mem);
1222 assert!(matches!(r, EcallResult::Continue));
1223 let new_chain = vm.stack.running_instance().unwrap().image_hash_chain;
1224 assert_ne!(original_chain, new_chain);
1225 }
1226
1227 #[test]
1228 fn set_image_reloads_program_from_cache() {
1229 let mut vm = fixture_vm();
1230 let mut cache = CacheDirectory::new();
1231 let mut img = Image::empty();
1232 img.code = vec![10u8, 0];
1233 img.packed_bitmask = vec![0b01u8];
1234 let image_hash = cache
1235 .put_cap(&Cap::image_with_slots(&img, &[], &[]).unwrap())
1236 .unwrap();
1237 vm.stack
1238 .running_instance_mut()
1239 .unwrap()
1240 .root_cnode
1241 .set(SlotIdx(3), Some(CapHashOrRef::Hash(image_hash)))
1242 .unwrap();
1243
1244 let mut regs = Regs::new();
1245 regs.gpr[7] = 3;
1246 let mut mem = Mem::new();
1247 let r = handle_cached(&mut vm, &mut cache, host_op::SET_IMAGE, &mut regs, &mut mem);
1248 assert!(
1249 matches!(r, EcallResult::Exit(ExitReason::HostCall(op)) if op == host_op::SET_IMAGE)
1250 );
1251 let running = vm.stack.running_instance().unwrap();
1252 assert_eq!(running.image_hash, image_hash);
1253 assert_eq!(running.program.code, vec![10u8, 0]);
1254 }
1255
1256 #[test]
1257 fn derive_spawn_mints_extended_chain_target() {
1258 let mut vm = fixture_vm();
1259 let parent_chain = vm.stack.running_instance().unwrap().image_hash_chain;
1260 let mut regs = Regs::new();
1261 regs.gpr[7] = 2;
1262 regs.gpr[8] = 5;
1263 let mut mem = Mem::new();
1264 let r = vm.handle(
1265 EcallKind::Ecalli(host_op::DERIVE_SPAWN),
1266 &mut regs,
1267 &mut mem,
1268 );
1269 assert!(matches!(r, EcallResult::Continue));
1270 let cnode = &vm.stack.running_instance().unwrap().root_cnode;
1271 let child = cnode.get(SlotIdx(5)).unwrap();
1272 match child {
1273 CapHashOrRef::Hash(h) => assert_ne!(h, parent_chain),
1274 _ => panic!("expected Hash form at dst"),
1275 }
1276 }
1277
1278 #[test]
1279 fn host_same_type_compares_chain() {
1280 let mut vm = fixture_vm();
1281 vm.stack
1282 .running_instance_mut()
1283 .unwrap()
1284 .root_cnode
1285 .set(SlotIdx(3), Some(CapHashOrRef::Hash([0xAA; 32])))
1286 .unwrap();
1287 vm.stack
1288 .running_instance_mut()
1289 .unwrap()
1290 .root_cnode
1291 .set(SlotIdx(4), Some(CapHashOrRef::Hash([0x99; 32])))
1292 .unwrap();
1293 let mut regs = Regs::new();
1295 regs.gpr[7] = 2;
1296 regs.gpr[8] = 3;
1297 let mut mem = Mem::new();
1298 let r = vm.handle(
1299 EcallKind::Ecalli(host_op::HOST_SAME_TYPE),
1300 &mut regs,
1301 &mut mem,
1302 );
1303 assert!(matches!(r, EcallResult::Continue));
1304 assert_eq!(regs.gpr[7], 1);
1305
1306 regs.gpr[7] = 2;
1307 regs.gpr[8] = 4;
1308 let r = vm.handle(
1309 EcallKind::Ecalli(host_op::HOST_SAME_TYPE),
1310 &mut regs,
1311 &mut mem,
1312 );
1313 assert!(matches!(r, EcallResult::Continue));
1314 assert_eq!(regs.gpr[7], 0);
1315 }
1316
1317 #[test]
1318 fn host_type_of_publishes_type_cap() {
1319 let mut vm = fixture_vm();
1320 let mut cache = CacheDirectory::new();
1321 let image_hash = cache
1324 .put_cap(&Cap::image_with_slots(&Image::empty(), &[], &[]).unwrap())
1325 .unwrap();
1326 let cnode_hash = cache.put_cap(&Cap::empty_cnode(0).unwrap()).unwrap();
1327 let inst_hash = cache
1328 .put_cap(&Cap::instance_with_overlays(
1329 [0x42; 32],
1330 image_hash,
1331 cnode_hash,
1332 &[],
1333 0,
1334 [0u64; NUM_REGS],
1335 0,
1336 0,
1337 ))
1338 .unwrap();
1339 vm.stack
1340 .running_instance_mut()
1341 .unwrap()
1342 .root_cnode
1343 .set(SlotIdx(3), Some(CapHashOrRef::Hash(inst_hash)))
1344 .unwrap();
1345
1346 let mut regs = Regs::new();
1347 regs.gpr[7] = 3;
1348 regs.gpr[8] = 6;
1349 let mut mem = Mem::new();
1350 let r = handle_cached(
1351 &mut vm,
1352 &mut cache,
1353 host_op::HOST_TYPE_OF,
1354 &mut regs,
1355 &mut mem,
1356 );
1357 assert!(matches!(r, EcallResult::Continue));
1358 let target = vm
1359 .stack
1360 .running_instance()
1361 .unwrap()
1362 .root_cnode
1363 .get(SlotIdx(6))
1364 .unwrap();
1365 assert!(matches!(
1366 cache.get(target).as_deref(),
1367 Some(Cap::Type(TypeCap {
1368 image_hash_chain
1369 })) if *image_hash_chain == [0x42; 32]
1370 ));
1371 }
1372
1373 #[test]
1374 fn host_read_data_cap_copies_bytes_from_cache() {
1375 let mut vm = fixture_vm();
1381 let mut cache = CacheDirectory::new();
1382 let data_hash = publish_data_inline(&mut cache, b"hello");
1383 vm.stack
1384 .running_instance_mut()
1385 .unwrap()
1386 .root_cnode
1387 .set(SlotIdx(3), Some(CapHashOrRef::Hash(data_hash)))
1388 .unwrap();
1389 let mut mem = Mem::new();
1390 mem.map_region(0, PAGE_SIZE as u64, Access::ReadWrite, None)
1391 .unwrap();
1392 let mut regs = Regs::new();
1393 regs.gpr[7] = 3;
1394 regs.gpr[8] = 16;
1395 regs.gpr[9] = 8;
1396
1397 let r = handle_cached(
1398 &mut vm,
1399 &mut cache,
1400 host_op::HOST_READ_DATA_CAP,
1401 &mut regs,
1402 &mut mem,
1403 );
1404 assert!(matches!(r, EcallResult::Continue));
1405 assert_eq!(regs.gpr[7], 8);
1408 assert_eq!(mem.read(16, 8).unwrap(), b"hello\0\0\0");
1409 }
1410
1411 #[test]
1412 fn host_mint_data_cap_publishes_page_padded_bytes() {
1413 let mut vm = fixture_vm();
1417 let mut cache = CacheDirectory::new();
1418 vm.kernel_assist.storage_quota_set(0, 8192);
1419 let mut mem = Mem::new();
1420 mem.map_region(0, PAGE_SIZE as u64, Access::ReadWrite, None)
1421 .unwrap();
1422 mem.write(32, b"abc\0\0").unwrap();
1423 let mut regs = Regs::new();
1424 regs.gpr[7] = 32;
1425 regs.gpr[8] = 5;
1426 regs.gpr[9] = 0;
1427 regs.gpr[10] = 6;
1428
1429 let r = handle_cached(
1430 &mut vm,
1431 &mut cache,
1432 host_op::HOST_MINT_DATA_CAP,
1433 &mut regs,
1434 &mut mem,
1435 );
1436 assert!(matches!(r, EcallResult::Continue));
1437 assert_eq!(vm.kernel_assist.storage_quota_get(0), 4096);
1439 let target = vm
1440 .stack
1441 .running_instance()
1442 .unwrap()
1443 .root_cnode
1444 .get(SlotIdx(6))
1445 .unwrap();
1446 let target_arc = cache.get(target).unwrap();
1447 match &*target_arc {
1448 Cap::Data(d) => {
1449 assert_eq!(d.content_len(), javm_cap::PAGE_SIZE as u64);
1450 assert_eq!(data_cap_prefix(d, 5), b"abc\0\0");
1453 }
1454 _ => panic!("expected Data cap"),
1455 }
1456 }
1457
1458 #[test]
1459 fn host_open_places_registered_file_data_in_slot() {
1460 let mut vm = fixture_vm();
1461 let mut cache = CacheDirectory::new();
1462 let data_hash = publish_data_inline(&mut cache, b"file");
1463 vm.kernel_assist
1464 .register_file(9, CapHashOrRef::Hash(data_hash));
1465 let mut regs = Regs::new();
1466 regs.gpr[7] = 9;
1467 regs.gpr[8] = 6;
1468 let mut mem = Mem::new();
1469 let r = handle_cached(&mut vm, &mut cache, host_op::HOST_OPEN, &mut regs, &mut mem);
1470 assert!(matches!(r, EcallResult::Continue));
1471 let target = vm
1472 .stack
1473 .running_instance()
1474 .unwrap()
1475 .root_cnode
1476 .get(SlotIdx(6))
1477 .unwrap();
1478 assert!(matches!(cache.get(target).as_deref(), Some(Cap::Data(_))));
1479 }
1480
1481 #[test]
1482 fn host_save_debits_actual_data_size_and_returns_file_id() {
1483 let mut vm = fixture_vm();
1487 let mut cache = CacheDirectory::new();
1488 let data_hash = publish_data_inline(&mut cache, b"stored");
1489 vm.kernel_assist.storage_quota_set(0, 8192);
1490 vm.stack
1491 .running_instance_mut()
1492 .unwrap()
1493 .root_cnode
1494 .set(SlotIdx(3), Some(CapHashOrRef::Hash(data_hash)))
1495 .unwrap();
1496 let mut regs = Regs::new();
1497 regs.gpr[7] = 3;
1498 regs.gpr[8] = 0;
1499 let mut mem = Mem::new();
1500 let r = handle_cached(&mut vm, &mut cache, host_op::HOST_SAVE, &mut regs, &mut mem);
1501 assert!(matches!(r, EcallResult::Continue));
1502 assert_eq!(regs.gpr[7], 1);
1503 assert_eq!(vm.kernel_assist.storage_quota_get(0), 4096);
1505 assert_eq!(
1506 vm.kernel_assist.host_open(1),
1507 Some(CapHashOrRef::Hash(data_hash))
1508 );
1509 }
1510
1511 #[test]
1512 fn make_image_stubbed_traps() {
1513 let mut vm = fixture_vm();
1514 let mut regs = Regs::new();
1515 let mut mem = Mem::new();
1516 let r = vm.handle(EcallKind::Ecalli(host_op::MAKE_IMAGE), &mut regs, &mut mem);
1517 assert!(matches!(r, EcallResult::Exit(ExitReason::Trap)));
1518 }
1519
1520 #[test]
1521 fn cache_dependent_host_calls_without_cache_still_trap() {
1522 for op in [
1525 host_op::HOST_TYPE_OF,
1526 host_op::HOST_READ_DATA_CAP,
1527 host_op::HOST_MINT_DATA_CAP,
1528 host_op::HOST_OPEN,
1529 host_op::HOST_SAVE,
1530 host_op::HOST_CALL,
1531 ] {
1532 let mut vm = fixture_vm();
1533 let mut regs = Regs::new();
1534 let mut mem = Mem::new();
1535 let r = vm.handle(EcallKind::Ecalli(op), &mut regs, &mut mem);
1536 assert!(
1537 matches!(r, EcallResult::Exit(ExitReason::Trap)),
1538 "op {} should trap (cache-dependent host call)",
1539 op
1540 );
1541 }
1542 }
1543
1544 #[test]
1550 fn derive_spawn_cached_publishes_child_instance() {
1551 let mut vm = fixture_vm();
1552 let mut cache = CacheDirectory::new();
1553
1554 let mut child_img = javm_cap::image::Image::empty();
1556 child_img.code = vec![10u8, 0]; child_img.packed_bitmask = vec![0b01u8];
1558 let image_hash = cache
1559 .put_cap(&Cap::image_with_slots(&child_img, &[], &[]).unwrap())
1560 .unwrap();
1561
1562 let prep_cnode_hash = cache.put_cap(&Cap::empty_cnode(4).unwrap()).unwrap();
1564
1565 let parent_chain = [0xC1; 32];
1567 {
1568 let running = vm.stack.running_instance_mut().unwrap();
1569 running.image_hash_chain = parent_chain;
1570 running
1571 .root_cnode
1572 .set(SlotIdx(3), Some(CapHashOrRef::Hash(image_hash)))
1573 .unwrap();
1574 running
1575 .root_cnode
1576 .set(SlotIdx(4), Some(CapHashOrRef::Hash(prep_cnode_hash)))
1577 .unwrap();
1578 }
1579
1580 let mut regs = Regs::new();
1581 regs.gpr[7] = 3; regs.gpr[8] = 4; regs.gpr[9] = 7; let mut mem = Mem::new();
1585 let mut handler = CachedEcallHandler {
1586 vm: &mut vm,
1587 cache: &mut cache,
1588 };
1589 let r = handler.handle(
1590 EcallKind::Ecalli(host_op::DERIVE_SPAWN),
1591 &mut regs,
1592 &mut mem,
1593 );
1594 assert!(matches!(r, EcallResult::Continue), "got {:?}", r);
1595
1596 let new_target = vm
1598 .stack
1599 .running_instance()
1600 .unwrap()
1601 .root_cnode
1602 .get(SlotIdx(7))
1603 .expect("dst slot populated");
1604 let new_instance_hash = match new_target {
1605 CapHashOrRef::Hash(h) => h,
1606 _ => panic!("expected Hash target"),
1607 };
1608
1609 let cap = cache.get(new_target).expect("instance in cache");
1611 let inst = match &*cap {
1612 Cap::Instance(i) => i,
1613 _ => panic!("expected Cap::Instance"),
1614 };
1615 let expected_chain = Blake2b256::hash_pair(&parent_chain, &image_hash);
1616 assert_eq!(inst.image_hash_chain, expected_chain);
1617 assert_eq!(inst.image_hash, image_hash);
1618 assert!(matches!(inst.root_cnode, CapHashOrRef::Hash(_)));
1619
1620 assert!(
1622 vm.stack
1623 .running_instance()
1624 .unwrap()
1625 .root_cnode
1626 .get(SlotIdx(4))
1627 .is_none()
1628 );
1629
1630 assert_eq!(new_instance_hash, javm_cap::cap_hash(&cap));
1633 }
1634
1635 #[test]
1639 fn host_call_cached_pushes_child_and_moves_slot0() {
1640 let mut vm = fixture_vm();
1641 let mut cache = CacheDirectory::new();
1642
1643 let mut child_img = javm_cap::image::Image::empty();
1646 child_img.code = vec![10u8, 0];
1647 child_img.packed_bitmask = vec![0b01u8];
1648 let image_hash = cache
1649 .put_cap(&Cap::image_with_slots(&child_img, &[], &[]).unwrap())
1650 .unwrap();
1651 let cnode_hash = cache.put_cap(&Cap::empty_cnode(4).unwrap()).unwrap();
1652 let child_instance_hash = cache
1653 .put_cap(&Cap::instance_with_overlays(
1654 [0xCC; 32],
1655 image_hash,
1656 cnode_hash,
1657 &[],
1658 0,
1659 [0u64; javm_cap::NUM_REGS],
1660 0,
1661 0,
1662 ))
1663 .unwrap();
1664
1665 let marker_hash = [0xAB; 32];
1668 {
1669 let running = vm.stack.running_instance_mut().unwrap();
1670 running
1671 .root_cnode
1672 .set(SlotIdx(9), Some(CapHashOrRef::Hash(child_instance_hash)))
1673 .unwrap();
1674 running
1675 .root_cnode
1676 .set(SlotIdx(0), Some(CapHashOrRef::Hash(marker_hash)))
1677 .unwrap();
1678 }
1679
1680 let mut regs = Regs::new();
1681 regs.gpr[7] = 9; regs.gpr[8] = 0; let mut mem = Mem::new();
1684 let mut handler = CachedEcallHandler {
1685 vm: &mut vm,
1686 cache: &mut cache,
1687 };
1688 let r = handler.handle(EcallKind::Ecalli(host_op::HOST_CALL), &mut regs, &mut mem);
1689 assert!(
1690 matches!(r, EcallResult::Exit(ExitReason::HostCall(op)) if op == host_op::HOST_CALL),
1691 "got {:?}",
1692 r,
1693 );
1694
1695 assert_eq!(vm.stack.len(), 2);
1697 assert_eq!(vm.stack.entries()[0].status(), EntryStatus::Waiting);
1698 assert_eq!(vm.stack.entries()[1].status(), EntryStatus::Running);
1699 let child = vm.stack.running_instance().unwrap();
1700 assert_eq!(child.image_hash, image_hash);
1702 assert_eq!(
1704 child.root_cnode.get(SlotIdx(0)),
1705 Some(CapHashOrRef::Hash(marker_hash))
1706 );
1707 let parent = match &vm.stack.entries()[0] {
1709 Entry::Instance(e) => e.as_ref(),
1710 _ => panic!("entry 0 not Instance"),
1711 };
1712 assert!(parent.root_cnode.get(SlotIdx(0)).is_none());
1713 }
1714}