Skip to content

Commit

Permalink
Merge pull request #13 from Hirevo/opt/inline-caches
Browse files Browse the repository at this point in the history
(opt): Inline caches for method lookups when sending messages
  • Loading branch information
Hirevo committed Jan 27, 2023
2 parents 3994838 + 7904ae0 commit 8e2c00e
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 139 deletions.
4 changes: 4 additions & 0 deletions som-interpreter-bc/src/block.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
use std::cell::RefCell;
use std::fmt;
use std::rc::Rc;

use som_core::bytecode::Bytecode;

use crate::class::Class;
use crate::compiler::Literal;
use crate::frame::Frame;
use crate::method::Method;
use crate::universe::Universe;
use crate::value::Value;
use crate::SOMRef;
Expand All @@ -18,6 +21,7 @@ pub struct Block {
pub literals: Vec<Literal>,
pub body: Vec<Bytecode>,
pub nb_params: usize,
pub inline_cache: Rc<RefCell<Vec<Option<(*const Class, Rc<Method>)>>>>,
}

impl Block {
Expand Down
35 changes: 24 additions & 11 deletions som-interpreter-bc/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -411,12 +411,17 @@ fn compile_method(outer: &mut dyn GenCtxt, defn: &ast::MethodDef) -> Option<Meth
kind: match &defn.body {
ast::MethodBody::Primitive => MethodKind::NotImplemented(defn.signature.clone()),
ast::MethodBody::Body { .. } => {
let env = MethodEnv {
locals: ctxt.inner.locals.iter().map(|_| Value::Nil).collect(),
literals: ctxt.inner.literals.into_iter().collect(),
body: ctxt.inner.body.unwrap_or_default(),
};
MethodKind::Defined(env)
let body = ctxt.inner.body.unwrap_or_default();
let locals = ctxt.inner.locals.iter().map(|_| Value::Nil).collect();
let literals = ctxt.inner.literals.into_iter().collect();
let inline_cache = RefCell::new(vec![None; body.len()]);

MethodKind::Defined(MethodEnv {
body,
locals,
literals,
inline_cache,
})
}
},
holder: Weak::new(),
Expand Down Expand Up @@ -449,12 +454,20 @@ fn compile_block(outer: &mut dyn GenCtxt, defn: &ast::Block) -> Option<Block> {
ctxt.push_instr(Bytecode::ReturnLocal);
}

let frame = None;
let locals = ctxt.locals.into_iter().map(|_| Value::Nil).collect();
let literals = ctxt.literals.into_iter().collect();
let body = ctxt.body.unwrap_or_default();
let nb_params = ctxt.args.len();
let inline_cache = Rc::new(RefCell::new(vec![None; body.len()]));

let block = Block {
frame: None,
locals: ctxt.locals.into_iter().map(|_| Value::Nil).collect(),
literals: ctxt.literals.into_iter().collect(),
body: ctxt.body.unwrap_or_default(),
nb_params: ctxt.args.len(),
frame,
locals,
literals,
body,
nb_params,
inline_cache,
};

// println!("(system) compiled block !");
Expand Down
270 changes: 142 additions & 128 deletions som-interpreter-bc/src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ use std::time::Instant;
use som_core::bytecode::Bytecode;

use crate::block::Block;
use crate::class::Class;
use crate::compiler::Literal;
use crate::frame::{Frame, FrameKind};
use crate::method::MethodKind;
use crate::interner::Interned;
use crate::method::{Method, MethodKind};
use crate::universe::Universe;
use crate::value::Value;
use crate::SOMRef;
Expand Down Expand Up @@ -51,6 +53,7 @@ impl Interpreter {
None => return Some(self.stack.pop().unwrap_or(Value::Nil)),
};

let bytecode_idx = frame.borrow().bytecode_idx;
let opt_bytecode = frame.borrow().get_current_bytecode();
let bytecode = match opt_bytecode {
Some(bytecode) => bytecode,
Expand Down Expand Up @@ -115,12 +118,12 @@ impl Interpreter {
Literal::Block(blk) => Block::clone(&blk),
_ => return None,
};
block.frame.replace(Rc::clone(frame));
block.frame.replace(Rc::clone(&frame));
self.stack.push(Value::Block(Rc::new(block)));
}
Bytecode::PushConstant(idx) => {
let literal = frame.borrow().lookup_constant(idx as usize).unwrap();
let value = convert_literal(frame, literal).unwrap();
let value = convert_literal(&frame, literal).unwrap();
self.stack.push(value);
}
Bytecode::PushGlobal(idx) => {
Expand Down Expand Up @@ -187,142 +190,33 @@ impl Interpreter {
}
Bytecode::Send(idx) => {
let literal = frame.borrow().lookup_constant(idx as usize).unwrap();
let symbol = match literal {
Literal::Symbol(sym) => sym,
_ => {
return None;
}
let Literal::Symbol(symbol) = literal else {
return None;
};
let signature = universe.lookup_symbol(symbol);
let nb_params = nb_params(signature);
let method = self
.stack
.iter()
.nth_back(nb_params)
.unwrap()
.lookup_method(universe, symbol);

if let Some(method) = method {
match method.kind() {
MethodKind::Defined(_) => {
let mut args = Vec::with_capacity(nb_params + 1);

for _ in 0..nb_params {
let arg = self.stack.pop().unwrap();
args.push(arg);
}
let self_value = self.stack.pop().unwrap();
args.push(self_value.clone());

args.reverse();

let holder = method.holder.upgrade().unwrap();
self.push_frame(FrameKind::Method {
self_value,
method,
holder,
});

let frame = self.current_frame().unwrap();
frame.borrow_mut().args = args;
}
MethodKind::Primitive(func) => {
func(self, universe);
}
MethodKind::NotImplemented(err) => {
let self_value = self.stack.iter().nth_back(nb_params).unwrap();
println!(
"{}>>#{}",
self_value.class(&universe).borrow().name(),
method.signature()
);
panic!("Primitive `#{}` not implemented", err)
}
}
} else {
let mut args = Vec::with_capacity(nb_params + 1);

for _ in 0..nb_params {
let arg = self.stack.pop().unwrap();
args.push(arg);
}
let self_value = self.stack.pop().unwrap();

args.reverse();
let method = {
let receiver = self.stack.iter().nth_back(nb_params)?;
let receiver_class = receiver.class(universe);
resolve_method(frame, &receiver_class, symbol, bytecode_idx)
};

universe.does_not_understand(self, self_value, symbol, args)
.expect(
"A message cannot be handled and `doesNotUnderstand:arguments:` is not defined on receiver"
);
}
do_send(self, universe, method, symbol, nb_params);
}
Bytecode::SuperSend(idx) => {
let literal = frame.borrow().lookup_constant(idx as usize).unwrap();
let symbol = match literal {
Literal::Symbol(sym) => sym,
_ => {
return None;
}
let Literal::Symbol(symbol) = literal else {
return None;
};
let signature = universe.lookup_symbol(symbol);
let nb_params = nb_params(signature);
let method = {
let holder = frame.borrow().get_method_holder();
let super_class = holder.borrow().super_class()?;
resolve_method(frame, &super_class, symbol, bytecode_idx)
};

let method = frame
.borrow()
.get_method_holder()
.borrow()
.super_class()
.unwrap()
.borrow()
.lookup_method(symbol);

if let Some(method) = method {
match method.kind() {
MethodKind::Defined(_) => {
let mut args = Vec::with_capacity(nb_params + 1);

for _ in 0..nb_params {
let arg = self.stack.pop().unwrap();
args.push(arg);
}
let self_value = self.stack.pop().unwrap();
args.push(self_value.clone());

args.reverse();

let holder = method.holder.upgrade().unwrap();
self.push_frame(FrameKind::Method {
self_value,
method,
holder,
});

let frame = self.current_frame().unwrap();
frame.borrow_mut().args = args;
}
MethodKind::Primitive(func) => {
func(self, universe);
}
MethodKind::NotImplemented(err) => {
panic!("Primitive `#{}` not implemented", err)
}
}
} else {
let mut args = Vec::with_capacity(nb_params + 1);

for _ in 0..nb_params {
let arg = self.stack.pop().unwrap();
args.push(arg);
}
let self_value = self.stack.pop().unwrap();

args.reverse();

universe.does_not_understand(self, self_value, symbol, args)
.expect(
"A message cannot be handled and `doesNotUnderstand:arguments:` is not defined on receiver"
);
}
do_send(self, universe, method, symbol, nb_params);
}
Bytecode::ReturnLocal => {
let value = self.stack.pop().unwrap();
Expand Down Expand Up @@ -363,6 +257,126 @@ impl Interpreter {
}
}

fn do_send(
interpreter: &mut Interpreter,
universe: &mut Universe,
method: Option<Rc<Method>>,
symbol: Interned,
nb_params: usize,
) {
let Some(method) = method else {
let mut args = Vec::with_capacity(nb_params + 1);

for _ in 0..nb_params {
let arg = interpreter.stack.pop().unwrap();
args.push(arg);
}
let self_value = interpreter.stack.pop().unwrap();

args.reverse();

universe.does_not_understand(interpreter, self_value, symbol, args)
.expect(
"A message cannot be handled and `doesNotUnderstand:arguments:` is not defined on receiver"
);

return;
};

match method.kind() {
MethodKind::Defined(_) => {
let mut args = Vec::with_capacity(nb_params + 1);

for _ in 0..nb_params {
let arg = interpreter.stack.pop().unwrap();
args.push(arg);
}
let self_value = interpreter.stack.pop().unwrap();
args.push(self_value.clone());

args.reverse();

let holder = method.holder.upgrade().unwrap();
let frame = interpreter.push_frame(FrameKind::Method {
self_value,
method,
holder,
});
frame.borrow_mut().args = args;
}
MethodKind::Primitive(func) => {
func(interpreter, universe);
}
MethodKind::NotImplemented(err) => {
let self_value = interpreter.stack.iter().nth_back(nb_params).unwrap();
println!(
"{}>>#{}",
self_value.class(&universe).borrow().name(),
method.signature(),
);
panic!("Primitive `#{}` not implemented", err)
}
}
}

fn resolve_method(
frame: &SOMRef<Frame>,
class: &SOMRef<Class>,
signature: Interned,
bytecode_idx: usize,
) -> Option<Rc<Method>> {
match frame.borrow().kind() {
FrameKind::Block { block } => {
let mut inline_cache = block.inline_cache.borrow_mut();

// SAFETY: this access is actually safe because the bytecode compiler
// makes sure the cache has as many entries as there are bytecode instructions,
// therefore we can avoid doing any redundant bounds checks here.
let maybe_found = unsafe { inline_cache.get_unchecked_mut(bytecode_idx) };

match maybe_found {
Some((receiver, method)) if *receiver == class.as_ptr() => {
Some(Rc::clone(method))
}
place @ None => {
let found = class.borrow().lookup_method(signature);
*place = found
.clone()
.map(|method| (class.as_ptr() as *const _, method));
found
}
_ => class.borrow().lookup_method(signature),
}
}
FrameKind::Method { method, .. } => {
if let MethodKind::Defined(env) = method.kind() {
let mut inline_cache = env.inline_cache.borrow_mut();

// SAFETY: this access is actually safe because the bytecode compiler
// makes sure the cache has as many entries as there are bytecode instructions,
// therefore we can avoid doing any redundant bounds checks here.
let maybe_found = unsafe { inline_cache.get_unchecked_mut(bytecode_idx) };

match maybe_found {
Some((receiver, method)) if *receiver == class.as_ptr() => {
Some(Rc::clone(method))
}
place @ None => {
let found = class.borrow().lookup_method(signature);
*place = found
.clone()
.map(|method| (class.as_ptr() as *const _, method));
found
}
_ => class.borrow().lookup_method(signature),
}
} else {
class.borrow().lookup_method(signature)
}
}
}
}

fn convert_literal(frame: &SOMRef<Frame>, literal: Literal) -> Option<Value> {
let value = match literal {
Literal::Symbol(sym) => Value::Symbol(sym),
Expand Down
Loading

0 comments on commit 8e2c00e

Please sign in to comment.