use crate::{Arg, Engine, HostCallError, InnerVm, OuterVm, PrettyUint, ReadVideoFrameError};
use alloc::vec;
use corevm_host::{
flags, AudioMode, AudioSampleFormat, GuestId, InputStreams, InputVideoFrameFormat, Outcome,
OutputStream, VideoMode, VideoModeOptions,
};
use corevm_types::{RecvHostMessageOutcome, RecvMessageOutcome};
use log::trace;
trait Trace {
#[must_use]
fn trace(self, callback: impl FnOnce(&Self)) -> Self;
}
impl<O: core::fmt::Debug, E: core::fmt::Debug> Trace for Result<O, E> {
#[inline]
fn trace(self, callback: impl FnOnce(&Self)) -> Self {
callback(&self);
self
}
}
pub fn gas<O: OuterVm>(engine: &mut Engine<O>) -> Result<(), HostCallError> {
let gas = engine.args.gas;
engine.args.set_return_value(gas);
trace!("Call gas() = {gas}");
Ok(())
}
pub fn alloc<O: OuterVm>(engine: &mut Engine<O>) -> Result<(), HostCallError> {
let mut i = 0;
let size: u32 = Arg::get(&engine.args, &mut i);
do_alloc(engine, size).trace(|result| {
trace!(
"Call alloc(size={size}) = {:?}",
result.as_ref().map(|address| engine.memory_map.pretty_address(*address)),
)
})?;
Ok(())
}
#[inline]
fn do_alloc<O: OuterVm>(engine: &mut Engine<O>, size: u32) -> Result<u32, HostCallError> {
let (address, _size) = engine.map_pages(size)?.unwrap_or_default();
engine.args.set_return_value(address);
Ok(address)
}
pub fn free<O: OuterVm>(engine: &mut Engine<O>) -> Result<(), HostCallError> {
let mut i = 0;
let address: u32 = Arg::get(&engine.args, &mut i);
let size: u32 = Arg::get(&engine.args, &mut i);
do_free(engine, address, size).trace(|result| {
trace!(
"Call free(address={:?}, size={size}) = {result:?}",
engine.memory_map.pretty_address(address)
);
})
}
#[inline]
fn do_free<O: OuterVm>(
engine: &mut Engine<O>,
address: u32,
size: u32,
) -> Result<(), HostCallError> {
engine.validate_guest_can_alloc(address, size)?;
engine.unmap_pages(address, size)?;
Ok(())
}
pub fn yield_console_data<O: OuterVm>(engine: &mut Engine<O>) -> Result<(), HostCallError> {
let mut i = 0;
let stream: u64 = Arg::get(&engine.args, &mut i);
let inner_src: u32 = Arg::get(&engine.args, &mut i);
let length: u32 = Arg::get(&engine.args, &mut i);
do_yield_console_data(engine, stream, inner_src, length).trace(|result| {
trace!(
"Call yield_console_data(stream={:?}, buf={:?}, len={length}) = {result:?}",
console_output_stream(stream).ok_or(stream),
engine.memory_map.pretty_address(inner_src)
);
})
}
#[inline]
fn console_output_stream(stream: u64) -> Option<OutputStream> {
match stream {
1 => Some(OutputStream::Stdout),
2 => Some(OutputStream::Stderr),
_ => None,
}
}
#[inline]
fn do_yield_console_data<O: OuterVm>(
engine: &mut Engine<O>,
stream: u64,
inner_src: u32,
length: u32,
) -> Result<(), HostCallError> {
let stream = console_output_stream(stream).ok_or(Outcome::Panic)?;
engine.validate_guest_can_read(inner_src, length)?;
engine.append_output(stream, inner_src, length)?;
Ok(())
}
pub fn yield_video_frame<O: OuterVm>(engine: &mut Engine<O>) -> Result<(), HostCallError> {
let mut i = 0;
let inner_src: u32 = Arg::get(&engine.args, &mut i);
let length: u32 = Arg::get(&engine.args, &mut i);
do_yield_video_frame(engine, inner_src, length).trace(|result| {
trace!(
"Call yield_video_frame(buf={:?}, len={length}) = {result:?}",
engine.memory_map.pretty_address(inner_src)
);
})
}
#[inline]
fn do_yield_video_frame<O: OuterVm>(
engine: &mut Engine<O>,
inner_src: u32,
length: u32,
) -> Result<(), HostCallError> {
engine.validate_guest_can_read(inner_src, length)?;
engine.append_output(OutputStream::Video, inner_src, length)?;
engine.num_video_frames += 1;
engine.advance_time()?;
Ok(())
}
pub fn video_mode<O: OuterVm>(engine: &mut Engine<O>) -> Result<(), HostCallError> {
let mut i = 0;
let width = Arg::get(&engine.args, &mut i);
let height = Arg::get(&engine.args, &mut i);
let refresh_rate = Arg::get(&engine.args, &mut i);
let options = Arg::get(&engine.args, &mut i);
do_video_mode(engine, width, height, refresh_rate, options).trace(|result| {
trace!(
"Call video_mode(width={width}px, height={height}px, \
refresh_rate={refresh_rate}fps, options={options:?}) = {result:?}",
options = VideoModeOptions::try_from(options).map_err(|_| options),
);
})
}
#[inline]
fn do_video_mode<O: OuterVm>(
engine: &mut Engine<O>,
width: u16,
height: u16,
refresh_rate: u16,
options: u64,
) -> Result<(), HostCallError> {
let options = VideoModeOptions::try_from(options).map_err(|_| Outcome::Panic)?;
let video = VideoMode::new(width, height, refresh_rate, options).map_err(|_| Outcome::Panic)?;
engine.work_output.set_output_video_mode(Some(video))?;
Ok(())
}
pub fn yield_audio_samples<O: OuterVm>(engine: &mut Engine<O>) -> Result<(), HostCallError> {
let mut i = 0;
let inner_src: u32 = Arg::get(&engine.args, &mut i);
let length: u32 = Arg::get(&engine.args, &mut i);
do_yield_audio_samples(engine, inner_src, length).trace(|result| {
trace!(
"Call yield_audio_samples(buf={:?}, len={length}) = {result:?}",
engine.memory_map.pretty_address(inner_src)
);
})
}
#[inline]
fn do_yield_audio_samples<O: OuterVm>(
engine: &mut Engine<O>,
inner_src: u32,
length: u32,
) -> Result<(), HostCallError> {
engine.validate_guest_can_read(inner_src, length)?;
engine.append_output(OutputStream::Audio, inner_src, length)?;
engine.num_audio_bytes += u64::from(length);
engine.advance_time()?;
Ok(())
}
pub fn audio_mode<O: OuterVm>(engine: &mut Engine<O>) -> Result<(), HostCallError> {
let mut i = 0;
let channels = Arg::get(&engine.args, &mut i);
let sample_rate = Arg::get(&engine.args, &mut i);
let sample_format = Arg::get(&engine.args, &mut i);
do_audio_mode(engine, sample_rate, channels, sample_format).trace(|result| {
trace!(
"Call audio_mode(channels={channels}, sample_rate={sample_rate}Hz, format={format:?}) = {result:?}",
format = AudioSampleFormat::try_from(sample_format).map_err(|_| sample_format),
)
})
}
#[inline]
fn do_audio_mode<O: OuterVm>(
engine: &mut Engine<O>,
sample_rate: u32,
channels: u8,
sample_format: u8,
) -> Result<(), HostCallError> {
let sample_format = sample_format.try_into().map_err(|_| Outcome::Panic)?;
let audio = AudioMode::new(sample_rate, channels, sample_format).map_err(|_| Outcome::Panic)?;
engine.work_output.set_output_audio_mode(Some(audio))?;
Ok(())
}
pub fn recv_message<O: OuterVm>(engine: &mut Engine<O>) -> Result<(), HostCallError> {
let mut i = 0;
let inner_src: u32 = Arg::get(&engine.args, &mut i);
let len: u32 = Arg::get(&engine.args, &mut i);
let flags: u64 = Arg::get(&engine.args, &mut i);
let flags = flags::RecvMessage::from_bits_truncate(flags);
do_recv_message(engine, inner_src, len, flags).trace(|result| {
trace!(
"Call recv_message(buf={:?}, len={len}, flags={flags:?}) = {result:?}",
engine.memory_map.pretty_address(inner_src),
);
})?;
Ok(())
}
#[inline]
fn do_recv_message<O: OuterVm>(
engine: &mut Engine<O>,
inner_src: u32,
buf_len: u32,
flags: flags::RecvMessage,
) -> Result<RecvMessageOutcome, HostCallError> {
let outcome = if inner_src == 0 {
get_message_length_and_source(engine, flags)?
} else {
copy_message_to_buffer(engine, inner_src, buf_len, flags)?
};
engine.args.set_return_value(outcome);
Ok(outcome)
}
fn get_message_length_and_source<O: OuterVm>(
engine: &mut Engine<O>,
flags: flags::RecvMessage,
) -> Result<RecvMessageOutcome, HostCallError> {
match engine.input.peek_service_message()? {
Some((message, contents)) =>
Ok(RecvMessageOutcome::new(contents.len() as u32, message.source)
.map_err(|_| Outcome::Panic)?),
None if flags.contains(flags::RecvMessage::NON_BLOCKING) => Ok(RecvMessageOutcome::none()),
None => Err(Outcome::WaitingForInput(InputStreams::SERVICE_MESSAGES).into()),
}
}
fn copy_message_to_buffer<O: OuterVm>(
engine: &mut Engine<O>,
inner_src: u32,
buf_len: u32,
flags: flags::RecvMessage,
) -> Result<RecvMessageOutcome, HostCallError> {
while let Some((contents_len, source)) =
get_message_length_and_source(engine, flags)?.into_inner()
{
if buf_len < contents_len {
if flags.contains(flags::RecvMessage::DISCARD) {
continue;
}
return Ok(RecvMessageOutcome::new(contents_len, source).map_err(|_| Outcome::Panic)?);
}
engine.validate_guest_can_write(inner_src, buf_len)?;
match engine.input.pop_service_message()? {
Some((message, contents)) => {
let source = message.source;
let contents_len = contents.len() as u32;
if contents_len != 0 {
engine.touch_once_range(inner_src, contents_len)?;
engine.inner_vm.poke(&contents[..], inner_src)?;
}
engine.work_output.update_processed_service_messages(
|messages| messages.push(message),
|messages| {
messages.pop();
},
)?;
return Ok(
RecvMessageOutcome::new(contents_len, source).map_err(|_| Outcome::Panic)?
);
},
None if flags.contains(flags::RecvMessage::NON_BLOCKING) =>
return Ok(RecvMessageOutcome::none()),
None => return Err(Outcome::WaitingForInput(InputStreams::SERVICE_MESSAGES).into()),
}
}
Ok(RecvMessageOutcome::none())
}
pub fn send_message<O: OuterVm>(engine: &mut Engine<O>) -> Result<(), HostCallError> {
let mut i = 0;
let guest_id = Arg::get(&engine.args, &mut i);
let inner_src = Arg::get(&engine.args, &mut i);
let length = Arg::get(&engine.args, &mut i);
do_send_message(engine, guest_id, inner_src, length).trace(|result| {
trace!(
"Call send_message(guest_id={guest_id:#x}, buf={:?}, len={length}) = {result:?}",
engine.memory_map.pretty_address(inner_src),
);
})
}
#[inline]
fn do_send_message<O: OuterVm>(
engine: &mut Engine<O>,
guest_id: GuestId,
inner_src: u32,
length: u32,
) -> Result<(), HostCallError> {
engine.validate_guest_can_read(inner_src, length)?;
let mut buf = vec![0_u8; length as usize];
engine.inner_vm.peek_into(&mut buf[..], inner_src)?;
engine.work_output.update_outgoing_messages(
|outgoing_messages| outgoing_messages.push((guest_id, buf)),
|outgoing_messages| {
outgoing_messages.pop();
},
)?;
Ok(())
}
pub fn recv_host_message<O: OuterVm>(engine: &mut Engine<O>) -> Result<(), HostCallError> {
let mut i = 0;
let inner_src: u32 = Arg::get(&engine.args, &mut i);
let len: u32 = Arg::get(&engine.args, &mut i);
let flags: u64 = Arg::get(&engine.args, &mut i);
let flags = flags::RecvMessage::from_bits_truncate(flags);
do_recv_host_message(engine, inner_src, len, flags).trace(|result| {
trace!(
"Call recv_host_message(buf={:?}, len={len}, flags={flags:?}) = {result:?}",
engine.memory_map.pretty_address(inner_src),
);
})?;
Ok(())
}
#[inline]
fn do_recv_host_message<O: OuterVm>(
engine: &mut Engine<O>,
inner_src: u32,
buf_len: u32,
flags: flags::RecvMessage,
) -> Result<RecvHostMessageOutcome, HostCallError> {
let outcome = if inner_src == 0 {
let len = get_host_message_length(engine, flags)?;
match len {
Some(len) => RecvHostMessageOutcome::new(len).map_err(|_| Outcome::Panic)?,
None => RecvHostMessageOutcome::none(),
}
} else {
copy_host_message_to_buffer(engine, inner_src, buf_len, flags)?
};
engine.args.set_return_value(outcome);
Ok(outcome)
}
fn get_host_message_length<O: OuterVm>(
engine: &mut Engine<O>,
flags: flags::RecvMessage,
) -> Result<Option<u32>, HostCallError> {
match engine.input.peek_host_message()? {
Some(contents) => Ok(Some(contents.len() as u32)),
None if flags.contains(flags::RecvMessage::NON_BLOCKING) => Ok(None),
None => Err(Outcome::WaitingForInput(InputStreams::HOST_MESSAGES).into()),
}
}
fn copy_host_message_to_buffer<O: OuterVm>(
engine: &mut Engine<O>,
inner_src: u32,
buf_len: u32,
flags: flags::RecvMessage,
) -> Result<RecvHostMessageOutcome, HostCallError> {
while let Some(contents_len) = get_host_message_length(engine, flags)? {
if buf_len < contents_len {
if flags.contains(flags::RecvMessage::DISCARD) {
continue;
}
return Ok(RecvHostMessageOutcome::new(contents_len).map_err(|_| Outcome::Panic)?);
}
engine.validate_guest_can_write(inner_src, buf_len)?;
match engine.input.pop_host_message()? {
Some(contents) => {
let contents_len = contents.len() as u32;
if contents_len != 0 {
engine.touch_once_range(inner_src, contents_len)?;
engine.inner_vm.poke(&contents[..], inner_src)?;
}
return Ok(RecvHostMessageOutcome::new(contents_len).map_err(|_| Outcome::Panic)?);
},
None if flags.contains(flags::RecvMessage::NON_BLOCKING) =>
return Ok(RecvHostMessageOutcome::none()),
None => return Err(Outcome::WaitingForInput(InputStreams::HOST_MESSAGES).into()),
}
}
Ok(RecvHostMessageOutcome::none())
}
pub fn poll<O: OuterVm>(engine: &mut Engine<O>) -> Result<(), HostCallError> {
let mut i = 0;
let streams = Arg::get(&engine.args, &mut i);
let flags = Arg::get(&engine.args, &mut i);
let streams = InputStreams::from_bits_truncate(streams);
let flags = flags::Poll::from_bits_truncate(flags);
do_poll(engine, streams, flags).trace(|result| {
trace!("Call poll(streams={streams:?}, flags={flags:?}) = {result:?}");
})?;
Ok(())
}
fn do_poll<O: OuterVm>(
engine: &mut Engine<O>,
requested: InputStreams,
flags: flags::Poll,
) -> Result<InputStreams, HostCallError> {
let mut available = InputStreams::empty();
if requested.contains(InputStreams::SERVICE_MESSAGES) &&
engine.input.peek_service_message()?.is_some()
{
available.insert(InputStreams::SERVICE_MESSAGES);
}
if requested.contains(InputStreams::HOST_MESSAGES) &&
engine.input.peek_host_message()?.is_some()
{
available.insert(InputStreams::HOST_MESSAGES);
}
if requested.contains(InputStreams::CONSOLE) && !engine.input.console_is_empty()? {
available.insert(InputStreams::CONSOLE);
}
if requested.contains(InputStreams::VIDEO) {
let Some(mode) = engine.exec.video_input else {
log::debug!("No video input");
return Err(Outcome::Panic.into());
};
if !engine.input.video_is_empty(mode)? {
available.insert(InputStreams::VIDEO);
}
}
if requested.contains(InputStreams::AUDIO) {
let Some(mode) = engine.exec.audio_input else {
log::debug!("No audio input");
return Err(Outcome::Panic.into());
};
if !engine.input.audio_is_empty(mode)? {
available.insert(InputStreams::AUDIO);
}
}
if !requested.is_empty() && available.is_empty() && !flags.contains(flags::Poll::NON_BLOCKING) {
return Err(Outcome::WaitingForInput(requested).into());
}
engine.args.set_return_value(available.bits());
Ok(available)
}
pub fn read_console_data<O: OuterVm>(engine: &mut Engine<O>) -> Result<(), HostCallError> {
let mut i = 0;
let inner_dst = Arg::get(&engine.args, &mut i);
let length = Arg::get(&engine.args, &mut i);
let flags = Arg::get(&engine.args, &mut i);
let flags = flags::ReadConsoleData::from_bits_truncate(flags);
do_read_console_data(engine, inner_dst, length, flags).trace(|result| {
trace!(
"Call read_console_data(buf={:?}, len={length}, flags={flags:?}) = {:?}",
engine.memory_map.pretty_address(inner_dst),
result.as_ref().map(|ret| PrettyUint(*ret))
);
})?;
Ok(())
}
#[inline]
fn do_read_console_data<O: OuterVm>(
engine: &mut Engine<O>,
inner_dst: u32,
mut length: u32,
flags: flags::ReadConsoleData,
) -> Result<u64, HostCallError> {
if length == 0 {
let ret: u64 = 0;
engine.args.set_return_value(ret);
return Ok(ret);
}
if engine.input.console_is_empty()? {
if !flags.contains(flags::ReadConsoleData::NON_BLOCKING) {
return Err(Outcome::WaitingForInput(InputStreams::CONSOLE).into());
}
let ret = u64::MAX;
engine.args.set_return_value(ret);
return Ok(ret);
}
engine.validate_guest_can_write(inner_dst, length)?;
engine.touch_once_range(inner_dst, length)?;
let mut num_bytes_read = 0_u32;
while let Some(buf) = engine.input.read_console(length)? {
engine.inner_vm.poke(&buf[..], inner_dst)?;
let n = buf.len() as u32;
num_bytes_read += n;
length -= n;
if length == 0 {
break;
}
}
let ret = u64::from(num_bytes_read);
engine.args.set_return_value(ret);
Ok(ret)
}
pub fn video_input_mode<O: OuterVm>(engine: &mut Engine<O>) -> Result<(), HostCallError> {
let ret = engine
.exec
.video_input
.as_ref()
.map(|video| video.to_regs())
.unwrap_or_default();
engine.args.set_return_value(ret);
trace!("Call video_input_mode() = {:?}", engine.exec.video_input);
Ok(())
}
pub fn read_video_frame<O: OuterVm>(engine: &mut Engine<O>) -> Result<(), HostCallError> {
let mut i = 0;
let inner_dst = Arg::get(&engine.args, &mut i);
let length = Arg::get(&engine.args, &mut i);
let format: u8 = Arg::get(&engine.args, &mut i);
let flags = Arg::get(&engine.args, &mut i);
let flags = flags::ReadVideoFrame::from_bits_truncate(flags);
do_read_video_frame(engine, inner_dst, length, format, flags).trace(|result| {
trace!(
"Call read_video_frame(buf={:?}, len={length}, format={format:?}, flags={flags:?}) = {:?}",
engine.memory_map.pretty_address(inner_dst),
result.as_ref().map(|ret| PrettyUint(*ret)),
format = InputVideoFrameFormat::try_from(format).map_err(|_| format),
);
})?;
Ok(())
}
#[inline]
fn do_read_video_frame<O: OuterVm>(
engine: &mut Engine<O>,
inner_dst: u32,
length: u32,
format: u8,
flags: flags::ReadVideoFrame,
) -> Result<u64, HostCallError> {
let format = InputVideoFrameFormat::try_from(format).map_err(|_| Outcome::Panic)?;
let Some(mode) = engine.exec.video_input else {
log::debug!("No video input");
return Err(Outcome::Panic.into());
};
assert_eq!(InputVideoFrameFormat::Rgb888, format);
let frame = match engine.input.read_video_frame(mode) {
Ok(frame) => frame,
Err(ReadVideoFrameError::InputLimitReached) =>
return Err(Outcome::InputLimitReached.into()),
Err(ReadVideoFrameError::InvalidVideoStream) => return Err(Outcome::Panic.into()),
Err(ReadVideoFrameError::Eof) if !flags.contains(flags::ReadVideoFrame::NON_BLOCKING) =>
return Err(Outcome::WaitingForInput(InputStreams::VIDEO).into()),
Err(ReadVideoFrameError::Eof) => {
let ret = u64::MAX;
engine.args.set_return_value(ret);
return Ok(ret);
},
};
let frame_len = frame.len() as u32;
if inner_dst == 0 || length < frame_len {
engine.args.set_return_value(frame_len);
return Ok(u64::from(frame_len));
}
engine.validate_guest_can_write(inner_dst, frame_len)?;
engine.touch_once_range(inner_dst, frame_len)?;
engine.inner_vm.poke(&frame[..], inner_dst)?;
engine.args.set_return_value(frame_len);
Ok(u64::from(frame_len))
}
pub fn audio_input_mode<O: OuterVm>(engine: &mut Engine<O>) -> Result<(), HostCallError> {
let ret = engine.exec.audio_input.as_ref().map(|audio| audio.to_regs()).unwrap_or(0);
engine.args.set_return_value(ret);
trace!("Call audio_input_mode() = {:?}", engine.exec.audio_input);
Ok(())
}
pub fn read_audio_frames<O: OuterVm>(engine: &mut Engine<O>) -> Result<(), HostCallError> {
let mut i = 0;
let inner_dst = Arg::get(&engine.args, &mut i);
let length = Arg::get(&engine.args, &mut i);
let flags = Arg::get(&engine.args, &mut i);
let flags = flags::ReadAudioFrames::from_bits_truncate(flags);
do_read_audio_frames(engine, inner_dst, length, flags).trace(|result| {
trace!(
"Call read_audio_frames(buf={:?}, len={length}, flags={flags:?}) = {:?}",
engine.memory_map.pretty_address(inner_dst),
result.as_ref().map(|ret| PrettyUint(*ret))
);
})?;
Ok(())
}
#[inline]
fn do_read_audio_frames<O: OuterVm>(
engine: &mut Engine<O>,
inner_dst: u32,
mut length: u32,
flags: flags::ReadAudioFrames,
) -> Result<u64, HostCallError> {
let Some(mode) = engine.exec.audio_input else {
log::debug!("No audio input");
return Err(Outcome::Panic.into());
};
if engine.input.audio_is_empty(mode)? {
if !flags.contains(flags::ReadAudioFrames::NON_BLOCKING) {
return Err(Outcome::WaitingForInput(InputStreams::AUDIO).into());
}
let ret = u64::MAX;
engine.args.set_return_value(ret);
return Ok(ret);
}
length -= length % u32::from(mode.frame_size());
if length == 0 {
let ret = 0_u64;
engine.args.set_return_value(ret);
return Ok(ret);
}
engine.validate_guest_can_write(inner_dst, length)?;
engine.touch_once_range(inner_dst, length)?;
let mut num_bytes_read = 0_u32;
while let Some(buf) = engine.input.read_audio_frames(length, mode)? {
engine.inner_vm.poke(&buf[..], inner_dst + num_bytes_read)?;
let n = buf.len() as u32;
num_bytes_read += n;
length -= n;
if length == 0 {
break;
}
}
let ret = u64::from(num_bytes_read);
engine.args.set_return_value(ret);
Ok(ret)
}