Begin value overhaul
This commit is contained in:
parent
3fcbde59e5
commit
a89b927a80
4
Cargo.lock
generated
4
Cargo.lock
generated
@ -349,6 +349,7 @@ dependencies = [
|
||||
"rand",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smallvec",
|
||||
"smartstring",
|
||||
"tracing",
|
||||
]
|
||||
@ -805,6 +806,9 @@ name = "smallvec"
|
||||
version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smartstring"
|
||||
|
@ -22,6 +22,7 @@ smartstring = { version = "1.0.1", features = [
|
||||
], default-features = false }
|
||||
tracing = "0.1.41"
|
||||
crossbeam-channel = "0.5.14"
|
||||
smallvec = { version = "1.13.2", features = ["serde", "const_generics"] }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.3.4", features = ["html_reports"] }
|
||||
|
@ -27,7 +27,7 @@ use std::sync::Arc;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{DustString, Function, FunctionType, Instruction, Span, Value};
|
||||
use crate::{DustString, Function, FunctionType, Instruction, Span};
|
||||
|
||||
/// Representation of a Dust program or function.
|
||||
///
|
||||
@ -39,8 +39,14 @@ pub struct Chunk {
|
||||
|
||||
pub(crate) instructions: Vec<Instruction>,
|
||||
pub(crate) positions: Vec<Span>,
|
||||
pub(crate) constants: Vec<Value>,
|
||||
|
||||
pub(crate) constant_characters: Vec<char>,
|
||||
pub(crate) constant_floats: Vec<f64>,
|
||||
pub(crate) constant_integers: Vec<i64>,
|
||||
pub(crate) constant_strings: Vec<DustString>,
|
||||
|
||||
pub(crate) locals: Vec<Local>,
|
||||
|
||||
pub(crate) prototypes: Vec<Arc<Chunk>>,
|
||||
|
||||
pub(crate) register_count: usize,
|
||||
@ -48,29 +54,6 @@ pub struct Chunk {
|
||||
}
|
||||
|
||||
impl Chunk {
|
||||
#[cfg(any(test, debug_assertions))]
|
||||
pub fn with_data(
|
||||
name: Option<DustString>,
|
||||
r#type: FunctionType,
|
||||
instructions: impl Into<Vec<Instruction>>,
|
||||
positions: impl Into<Vec<Span>>,
|
||||
constants: impl Into<Vec<Value>>,
|
||||
locals: impl Into<Vec<Local>>,
|
||||
prototypes: impl IntoIterator<Item = Chunk>,
|
||||
) -> Self {
|
||||
Self {
|
||||
name,
|
||||
r#type,
|
||||
instructions: instructions.into(),
|
||||
positions: positions.into(),
|
||||
constants: constants.into(),
|
||||
locals: locals.into(),
|
||||
prototypes: prototypes.into_iter().map(Arc::new).collect(),
|
||||
register_count: 0,
|
||||
prototype_index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_function(&self) -> Function {
|
||||
Function {
|
||||
name: self.name.clone(),
|
||||
@ -125,7 +108,11 @@ impl PartialEq for Chunk {
|
||||
self.name == other.name
|
||||
&& self.r#type == other.r#type
|
||||
&& self.instructions == other.instructions
|
||||
&& self.constants == other.constants
|
||||
&& self.positions == other.positions
|
||||
&& self.constant_characters == other.constant_characters
|
||||
&& self.constant_floats == other.constant_floats
|
||||
&& self.constant_integers == other.constant_integers
|
||||
&& self.constant_strings == other.constant_strings
|
||||
&& self.locals == other.locals
|
||||
&& self.prototypes == other.prototypes
|
||||
}
|
||||
|
@ -42,15 +42,13 @@ pub mod vm;
|
||||
pub use crate::chunk::{Chunk, Disassembler, Local, Scope};
|
||||
pub use crate::compiler::{CompileError, Compiler, compile};
|
||||
pub use crate::dust_error::{AnnotatedError, DustError};
|
||||
pub use crate::instruction::{Operand, Instruction, Operation};
|
||||
pub use crate::instruction::{Instruction, Operand, Operation};
|
||||
pub use crate::lexer::{LexError, Lexer, lex};
|
||||
pub use crate::native_function::{NativeFunction, NativeFunctionError};
|
||||
pub use crate::token::{Token, TokenKind, TokenOwned};
|
||||
pub use crate::r#type::{EnumType, FunctionType, StructType, Type, TypeConflict};
|
||||
pub use crate::value::{
|
||||
AbstractList, ConcreteValue, DustString, Function, RangeValue, Value, ValueError,
|
||||
};
|
||||
pub use crate::vm::{Pointer, Vm, run};
|
||||
pub use crate::value::{DustString, Function, RangeValue, Value, ValueError};
|
||||
pub use crate::vm::{Vm, run};
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
|
@ -1,238 +1,40 @@
|
||||
//! Runtime values used by the VM.
|
||||
mod abstract_list;
|
||||
mod concrete_value;
|
||||
mod function;
|
||||
mod range_value;
|
||||
|
||||
pub use abstract_list::AbstractList;
|
||||
pub use concrete_value::{ConcreteValue, DustString};
|
||||
pub use function::Function;
|
||||
pub use range_value::RangeValue;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smartstring::{LazyCompact, SmartString};
|
||||
|
||||
use std::fmt::{self, Debug, Display, Formatter};
|
||||
|
||||
use crate::{Type, vm::ThreadData};
|
||||
pub type DustString = SmartString<LazyCompact>;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
|
||||
pub enum Value {
|
||||
Concrete(ConcreteValue),
|
||||
|
||||
#[serde(skip)]
|
||||
AbstractList(AbstractList),
|
||||
Boolean(bool),
|
||||
Byte(u8),
|
||||
Character(char),
|
||||
Float(f64),
|
||||
Integer(i64),
|
||||
String(DustString),
|
||||
List(Vec<Value>),
|
||||
|
||||
#[serde(skip)]
|
||||
Function(Function),
|
||||
}
|
||||
|
||||
impl Value {
|
||||
pub fn boolean(boolean: bool) -> Self {
|
||||
Value::Concrete(ConcreteValue::Boolean(boolean))
|
||||
}
|
||||
|
||||
pub fn byte(byte: u8) -> Self {
|
||||
Value::Concrete(ConcreteValue::Byte(byte))
|
||||
}
|
||||
|
||||
pub fn character(character: char) -> Self {
|
||||
Value::Concrete(ConcreteValue::Character(character))
|
||||
}
|
||||
|
||||
pub fn float(float: f64) -> Self {
|
||||
Value::Concrete(ConcreteValue::Float(float))
|
||||
}
|
||||
|
||||
pub fn integer(integer: i64) -> Self {
|
||||
Value::Concrete(ConcreteValue::Integer(integer))
|
||||
}
|
||||
|
||||
pub fn string(string: impl Into<DustString>) -> Self {
|
||||
Value::Concrete(ConcreteValue::String(string.into()))
|
||||
}
|
||||
|
||||
pub fn as_boolean(&self) -> Option<bool> {
|
||||
if let Value::Concrete(ConcreteValue::Boolean(boolean)) = self {
|
||||
Some(*boolean)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_byte(&self) -> Option<u8> {
|
||||
if let Value::Concrete(ConcreteValue::Byte(byte)) = self {
|
||||
Some(*byte)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_character(&self) -> Option<char> {
|
||||
if let Value::Concrete(ConcreteValue::Character(character)) = self {
|
||||
Some(*character)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_float(&self) -> Option<f64> {
|
||||
if let Value::Concrete(ConcreteValue::Float(float)) = self {
|
||||
Some(*float)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_function(&self) -> Option<&Function> {
|
||||
if let Value::Function(function) = self {
|
||||
Some(function)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_integer(&self) -> Option<i64> {
|
||||
if let Value::Concrete(ConcreteValue::Integer(integer)) = self {
|
||||
Some(*integer)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_string(&self) -> Option<&DustString> {
|
||||
if let Value::Concrete(ConcreteValue::String(value)) = self {
|
||||
Some(value)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_string(&self) -> bool {
|
||||
matches!(self, Value::Concrete(ConcreteValue::String(_)))
|
||||
}
|
||||
|
||||
pub fn is_function(&self) -> bool {
|
||||
matches!(self, Value::Function(_))
|
||||
}
|
||||
|
||||
pub fn r#type(&self) -> Type {
|
||||
match self {
|
||||
Value::Concrete(concrete_value) => concrete_value.r#type(),
|
||||
Value::AbstractList(AbstractList { item_type, .. }) => {
|
||||
Type::List(Box::new(item_type.clone()))
|
||||
}
|
||||
Value::Function(Function { r#type, .. }) => Type::Function(Box::new(r#type.clone())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(&self, other: &Value) -> Value {
|
||||
let sum = match (self, other) {
|
||||
(Value::Concrete(left), Value::Concrete(right)) => left.add(right),
|
||||
_ => panic!("{}", ValueError::CannotAdd(self.clone(), other.clone())),
|
||||
};
|
||||
|
||||
Value::Concrete(sum)
|
||||
}
|
||||
|
||||
pub fn subtract(&self, other: &Value) -> Value {
|
||||
let difference = match (self, other) {
|
||||
(Value::Concrete(left), Value::Concrete(right)) => left.subtract(right),
|
||||
_ => panic!(
|
||||
"{}",
|
||||
ValueError::CannotSubtract(self.clone(), other.clone())
|
||||
),
|
||||
};
|
||||
|
||||
Value::Concrete(difference)
|
||||
}
|
||||
|
||||
pub fn multiply(&self, other: &Value) -> Result<Value, ValueError> {
|
||||
match (self, other) {
|
||||
(Value::Concrete(left), Value::Concrete(right)) => {
|
||||
left.multiply(right).map(Value::Concrete)
|
||||
}
|
||||
_ => Err(ValueError::CannotMultiply(
|
||||
self.to_owned(),
|
||||
other.to_owned(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn divide(&self, other: &Value) -> Result<Value, ValueError> {
|
||||
match (self, other) {
|
||||
(Value::Concrete(left), Value::Concrete(right)) => {
|
||||
left.divide(right).map(Value::Concrete)
|
||||
}
|
||||
_ => Err(ValueError::CannotDivide(self.to_owned(), other.to_owned())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn modulo(&self, other: &Value) -> Result<Value, ValueError> {
|
||||
match (self, other) {
|
||||
(Value::Concrete(left), Value::Concrete(right)) => {
|
||||
left.modulo(right).map(Value::Concrete)
|
||||
}
|
||||
_ => Err(ValueError::CannotModulo(self.to_owned(), other.to_owned())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn negate(&self) -> Value {
|
||||
let concrete = match self {
|
||||
Value::Concrete(concrete_value) => concrete_value.negate(),
|
||||
_ => panic!("{}", ValueError::CannotNegate(self.clone())),
|
||||
};
|
||||
|
||||
Value::Concrete(concrete)
|
||||
}
|
||||
|
||||
pub fn not(&self) -> Result<Value, ValueError> {
|
||||
match self {
|
||||
Value::Concrete(concrete_value) => concrete_value.not().map(Value::Concrete),
|
||||
_ => Err(ValueError::CannotNot(self.to_owned())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn equals(&self, other: &Value) -> bool {
|
||||
match (self, other) {
|
||||
(Value::Concrete(left), Value::Concrete(right)) => left.equals(right),
|
||||
_ => panic!(
|
||||
"{}",
|
||||
ValueError::CannotCompare(self.to_owned(), other.to_owned())
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn less(&self, other: &Value) -> Result<Value, ValueError> {
|
||||
match (self, other) {
|
||||
(Value::Concrete(left), Value::Concrete(right)) => {
|
||||
left.less_than(right).map(Value::Concrete)
|
||||
}
|
||||
_ => Err(ValueError::CannotCompare(self.to_owned(), other.to_owned())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn less_than_or_equals(&self, other: &Value) -> Result<Value, ValueError> {
|
||||
match (self, other) {
|
||||
(Value::Concrete(left), Value::Concrete(right)) => {
|
||||
left.less_than_or_equals(right).map(Value::Concrete)
|
||||
}
|
||||
_ => Err(ValueError::CannotCompare(self.to_owned(), other.to_owned())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn display(&self, data: &ThreadData) -> DustString {
|
||||
match self {
|
||||
Value::AbstractList(list) => list.display(data),
|
||||
Value::Concrete(concrete_value) => concrete_value.display(),
|
||||
Value::Function(function) => DustString::from(function.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Value {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Value::Concrete(concrete_value) => write!(f, "{concrete_value}"),
|
||||
Value::AbstractList(list) => write!(f, "{list}"),
|
||||
Value::Boolean(boolean) => write!(f, "{boolean}"),
|
||||
Value::Byte(byte) => write!(f, "{byte}"),
|
||||
Value::Character(character) => write!(f, "{character}"),
|
||||
Value::Float(float) => write!(f, "{float}"),
|
||||
Value::Integer(integer) => write!(f, "{integer}"),
|
||||
Value::String(string) => write!(f, "{string}"),
|
||||
Value::List(list) => write!(f, "{list:?}"),
|
||||
Value::Function(function) => write!(f, "{function}"),
|
||||
}
|
||||
}
|
||||
|
@ -7,27 +7,27 @@ use crate::{
|
||||
LoadConstant, LoadFunction, LoadList, LoadSelf, Modulo, Multiply, Negate, Not, Point,
|
||||
Return, SetLocal, Subtract, Test, TestSet, TypeCode,
|
||||
},
|
||||
vm::FunctionCall,
|
||||
vm::CallFrame,
|
||||
};
|
||||
|
||||
use super::{Pointer, Register, thread::ThreadData};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct RunAction {
|
||||
pub struct Action {
|
||||
pub logic: RunnerLogic,
|
||||
pub instruction: Instruction,
|
||||
}
|
||||
|
||||
impl From<Instruction> for RunAction {
|
||||
impl From<Instruction> for Action {
|
||||
fn from(instruction: Instruction) -> Self {
|
||||
let operation = instruction.operation();
|
||||
let logic = RUNNER_LOGIC_TABLE[operation.0 as usize];
|
||||
|
||||
RunAction { logic, instruction }
|
||||
Action { logic, instruction }
|
||||
}
|
||||
}
|
||||
|
||||
pub type RunnerLogic = fn(Instruction, &mut ThreadData) -> bool;
|
||||
pub type RunnerLogic = fn(Instruction, &mut Thread) -> bool;
|
||||
|
||||
pub const RUNNER_LOGIC_TABLE: [RunnerLogic; 25] = [
|
||||
point,
|
||||
@ -57,7 +57,7 @@ pub const RUNNER_LOGIC_TABLE: [RunnerLogic; 25] = [
|
||||
r#return,
|
||||
];
|
||||
|
||||
pub(crate) fn get_next_action(data: &mut ThreadData) -> RunAction {
|
||||
pub(crate) fn get_next_action(data: &mut ThreadData) -> Action {
|
||||
let current_call = data.call_stack.last_mut_unchecked();
|
||||
let instruction = current_call.chunk.instructions[current_call.ip];
|
||||
let operation = instruction.operation();
|
||||
@ -65,7 +65,7 @@ pub(crate) fn get_next_action(data: &mut ThreadData) -> RunAction {
|
||||
|
||||
current_call.ip += 1;
|
||||
|
||||
RunAction { logic, instruction }
|
||||
Action { logic, instruction }
|
||||
}
|
||||
|
||||
pub fn point(instruction: Instruction, data: &mut ThreadData) -> bool {
|
||||
@ -782,7 +782,7 @@ pub fn call(instruction: Instruction, data: &mut ThreadData) -> bool {
|
||||
|
||||
current_call.chunk.prototypes[function.prototype_index as usize].clone()
|
||||
};
|
||||
let mut next_call = FunctionCall::new(prototype, return_register);
|
||||
let mut next_call = CallFrame::new(prototype, return_register);
|
||||
let mut argument_index = 0;
|
||||
|
||||
for register_index in first_argument_register..return_register {
|
73
dust-lang/src/vm/call_frame.rs
Normal file
73
dust-lang/src/vm/call_frame.rs
Normal file
@ -0,0 +1,73 @@
|
||||
use std::{
|
||||
fmt::{self, Debug, Display, Formatter},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use smallvec::{SmallVec, smallvec};
|
||||
|
||||
use crate::{Chunk, DustString};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CallFrame {
|
||||
pub chunk: Arc<Chunk>,
|
||||
pub ip: usize,
|
||||
pub return_register: u16,
|
||||
pub registers: RegisterTable,
|
||||
}
|
||||
|
||||
impl CallFrame {
|
||||
pub fn new(chunk: Arc<Chunk>, return_register: u16) -> Self {
|
||||
let register_count = chunk.register_count;
|
||||
|
||||
Self {
|
||||
chunk,
|
||||
ip: 0,
|
||||
return_register,
|
||||
registers: RegisterTable::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for CallFrame {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"FunctionCall: {} | IP: {}",
|
||||
self.chunk
|
||||
.name
|
||||
.as_ref()
|
||||
.unwrap_or(&DustString::from("anonymous")),
|
||||
self.ip,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RegisterTable {
|
||||
pub booleans: SmallVec<[Register<bool>; 64]>,
|
||||
pub bytes: SmallVec<[Register<u8>; 64]>,
|
||||
pub characters: SmallVec<[Register<char>; 64]>,
|
||||
pub floats: SmallVec<[Register<f64>; 64]>,
|
||||
pub integers: SmallVec<[Register<i64>; 64]>,
|
||||
pub strings: SmallVec<[Register<DustString>; 64]>,
|
||||
}
|
||||
|
||||
impl RegisterTable {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
booleans: smallvec![Register::Empty; 64],
|
||||
bytes: smallvec![Register::Empty; 64],
|
||||
characters: smallvec![Register::Empty; 64],
|
||||
floats: smallvec![Register::Empty; 64],
|
||||
integers: smallvec![Register::Empty; 64],
|
||||
strings: smallvec![Register::Empty; 64],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Register<T> {
|
||||
Empty,
|
||||
Value(T),
|
||||
Pointer(*const T),
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
use std::{
|
||||
fmt::{self, Debug, Display, Formatter},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use crate::{Chunk, DustString};
|
||||
|
||||
use super::Register;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FunctionCall {
|
||||
pub chunk: Arc<Chunk>,
|
||||
pub ip: usize,
|
||||
pub return_register: u16,
|
||||
pub registers: Vec<Register>,
|
||||
}
|
||||
|
||||
impl FunctionCall {
|
||||
pub fn new(chunk: Arc<Chunk>, return_register: u16) -> Self {
|
||||
let register_count = chunk.register_count;
|
||||
|
||||
Self {
|
||||
chunk,
|
||||
ip: 0,
|
||||
return_register,
|
||||
registers: vec![Register::Empty; register_count],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FunctionCall {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"FunctionCall: {} | IP: {} | Registers: {}",
|
||||
self.chunk
|
||||
.name
|
||||
.as_ref()
|
||||
.unwrap_or(&DustString::from("anonymous")),
|
||||
self.ip,
|
||||
self.registers.len()
|
||||
)
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
//! Virtual machine and errors
|
||||
mod function_call;
|
||||
mod run_action;
|
||||
mod stack;
|
||||
mod action;
|
||||
mod call_frame;
|
||||
mod thread;
|
||||
|
||||
use std::{
|
||||
@ -10,11 +9,10 @@ use std::{
|
||||
thread::Builder,
|
||||
};
|
||||
|
||||
pub use function_call::FunctionCall;
|
||||
pub use run_action::RunAction;
|
||||
pub(crate) use run_action::get_next_action;
|
||||
pub use stack::Stack;
|
||||
pub use thread::{Thread, ThreadData};
|
||||
pub use action::Action;
|
||||
pub(crate) use action::get_next_action;
|
||||
pub use call_frame::{CallFrame, Register, RegisterTable};
|
||||
pub use thread::Thread;
|
||||
|
||||
use crossbeam_channel::bounded;
|
||||
use tracing::{Level, span};
|
||||
@ -46,12 +44,13 @@ impl Vm {
|
||||
.as_ref()
|
||||
.map(|name| name.to_string())
|
||||
.unwrap_or_else(|| "anonymous".to_string());
|
||||
let mut main_thread = Thread::new(Arc::new(self.main_chunk));
|
||||
let (tx, rx) = bounded(1);
|
||||
let main_chunk = Arc::new(self.main_chunk);
|
||||
|
||||
Builder::new()
|
||||
.name(thread_name)
|
||||
.spawn(move || {
|
||||
let mut main_thread = Thread::new(main_chunk);
|
||||
let value_option = main_thread.run();
|
||||
let _ = tx.send(value_option);
|
||||
})
|
||||
@ -62,39 +61,3 @@ impl Vm {
|
||||
rx.recv().unwrap_or(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Register {
|
||||
Empty,
|
||||
Value(Value),
|
||||
Pointer(Pointer),
|
||||
}
|
||||
|
||||
impl Display for Register {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Empty => write!(f, "empty"),
|
||||
Self::Value(value) => write!(f, "{}", value),
|
||||
Self::Pointer(pointer) => write!(f, "{}", pointer),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub enum Pointer {
|
||||
Register(u16),
|
||||
Constant(u16),
|
||||
Stack(usize, u16),
|
||||
}
|
||||
|
||||
impl Display for Pointer {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Register(index) => write!(f, "PR{}", index),
|
||||
Self::Constant(index) => write!(f, "PC{}", index),
|
||||
Self::Stack(call_index, register_index) => {
|
||||
write!(f, "PS{}R{}", call_index, register_index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,137 +0,0 @@
|
||||
use std::{
|
||||
fmt::{self, Debug, Display, Formatter},
|
||||
ops::{Index, IndexMut, Range},
|
||||
};
|
||||
|
||||
use super::FunctionCall;
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct Stack<T> {
|
||||
items: Vec<T>,
|
||||
}
|
||||
|
||||
impl<T> Stack<T> {
|
||||
pub fn new() -> Self {
|
||||
Stack {
|
||||
items: Vec::with_capacity(1),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Stack {
|
||||
items: Vec::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.items.is_empty()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.items.len()
|
||||
}
|
||||
|
||||
pub fn get_unchecked(&self, index: usize) -> &T {
|
||||
if cfg!(debug_assertions) {
|
||||
assert!(index < self.len(), "Stack underflow");
|
||||
|
||||
&self.items[index]
|
||||
} else {
|
||||
unsafe { self.items.get_unchecked(index) }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_unchecked_mut(&mut self, index: usize) -> &mut T {
|
||||
if cfg!(debug_assertions) {
|
||||
assert!(index < self.len(), "Stack underflow");
|
||||
|
||||
&mut self.items[index]
|
||||
} else {
|
||||
unsafe { self.items.get_unchecked_mut(index) }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, item: T) {
|
||||
self.items.push(item);
|
||||
}
|
||||
|
||||
pub fn pop(&mut self) -> Option<T> {
|
||||
self.items.pop()
|
||||
}
|
||||
|
||||
pub fn last(&self) -> Option<&T> {
|
||||
self.items.last()
|
||||
}
|
||||
|
||||
pub fn last_mut(&mut self) -> Option<&mut T> {
|
||||
self.items.last_mut()
|
||||
}
|
||||
|
||||
pub fn pop_unchecked(&mut self) -> T {
|
||||
if cfg!(debug_assertions) {
|
||||
assert!(!self.is_empty(), "Stack underflow");
|
||||
|
||||
self.items.pop().unwrap()
|
||||
} else {
|
||||
unsafe { self.items.pop().unwrap_unchecked() }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn last_unchecked(&self) -> &T {
|
||||
if cfg!(debug_assertions) {
|
||||
assert!(!self.is_empty(), "Stack underflow");
|
||||
|
||||
self.items.last().unwrap()
|
||||
} else {
|
||||
unsafe { self.items.last().unwrap_unchecked() }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn last_mut_unchecked(&mut self) -> &mut T {
|
||||
if cfg!(debug_assertions) {
|
||||
assert!(!self.is_empty(), "Stack underflow");
|
||||
|
||||
self.items.last_mut().unwrap()
|
||||
} else {
|
||||
unsafe { self.items.last_mut().unwrap_unchecked() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for Stack<T> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Index<Range<usize>> for Stack<T> {
|
||||
type Output = [T];
|
||||
|
||||
fn index(&self, index: Range<usize>) -> &Self::Output {
|
||||
&self.items[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IndexMut<Range<usize>> for Stack<T> {
|
||||
fn index_mut(&mut self, index: Range<usize>) -> &mut Self::Output {
|
||||
&mut self.items[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug> Debug for Stack<T> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{:?}", self.items)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Stack<FunctionCall> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
writeln!(f, "----- DUST CALL STACK -----")?;
|
||||
|
||||
for (index, function_call) in self.items.iter().enumerate().rev() {
|
||||
writeln!(f, "{index:02} | {function_call}")?;
|
||||
}
|
||||
|
||||
write!(f, "---------------------------")
|
||||
}
|
||||
}
|
@ -1,18 +1,36 @@
|
||||
use std::{mem::replace, sync::Arc, thread::JoinHandle};
|
||||
use std::{sync::Arc, thread::JoinHandle};
|
||||
|
||||
use tracing::{info, trace};
|
||||
|
||||
use crate::{Chunk, DustString, Operand, Span, Value, vm::FunctionCall};
|
||||
use crate::{Chunk, DustString, Span, Value, vm::CallFrame};
|
||||
|
||||
use super::{Pointer, Register, RunAction, Stack};
|
||||
use super::{Action, Register};
|
||||
|
||||
pub struct Thread {
|
||||
chunk: Arc<Chunk>,
|
||||
call_stack: Vec<CallFrame>,
|
||||
next_action: Action,
|
||||
return_value: Option<Value>,
|
||||
spawned_threads: Vec<JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl Thread {
|
||||
pub fn new(chunk: Arc<Chunk>) -> Self {
|
||||
Thread { chunk }
|
||||
let mut call_stack = Vec::with_capacity(chunk.prototypes.len() + 1);
|
||||
let mut main_call = CallFrame::new(chunk.clone(), 0);
|
||||
main_call.ip = 1; // The first action is already known
|
||||
|
||||
call_stack.push(main_call);
|
||||
|
||||
let first_action = Action::from(*chunk.instructions.first().unwrap());
|
||||
|
||||
Thread {
|
||||
chunk,
|
||||
call_stack,
|
||||
next_action: first_action,
|
||||
return_value: None,
|
||||
spawned_threads: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> Option<Value> {
|
||||
@ -24,247 +42,287 @@ impl Thread {
|
||||
.unwrap_or_else(|| DustString::from("anonymous"))
|
||||
);
|
||||
|
||||
let mut call_stack = Stack::with_capacity(self.chunk.prototypes.len() + 1);
|
||||
let mut main_call = FunctionCall::new(self.chunk.clone(), 0);
|
||||
main_call.ip = 1; // The first action is already known
|
||||
|
||||
call_stack.push(main_call);
|
||||
|
||||
let first_action = RunAction::from(*self.chunk.instructions.first().unwrap());
|
||||
let mut thread_data = ThreadData {
|
||||
call_stack,
|
||||
next_action: first_action,
|
||||
return_value_index: None,
|
||||
spawned_threads: Vec::new(),
|
||||
};
|
||||
|
||||
loop {
|
||||
trace!("Instruction: {}", thread_data.next_action.instruction);
|
||||
trace!("Instruction: {}", self.next_action.instruction);
|
||||
|
||||
let should_end = (thread_data.next_action.logic)(
|
||||
thread_data.next_action.instruction,
|
||||
&mut thread_data,
|
||||
);
|
||||
let should_end = (self.next_action.logic)(self.next_action.instruction, self);
|
||||
|
||||
if should_end {
|
||||
let value_option = if let Some(register_index) = thread_data.return_value_index {
|
||||
let value =
|
||||
thread_data.empty_register_or_clone_constant_unchecked(register_index);
|
||||
self.spawned_threads.into_iter().for_each(|join_handle| {
|
||||
let _ = join_handle.join();
|
||||
});
|
||||
|
||||
Some(value)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
thread_data
|
||||
.spawned_threads
|
||||
.into_iter()
|
||||
.for_each(|join_handle| {
|
||||
let _ = join_handle.join();
|
||||
});
|
||||
|
||||
return value_option;
|
||||
return self.return_value.take();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ThreadData {
|
||||
pub call_stack: Stack<FunctionCall>,
|
||||
pub next_action: RunAction,
|
||||
pub return_value_index: Option<u16>,
|
||||
pub spawned_threads: Vec<JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl ThreadData {
|
||||
pub fn current_position(&self) -> Span {
|
||||
let current_call = self.call_stack.last_unchecked();
|
||||
let current_frame = self.current_frame();
|
||||
|
||||
current_call.chunk.positions[current_call.ip]
|
||||
current_frame.chunk.positions[current_frame.ip]
|
||||
}
|
||||
|
||||
pub(crate) fn follow_pointer_unchecked(&self, pointer: Pointer) -> &Value {
|
||||
trace!("Follow {pointer}");
|
||||
|
||||
match pointer {
|
||||
Pointer::Register(register_index) => self.open_register_unchecked(register_index),
|
||||
Pointer::Constant(constant_index) => self.get_constant_unchecked(constant_index),
|
||||
Pointer::Stack(stack_index, register_index) => unsafe {
|
||||
let register = self
|
||||
.call_stack
|
||||
.get_unchecked(stack_index)
|
||||
.registers
|
||||
.get_unchecked(register_index as usize);
|
||||
|
||||
match register {
|
||||
Register::Value(value) => value,
|
||||
Register::Pointer(pointer) => self.follow_pointer_unchecked(*pointer),
|
||||
Register::Empty => panic!("VM Error: Register {register_index} is empty"),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_register_unchecked(&self, register_index: u16) -> &Register {
|
||||
trace!("Get R{register_index}");
|
||||
|
||||
let register_index = register_index as usize;
|
||||
|
||||
pub fn current_frame(&self) -> &CallFrame {
|
||||
if cfg!(debug_assertions) {
|
||||
&self.call_stack.last_unchecked().registers[register_index]
|
||||
self.call_stack.last().unwrap()
|
||||
} else {
|
||||
unsafe {
|
||||
self.call_stack
|
||||
.last_unchecked()
|
||||
.registers
|
||||
.get_unchecked(register_index)
|
||||
}
|
||||
unsafe { self.call_stack.last().unwrap_unchecked() }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_register(&mut self, to_register: u16, register: Register) {
|
||||
let to_register = to_register as usize;
|
||||
|
||||
self.call_stack.last_mut_unchecked().registers[to_register] = register;
|
||||
pub fn current_frame_mut(&mut self) -> &mut CallFrame {
|
||||
if cfg!(debug_assertions) {
|
||||
self.call_stack.last_mut().unwrap()
|
||||
} else {
|
||||
unsafe { self.call_stack.last_mut().unwrap_unchecked() }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_register_unchecked(&self, register_index: u16) -> &Value {
|
||||
let register_index = register_index as usize;
|
||||
pub fn get_frame(&self, index: usize) -> &CallFrame {
|
||||
if cfg!(debug_assertions) {
|
||||
self.call_stack.get(index).unwrap()
|
||||
} else {
|
||||
unsafe { self.call_stack.get_unchecked(index) }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_boolean_register(&self, index: usize) -> bool {
|
||||
let register = if cfg!(debug_assertions) {
|
||||
&self.call_stack.last_unchecked().registers[register_index]
|
||||
self.current_frame()
|
||||
.registers
|
||||
.booleans
|
||||
.get(index as usize)
|
||||
.unwrap()
|
||||
} else {
|
||||
unsafe {
|
||||
self.call_stack
|
||||
.last_unchecked()
|
||||
self.current_frame()
|
||||
.registers
|
||||
.get_unchecked(register_index)
|
||||
.booleans
|
||||
.get_unchecked(index as usize)
|
||||
}
|
||||
};
|
||||
|
||||
trace!("Open R{register_index} to {register}");
|
||||
|
||||
match register {
|
||||
Register::Value(value) => value,
|
||||
Register::Pointer(pointer) => self.follow_pointer_unchecked(*pointer),
|
||||
Register::Empty => panic!("VM Error: Register {register_index} is empty"),
|
||||
Register::Value(boolean) => *boolean,
|
||||
Register::Pointer(pointer) => unsafe { **pointer },
|
||||
Register::Empty => panic!("Attempted to get a boolean from an empty register"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_register_allow_empty_unchecked(&self, register_index: u16) -> Option<&Value> {
|
||||
trace!("Open R{register_index}");
|
||||
|
||||
let register = self.get_register_unchecked(register_index);
|
||||
|
||||
trace!("Open R{register_index} to {register}");
|
||||
|
||||
match register {
|
||||
Register::Value(value) => Some(value),
|
||||
Register::Pointer(pointer) => Some(self.follow_pointer_unchecked(*pointer)),
|
||||
Register::Empty => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty_register_or_clone_constant_unchecked(&mut self, register_index: u16) -> Value {
|
||||
let register_index = register_index as usize;
|
||||
let old_register = replace(
|
||||
&mut self.call_stack.last_mut_unchecked().registers[register_index],
|
||||
Register::Empty,
|
||||
);
|
||||
|
||||
match old_register {
|
||||
Register::Value(value) => value,
|
||||
Register::Pointer(pointer) => match pointer {
|
||||
Pointer::Register(register_index) => {
|
||||
self.empty_register_or_clone_constant_unchecked(register_index)
|
||||
}
|
||||
Pointer::Constant(constant_index) => {
|
||||
self.get_constant_unchecked(constant_index).clone()
|
||||
}
|
||||
Pointer::Stack(stack_index, register_index) => {
|
||||
let call = self.call_stack.get_unchecked_mut(stack_index);
|
||||
|
||||
let old_register = replace(
|
||||
&mut call.registers[register_index as usize],
|
||||
Register::Empty,
|
||||
);
|
||||
|
||||
match old_register {
|
||||
Register::Value(value) => value,
|
||||
Register::Pointer(pointer) => {
|
||||
self.follow_pointer_unchecked(pointer).clone()
|
||||
}
|
||||
Register::Empty => panic!("VM Error: Register {register_index} is empty"),
|
||||
}
|
||||
}
|
||||
},
|
||||
Register::Empty => panic!("VM Error: Register {register_index} is empty"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clone_register_value_or_constant_unchecked(&self, register_index: u16) -> Value {
|
||||
let register = self.get_register_unchecked(register_index);
|
||||
|
||||
match register {
|
||||
Register::Value(value) => value.clone(),
|
||||
Register::Pointer(pointer) => match pointer {
|
||||
Pointer::Register(register_index) => {
|
||||
self.open_register_unchecked(*register_index).clone()
|
||||
}
|
||||
Pointer::Constant(constant_index) => {
|
||||
self.get_constant_unchecked(*constant_index).clone()
|
||||
}
|
||||
Pointer::Stack(stack_index, register_index) => {
|
||||
let call = self.call_stack.get_unchecked(*stack_index);
|
||||
let register = &call.registers[*register_index as usize];
|
||||
|
||||
match register {
|
||||
Register::Value(value) => value.clone(),
|
||||
Register::Pointer(pointer) => {
|
||||
self.follow_pointer_unchecked(*pointer).clone()
|
||||
}
|
||||
Register::Empty => panic!("VM Error: Register {register_index} is empty"),
|
||||
}
|
||||
}
|
||||
},
|
||||
Register::Empty => panic!("VM Error: Register {register_index} is empty"),
|
||||
}
|
||||
}
|
||||
|
||||
/// DRY helper to get a value from an Argument
|
||||
pub fn get_argument_unchecked(&self, argument: Operand) -> &Value {
|
||||
match argument {
|
||||
Operand::Constant(constant_index) => self.get_constant_unchecked(constant_index),
|
||||
Operand::Register(register_index) => self.open_register_unchecked(register_index),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_constant_unchecked(&self, constant_index: u16) -> &Value {
|
||||
let constant_index = constant_index as usize;
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
&self.call_stack.last().unwrap().chunk.constants[constant_index]
|
||||
pub fn set_boolean_register(&mut self, index: usize, new_register: Register<bool>) {
|
||||
let old_register = if cfg!(debug_assertions) {
|
||||
self.current_frame_mut()
|
||||
.registers
|
||||
.booleans
|
||||
.get_mut(index as usize)
|
||||
.unwrap()
|
||||
} else {
|
||||
unsafe {
|
||||
self.call_stack
|
||||
.last_unchecked()
|
||||
.chunk
|
||||
.constants
|
||||
.get_unchecked(constant_index)
|
||||
self.current_frame_mut()
|
||||
.registers
|
||||
.booleans
|
||||
.get_unchecked_mut(index as usize)
|
||||
}
|
||||
};
|
||||
|
||||
*old_register = new_register;
|
||||
}
|
||||
|
||||
pub fn get_byte_register(&self, index: usize) -> u8 {
|
||||
let register = if cfg!(debug_assertions) {
|
||||
self.current_frame()
|
||||
.registers
|
||||
.bytes
|
||||
.get(index as usize)
|
||||
.unwrap()
|
||||
} else {
|
||||
unsafe {
|
||||
self.current_frame()
|
||||
.registers
|
||||
.bytes
|
||||
.get_unchecked(index as usize)
|
||||
}
|
||||
};
|
||||
|
||||
match register {
|
||||
Register::Value(byte) => *byte,
|
||||
Register::Pointer(pointer) => unsafe { **pointer },
|
||||
Register::Empty => panic!("Attempted to get a byte from an empty register"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_local_register(&self, local_index: u16) -> u16 {
|
||||
let local_index = local_index as usize;
|
||||
let chunk = &self.call_stack.last_unchecked().chunk;
|
||||
pub fn set_byte_register(&mut self, index: usize, new_register: Register<u8>) {
|
||||
let old_register = if cfg!(debug_assertions) {
|
||||
self.current_frame_mut()
|
||||
.registers
|
||||
.bytes
|
||||
.get_mut(index as usize)
|
||||
.unwrap()
|
||||
} else {
|
||||
unsafe {
|
||||
self.current_frame_mut()
|
||||
.registers
|
||||
.bytes
|
||||
.get_unchecked_mut(index as usize)
|
||||
}
|
||||
};
|
||||
|
||||
assert!(
|
||||
local_index < chunk.locals.len(),
|
||||
"VM Error: Local index out of bounds"
|
||||
);
|
||||
*old_register = new_register;
|
||||
}
|
||||
|
||||
chunk.locals[local_index].register_index
|
||||
pub fn get_character_register(&self, index: usize) -> char {
|
||||
let register = if cfg!(debug_assertions) {
|
||||
self.current_frame()
|
||||
.registers
|
||||
.characters
|
||||
.get(index as usize)
|
||||
.unwrap()
|
||||
} else {
|
||||
unsafe {
|
||||
self.current_frame()
|
||||
.registers
|
||||
.characters
|
||||
.get_unchecked(index as usize)
|
||||
}
|
||||
};
|
||||
|
||||
match register {
|
||||
Register::Value(character) => *character,
|
||||
Register::Pointer(pointer) => unsafe { **pointer },
|
||||
Register::Empty => panic!("Attempted to get a character from an empty register"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_character_register(&mut self, index: usize, new_register: Register<char>) {
|
||||
let old_register = if cfg!(debug_assertions) {
|
||||
self.current_frame_mut()
|
||||
.registers
|
||||
.characters
|
||||
.get_mut(index as usize)
|
||||
.unwrap()
|
||||
} else {
|
||||
unsafe {
|
||||
self.current_frame_mut()
|
||||
.registers
|
||||
.characters
|
||||
.get_unchecked_mut(index as usize)
|
||||
}
|
||||
};
|
||||
|
||||
*old_register = new_register;
|
||||
}
|
||||
|
||||
pub fn get_float_register(&self, index: usize) -> f64 {
|
||||
let register = if cfg!(debug_assertions) {
|
||||
self.current_frame()
|
||||
.registers
|
||||
.floats
|
||||
.get(index as usize)
|
||||
.unwrap()
|
||||
} else {
|
||||
unsafe {
|
||||
self.current_frame()
|
||||
.registers
|
||||
.floats
|
||||
.get_unchecked(index as usize)
|
||||
}
|
||||
};
|
||||
|
||||
match register {
|
||||
Register::Value(float) => *float,
|
||||
Register::Pointer(pointer) => unsafe { **pointer },
|
||||
Register::Empty => panic!("Attempted to get a float from an empty register"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_integer_register(&self, index: usize) -> i64 {
|
||||
let register = if cfg!(debug_assertions) {
|
||||
self.current_frame()
|
||||
.registers
|
||||
.integers
|
||||
.get(index as usize)
|
||||
.unwrap()
|
||||
} else {
|
||||
unsafe {
|
||||
self.current_frame()
|
||||
.registers
|
||||
.integers
|
||||
.get_unchecked(index as usize)
|
||||
}
|
||||
};
|
||||
|
||||
match register {
|
||||
Register::Value(integer) => *integer,
|
||||
Register::Pointer(pointer) => unsafe { **pointer },
|
||||
Register::Empty => panic!("Attempted to get an integer from an empty register"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_integer_register(&mut self, index: usize, new_register: Register<i64>) {
|
||||
let old_register = if cfg!(debug_assertions) {
|
||||
self.current_frame_mut()
|
||||
.registers
|
||||
.integers
|
||||
.get_mut(index as usize)
|
||||
.unwrap()
|
||||
} else {
|
||||
unsafe {
|
||||
self.current_frame_mut()
|
||||
.registers
|
||||
.integers
|
||||
.get_unchecked_mut(index as usize)
|
||||
}
|
||||
};
|
||||
|
||||
*old_register = new_register;
|
||||
}
|
||||
|
||||
pub fn get_string_register(&self, index: usize) -> &DustString {
|
||||
let register = if cfg!(debug_assertions) {
|
||||
self.current_frame()
|
||||
.registers
|
||||
.strings
|
||||
.get(index as usize)
|
||||
.unwrap()
|
||||
} else {
|
||||
unsafe {
|
||||
self.current_frame()
|
||||
.registers
|
||||
.strings
|
||||
.get_unchecked(index as usize)
|
||||
}
|
||||
};
|
||||
|
||||
match register {
|
||||
Register::Value(string) => string,
|
||||
Register::Pointer(pointer) => {
|
||||
if cfg!(debug_assertions) {
|
||||
unsafe { pointer.as_ref().unwrap() }
|
||||
} else {
|
||||
unsafe { pointer.as_ref().unwrap_unchecked() }
|
||||
}
|
||||
}
|
||||
Register::Empty => panic!("Attempted to get a string from an empty register"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_string_register(&mut self, index: usize, new_register: Register<DustString>) {
|
||||
let old_register = if cfg!(debug_assertions) {
|
||||
self.current_frame_mut()
|
||||
.registers
|
||||
.strings
|
||||
.get_mut(index as usize)
|
||||
.unwrap()
|
||||
} else {
|
||||
unsafe {
|
||||
self.current_frame_mut()
|
||||
.registers
|
||||
.strings
|
||||
.get_unchecked_mut(index as usize)
|
||||
}
|
||||
};
|
||||
|
||||
*old_register = new_register;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user