#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NodeId(pub u32);
#[derive(Debug, Clone, PartialEq)]
pub enum ValueNode {
SmiConstant {
value: i32,
},
Float64Constant {
value: f64,
},
Int32Constant {
value: i32,
},
Uint32Constant {
value: u32,
},
BigIntConstant {
value: String,
},
TrueConstant,
FalseConstant,
NullConstant,
UndefinedConstant,
RootConstant {
index: u32,
},
ExternalConstant {
address: u64,
},
StringConstant {
value: String,
},
ConstantPoolEntry {
index: u32,
},
Parameter {
index: u32,
},
RegisterInput {
reg: u32,
},
ArgumentsLength,
RestLength,
GetArgument {
index: NodeId,
},
CheckedSmiAdd {
left: NodeId,
right: NodeId,
},
CheckedSmiSubtract {
left: NodeId,
right: NodeId,
},
CheckedSmiMultiply {
left: NodeId,
right: NodeId,
},
CheckedSmiDivide {
left: NodeId,
right: NodeId,
},
CheckedSmiModulus {
left: NodeId,
right: NodeId,
},
CheckedSmiIncrement {
value: NodeId,
},
CheckedSmiDecrement {
value: NodeId,
},
Int32Add {
left: NodeId,
right: NodeId,
},
Int32Subtract {
left: NodeId,
right: NodeId,
},
Int32Multiply {
left: NodeId,
right: NodeId,
},
Int32Divide {
left: NodeId,
right: NodeId,
},
Int32Modulus {
left: NodeId,
right: NodeId,
},
Int32Negate {
value: NodeId,
},
Int32Increment {
value: NodeId,
},
Int32Decrement {
value: NodeId,
},
Int32BitwiseAnd {
left: NodeId,
right: NodeId,
},
Int32BitwiseOr {
left: NodeId,
right: NodeId,
},
Int32BitwiseXor {
left: NodeId,
right: NodeId,
},
Int32ShiftLeft {
left: NodeId,
right: NodeId,
},
Int32ShiftRight {
left: NodeId,
right: NodeId,
},
Int32ShiftRightLogical {
left: NodeId,
right: NodeId,
},
Uint32Add {
left: NodeId,
right: NodeId,
},
Uint32Subtract {
left: NodeId,
right: NodeId,
},
Uint32Multiply {
left: NodeId,
right: NodeId,
},
Uint32Divide {
left: NodeId,
right: NodeId,
},
Uint32Modulus {
left: NodeId,
right: NodeId,
},
Float64Add {
left: NodeId,
right: NodeId,
},
Float64Subtract {
left: NodeId,
right: NodeId,
},
Float64Multiply {
left: NodeId,
right: NodeId,
},
Float64Divide {
left: NodeId,
right: NodeId,
},
Float64Modulus {
left: NodeId,
right: NodeId,
},
Float64Negate {
value: NodeId,
},
Float64Exponentiate {
left: NodeId,
right: NodeId,
},
Float64Ieee754Unary {
value: NodeId,
function_id: u32,
},
GenericAdd {
left: NodeId,
right: NodeId,
feedback_slot: u32,
},
GenericSubtract {
left: NodeId,
right: NodeId,
feedback_slot: u32,
},
GenericMultiply {
left: NodeId,
right: NodeId,
feedback_slot: u32,
},
GenericDivide {
left: NodeId,
right: NodeId,
feedback_slot: u32,
},
GenericModulus {
left: NodeId,
right: NodeId,
feedback_slot: u32,
},
GenericExponentiate {
left: NodeId,
right: NodeId,
feedback_slot: u32,
},
GenericBitwiseAnd {
left: NodeId,
right: NodeId,
feedback_slot: u32,
},
GenericBitwiseOr {
left: NodeId,
right: NodeId,
feedback_slot: u32,
},
GenericBitwiseXor {
left: NodeId,
right: NodeId,
feedback_slot: u32,
},
GenericShiftLeft {
left: NodeId,
right: NodeId,
feedback_slot: u32,
},
GenericShiftRight {
left: NodeId,
right: NodeId,
feedback_slot: u32,
},
GenericShiftRightLogical {
left: NodeId,
right: NodeId,
feedback_slot: u32,
},
GenericBitwiseNot {
value: NodeId,
feedback_slot: u32,
},
GenericNegate {
value: NodeId,
feedback_slot: u32,
},
GenericIncrement {
value: NodeId,
feedback_slot: u32,
},
GenericDecrement {
value: NodeId,
feedback_slot: u32,
},
Int32Equal {
left: NodeId,
right: NodeId,
},
Int32StrictEqual {
left: NodeId,
right: NodeId,
},
Int32LessThan {
left: NodeId,
right: NodeId,
},
Int32LessThanOrEqual {
left: NodeId,
right: NodeId,
},
Int32GreaterThan {
left: NodeId,
right: NodeId,
},
Int32GreaterThanOrEqual {
left: NodeId,
right: NodeId,
},
Float64Equal {
left: NodeId,
right: NodeId,
},
Float64LessThan {
left: NodeId,
right: NodeId,
},
Float64LessThanOrEqual {
left: NodeId,
right: NodeId,
},
Float64GreaterThan {
left: NodeId,
right: NodeId,
},
Float64GreaterThanOrEqual {
left: NodeId,
right: NodeId,
},
TaggedEqual {
left: NodeId,
right: NodeId,
feedback_slot: u32,
},
TaggedNotEqual {
left: NodeId,
right: NodeId,
feedback_slot: u32,
},
TestInstanceOf {
object: NodeId,
callable: NodeId,
feedback_slot: u32,
},
TestIn {
key: NodeId,
object: NodeId,
feedback_slot: u32,
},
TestUndetectable {
value: NodeId,
},
TestNullOrUndefined {
value: NodeId,
},
TestTypeOf {
value: NodeId,
literal_flag: u32,
},
ChangeInt32ToFloat64 {
input: NodeId,
},
ChangeUint32ToFloat64 {
input: NodeId,
},
ChangeFloat64ToInt32 {
input: NodeId,
},
CheckedFloat64ToInt32 {
input: NodeId,
},
ChangeInt32ToTagged {
input: NodeId,
},
ChangeUint32ToTagged {
input: NodeId,
},
ChangeFloat64ToTagged {
input: NodeId,
},
ChangeTaggedToInt32 {
input: NodeId,
},
ChangeTaggedToUint32 {
input: NodeId,
},
ChangeTaggedToFloat64 {
input: NodeId,
},
CheckedTaggedToInt32 {
input: NodeId,
},
CheckedTaggedToFloat64 {
input: NodeId,
},
ToBoolean {
value: NodeId,
},
ToString {
value: NodeId,
feedback_slot: u32,
},
ToObject {
value: NodeId,
feedback_slot: u32,
},
ToName {
value: NodeId,
feedback_slot: u32,
},
ToNumber {
value: NodeId,
feedback_slot: u32,
},
ToNumberOrNumeric {
value: NodeId,
feedback_slot: u32,
},
CheckSmi {
receiver: NodeId,
},
CheckNumber {
receiver: NodeId,
},
CheckHeapObject {
receiver: NodeId,
},
CheckSymbol {
receiver: NodeId,
},
CheckString {
receiver: NodeId,
},
CheckStringOrStringWrapper {
receiver: NodeId,
},
CheckSeqOneByteString {
receiver: NodeId,
},
CheckMaps {
receiver: NodeId,
feedback_slot: u32,
},
CheckMapsWithMigration {
receiver: NodeId,
feedback_slot: u32,
},
CheckValue {
receiver: NodeId,
expected: u32,
},
CheckDynamicValue {
receiver: NodeId,
expected: NodeId,
},
CheckInt32IsSmi {
input: NodeId,
},
CheckUint32IsSmi {
input: NodeId,
},
CheckHoleyFloat64IsSmi {
input: NodeId,
},
CheckInt32Condition {
left: NodeId,
right: NodeId,
condition: u32,
},
CheckCacheIndicesNotCleared {
receiver: NodeId,
indices: NodeId,
},
CheckFloat64IsNan {
input: NodeId,
},
LoadField {
object: NodeId,
offset: u32,
},
StoreField {
object: NodeId,
offset: u32,
value: NodeId,
},
LoadTaggedField {
object: NodeId,
offset: u32,
},
LoadDoubleField {
object: NodeId,
offset: u32,
},
LoadFixedArrayElement {
elements: NodeId,
index: NodeId,
},
LoadFixedDoubleArrayElement {
elements: NodeId,
index: NodeId,
},
LoadHoleyFixedDoubleArrayElement {
elements: NodeId,
index: NodeId,
},
StoreFixedArrayElement {
elements: NodeId,
index: NodeId,
value: NodeId,
},
StoreFixedDoubleArrayElement {
elements: NodeId,
index: NodeId,
value: NodeId,
},
LoadNamedGeneric {
object: NodeId,
name: u32,
feedback_slot: u32,
},
StoreNamedGeneric {
object: NodeId,
name: u32,
value: NodeId,
feedback_slot: u32,
},
LoadKeyedGeneric {
object: NodeId,
key: NodeId,
feedback_slot: u32,
},
StoreKeyedGeneric {
object: NodeId,
key: NodeId,
value: NodeId,
feedback_slot: u32,
},
LoadGlobal {
name: u32,
feedback_slot: u32,
},
StoreGlobal {
name: u32,
value: NodeId,
feedback_slot: u32,
},
LoadContextSlot {
context: NodeId,
depth: u32,
slot: u32,
},
StoreContextSlot {
context: NodeId,
depth: u32,
slot: u32,
value: NodeId,
},
LoadCurrentContextSlot {
slot: u32,
},
StoreCurrentContextSlot {
slot: u32,
value: NodeId,
},
Call {
callee: NodeId,
receiver: NodeId,
args: Vec<NodeId>,
feedback_slot: u32,
},
CallArrayPush {
callee: NodeId,
receiver: NodeId,
args: Vec<NodeId>,
feedback_slot: u32,
},
CallKnownFunction {
callee: NodeId,
receiver: NodeId,
args: Vec<NodeId>,
},
SpeculativeCallFusion {
callee: NodeId,
trip_count: u32,
resolved_slot: Option<u32>,
resolved_k: Option<i64>,
},
SpeculativeSumFusion {
array: NodeId,
},
SpeculativePushFusion {
array: NodeId,
count: u32,
},
SpeculativeFillTrueFusion {
array: NodeId,
count: NodeId,
},
SpeculativeCountTruthyFusion {
array: NodeId,
count: NodeId,
},
CallBuiltin {
builtin_id: u32,
args: Vec<NodeId>,
},
CallRuntime {
function_id: u32,
args: Vec<NodeId>,
},
CallWithSpread {
callee: NodeId,
receiver: NodeId,
args: Vec<NodeId>,
feedback_slot: u32,
},
Construct {
constructor: NodeId,
args: Vec<NodeId>,
feedback_slot: u32,
},
ConstructWithSpread {
constructor: NodeId,
args: Vec<NodeId>,
feedback_slot: u32,
},
CreateObjectLiteral {
boilerplate_descriptor: u32,
feedback_slot: u32,
flags: u32,
},
CreateObjectLiteralWithProperties {
feedback_slot: u32,
flags: u32,
names: Vec<u32>,
values: Vec<NodeId>,
},
CreateArrayLiteral {
constant_elements: u32,
feedback_slot: u32,
flags: u32,
},
CreateShallowObjectLiteral {
boilerplate_descriptor: u32,
feedback_slot: u32,
flags: u32,
},
CreateShallowArrayLiteral {
constant_elements: u32,
feedback_slot: u32,
flags: u32,
},
CreateFunctionContext {
scope_info: u32,
slot_count: u32,
},
PushContext {
context: NodeId,
},
PopContext {
context: NodeId,
},
CreateBlockContext {
scope_info: u32,
},
CreateCatchContext {
exception: NodeId,
scope_info: u32,
},
CreateWithContext {
object: NodeId,
scope_info: u32,
},
CreateClosure {
shared_function_info: u32,
feedback_slot: u32,
flags: u32,
},
FastCreateClosure {
shared_function_info: u32,
feedback_slot: u32,
},
CreateEmptyObjectLiteral,
CreateEmptyArrayLiteral {
feedback_slot: u32,
},
CreateMappedArguments,
CreateUnmappedArguments,
CreateRestParameter,
CreateRegExpLiteral {
pattern: u32,
feedback_slot: u32,
flags: u32,
},
Phi {
inputs: Vec<NodeId>,
},
ArgumentsElements {
kind: u32,
},
RestElements {
formal_parameter_count: u32,
},
VirtualObject {
map: u32,
},
GetTemplateObject {
description: u32,
feedback_slot: u32,
},
HasInPrototypeChain {
object: NodeId,
prototype: NodeId,
},
DeleteProperty {
object: NodeId,
key: NodeId,
feedback_slot: u32,
},
ForInPrepare {
enumerator: NodeId,
feedback_slot: u32,
},
ForInNext {
receiver: NodeId,
cache_index: NodeId,
cache_array: NodeId,
feedback_slot: u32,
},
LoadEnumCacheLength {
map: NodeId,
},
StringAt {
string: NodeId,
index: NodeId,
},
StringLength {
string: NodeId,
},
StringConcat {
left: NodeId,
right: NodeId,
},
StringEqual {
left: NodeId,
right: NodeId,
},
NumberToString {
value: NodeId,
feedback_slot: u32,
},
TypeOf {
value: NodeId,
},
Debugger,
Abort {
reason: u32,
},
}
#[derive(Debug, Clone, PartialEq)]
pub enum ControlNode {
Jump {
target: u32,
},
Branch {
condition: NodeId,
if_true: u32,
if_false: u32,
},
Deoptimize {
bytecode_offset: u32,
reason: u32,
},
Return {
value: NodeId,
},
}
#[derive(Debug, Clone)]
pub struct BasicBlock {
pub id: u32,
pub nodes: Vec<(NodeId, ValueNode)>,
pub control: Option<ControlNode>,
pub predecessors: Vec<u32>,
pub is_loop_header: bool,
}
impl BasicBlock {
pub fn new(id: u32) -> Self {
Self {
id,
nodes: Vec::new(),
control: None,
predecessors: Vec::new(),
is_loop_header: false,
}
}
pub fn push_value(&mut self, node: ValueNode) -> NodeId {
let id = NodeId(self.nodes.len() as u32);
self.nodes.push((id, node));
id
}
pub fn push_with_id(&mut self, id: NodeId, node: ValueNode) {
self.nodes.push((id, node));
}
pub fn set_control(&mut self, control: ControlNode) {
assert!(
self.control.is_none(),
"BasicBlock {}: control node already set",
self.id
);
self.control = Some(control);
}
pub fn is_complete(&self) -> bool {
self.control.is_some()
}
pub fn control(&self) -> Option<&ControlNode> {
self.control.as_ref()
}
pub fn add_predecessor(&mut self, pred: u32) {
self.predecessors.push(pred);
}
}
#[derive(Debug, Clone)]
pub struct MaglevGraph {
blocks: Vec<BasicBlock>,
parameter_count: u32,
next_node_id: u32,
inline_candidates: u32,
closure_fusion_patterns: std::collections::HashMap<u32, (u32, i64)>,
factory_fusion_patterns: std::collections::HashMap<u32, (u32, i64)>,
}
impl MaglevGraph {
pub fn new(parameter_count: u32) -> Self {
Self {
blocks: Vec::new(),
parameter_count,
next_node_id: 0,
inline_candidates: 0,
closure_fusion_patterns: std::collections::HashMap::new(),
factory_fusion_patterns: std::collections::HashMap::new(),
}
}
pub fn add_block(&mut self, block: BasicBlock) {
self.blocks.push(block);
}
pub fn add_value_node(&mut self, block_idx: u32, node: ValueNode) -> Option<NodeId> {
let id = NodeId(self.next_node_id);
self.next_node_id += 1;
let block = self.blocks.get_mut(block_idx as usize)?;
block.push_with_id(id, node);
Some(id)
}
pub fn alloc_node_id(&mut self) -> NodeId {
let id = NodeId(self.next_node_id);
self.next_node_id += 1;
id
}
pub fn blocks(&self) -> &[BasicBlock] {
&self.blocks
}
pub fn blocks_mut(&mut self) -> &mut [BasicBlock] {
&mut self.blocks
}
pub fn parameter_count(&self) -> u32 {
self.parameter_count
}
pub fn block(&self, index: u32) -> Option<&BasicBlock> {
self.blocks.get(index as usize)
}
pub fn block_mut(&mut self, index: u32) -> Option<&mut BasicBlock> {
self.blocks.get_mut(index as usize)
}
pub fn entry_block(&self) -> Option<&BasicBlock> {
self.blocks.first()
}
pub fn is_degenerate(&self) -> bool {
let Some(entry) = self.entry_block() else {
return true;
};
let Some(ctrl) = entry.control() else {
return false;
};
if !matches!(ctrl, ControlNode::Deoptimize { .. }) {
return false;
}
entry.nodes.iter().all(|(_, n)| {
matches!(
n,
ValueNode::SmiConstant { .. }
| ValueNode::Float64Constant { .. }
| ValueNode::UndefinedConstant
| ValueNode::TrueConstant
| ValueNode::FalseConstant
| ValueNode::NullConstant
)
})
}
pub fn set_inline_candidates(&mut self, count: u32) {
self.inline_candidates = count;
}
pub fn inline_candidates(&self) -> u32 {
self.inline_candidates
}
pub fn closure_fusion_patterns(&self) -> &std::collections::HashMap<u32, (u32, i64)> {
&self.closure_fusion_patterns
}
pub fn set_closure_fusion_pattern(&mut self, cp_idx: u32, slot: u32, k: i64) {
self.closure_fusion_patterns.insert(cp_idx, (slot, k));
}
pub fn factory_fusion_patterns(&self) -> &std::collections::HashMap<u32, (u32, i64)> {
&self.factory_fusion_patterns
}
pub fn set_factory_fusion_pattern(&mut self, cp_idx: u32, slot: u32, k: i64) {
self.factory_fusion_patterns.insert(cp_idx, (slot, k));
}
pub fn node(&self, id: NodeId) -> Option<&ValueNode> {
for block in &self.blocks {
for (nid, node) in &block.nodes {
if *nid == id {
return Some(node);
}
}
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
fn single_block_graph() -> MaglevGraph {
let mut graph = MaglevGraph::new(0);
let mut block = BasicBlock::new(0);
let undef = block.push_value(ValueNode::UndefinedConstant);
block.set_control(ControlNode::Return { value: undef });
graph.add_block(block);
graph
}
#[test]
fn test_basic_block_starts_empty() {
let block = BasicBlock::new(7);
assert_eq!(block.id, 7);
assert!(block.nodes.is_empty());
assert!(block.control.is_none());
assert!(!block.is_complete());
}
#[test]
fn test_basic_block_push_assigns_sequential_ids() {
let mut block = BasicBlock::new(0);
let a = block.push_value(ValueNode::SmiConstant { value: 1 });
let b = block.push_value(ValueNode::SmiConstant { value: 2 });
let c = block.push_value(ValueNode::SmiConstant { value: 3 });
assert_eq!(a, NodeId(0));
assert_eq!(b, NodeId(1));
assert_eq!(c, NodeId(2));
assert_eq!(block.nodes.len(), 3);
}
#[test]
fn test_basic_block_set_control_completes_block() {
let mut block = BasicBlock::new(0);
let undef = block.push_value(ValueNode::UndefinedConstant);
assert!(!block.is_complete());
block.set_control(ControlNode::Return { value: undef });
assert!(block.is_complete());
}
#[test]
#[should_panic(expected = "control node already set")]
fn test_basic_block_set_control_twice_panics() {
let mut block = BasicBlock::new(0);
let undef = block.push_value(ValueNode::UndefinedConstant);
block.set_control(ControlNode::Return { value: undef });
block.set_control(ControlNode::Return { value: NodeId(0) });
}
#[test]
fn test_basic_block_predecessors() {
let mut block = BasicBlock::new(3);
block.add_predecessor(0);
block.add_predecessor(1);
assert_eq!(block.predecessors, vec![0, 1]);
}
#[test]
fn test_graph_new_empty() {
let graph = MaglevGraph::new(3);
assert_eq!(graph.parameter_count(), 3);
assert!(graph.blocks().is_empty());
assert!(graph.entry_block().is_none());
assert!(graph.block(0).is_none());
}
#[test]
fn test_graph_add_and_retrieve_blocks() {
let graph = single_block_graph();
assert_eq!(graph.blocks().len(), 1);
let entry = graph.entry_block().unwrap();
assert_eq!(entry.id, 0);
}
#[test]
fn test_graph_block_out_of_range_returns_none() {
let graph = single_block_graph();
assert!(graph.block(99).is_none());
}
#[test]
fn test_smi_constant_node() {
let mut block = BasicBlock::new(0);
let id = block.push_value(ValueNode::SmiConstant { value: 42 });
let (nid, node) = &block.nodes[0];
assert_eq!(*nid, id);
assert_eq!(*node, ValueNode::SmiConstant { value: 42 });
}
#[test]
fn test_float64_constant_node() {
let mut block = BasicBlock::new(0);
let id = block.push_value(ValueNode::Float64Constant { value: 3.14 });
let (_, node) = &block.nodes[0];
assert_eq!(id, NodeId(0));
if let ValueNode::Float64Constant { value } = node {
assert!((value - 3.14).abs() < f64::EPSILON);
} else {
panic!("expected Float64Constant");
}
}
#[test]
fn test_true_false_null_undefined_constants() {
let mut block = BasicBlock::new(0);
block.push_value(ValueNode::TrueConstant);
block.push_value(ValueNode::FalseConstant);
block.push_value(ValueNode::NullConstant);
block.push_value(ValueNode::UndefinedConstant);
assert_eq!(block.nodes.len(), 4);
assert_eq!(block.nodes[0].1, ValueNode::TrueConstant);
assert_eq!(block.nodes[1].1, ValueNode::FalseConstant);
assert_eq!(block.nodes[2].1, ValueNode::NullConstant);
assert_eq!(block.nodes[3].1, ValueNode::UndefinedConstant);
}
#[test]
fn test_string_constant_node() {
let mut block = BasicBlock::new(0);
block.push_value(ValueNode::StringConstant {
value: "hello".to_string(),
});
if let ValueNode::StringConstant { value } = &block.nodes[0].1 {
assert_eq!(value, "hello");
} else {
panic!("expected StringConstant");
}
}
#[test]
fn test_parameter_node() {
let mut block = BasicBlock::new(0);
let id = block.push_value(ValueNode::Parameter { index: 2 });
assert_eq!(id, NodeId(0));
assert_eq!(block.nodes[0].1, ValueNode::Parameter { index: 2 });
}
#[test]
fn test_checked_smi_add_node() {
let mut block = BasicBlock::new(0);
let a = block.push_value(ValueNode::SmiConstant { value: 10 });
let b = block.push_value(ValueNode::SmiConstant { value: 20 });
let sum = block.push_value(ValueNode::CheckedSmiAdd { left: a, right: b });
assert_eq!(sum, NodeId(2));
if let ValueNode::CheckedSmiAdd { left, right } = block.nodes[2].1 {
assert_eq!(left, a);
assert_eq!(right, b);
} else {
panic!("expected CheckedSmiAdd");
}
}
#[test]
fn test_float64_arithmetic_nodes() {
let mut block = BasicBlock::new(0);
let x = block.push_value(ValueNode::Float64Constant { value: 1.0 });
let y = block.push_value(ValueNode::Float64Constant { value: 2.0 });
let add = block.push_value(ValueNode::Float64Add { left: x, right: y });
let sub = block.push_value(ValueNode::Float64Subtract { left: x, right: y });
let mul = block.push_value(ValueNode::Float64Multiply { left: x, right: y });
let div = block.push_value(ValueNode::Float64Divide { left: x, right: y });
assert_eq!(add, NodeId(2));
assert_eq!(sub, NodeId(3));
assert_eq!(mul, NodeId(4));
assert_eq!(div, NodeId(5));
}
#[test]
fn test_phi_node() {
let mut block = BasicBlock::new(2);
let a = NodeId(0);
let b = NodeId(1);
let phi = block.push_value(ValueNode::Phi { inputs: vec![a, b] });
assert_eq!(phi, NodeId(0));
if let ValueNode::Phi { inputs } = &block.nodes[0].1 {
assert_eq!(inputs, &[NodeId(0), NodeId(1)]);
} else {
panic!("expected Phi");
}
}
#[test]
fn test_load_field_node() {
let mut block = BasicBlock::new(0);
let obj = block.push_value(ValueNode::Parameter { index: 0 });
let field = block.push_value(ValueNode::LoadField {
object: obj,
offset: 8,
});
assert_eq!(field, NodeId(1));
assert_eq!(
block.nodes[1].1,
ValueNode::LoadField {
object: obj,
offset: 8
}
);
}
#[test]
fn test_store_field_node() {
let mut block = BasicBlock::new(0);
let obj = block.push_value(ValueNode::Parameter { index: 0 });
let val = block.push_value(ValueNode::SmiConstant { value: 99 });
let store = block.push_value(ValueNode::StoreField {
object: obj,
offset: 16,
value: val,
});
assert_eq!(store, NodeId(2));
}
#[test]
fn test_call_node() {
let mut block = BasicBlock::new(0);
let callee = block.push_value(ValueNode::Parameter { index: 0 });
let recv = block.push_value(ValueNode::UndefinedConstant);
let arg0 = block.push_value(ValueNode::SmiConstant { value: 1 });
let call = block.push_value(ValueNode::Call {
callee,
receiver: recv,
args: vec![arg0],
feedback_slot: 0,
});
assert_eq!(call, NodeId(3));
if let ValueNode::Call { args, .. } = &block.nodes[3].1 {
assert_eq!(args.len(), 1);
assert_eq!(args[0], arg0);
} else {
panic!("expected Call");
}
}
#[test]
fn test_control_jump() {
let mut block = BasicBlock::new(0);
block.push_value(ValueNode::UndefinedConstant);
block.set_control(ControlNode::Jump { target: 1 });
assert_eq!(block.control, Some(ControlNode::Jump { target: 1 }));
}
#[test]
fn test_control_branch() {
let mut block = BasicBlock::new(0);
let cond = block.push_value(ValueNode::TrueConstant);
block.set_control(ControlNode::Branch {
condition: cond,
if_true: 1,
if_false: 2,
});
if let Some(ControlNode::Branch {
condition,
if_true,
if_false,
}) = block.control
{
assert_eq!(condition, cond);
assert_eq!(if_true, 1);
assert_eq!(if_false, 2);
} else {
panic!("expected Branch");
}
}
#[test]
fn test_control_deoptimize() {
let mut block = BasicBlock::new(0);
block.push_value(ValueNode::UndefinedConstant);
block.set_control(ControlNode::Deoptimize {
bytecode_offset: 42,
reason: 7,
});
assert_eq!(
block.control,
Some(ControlNode::Deoptimize {
bytecode_offset: 42,
reason: 7,
})
);
}
#[test]
fn test_control_return() {
let mut block = BasicBlock::new(0);
let undef = block.push_value(ValueNode::UndefinedConstant);
block.set_control(ControlNode::Return { value: undef });
assert_eq!(
block.control,
Some(ControlNode::Return { value: NodeId(0) })
);
}
#[test]
fn test_construct_return_parameter_graph() {
let mut graph = MaglevGraph::new(1);
let mut entry = BasicBlock::new(0);
let p0 = entry.push_value(ValueNode::Parameter { index: 0 });
entry.set_control(ControlNode::Return { value: p0 });
graph.add_block(entry);
assert_eq!(graph.blocks().len(), 1);
let b = graph.block(0).unwrap();
assert_eq!(b.nodes.len(), 1);
assert!(b.is_complete());
}
#[test]
fn test_construct_two_block_graph_with_jump() {
let mut graph = MaglevGraph::new(0);
let mut b0 = BasicBlock::new(0);
b0.push_value(ValueNode::UndefinedConstant);
b0.set_control(ControlNode::Jump { target: 1 });
graph.add_block(b0);
let mut b1 = BasicBlock::new(1);
let undef = b1.push_value(ValueNode::UndefinedConstant);
b1.set_control(ControlNode::Return { value: undef });
graph.add_block(b1);
assert_eq!(graph.blocks().len(), 2);
assert_eq!(
graph.block(0).unwrap().control,
Some(ControlNode::Jump { target: 1 })
);
}
#[test]
fn test_construct_branch_graph() {
let mut graph = MaglevGraph::new(1);
let mut b0 = BasicBlock::new(0);
let cond = b0.push_value(ValueNode::Parameter { index: 0 });
b0.set_control(ControlNode::Branch {
condition: cond,
if_true: 1,
if_false: 2,
});
graph.add_block(b0);
let mut b1 = BasicBlock::new(1);
let one = b1.push_value(ValueNode::SmiConstant { value: 1 });
b1.set_control(ControlNode::Return { value: one });
b1.add_predecessor(0);
graph.add_block(b1);
let mut b2 = BasicBlock::new(2);
let zero = b2.push_value(ValueNode::SmiConstant { value: 0 });
b2.set_control(ControlNode::Return { value: zero });
b2.add_predecessor(0);
graph.add_block(b2);
assert_eq!(graph.blocks().len(), 3);
assert_eq!(graph.block(1).unwrap().predecessors, vec![0]);
assert_eq!(graph.block(2).unwrap().predecessors, vec![0]);
}
#[test]
fn test_construct_diamond_with_phi() {
let mut graph = MaglevGraph::new(1);
graph.add_block(BasicBlock::new(0));
let cond = graph
.add_value_node(0, ValueNode::Parameter { index: 0 })
.unwrap();
graph
.block_mut(0)
.unwrap()
.set_control(ControlNode::Branch {
condition: cond,
if_true: 1,
if_false: 2,
});
graph.add_block(BasicBlock::new(1));
graph.block_mut(1).unwrap().add_predecessor(0);
let one = graph
.add_value_node(1, ValueNode::SmiConstant { value: 1 })
.unwrap();
graph
.block_mut(1)
.unwrap()
.set_control(ControlNode::Jump { target: 3 });
graph.add_block(BasicBlock::new(2));
graph.block_mut(2).unwrap().add_predecessor(0);
let zero = graph
.add_value_node(2, ValueNode::SmiConstant { value: 0 })
.unwrap();
graph
.block_mut(2)
.unwrap()
.set_control(ControlNode::Jump { target: 3 });
graph.add_block(BasicBlock::new(3));
graph.block_mut(3).unwrap().add_predecessor(1);
graph.block_mut(3).unwrap().add_predecessor(2);
let phi = graph
.add_value_node(
3,
ValueNode::Phi {
inputs: vec![one, zero],
},
)
.unwrap();
graph
.block_mut(3)
.unwrap()
.set_control(ControlNode::Return { value: phi });
assert_ne!(cond, one);
assert_ne!(cond, zero);
assert_ne!(one, zero);
assert_ne!(phi, one);
assert_ne!(phi, zero);
assert_eq!(graph.blocks().len(), 4);
let merge = graph.block(3).unwrap();
assert_eq!(merge.predecessors, vec![1, 2]);
if let ValueNode::Phi { inputs } = &merge.nodes[0].1 {
assert_eq!(inputs, &[one, zero]);
} else {
panic!("expected Phi in merge block");
}
}
#[test]
fn test_add_value_node_global_ids_are_unique_across_blocks() {
let mut graph = MaglevGraph::new(0);
graph.add_block(BasicBlock::new(0));
graph.add_block(BasicBlock::new(1));
let a = graph
.add_value_node(0, ValueNode::UndefinedConstant)
.unwrap();
let b = graph
.add_value_node(0, ValueNode::UndefinedConstant)
.unwrap();
let c = graph
.add_value_node(1, ValueNode::UndefinedConstant)
.unwrap();
assert_ne!(a, b);
assert_ne!(a, c);
assert_ne!(b, c);
}
#[test]
fn test_add_value_node_out_of_range_returns_none() {
let mut graph = MaglevGraph::new(0);
assert!(
graph
.add_value_node(99, ValueNode::UndefinedConstant)
.is_none()
);
}
#[test]
fn test_graph_blocks_mut() {
let mut graph = single_block_graph();
graph.blocks_mut()[0].id = 99;
assert_eq!(graph.block(0).unwrap().id, 99);
}
#[test]
fn test_graph_block_mut() {
let mut graph = single_block_graph();
{
let b = graph.block_mut(0).unwrap();
b.add_predecessor(5);
}
assert_eq!(graph.block(0).unwrap().predecessors, vec![5]);
}
}