1use std::{collections::HashMap, error::Error, fmt, str};
2
3use crate::{
4 CompilerErrorCode, NcsAuxCode, NcsInstruction, NcsOpcode, NcsReadError, Ndb, NdbFunction,
5 NdbType, decode_ncs_instructions,
6};
7
8pub type VmObjectId = u32;
10
11#[derive(#[automatically_derived]
impl ::core::fmt::Debug for VmEngineStructureValue {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
match self {
VmEngineStructureValue::Word(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f, "Word",
&__self_0),
VmEngineStructureValue::Text(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f, "Text",
&__self_0),
}
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for VmEngineStructureValue {
#[inline]
fn clone(&self) -> VmEngineStructureValue {
match self {
VmEngineStructureValue::Word(__self_0) =>
VmEngineStructureValue::Word(::core::clone::Clone::clone(__self_0)),
VmEngineStructureValue::Text(__self_0) =>
VmEngineStructureValue::Text(::core::clone::Clone::clone(__self_0)),
}
}
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for VmEngineStructureValue {
#[inline]
fn eq(&self, other: &VmEngineStructureValue) -> bool {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
__self_discr == __arg1_discr &&
match (self, other) {
(VmEngineStructureValue::Word(__self_0),
VmEngineStructureValue::Word(__arg1_0)) =>
__self_0 == __arg1_0,
(VmEngineStructureValue::Text(__self_0),
VmEngineStructureValue::Text(__arg1_0)) =>
__self_0 == __arg1_0,
_ => unsafe { ::core::intrinsics::unreachable() }
}
}
}PartialEq)]
13pub enum VmEngineStructureValue {
14 Word(u32),
16 Text(String),
18}
19
20#[derive(#[automatically_derived]
impl ::core::fmt::Debug for VmValue {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
match self {
VmValue::Int(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f, "Int",
&__self_0),
VmValue::Float(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f, "Float",
&__self_0),
VmValue::String(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f, "String",
&__self_0),
VmValue::Object(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f, "Object",
&__self_0),
VmValue::EngineStructure { index: __self_0, value: __self_1 } =>
::core::fmt::Formatter::debug_struct_field2_finish(f,
"EngineStructure", "index", __self_0, "value", &__self_1),
}
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for VmValue {
#[inline]
fn clone(&self) -> VmValue {
match self {
VmValue::Int(__self_0) =>
VmValue::Int(::core::clone::Clone::clone(__self_0)),
VmValue::Float(__self_0) =>
VmValue::Float(::core::clone::Clone::clone(__self_0)),
VmValue::String(__self_0) =>
VmValue::String(::core::clone::Clone::clone(__self_0)),
VmValue::Object(__self_0) =>
VmValue::Object(::core::clone::Clone::clone(__self_0)),
VmValue::EngineStructure { index: __self_0, value: __self_1 } =>
VmValue::EngineStructure {
index: ::core::clone::Clone::clone(__self_0),
value: ::core::clone::Clone::clone(__self_1),
},
}
}
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for VmValue {
#[inline]
fn eq(&self, other: &VmValue) -> bool {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
__self_discr == __arg1_discr &&
match (self, other) {
(VmValue::Int(__self_0), VmValue::Int(__arg1_0)) =>
__self_0 == __arg1_0,
(VmValue::Float(__self_0), VmValue::Float(__arg1_0)) =>
__self_0 == __arg1_0,
(VmValue::String(__self_0), VmValue::String(__arg1_0)) =>
__self_0 == __arg1_0,
(VmValue::Object(__self_0), VmValue::Object(__arg1_0)) =>
__self_0 == __arg1_0,
(VmValue::EngineStructure { index: __self_0, value: __self_1
}, VmValue::EngineStructure {
index: __arg1_0, value: __arg1_1 }) =>
__self_0 == __arg1_0 && __self_1 == __arg1_1,
_ => unsafe { ::core::intrinsics::unreachable() }
}
}
}PartialEq)]
22pub enum VmValue {
23 Int(i32),
25 Float(f32),
27 String(String),
29 Object(VmObjectId),
31 EngineStructure {
33 index: u8,
35 value: VmEngineStructureValue,
37 },
38}
39
40impl VmValue {
41 #[must_use]
43 pub fn kind_name(&self) -> &'static str {
44 match self {
45 Self::Int(_) => "int",
46 Self::Float(_) => "float",
47 Self::String(_) => "string",
48 Self::Object(_) => "object",
49 Self::EngineStructure {
50 ..
51 } => "engine structure",
52 }
53 }
54}
55
56impl VmEngineStructureValue {
57 #[must_use]
59 pub fn as_word(&self) -> Option<u32> {
60 match self {
61 Self::Word(value) => Some(*value),
62 Self::Text(_) => None,
63 }
64 }
65
66 #[must_use]
68 pub fn as_text(&self) -> Option<&str> {
69 match self {
70 Self::Word(_) => None,
71 Self::Text(value) => Some(value),
72 }
73 }
74}
75
76#[derive(#[automatically_derived]
impl ::core::fmt::Debug for VmError {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
match self {
VmError::Read(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f, "Read",
&__self_0),
VmError::Unsupported {
offset: __self_0,
opcode: __self_1,
auxcode: __self_2,
message: __self_3 } =>
::core::fmt::Formatter::debug_struct_field4_finish(f,
"Unsupported", "offset", __self_0, "opcode", __self_1,
"auxcode", __self_2, "message", &__self_3),
VmError::StackUnderflow { message: __self_0 } =>
::core::fmt::Formatter::debug_struct_field1_finish(f,
"StackUnderflow", "message", &__self_0),
VmError::TypeMismatch {
offset: __self_0,
message: __self_1,
expected: __self_2,
actual: __self_3 } =>
::core::fmt::Formatter::debug_struct_field4_finish(f,
"TypeMismatch", "offset", __self_0, "message", __self_1,
"expected", __self_2, "actual", &__self_3),
VmError::InvalidInstructionPointer { offset: __self_0 } =>
::core::fmt::Formatter::debug_struct_field1_finish(f,
"InvalidInstructionPointer", "offset", &__self_0),
VmError::InvalidExtra {
offset: __self_0,
opcode: __self_1,
auxcode: __self_2,
message: __self_3 } =>
::core::fmt::Formatter::debug_struct_field4_finish(f,
"InvalidExtra", "offset", __self_0, "opcode", __self_1,
"auxcode", __self_2, "message", &__self_3),
VmError::InvalidCommand { offset: __self_0, command: __self_1 } =>
::core::fmt::Formatter::debug_struct_field2_finish(f,
"InvalidCommand", "offset", __self_0, "command", &__self_1),
VmError::Setup { message: __self_0 } =>
::core::fmt::Formatter::debug_struct_field1_finish(f, "Setup",
"message", &__self_0),
VmError::InstructionLimitExceeded {
offset: __self_0, limit: __self_1 } =>
::core::fmt::Formatter::debug_struct_field2_finish(f,
"InstructionLimitExceeded", "offset", __self_0, "limit",
&__self_1),
}
}
}Debug)]
78pub enum VmError {
79 Read(NcsReadError),
81 Unsupported {
83 offset: usize,
85 opcode: NcsOpcode,
87 auxcode: NcsAuxCode,
89 message: String,
91 },
92 StackUnderflow {
94 message: String,
96 },
97 TypeMismatch {
99 offset: usize,
101 message: String,
103 expected: Option<&'static str>,
105 actual: &'static str,
107 },
108 InvalidInstructionPointer {
110 offset: usize,
112 },
113 InvalidExtra {
115 offset: usize,
117 opcode: NcsOpcode,
119 auxcode: NcsAuxCode,
121 message: String,
123 },
124 InvalidCommand {
126 offset: usize,
128 command: u16,
130 },
131 Setup {
133 message: String,
135 },
136 InstructionLimitExceeded {
138 offset: usize,
140 limit: usize,
142 },
143}
144
145impl VmError {
146 #[must_use]
149 pub fn code(&self) -> Option<CompilerErrorCode> {
150 match self {
151 Self::Read(NcsReadError::Opcode(_)) => Some(CompilerErrorCode::VmInvalidOpCode),
152 Self::Read(NcsReadError::AuxCode(_)) => Some(CompilerErrorCode::VmInvalidAuxCode),
153 Self::Read(
154 NcsReadError::Header(_)
155 | NcsReadError::TruncatedInstruction {
156 ..
157 },
158 )
159 | Self::InvalidExtra {
160 ..
161 } => Some(CompilerErrorCode::VmInvalidExtraDataOnOpCode),
162 Self::Unsupported {
163 ..
164 } => None,
165 Self::StackUnderflow {
166 ..
167 } => Some(CompilerErrorCode::VmStackUnderflow),
168 Self::TypeMismatch {
169 ..
170 } => Some(CompilerErrorCode::VmUnknownTypeOnRunTimeStack),
171 Self::InvalidInstructionPointer {
172 ..
173 } => Some(CompilerErrorCode::VmIpOutOfCodeSegment),
174 Self::InvalidCommand {
175 ..
176 } => Some(CompilerErrorCode::VmInvalidCommand),
177 Self::Setup {
178 ..
179 } => None,
180 Self::InstructionLimitExceeded {
181 ..
182 } => None,
183 }
184 }
185}
186
187impl fmt::Display for VmError {
188 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189 match self {
190 Self::Read(error) => error.fmt(f),
191 Self::Unsupported {
192 offset,
193 opcode,
194 auxcode,
195 message,
196 } => f.write_fmt(format_args!("unsupported VM instruction {0}.{1} at byte {2}: {3}",
opcode, auxcode, offset, message))write!(
197 f,
198 "unsupported VM instruction {}.{} at byte {}: {}",
199 opcode, auxcode, offset, message
200 ),
201 Self::StackUnderflow {
202 message,
203 } => f.write_fmt(format_args!("VM stack underflow: {0}", message))write!(f, "VM stack underflow: {message}"),
204 Self::TypeMismatch {
205 offset,
206 message,
207 expected,
208 actual,
209 } => match expected {
210 Some(expected) => f.write_fmt(format_args!("VM type mismatch at byte {0}: {1} (expected {2}, got {3})",
offset, message, expected, actual))write!(
211 f,
212 "VM type mismatch at byte {}: {} (expected {}, got {})",
213 offset, message, expected, actual
214 ),
215 None => f.write_fmt(format_args!("VM type mismatch at byte {0}: {1} ({2})", offset,
message, actual))write!(
216 f,
217 "VM type mismatch at byte {}: {} ({})",
218 offset, message, actual
219 ),
220 },
221 Self::InvalidInstructionPointer {
222 offset,
223 } => f.write_fmt(format_args!("VM instruction pointer left the code segment at byte {0}",
offset))write!(
224 f,
225 "VM instruction pointer left the code segment at byte {offset}"
226 ),
227 Self::InvalidExtra {
228 offset,
229 opcode,
230 auxcode,
231 message,
232 } => f.write_fmt(format_args!("invalid {0}.{1} payload at byte {2}: {3}", opcode,
auxcode, offset, message))write!(
233 f,
234 "invalid {}.{} payload at byte {}: {}",
235 opcode, auxcode, offset, message
236 ),
237 Self::InvalidCommand {
238 offset,
239 command,
240 } => {
241 f.write_fmt(format_args!("invalid VM command {0} at byte {1}", command,
offset))write!(f, "invalid VM command {} at byte {}", command, offset)
242 }
243 Self::Setup {
244 message,
245 } => f.write_str(message),
246 Self::InstructionLimitExceeded {
247 offset,
248 limit,
249 } => f.write_fmt(format_args!("VM instruction limit of {0} exceeded before byte {1}",
limit, offset))write!(
250 f,
251 "VM instruction limit of {} exceeded before byte {}",
252 limit, offset
253 ),
254 }
255 }
256}
257
258impl Error for VmError {}
259
260impl From<NcsReadError> for VmError {
261 fn from(value: NcsReadError) -> Self {
262 Self::Read(value)
263 }
264}
265
266#[derive(#[automatically_derived]
impl ::core::fmt::Debug for VmTraceEvent {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field5_finish(f, "VmTraceEvent",
"offset", &self.offset, "ip", &self.ip, "sp", &self.sp, "bp",
&self.bp, "instruction", &&self.instruction)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for VmTraceEvent {
#[inline]
fn clone(&self) -> VmTraceEvent {
VmTraceEvent {
offset: ::core::clone::Clone::clone(&self.offset),
ip: ::core::clone::Clone::clone(&self.ip),
sp: ::core::clone::Clone::clone(&self.sp),
bp: ::core::clone::Clone::clone(&self.bp),
instruction: ::core::clone::Clone::clone(&self.instruction),
}
}
}Clone)]
268pub struct VmTraceEvent {
269 pub offset: usize,
271 pub ip: usize,
273 pub sp: usize,
275 pub bp: usize,
277 pub instruction: NcsInstruction,
279}
280
281#[derive(#[automatically_derived]
impl ::core::fmt::Debug for VmProgramInstruction {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field2_finish(f,
"VmProgramInstruction", "offset", &self.offset, "instruction",
&&self.instruction)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for VmProgramInstruction {
#[inline]
fn clone(&self) -> VmProgramInstruction {
VmProgramInstruction {
offset: ::core::clone::Clone::clone(&self.offset),
instruction: ::core::clone::Clone::clone(&self.instruction),
}
}
}Clone)]
282struct VmProgramInstruction {
283 offset: usize,
284 instruction: NcsInstruction,
285}
286
287#[derive(#[automatically_derived]
impl ::core::fmt::Debug for VmFunctionShape {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field2_finish(f,
"VmFunctionShape", "arg_cells", &self.arg_cells, "return_cells",
&&self.return_cells)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for VmFunctionShape {
#[inline]
fn clone(&self) -> VmFunctionShape {
let _: ::core::clone::AssertParamIsClone<usize>;
*self
}
}Clone, #[automatically_derived]
impl ::core::marker::Copy for VmFunctionShape { }Copy)]
288struct VmFunctionShape {
289 arg_cells: usize,
290 return_cells: usize,
291}
292
293#[derive(#[automatically_derived]
impl ::core::fmt::Debug for VmFunctionDebug {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field3_finish(f,
"VmFunctionDebug", "label", &self.label, "start", &self.start,
"end", &&self.end)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for VmFunctionDebug {
#[inline]
fn clone(&self) -> VmFunctionDebug {
VmFunctionDebug {
label: ::core::clone::Clone::clone(&self.label),
start: ::core::clone::Clone::clone(&self.start),
end: ::core::clone::Clone::clone(&self.end),
}
}
}Clone)]
294struct VmFunctionDebug {
295 label: String,
296 start: usize,
297 end: usize,
298}
299
300#[derive(#[automatically_derived]
impl ::core::fmt::Debug for VmSourceLineDebug {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field5_finish(f,
"VmSourceLineDebug", "file_name", &self.file_name, "is_root",
&self.is_root, "line_number", &self.line_number, "start",
&self.start, "end", &&self.end)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for VmSourceLineDebug {
#[inline]
fn clone(&self) -> VmSourceLineDebug {
VmSourceLineDebug {
file_name: ::core::clone::Clone::clone(&self.file_name),
is_root: ::core::clone::Clone::clone(&self.is_root),
line_number: ::core::clone::Clone::clone(&self.line_number),
start: ::core::clone::Clone::clone(&self.start),
end: ::core::clone::Clone::clone(&self.end),
}
}
}Clone)]
301struct VmSourceLineDebug {
302 file_name: String,
303 is_root: bool,
304 line_number: usize,
305 start: usize,
306 end: usize,
307}
308
309#[derive(#[automatically_derived]
impl ::core::fmt::Debug for VmCallCleanup {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field2_finish(f, "VmCallCleanup",
"arg_cells", &self.arg_cells, "return_cells", &&self.return_cells)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for VmCallCleanup {
#[inline]
fn clone(&self) -> VmCallCleanup {
let _: ::core::clone::AssertParamIsClone<usize>;
*self
}
}Clone, #[automatically_derived]
impl ::core::marker::Copy for VmCallCleanup { }Copy)]
310struct VmCallCleanup {
311 arg_cells: usize,
312 return_cells: usize,
313}
314
315#[derive(#[automatically_derived]
impl ::core::fmt::Debug for VmReturnFrame {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field2_finish(f, "VmReturnFrame",
"target", &self.target, "cleanup", &&self.cleanup)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for VmReturnFrame {
#[inline]
fn clone(&self) -> VmReturnFrame {
let _: ::core::clone::AssertParamIsClone<usize>;
let _: ::core::clone::AssertParamIsClone<Option<VmCallCleanup>>;
*self
}
}Clone, #[automatically_derived]
impl ::core::marker::Copy for VmReturnFrame { }Copy)]
316struct VmReturnFrame {
317 target: usize,
318 cleanup: Option<VmCallCleanup>,
319}
320
321#[derive(#[automatically_derived]
impl ::core::fmt::Debug for VmProgram {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field5_finish(f, "VmProgram",
"instructions", &self.instructions, "offsets_to_index",
&self.offsets_to_index, "function_shapes", &self.function_shapes,
"functions", &self.functions, "source_lines", &&self.source_lines)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for VmProgram {
#[inline]
fn clone(&self) -> VmProgram {
VmProgram {
instructions: ::core::clone::Clone::clone(&self.instructions),
offsets_to_index: ::core::clone::Clone::clone(&self.offsets_to_index),
function_shapes: ::core::clone::Clone::clone(&self.function_shapes),
functions: ::core::clone::Clone::clone(&self.functions),
source_lines: ::core::clone::Clone::clone(&self.source_lines),
}
}
}Clone)]
322struct VmProgram {
323 instructions: Vec<VmProgramInstruction>,
324 offsets_to_index: HashMap<usize, usize>,
325 function_shapes: HashMap<usize, VmFunctionShape>,
326 functions: Vec<VmFunctionDebug>,
327 source_lines: Vec<VmSourceLineDebug>,
328}
329
330impl VmProgram {
331 fn decode(bytes: &[u8]) -> Result<Self, VmError> {
332 let instructions = decode_ncs_instructions(bytes)?;
333 Ok(Self::from_instructions(instructions))
334 }
335
336 fn from_instructions(instructions: Vec<NcsInstruction>) -> Self {
337 let mut decoded = Vec::with_capacity(instructions.len());
338 let mut offsets_to_index = HashMap::with_capacity(instructions.len());
339 let mut offset = 0usize;
340 for (index, instruction) in instructions.into_iter().enumerate() {
341 let encoded_len = instruction.encoded_len();
342 offsets_to_index.insert(offset, index);
343 decoded.push(VmProgramInstruction {
344 offset,
345 instruction,
346 });
347 offset += encoded_len;
348 }
349 Self {
350 instructions: decoded,
351 offsets_to_index,
352 function_shapes: HashMap::new(),
353 functions: Vec::new(),
354 source_lines: Vec::new(),
355 }
356 }
357
358 fn instruction_at(&self, offset: usize) -> Option<&VmProgramInstruction> {
359 self.offsets_to_index
360 .get(&offset)
361 .and_then(|index| self.instructions.get(*index))
362 }
363
364 fn attach_ndb(&mut self, ndb: &Ndb) -> Result<(), VmError> {
365 self.function_shapes.clear();
366 self.functions.clear();
367 self.source_lines.clear();
368 for function in &ndb.functions {
369 let start =
370 usize::try_from(function.binary_start).map_err(|_error| VmError::Setup {
371 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("function {0:?} start offset exceeds usize range",
function.label))
})format!(
372 "function {:?} start offset exceeds usize range",
373 function.label
374 ),
375 })?;
376 let end = usize::try_from(function.binary_end).map_err(|_error| VmError::Setup {
377 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("function {0:?} end offset exceeds usize range",
function.label))
})format!(
378 "function {:?} end offset exceeds usize range",
379 function.label
380 ),
381 })?;
382 self.function_shapes.insert(
383 start,
384 VmFunctionShape {
385 arg_cells: function
386 .args
387 .iter()
388 .map(cells_for_ndb_type)
389 .try_fold(0usize, |total, cells| cells.map(|cells| total + cells))?,
390 return_cells: if function.return_type == NdbType::Void {
391 0
392 } else {
393 cells_for_ndb_type(&function.return_type)?
394 },
395 },
396 );
397 self.functions.push(VmFunctionDebug {
398 label: function.label.to_string(),
399 start,
400 end,
401 });
402 }
403 for line in &ndb.lines {
404 let start = usize::try_from(line.binary_start).map_err(|_error| VmError::Setup {
405 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("line mapping {0}:{1} start offset exceeds usize range",
line.file_num, line.line_num))
})format!(
406 "line mapping {}:{} start offset exceeds usize range",
407 line.file_num, line.line_num
408 ),
409 })?;
410 let end = usize::try_from(line.binary_end).map_err(|_error| VmError::Setup {
411 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("line mapping {0}:{1} end offset exceeds usize range",
line.file_num, line.line_num))
})format!(
412 "line mapping {}:{} end offset exceeds usize range",
413 line.file_num, line.line_num
414 ),
415 })?;
416 let file = ndb.files.get(line.file_num).ok_or_else(|| VmError::Setup {
417 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("line mapping {0}:{1} references missing file index {2}",
line.file_num, line.line_num, line.file_num))
})format!(
418 "line mapping {}:{} references missing file index {}",
419 line.file_num, line.line_num, line.file_num
420 ),
421 })?;
422 self.source_lines.push(VmSourceLineDebug {
423 file_name: file.name.clone(),
424 is_root: file.is_root,
425 line_number: line.line_num,
426 start,
427 end,
428 });
429 }
430 Ok(())
431 }
432
433 fn function_at(&self, offset: usize) -> Option<&VmFunctionDebug> {
434 self.functions
435 .iter()
436 .find(|function| contains_debug_offset(offset, function.start, function.end))
437 }
438
439 fn source_line_at(&self, offset: usize) -> Option<&VmSourceLineDebug> {
440 self.source_lines
441 .iter()
442 .find(|line| contains_debug_offset(offset, line.start, line.end))
443 }
444}
445
446fn contains_debug_offset(offset: usize, start: usize, end: usize) -> bool {
447 if end <= start {
448 offset == start
449 } else {
450 (start..end).contains(&offset)
451 }
452}
453
454#[derive(#[automatically_derived]
impl ::core::fmt::Debug for VmSourceLocation {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field3_finish(f,
"VmSourceLocation", "file_name", &self.file_name, "is_root",
&self.is_root, "line_number", &&self.line_number)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for VmSourceLocation {
#[inline]
fn clone(&self) -> VmSourceLocation {
VmSourceLocation {
file_name: ::core::clone::Clone::clone(&self.file_name),
is_root: ::core::clone::Clone::clone(&self.is_root),
line_number: ::core::clone::Clone::clone(&self.line_number),
}
}
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for VmSourceLocation {
#[inline]
fn eq(&self, other: &VmSourceLocation) -> bool {
self.is_root == other.is_root && self.file_name == other.file_name &&
self.line_number == other.line_number
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for VmSourceLocation {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {
let _: ::core::cmp::AssertParamIsEq<String>;
let _: ::core::cmp::AssertParamIsEq<bool>;
let _: ::core::cmp::AssertParamIsEq<usize>;
}
}Eq)]
456pub struct VmSourceLocation {
457 pub file_name: String,
459 pub is_root: bool,
461 pub line_number: usize,
463}
464
465#[derive(#[automatically_derived]
impl ::core::fmt::Debug for VmFunctionInfo {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field3_finish(f,
"VmFunctionInfo", "name", &self.name, "start", &self.start, "end",
&&self.end)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for VmFunctionInfo {
#[inline]
fn clone(&self) -> VmFunctionInfo {
VmFunctionInfo {
name: ::core::clone::Clone::clone(&self.name),
start: ::core::clone::Clone::clone(&self.start),
end: ::core::clone::Clone::clone(&self.end),
}
}
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for VmFunctionInfo {
#[inline]
fn eq(&self, other: &VmFunctionInfo) -> bool {
self.name == other.name && self.start == other.start &&
self.end == other.end
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for VmFunctionInfo {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {
let _: ::core::cmp::AssertParamIsEq<String>;
let _: ::core::cmp::AssertParamIsEq<usize>;
}
}Eq)]
467pub struct VmFunctionInfo {
468 pub name: String,
470 pub start: usize,
472 pub end: usize,
474}
475
476#[derive(#[automatically_derived]
impl ::core::fmt::Debug for VmSituation {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
let names: &'static _ =
&["label", "program", "ip", "sp", "bp", "stack"];
let values: &[&dyn ::core::fmt::Debug] =
&[&self.label, &self.program, &self.ip, &self.sp, &self.bp,
&&self.stack];
::core::fmt::Formatter::debug_struct_fields_finish(f, "VmSituation",
names, values)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for VmSituation {
#[inline]
fn clone(&self) -> VmSituation {
VmSituation {
label: ::core::clone::Clone::clone(&self.label),
program: ::core::clone::Clone::clone(&self.program),
ip: ::core::clone::Clone::clone(&self.ip),
sp: ::core::clone::Clone::clone(&self.sp),
bp: ::core::clone::Clone::clone(&self.bp),
stack: ::core::clone::Clone::clone(&self.stack),
}
}
}Clone)]
478pub struct VmSituation {
479 label: String,
480 program: VmProgram,
481 ip: usize,
482 sp: usize,
483 bp: usize,
484 stack: Vec<VmValue>,
485}
486
487impl VmSituation {
488 #[must_use]
490 pub fn label(&self) -> &str {
491 &self.label
492 }
493
494 #[must_use]
496 pub fn ip(&self) -> usize {
497 self.ip
498 }
499
500 #[must_use]
502 pub fn sp(&self) -> usize {
503 self.sp
504 }
505
506 #[must_use]
508 pub fn bp(&self) -> usize {
509 self.bp
510 }
511
512 #[must_use]
514 pub fn stack(&self) -> &[VmValue] {
515 &self.stack
516 }
517
518 #[must_use]
520 pub fn to_script(&self) -> VmScript {
521 VmScript {
522 label: self.label.clone(),
523 program: self.program.clone(),
524 ip: self.ip,
525 sp: self.sp,
526 bp: self.bp,
527 ret: Vec::new(),
528 stack: self.stack.clone(),
529 save_ip: 0,
530 save_sp: 0,
531 save_bp: 0,
532 saved_situation: None,
533 abort_requested: false,
534 aborted: false,
535 }
536 }
537}
538
539#[derive(#[automatically_derived]
impl ::core::fmt::Debug for VmScript {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
let names: &'static _ =
&["label", "program", "ip", "sp", "bp", "ret", "stack", "save_ip",
"save_sp", "save_bp", "saved_situation", "abort_requested",
"aborted"];
let values: &[&dyn ::core::fmt::Debug] =
&[&self.label, &self.program, &self.ip, &self.sp, &self.bp,
&self.ret, &self.stack, &self.save_ip, &self.save_sp,
&self.save_bp, &self.saved_situation, &self.abort_requested,
&&self.aborted];
::core::fmt::Formatter::debug_struct_fields_finish(f, "VmScript",
names, values)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for VmScript {
#[inline]
fn clone(&self) -> VmScript {
VmScript {
label: ::core::clone::Clone::clone(&self.label),
program: ::core::clone::Clone::clone(&self.program),
ip: ::core::clone::Clone::clone(&self.ip),
sp: ::core::clone::Clone::clone(&self.sp),
bp: ::core::clone::Clone::clone(&self.bp),
ret: ::core::clone::Clone::clone(&self.ret),
stack: ::core::clone::Clone::clone(&self.stack),
save_ip: ::core::clone::Clone::clone(&self.save_ip),
save_sp: ::core::clone::Clone::clone(&self.save_sp),
save_bp: ::core::clone::Clone::clone(&self.save_bp),
saved_situation: ::core::clone::Clone::clone(&self.saved_situation),
abort_requested: ::core::clone::Clone::clone(&self.abort_requested),
aborted: ::core::clone::Clone::clone(&self.aborted),
}
}
}Clone)]
541pub struct VmScript {
542 label: String,
543 program: VmProgram,
544 ip: usize,
545 sp: usize,
546 bp: usize,
547 ret: Vec<VmReturnFrame>,
548 stack: Vec<VmValue>,
549 save_ip: usize,
550 save_sp: usize,
551 save_bp: usize,
552 saved_situation: Option<VmSituation>,
553 abort_requested: bool,
554 aborted: bool,
555}
556
557impl VmScript {
558 pub fn from_bytes(bytes: &[u8], label: impl Into<String>) -> Result<Self, VmError> {
564 Ok(Self {
565 label: label.into(),
566 program: VmProgram::decode(bytes)?,
567 ip: 0,
568 sp: 0,
569 bp: 0,
570 ret: Vec::new(),
571 stack: Vec::new(),
572 save_ip: 0,
573 save_sp: 0,
574 save_bp: 0,
575 saved_situation: None,
576 abort_requested: false,
577 aborted: false,
578 })
579 }
580
581 #[must_use]
583 pub fn from_instructions(instructions: Vec<NcsInstruction>, label: impl Into<String>) -> Self {
584 Self {
585 label: label.into(),
586 program: VmProgram::from_instructions(instructions),
587 ip: 0,
588 sp: 0,
589 bp: 0,
590 ret: Vec::new(),
591 stack: Vec::new(),
592 save_ip: 0,
593 save_sp: 0,
594 save_bp: 0,
595 saved_situation: None,
596 abort_requested: false,
597 aborted: false,
598 }
599 }
600
601 pub fn attach_ndb(&mut self, ndb: &Ndb) -> Result<(), VmError> {
609 self.program.attach_ndb(ndb)
610 }
611
612 pub fn from_bytes_with_ndb(
620 bytes: &[u8],
621 label: impl Into<String>,
622 ndb: &Ndb,
623 ) -> Result<Self, VmError> {
624 let mut script = Self::from_bytes(bytes, label)?;
625 script.attach_ndb(ndb)?;
626 Ok(script)
627 }
628
629 pub fn run(&mut self, vm: &Vm) -> Result<(), VmError> {
635 vm.run(self)
636 }
637
638 #[must_use]
640 pub fn label(&self) -> &str {
641 &self.label
642 }
643
644 #[must_use]
646 pub fn ip(&self) -> usize {
647 self.ip
648 }
649
650 #[must_use]
652 pub fn sp(&self) -> usize {
653 self.sp
654 }
655
656 #[must_use]
658 pub fn bp(&self) -> usize {
659 self.bp
660 }
661
662 #[must_use]
664 pub fn current_instruction(&self) -> Option<&NcsInstruction> {
665 self.program
666 .instruction_at(self.ip)
667 .map(|decoded| &decoded.instruction)
668 }
669
670 #[must_use]
672 pub fn instruction_at(&self, offset: usize) -> Option<&NcsInstruction> {
673 self.program
674 .instruction_at(offset)
675 .map(|decoded| &decoded.instruction)
676 }
677
678 #[must_use]
681 pub fn current_function(&self) -> Option<VmFunctionInfo> {
682 self.function_at(self.ip)
683 }
684
685 #[must_use]
687 pub fn function_at(&self, offset: usize) -> Option<VmFunctionInfo> {
688 self.program
689 .function_at(offset)
690 .map(|function| VmFunctionInfo {
691 name: function.label.clone(),
692 start: function.start,
693 end: function.end,
694 })
695 }
696
697 #[must_use]
700 pub fn current_source_location(&self) -> Option<VmSourceLocation> {
701 self.source_location_at(self.ip)
702 }
703
704 #[must_use]
706 pub fn source_location_at(&self, offset: usize) -> Option<VmSourceLocation> {
707 self.program
708 .source_line_at(offset)
709 .map(|line| VmSourceLocation {
710 file_name: line.file_name.clone(),
711 is_root: line.is_root,
712 line_number: line.line_number,
713 })
714 }
715
716 #[must_use]
718 pub fn save_ip(&self) -> usize {
719 self.save_ip
720 }
721
722 #[must_use]
724 pub fn save_sp(&self) -> usize {
725 self.save_sp
726 }
727
728 #[must_use]
730 pub fn save_bp(&self) -> usize {
731 self.save_bp
732 }
733
734 #[must_use]
736 pub fn stack(&self) -> &[VmValue] {
737 &self.stack
738 }
739
740 #[must_use]
743 pub fn stack_string(&self) -> String {
744 let mut rendered = String::new();
745 for (index, value) in self.stack.iter().enumerate() {
746 if index > 0 {
747 rendered.push(' ');
748 }
749 if index == self.bp {
750 rendered.push('^');
751 }
752 if index + 1 == self.sp {
753 rendered.push('*');
754 }
755 rendered.push_str(&::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0:?}", value))
})format!("{value:?}"));
756 }
757 rendered
758 }
759
760 #[must_use]
762 pub fn return_depth(&self) -> usize {
763 self.ret.len()
764 }
765
766 #[must_use]
768 pub fn saved_situation(&self) -> Option<&VmSituation> {
769 self.saved_situation.as_ref()
770 }
771
772 pub fn take_saved_situation(&mut self) -> Option<VmSituation> {
775 self.saved_situation.take()
776 }
777
778 pub fn prepare_function_call(
790 &mut self,
791 ndb: &Ndb,
792 name: &str,
793 args: &[VmValue],
794 ) -> Result<(), VmError> {
795 self.attach_ndb(ndb)?;
796 let function = ndb
797 .functions
798 .iter()
799 .find(|function| function.label == name)
800 .ok_or_else(|| VmError::Setup {
801 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("unknown NDB function {0:?}", name))
})format!("unknown NDB function {name:?}"),
802 })?;
803 expect_argument_count(function, args.len())?;
804
805 self.ip = usize::try_from(function.binary_start).map_err(|_error| VmError::Setup {
806 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("function {0:?} start offset exceeds usize range",
name))
})format!("function {name:?} start offset exceeds usize range"),
807 })?;
808 let preserved_sp = self.sp;
809 let preserved_bp = self.bp;
810 self.sp = preserved_sp;
811 self.bp = preserved_bp;
812 self.ret.clear();
813 self.ret.push(VmReturnFrame {
814 target: usize::MAX,
815 cleanup: Some(VmCallCleanup {
816 arg_cells: function
817 .args
818 .iter()
819 .map(cells_for_ndb_type)
820 .try_fold(0usize, |total, cells| cells.map(|cells| total + cells))?,
821 return_cells: if function.return_type == NdbType::Void {
822 0
823 } else {
824 cells_for_ndb_type(&function.return_type)?
825 },
826 }),
827 });
828 self.save_ip = 0;
829 self.save_sp = 0;
830 self.save_bp = 0;
831 self.saved_situation = None;
832 self.abort_requested = false;
833 self.aborted = false;
834
835 if preserved_sp == 0 {
836 self.stack.clear();
837 self.bp = 0;
838 }
839 if function.return_type != NdbType::Void {
840 self.push(default_value_for_ndb_type(&function.return_type)?);
841 }
842 for (expected, actual) in function.args.iter().zip(args) {
843 validate_entry_argument(expected, actual)?;
844 self.push(actual.clone());
845 }
846 Ok(())
847 }
848
849 pub fn function_return_value(&self, ndb: &Ndb, name: &str) -> Result<Option<VmValue>, VmError> {
857 let function = ndb
858 .functions
859 .iter()
860 .find(|function| function.label == name)
861 .ok_or_else(|| VmError::Setup {
862 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("unknown NDB function {0:?}", name))
})format!("unknown NDB function {name:?}"),
863 })?;
864 if function.return_type == NdbType::Void {
865 return Ok(None);
866 }
867 validate_supported_ndb_value_type(&function.return_type, "return type", None)?;
868 self.stack
869 .last()
870 .cloned()
871 .ok_or_else(|| VmError::StackUnderflow {
872 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("function {0:?} return slot is missing",
name))
})format!("function {name:?} return slot is missing"),
873 })
874 .map(Some)
875 }
876
877 pub fn abort(&mut self) {
880 self.abort_requested = true;
881 }
882
883 #[must_use]
885 pub fn aborted(&self) -> bool {
886 self.aborted
887 }
888
889 pub fn push(&mut self, value: VmValue) {
891 self.stack.push(value);
892 self.sp += 1;
893 }
894
895 pub fn push_int(&mut self, value: i32) {
897 self.push(VmValue::Int(value));
898 }
899
900 pub fn push_float(&mut self, value: f32) {
902 self.push(VmValue::Float(value));
903 }
904
905 pub fn push_string(&mut self, value: impl Into<String>) {
907 self.push(VmValue::String(value.into()));
908 }
909
910 pub fn push_object(&mut self, value: VmObjectId) {
912 self.push(VmValue::Object(value));
913 }
914
915 pub fn push_engine_structure(&mut self, index: u8, value: VmEngineStructureValue) {
917 self.push(VmValue::EngineStructure {
918 index,
919 value,
920 });
921 }
922
923 pub fn push_vector(&mut self, value: [f32; 3]) {
925 for component in value {
926 self.push_float(component);
927 }
928 }
929
930 pub fn pop(&mut self) -> Result<VmValue, VmError> {
936 let value = self.stack.pop().ok_or_else(|| VmError::StackUnderflow {
937 message: "attempted to pop from an empty stack".to_string(),
938 })?;
939 self.sp -= 1;
940 Ok(value)
941 }
942
943 pub fn pop_int(&mut self) -> Result<i32, VmError> {
949 match self.pop()? {
950 VmValue::Int(value) => Ok(value),
951 other => Err(VmError::TypeMismatch {
952 offset: self.ip,
953 message: "expected integer on stack top".to_string(),
954 expected: Some("int"),
955 actual: other.kind_name(),
956 }),
957 }
958 }
959
960 pub fn pop_float(&mut self) -> Result<f32, VmError> {
966 match self.pop()? {
967 VmValue::Float(value) => Ok(value),
968 other => Err(VmError::TypeMismatch {
969 offset: self.ip,
970 message: "expected float on stack top".to_string(),
971 expected: Some("float"),
972 actual: other.kind_name(),
973 }),
974 }
975 }
976
977 pub fn pop_string(&mut self) -> Result<String, VmError> {
983 match self.pop()? {
984 VmValue::String(value) => Ok(value),
985 other => Err(VmError::TypeMismatch {
986 offset: self.ip,
987 message: "expected string on stack top".to_string(),
988 expected: Some("string"),
989 actual: other.kind_name(),
990 }),
991 }
992 }
993
994 pub fn pop_object(&mut self) -> Result<VmObjectId, VmError> {
1000 match self.pop()? {
1001 VmValue::Object(value) => Ok(value),
1002 other => Err(VmError::TypeMismatch {
1003 offset: self.ip,
1004 message: "expected object on stack top".to_string(),
1005 expected: Some("object"),
1006 actual: other.kind_name(),
1007 }),
1008 }
1009 }
1010
1011 pub fn pop_engine_structure(&mut self) -> Result<(u8, VmEngineStructureValue), VmError> {
1017 match self.pop()? {
1018 VmValue::EngineStructure {
1019 index,
1020 value,
1021 } => Ok((index, value)),
1022 other => Err(VmError::TypeMismatch {
1023 offset: self.ip,
1024 message: "expected engine structure on stack top".to_string(),
1025 expected: Some("engine structure"),
1026 actual: other.kind_name(),
1027 }),
1028 }
1029 }
1030
1031 pub fn pop_engine_structure_index(
1038 &mut self,
1039 expected_index: u8,
1040 ) -> Result<VmEngineStructureValue, VmError> {
1041 let (index, value) = self.pop_engine_structure()?;
1042 if index != expected_index {
1043 return Err(VmError::TypeMismatch {
1044 offset: self.ip,
1045 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("expected engine structure {0} on stack top, found {1}",
expected_index, index))
})format!(
1046 "expected engine structure {} on stack top, found {}",
1047 expected_index, index
1048 ),
1049 expected: Some("engine structure"),
1050 actual: "engine structure",
1051 });
1052 }
1053 Ok(value)
1054 }
1055
1056 pub fn pop_vector(&mut self) -> Result<[f32; 3], VmError> {
1062 let z = self.pop_float()?;
1063 let y = self.pop_float()?;
1064 let x = self.pop_float()?;
1065 Ok([x, y, z])
1066 }
1067
1068 fn set_stack_pointer(&mut self, pointer: usize) -> Result<(), VmError> {
1069 if pointer > self.stack.len() {
1070 return Err(VmError::StackUnderflow {
1071 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("attempted to move stack pointer to {0}, but stack has {1} values",
pointer, self.stack.len()))
})format!(
1072 "attempted to move stack pointer to {}, but stack has {} values",
1073 pointer,
1074 self.stack.len()
1075 ),
1076 });
1077 }
1078 self.stack.truncate(pointer);
1079 self.sp = pointer;
1080 Ok(())
1081 }
1082
1083 fn assign_cell(&mut self, src: usize, dst: usize) -> Result<(), VmError> {
1084 let Some(value) = self.stack.get(src).cloned() else {
1085 return Err(VmError::StackUnderflow {
1086 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("attempted to copy from missing stack cell {0}",
src))
})format!("attempted to copy from missing stack cell {src}"),
1087 });
1088 };
1089 if dst >= self.stack.len() {
1090 self.stack.push(value);
1091 self.sp += 1;
1092 } else {
1093 let Some(target) = self.stack.get_mut(dst) else {
1094 return Err(VmError::StackUnderflow {
1095 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("attempted to write missing stack cell {0}",
dst))
})format!("attempted to write missing stack cell {dst}"),
1096 });
1097 };
1098 *target = value;
1099 }
1100 Ok(())
1101 }
1102}
1103
1104pub type VmCommandHandler = dyn Fn(&mut VmScript, u16, u8) -> Result<(), VmError> + 'static;
1106
1107pub type VmEngineStructureFactory = dyn Fn(u8) -> VmEngineStructureValue + 'static;
1109
1110pub type VmEngineStructureComparer =
1112 dyn Fn(u8, &VmEngineStructureValue, &VmEngineStructureValue) -> bool + 'static;
1113
1114pub type VmTraceHook = dyn Fn(&VmScript, &VmTraceEvent) + 'static;
1116
1117#[derive(#[automatically_derived]
impl ::core::fmt::Debug for VmRunOptions {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field1_finish(f, "VmRunOptions",
"max_instructions", &&self.max_instructions)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for VmRunOptions {
#[inline]
fn clone(&self) -> VmRunOptions {
let _: ::core::clone::AssertParamIsClone<Option<usize>>;
*self
}
}Clone, #[automatically_derived]
impl ::core::marker::Copy for VmRunOptions { }Copy, #[automatically_derived]
impl ::core::default::Default for VmRunOptions {
#[inline]
fn default() -> VmRunOptions {
VmRunOptions { max_instructions: ::core::default::Default::default() }
}
}Default)]
1119pub struct VmRunOptions {
1120 pub max_instructions: Option<usize>,
1123}
1124
1125#[derive(#[automatically_derived]
impl ::core::fmt::Debug for VmStepOutcome {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::write_str(f,
match self {
VmStepOutcome::Running => "Running",
VmStepOutcome::Halted => "Halted",
VmStepOutcome::Aborted => "Aborted",
})
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for VmStepOutcome {
#[inline]
fn clone(&self) -> VmStepOutcome { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for VmStepOutcome { }Copy, #[automatically_derived]
impl ::core::cmp::PartialEq for VmStepOutcome {
#[inline]
fn eq(&self, other: &VmStepOutcome) -> bool {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
__self_discr == __arg1_discr
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for VmStepOutcome {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {}
}Eq)]
1127pub enum VmStepOutcome {
1128 Running,
1130 Halted,
1132 Aborted,
1134}
1135
1136#[derive(#[automatically_derived]
impl ::core::default::Default for Vm {
#[inline]
fn default() -> Vm {
Vm {
commands: ::core::default::Default::default(),
engine_structures: ::core::default::Default::default(),
engine_structure_comparers: ::core::default::Default::default(),
trace_hook: ::core::default::Default::default(),
}
}
}Default)]
1138pub struct Vm {
1139 commands: Vec<Option<Box<VmCommandHandler>>>,
1140 engine_structures: Vec<Option<Box<VmEngineStructureFactory>>>,
1141 engine_structure_comparers: Vec<Option<Box<VmEngineStructureComparer>>>,
1142 trace_hook: Option<Box<VmTraceHook>>,
1143}
1144
1145impl fmt::Debug for Vm {
1146 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1147 f.debug_struct("Vm")
1148 .field("registered_commands", &self.commands.len())
1149 .field(
1150 "registered_engine_structures",
1151 &self.engine_structures.len(),
1152 )
1153 .field(
1154 "registered_engine_structure_comparers",
1155 &self.engine_structure_comparers.len(),
1156 )
1157 .field("has_trace_hook", &self.trace_hook.is_some())
1158 .finish()
1159 }
1160}
1161
1162impl Vm {
1163 #[must_use]
1165 pub fn new() -> Self {
1166 Self::default()
1167 }
1168
1169 pub fn define_command<F>(&mut self, command: u16, handler: F)
1171 where
1172 F: Fn(&mut VmScript, u16, u8) -> Result<(), VmError> + 'static,
1173 {
1174 let index = usize::from(command);
1175 if self.commands.len() <= index {
1176 self.commands.resize_with(index + 1, || None);
1177 }
1178 if let Some(slot) = self.commands.get_mut(index) {
1179 *slot = Some(Box::new(handler));
1180 }
1181 }
1182
1183 pub fn define_simple_command<F>(&mut self, command: u16, handler: F)
1185 where
1186 F: Fn(&mut VmScript) -> Result<(), VmError> + 'static,
1187 {
1188 self.define_command(command, move |script, _command, _argc| handler(script));
1189 }
1190
1191 pub fn define_engine_structure<F>(&mut self, index: u8, factory: F)
1194 where
1195 F: Fn(u8) -> VmEngineStructureValue + 'static,
1196 {
1197 let index = usize::from(index);
1198 if self.engine_structures.len() <= index {
1199 self.engine_structures.resize_with(index + 1, || None);
1200 }
1201 if let Some(slot) = self.engine_structures.get_mut(index) {
1202 *slot = Some(Box::new(factory));
1203 }
1204 }
1205
1206 pub fn define_engine_structure_default(&mut self, index: u8, value: VmEngineStructureValue) {
1209 self.define_engine_structure(index, move |_index| value.clone());
1210 }
1211
1212 pub fn define_engine_structure_comparer<F>(&mut self, index: u8, comparer: F)
1214 where
1215 F: Fn(u8, &VmEngineStructureValue, &VmEngineStructureValue) -> bool + 'static,
1216 {
1217 let index = usize::from(index);
1218 if self.engine_structure_comparers.len() <= index {
1219 self.engine_structure_comparers
1220 .resize_with(index + 1, || None);
1221 }
1222 if let Some(slot) = self.engine_structure_comparers.get_mut(index) {
1223 *slot = Some(Box::new(comparer));
1224 }
1225 }
1226
1227 pub fn define_trace_hook<F>(&mut self, hook: F)
1230 where
1231 F: Fn(&VmScript, &VmTraceEvent) + 'static,
1232 {
1233 self.trace_hook = Some(Box::new(hook));
1234 }
1235
1236 pub fn clear_trace_hook(&mut self) {
1238 self.trace_hook = None;
1239 }
1240
1241 pub fn run(&self, script: &mut VmScript) -> Result<(), VmError> {
1247 self.run_with_options(script, VmRunOptions::default())
1248 }
1249
1250 pub fn step(&self, script: &mut VmScript) -> Result<VmStepOutcome, VmError> {
1256 const HALT_IP: usize = usize::MAX;
1257
1258 script.aborted = false;
1259
1260 if script.ret.is_empty() {
1261 script.ret.push(VmReturnFrame {
1262 target: HALT_IP,
1263 cleanup: None,
1264 });
1265 }
1266
1267 if consume_abort_request(script) {
1268 return Ok(VmStepOutcome::Aborted);
1269 }
1270
1271 let decoded = script
1272 .program
1273 .instruction_at(script.ip)
1274 .ok_or(VmError::InvalidInstructionPointer {
1275 offset: script.ip
1276 })?
1277 .clone();
1278 self.emit_trace(script, &decoded);
1279 let next_ip = decoded.offset + decoded.instruction.encoded_len();
1280
1281 match decoded.instruction.opcode {
1282 NcsOpcode::NoOperation => {
1283 script.ip = next_ip;
1284 }
1285 NcsOpcode::Jmp => {
1286 script.ip = jump_target(decoded.offset, read_i32(&decoded, 0)?)?;
1287 }
1288 NcsOpcode::Jsr => {
1289 let target = jump_target(decoded.offset, read_i32(&decoded, 0)?)?;
1290 script.ret.push(VmReturnFrame {
1291 target: next_ip,
1292 cleanup: script
1293 .program
1294 .function_shapes
1295 .get(&target)
1296 .copied()
1297 .map(|shape| VmCallCleanup {
1298 arg_cells: shape.arg_cells,
1299 return_cells: shape.return_cells,
1300 }),
1301 });
1302 script.ip = target;
1303 }
1304 NcsOpcode::Jz => {
1305 if script.pop_int()? == 0 {
1306 script.ip = jump_target(decoded.offset, read_i32(&decoded, 0)?)?;
1307 } else {
1308 script.ip = next_ip;
1309 }
1310 }
1311 NcsOpcode::Jnz => {
1312 if script.pop_int()? != 0 {
1313 script.ip = jump_target(decoded.offset, read_i32(&decoded, 0)?)?;
1314 } else {
1315 script.ip = next_ip;
1316 }
1317 }
1318 NcsOpcode::Ret => {
1319 let frame = script.ret.pop().ok_or_else(|| VmError::StackUnderflow {
1320 message: "attempted to return without a return frame".to_string(),
1321 })?;
1322 if let Some(cleanup) = frame.cleanup {
1323 cleanup_call_frame(script, cleanup)?;
1324 }
1325 if frame.target == HALT_IP {
1326 return Ok(VmStepOutcome::Halted);
1327 }
1328 script.ip = frame.target;
1329 }
1330 NcsOpcode::SaveBasePointer => {
1331 script.push_int(
1332 i32::try_from(script.bp).map_err(|_error| {
1333 invalid_extra(&decoded, "base pointer exceeds i32 range")
1334 })?,
1335 );
1336 script.bp = script.sp.saturating_sub(1);
1337 script.ip = next_ip;
1338 }
1339 NcsOpcode::RestoreBasePointer => {
1340 script.bp = usize::try_from(script.pop_int()?)
1341 .map_err(|_error| invalid_extra(&decoded, "negative base pointer restore"))?;
1342 script.ip = next_ip;
1343 }
1344 NcsOpcode::RunstackAdd => {
1345 push_default_value(script, &decoded, self)?;
1346 script.ip = next_ip;
1347 }
1348 NcsOpcode::RunstackCopy | NcsOpcode::RunstackCopyBase => {
1349 let base = if decoded.instruction.opcode == NcsOpcode::RunstackCopyBase {
1350 script.bp
1351 } else {
1352 script.sp
1353 };
1354 let src = relative_stack_cell(&decoded, base, read_i32(&decoded, 0)?)?;
1355 let cells = usize::from(read_u16(&decoded, 4)?) / 4;
1356 for index in 0..cells {
1357 script.assign_cell(src + index, script.sp + index)?;
1358 }
1359 script.ip = next_ip;
1360 }
1361 NcsOpcode::Assignment | NcsOpcode::AssignmentBase => {
1362 let cells = usize::from(read_u16(&decoded, 4)?) / 4;
1363 let dst = if decoded.instruction.opcode == NcsOpcode::AssignmentBase {
1364 relative_stack_cell(&decoded, script.bp, read_i32(&decoded, 0)?)?
1365 } else {
1366 let encoded_offset = read_i32(&decoded, 0)?;
1367 match relative_stack_cell(&decoded, script.sp, encoded_offset) {
1368 Ok(dst) => dst,
1369 Err(VmError::StackUnderflow {
1370 ..
1371 }) => relative_stack_cell(&decoded, script.sp + cells, encoded_offset)?,
1372 Err(error) => return Err(error),
1373 }
1374 };
1375 for index in 0..cells {
1376 script.assign_cell(script.sp.saturating_sub(cells) + index, dst + index)?;
1377 }
1378 script.ip = next_ip;
1379 }
1380 NcsOpcode::Constant => {
1381 push_constant_value(script, &decoded)?;
1382 script.ip = next_ip;
1383 }
1384 NcsOpcode::ModifyStackPointer => {
1385 let byte_delta = read_i32(&decoded, 0)?;
1386 if byte_delta > 0 {
1387 let cells = usize::try_from(byte_delta / 4)
1388 .map_err(|_error| invalid_extra(&decoded, "invalid MOVSP payload"))?;
1389 for _ in 0..cells {
1390 script.push_int(0);
1391 }
1392 script.ip = next_ip;
1393 return Ok(VmStepOutcome::Running);
1394 }
1395 let cells = usize::try_from((-byte_delta) / 4)
1396 .map_err(|_error| invalid_extra(&decoded, "invalid MOVSP payload"))?;
1397 script.set_stack_pointer(script.sp.saturating_sub(cells))?;
1398 script.ip = next_ip;
1399 }
1400 NcsOpcode::Increment
1401 | NcsOpcode::Decrement
1402 | NcsOpcode::IncrementBase
1403 | NcsOpcode::DecrementBase => {
1404 let base = if #[allow(non_exhaustive_omitted_patterns)] match decoded.instruction.opcode {
NcsOpcode::IncrementBase | NcsOpcode::DecrementBase => true,
_ => false,
}matches!(
1405 decoded.instruction.opcode,
1406 NcsOpcode::IncrementBase | NcsOpcode::DecrementBase
1407 ) {
1408 script.bp
1409 } else {
1410 script.sp
1411 };
1412 let dst = relative_stack_cell(&decoded, base, read_i32(&decoded, 0)?)?;
1413 let delta = if #[allow(non_exhaustive_omitted_patterns)] match decoded.instruction.opcode {
NcsOpcode::Increment | NcsOpcode::IncrementBase => true,
_ => false,
}matches!(
1414 decoded.instruction.opcode,
1415 NcsOpcode::Increment | NcsOpcode::IncrementBase
1416 ) {
1417 1
1418 } else {
1419 -1
1420 };
1421 let value = script
1422 .stack
1423 .get_mut(dst)
1424 .ok_or_else(|| VmError::StackUnderflow {
1425 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("attempted to update missing stack cell {0}",
dst))
})format!("attempted to update missing stack cell {dst}"),
1426 })?;
1427 match value {
1428 VmValue::Int(int_value) => *int_value += delta,
1429 other => {
1430 return Err(VmError::TypeMismatch {
1431 offset: decoded.offset,
1432 message: "increment/decrement requires integer target".to_string(),
1433 expected: Some("int"),
1434 actual: other.kind_name(),
1435 });
1436 }
1437 }
1438 script.ip = next_ip;
1439 }
1440 NcsOpcode::Negation => {
1441 match decoded.instruction.auxcode {
1442 NcsAuxCode::TypeInteger => {
1443 let value = script.pop_int()?;
1444 script.push_int(-value);
1445 }
1446 NcsAuxCode::TypeFloat => {
1447 let value = script.pop_float()?;
1448 script.push_float(-value);
1449 }
1450 NcsAuxCode::TypeTypeVectorVector => {
1451 let [x, y, z] = script.pop_vector()?;
1452 script.push_vector([-x, -y, -z]);
1453 }
1454 _ => {
1455 return unsupported(
1456 &decoded,
1457 "NEG only supports integer, float, and vector",
1458 );
1459 }
1460 }
1461 script.ip = next_ip;
1462 }
1463 NcsOpcode::Equal
1464 | NcsOpcode::NotEqual
1465 | NcsOpcode::Lt
1466 | NcsOpcode::Gt
1467 | NcsOpcode::Leq
1468 | NcsOpcode::Geq => {
1469 apply_comparison(script, &decoded, self)?;
1470 script.ip = next_ip;
1471 }
1472 NcsOpcode::LogicalOr => {
1473 let rhs = script.pop_int()? != 0;
1474 let lhs = script.pop_int()? != 0;
1475 script.push_int(bool_to_int(lhs || rhs));
1476 script.ip = next_ip;
1477 }
1478 NcsOpcode::LogicalAnd => {
1479 let rhs = script.pop_int()? != 0;
1480 let lhs = script.pop_int()? != 0;
1481 script.push_int(bool_to_int(lhs && rhs));
1482 script.ip = next_ip;
1483 }
1484 NcsOpcode::InclusiveOr => {
1485 let rhs = script.pop_int()?;
1486 let lhs = script.pop_int()?;
1487 script.push_int(lhs | rhs);
1488 script.ip = next_ip;
1489 }
1490 NcsOpcode::ExclusiveOr => {
1491 let rhs = script.pop_int()?;
1492 let lhs = script.pop_int()?;
1493 script.push_int(lhs ^ rhs);
1494 script.ip = next_ip;
1495 }
1496 NcsOpcode::BooleanAnd => {
1497 let rhs = script.pop_int()?;
1498 let lhs = script.pop_int()?;
1499 script.push_int(lhs & rhs);
1500 script.ip = next_ip;
1501 }
1502 NcsOpcode::BooleanNot => {
1503 let value = script.pop_int()? == 0;
1504 script.push_int(bool_to_int(value));
1505 script.ip = next_ip;
1506 }
1507 NcsOpcode::OnesComplement => {
1508 let value = script.pop_int()?;
1509 script.push_int(!value);
1510 script.ip = next_ip;
1511 }
1512 NcsOpcode::ShiftLeft => {
1513 let rhs = script.pop_int()?;
1514 let lhs = script.pop_int()?;
1515 script.push_int(lhs.wrapping_shl(rhs as u32));
1516 script.ip = next_ip;
1517 }
1518 NcsOpcode::ShiftRight => {
1519 let rhs = script.pop_int()?;
1520 let lhs = script.pop_int()?;
1521 script.push_int(lhs.wrapping_shr(rhs as u32));
1522 script.ip = next_ip;
1523 }
1524 NcsOpcode::UShiftRight => {
1525 let rhs = script.pop_int()?;
1526 let lhs = script.pop_int()?;
1527 script.push_int(((lhs as u32).wrapping_shr(rhs as u32)) as i32);
1528 script.ip = next_ip;
1529 }
1530 NcsOpcode::Add => {
1531 apply_add(script, &decoded)?;
1532 script.ip = next_ip;
1533 }
1534 NcsOpcode::Sub => {
1535 apply_sub(script, &decoded)?;
1536 script.ip = next_ip;
1537 }
1538 NcsOpcode::Mul => {
1539 apply_mul(script, &decoded)?;
1540 script.ip = next_ip;
1541 }
1542 NcsOpcode::Div => {
1543 apply_div(script, &decoded)?;
1544 script.ip = next_ip;
1545 }
1546 NcsOpcode::Modulus => {
1547 let rhs = script.pop_int()?;
1548 let lhs = script.pop_int()?;
1549 if rhs == 0 {
1550 return unsupported(&decoded, "modulus by zero");
1551 }
1552 script.push_int(lhs % rhs);
1553 script.ip = next_ip;
1554 }
1555 NcsOpcode::DeStruct => {
1556 let size_orig = usize::from(read_u16(&decoded, 0)?) / 4;
1557 let start = usize::from(read_u16(&decoded, 2)?) / 4;
1558 let size = usize::from(read_u16(&decoded, 4)?) / 4;
1559
1560 if size + start < size_orig {
1561 let new_sp = script.sp.saturating_sub(size_orig) + size + start;
1562 script.set_stack_pointer(new_sp)?;
1563 }
1564
1565 if start > 0 {
1566 let from = script.sp.saturating_sub(size + start);
1567 let to = script.sp.saturating_sub(start);
1568 for index in from..to {
1569 script.assign_cell(index + start, index)?;
1570 }
1571 script.set_stack_pointer(script.sp.saturating_sub(start))?;
1572 }
1573 script.ip = next_ip;
1574 }
1575 NcsOpcode::StoreIp | NcsOpcode::StoreState => {
1576 script.save_ip =
1577 jump_target(decoded.offset, i32::from(decoded.instruction.auxcode as u8))?;
1578 if decoded.instruction.opcode == NcsOpcode::StoreState {
1579 let situation = capture_saved_situation(script, &decoded, script.save_ip)?;
1580 script.save_bp = situation.bp;
1581 script.save_sp = situation.sp;
1582 script.saved_situation = Some(situation);
1583 }
1584 script.ip = next_ip;
1585 }
1586 NcsOpcode::ExecuteCommand => {
1587 let command = read_u16(&decoded, 0)?;
1588 let argc = read_u8(&decoded, 2)?;
1589 let Some(handler) = self
1590 .commands
1591 .get(usize::from(command))
1592 .and_then(Option::as_ref)
1593 else {
1594 return Err(VmError::InvalidCommand {
1595 offset: decoded.offset,
1596 command,
1597 });
1598 };
1599 handler(script, command, argc)?;
1600 if consume_abort_request(script) {
1601 return Ok(VmStepOutcome::Aborted);
1602 }
1603 script.ip = next_ip;
1604 }
1605 }
1606
1607 Ok(VmStepOutcome::Running)
1608 }
1609
1610 pub fn run_until_offset(
1621 &self,
1622 script: &mut VmScript,
1623 offset: usize,
1624 options: VmRunOptions,
1625 ) -> Result<VmStepOutcome, VmError> {
1626 let mut instructions_executed = 0usize;
1627 loop {
1628 if script.ip == offset {
1629 return Ok(VmStepOutcome::Running);
1630 }
1631 if let Some(limit) = options.max_instructions
1632 && instructions_executed >= limit
1633 {
1634 return Err(VmError::InstructionLimitExceeded {
1635 offset: script.ip,
1636 limit,
1637 });
1638 }
1639 instructions_executed += 1;
1640 match self.step(script)? {
1641 VmStepOutcome::Running => {}
1642 outcome => return Ok(outcome),
1643 }
1644 }
1645 }
1646
1647 pub fn step_over(
1658 &self,
1659 script: &mut VmScript,
1660 options: VmRunOptions,
1661 ) -> Result<VmStepOutcome, VmError> {
1662 let Some(instruction) = script.current_instruction().cloned() else {
1663 return Err(VmError::InvalidInstructionPointer {
1664 offset: script.ip
1665 });
1666 };
1667 if instruction.opcode != NcsOpcode::Jsr {
1668 return self.step(script);
1669 }
1670
1671 let return_offset = script.ip + instruction.encoded_len();
1672 let depth = script.ret.len();
1673 match self.step(script)? {
1674 VmStepOutcome::Running => {}
1675 outcome => return Ok(outcome),
1676 }
1677
1678 let mut instructions_executed = 0usize;
1679 loop {
1680 if script.ip == return_offset && script.ret.len() == depth {
1681 return Ok(VmStepOutcome::Running);
1682 }
1683 if let Some(limit) = options.max_instructions
1684 && instructions_executed >= limit
1685 {
1686 return Err(VmError::InstructionLimitExceeded {
1687 offset: script.ip,
1688 limit,
1689 });
1690 }
1691 instructions_executed += 1;
1692 match self.step(script)? {
1693 VmStepOutcome::Running => {}
1694 outcome => return Ok(outcome),
1695 }
1696 }
1697 }
1698
1699 pub fn step_out(
1709 &self,
1710 script: &mut VmScript,
1711 options: VmRunOptions,
1712 ) -> Result<VmStepOutcome, VmError> {
1713 let depth = script.ret.len();
1714 let mut instructions_executed = 0usize;
1715 loop {
1716 if let Some(limit) = options.max_instructions
1717 && instructions_executed >= limit
1718 {
1719 return Err(VmError::InstructionLimitExceeded {
1720 offset: script.ip,
1721 limit,
1722 });
1723 }
1724 instructions_executed += 1;
1725 match self.step(script)? {
1726 VmStepOutcome::Running => {
1727 if script.ret.len() < depth {
1728 return Ok(VmStepOutcome::Running);
1729 }
1730 }
1731 outcome => return Ok(outcome),
1732 }
1733 }
1734 }
1735
1736 pub fn run_until_line(
1747 &self,
1748 script: &mut VmScript,
1749 file_name: &str,
1750 line_number: usize,
1751 options: VmRunOptions,
1752 ) -> Result<VmStepOutcome, VmError> {
1753 let target = script
1754 .program
1755 .source_lines
1756 .iter()
1757 .find(|line| {
1758 line.file_name.eq_ignore_ascii_case(file_name) && line.line_number == line_number
1759 })
1760 .map(|line| line.start)
1761 .ok_or_else(|| VmError::Setup {
1762 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("no attached source line mapping for {0}:{1}",
file_name, line_number))
})format!("no attached source line mapping for {file_name}:{line_number}"),
1763 })?;
1764 self.run_until_offset(script, target, options)
1765 }
1766
1767 pub fn run_until_function(
1778 &self,
1779 script: &mut VmScript,
1780 name: &str,
1781 options: VmRunOptions,
1782 ) -> Result<VmStepOutcome, VmError> {
1783 let target = script
1784 .program
1785 .functions
1786 .iter()
1787 .find(|function| function.label == name)
1788 .map(|function| function.start)
1789 .ok_or_else(|| VmError::Setup {
1790 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("no attached function mapping for {0:?}",
name))
})format!("no attached function mapping for {name:?}"),
1791 })?;
1792 self.run_until_offset(script, target, options)
1793 }
1794
1795 pub fn run_with_options(
1803 &self,
1804 script: &mut VmScript,
1805 options: VmRunOptions,
1806 ) -> Result<(), VmError> {
1807 let mut instructions_executed = 0usize;
1808
1809 loop {
1810 if let Some(limit) = options.max_instructions
1811 && instructions_executed >= limit
1812 {
1813 return Err(VmError::InstructionLimitExceeded {
1814 offset: script.ip,
1815 limit,
1816 });
1817 }
1818 instructions_executed += 1;
1819 match self.step(script)? {
1820 VmStepOutcome::Running => {}
1821 VmStepOutcome::Halted | VmStepOutcome::Aborted => break,
1822 }
1823 }
1824
1825 Ok(())
1826 }
1827
1828 pub fn run_situation(&self, situation: &VmSituation) -> Result<VmScript, VmError> {
1834 self.run_situation_with_options(situation, VmRunOptions::default())
1835 }
1836
1837 pub fn run_situation_with_options(
1844 &self,
1845 situation: &VmSituation,
1846 options: VmRunOptions,
1847 ) -> Result<VmScript, VmError> {
1848 let mut script = situation.to_script();
1849 self.run_with_options(&mut script, options)?;
1850 Ok(script)
1851 }
1852
1853 pub fn run_bytes(&self, bytes: &[u8], label: impl Into<String>) -> Result<VmScript, VmError> {
1859 self.run_bytes_with_options(bytes, label, VmRunOptions::default())
1860 }
1861
1862 pub fn run_bytes_with_options(
1869 &self,
1870 bytes: &[u8],
1871 label: impl Into<String>,
1872 options: VmRunOptions,
1873 ) -> Result<VmScript, VmError> {
1874 let mut script = VmScript::from_bytes(bytes, label)?;
1875 self.run_with_options(&mut script, options)?;
1876 Ok(script)
1877 }
1878
1879 pub fn run_bytes_with_ndb(
1891 &self,
1892 bytes: &[u8],
1893 label: impl Into<String>,
1894 ndb: &Ndb,
1895 ) -> Result<VmScript, VmError> {
1896 self.run_bytes_with_ndb_and_options(bytes, label, ndb, VmRunOptions::default())
1897 }
1898
1899 pub fn run_bytes_with_ndb_and_options(
1907 &self,
1908 bytes: &[u8],
1909 label: impl Into<String>,
1910 ndb: &Ndb,
1911 options: VmRunOptions,
1912 ) -> Result<VmScript, VmError> {
1913 let mut script = VmScript::from_bytes_with_ndb(bytes, label, ndb)?;
1914 self.run_with_options(&mut script, options)?;
1915 Ok(script)
1916 }
1917
1918 pub fn run_function_bytes(
1928 &self,
1929 bytes: &[u8],
1930 label: impl Into<String>,
1931 ndb: &Ndb,
1932 name: &str,
1933 args: &[VmValue],
1934 ) -> Result<VmScript, VmError> {
1935 self.run_function_bytes_with_options(bytes, label, ndb, name, args, VmRunOptions::default())
1936 }
1937
1938 pub fn run_function_bytes_with_options(
1946 &self,
1947 bytes: &[u8],
1948 label: impl Into<String>,
1949 ndb: &Ndb,
1950 name: &str,
1951 args: &[VmValue],
1952 options: VmRunOptions,
1953 ) -> Result<VmScript, VmError> {
1954 let mut script = VmScript::from_bytes_with_ndb(bytes, label, ndb)?;
1955 if script_has_globals(ndb) {
1956 self.bootstrap_globals_for_direct_call(&mut script, ndb, options)?;
1957 }
1958 script.prepare_function_call(ndb, name, args)?;
1959 self.run_with_options(&mut script, options)?;
1960 Ok(script)
1961 }
1962
1963 fn default_engine_structure_value(&self, index: u8) -> VmEngineStructureValue {
1964 self.engine_structures
1965 .get(usize::from(index))
1966 .and_then(Option::as_ref)
1967 .map_or_else(
1968 || default_engine_structure_value(index),
1969 |factory| factory(index),
1970 )
1971 }
1972
1973 fn compare_engine_structure_values(
1974 &self,
1975 index: u8,
1976 lhs: &VmEngineStructureValue,
1977 rhs: &VmEngineStructureValue,
1978 ) -> bool {
1979 self.engine_structure_comparers
1980 .get(usize::from(index))
1981 .and_then(Option::as_ref)
1982 .map_or_else(|| lhs == rhs, |comparer| comparer(index, lhs, rhs))
1983 }
1984
1985 fn emit_trace(&self, script: &VmScript, decoded: &VmProgramInstruction) {
1986 let Some(hook) = self.trace_hook.as_ref() else {
1987 return;
1988 };
1989 hook(
1990 script,
1991 &VmTraceEvent {
1992 offset: decoded.offset,
1993 ip: script.ip,
1994 sp: script.sp,
1995 bp: script.bp,
1996 instruction: decoded.instruction.clone(),
1997 },
1998 );
1999 }
2000
2001 fn bootstrap_globals_for_direct_call(
2002 &self,
2003 script: &mut VmScript,
2004 ndb: &Ndb,
2005 options: VmRunOptions,
2006 ) -> Result<(), VmError> {
2007 if !script_has_globals(ndb) {
2008 return Ok(());
2009 }
2010
2011 let entry_name = entry_function_name(ndb).ok_or_else(|| VmError::Setup {
2012 message: "script entry function is missing".to_string(),
2013 })?;
2014 let entry = ndb
2015 .functions
2016 .iter()
2017 .find(|function| function.label == entry_name)
2018 .ok_or_else(|| VmError::Setup {
2019 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("missing NDB entry for {0:?}",
entry_name))
})format!("missing NDB entry for {entry_name:?}"),
2020 })?;
2021 let entry_start = usize::try_from(entry.binary_start).map_err(|_error| VmError::Setup {
2022 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("entry function {0:?} start offset exceeds usize range",
entry_name))
})format!("entry function {entry_name:?} start offset exceeds usize range"),
2023 })?;
2024
2025 let mut bootstrap =
2026 VmScript::from_instructions(Vec::new(), ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}#bootstrap", script.label))
})format!("{}#bootstrap", script.label));
2027 bootstrap.program = script.program.clone();
2028 let entry_index = *bootstrap.program.offsets_to_index.get(&entry_start).ok_or(
2029 VmError::InvalidInstructionPointer {
2030 offset: entry_start,
2031 },
2032 )?;
2033 let Some(instruction) = bootstrap.program.instructions.get_mut(entry_index) else {
2034 return Err(VmError::InvalidInstructionPointer {
2035 offset: entry_start,
2036 });
2037 };
2038 instruction.instruction = NcsInstruction {
2039 opcode: NcsOpcode::Ret,
2040 auxcode: NcsAuxCode::None,
2041 extra: Vec::new(),
2042 };
2043 self.run_with_options(&mut bootstrap, options)?;
2044
2045 let globals_cells = global_stack_cells(ndb)?;
2046 let prefix_cells = loader_prefix_cells(ndb)?;
2047 let globals_end = prefix_cells + globals_cells;
2048 let globals = bootstrap
2049 .stack
2050 .get(prefix_cells..globals_end)
2051 .ok_or_else(|| VmError::StackUnderflow {
2052 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("bootstrapped globals frame expected cells {0}..{1}, but stack only has {2}",
prefix_cells, globals_end, bootstrap.stack.len()))
})format!(
2053 "bootstrapped globals frame expected cells {}..{}, but stack only has {}",
2054 prefix_cells,
2055 globals_end,
2056 bootstrap.stack.len()
2057 ),
2058 })?
2059 .to_vec();
2060 script.ip = 0;
2061 script.sp = globals.len();
2062 script.bp = 0;
2063 script.ret.clear();
2064 script.stack = globals;
2065 if globals_cells > 0 {
2066 script.push_int(0);
2067 script.bp = globals_cells;
2068 }
2069 script.save_ip = 0;
2070 script.save_sp = 0;
2071 script.save_bp = 0;
2072 script.saved_situation = None;
2073 script.abort_requested = false;
2074 script.aborted = false;
2075 Ok(())
2076 }
2077}
2078
2079fn script_has_globals(ndb: &Ndb) -> bool {
2080 ndb.variables
2081 .iter()
2082 .any(|variable| variable.binary_end == u32::MAX && variable.label != "#retval")
2083}
2084
2085fn entry_function_name(ndb: &Ndb) -> Option<&'static str> {
2086 if ndb
2087 .functions
2088 .iter()
2089 .any(|function| function.label == "main")
2090 {
2091 Some("main")
2092 } else if ndb
2093 .functions
2094 .iter()
2095 .any(|function| function.label == "StartingConditional")
2096 {
2097 Some("StartingConditional")
2098 } else {
2099 None
2100 }
2101}
2102
2103fn global_stack_cells(ndb: &Ndb) -> Result<usize, VmError> {
2104 ndb.variables
2105 .iter()
2106 .filter(|variable| variable.binary_end == u32::MAX && variable.label != "#retval")
2107 .try_fold(0usize, |cells, variable| {
2108 let start =
2109 usize::try_from(variable.stack_loc / 4).map_err(|_error| VmError::Setup {
2110 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("global {0:?} stack location exceeds usize range",
variable.label))
})format!(
2111 "global {:?} stack location exceeds usize range",
2112 variable.label
2113 ),
2114 })?;
2115 let width = cells_for_ndb_type(&variable.ty)?;
2116 Ok(cells.max(start + width))
2117 })
2118}
2119
2120fn loader_prefix_cells(ndb: &Ndb) -> Result<usize, VmError> {
2121 let Some(retval) = ndb
2122 .variables
2123 .iter()
2124 .find(|variable| variable.binary_end == u32::MAX && variable.label == "#retval")
2125 else {
2126 return Ok(0);
2127 };
2128 cells_for_ndb_type(&retval.ty)
2129}
2130
2131fn expect_argument_count(function: &NdbFunction, actual: usize) -> Result<(), VmError> {
2132 if function.args.len() != actual {
2133 return Err(VmError::Setup {
2134 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("function {0:?} expects {1} arguments, got {2}",
function.label, function.args.len(), actual))
})format!(
2135 "function {:?} expects {} arguments, got {}",
2136 function.label,
2137 function.args.len(),
2138 actual
2139 ),
2140 });
2141 }
2142 Ok(())
2143}
2144
2145fn validate_supported_ndb_value_type(
2146 ty: &NdbType,
2147 role: &str,
2148 index: Option<usize>,
2149) -> Result<(), VmError> {
2150 match ty {
2151 NdbType::Float
2152 | NdbType::Int
2153 | NdbType::Void
2154 | NdbType::Object
2155 | NdbType::String
2156 | NdbType::EngineStructure(_) => Ok(()),
2157 NdbType::Struct(struct_index) => Err(VmError::Setup {
2158 message: match index {
2159 Some(index) => ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("unsupported direct-call {0} at position {1}: struct t{2:04}",
role, index, struct_index))
})format!(
2160 "unsupported direct-call {role} at position {index}: struct t{struct_index:04}"
2161 ),
2162 None => ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("unsupported direct-call {0}: struct t{1:04}",
role, struct_index))
})format!("unsupported direct-call {role}: struct t{struct_index:04}"),
2163 },
2164 }),
2165 NdbType::Unknown | NdbType::Raw(_) => Err(VmError::Setup {
2166 message: match index {
2167 Some(index) => ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("unsupported direct-call {0} at position {1}: {2}",
role, index, ty))
})format!("unsupported direct-call {role} at position {index}: {ty}"),
2168 None => ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("unsupported direct-call {0}: {1}",
role, ty))
})format!("unsupported direct-call {role}: {ty}"),
2169 },
2170 }),
2171 }
2172}
2173
2174fn validate_entry_argument(expected: &NdbType, actual: &VmValue) -> Result<(), VmError> {
2175 validate_supported_ndb_value_type(expected, "argument type", None)?;
2176 match (expected, actual) {
2177 (NdbType::Int, VmValue::Int(_))
2178 | (NdbType::Float, VmValue::Float(_))
2179 | (NdbType::String, VmValue::String(_))
2180 | (NdbType::Object, VmValue::Object(_)) => Ok(()),
2181 (
2182 NdbType::EngineStructure(expected),
2183 VmValue::EngineStructure {
2184 index, ..
2185 },
2186 ) if expected == index => Ok(()),
2187 _ => Err(VmError::Setup {
2188 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("argument type mismatch: expected {0}, got {1}",
expected, actual.kind_name()))
})format!(
2189 "argument type mismatch: expected {}, got {}",
2190 expected,
2191 actual.kind_name()
2192 ),
2193 }),
2194 }
2195}
2196
2197fn default_value_for_ndb_type(ty: &NdbType) -> Result<VmValue, VmError> {
2198 validate_supported_ndb_value_type(ty, "return type", None)?;
2199 Ok(match ty {
2200 NdbType::Float => VmValue::Float(0.0),
2201 NdbType::Int => VmValue::Int(0),
2202 NdbType::Void => {
2203 return Err(VmError::Setup {
2204 message: "void return slots are not materialized".to_string(),
2205 });
2206 }
2207 NdbType::Object => VmValue::Object(0),
2208 NdbType::String => VmValue::String(String::new()),
2209 NdbType::EngineStructure(index) => VmValue::EngineStructure {
2210 index: *index,
2211 value: default_engine_structure_value(*index),
2212 },
2213 NdbType::Struct(_) | NdbType::Unknown | NdbType::Raw(_) => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
2214 })
2215}
2216
2217fn read_u8(decoded: &VmProgramInstruction, start: usize) -> Result<u8, VmError> {
2218 decoded
2219 .instruction
2220 .extra
2221 .get(start)
2222 .copied()
2223 .ok_or_else(|| invalid_extra(decoded, "payload ended early"))
2224}
2225
2226fn read_u16(decoded: &VmProgramInstruction, start: usize) -> Result<u16, VmError> {
2227 let window = decoded
2228 .instruction
2229 .extra
2230 .get(start..start + 2)
2231 .ok_or_else(|| invalid_extra(decoded, "payload ended early"))?;
2232 let bytes =
2233 <[u8; 2]>::try_from(window).map_err(|_error| invalid_extra(decoded, "bad u16 payload"))?;
2234 Ok(u16::from_be_bytes(bytes))
2235}
2236
2237fn read_i32(decoded: &VmProgramInstruction, start: usize) -> Result<i32, VmError> {
2238 let window = decoded
2239 .instruction
2240 .extra
2241 .get(start..start + 4)
2242 .ok_or_else(|| invalid_extra(decoded, "payload ended early"))?;
2243 let bytes =
2244 <[u8; 4]>::try_from(window).map_err(|_error| invalid_extra(decoded, "bad i32 payload"))?;
2245 Ok(i32::from_be_bytes(bytes))
2246}
2247
2248fn read_u32(decoded: &VmProgramInstruction, start: usize) -> Result<u32, VmError> {
2249 let window = decoded
2250 .instruction
2251 .extra
2252 .get(start..start + 4)
2253 .ok_or_else(|| invalid_extra(decoded, "payload ended early"))?;
2254 let bytes =
2255 <[u8; 4]>::try_from(window).map_err(|_error| invalid_extra(decoded, "bad u32 payload"))?;
2256 Ok(u32::from_be_bytes(bytes))
2257}
2258
2259fn read_f32(decoded: &VmProgramInstruction, start: usize) -> Result<f32, VmError> {
2260 Ok(f32::from_bits(read_u32(decoded, start)?))
2261}
2262
2263fn read_ncs_string(decoded: &VmProgramInstruction) -> Result<String, VmError> {
2264 let length = usize::from(read_u16(decoded, 0)?);
2265 let window = decoded
2266 .instruction
2267 .extra
2268 .get(2..2 + length)
2269 .ok_or_else(|| invalid_extra(decoded, "string payload shorter than declared length"))?;
2270 let value = str::from_utf8(window).map_err(|error| {
2271 invalid_extra(decoded, &::alloc::__export::must_use({
::alloc::fmt::format(format_args!("invalid UTF-8 string payload: {0}",
error))
})format!("invalid UTF-8 string payload: {error}"))
2272 })?;
2273 Ok(value.to_string())
2274}
2275
2276fn relative_stack_cell(
2277 decoded: &VmProgramInstruction,
2278 base: usize,
2279 encoded_offset: i32,
2280) -> Result<usize, VmError> {
2281 if encoded_offset > 0 || encoded_offset % 4 != 0 {
2282 return Err(invalid_extra(
2283 decoded,
2284 &::alloc::__export::must_use({
::alloc::fmt::format(format_args!("expected negative 4-byte-aligned stack offset, got {0}",
encoded_offset))
})format!("expected negative 4-byte-aligned stack offset, got {encoded_offset}"),
2285 ));
2286 }
2287 let cells = usize::try_from((-encoded_offset) / 4)
2288 .map_err(|_error| invalid_extra(decoded, "invalid negative stack offset"))?;
2289 base.checked_sub(cells)
2290 .ok_or_else(|| VmError::StackUnderflow {
2291 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("stack offset {0} underflowed base {1}",
encoded_offset, base))
})format!("stack offset {encoded_offset} underflowed base {base}"),
2292 })
2293}
2294
2295fn jump_target(current: usize, delta: i32) -> Result<usize, VmError> {
2296 if delta >= 0 {
2297 current
2298 .checked_add(usize::try_from(delta).ok().unwrap_or(usize::MAX))
2299 .ok_or(VmError::InvalidInstructionPointer {
2300 offset: current
2301 })
2302 } else {
2303 current
2304 .checked_sub(usize::try_from(-delta).ok().unwrap_or(usize::MAX))
2305 .ok_or(VmError::InvalidInstructionPointer {
2306 offset: current
2307 })
2308 }
2309}
2310
2311fn capture_saved_situation(
2312 script: &VmScript,
2313 decoded: &VmProgramInstruction,
2314 target_ip: usize,
2315) -> Result<VmSituation, VmError> {
2316 let global_bytes = usize::try_from(read_u32(decoded, 0)?)
2317 .map_err(|_error| invalid_extra(decoded, "global size exceeds usize range"))?;
2318 let stack_bytes = usize::try_from(read_u32(decoded, 4)?)
2319 .map_err(|_error| invalid_extra(decoded, "stack size exceeds usize range"))?;
2320 if global_bytes % 4 != 0 || stack_bytes % 4 != 0 {
2321 return Err(invalid_extra(
2322 decoded,
2323 "saved state sizes must be 4-byte aligned",
2324 ));
2325 }
2326
2327 let saved_sp = (global_bytes + stack_bytes) / 4;
2328 if saved_sp > script.sp {
2329 return Err(VmError::StackUnderflow {
2330 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("saved situation requested {0} cells, but stack only has {1}",
saved_sp, script.sp))
})format!(
2331 "saved situation requested {} cells, but stack only has {}",
2332 saved_sp, script.sp
2333 ),
2334 });
2335 }
2336
2337 Ok(VmSituation {
2338 label: script.label.clone(),
2339 program: script.program.clone(),
2340 ip: target_ip,
2341 sp: saved_sp,
2342 bp: script.bp,
2343 stack: script
2344 .stack
2345 .get(..saved_sp)
2346 .ok_or_else(|| VmError::StackUnderflow {
2347 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("saved situation requested {0} cells, but stack only has {1}",
saved_sp, script.stack.len()))
})format!(
2348 "saved situation requested {} cells, but stack only has {}",
2349 saved_sp,
2350 script.stack.len()
2351 ),
2352 })?
2353 .to_vec(),
2354 })
2355}
2356
2357fn default_engine_structure_value(index: u8) -> VmEngineStructureValue {
2358 if index == 7 {
2359 VmEngineStructureValue::Text(String::new())
2360 } else {
2361 VmEngineStructureValue::Word(0)
2362 }
2363}
2364
2365fn consume_abort_request(script: &mut VmScript) -> bool {
2366 if script.abort_requested {
2367 script.abort_requested = false;
2368 script.aborted = true;
2369 script.ret.clear();
2370 return true;
2371 }
2372 false
2373}
2374
2375fn cells_for_ndb_type(ty: &NdbType) -> Result<usize, VmError> {
2376 Ok(match ty {
2377 NdbType::Float
2378 | NdbType::Int
2379 | NdbType::Object
2380 | NdbType::String
2381 | NdbType::EngineStructure(_) => 1,
2382 NdbType::Void => 0,
2383 NdbType::Struct(struct_index) => {
2384 return Err(VmError::Setup {
2385 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("unsupported VM function metadata for struct t{0:04}",
struct_index))
})format!("unsupported VM function metadata for struct t{struct_index:04}"),
2386 });
2387 }
2388 NdbType::Unknown | NdbType::Raw(_) => {
2389 return Err(VmError::Setup {
2390 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("unsupported VM function metadata type {0}",
ty))
})format!("unsupported VM function metadata type {ty}"),
2391 });
2392 }
2393 })
2394}
2395
2396fn cleanup_call_frame(script: &mut VmScript, cleanup: VmCallCleanup) -> Result<(), VmError> {
2397 let frame_cells = cleanup.arg_cells + cleanup.return_cells;
2398 let frame_start =
2399 script
2400 .sp
2401 .checked_sub(frame_cells)
2402 .ok_or_else(|| VmError::StackUnderflow {
2403 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("attempted to clean up {0} call-frame cells from stack with only {1} values",
frame_cells, script.sp))
})format!(
2404 "attempted to clean up {} call-frame cells from stack with only {} values",
2405 frame_cells, script.sp
2406 ),
2407 })?;
2408 if cleanup.return_cells == 0 {
2409 return script.set_stack_pointer(frame_start);
2410 }
2411
2412 let return_end = frame_start + cleanup.return_cells;
2413 let return_values = script
2414 .stack
2415 .get(frame_start..return_end)
2416 .ok_or_else(|| VmError::StackUnderflow {
2417 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("missing {0} return cells in call frame starting at {1}",
cleanup.return_cells, frame_start))
})format!(
2418 "missing {} return cells in call frame starting at {}",
2419 cleanup.return_cells, frame_start
2420 ),
2421 })?
2422 .to_vec();
2423 script.set_stack_pointer(frame_start)?;
2424 for value in return_values {
2425 script.push(value);
2426 }
2427 Ok(())
2428}
2429
2430fn push_default_value(
2431 script: &mut VmScript,
2432 decoded: &VmProgramInstruction,
2433 vm: &Vm,
2434) -> Result<(), VmError> {
2435 match decoded.instruction.auxcode {
2436 NcsAuxCode::TypeInteger => script.push_int(0),
2437 NcsAuxCode::TypeFloat => script.push_float(0.0),
2438 NcsAuxCode::TypeString => script.push_string(String::new()),
2439 NcsAuxCode::TypeObject => script.push_object(0),
2440 aux => {
2441 let Some(index) = engine_structure_index(aux) else {
2442 return unsupported(decoded, "RSADD does not support this auxcode");
2443 };
2444 script.push_engine_structure(index, vm.default_engine_structure_value(index));
2445 }
2446 }
2447 Ok(())
2448}
2449
2450fn push_constant_value(
2451 script: &mut VmScript,
2452 decoded: &VmProgramInstruction,
2453) -> Result<(), VmError> {
2454 match decoded.instruction.auxcode {
2455 NcsAuxCode::TypeInteger => script.push_int(read_i32(decoded, 0)?),
2456 NcsAuxCode::TypeFloat => script.push_float(read_f32(decoded, 0)?),
2457 NcsAuxCode::TypeString => script.push_string(read_ncs_string(decoded)?),
2458 NcsAuxCode::TypeObject => script.push_object(read_u32(decoded, 0)?),
2459 aux => {
2460 let Some(index) = engine_structure_index(aux) else {
2461 return unsupported(decoded, "CONST does not support this auxcode");
2462 };
2463 let value = if index == 7 {
2464 VmEngineStructureValue::Text(read_ncs_string(decoded)?)
2465 } else {
2466 VmEngineStructureValue::Word(read_u32(decoded, 0)?)
2467 };
2468 script.push(VmValue::EngineStructure {
2469 index,
2470 value,
2471 });
2472 }
2473 }
2474 Ok(())
2475}
2476
2477fn engine_structure_index(auxcode: NcsAuxCode) -> Option<u8> {
2478 match auxcode {
2479 NcsAuxCode::TypeEngst0 => Some(0),
2480 NcsAuxCode::TypeEngst1 => Some(1),
2481 NcsAuxCode::TypeEngst2 => Some(2),
2482 NcsAuxCode::TypeEngst3 => Some(3),
2483 NcsAuxCode::TypeEngst4 => Some(4),
2484 NcsAuxCode::TypeEngst5 => Some(5),
2485 NcsAuxCode::TypeEngst6 => Some(6),
2486 NcsAuxCode::TypeEngst7 => Some(7),
2487 NcsAuxCode::TypeEngst8 => Some(8),
2488 NcsAuxCode::TypeEngst9 => Some(9),
2489 _ => None,
2490 }
2491}
2492
2493fn apply_comparison(
2494 script: &mut VmScript,
2495 decoded: &VmProgramInstruction,
2496 vm: &Vm,
2497) -> Result<(), VmError> {
2498 if decoded.instruction.auxcode == NcsAuxCode::TypeTypeVectorVector {
2499 let rhs = script.pop_vector()?;
2500 let lhs = script.pop_vector()?;
2501 let result = match decoded.instruction.opcode {
2502 NcsOpcode::Equal => lhs == rhs,
2503 NcsOpcode::NotEqual => lhs != rhs,
2504 _ => return unsupported(decoded, "ordered comparisons are not valid for vectors"),
2505 };
2506 script.push_int(bool_to_int(result));
2507 return Ok(());
2508 }
2509
2510 if decoded.instruction.auxcode == NcsAuxCode::TypeTypeStructStruct {
2511 let size_bytes = usize::from(read_u16(decoded, 0)?);
2512 let cell_count = size_bytes / 4;
2513 let rhs_start =
2514 script
2515 .sp
2516 .checked_sub(cell_count)
2517 .ok_or_else(|| VmError::StackUnderflow {
2518 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("missing {0} cells for rhs struct comparison",
cell_count))
})format!("missing {} cells for rhs struct comparison", cell_count),
2519 })?;
2520 let lhs_start =
2521 rhs_start
2522 .checked_sub(cell_count)
2523 .ok_or_else(|| VmError::StackUnderflow {
2524 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("missing {0} cells for lhs struct comparison",
cell_count))
})format!("missing {} cells for lhs struct comparison", cell_count),
2525 })?;
2526 let equal =
2527 script.stack.get(lhs_start..rhs_start) == script.stack.get(rhs_start..script.sp);
2528 script.set_stack_pointer(lhs_start)?;
2529 let result = match decoded.instruction.opcode {
2530 NcsOpcode::Equal => equal,
2531 NcsOpcode::NotEqual => !equal,
2532 _ => return unsupported(decoded, "ordered comparisons are not valid for structs"),
2533 };
2534 script.push_int(bool_to_int(result));
2535 return Ok(());
2536 }
2537
2538 let rhs = script.pop()?;
2539 let lhs = script.pop()?;
2540 let result = match (&lhs, &rhs) {
2541 (VmValue::Int(lhs), VmValue::Int(rhs)) => {
2542 compare_ordered(decoded.instruction.opcode, lhs, rhs)
2543 }
2544 (VmValue::Int(lhs), VmValue::Float(rhs)) => {
2545 compare_ordered(decoded.instruction.opcode, &(*lhs as f32), rhs)
2546 }
2547 (VmValue::Float(lhs), VmValue::Int(rhs)) => {
2548 compare_ordered(decoded.instruction.opcode, lhs, &(*rhs as f32))
2549 }
2550 (VmValue::Float(lhs), VmValue::Float(rhs)) => {
2551 compare_ordered(decoded.instruction.opcode, lhs, rhs)
2552 }
2553 (VmValue::String(lhs), VmValue::String(rhs)) => {
2554 compare_ordered(decoded.instruction.opcode, lhs, rhs)
2555 }
2556 (VmValue::Object(lhs), VmValue::Object(rhs)) => compare_equality(decoded, lhs, rhs)?,
2557 (
2558 VmValue::EngineStructure {
2559 index: lhs_index,
2560 value: lhs_value,
2561 },
2562 VmValue::EngineStructure {
2563 index: rhs_index,
2564 value: rhs_value,
2565 },
2566 ) if lhs_index == rhs_index => {
2567 compare_engine_structure_equality(decoded, vm, *lhs_index, lhs_value, rhs_value)?
2568 }
2569 _ => {
2570 return unsupported(
2571 decoded,
2572 &::alloc::__export::must_use({
::alloc::fmt::format(format_args!("comparison between {0} and {1} is not implemented",
lhs.kind_name(), rhs.kind_name()))
})format!(
2573 "comparison between {} and {} is not implemented",
2574 lhs.kind_name(),
2575 rhs.kind_name()
2576 ),
2577 );
2578 }
2579 };
2580 script.push_int(bool_to_int(result));
2581 Ok(())
2582}
2583
2584fn apply_add(script: &mut VmScript, decoded: &VmProgramInstruction) -> Result<(), VmError> {
2585 if decoded.instruction.auxcode == NcsAuxCode::TypeTypeVectorVector {
2586 let rhs = script.pop_vector()?;
2587 let lhs = script.pop_vector()?;
2588 script.push_vector([lhs[0] + rhs[0], lhs[1] + rhs[1], lhs[2] + rhs[2]]);
2589 return Ok(());
2590 }
2591 let rhs = script.pop()?;
2592 let lhs = script.pop()?;
2593 match (lhs, rhs) {
2594 (VmValue::Int(lhs), VmValue::Int(rhs)) => script.push_int(lhs.wrapping_add(rhs)),
2595 (VmValue::Int(lhs), VmValue::Float(rhs)) => script.push_float(lhs as f32 + rhs),
2596 (VmValue::Float(lhs), VmValue::Int(rhs)) => script.push_float(lhs + rhs as f32),
2597 (VmValue::Float(lhs), VmValue::Float(rhs)) => script.push_float(lhs + rhs),
2598 (VmValue::String(lhs), VmValue::String(rhs)) => {
2599 script.push_string(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}{1}", lhs, rhs))
})format!("{lhs}{rhs}"));
2600 }
2601 _ => {
2602 return unsupported(
2603 decoded,
2604 "ADD currently supports int, float, string, and vector",
2605 );
2606 }
2607 }
2608 Ok(())
2609}
2610
2611fn apply_sub(script: &mut VmScript, decoded: &VmProgramInstruction) -> Result<(), VmError> {
2612 if decoded.instruction.auxcode == NcsAuxCode::TypeTypeVectorVector {
2613 let rhs = script.pop_vector()?;
2614 let lhs = script.pop_vector()?;
2615 script.push_vector([lhs[0] - rhs[0], lhs[1] - rhs[1], lhs[2] - rhs[2]]);
2616 return Ok(());
2617 }
2618 let rhs = script.pop()?;
2619 let lhs = script.pop()?;
2620 match (lhs, rhs) {
2621 (VmValue::Int(lhs), VmValue::Int(rhs)) => script.push_int(lhs.wrapping_sub(rhs)),
2622 (VmValue::Int(lhs), VmValue::Float(rhs)) => script.push_float(lhs as f32 - rhs),
2623 (VmValue::Float(lhs), VmValue::Int(rhs)) => script.push_float(lhs - rhs as f32),
2624 (VmValue::Float(lhs), VmValue::Float(rhs)) => script.push_float(lhs - rhs),
2625 _ => return unsupported(decoded, "SUB currently supports int, float, and vector"),
2626 }
2627 Ok(())
2628}
2629
2630fn apply_mul(script: &mut VmScript, decoded: &VmProgramInstruction) -> Result<(), VmError> {
2631 match decoded.instruction.auxcode {
2632 NcsAuxCode::TypeTypeVectorFloat => {
2633 let rhs = script.pop_float()?;
2634 let lhs = script.pop_vector()?;
2635 script.push_vector([lhs[0] * rhs, lhs[1] * rhs, lhs[2] * rhs]);
2636 return Ok(());
2637 }
2638 NcsAuxCode::TypeTypeFloatVector => {
2639 let rhs = script.pop_vector()?;
2640 let lhs = script.pop_float()?;
2641 script.push_vector([lhs * rhs[0], lhs * rhs[1], lhs * rhs[2]]);
2642 return Ok(());
2643 }
2644 _ => {}
2645 }
2646 let rhs = script.pop()?;
2647 let lhs = script.pop()?;
2648 match (lhs, rhs) {
2649 (VmValue::Int(lhs), VmValue::Int(rhs)) => script.push_int(lhs.wrapping_mul(rhs)),
2650 (VmValue::Int(lhs), VmValue::Float(rhs)) => script.push_float(lhs as f32 * rhs),
2651 (VmValue::Float(lhs), VmValue::Int(rhs)) => script.push_float(lhs * rhs as f32),
2652 (VmValue::Float(lhs), VmValue::Float(rhs)) => script.push_float(lhs * rhs),
2653 _ => return unsupported(decoded, "MUL currently supports int, float, and vector"),
2654 }
2655 Ok(())
2656}
2657
2658fn apply_div(script: &mut VmScript, decoded: &VmProgramInstruction) -> Result<(), VmError> {
2659 if decoded.instruction.auxcode == NcsAuxCode::TypeTypeVectorFloat {
2660 let rhs = script.pop_float()?;
2661 if rhs == 0.0 {
2662 return unsupported(decoded, "division by zero");
2663 }
2664 let lhs = script.pop_vector()?;
2665 script.push_vector([lhs[0] / rhs, lhs[1] / rhs, lhs[2] / rhs]);
2666 return Ok(());
2667 }
2668 let rhs = script.pop()?;
2669 let lhs = script.pop()?;
2670 match (lhs, rhs) {
2671 (VmValue::Int(lhs), VmValue::Int(rhs)) => {
2672 if rhs == 0 {
2673 return unsupported(decoded, "division by zero");
2674 }
2675 script.push_int(lhs / rhs);
2676 }
2677 (VmValue::Int(lhs), VmValue::Float(rhs)) => {
2678 if rhs == 0.0 {
2679 return unsupported(decoded, "division by zero");
2680 }
2681 script.push_float(lhs as f32 / rhs);
2682 }
2683 (VmValue::Float(lhs), VmValue::Int(rhs)) => {
2684 if rhs == 0 {
2685 return unsupported(decoded, "division by zero");
2686 }
2687 script.push_float(lhs / rhs as f32);
2688 }
2689 (VmValue::Float(lhs), VmValue::Float(rhs)) => {
2690 if rhs == 0.0 {
2691 return unsupported(decoded, "division by zero");
2692 }
2693 script.push_float(lhs / rhs);
2694 }
2695 _ => return unsupported(decoded, "DIV currently supports int, float, and vector"),
2696 }
2697 Ok(())
2698}
2699
2700fn compare_engine_structure_equality(
2701 decoded: &VmProgramInstruction,
2702 vm: &Vm,
2703 index: u8,
2704 lhs: &VmEngineStructureValue,
2705 rhs: &VmEngineStructureValue,
2706) -> Result<bool, VmError> {
2707 let equal = vm.compare_engine_structure_values(index, lhs, rhs);
2708 match decoded.instruction.opcode {
2709 NcsOpcode::Equal => Ok(equal),
2710 NcsOpcode::NotEqual => Ok(!equal),
2711 _ => unsupported(
2712 decoded,
2713 "ordered comparison is not valid for engine structures",
2714 ),
2715 }
2716}
2717
2718fn compare_equality<T: PartialEq>(
2719 decoded: &VmProgramInstruction,
2720 lhs: &T,
2721 rhs: &T,
2722) -> Result<bool, VmError> {
2723 match decoded.instruction.opcode {
2724 NcsOpcode::Equal => Ok(lhs == rhs),
2725 NcsOpcode::NotEqual => Ok(lhs != rhs),
2726 _ => unsupported(
2727 decoded,
2728 "ordered comparison is not valid for this runtime type",
2729 ),
2730 }
2731}
2732
2733fn compare_ordered<T: PartialEq + PartialOrd>(opcode: NcsOpcode, lhs: &T, rhs: &T) -> bool {
2734 match opcode {
2735 NcsOpcode::Equal => lhs == rhs,
2736 NcsOpcode::NotEqual => lhs != rhs,
2737 NcsOpcode::Lt => lhs < rhs,
2738 NcsOpcode::Gt => lhs > rhs,
2739 NcsOpcode::Leq => lhs <= rhs,
2740 NcsOpcode::Geq => lhs >= rhs,
2741 _ => false,
2742 }
2743}
2744
2745fn bool_to_int(value: bool) -> i32 {
2746 if value { 1 } else { 0 }
2747}
2748
2749fn invalid_extra(decoded: &VmProgramInstruction, message: &str) -> VmError {
2750 VmError::InvalidExtra {
2751 offset: decoded.offset,
2752 opcode: decoded.instruction.opcode,
2753 auxcode: decoded.instruction.auxcode,
2754 message: message.to_string(),
2755 }
2756}
2757
2758fn unsupported<T>(decoded: &VmProgramInstruction, message: &str) -> Result<T, VmError> {
2759 Err(VmError::Unsupported {
2760 offset: decoded.offset,
2761 opcode: decoded.instruction.opcode,
2762 auxcode: decoded.instruction.auxcode,
2763 message: message.to_string(),
2764 })
2765}
2766
2767#[cfg(test)]
2768mod tests {
2769 use std::{cell::RefCell, collections::HashMap, io::Cursor, rc::Rc};
2770
2771 use super::{Vm, VmEngineStructureValue, VmRunOptions, VmScript, VmStepOutcome, VmValue};
2772 use crate::{
2773 CompileOptions, InMemoryScriptResolver, NcsAuxCode, NcsInstruction, NcsOpcode, SourceId,
2774 SourceMap, compile_script, compile_script_with_source_map, load_source_bundle,
2775 parse_langspec, parse_text, read_ndb,
2776 };
2777
2778 fn compile_debug_script(
2779 file_name: &str,
2780 source: &[u8],
2781 ) -> Result<(Vec<u8>, crate::Ndb), Box<dyn std::error::Error>> {
2782 let mut source_map = SourceMap::new();
2783 let root_id = source_map.add_file(file_name.to_string(), source.to_vec());
2784 let script = parse_text(root_id, std::str::from_utf8(source)?, None)?;
2785 let artifacts = compile_script_with_source_map(
2786 &script,
2787 &source_map,
2788 root_id,
2789 None,
2790 CompileOptions::default(),
2791 )?;
2792 let ndb_bytes = artifacts
2793 .ndb
2794 .ok_or("compile_script_with_source_map did not emit NDB bytes")?;
2795 let mut reader = Cursor::new(ndb_bytes);
2796 let ndb = read_ndb(&mut reader)?;
2797 Ok((artifacts.ncs, ndb))
2798 }
2799
2800 #[test]
2801 fn runs_manual_integer_program() -> Result<(), Box<dyn std::error::Error>> {
2802 let instructions = vec![
2803 NcsInstruction {
2804 opcode: NcsOpcode::Constant,
2805 auxcode: NcsAuxCode::TypeInteger,
2806 extra: 7_i32.to_be_bytes().to_vec(),
2807 },
2808 NcsInstruction {
2809 opcode: NcsOpcode::Constant,
2810 auxcode: NcsAuxCode::TypeInteger,
2811 extra: 35_i32.to_be_bytes().to_vec(),
2812 },
2813 NcsInstruction {
2814 opcode: NcsOpcode::Add,
2815 auxcode: NcsAuxCode::TypeTypeIntegerInteger,
2816 extra: Vec::new(),
2817 },
2818 NcsInstruction {
2819 opcode: NcsOpcode::Ret,
2820 auxcode: NcsAuxCode::None,
2821 extra: Vec::new(),
2822 },
2823 ];
2824 let mut script = VmScript::from_instructions(instructions, "arith");
2825
2826 script.run(&Vm::new())?;
2827
2828 assert_eq!(script.stack(), &[VmValue::Int(42)]);
2829 Ok(())
2830 }
2831
2832 #[test]
2833 fn invokes_registered_action_command() -> Result<(), Box<dyn std::error::Error>> {
2834 let langspec = parse_langspec("nwscript", "void PrintInteger(int n);")?;
2835 let script = parse_text(
2836 SourceId::new(0),
2837 "void main() { PrintInteger(42); }",
2838 Some(&langspec),
2839 )?;
2840 let artifacts = compile_script(&script, Some(&langspec), CompileOptions::default())?;
2841
2842 let called = Rc::new(RefCell::new(None));
2843 let mut vm = Vm::new();
2844 {
2845 let called = Rc::clone(&called);
2846 vm.define_command(0, move |script, _command, argc| {
2847 assert_eq!(argc, 1);
2848 *called.borrow_mut() = Some(script.pop_int()?);
2849 Ok(())
2850 });
2851 }
2852
2853 let mut runtime = VmScript::from_bytes(&artifacts.ncs, "compiled-main")?;
2854 runtime.run(&vm)?;
2855
2856 assert_eq!(*called.borrow(), Some(42));
2857 Ok(())
2858 }
2859
2860 #[test]
2861 fn loads_and_runs_script_through_source_bundle_pipeline()
2862 -> Result<(), Box<dyn std::error::Error>> {
2863 let langspec = parse_langspec("nwscript", "void PrintInteger(int n);")?;
2864 let mut resolver = InMemoryScriptResolver::new();
2865 resolver.insert_source("main", "void main() { PrintInteger(7); }");
2866 let bundle = load_source_bundle(&resolver, "main", crate::SourceLoadOptions::default())?;
2867 let artifacts =
2868 crate::compile_source_bundle(&bundle, Some(&langspec), CompileOptions::default())?;
2869
2870 let seen = Rc::new(RefCell::new(Vec::new()));
2871 let mut vm = Vm::new();
2872 {
2873 let seen = Rc::clone(&seen);
2874 vm.define_simple_command(0, move |script| {
2875 seen.borrow_mut().push(script.pop_int()?);
2876 Ok(())
2877 });
2878 }
2879
2880 let mut runtime = VmScript::from_bytes(&artifacts.ncs, "bundle-main")?;
2881 runtime.run(&vm)?;
2882
2883 assert_eq!(&*seen.borrow(), &[7]);
2884 Ok(())
2885 }
2886
2887 #[test]
2888 fn executes_compiled_vector_arithmetic() -> Result<(), Box<dyn std::error::Error>> {
2889 let langspec = parse_langspec("nwscript", "void PrintVector(vector v);")?;
2890 let script = parse_text(
2891 SourceId::new(0),
2892 "void main() { PrintVector(([1.0, 2.0, 3.0] + [4.0, 5.0, 6.0]) * 2.0); }",
2893 Some(&langspec),
2894 )?;
2895 let artifacts = compile_script(&script, Some(&langspec), CompileOptions::default())?;
2896
2897 let seen = Rc::new(RefCell::new(Vec::new()));
2898 let mut vm = Vm::new();
2899 {
2900 let seen = Rc::clone(&seen);
2901 vm.define_simple_command(0, move |script| {
2902 seen.borrow_mut().push(script.pop_vector()?);
2903 Ok(())
2904 });
2905 }
2906
2907 let mut runtime = VmScript::from_bytes(&artifacts.ncs, "vector-main")?;
2908 runtime.run(&vm)?;
2909
2910 assert_eq!(&*seen.borrow(), &[[10.0, 14.0, 18.0]]);
2911 Ok(())
2912 }
2913
2914 #[test]
2915 fn executes_compiled_mixed_numeric_arithmetic() -> Result<(), Box<dyn std::error::Error>> {
2916 let langspec = parse_langspec("nwscript", "void PrintFloat(float f);")?;
2917 let script = parse_text(
2918 SourceId::new(0),
2919 "void main() { PrintFloat(1 + 2.5); PrintFloat(5.5 - 2); PrintFloat(3 * 1.5); \
2920 PrintFloat(9.0 / 2); }",
2921 Some(&langspec),
2922 )?;
2923 let artifacts = compile_script(&script, Some(&langspec), CompileOptions::default())?;
2924
2925 let seen = Rc::new(RefCell::new(Vec::new()));
2926 let mut vm = Vm::new();
2927 {
2928 let seen = Rc::clone(&seen);
2929 vm.define_simple_command(0, move |script| {
2930 seen.borrow_mut().push(script.pop_float()?);
2931 Ok(())
2932 });
2933 }
2934
2935 let mut runtime = VmScript::from_bytes(&artifacts.ncs, "mixed-numeric-main")?;
2936 runtime.run(&vm)?;
2937
2938 assert_eq!(&*seen.borrow(), &[3.5, 3.5, 4.5, 4.5]);
2939 Ok(())
2940 }
2941
2942 #[test]
2943 fn executes_compiled_mixed_numeric_comparisons() -> Result<(), Box<dyn std::error::Error>> {
2944 let instructions = vec![
2945 NcsInstruction {
2946 opcode: NcsOpcode::Constant,
2947 auxcode: NcsAuxCode::TypeInteger,
2948 extra: 1_i32.to_be_bytes().to_vec(),
2949 },
2950 NcsInstruction {
2951 opcode: NcsOpcode::Constant,
2952 auxcode: NcsAuxCode::TypeFloat,
2953 extra: 2.5_f32.to_bits().to_be_bytes().to_vec(),
2954 },
2955 NcsInstruction {
2956 opcode: NcsOpcode::Lt,
2957 auxcode: NcsAuxCode::TypeTypeIntegerFloat,
2958 extra: Vec::new(),
2959 },
2960 NcsInstruction {
2961 opcode: NcsOpcode::Constant,
2962 auxcode: NcsAuxCode::TypeFloat,
2963 extra: 2.5_f32.to_bits().to_be_bytes().to_vec(),
2964 },
2965 NcsInstruction {
2966 opcode: NcsOpcode::Constant,
2967 auxcode: NcsAuxCode::TypeInteger,
2968 extra: 1_i32.to_be_bytes().to_vec(),
2969 },
2970 NcsInstruction {
2971 opcode: NcsOpcode::Gt,
2972 auxcode: NcsAuxCode::TypeTypeFloatInteger,
2973 extra: Vec::new(),
2974 },
2975 NcsInstruction {
2976 opcode: NcsOpcode::Constant,
2977 auxcode: NcsAuxCode::TypeInteger,
2978 extra: 3_i32.to_be_bytes().to_vec(),
2979 },
2980 NcsInstruction {
2981 opcode: NcsOpcode::Constant,
2982 auxcode: NcsAuxCode::TypeFloat,
2983 extra: 3.0_f32.to_bits().to_be_bytes().to_vec(),
2984 },
2985 NcsInstruction {
2986 opcode: NcsOpcode::Equal,
2987 auxcode: NcsAuxCode::TypeTypeIntegerFloat,
2988 extra: Vec::new(),
2989 },
2990 NcsInstruction {
2991 opcode: NcsOpcode::Constant,
2992 auxcode: NcsAuxCode::TypeFloat,
2993 extra: 4.5_f32.to_bits().to_be_bytes().to_vec(),
2994 },
2995 NcsInstruction {
2996 opcode: NcsOpcode::Constant,
2997 auxcode: NcsAuxCode::TypeInteger,
2998 extra: 4_i32.to_be_bytes().to_vec(),
2999 },
3000 NcsInstruction {
3001 opcode: NcsOpcode::NotEqual,
3002 auxcode: NcsAuxCode::TypeTypeFloatInteger,
3003 extra: Vec::new(),
3004 },
3005 NcsInstruction {
3006 opcode: NcsOpcode::Ret,
3007 auxcode: NcsAuxCode::None,
3008 extra: Vec::new(),
3009 },
3010 ];
3011 let mut runtime = VmScript::from_instructions(instructions, "mixed-compare-manual");
3012 runtime.run(&Vm::new())?;
3013
3014 assert_eq!(
3015 runtime.stack(),
3016 &[
3017 VmValue::Int(1),
3018 VmValue::Int(1),
3019 VmValue::Int(1),
3020 VmValue::Int(1),
3021 ]
3022 );
3023 Ok(())
3024 }
3025
3026 #[test]
3027 fn grows_stack_with_positive_movsp() -> Result<(), Box<dyn std::error::Error>> {
3028 let instructions = vec![
3029 NcsInstruction {
3030 opcode: NcsOpcode::ModifyStackPointer,
3031 auxcode: NcsAuxCode::None,
3032 extra: 4_i32.to_be_bytes().to_vec(),
3033 },
3034 NcsInstruction {
3035 opcode: NcsOpcode::Ret,
3036 auxcode: NcsAuxCode::None,
3037 extra: Vec::new(),
3038 },
3039 ];
3040 let mut script = VmScript::from_instructions(instructions, "movsp-grow");
3041
3042 script.run(&Vm::new())?;
3043
3044 assert_eq!(script.stack(), &[VmValue::Int(0)]);
3045 Ok(())
3046 }
3047
3048 #[test]
3049 fn steps_manual_program_instruction_by_instruction() -> Result<(), Box<dyn std::error::Error>> {
3050 let instructions = vec![
3051 NcsInstruction {
3052 opcode: NcsOpcode::Constant,
3053 auxcode: NcsAuxCode::TypeInteger,
3054 extra: 7_i32.to_be_bytes().to_vec(),
3055 },
3056 NcsInstruction {
3057 opcode: NcsOpcode::Constant,
3058 auxcode: NcsAuxCode::TypeInteger,
3059 extra: 35_i32.to_be_bytes().to_vec(),
3060 },
3061 NcsInstruction {
3062 opcode: NcsOpcode::Add,
3063 auxcode: NcsAuxCode::TypeTypeIntegerInteger,
3064 extra: Vec::new(),
3065 },
3066 NcsInstruction {
3067 opcode: NcsOpcode::Ret,
3068 auxcode: NcsAuxCode::None,
3069 extra: Vec::new(),
3070 },
3071 ];
3072 let mut script = VmScript::from_instructions(instructions, "step-arith");
3073 let vm = Vm::new();
3074
3075 assert_eq!(
3076 script
3077 .current_instruction()
3078 .map(|instruction| instruction.opcode),
3079 Some(NcsOpcode::Constant)
3080 );
3081 assert_eq!(vm.step(&mut script)?, VmStepOutcome::Running);
3082 assert_eq!(script.stack(), &[VmValue::Int(7)]);
3083
3084 assert_eq!(vm.step(&mut script)?, VmStepOutcome::Running);
3085 assert_eq!(script.stack(), &[VmValue::Int(7), VmValue::Int(35)]);
3086
3087 assert_eq!(vm.step(&mut script)?, VmStepOutcome::Running);
3088 assert_eq!(script.stack(), &[VmValue::Int(42)]);
3089
3090 assert_eq!(vm.step(&mut script)?, VmStepOutcome::Halted);
3091 assert_eq!(script.stack(), &[VmValue::Int(42)]);
3092 Ok(())
3093 }
3094
3095 #[test]
3096 fn steps_over_and_out_of_compiled_user_calls() -> Result<(), Box<dyn std::error::Error>> {
3097 let source = br#"int AddOne(int x) {
3098 return x + 1;
3099}
3100
3101int Twice(int x) {
3102 int first = AddOne(x);
3103 int second = AddOne(x);
3104 return first + second;
3105}
3106"#;
3107 let (ncs, ndb) = compile_debug_script("debug_calls.nss", source)?;
3108 let vm = Vm::new();
3109 let mut script = VmScript::from_bytes_with_ndb(&ncs, "debug-calls", &ndb)?;
3110 script.prepare_function_call(&ndb, "Twice", &[VmValue::Int(2)])?;
3111
3112 for _ in 0..32 {
3113 if script
3114 .current_instruction()
3115 .map(|instruction| instruction.opcode)
3116 == Some(NcsOpcode::Jsr)
3117 {
3118 break;
3119 }
3120 assert_eq!(vm.step(&mut script)?, VmStepOutcome::Running);
3121 }
3122
3123 let before = script.ip();
3124 assert_eq!(
3125 script.current_function().map(|function| function.name),
3126 Some("Twice".to_string())
3127 );
3128 assert_eq!(
3129 vm.step_over(&mut script, VmRunOptions::default())?,
3130 VmStepOutcome::Running
3131 );
3132 assert!(script.ip() > before);
3133 assert_eq!(
3134 script.current_function().map(|function| function.name),
3135 Some("Twice".to_string())
3136 );
3137
3138 assert_eq!(
3139 vm.run_until_function(&mut script, "AddOne", VmRunOptions::default())?,
3140 VmStepOutcome::Running
3141 );
3142 assert_eq!(
3143 script.current_function().map(|function| function.name),
3144 Some("AddOne".to_string())
3145 );
3146
3147 assert_eq!(
3148 vm.step_out(&mut script, VmRunOptions::default())?,
3149 VmStepOutcome::Running
3150 );
3151 assert_eq!(
3152 script.current_function().map(|function| function.name),
3153 Some("Twice".to_string())
3154 );
3155 Ok(())
3156 }
3157
3158 #[test]
3159 fn exposes_source_locations_and_runs_until_lines() -> Result<(), Box<dyn std::error::Error>> {
3160 let source = br#"int AddOne(int x) {
3161 return x + 1;
3162}
3163
3164int Twice(int x) {
3165 int first = AddOne(x);
3166 int second = AddOne(x);
3167 return first + second;
3168}
3169"#;
3170 let (ncs, ndb) = compile_debug_script("debug_lines.nss", source)?;
3171 let vm = Vm::new();
3172 let mut script = VmScript::from_bytes_with_ndb(&ncs, "debug-lines", &ndb)?;
3173 script.prepare_function_call(&ndb, "Twice", &[VmValue::Int(2)])?;
3174 let root_file_index = ndb
3175 .files
3176 .iter()
3177 .position(|file| file.name == "debug_lines.nss")
3178 .ok_or("missing root file entry")?;
3179 let mut root_lines = ndb
3180 .lines
3181 .iter()
3182 .filter(|line| line.file_num == root_file_index)
3183 .map(|line| line.line_num)
3184 .collect::<Vec<_>>();
3185 root_lines.sort_unstable();
3186 root_lines.dedup();
3187 let first_line = *root_lines.first().ok_or("missing root source lines")?;
3188 let second_line = *root_lines
3189 .iter()
3190 .find(|line| **line > first_line)
3191 .ok_or("missing second root source line")?;
3192
3193 assert_eq!(
3194 vm.run_until_line(
3195 &mut script,
3196 "debug_lines.nss",
3197 first_line,
3198 VmRunOptions::default()
3199 )?,
3200 VmStepOutcome::Running
3201 );
3202 let location = script
3203 .current_source_location()
3204 .ok_or("missing first source location")?;
3205 assert_eq!(location.file_name, "debug_lines.nss");
3206 assert!(location.is_root);
3207 assert_eq!(location.line_number, first_line);
3208
3209 assert_eq!(
3210 vm.run_until_line(
3211 &mut script,
3212 "debug_lines.nss",
3213 second_line,
3214 VmRunOptions::default()
3215 )?,
3216 VmStepOutcome::Running
3217 );
3218 let location = script
3219 .current_source_location()
3220 .ok_or("missing second source location")?;
3221 assert_eq!(location.line_number, second_line);
3222 assert_eq!(location.file_name, "debug_lines.nss");
3223 Ok(())
3224 }
3225
3226 #[test]
3227 fn executes_compiled_struct_equality() -> Result<(), Box<dyn std::error::Error>> {
3228 let langspec = parse_langspec("nwscript", "void PrintInteger(int n);")?;
3229 let source = r#"
3230 struct Pair {
3231 int a;
3232 int b;
3233 };
3234
3235 void main() {
3236 struct Pair left;
3237 struct Pair right;
3238 left.a = 1;
3239 left.b = 2;
3240 right.a = 1;
3241 right.b = 2;
3242 PrintInteger(left == right);
3243 }
3244 "#;
3245 let script = parse_text(SourceId::new(0), source, Some(&langspec))?;
3246 let artifacts = compile_script(&script, Some(&langspec), CompileOptions::default())?;
3247
3248 let seen = Rc::new(RefCell::new(Vec::new()));
3249 let mut vm = Vm::new();
3250 {
3251 let seen = Rc::clone(&seen);
3252 vm.define_simple_command(0, move |script| {
3253 seen.borrow_mut().push(script.pop_int()?);
3254 Ok(())
3255 });
3256 }
3257
3258 let mut runtime = VmScript::from_bytes(&artifacts.ncs, "struct-main")?;
3259 runtime.run(&vm)?;
3260
3261 assert_eq!(&*seen.borrow(), &[1]);
3262 Ok(())
3263 }
3264
3265 #[test]
3266 fn executes_compiled_user_function_calls() -> Result<(), Box<dyn std::error::Error>> {
3267 let source = br#"
3268 int AddOne(int nValue) {
3269 return nValue + 1;
3270 }
3271
3272 void main() {
3273 PrintInteger(AddOne(41));
3274 }
3275 "#;
3276 let mut source_map = SourceMap::new();
3277 let root_id = source_map.add_file("user_call_main.nss".to_string(), source.to_vec());
3278 let langspec = parse_langspec("nwscript", "void PrintInteger(int n);")?;
3279 let script = parse_text(root_id, std::str::from_utf8(source)?, Some(&langspec))?;
3280 let artifacts = compile_script_with_source_map(
3281 &script,
3282 &source_map,
3283 root_id,
3284 Some(&langspec),
3285 CompileOptions::default(),
3286 )?;
3287 let ndb = read_ndb(&mut std::io::Cursor::new(
3288 artifacts.ndb.clone().ok_or("missing ndb")?,
3289 ))?;
3290
3291 let seen = Rc::new(RefCell::new(Vec::new()));
3292 let mut vm = Vm::new();
3293 {
3294 let seen = Rc::clone(&seen);
3295 vm.define_simple_command(0, move |script| {
3296 seen.borrow_mut().push(script.pop_int()?);
3297 Ok(())
3298 });
3299 }
3300
3301 vm.run_bytes_with_ndb(&artifacts.ncs, "user-call-main", &ndb)?;
3302
3303 assert_eq!(&*seen.borrow(), &[42]);
3304 Ok(())
3305 }
3306
3307 #[test]
3308 fn captures_and_executes_saved_action_situations() -> Result<(), Box<dyn std::error::Error>> {
3309 let langspec = parse_langspec(
3310 "nwscript",
3311 "void DelayCommand(float seconds, action aAction);\nvoid PrintInteger(int n);",
3312 )?;
3313 let script = parse_text(
3314 SourceId::new(0),
3315 "void main() { int value = 42; DelayCommand(1.0, PrintInteger(value + 1)); }",
3316 Some(&langspec),
3317 )?;
3318 let artifacts = compile_script(&script, Some(&langspec), CompileOptions::default())?;
3319
3320 let delayed = Rc::new(RefCell::new(None));
3321 let printed = Rc::new(RefCell::new(None));
3322 let mut vm = Vm::new();
3323 {
3324 let delayed = Rc::clone(&delayed);
3325 vm.define_command(0, move |script, _command, argc| {
3326 assert_eq!(argc, 2);
3327 let seconds = script.pop_float()?;
3328 assert_eq!(seconds, 1.0);
3329 *delayed.borrow_mut() = script.saved_situation().cloned();
3330 Ok(())
3331 });
3332 }
3333 {
3334 let printed = Rc::clone(&printed);
3335 vm.define_command(1, move |script, _command, argc| {
3336 assert_eq!(argc, 1);
3337 *printed.borrow_mut() = Some(script.pop_int()?);
3338 Ok(())
3339 });
3340 }
3341
3342 let mut runtime = VmScript::from_bytes(&artifacts.ncs, "compiled-delay")?;
3343 runtime.run(&vm)?;
3344
3345 let saved = delayed
3346 .borrow()
3347 .clone()
3348 .ok_or("missing saved action situation")?;
3349 vm.run_situation(&saved)?;
3350
3351 assert_eq!(*printed.borrow(), Some(43));
3352 Ok(())
3353 }
3354
3355 #[test]
3356 fn roundtrips_engine_structures_through_action_handlers()
3357 -> Result<(), Box<dyn std::error::Error>> {
3358 let langspec = parse_langspec(
3359 "nwscript",
3360 "effect EffectDamage(int nAmount);\nvoid PrintEffect(effect eValue);",
3361 )?;
3362 let script = parse_text(
3363 SourceId::new(0),
3364 "void main() { PrintEffect(EffectDamage(42)); }",
3365 Some(&langspec),
3366 )?;
3367 let artifacts = compile_script(&script, Some(&langspec), CompileOptions::default())?;
3368
3369 let seen = Rc::new(RefCell::new(Vec::new()));
3370 let mut vm = Vm::new();
3371 vm.define_command(0, move |script, _command, argc| {
3372 assert_eq!(argc, 1);
3373 let amount = script.pop_int()?;
3374 script.push_engine_structure(0, VmEngineStructureValue::Word(amount as u32));
3375 Ok(())
3376 });
3377 {
3378 let seen = Rc::clone(&seen);
3379 vm.define_command(1, move |script, _command, argc| {
3380 assert_eq!(argc, 1);
3381 let value = script.pop_engine_structure_index(0)?;
3382 let Some(value) = value.as_word() else {
3383 return Err(super::VmError::TypeMismatch {
3384 offset: script.ip(),
3385 message: "expected word-backed effect value".to_string(),
3386 expected: Some("engine structure"),
3387 actual: "engine structure",
3388 });
3389 };
3390 seen.borrow_mut().push(value);
3391 Ok(())
3392 });
3393 }
3394
3395 let mut runtime = VmScript::from_bytes(&artifacts.ncs, "effect-main")?;
3396 runtime.run(&vm)?;
3397
3398 assert_eq!(&*seen.borrow(), &[42]);
3399 Ok(())
3400 }
3401
3402 #[test]
3403 fn uses_host_defined_engine_structure_defaults_for_rsadd()
3404 -> Result<(), Box<dyn std::error::Error>> {
3405 let langspec = parse_langspec(
3406 "nwscript",
3407 "#define ENGINE_NUM_STRUCTURES 8\n#define ENGINE_STRUCTURE_7 json\nvoid \
3408 PrintJson(json jValue);",
3409 )?;
3410 let script = parse_text(
3411 SourceId::new(0),
3412 "void main() { json jValue; PrintJson(jValue); }",
3413 Some(&langspec),
3414 )?;
3415 let artifacts = compile_script(&script, Some(&langspec), CompileOptions::default())?;
3416
3417 let seen = Rc::new(RefCell::new(Vec::new()));
3418 let mut vm = Vm::new();
3419 vm.define_engine_structure_default(7, VmEngineStructureValue::Text("{\"ok\":true}".into()));
3420 {
3421 let seen = Rc::clone(&seen);
3422 vm.define_command(0, move |script, _command, argc| {
3423 assert_eq!(argc, 1);
3424 let value = script.pop_engine_structure_index(7)?;
3425 let Some(value) = value.as_text() else {
3426 return Err(super::VmError::TypeMismatch {
3427 offset: script.ip(),
3428 message: "expected text-backed json value".to_string(),
3429 expected: Some("engine structure"),
3430 actual: "engine structure",
3431 });
3432 };
3433 seen.borrow_mut().push(value.to_string());
3434 Ok(())
3435 });
3436 }
3437
3438 let mut runtime = VmScript::from_bytes(&artifacts.ncs, "json-main")?;
3439 runtime.run(&vm)?;
3440
3441 assert_eq!(&*seen.borrow(), &["{\"ok\":true}".to_string()]);
3442 Ok(())
3443 }
3444
3445 #[test]
3446 fn uses_host_defined_engine_structure_comparison() -> Result<(), Box<dyn std::error::Error>> {
3447 let langspec = parse_langspec(
3448 "nwscript",
3449 "effect EffectDamage(int nAmount);\nvoid PrintInteger(int n);",
3450 )?;
3451 let script = parse_text(
3452 SourceId::new(0),
3453 "void main() { PrintInteger(EffectDamage(5) == EffectDamage(5)); }",
3454 Some(&langspec),
3455 )?;
3456 let artifacts = compile_script(&script, Some(&langspec), CompileOptions::default())?;
3457
3458 let next_handle = Rc::new(RefCell::new(100_u32));
3459 let effect_values = Rc::new(RefCell::new(HashMap::<u32, i32>::new()));
3460 let seen = Rc::new(RefCell::new(Vec::new()));
3461 let mut vm = Vm::new();
3462 {
3463 let next_handle = Rc::clone(&next_handle);
3464 let effect_values = Rc::clone(&effect_values);
3465 vm.define_command(0, move |script, _command, argc| {
3466 assert_eq!(argc, 1);
3467 let amount = script.pop_int()?;
3468 let handle = *next_handle.borrow();
3469 *next_handle.borrow_mut() = handle + 1;
3470 effect_values.borrow_mut().insert(handle, amount);
3471 script.push_engine_structure(0, VmEngineStructureValue::Word(handle));
3472 Ok(())
3473 });
3474 }
3475 {
3476 let effect_values = Rc::clone(&effect_values);
3477 vm.define_engine_structure_comparer(0, move |_index, lhs, rhs| {
3478 let Some(lhs) = lhs.as_word() else {
3479 return false;
3480 };
3481 let Some(rhs) = rhs.as_word() else {
3482 return false;
3483 };
3484 effect_values.borrow().get(&lhs) == effect_values.borrow().get(&rhs)
3485 });
3486 }
3487 {
3488 let seen = Rc::clone(&seen);
3489 vm.define_command(1, move |script, _command, argc| {
3490 assert_eq!(argc, 1);
3491 seen.borrow_mut().push(script.pop_int()?);
3492 Ok(())
3493 });
3494 }
3495
3496 let mut runtime = VmScript::from_bytes(&artifacts.ncs, "effect-compare-main")?;
3497 runtime.run(&vm)?;
3498
3499 assert_eq!(&*seen.borrow(), &[1]);
3500 Ok(())
3501 }
3502
3503 #[test]
3504 fn aborts_script_after_action_handler_requests_abort() -> Result<(), Box<dyn std::error::Error>>
3505 {
3506 let langspec = parse_langspec("nwscript", "void StopNow();\nvoid PrintInteger(int n);")?;
3507 let script = parse_text(
3508 SourceId::new(0),
3509 "void main() { StopNow(); PrintInteger(42); }",
3510 Some(&langspec),
3511 )?;
3512 let artifacts = compile_script(&script, Some(&langspec), CompileOptions::default())?;
3513
3514 let seen = Rc::new(RefCell::new(Vec::new()));
3515 let mut vm = Vm::new();
3516 vm.define_command(0, move |script, _command, argc| {
3517 assert_eq!(argc, 0);
3518 script.abort();
3519 Ok(())
3520 });
3521 {
3522 let seen = Rc::clone(&seen);
3523 vm.define_command(1, move |script, _command, argc| {
3524 assert_eq!(argc, 1);
3525 seen.borrow_mut().push(script.pop_int()?);
3526 Ok(())
3527 });
3528 }
3529
3530 let mut runtime = VmScript::from_bytes(&artifacts.ncs, "abort-main")?;
3531 runtime.run(&vm)?;
3532
3533 assert!(runtime.aborted());
3534 assert!(seen.borrow().is_empty());
3535 Ok(())
3536 }
3537
3538 #[test]
3539 fn emits_instruction_trace_events_during_execution() -> Result<(), Box<dyn std::error::Error>> {
3540 let langspec = parse_langspec("nwscript", "void PrintInteger(int n);")?;
3541 let script = parse_text(
3542 SourceId::new(0),
3543 "void main() { PrintInteger(7); }",
3544 Some(&langspec),
3545 )?;
3546 let artifacts = compile_script(&script, Some(&langspec), CompileOptions::default())?;
3547
3548 let seen = Rc::new(RefCell::new(Vec::new()));
3549 let traces = Rc::new(RefCell::new(Vec::new()));
3550 let mut vm = Vm::new();
3551 {
3552 let traces = Rc::clone(&traces);
3553 vm.define_trace_hook(move |_script, event| {
3554 traces.borrow_mut().push((
3555 event.offset,
3556 event.instruction.opcode,
3557 event.instruction.auxcode,
3558 event.sp,
3559 event.bp,
3560 ));
3561 });
3562 }
3563 {
3564 let seen = Rc::clone(&seen);
3565 vm.define_command(0, move |script, _command, argc| {
3566 assert_eq!(argc, 1);
3567 seen.borrow_mut().push(script.pop_int()?);
3568 Ok(())
3569 });
3570 }
3571
3572 let mut runtime = VmScript::from_bytes(&artifacts.ncs, "trace-main")?;
3573 runtime.run(&vm)?;
3574
3575 assert_eq!(&*seen.borrow(), &[7]);
3576 let trace = traces.borrow();
3577 assert!(!trace.is_empty());
3578 assert_eq!(trace.first().map(|entry| entry.1), Some(NcsOpcode::Jsr));
3579 assert!(
3580 trace
3581 .iter()
3582 .any(|(_, opcode, auxcode, _, _)| *opcode == NcsOpcode::Constant
3583 && *auxcode == NcsAuxCode::TypeInteger)
3584 );
3585 assert!(
3586 trace
3587 .iter()
3588 .any(|(_, opcode, _, _, _)| *opcode == NcsOpcode::ExecuteCommand)
3589 );
3590 Ok(())
3591 }
3592
3593 #[test]
3594 fn rejects_runs_that_exceed_instruction_budget() -> Result<(), Box<dyn std::error::Error>> {
3595 let langspec = parse_langspec("nwscript", "void PrintInteger(int n);")?;
3596 let script = parse_text(
3597 SourceId::new(0),
3598 "void main() { while (1) {} }",
3599 Some(&langspec),
3600 )?;
3601 let artifacts = compile_script(&script, Some(&langspec), CompileOptions::default())?;
3602
3603 let vm = Vm::new();
3604 let error = vm
3605 .run_bytes_with_options(
3606 &artifacts.ncs,
3607 "budget-main",
3608 VmRunOptions {
3609 max_instructions: Some(16),
3610 },
3611 )
3612 .expect_err("infinite loop should exceed instruction budget");
3613
3614 assert!(matches!(
3615 error,
3616 super::VmError::InstructionLimitExceeded {
3617 limit: 16,
3618 ..
3619 }
3620 ));
3621 Ok(())
3622 }
3623
3624 #[test]
3625 fn runs_named_function_calls_from_ndb_without_globals() -> Result<(), Box<dyn std::error::Error>>
3626 {
3627 let source = br#"
3628 int AddOne(int nValue) {
3629 return nValue + 1;
3630 }
3631
3632 void main() {
3633 return;
3634 }
3635 "#;
3636 let mut source_map = SourceMap::new();
3637 let root_id = source_map.add_file("direct_call.nss".to_string(), source.to_vec());
3638 let langspec = parse_langspec("nwscript", "void Dummy();")?;
3639 let script = parse_text(root_id, std::str::from_utf8(source)?, Some(&langspec))?;
3640 let artifacts = compile_script_with_source_map(
3641 &script,
3642 &source_map,
3643 root_id,
3644 Some(&langspec),
3645 CompileOptions::default(),
3646 )?;
3647 let ndb = read_ndb(&mut std::io::Cursor::new(
3648 artifacts.ndb.clone().ok_or("missing ndb")?,
3649 ))?;
3650
3651 let vm = Vm::new();
3652 let runtime = vm.run_function_bytes(
3653 &artifacts.ncs,
3654 "direct-call",
3655 &ndb,
3656 "AddOne",
3657 &[VmValue::Int(41)],
3658 )?;
3659
3660 assert_eq!(
3661 runtime.function_return_value(&ndb, "AddOne")?,
3662 Some(VmValue::Int(42))
3663 );
3664 Ok(())
3665 }
3666
3667 #[test]
3668 fn rejects_direct_function_calls_for_scripts_with_globals()
3669 -> Result<(), Box<dyn std::error::Error>> {
3670 let source = br#"
3671 int GLOBAL = 1;
3672 int SECOND = GLOBAL + 2;
3673
3674 int ReadGlobal() {
3675 return GLOBAL + SECOND;
3676 }
3677
3678 void main() {
3679 return;
3680 }
3681 "#;
3682 let mut source_map = SourceMap::new();
3683 let root_id = source_map.add_file("globals_direct_call.nss".to_string(), source.to_vec());
3684 let langspec = parse_langspec("nwscript", "void Dummy();")?;
3685 let script = parse_text(root_id, std::str::from_utf8(source)?, Some(&langspec))?;
3686 let artifacts = compile_script_with_source_map(
3687 &script,
3688 &source_map,
3689 root_id,
3690 Some(&langspec),
3691 CompileOptions::default(),
3692 )?;
3693 let ndb = read_ndb(&mut std::io::Cursor::new(
3694 artifacts.ndb.clone().ok_or("missing ndb")?,
3695 ))?;
3696
3697 let vm = Vm::new();
3698 let runtime = vm.run_function_bytes(
3699 &artifacts.ncs,
3700 "globals-direct-call",
3701 &ndb,
3702 "ReadGlobal",
3703 &[],
3704 )?;
3705
3706 assert_eq!(
3707 runtime.function_return_value(&ndb, "ReadGlobal")?,
3708 Some(VmValue::Int(4))
3709 );
3710 Ok(())
3711 }
3712
3713 #[test]
3714 fn runs_direct_function_calls_for_globals_with_non_void_entry_loader()
3715 -> Result<(), Box<dyn std::error::Error>> {
3716 let source = br#"
3717 int GLOBAL = 1;
3718
3719 int ReadGlobal() {
3720 return GLOBAL;
3721 }
3722
3723 int StartingConditional() {
3724 return 0;
3725 }
3726 "#;
3727 let mut source_map = SourceMap::new();
3728 let root_id = source_map.add_file(
3729 "globals_loader_conditional.nss".to_string(),
3730 source.to_vec(),
3731 );
3732 let langspec = parse_langspec("nwscript", "void Dummy();")?;
3733 let script = parse_text(root_id, std::str::from_utf8(source)?, Some(&langspec))?;
3734 let artifacts = compile_script_with_source_map(
3735 &script,
3736 &source_map,
3737 root_id,
3738 Some(&langspec),
3739 CompileOptions::default(),
3740 )?;
3741 let ndb = read_ndb(&mut std::io::Cursor::new(
3742 artifacts.ndb.clone().ok_or("missing ndb")?,
3743 ))?;
3744 let vm = Vm::new();
3745 let runtime = vm.run_function_bytes(
3746 &artifacts.ncs,
3747 "globals-conditional",
3748 &ndb,
3749 "ReadGlobal",
3750 &[],
3751 )?;
3752 assert_eq!(
3753 runtime.function_return_value(&ndb, "ReadGlobal")?,
3754 Some(VmValue::Int(1))
3755 );
3756 Ok(())
3757 }
3758}