Skip to content

Commit

Permalink
Implement control flow graph construction
Browse files Browse the repository at this point in the history
  • Loading branch information
HalidOdat committed Dec 2, 2023
1 parent eb2f33e commit ca3c13d
Show file tree
Hide file tree
Showing 8 changed files with 820 additions and 9 deletions.
48 changes: 46 additions & 2 deletions boa_cli/src/debug/optimizer.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
use boa_engine::{
js_string,
object::{FunctionObjectBuilder, ObjectInitializer},
optimizer::OptimizerOptions,
optimizer::{
control_flow_graph::{
ControlFlowGraph, GraphEliminateUnreachableBasicBlocks, GraphSimplification,
},
OptimizerOptions,
},
property::Attribute,
Context, JsArgs, JsObject, JsResult, JsValue, NativeFunction,
Context, JsArgs, JsNativeError, JsObject, JsResult, JsValue, NativeFunction,
};

fn get_constant_folding(_: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
Expand Down Expand Up @@ -36,6 +41,44 @@ fn set_statistics(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsRes
Ok(JsValue::undefined())
}

fn graph(_: &JsValue, args: &[JsValue], _context: &mut Context) -> JsResult<JsValue> {
let Some(value) = args.get(0) else {
return Err(JsNativeError::typ()
.with_message("expected function argument")
.into());
};

let Some(object) = value.as_object() else {
return Err(JsNativeError::typ()
.with_message(format!("expected object, got {}", value.type_of()))
.into());
};
let object = object.borrow();
let Some(function) = object.as_function() else {
return Err(JsNativeError::typ()
.with_message("expected function object")
.into());
};
let code = function.codeblock();

let cfg = ControlFlowGraph::generate(code.bytecode());
// println!("{:#?}", cfg);

let bytecode = cfg.finalize();
assert_eq!(code.bytecode(), &bytecode);

let mut cfg = ControlFlowGraph::generate(&bytecode);
println!("Original\n{cfg:#?}\n");

let changed = GraphSimplification::perform(&mut cfg);
println!("Simplified({changed}) \n{cfg:#?}");

let changed = GraphEliminateUnreachableBasicBlocks::perform(&mut cfg);
println!("Eliminate Unreachble({changed}) \n{cfg:#?}");

Ok(JsValue::undefined())
}

pub(super) fn create_object(context: &mut Context) -> JsObject {
let get_constant_folding = FunctionObjectBuilder::new(
context.realm(),
Expand Down Expand Up @@ -75,5 +118,6 @@ pub(super) fn create_object(context: &mut Context) -> JsObject {
Some(set_statistics),
Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE,
)
.function(NativeFunction::from_fn_ptr(graph), js_string!("graph"), 1)
.build()
}
6 changes: 5 additions & 1 deletion boa_engine/src/bytecompiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use crate::{
builtins::function::ThisMode,
environments::{BindingLocator, BindingLocatorError, CompileTimeEnvironment},
js_string,
optimizer::control_flow_graph::ControlFlowGraph,
vm::{
BindingOpcode, CodeBlock, CodeBlockFlags, Constant, GeneratorResumeKind, Handler, Opcode,
VaryingOperandKind,
Expand Down Expand Up @@ -1458,12 +1459,15 @@ impl<'ctx> ByteCompiler<'ctx> {
.utf16()
.into();

// FIXME: remove this, this is used to ensure that `finalize` works correctly.
let graph = ControlFlowGraph::generate(&self.bytecode);

CodeBlock {
name,
length: self.length,
this_mode: self.this_mode,
params: self.params,
bytecode: self.bytecode.into_boxed_slice(),
bytecode: graph.finalize().into_boxed_slice(),
constants: self.constants,
bindings: self.bindings.into_boxed_slice(),
handlers: self.handlers,
Expand Down
174 changes: 174 additions & 0 deletions boa_engine/src/optimizer/control_flow_graph/basic_block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
use std::{
cell::RefCell,
hash::{Hash, Hasher},
ops::Deref,
rc::{Rc, Weak},
};

use bitflags::bitflags;

use crate::vm::Instruction;

use super::Terminator;

bitflags! {
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) struct BasicBlockFlags: u8 {
const REACHABLE = 0b0000_0001;
}
}

/// TODO: doc
#[derive(Default, Clone)]
pub struct BasicBlock {
pub(crate) predecessors: Vec<WeakBasicBlock>,
pub(crate) instructions: Vec<Instruction>,
pub(crate) terminator: Terminator,

pub(crate) flags: BasicBlockFlags,
}

impl BasicBlock {
/// Get nth instruction in the [`BasicBlock`].
pub(crate) fn get(&mut self, nth: usize) -> Option<&Instruction> {
self.instructions.get(nth)
}

/// Insert nth instruction in the [`BasicBlock`].
pub(crate) fn insert(&mut self, nth: usize, instruction: Instruction) {
self.instructions.insert(nth, instruction);
}

/// Insert instruction in the last position in the [`BasicBlock`].
pub(crate) fn insert_last(&mut self, instruction: Instruction) {
self.instructions.push(instruction);
}

/// Remove nth instruction in the [`BasicBlock`].
pub(crate) fn remove(&mut self, nth: usize) -> Instruction {
self.instructions.remove(nth)
}

/// Remove last instruction in the [`BasicBlock`].
pub(crate) fn remove_last(&mut self) -> bool {
self.instructions.pop().is_some()
}

pub(crate) fn reachable(&self) -> bool {
self.flags.contains(BasicBlockFlags::REACHABLE)
}

pub(crate) fn successors(&self) -> Vec<RcBasicBlock> {
match &self.terminator {
Terminator::None => vec![],
Terminator::JumpUnconditional { target, .. } => {
vec![target.clone()]
}
Terminator::JumpConditional { no, yes, .. }
| Terminator::TemplateLookup { no, yes, .. } => {
vec![no.clone(), yes.clone()]
}
Terminator::Return { finally } => {
let mut successors = Vec::new();
if let Some(finally) = finally {
successors.push(finally.clone());
}
successors
}
}
}

pub(crate) fn next(&self, nexts: &mut Vec<RcBasicBlock>) {
match &self.terminator {
Terminator::None => {}
Terminator::JumpUnconditional { target, .. } => {
nexts.push(target.clone());
}
Terminator::JumpConditional { no, yes, .. }
| Terminator::TemplateLookup { no, yes, .. } => {
nexts.push(no.clone());
nexts.push(yes.clone());
}
Terminator::Return { finally } => {
if let Some(_finally) = finally {
// FIXME: should we include this??
// nexts.push(finally.clone());
}
}
}
}
}

/// Reference counted [`BasicBlock`] with interor mutability.
#[derive(Default, Clone)]
pub struct RcBasicBlock {
inner: Rc<RefCell<BasicBlock>>,
}

impl From<Rc<RefCell<BasicBlock>>> for RcBasicBlock {
fn from(inner: Rc<RefCell<BasicBlock>>) -> Self {
Self { inner }
}
}

impl Deref for RcBasicBlock {
type Target = Rc<RefCell<BasicBlock>>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}

impl PartialEq<RcBasicBlock> for RcBasicBlock {
fn eq(&self, other: &RcBasicBlock) -> bool {
Rc::ptr_eq(&self.inner, &other.inner)
}
}

impl Eq for RcBasicBlock {}

impl Hash for RcBasicBlock {
fn hash<H: Hasher>(&self, state: &mut H) {
(self.as_ptr() as usize).hash(state);
}
}

impl RcBasicBlock {
/// TODO: doc
#[must_use]
pub fn downgrade(&self) -> WeakBasicBlock {
WeakBasicBlock::from(Rc::downgrade(&self.inner))
}
}

/// Reference counted [`BasicBlock`] with interor mutability.
#[derive(Default, Clone)]
pub struct WeakBasicBlock {
inner: Weak<RefCell<BasicBlock>>,
}

impl From<Weak<RefCell<BasicBlock>>> for WeakBasicBlock {
fn from(inner: Weak<RefCell<BasicBlock>>) -> Self {
Self { inner }
}
}

impl Deref for WeakBasicBlock {
type Target = Weak<RefCell<BasicBlock>>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}

impl PartialEq<WeakBasicBlock> for WeakBasicBlock {
fn eq(&self, other: &WeakBasicBlock) -> bool {
Weak::ptr_eq(&self.inner, &other.inner)
}
}

impl WeakBasicBlock {
/// TODO: doc
#[must_use]
pub fn upgrade(&self) -> Option<RcBasicBlock> {
Some(RcBasicBlock::from(self.inner.upgrade()?))
}
}
Loading

0 comments on commit ca3c13d

Please sign in to comment.