Compare commits

...

5 Commits

Author SHA1 Message Date
Scott Richmond
a5f2e2a9bd tuple patterns now use jump_stub and patch_jump, with 16 bit jump values 2025-06-18 14:47:00 -04:00
Scott Richmond
a0ef6d2777 chip away at 16-bit jump instructions 2025-06-18 14:12:54 -04:00
Scott Richmond
d727096422 VM instructions now take 3 bytes 2025-06-18 14:12:10 -04:00
Scott Richmond
979afdbcb9 add jump fn for 16 bit jump 2025-06-18 13:15:57 -04:00
Scott Richmond
316e8a6a58 remove unused pattern.rs 2025-06-18 13:15:39 -04:00
3 changed files with 147 additions and 80 deletions

View File

@ -261,13 +261,20 @@ impl Chunk {
*i += 1; *i += 1;
} }
PushBinding | MatchTuple | MatchList | MatchDict | LoadDictValue | PushTuple PushBinding | MatchTuple | MatchList | MatchDict | LoadDictValue | PushTuple
| PushBox | Jump | JumpIfFalse | JumpIfTrue | JumpIfNoMatch | JumpIfMatch | PushBox | MatchDepth | PopN | StoreAt | Call | SetUpvalue | GetUpvalue | Partial
| JumpBack | JumpIfZero | MatchDepth | PopN | StoreAt | Call | SetUpvalue | MatchString | PushStringMatches => {
| GetUpvalue | Partial | MatchString | PushStringMatches => {
let next = self.bytecode[*i + 1]; let next = self.bytecode[*i + 1];
println!("{i:04}: {:16} {next:03}", op.to_string()); println!("{i:04}: {:16} {next:03}", op.to_string());
*i += 1; *i += 1;
} }
Jump | JumpIfFalse | JumpIfTrue | JumpIfNoMatch | JumpIfMatch | JumpBack
| JumpIfZero => {
let high = self.bytecode[*i + 1];
let low = self.bytecode[*i + 2];
let len = ((high as u16) << 8) + low as u16;
println!("{i:04}: {:16} {len:05}", op.to_string());
*i += 2;
}
} }
} }
@ -395,6 +402,29 @@ impl<'a> Compiler<'a> {
self.span = root_span; self.span = root_span;
} }
fn jump(&mut self, op: Op, len: usize) {
let low = len as u8;
let high = (len >> 8) as u8;
self.emit_op(op);
self.chunk.bytecode.push(high);
self.chunk.bytecode.push(low);
}
fn jump_stub(&mut self, op: Op) -> usize {
let out = self.chunk.bytecode.len();
self.emit_op(op);
self.emit_byte(0xff);
self.emit_byte(0xff);
out
}
fn patch_jump(&mut self, i: usize, len: usize) {
let low = len as u8;
let high = (len >> 8) as u8;
self.chunk.bytecode[i + 1] = high;
self.chunk.bytecode[i + 2] = low;
}
fn emit_constant(&mut self, val: Value) { fn emit_constant(&mut self, val: Value) {
let const_idx = if let Some(idx) = self.chunk.constants.iter().position(|v| *v == val) { let const_idx = if let Some(idx) = self.chunk.constants.iter().position(|v| *v == val) {
idx idx
@ -568,18 +598,26 @@ impl<'a> Compiler<'a> {
} }
Keyword(s) => self.emit_constant(Value::Keyword(s)), Keyword(s) => self.emit_constant(Value::Keyword(s)),
Block(lines) => { Block(lines) => {
// increase the scope
self.scope_depth += 1; self.scope_depth += 1;
// stash the stack depth
let stack_depth = self.stack_depth; let stack_depth = self.stack_depth;
// evaluate all the lines but the last
for expr in lines.iter().take(lines.len() - 1) { for expr in lines.iter().take(lines.len() - 1) {
if is_binding(expr) { // evaluate the expression
self.visit(expr);
} else {
self.visit(expr); self.visit(expr);
// if it doesn't bind a name, pop the result from the stack
if !is_binding(expr) {
self.pop(); self.pop();
} }
} }
// now, evaluate the last expression in the block
let last_expr = lines.last().unwrap(); let last_expr = lines.last().unwrap();
match last_expr { match last_expr {
// if the last expression is a let form,
// return the evaluated rhs instead of whatever is last on the stack
// we do this by pretending it's a binding
(Let(patt, expr), _) => { (Let(patt, expr), _) => {
self.match_depth = 0; self.match_depth = 0;
self.emit_op(Op::ResetMatch); self.emit_op(Op::ResetMatch);
@ -591,14 +629,20 @@ impl<'a> Compiler<'a> {
self.emit_byte(expr_pos); self.emit_byte(expr_pos);
self.stack_depth += 1; self.stack_depth += 1;
} }
// otherwise, just evaluate it and leave the value on the stack
_ => { _ => {
self.visit(last_expr); self.visit(last_expr);
} }
} }
self.stack_depth += 1;
self.emit_op(Op::Store);
self.scope_depth -= 1;
// we've made a new value, so increase the stack level in the compiler
self.stack_depth += 1;
// store the value in the return register
self.emit_op(Op::Store);
// reset the scope
self.scope_depth -= 1;
while let Some(binding) = self.bindings.last() { while let Some(binding) = self.bindings.last() {
if binding.depth > self.scope_depth { if binding.depth > self.scope_depth {
self.bindings.pop(); self.bindings.pop();
@ -606,29 +650,24 @@ impl<'a> Compiler<'a> {
break; break;
} }
} }
// reset stack // reset the stack
while self.stack_depth > stack_depth + 1 { self.pop_n(self.stack_depth - stack_depth - 1);
self.pop(); // load the value from the return register
}
self.emit_op(Op::Load); self.emit_op(Op::Load);
} }
If(cond, then, r#else) => { If(cond, then, r#else) => {
self.visit(cond); self.visit(cond);
let jif_idx = self.len(); let jif_idx = self.jump_stub(Op::JumpIfFalse);
self.emit_op(Op::JumpIfFalse);
self.emit_byte(0xff);
self.stack_depth -= 1; self.stack_depth -= 1;
self.visit(then); self.visit(then);
let jump_idx = self.len(); let jump_idx = self.jump_stub(Op::Jump);
self.emit_op(Op::Jump);
self.emit_byte(0xff);
self.visit(r#else); self.visit(r#else);
self.stack_depth -= 1; self.stack_depth -= 1;
let end_idx = self.len(); let end_idx = self.len();
let jif_offset = jump_idx - jif_idx; let jif_offset = jump_idx - jif_idx;
let jump_offset = end_idx - jump_idx - 2; let jump_offset = end_idx - jump_idx - 1;
self.chunk.bytecode[jif_idx + 1] = jif_offset as u8; self.patch_jump(jif_idx, jif_offset);
self.chunk.bytecode[jump_idx + 1] = jump_offset as u8; self.patch_jump(jump_idx, jump_offset);
} }
Let(patt, expr) => { Let(patt, expr) => {
self.match_depth = 0; self.match_depth = 0;
@ -679,36 +718,54 @@ impl<'a> Compiler<'a> {
self.match_constant(Value::Interned(s)); self.match_constant(Value::Interned(s));
} }
TuplePattern(members) => { TuplePattern(members) => {
// first, test the tuple against length
self.emit_op(Op::MatchTuple); self.emit_op(Op::MatchTuple);
self.emit_byte(members.len()); self.emit_byte(members.len());
self.emit_op(Op::JumpIfNoMatch); // skip everything if tuple lengths don't match
let before_load_tup_idx = self.len(); let before_load_tup_idx = self.jump_stub(Op::JumpIfNoMatch);
self.emit_byte(0xff);
// set up the per-member conditional logic
let mut jump_idxes = vec![]; let mut jump_idxes = vec![];
self.match_depth += members.len(); // stash match_depth, and set it to the tuple len
let match_depth = self.match_depth;
self.match_depth = members.len();
// load the tuple and update the stack len
self.emit_op(Op::LoadTuple); self.emit_op(Op::LoadTuple);
self.stack_depth += members.len(); self.stack_depth += members.len();
// visit each member
for member in members { for member in members {
// reduce the match depth to start
self.match_depth -= 1; self.match_depth -= 1;
self.emit_op(Op::MatchDepth); self.emit_op(Op::MatchDepth);
self.emit_byte(self.match_depth); self.emit_byte(self.match_depth);
// visit the pattern member
self.visit(member); self.visit(member);
self.emit_op(Op::JumpIfNoMatch); // and jump if there's no match
jump_idxes.push(self.len()); jump_idxes.push(self.jump_stub(Op::JumpIfNoMatch));
self.emit_byte(0xff);
} }
self.emit_op(Op::Jump);
let jump_idx = self.len(); // if we get here--not having jumped on no match--we're matched; jump the "no match" code
self.emit_byte(0xff); self.match_depth = match_depth + members.len();
let jump_idx = self.jump_stub(Op::Jump);
// patch up the previous no match jumps to jump to clean-up code
for idx in jump_idxes { for idx in jump_idxes {
self.chunk.bytecode[idx] = (self.len() - idx) as u8 - 1; self.patch_jump(idx, self.len() - idx - 2)
} }
for _ in 0..members.len() { // pop everything that was pushed
self.emit_op(Op::Pop); // don't change the compiler stack representation, tho
} // we need this as cleanup code with no matches
self.chunk.bytecode[before_load_tup_idx] = // the compiler should still have access to the bindings in this pattern
(self.len() - before_load_tup_idx) as u8 - 1; self.emit_op(Op::PopN);
self.chunk.bytecode[jump_idx] = (self.len() - jump_idx) as u8 - 1; self.emit_byte(members.len());
// patch up the tuple length match jump
self.patch_jump(before_load_tup_idx, self.len() - before_load_tup_idx - 3);
// patch up the yes-matches unconditional jump
self.patch_jump(jump_idx, self.len() - jump_idx - 3);
} }
ListPattern(members) => { ListPattern(members) => {
self.emit_op(Op::MatchList); self.emit_op(Op::MatchList);

View File

@ -20,7 +20,6 @@ mod validator;
mod compiler; mod compiler;
use crate::compiler::Compiler; use crate::compiler::Compiler;
mod pattern;
mod value; mod value;
mod vm; mod vm;
@ -75,23 +74,8 @@ pub fn run(src: &'static str) {
pub fn main() { pub fn main() {
// env::set_var("RUST_BACKTRACE", "1"); // env::set_var("RUST_BACKTRACE", "1");
// let src = " let src = "
// let x = { let ((1)) = ((1))
// match #{:a 1, :b 2, :c 3} with { ";
// #{a} -> :one run(src);
// #{a, b, :c 3} -> :two
// #{a, b, c} -> :three
// (1, 2, 3) -> :thing
// (4, 5, (6, 7, a)) -> if or (true, false, false, true) then :thing_1 else :thing_2
// ([:a, :b, :c, [:d, [:e, (:f, :g)]]]) -> if or (true, false, false, true) then :thing_1 else :thing_2
// }
// }
// ";
// run(src);
let a: u16 = 14261;
let b_high: u8 = (a >> 8) as u8;
let b_low: u8 = a as u8;
let c: u16 = ((b_high as u16) << 8) + b_low as u16;
println!("{a} // {b_high}/{b_low} // {c}");
} }

View File

@ -57,7 +57,7 @@ impl CallFrame {
let Value::Fn(ref function) = self.function else { let Value::Fn(ref function) = self.function else {
unreachable!() unreachable!()
}; };
&function.chunk(self.arity) function.chunk(self.arity)
} }
} }
@ -76,6 +76,11 @@ impl fmt::Display for CallFrame {
} }
} }
fn combine_bytes(high: u8, low: u8) -> usize {
let out = ((high as u16) << 8) + low as u16;
out as usize
}
pub struct Vm { pub struct Vm {
pub stack: Vec<Value>, pub stack: Vec<Value>,
pub call_stack: Vec<CallFrame>, pub call_stack: Vec<CallFrame>,
@ -205,46 +210,61 @@ impl Vm {
self.ip += 2; self.ip += 2;
} }
Jump => { Jump => {
let jump_len = self.chunk().bytecode[self.ip + 1]; let high = self.chunk().bytecode[self.ip + 1];
self.ip += jump_len as usize + 2; let low = self.chunk().bytecode[self.ip + 2];
let jump_len = combine_bytes(high, low);
// let jump_len = self.chunk().bytecode[self.ip + 1];
self.ip += jump_len + 3;
} }
JumpBack => { JumpBack => {
let jump_len = self.chunk().bytecode[self.ip + 1]; let high = self.chunk().bytecode[self.ip + 1];
self.ip -= jump_len as usize; let low = self.chunk().bytecode[self.ip + 2];
let jump_len = combine_bytes(high, low);
// let jump_len = self.chunk().bytecode[self.ip + 1];
self.ip -= jump_len;
} }
JumpIfFalse => { JumpIfFalse => {
let jump_len = self.chunk().bytecode[self.ip + 1]; let high = self.chunk().bytecode[self.ip + 1];
let low = self.chunk().bytecode[self.ip + 2];
let jump_len = combine_bytes(high, low);
// let jump_len = self.chunk().bytecode[self.ip + 1];
let cond = self.pop(); let cond = self.pop();
match cond { match cond {
Value::Nil | Value::False => { Value::Nil | Value::False => {
self.ip += jump_len as usize + 2; self.ip += jump_len + 3;
} }
_ => { _ => {
self.ip += 2; self.ip += 3;
} }
} }
} }
JumpIfTrue => { JumpIfTrue => {
let jump_len = self.chunk().bytecode[self.ip + 1]; let high = self.chunk().bytecode[self.ip + 1];
let low = self.chunk().bytecode[self.ip + 2];
let jump_len = combine_bytes(high, low);
// let jump_len = self.chunk().bytecode[self.ip + 1];
let cond = self.pop(); let cond = self.pop();
match cond { match cond {
Value::Nil | Value::False => { Value::Nil | Value::False => {
self.ip += 2; self.ip += 3;
} }
_ => { _ => {
self.ip += jump_len as usize + 2; self.ip += jump_len + 3;
} }
} }
} }
JumpIfZero => { JumpIfZero => {
let jump_len = self.chunk().bytecode[self.ip + 1]; let high = self.chunk().bytecode[self.ip + 1];
let low = self.chunk().bytecode[self.ip + 2];
let jump_len = combine_bytes(high, low);
// let jump_len = self.chunk().bytecode[self.ip + 1];
let cond = self.pop(); let cond = self.pop();
match cond { match cond {
Value::Number(x) if x <= 0.0 => { Value::Number(x) if x <= 0.0 => {
self.ip += jump_len as usize + 2; self.ip += jump_len as usize + 3;
} }
Value::Number(..) => { Value::Number(..) => {
self.ip += 2; self.ip += 3;
} }
_ => return self.panic("repeat requires a number"), _ => return self.panic("repeat requires a number"),
} }
@ -542,19 +562,25 @@ impl Vm {
self.ip += 1; self.ip += 1;
} }
JumpIfNoMatch => { JumpIfNoMatch => {
let jump_len = self.chunk().bytecode[self.ip + 1] as usize; let high = self.chunk().bytecode[self.ip + 1];
let low = self.chunk().bytecode[self.ip + 2];
let jump_len = combine_bytes(high, low);
// let jump_len = self.chunk().bytecode[self.ip + 1] as usize;
if !self.matches { if !self.matches {
self.ip += jump_len + 2; self.ip += jump_len + 3;
} else { } else {
self.ip += 2; self.ip += 3;
} }
} }
JumpIfMatch => { JumpIfMatch => {
let jump_len = self.chunk().bytecode[self.ip + 1] as usize; let high = self.chunk().bytecode[self.ip + 1];
let low = self.chunk().bytecode[self.ip + 2];
let jump_len = combine_bytes(high, low);
// let jump_len = self.chunk().bytecode[self.ip + 1] as usize;
if self.matches { if self.matches {
self.ip += jump_len + 2; self.ip += jump_len + 3;
} else { } else {
self.ip += 2; self.ip += 3;
} }
} }
TypeOf => { TypeOf => {