Compare commits

..

2 Commits

Author SHA1 Message Date
_ 6db6dc3725 📝 doc: failed optimization 2023-10-04 03:05:21 -05:00
_ 465ee55183 🚧 wip: interning Tables
Performs better but the tests are all busted, won't even compile.
The instruction decoding change messed that up too. Might roll that back.
2023-10-04 02:42:55 -05:00
4 changed files with 94 additions and 77 deletions

View File

@ -5,6 +5,7 @@ use crate::{
string_interner::Interner, string_interner::Interner,
value::{ value::{
BogusClosure, BogusClosure,
Table,
Value, Value,
}, },
}; };
@ -59,6 +60,7 @@ pub struct State {
current_instructions: Rc <[u32]>, current_instructions: Rc <[u32]>,
pub upvalues: Vec <Value>, pub upvalues: Vec <Value>,
pub si: Interner, pub si: Interner,
tables: Vec <crate::value::Table>,
} }
fn lw_io_write (l: &mut State, num_args: usize) -> usize { fn lw_io_write (l: &mut State, num_args: usize) -> usize {
@ -114,7 +116,7 @@ fn lw_table_concat (l: &mut State, num_args: usize) -> usize {
assert_eq! (num_args, 2); assert_eq! (num_args, 2);
let s = { let s = {
let t = l.reg (0).as_table ().unwrap ().borrow (); let t = &l.tables [l.reg (0).as_table ().unwrap ()];
let joiner = l.reg (1).as_str ().unwrap (); let joiner = l.reg (1).as_str ().unwrap ();
let mut s = String::new (); let mut s = String::new ();
@ -135,9 +137,9 @@ fn lw_table_concat (l: &mut State, num_args: usize) -> usize {
fn lw_table_pack (l: &mut State, num_args: usize) -> usize { fn lw_table_pack (l: &mut State, num_args: usize) -> usize {
let mut v = vec! []; let mut v = vec! [];
for i in 0..num_args { for i in 0..num_args {
v.push (l.reg (u8::try_from (i).unwrap ()).clone ()); v.push ((Value::from (i + 1), l.reg (u8::try_from (i).unwrap ()).clone ()));
} }
*l.reg_mut (0) = Value::from_iter (v.into_iter ()); *l.reg_mut (0) = l.intern_table (Table::from_iter (v));
1 1
} }
@ -197,17 +199,17 @@ impl State {
current_instructions, current_instructions,
upvalues, upvalues,
si: Default::default (), si: Default::default (),
tables: Default::default (),
} }
} }
pub fn new_with_args <I: Iterator <Item = String>> (chunk: Chunk, mut si: Interner, args: I) -> Self { pub fn new_with_args <I: Iterator <Item = String>> (chunk: Chunk, si: Interner, args: I) -> Self {
let upvalues = Self::upvalues_from_args (&mut si, args);
let current_instructions = match chunk.blocks.get (0) { let current_instructions = match chunk.blocks.get (0) {
Some (x) => Rc::clone (&x.instructions), Some (x) => Rc::clone (&x.instructions),
None => Rc::from ([]), None => Rc::from ([]),
}; };
Self { let mut that = Self {
// TODO: Stack is actually supposed to grow to a limit of // TODO: Stack is actually supposed to grow to a limit of
// idk 10,000. I thought it was fixed at 256. // idk 10,000. I thought it was fixed at 256.
registers: vec! [Value::Nil; 256], registers: vec! [Value::Nil; 256],
@ -217,9 +219,13 @@ impl State {
debug_print: false, debug_print: false,
chunk, chunk,
current_instructions, current_instructions,
upvalues, upvalues: Default::default (),
si, si,
} tables: Default::default (),
};
that.upvalues = that.upvalues_from_args (args);
that
} }
pub fn at_breakpoint (&self, bp: &Breakpoint) -> bool { pub fn at_breakpoint (&self, bp: &Breakpoint) -> bool {
@ -227,40 +233,46 @@ impl State {
frame.block_idx == bp.block_idx && frame.program_counter == bp.program_counter frame.block_idx == bp.block_idx && frame.program_counter == bp.program_counter
} }
pub fn upvalues_from_args <I: Iterator <Item = String>> (si: &mut Interner, args: I) -> Vec <Value> fn intern_table (&mut self, table: Table) -> Value {
let t = self.tables.len ();
self.tables.push (table);
Value::Table (t)
}
pub fn upvalues_from_args <I: Iterator <Item = String>> (&mut self, args: I) -> Vec <Value>
{ {
let arg = args.map (|s| si.intern (&s)).enumerate (); let arg: Vec <_> = args.map (|s| self.si.intern (&s)).enumerate ().collect ();
let arg = Value::from_iter (arg.map (|(i, v)| (Value::from (i), Value::String (v)))); let arg = self.intern_table (Table::from_iter (arg.into_iter ().map (|(i, v)| (Value::from (i), Value::String (v)))));
let io: Vec <_> = [ let io: Vec <_> = [
("write", Value::RsFunc (lw_io_write)), ("write", Value::RsFunc (lw_io_write)),
].into_iter ().map (|(k, v)| (si.intern (k), v)).collect (); ].into_iter ().map (|(k, v)| (Value::String (self.si.intern (k)), v)).collect ();
let math: Vec <_> = [ let math: Vec <_> = [
("sqrt", Value::RsFunc (lw_sqrt)), ("sqrt", Value::RsFunc (lw_sqrt)),
].into_iter ().map (|(k, v)| (si.intern (k), v)).collect (); ].into_iter ().map (|(k, v)| (Value::String (self.si.intern (k)), v)).collect ();
let string: Vec <_> = [ let string: Vec <_> = [
("format", Value::RsFunc (lw_string_format)), ("format", Value::RsFunc (lw_string_format)),
].into_iter ().map (|(k, v)| (si.intern (k), v)).collect (); ].into_iter ().map (|(k, v)| (Value::String (self.si.intern (k)), v)).collect ();
let table: Vec <_> = [ let table: Vec <_> = [
("concat", Value::RsFunc (lw_table_concat)), ("concat", Value::RsFunc (lw_table_concat)),
("pack", Value::RsFunc (lw_table_pack)), ("pack", Value::RsFunc (lw_table_pack)),
].into_iter ().map (|(k, v)| (si.intern (k), v)).collect (); ].into_iter ().map (|(k, v)| (Value::String (self.si.intern (k)), v)).collect ();
let env = [ let env: Vec <_> = [
("arg", arg), ("arg", arg),
("io", Value::from_iter (io.into_iter ())), ("io", self.intern_table (Table::from_iter (io))),
("math", Value::from_iter (math.into_iter ())), ("math", self.intern_table (Table::from_iter (math))),
("print", Value::RsFunc (lw_print)), ("print", Value::RsFunc (lw_print)),
("string", Value::from_iter (string.into_iter ())), ("string", self.intern_table (Table::from_iter (string))),
("table", Value::from_iter (table.into_iter ())), ("table", self.intern_table (Table::from_iter (table))),
("tonumber", Value::RsFunc (lw_tonumber)), ("tonumber", Value::RsFunc (lw_tonumber)),
].into_iter ().map (|(k, v)| (si.intern (k), v)); ].into_iter ().map (|(k, v)| (Value::String (self.si.intern (k)), v)).collect ();
vec! [ vec! [
Value::from_iter (env.into_iter ()), self.intern_table (Table::from_iter (env)),
] ]
} }
@ -413,7 +425,7 @@ impl State {
let val = match &self.registers [self.stack_top.register_offset + usize::from (b)] { let val = match &self.registers [self.stack_top.register_offset + usize::from (b)] {
Value::Nil => panic! ("R[B] must not be nil"), Value::Nil => panic! ("R[B] must not be nil"),
Value::Table (t) => t.borrow ().get_str (*key).clone (), Value::Table (t) => self.tables [*t].get_str (*key).clone (),
_ => panic! ("R[B] must be a table"), _ => panic! ("R[B] must be a table"),
}; };
@ -422,13 +434,13 @@ impl State {
fn op_get_table (&mut self, a: u8, b: u8, c: u8) { fn op_get_table (&mut self, a: u8, b: u8, c: u8) {
let t = match self.reg (b) { let t = match self.reg (b) {
Value::Table (t) => t, Value::Table (t) => *t,
_ => panic! ("R[B] must be a table"), _ => panic! ("R[B] must be a table"),
}; };
let key = self.reg (c); let key = self.reg (c);
let val = t.borrow ().get (key.clone ()).clone (); let val = self.tables [t].get (key.clone ()).clone ();
*self.reg_mut (a) = val; *self.reg_mut (a) = val;
} }
@ -482,8 +494,9 @@ impl State {
self.reg_mut (c) self.reg_mut (c)
}.clone (); }.clone ();
let mut dst = self.reg (a).as_table () let t = self.reg (a).as_table ()
.expect ("SetField only works on tables").borrow_mut (); .expect ("SetField only works on tables");
let dst = &mut self.tables [t];
dst.insert_str (key, value); dst.insert_str (key, value);
} }
@ -666,7 +679,7 @@ impl State {
self.upvalues.get (b).unwrap ().clone () self.upvalues.get (b).unwrap ().clone ()
}; };
let table = value.as_table ().expect ("GetTabUp only works on tables").borrow (); let table = &self.tables [value.as_table ().expect ("GetTabUp only works on tables")];
let key = match self.constants ().get (c).unwrap () { let key = match self.constants ().get (c).unwrap () {
Value::String (s) => *s, Value::String (s) => *s,
@ -679,7 +692,7 @@ impl State {
let key = i64::try_from (i.c ()).unwrap (); let key = i64::try_from (i.c ()).unwrap ();
let value = { let value = {
let table = self.reg (i.b ()).as_table ().expect ("GetI only works on tables").borrow (); let table = &self.tables [self.reg (i.b ()).as_table ().expect ("GetI only works on tables")];
table.get_int (key).clone () table.get_int (key).clone ()
}; };
@ -713,7 +726,7 @@ impl State {
Value::Nil => Err (make_step_error ("attempt to get length of a nil value"))?, Value::Nil => Err (make_step_error ("attempt to get length of a nil value"))?,
Value::RsFunc (_) => Err (make_step_error ("attempt to get length of a function value"))?, Value::RsFunc (_) => Err (make_step_error ("attempt to get length of a function value"))?,
Value::String (s) => self.si.get (*s).len ().into (), Value::String (s) => self.si.get (*s).len ().into (),
Value::Table (t) => t.borrow ().length ().into (), Value::Table (t) => self.tables [*t].length ().into (),
}; };
*self.reg_mut (i.a ()) = len; *self.reg_mut (i.a ()) = len;
@ -784,7 +797,9 @@ impl State {
*self.reg_mut (i.a ()) = x; *self.reg_mut (i.a ()) = x;
}, },
0x13 => { 0x13 => {
*self.reg_mut (i.a ()) = Value::Table (Default::default ()); let t = self.tables.len ();
self.tables.push (Default::default ());
*self.reg_mut (i.a ()) = Value::Table (t);
}, },
0x33 => { 0x33 => {
*self.reg_mut (i.a ()) = Value::Boolean (! self.reg (i.b ()).is_truthy()); *self.reg_mut (i.a ()) = Value::Boolean (! self.reg (i.b ()).is_truthy());
@ -879,13 +894,14 @@ impl State {
} }
.clone (); .clone ();
let mut dst = self.reg_mut (i.a ()).as_table ().expect ("SetI only works on tables").borrow_mut (); let t = self.reg_mut (i.a ()).as_table ().expect ("SetI only works on tables");
let dst = &mut self.tables [t];
dst.insert_int (i64::from (i.b ()), value); dst.insert_int (i64::from (i.b ()), value);
}, },
0x4e => { 0x4e => {
let a = i.a (); let a = i.a ();
let b = i.b (); let b = i.b () as usize;
if b == 0 { if b == 0 {
panic! ("SetList with b == 0 not implemented"); panic! ("SetList with b == 0 not implemented");
@ -894,11 +910,12 @@ impl State {
panic! ("SetList with k = true not implemented"); panic! ("SetList with k = true not implemented");
} }
let mut dst = self.reg (a).as_table ().expect ("SetList only works on tables").borrow_mut (); let t = self.reg (a).as_table ().expect ("SetList only works on tables");
let dst = &mut self.tables [t];
for j in 1..=b { for j in 1..=b {
let src = self.reg (a + j); let src = self.registers [self.stack_top.register_offset + a as usize + j].clone ();
dst.insert_int (i64::from (i.c () + j), src.clone ()); dst.insert_int (i64::try_from (i.c () as usize + j).unwrap (), src);
} }
}, },
0x0f => { 0x0f => {
@ -915,8 +932,8 @@ impl State {
.clone (); .clone ();
let key = self.constants ().get (b).unwrap ().as_str ().expect ("SetTabUp K[B] must be a string"); let key = self.constants ().get (b).unwrap ().as_str ().expect ("SetTabUp K[B] must be a string");
let table = self.upvalues.get_mut (a).unwrap ().as_table ().unwrap (); let t = self.upvalues.get_mut (a).unwrap ().as_table ().unwrap ();
table.borrow_mut ().insert_str (key, value); self.tables [t].insert_str (key, value);
}, },
0x23 => { 0x23 => {
if self.op_sub (i.a (), i.b (), i.c ()) { if self.op_sub (i.a (), i.b (), i.c ()) {

View File

@ -30,7 +30,7 @@ fn calculate_hash<T: Hash>(t: &T) -> u64 {
/// and returns the output /// and returns the output
fn run_chunk (vm: &mut State, args: &[&str], chunk: Chunk) -> Vec <Value> { fn run_chunk (vm: &mut State, args: &[&str], chunk: Chunk) -> Vec <Value> {
vm.upvalues = State::upvalues_from_args(&mut vm.si, args.into_iter ().map (|s| s.to_string ())); vm.upvalues = vm.upvalues_from_args(args.into_iter ().map (|s| s.to_string ()));
vm.set_chunk (chunk); vm.set_chunk (chunk);
vm.execute ().unwrap () vm.execute ().unwrap ()
} }

View File

@ -33,7 +33,7 @@ pub enum Value {
Integer (i64), Integer (i64),
RsFunc (fn (&mut crate::state::State, usize) -> usize), RsFunc (fn (&mut crate::state::State, usize) -> usize),
String (InternedString), String (InternedString),
Table (Rc <RefCell <Table>>), Table (usize),
// These are all bogus, I haven't figured out how to implement // These are all bogus, I haven't figured out how to implement
// closures yet // closures yet
@ -53,7 +53,7 @@ impl fmt::Debug for Value {
Value::Integer (x) => write! (f, "{}", x), Value::Integer (x) => write! (f, "{}", x),
Value::RsFunc (x) => write! (f, "function: {:?}", x), Value::RsFunc (x) => write! (f, "function: {:?}", x),
Value::String (s) => write! (f, "unimplemented Debug",), Value::String (s) => write! (f, "unimplemented Debug",),
Value::Table (t) => write! (f, "{:?}", t.borrow ()), Value::Table (t) => write! (f, "table"),
Value::BogusClosure (x) => write! (f, "{:?}", x.borrow ()), Value::BogusClosure (x) => write! (f, "{:?}", x.borrow ()),
} }
@ -76,7 +76,7 @@ impl fmt::Display for Value {
Value::Integer (x) => write! (f, "{}", x), Value::Integer (x) => write! (f, "{}", x),
Value::RsFunc (x) => write! (f, "function: {:?}", x), Value::RsFunc (x) => write! (f, "function: {:?}", x),
Value::String (s) => write! (f, "unimplemented Display"), Value::String (s) => write! (f, "unimplemented Display"),
Value::Table (t) => write! (f, "table: {:?}", std::rc::Rc::as_ptr (t)), Value::Table (t) => write! (f, "table"),
Value::BogusClosure (x) => write! (f, "BogusClosure: {:?}", std::rc::Rc::as_ptr (x)), Value::BogusClosure (x) => write! (f, "BogusClosure: {:?}", std::rc::Rc::as_ptr (x)),
} }
@ -125,31 +125,6 @@ impl From <InternedString> for Value {
} }
} }
impl From <Table> for Value {
fn from (x: Table) -> Self {
Self::Table (Rc::new (RefCell::new (x)))
}
}
impl FromIterator <(Value, Value)> for Value {
fn from_iter <I: IntoIterator <Item=(Value, Value)>> (i: I) -> Self {
let table = Table::from_iter (i);
Self::from (table)
}
}
impl FromIterator <(InternedString, Value)> for Value {
fn from_iter <I: IntoIterator <Item=(InternedString, Value)>> (i: I) -> Self {
Self::from_iter (i.into_iter ().map (|(s, v)| (Value::String (s), v)))
}
}
impl FromIterator <Value> for Value {
fn from_iter <I: IntoIterator <Item=Value>> (i: I) -> Self {
Self::from_iter ((1..).zip (i.into_iter ()).map (|(i, v)| (Value::from (i), v)))
}
}
impl Eq for Value {} impl Eq for Value {}
impl std::hash::Hash for Value { impl std::hash::Hash for Value {
@ -160,14 +135,31 @@ impl std::hash::Hash for Value {
match self { match self {
// TODO: Weaken to a Lua error // TODO: Weaken to a Lua error
Self::Nil => panic! ("can't hash a nil value"), Self::Nil => panic! ("can't hash a nil value"),
Self::Boolean (x) => x.hash (state), Self::Boolean (x) => {
Self::Float (x) => x.to_ne_bytes ().hash (state), [0x01].hash (state);
Self::Integer (x) => x.hash (state), x.hash (state)
},
Self::RsFunc (x) => x.hash (state), Self::Float (x) => {
[0x02].hash (state);
x.to_ne_bytes ().hash (state)
},
Self::Integer (x) => {
[0x03].hash (state);
x.hash (state)
},
Self::RsFunc (x) => {
[0x04].hash (state);
x.hash (state)
},
// TODO: Implement string interning so we don't hash the whole string here // TODO: Implement string interning so we don't hash the whole string here
Self::String (x) => x.hash (state), Self::String (x) => {
Self::Table (x) => Rc::as_ptr (&x).hash (state), [0x05].hash (state);
x.hash (state)
},
Self::Table (x) => {
[0x06].hash (state);
x.hash (state)
},
Self::BogusClosure (_) => panic! ("can't hash Bogus values"), Self::BogusClosure (_) => panic! ("can't hash Bogus values"),
} }
@ -215,9 +207,9 @@ impl Value {
} }
} }
pub fn as_table (&self) -> Option <&Rc <RefCell <Table>>> { pub fn as_table (&self) -> Option <usize> {
match self { match self {
Self::Table (t) => Some (t), Self::Table (t) => Some (*t),
_ => None, _ => None,
} }
} }

View File

@ -49,13 +49,21 @@ Did absolutely nothing. I couldn't outsmart LLVM.
## Remove RefCell ## Remove RefCell
(upcoming) Result: Worked well. Dropped from about 3,700 to 3,200 samples. The code got even uglier.
Plan:
I think the `borrow` and `borrow_mut` calls slow down OP_GETFIELD and OP_SETFIELD. I can remove them if I store all the tables in State directly, replacing `Rc <RefCell <Table>>` with my own ref counting. This might I think the `borrow` and `borrow_mut` calls slow down OP_GETFIELD and OP_SETFIELD. I can remove them if I store all the tables in State directly, replacing `Rc <RefCell <Table>>` with my own ref counting. This might
remove a layer of indirection, too. remove a layer of indirection, too.
It's a big change, but I'd need _something_ like this for adding a GC anyway, and sometimes big changes have paid off. It's a big change, but I'd need _something_ like this for adding a GC anyway, and sometimes big changes have paid off.
## Cache constants
Result: Regressed from 3200 to 3600. Not sure why.
Plan: OP_GETFIELD hits the constants a lot. I thought caching it and not dereferencing the chunk and block constantly might help.
## Iterating over instruction list ## Iterating over instruction list
(upcoming) (upcoming)