New VM optimizations; Pass all tests
This commit is contained in:
parent
69ef1b3b06
commit
8940f37654
@ -366,6 +366,10 @@ impl Instruction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn no_op() -> Instruction {
|
||||||
|
Instruction(Operation::NO_OP.0 as u64)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn point(destination: u16, to: Operand) -> Instruction {
|
pub fn point(destination: u16, to: Operand) -> Instruction {
|
||||||
Instruction::from(Point { destination, to })
|
Instruction::from(Point { destination, to })
|
||||||
}
|
}
|
||||||
|
@ -9,49 +9,52 @@ use serde::{Deserialize, Serialize};
|
|||||||
pub struct Operation(pub u8);
|
pub struct Operation(pub u8);
|
||||||
|
|
||||||
impl Operation {
|
impl Operation {
|
||||||
|
pub const NO_OP: Operation = Operation(0);
|
||||||
|
|
||||||
// Stack manipulation
|
// Stack manipulation
|
||||||
pub const POINT: Operation = Operation(0);
|
pub const POINT: Operation = Operation(1);
|
||||||
pub const CLOSE: Operation = Operation(1);
|
pub const CLOSE: Operation = Operation(2);
|
||||||
|
|
||||||
// Loaders
|
// Loaders
|
||||||
pub const LOAD_ENCODED: Operation = Operation(2);
|
pub const LOAD_ENCODED: Operation = Operation(3);
|
||||||
pub const LOAD_CONSTANT: Operation = Operation(3);
|
pub const LOAD_CONSTANT: Operation = Operation(4);
|
||||||
pub const LOAD_FUNCTION: Operation = Operation(4);
|
pub const LOAD_FUNCTION: Operation = Operation(5);
|
||||||
pub const LOAD_LIST: Operation = Operation(5);
|
pub const LOAD_LIST: Operation = Operation(6);
|
||||||
pub const LOAD_SELF: Operation = Operation(6);
|
pub const LOAD_SELF: Operation = Operation(7);
|
||||||
|
|
||||||
// Arithmetic
|
// Arithmetic
|
||||||
pub const ADD: Operation = Operation(7);
|
pub const ADD: Operation = Operation(8);
|
||||||
pub const SUBTRACT: Operation = Operation(8);
|
pub const SUBTRACT: Operation = Operation(9);
|
||||||
pub const MULTIPLY: Operation = Operation(9);
|
pub const MULTIPLY: Operation = Operation(10);
|
||||||
pub const DIVIDE: Operation = Operation(10);
|
pub const DIVIDE: Operation = Operation(11);
|
||||||
pub const MODULO: Operation = Operation(11);
|
pub const MODULO: Operation = Operation(12);
|
||||||
|
|
||||||
// Comparison
|
// Comparison
|
||||||
pub const EQUAL: Operation = Operation(12);
|
pub const EQUAL: Operation = Operation(13);
|
||||||
pub const LESS: Operation = Operation(13);
|
pub const LESS: Operation = Operation(14);
|
||||||
pub const LESS_EQUAL: Operation = Operation(14);
|
pub const LESS_EQUAL: Operation = Operation(15);
|
||||||
|
|
||||||
// Unary operations
|
// Unary operations
|
||||||
pub const NEGATE: Operation = Operation(15);
|
pub const NEGATE: Operation = Operation(16);
|
||||||
pub const NOT: Operation = Operation(16);
|
pub const NOT: Operation = Operation(17);
|
||||||
|
|
||||||
// Logical operations
|
// Logical operations
|
||||||
pub const TEST: Operation = Operation(17);
|
pub const TEST: Operation = Operation(18);
|
||||||
pub const TEST_SET: Operation = Operation(18);
|
pub const TEST_SET: Operation = Operation(19);
|
||||||
|
|
||||||
// Function calls
|
// Function calls
|
||||||
pub const CALL: Operation = Operation(19);
|
pub const CALL: Operation = Operation(20);
|
||||||
pub const CALL_NATIVE: Operation = Operation(20);
|
pub const CALL_NATIVE: Operation = Operation(21);
|
||||||
|
|
||||||
// Control flow
|
// Control flow
|
||||||
pub const JUMP: Operation = Operation(21);
|
pub const JUMP: Operation = Operation(22);
|
||||||
pub const RETURN: Operation = Operation(22);
|
pub const RETURN: Operation = Operation(23);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Operation {
|
impl Operation {
|
||||||
pub fn name(&self) -> &'static str {
|
pub fn name(&self) -> &'static str {
|
||||||
match *self {
|
match *self {
|
||||||
|
Self::NO_OP => "NO_OP",
|
||||||
Self::POINT => "POINT",
|
Self::POINT => "POINT",
|
||||||
Self::CLOSE => "CLOSE",
|
Self::CLOSE => "CLOSE",
|
||||||
Self::LOAD_ENCODED => "LOAD_ENCODED",
|
Self::LOAD_ENCODED => "LOAD_ENCODED",
|
||||||
|
@ -43,6 +43,15 @@ impl AbstractList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for AbstractList {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
item_type: TypeCode::NONE,
|
||||||
|
item_pointers: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for AbstractList {
|
impl Display for AbstractList {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
write!(f, "[")?;
|
write!(f, "[")?;
|
||||||
|
@ -11,6 +11,16 @@ pub struct Function {
|
|||||||
pub prototype_index: u16,
|
pub prototype_index: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Function {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
name: None,
|
||||||
|
r#type: FunctionType::default(),
|
||||||
|
prototype_index: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for Function {
|
impl Display for Function {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
let mut type_string = self.r#type.to_string();
|
let mut type_string = self.r#type.to_string();
|
||||||
|
259
dust-lang/src/vm/action/add.rs
Normal file
259
dust-lang/src/vm/action/add.rs
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
use tracing::trace;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
DustString,
|
||||||
|
instruction::{InstructionFields, TypeCode},
|
||||||
|
vm::{Register, Thread, call_frame::PointerCache},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn add(
|
||||||
|
_: &mut usize,
|
||||||
|
instruction: InstructionFields,
|
||||||
|
thread: &mut Thread,
|
||||||
|
pointer_cache: &mut PointerCache,
|
||||||
|
) {
|
||||||
|
let destination = instruction.a_field as usize;
|
||||||
|
let left = instruction.b_field as usize;
|
||||||
|
let left_is_constant = instruction.b_is_constant;
|
||||||
|
let left_type = instruction.b_type;
|
||||||
|
let right = instruction.c_field as usize;
|
||||||
|
let right_is_constant = instruction.c_is_constant;
|
||||||
|
let right_type = instruction.c_type;
|
||||||
|
|
||||||
|
match (left_type, right_type) {
|
||||||
|
(TypeCode::INTEGER, TypeCode::INTEGER) => add_integers(instruction, thread, pointer_cache),
|
||||||
|
(TypeCode::BYTE, TypeCode::BYTE) => {
|
||||||
|
let left_value = if left_is_constant {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
thread.get_constant(left).as_byte().unwrap()
|
||||||
|
} else {
|
||||||
|
unsafe { thread.get_constant(left).as_byte().unwrap_unchecked() }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
thread.get_byte_register(left)
|
||||||
|
};
|
||||||
|
let right_value = if right_is_constant {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
thread.get_constant(right).as_byte().unwrap()
|
||||||
|
} else {
|
||||||
|
unsafe { thread.get_constant(right).as_byte().unwrap_unchecked() }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
thread.get_byte_register(right)
|
||||||
|
};
|
||||||
|
let sum = left_value + right_value;
|
||||||
|
let register = Register::Value(sum);
|
||||||
|
|
||||||
|
thread.set_byte_register(destination, register);
|
||||||
|
}
|
||||||
|
(TypeCode::FLOAT, TypeCode::FLOAT) => {
|
||||||
|
let left_value = if left_is_constant {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
thread.get_constant(left).as_float().unwrap()
|
||||||
|
} else {
|
||||||
|
unsafe { thread.get_constant(left).as_float().unwrap_unchecked() }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
thread.get_float_register(left)
|
||||||
|
};
|
||||||
|
let right_value = if right_is_constant {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
thread.get_constant(right).as_float().unwrap()
|
||||||
|
} else {
|
||||||
|
unsafe { thread.get_constant(right).as_float().unwrap_unchecked() }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
thread.get_float_register(right)
|
||||||
|
};
|
||||||
|
let sum = left_value + right_value;
|
||||||
|
let register = Register::Value(sum);
|
||||||
|
|
||||||
|
thread.set_float_register(destination, register);
|
||||||
|
}
|
||||||
|
(TypeCode::STRING, TypeCode::STRING) => {
|
||||||
|
let left_value = if left_is_constant {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
thread.get_constant(left).as_string().unwrap().clone()
|
||||||
|
} else {
|
||||||
|
unsafe {
|
||||||
|
thread
|
||||||
|
.get_constant(left)
|
||||||
|
.as_string()
|
||||||
|
.unwrap_unchecked()
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
thread.get_string_register(left).clone()
|
||||||
|
};
|
||||||
|
let right_value = if right_is_constant {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
thread.get_constant(right).as_string().unwrap().clone()
|
||||||
|
} else {
|
||||||
|
unsafe {
|
||||||
|
thread
|
||||||
|
.get_constant(right)
|
||||||
|
.as_string()
|
||||||
|
.unwrap_unchecked()
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
thread.get_string_register(right).clone()
|
||||||
|
};
|
||||||
|
let concatenated = left_value + &right_value;
|
||||||
|
let register = Register::Value(concatenated);
|
||||||
|
|
||||||
|
thread.set_string_register(destination, register);
|
||||||
|
}
|
||||||
|
(TypeCode::CHARACTER, TypeCode::CHARACTER) => {
|
||||||
|
let left_value = if left_is_constant {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
thread.get_constant(left).as_character().unwrap()
|
||||||
|
} else {
|
||||||
|
unsafe { thread.get_constant(left).as_character().unwrap_unchecked() }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
thread.get_character_register(left)
|
||||||
|
};
|
||||||
|
let right_value = if right_is_constant {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
thread.get_constant(right).as_character().unwrap()
|
||||||
|
} else {
|
||||||
|
unsafe { thread.get_constant(right).as_character().unwrap_unchecked() }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
thread.get_character_register(right)
|
||||||
|
};
|
||||||
|
let mut sum = DustString::new();
|
||||||
|
|
||||||
|
sum.push(*left_value);
|
||||||
|
sum.push(*right_value);
|
||||||
|
|
||||||
|
let register = Register::Value(sum);
|
||||||
|
|
||||||
|
thread.set_string_register(destination, register);
|
||||||
|
}
|
||||||
|
(TypeCode::STRING, TypeCode::CHARACTER) => {
|
||||||
|
let left_value = if left_is_constant {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
thread.get_constant(left).as_string().unwrap().clone()
|
||||||
|
} else {
|
||||||
|
unsafe {
|
||||||
|
thread
|
||||||
|
.get_constant(left)
|
||||||
|
.as_string()
|
||||||
|
.unwrap_unchecked()
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
thread.get_string_register(left).clone()
|
||||||
|
};
|
||||||
|
let right_value = if right_is_constant {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
thread.get_constant(right).as_character().unwrap()
|
||||||
|
} else {
|
||||||
|
unsafe { thread.get_constant(right).as_character().unwrap_unchecked() }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
thread.get_character_register(right)
|
||||||
|
};
|
||||||
|
let mut sum = left_value.clone();
|
||||||
|
|
||||||
|
sum.push(*right_value);
|
||||||
|
|
||||||
|
let register = Register::Value(sum);
|
||||||
|
|
||||||
|
thread.set_string_register(destination, register);
|
||||||
|
}
|
||||||
|
(TypeCode::CHARACTER, TypeCode::STRING) => {
|
||||||
|
let left_value = if left_is_constant {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
thread.get_constant(left).as_character().unwrap()
|
||||||
|
} else {
|
||||||
|
unsafe { thread.get_constant(left).as_character().unwrap_unchecked() }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
thread.get_character_register(left)
|
||||||
|
};
|
||||||
|
let right_value = if right_is_constant {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
thread.get_constant(right).as_string().unwrap()
|
||||||
|
} else {
|
||||||
|
unsafe { thread.get_constant(right).as_string().unwrap_unchecked() }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
thread.get_string_register(right)
|
||||||
|
};
|
||||||
|
let mut sum = right_value.clone();
|
||||||
|
|
||||||
|
sum.insert(0, *left_value);
|
||||||
|
|
||||||
|
let register = Register::Value(sum);
|
||||||
|
|
||||||
|
thread.set_string_register(destination, register);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_integers(
|
||||||
|
instruction: InstructionFields,
|
||||||
|
thread: &mut Thread,
|
||||||
|
pointer_cache: &mut PointerCache,
|
||||||
|
) {
|
||||||
|
let destination = instruction.a_field as usize;
|
||||||
|
let left = instruction.b_field as usize;
|
||||||
|
let left_is_constant = instruction.b_is_constant;
|
||||||
|
let right = instruction.c_field as usize;
|
||||||
|
let right_is_constant = instruction.c_is_constant;
|
||||||
|
|
||||||
|
if pointer_cache.integer_mut.is_null() {
|
||||||
|
trace!("ADD: Run and cache pointers");
|
||||||
|
|
||||||
|
let left_value = if left_is_constant {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
thread.get_constant(left).as_integer().unwrap()
|
||||||
|
} else {
|
||||||
|
unsafe { thread.get_constant(left).as_integer().unwrap_unchecked() }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
thread.get_integer_register(left)
|
||||||
|
};
|
||||||
|
let right_value = if right_is_constant {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
thread.get_constant(right).as_integer().unwrap()
|
||||||
|
} else {
|
||||||
|
unsafe { thread.get_constant(right).as_integer().unwrap_unchecked() }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
thread.get_integer_register(right)
|
||||||
|
};
|
||||||
|
let sum = left_value.saturating_add(*right_value);
|
||||||
|
|
||||||
|
pointer_cache.integer_left = left_value;
|
||||||
|
pointer_cache.integer_right = right_value;
|
||||||
|
pointer_cache.integer_mut = thread.get_integer_register_mut_allow_empty(destination);
|
||||||
|
|
||||||
|
thread.set_integer_register(destination, Register::Value(sum));
|
||||||
|
} else {
|
||||||
|
trace!("ADD: Use cached pointers");
|
||||||
|
|
||||||
|
add_integer_pointers(
|
||||||
|
pointer_cache.integer_mut,
|
||||||
|
pointer_cache.integer_left,
|
||||||
|
pointer_cache.integer_right,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_integer_pointers(destination: *mut i64, left: *const i64, right: *const i64) {
|
||||||
|
assert!(destination.is_aligned());
|
||||||
|
assert!(left.is_aligned());
|
||||||
|
assert!(right.is_aligned());
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
*destination = (*left).saturating_add(*right);
|
||||||
|
}
|
||||||
|
}
|
28
dust-lang/src/vm/action/jump.rs
Normal file
28
dust-lang/src/vm/action/jump.rs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
use tracing::trace;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
instruction::InstructionFields,
|
||||||
|
vm::{Thread, call_frame::PointerCache},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn jump(
|
||||||
|
ip: &mut usize,
|
||||||
|
instruction: InstructionFields,
|
||||||
|
_: &mut Thread,
|
||||||
|
_pointer_cache: &mut PointerCache,
|
||||||
|
) {
|
||||||
|
let offset = instruction.b_field as usize;
|
||||||
|
let is_positive = instruction.c_field != 0;
|
||||||
|
|
||||||
|
if is_positive {
|
||||||
|
trace!("JUMP +{}", offset);
|
||||||
|
} else {
|
||||||
|
trace!("JUMP -{}", offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_positive {
|
||||||
|
*ip += offset;
|
||||||
|
} else {
|
||||||
|
*ip -= offset + 1;
|
||||||
|
}
|
||||||
|
}
|
232
dust-lang/src/vm/action/less.rs
Normal file
232
dust-lang/src/vm/action/less.rs
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
use tracing::trace;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
instruction::{InstructionFields, TypeCode},
|
||||||
|
vm::{Thread, call_frame::PointerCache},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn less(
|
||||||
|
ip: &mut usize,
|
||||||
|
instruction: InstructionFields,
|
||||||
|
thread: &mut Thread,
|
||||||
|
pointer_cache: &mut PointerCache,
|
||||||
|
) {
|
||||||
|
let comparator = instruction.d_field;
|
||||||
|
let left = instruction.b_field as usize;
|
||||||
|
let left_type = instruction.b_type;
|
||||||
|
let left_is_constant = instruction.b_is_constant;
|
||||||
|
let right = instruction.c_field as usize;
|
||||||
|
let right_type = instruction.c_type;
|
||||||
|
let right_is_constant = instruction.c_is_constant;
|
||||||
|
|
||||||
|
match (left_type, right_type) {
|
||||||
|
(TypeCode::BOOLEAN, TypeCode::BOOLEAN) => {
|
||||||
|
let left_value = if left_is_constant {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
thread.get_constant(left).as_boolean().unwrap()
|
||||||
|
} else {
|
||||||
|
unsafe { thread.get_constant(left).as_boolean().unwrap_unchecked() }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
thread.get_boolean_register(left)
|
||||||
|
};
|
||||||
|
let right_value = if right_is_constant {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
thread.get_constant(right).as_boolean().unwrap()
|
||||||
|
} else {
|
||||||
|
unsafe { thread.get_constant(right).as_boolean().unwrap_unchecked() }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
thread.get_boolean_register(right)
|
||||||
|
};
|
||||||
|
let result = left_value < right_value;
|
||||||
|
|
||||||
|
if result == comparator {
|
||||||
|
*ip += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(TypeCode::BYTE, TypeCode::BYTE) => {
|
||||||
|
let left_value = if left_is_constant {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
thread.get_constant(left).as_byte().unwrap()
|
||||||
|
} else {
|
||||||
|
unsafe { thread.get_constant(left).as_byte().unwrap_unchecked() }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
thread.get_byte_register(left)
|
||||||
|
};
|
||||||
|
let right_value = if right_is_constant {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
thread.get_constant(right).as_byte().unwrap()
|
||||||
|
} else {
|
||||||
|
unsafe { thread.get_constant(right).as_byte().unwrap_unchecked() }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
thread.get_byte_register(right)
|
||||||
|
};
|
||||||
|
let result = left_value < right_value;
|
||||||
|
|
||||||
|
if result == comparator {
|
||||||
|
*ip += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(TypeCode::CHARACTER, TypeCode::CHARACTER) => {
|
||||||
|
let left_value = if left_is_constant {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
thread.get_constant(left).as_character().unwrap()
|
||||||
|
} else {
|
||||||
|
unsafe { thread.get_constant(left).as_character().unwrap_unchecked() }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
thread.get_character_register(left)
|
||||||
|
};
|
||||||
|
let right_value = if right_is_constant {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
thread.get_constant(right).as_character().unwrap()
|
||||||
|
} else {
|
||||||
|
unsafe { thread.get_constant(right).as_character().unwrap_unchecked() }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
thread.get_character_register(right)
|
||||||
|
};
|
||||||
|
let result = left_value < right_value;
|
||||||
|
|
||||||
|
if result == comparator {
|
||||||
|
*ip += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(TypeCode::FLOAT, TypeCode::FLOAT) => {
|
||||||
|
let left_value = if left_is_constant {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
thread.get_constant(left).as_float().unwrap()
|
||||||
|
} else {
|
||||||
|
unsafe { thread.get_constant(left).as_float().unwrap_unchecked() }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
thread.get_float_register(left)
|
||||||
|
};
|
||||||
|
let right_value = if right_is_constant {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
thread.get_constant(right).as_float().unwrap()
|
||||||
|
} else {
|
||||||
|
unsafe { thread.get_constant(right).as_float().unwrap_unchecked() }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
thread.get_float_register(right)
|
||||||
|
};
|
||||||
|
let result = left_value < right_value;
|
||||||
|
|
||||||
|
if result == comparator {
|
||||||
|
*ip += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(TypeCode::INTEGER, TypeCode::INTEGER) => {
|
||||||
|
less_integers(ip, instruction, thread, pointer_cache)
|
||||||
|
}
|
||||||
|
(TypeCode::STRING, TypeCode::STRING) => {
|
||||||
|
let left_value = if left_is_constant {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
thread.get_constant(left).as_string().unwrap().clone()
|
||||||
|
} else {
|
||||||
|
unsafe {
|
||||||
|
thread
|
||||||
|
.get_constant(left)
|
||||||
|
.as_string()
|
||||||
|
.unwrap_unchecked()
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
thread.get_string_register(left).clone()
|
||||||
|
};
|
||||||
|
let right_value = if right_is_constant {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
thread.get_constant(right).as_string().unwrap().clone()
|
||||||
|
} else {
|
||||||
|
unsafe {
|
||||||
|
thread
|
||||||
|
.get_constant(right)
|
||||||
|
.as_string()
|
||||||
|
.unwrap_unchecked()
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
thread.get_string_register(right).clone()
|
||||||
|
};
|
||||||
|
let result = left_value < right_value;
|
||||||
|
|
||||||
|
if result == comparator {
|
||||||
|
*ip += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn less_integers(
|
||||||
|
ip: &mut usize,
|
||||||
|
instruction: InstructionFields,
|
||||||
|
thread: &mut Thread,
|
||||||
|
pointer_cache: &mut PointerCache,
|
||||||
|
) {
|
||||||
|
if pointer_cache.integer_left.is_null() {
|
||||||
|
trace!("LESS: Run and cache pointers");
|
||||||
|
|
||||||
|
let left = instruction.b_field as usize;
|
||||||
|
let left_is_constant = instruction.b_is_constant;
|
||||||
|
let right = instruction.c_field as usize;
|
||||||
|
let right_is_constant = instruction.c_is_constant;
|
||||||
|
let left_pointer = if left_is_constant {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
thread.get_constant(left).as_integer().unwrap()
|
||||||
|
} else {
|
||||||
|
unsafe { thread.get_constant(left).as_integer().unwrap_unchecked() }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
thread.get_integer_register(left)
|
||||||
|
} as *const i64;
|
||||||
|
let right_pointer = if right_is_constant {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
thread.get_constant(right).as_integer().unwrap()
|
||||||
|
} else {
|
||||||
|
unsafe { thread.get_constant(right).as_integer().unwrap_unchecked() }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
thread.get_integer_register(right)
|
||||||
|
} as *const i64;
|
||||||
|
|
||||||
|
pointer_cache.integer_left = left_pointer;
|
||||||
|
pointer_cache.integer_right = right_pointer;
|
||||||
|
|
||||||
|
less_integer_pointers(ip, left_pointer, right_pointer, instruction.d_field);
|
||||||
|
} else {
|
||||||
|
trace!("LESS: Use cached pointers");
|
||||||
|
|
||||||
|
less_integer_pointers(
|
||||||
|
ip,
|
||||||
|
pointer_cache.integer_left,
|
||||||
|
pointer_cache.integer_right,
|
||||||
|
instruction.d_field,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn less_integer_pointers(
|
||||||
|
ip: *mut usize,
|
||||||
|
left: *const i64,
|
||||||
|
right: *const i64,
|
||||||
|
comparator: bool,
|
||||||
|
) {
|
||||||
|
assert!(ip.is_aligned());
|
||||||
|
assert!(left.is_aligned());
|
||||||
|
assert!(right.is_aligned());
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let is_less_than = *left < *right;
|
||||||
|
|
||||||
|
if is_less_than == comparator {
|
||||||
|
*ip += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -1,37 +1,30 @@
|
|||||||
use std::{
|
use std::{
|
||||||
fmt::{self, Debug, Display, Formatter},
|
fmt::{self, Debug, Display, Formatter},
|
||||||
sync::Arc,
|
ptr,
|
||||||
|
rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use smallvec::{SmallVec, smallvec};
|
use smallvec::{SmallVec, smallvec};
|
||||||
|
|
||||||
use crate::{AbstractList, Chunk, DustString, Function};
|
use crate::{AbstractList, Chunk, DustString, Function};
|
||||||
|
|
||||||
use super::action::ActionSequence;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct CallFrame {
|
pub struct CallFrame {
|
||||||
pub chunk: Arc<Chunk>,
|
pub chunk: Rc<Chunk>,
|
||||||
pub ip: usize,
|
pub ip: usize,
|
||||||
pub return_register: u16,
|
pub return_register: u16,
|
||||||
pub registers: RegisterTable,
|
pub registers: RegisterTable,
|
||||||
pub action_sequence: ActionSequence,
|
|
||||||
pub pointer_caches: Vec<PointerCache>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CallFrame {
|
impl CallFrame {
|
||||||
pub fn new(chunk: Arc<Chunk>, return_register: u16) -> Self {
|
pub fn new(chunk: Rc<Chunk>, return_register: u16) -> Self {
|
||||||
let registers = RegisterTable::new();
|
let registers = RegisterTable::new();
|
||||||
let action_sequence = ActionSequence::new(&chunk.instructions);
|
|
||||||
let optimization_data = vec![PointerCache::default(); chunk.instructions.len()];
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
chunk,
|
chunk,
|
||||||
ip: 0,
|
ip: 0,
|
||||||
return_register,
|
return_register,
|
||||||
registers,
|
registers,
|
||||||
action_sequence,
|
|
||||||
pointer_caches: optimization_data,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -65,14 +58,14 @@ pub struct RegisterTable {
|
|||||||
impl RegisterTable {
|
impl RegisterTable {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
booleans: smallvec![Register::Empty; 64],
|
booleans: smallvec![Register::default(); 64],
|
||||||
bytes: smallvec![Register::Empty; 64],
|
bytes: smallvec![Register::default(); 64],
|
||||||
characters: smallvec![Register::Empty; 64],
|
characters: smallvec![Register::default(); 64],
|
||||||
floats: smallvec![Register::Empty; 64],
|
floats: smallvec![Register::default(); 64],
|
||||||
integers: smallvec![Register::Empty; 64],
|
integers: smallvec![Register::default(); 64],
|
||||||
strings: smallvec![Register::Empty; 64],
|
strings: smallvec![Register::default(); 64],
|
||||||
lists: smallvec![Register::Empty; 64],
|
lists: smallvec![Register::default(); 64],
|
||||||
functions: smallvec![Register::Empty; 64],
|
functions: smallvec![Register::default(); 64],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -84,27 +77,31 @@ impl Default for RegisterTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum Register<T> {
|
pub enum Register<T: Default> {
|
||||||
Empty,
|
|
||||||
Value(T),
|
Value(T),
|
||||||
Closed(T),
|
Closed(T),
|
||||||
Pointer(Pointer),
|
Pointer(Pointer),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Register<T> {
|
impl<T: Default> Register<T> {
|
||||||
pub fn contained_value_mut(&mut self) -> Option<&mut T> {
|
pub fn contained_value_mut(&mut self) -> Option<&mut T> {
|
||||||
match self {
|
match self {
|
||||||
Self::Value(value) => Some(value),
|
Self::Value(value) => Some(value),
|
||||||
Self::Closed(value) => Some(value),
|
Self::Closed(value) => Some(value),
|
||||||
_ => None,
|
Self::Pointer(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Display> Display for Register<T> {
|
impl<T: Default> Default for Register<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Value(T::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Default + Display> Display for Register<T> {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Empty => write!(f, "empty"),
|
|
||||||
Self::Closed(value) => write!(f, "Closed({value})"),
|
Self::Closed(value) => write!(f, "Closed({value})"),
|
||||||
Self::Value(value) => write!(f, "Value({value})"),
|
Self::Value(value) => write!(f, "Value({value})"),
|
||||||
Self::Pointer(pointer) => write!(f, "Pointer({pointer:?})"),
|
Self::Pointer(pointer) => write!(f, "Pointer({pointer:?})"),
|
||||||
@ -147,12 +144,25 @@ impl Display for Pointer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum PointerCache {
|
pub struct PointerCache {
|
||||||
#[default]
|
pub integer_mut: *mut i64,
|
||||||
Empty,
|
pub integer_left: *const i64,
|
||||||
Integers {
|
pub integer_right: *const i64,
|
||||||
left: *const i64,
|
}
|
||||||
right: *const i64,
|
|
||||||
},
|
impl PointerCache {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
integer_mut: ptr::null_mut(),
|
||||||
|
integer_left: ptr::null(),
|
||||||
|
integer_right: ptr::null(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PointerCache {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ mod action;
|
|||||||
mod call_frame;
|
mod call_frame;
|
||||||
mod thread;
|
mod thread;
|
||||||
|
|
||||||
use std::{sync::Arc, thread::Builder};
|
use std::{rc::Rc, thread::Builder};
|
||||||
|
|
||||||
pub use action::Action;
|
pub use action::Action;
|
||||||
pub use call_frame::{CallFrame, Pointer, Register, RegisterTable};
|
pub use call_frame::{CallFrame, Pointer, Register, RegisterTable};
|
||||||
@ -40,14 +40,14 @@ impl Vm {
|
|||||||
.map(|name| name.to_string())
|
.map(|name| name.to_string())
|
||||||
.unwrap_or_else(|| "anonymous".to_string());
|
.unwrap_or_else(|| "anonymous".to_string());
|
||||||
let (tx, rx) = bounded(1);
|
let (tx, rx) = bounded(1);
|
||||||
let main_chunk = Arc::new(self.main_chunk);
|
|
||||||
|
|
||||||
Builder::new()
|
Builder::new()
|
||||||
.name(thread_name)
|
.name(thread_name)
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
|
let main_chunk = Rc::new(self.main_chunk);
|
||||||
let main_thread = Thread::new(main_chunk);
|
let main_thread = Thread::new(main_chunk);
|
||||||
let value_option = main_thread.run();
|
let return_value = main_thread.run();
|
||||||
let _ = tx.send(value_option);
|
let _ = tx.send(return_value);
|
||||||
})
|
})
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.join()
|
.join()
|
||||||
|
@ -1,22 +1,26 @@
|
|||||||
use std::{sync::Arc, thread::JoinHandle};
|
use std::{rc::Rc, thread::JoinHandle};
|
||||||
|
|
||||||
use tracing::{info, trace};
|
use tracing::{info, trace};
|
||||||
|
|
||||||
use crate::{AbstractList, Chunk, ConcreteValue, DustString, Span, Value, vm::CallFrame};
|
use crate::{
|
||||||
|
AbstractList, Chunk, ConcreteValue, DustString, Span, Value,
|
||||||
|
instruction::InstructionFields,
|
||||||
|
vm::{CallFrame, action::ActionSequence},
|
||||||
|
};
|
||||||
|
|
||||||
use super::call_frame::{Pointer, PointerCache, Register};
|
use super::call_frame::{Pointer, Register};
|
||||||
|
|
||||||
pub struct Thread {
|
pub struct Thread {
|
||||||
chunk: Arc<Chunk>,
|
chunk: Rc<Chunk>,
|
||||||
call_stack: Vec<CallFrame>,
|
call_stack: Vec<CallFrame>,
|
||||||
pub return_value: Option<Option<Value>>,
|
pub return_value: Option<Value>,
|
||||||
_spawned_threads: Vec<JoinHandle<()>>,
|
_spawned_threads: Vec<JoinHandle<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Thread {
|
impl Thread {
|
||||||
pub fn new(chunk: Arc<Chunk>) -> Self {
|
pub fn new(chunk: Rc<Chunk>) -> Self {
|
||||||
let mut call_stack = Vec::with_capacity(chunk.prototypes.len() + 1);
|
let mut call_stack = Vec::with_capacity(chunk.prototypes.len() + 1);
|
||||||
let main_call = CallFrame::new(Arc::clone(&chunk), 0);
|
let main_call = CallFrame::new(Rc::clone(&chunk), 0);
|
||||||
|
|
||||||
call_stack.push(main_call);
|
call_stack.push(main_call);
|
||||||
|
|
||||||
@ -36,28 +40,15 @@ impl Thread {
|
|||||||
.clone()
|
.clone()
|
||||||
.unwrap_or_else(|| DustString::from("anonymous"))
|
.unwrap_or_else(|| DustString::from("anonymous"))
|
||||||
);
|
);
|
||||||
trace!("Thread actions: {}", self.current_frame().action_sequence);
|
|
||||||
|
|
||||||
loop {
|
let actions =
|
||||||
if let Some(return_value_option) = self.return_value {
|
ActionSequence::new(self.chunk.instructions.iter().map(InstructionFields::from));
|
||||||
return return_value_option;
|
|
||||||
}
|
|
||||||
|
|
||||||
let current_frame = self.current_frame_mut();
|
trace!("Thread actions: {}", actions);
|
||||||
let ip = {
|
|
||||||
let ip = current_frame.ip;
|
|
||||||
current_frame.ip += 1;
|
|
||||||
|
|
||||||
ip
|
actions.run(&mut self);
|
||||||
};
|
|
||||||
let current_action = if cfg!(debug_assertions) {
|
|
||||||
current_frame.action_sequence.actions.get_mut(ip).unwrap()
|
|
||||||
} else {
|
|
||||||
unsafe { current_frame.action_sequence.actions.get_unchecked_mut(ip) }
|
|
||||||
};
|
|
||||||
|
|
||||||
(current_action.logic)(current_action.instruction, &mut self);
|
self.return_value
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn current_position(&self) -> Span {
|
pub fn current_position(&self) -> Span {
|
||||||
@ -82,29 +73,6 @@ impl Thread {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_current_pointer_cache(&mut self, new_cache: PointerCache) {
|
|
||||||
let ip = self.current_frame().ip;
|
|
||||||
|
|
||||||
let old_cache = if cfg!(debug_assertions) {
|
|
||||||
self.call_stack
|
|
||||||
.last_mut()
|
|
||||||
.unwrap()
|
|
||||||
.pointer_caches
|
|
||||||
.get_mut(ip - 1)
|
|
||||||
.unwrap()
|
|
||||||
} else {
|
|
||||||
unsafe {
|
|
||||||
self.call_stack
|
|
||||||
.last_mut()
|
|
||||||
.unwrap_unchecked()
|
|
||||||
.pointer_caches
|
|
||||||
.get_unchecked_mut(ip - 1)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
*old_cache = new_cache;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_value_from_pointer(&self, pointer: &Pointer) -> ConcreteValue {
|
pub fn get_value_from_pointer(&self, pointer: &Pointer) -> ConcreteValue {
|
||||||
match pointer {
|
match pointer {
|
||||||
Pointer::RegisterBoolean(register_index) => {
|
Pointer::RegisterBoolean(register_index) => {
|
||||||
@ -204,7 +172,6 @@ impl Thread {
|
|||||||
Register::Value(value) => value,
|
Register::Value(value) => value,
|
||||||
Register::Closed(value) => value,
|
Register::Closed(value) => value,
|
||||||
Register::Pointer(pointer) => self.get_pointer_to_boolean(pointer),
|
Register::Pointer(pointer) => self.get_pointer_to_boolean(pointer),
|
||||||
Register::Empty => panic!("Attempted to get value from empty register"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,7 +266,6 @@ impl Thread {
|
|||||||
Register::Value(value) => value,
|
Register::Value(value) => value,
|
||||||
Register::Closed(value) => value,
|
Register::Closed(value) => value,
|
||||||
Register::Pointer(pointer) => self.get_pointer_to_byte(pointer),
|
Register::Pointer(pointer) => self.get_pointer_to_byte(pointer),
|
||||||
Register::Empty => panic!("Attempted to get value from empty register"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -394,7 +360,6 @@ impl Thread {
|
|||||||
Register::Value(value) => value,
|
Register::Value(value) => value,
|
||||||
Register::Closed(value) => value,
|
Register::Closed(value) => value,
|
||||||
Register::Pointer(pointer) => self.get_pointer_to_character(pointer),
|
Register::Pointer(pointer) => self.get_pointer_to_character(pointer),
|
||||||
Register::Empty => panic!("Attempted to get value from empty register"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -494,7 +459,6 @@ impl Thread {
|
|||||||
Register::Value(value) => value,
|
Register::Value(value) => value,
|
||||||
Register::Closed(value) => value,
|
Register::Closed(value) => value,
|
||||||
Register::Pointer(pointer) => self.get_pointer_to_float(pointer),
|
Register::Pointer(pointer) => self.get_pointer_to_float(pointer),
|
||||||
Register::Empty => panic!("Attempted to get value from empty register"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -592,11 +556,10 @@ impl Thread {
|
|||||||
Register::Value(value) => value,
|
Register::Value(value) => value,
|
||||||
Register::Closed(value) => value,
|
Register::Closed(value) => value,
|
||||||
Register::Pointer(pointer) => self.get_pointer_to_integer(pointer),
|
Register::Pointer(pointer) => self.get_pointer_to_integer(pointer),
|
||||||
Register::Empty => panic!("Attempted to get value from empty register"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_integer_register_mut(&mut self, register_index: usize) -> &mut i64 {
|
pub fn get_integer_register_mut_allow_empty(&mut self, register_index: usize) -> &mut i64 {
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
self.call_stack
|
self.call_stack
|
||||||
.last_mut()
|
.last_mut()
|
||||||
@ -715,7 +678,6 @@ impl Thread {
|
|||||||
Register::Value(value) => value,
|
Register::Value(value) => value,
|
||||||
Register::Closed(value) => value,
|
Register::Closed(value) => value,
|
||||||
Register::Pointer(pointer) => self.get_pointer_to_string(pointer),
|
Register::Pointer(pointer) => self.get_pointer_to_string(pointer),
|
||||||
Register::Empty => panic!("Attempted to get value from empty register"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -782,7 +744,7 @@ impl Thread {
|
|||||||
pub fn close_string_register(&mut self, register_index: usize) {
|
pub fn close_string_register(&mut self, register_index: usize) {
|
||||||
let current_frame = self.current_frame_mut();
|
let current_frame = self.current_frame_mut();
|
||||||
|
|
||||||
current_frame.registers.strings.push(Register::Empty);
|
current_frame.registers.strings.push(Register::default());
|
||||||
|
|
||||||
let old_register = current_frame.registers.strings.swap_remove(register_index);
|
let old_register = current_frame.registers.strings.swap_remove(register_index);
|
||||||
|
|
||||||
@ -822,7 +784,6 @@ impl Thread {
|
|||||||
Register::Value(value) => value,
|
Register::Value(value) => value,
|
||||||
Register::Closed(value) => value,
|
Register::Closed(value) => value,
|
||||||
Register::Pointer(pointer) => self.get_pointer_to_list(pointer),
|
Register::Pointer(pointer) => self.get_pointer_to_list(pointer),
|
||||||
Register::Empty => panic!("Attempted to get value from empty register"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -886,7 +847,7 @@ impl Thread {
|
|||||||
pub fn close_list_register(&mut self, register_index: usize) {
|
pub fn close_list_register(&mut self, register_index: usize) {
|
||||||
let current_frame = self.current_frame_mut();
|
let current_frame = self.current_frame_mut();
|
||||||
|
|
||||||
current_frame.registers.lists.push(Register::Empty);
|
current_frame.registers.lists.push(Register::default());
|
||||||
|
|
||||||
let old_register = current_frame.registers.lists.swap_remove(register_index);
|
let old_register = current_frame.registers.lists.swap_remove(register_index);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user