177 lines
6.4 KiB
Rust
177 lines
6.4 KiB
Rust
//! Validated call sequences share the current VM budget and explicit frames.
|
|
use endor_core::{
|
|
CodeLayout, EndorCode, EndorCodeObject, Instruction, NativeContext, ObjectRef, Opcode, Value,
|
|
VmError,
|
|
};
|
|
|
|
pub fn build(
|
|
cx: &mut NativeContext<'_>,
|
|
calls: Vec<ObjectRef>,
|
|
name: &'static str,
|
|
desktop: ObjectRef,
|
|
) -> Result<ObjectRef, VmError> {
|
|
let mut instructions = Vec::with_capacity(calls.len() * 4 + 2);
|
|
let mut constants: Vec<Value> = calls.into_iter().map(Value::Object).collect();
|
|
for index in 0..constants.len() {
|
|
instructions.push(Instruction::new(Opcode::LoadConst, index as u32));
|
|
instructions.push(Instruction::simple(Opcode::PushNull));
|
|
instructions.push(Instruction::new(Opcode::Call, 0));
|
|
if index + 1 != constants.len() {
|
|
instructions.push(Instruction::simple(Opcode::PopTop));
|
|
}
|
|
}
|
|
if constants.is_empty() {
|
|
constants.push(Value::Nil);
|
|
instructions.push(Instruction::new(Opcode::LoadConst, 0));
|
|
}
|
|
instructions.push(Instruction::simple(Opcode::ReturnValue));
|
|
let code = EndorCode::from_instructions(&instructions, constants, CodeLayout::default())?
|
|
.with_location(name, "<tessera>", 1);
|
|
let code = cx.allocate(EndorCodeObject::new(code))?;
|
|
let globals = cx.new_dict()?;
|
|
let driver = cx.new_function(name, code, globals)?;
|
|
crate::ui_bridge::attach_desktop_type(cx, driver, "Function", desktop)?;
|
|
Ok(driver)
|
|
}
|
|
|
|
/// A fixed-size driver. Each callback returns whether another occurrence is
|
|
/// needed; delegated source calls and all iterations share this VM budget.
|
|
pub fn repeat_call(
|
|
cx: &mut NativeContext<'_>,
|
|
callback: ObjectRef,
|
|
name: &'static str,
|
|
desktop: ObjectRef,
|
|
) -> Result<ObjectRef, VmError> {
|
|
let mut instructions = vec![
|
|
Instruction::new(Opcode::LoadConst, 0),
|
|
Instruction::simple(Opcode::PushNull),
|
|
Instruction::new(Opcode::Call, 0),
|
|
];
|
|
repeat_while(&mut instructions)?;
|
|
instructions.extend([
|
|
Instruction::new(Opcode::LoadConst, 1),
|
|
Instruction::simple(Opcode::ReturnValue),
|
|
]);
|
|
driver(
|
|
cx,
|
|
instructions,
|
|
vec![Value::Object(callback), Value::Nil],
|
|
name,
|
|
desktop,
|
|
)
|
|
}
|
|
|
|
/// Prepare only the current occurrence, collect its result, then continue the
|
|
/// outer driver. The helper frame and its managed captures die before the next.
|
|
pub fn collect_and_continue(
|
|
cx: &mut NativeContext<'_>,
|
|
collector: ObjectRef,
|
|
function: ObjectRef,
|
|
arguments: Vec<Value>,
|
|
name: &'static str,
|
|
desktop: ObjectRef,
|
|
) -> Result<ObjectRef, VmError> {
|
|
let count = arguments.len();
|
|
let mut constants = vec![Value::Object(collector), Value::Object(function)];
|
|
constants.extend(arguments);
|
|
constants.push(Value::Bool(true));
|
|
let mut instructions = vec![
|
|
Instruction::new(Opcode::LoadConst, 0),
|
|
Instruction::simple(Opcode::PushNull),
|
|
Instruction::new(Opcode::LoadConst, 1),
|
|
Instruction::simple(Opcode::PushNull),
|
|
];
|
|
for index in 0..count {
|
|
instructions.push(Instruction::new(Opcode::LoadConst, index as u32 + 2));
|
|
}
|
|
instructions.extend([
|
|
Instruction::new(Opcode::Call, count as u32),
|
|
Instruction::new(Opcode::Call, 1),
|
|
Instruction::simple(Opcode::PopTop),
|
|
Instruction::new(Opcode::LoadConst, count as u32 + 2),
|
|
Instruction::simple(Opcode::ReturnValue),
|
|
]);
|
|
driver(cx, instructions, constants, name, desktop)
|
|
}
|
|
|
|
fn driver(
|
|
cx: &mut NativeContext<'_>,
|
|
instructions: Vec<Instruction>,
|
|
constants: Vec<Value>,
|
|
name: &'static str,
|
|
desktop: ObjectRef,
|
|
) -> Result<ObjectRef, VmError> {
|
|
let code = EndorCode::from_instructions(&instructions, constants, CodeLayout::default())?
|
|
.with_location(name, "<tessera>", 1);
|
|
let code = cx.allocate(EndorCodeObject::new(code))?;
|
|
let globals = cx.new_dict()?;
|
|
let function = cx.new_function(name, code, globals)?;
|
|
crate::ui_bridge::attach_desktop_type(cx, function, "Function", desktop)?;
|
|
Ok(function)
|
|
}
|
|
|
|
/// Consume the condition on the stack: true repeats this same frame from zero,
|
|
/// false falls through. Operand widths include EXTENDED_ARG code units.
|
|
pub fn repeat_while(instructions: &mut Vec<Instruction>) -> Result<(), VmError> {
|
|
let body = Instruction::encode_all(instructions)?.len() / 2;
|
|
let mut back = Instruction::new(Opcode::JumpBackward, 0);
|
|
loop {
|
|
let units = Instruction::encode_all(&[back])?.len() / 2;
|
|
let distance = u32::try_from(body + 1 + units)
|
|
.map_err(|_| VmError::ExecutionLimit("layout bytecode"))?;
|
|
if back.argument == distance {
|
|
instructions.push(Instruction::new(Opcode::PopJumpIfFalse, units as u32));
|
|
instructions.push(back);
|
|
return Ok(());
|
|
}
|
|
back.argument = distance;
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use endor_core::{ExecutionLimits, Vm};
|
|
#[test]
|
|
fn repeated_bytecode_handles_extended_operands_and_keeps_one_frame() {
|
|
for condition in [false, true] {
|
|
let mut instructions = vec![Instruction::simple(Opcode::Nop); 300];
|
|
instructions.push(Instruction::new(Opcode::LoadConst, 0));
|
|
repeat_while(&mut instructions).unwrap();
|
|
instructions.extend([
|
|
Instruction::new(Opcode::LoadConst, 1),
|
|
Instruction::simple(Opcode::ReturnValue),
|
|
]);
|
|
let code = EndorCode::from_instructions(
|
|
&instructions,
|
|
vec![Value::Bool(condition), Value::Nil],
|
|
CodeLayout::default(),
|
|
)
|
|
.unwrap();
|
|
let mut vm = Vm::new().unwrap();
|
|
let code = vm.new_code(code).unwrap();
|
|
let globals = vm.new_dict().unwrap();
|
|
let result = vm.execute_with_limits(
|
|
code,
|
|
globals,
|
|
ExecutionLimits {
|
|
max_frames: 1,
|
|
max_instructions: 1000,
|
|
},
|
|
);
|
|
if condition {
|
|
assert!(matches!(
|
|
result,
|
|
Err(VmError::ExecutionLimit("instructions"))
|
|
));
|
|
} else {
|
|
assert_eq!(result.unwrap(), Value::Nil);
|
|
}
|
|
vm.release(code).unwrap();
|
|
vm.release(globals).unwrap();
|
|
vm.collect_cycles();
|
|
assert_eq!(vm.object_count(), 0);
|
|
}
|
|
}
|
|
}
|