diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /third_party/rust/jsparagus-emitter/src/control_structures.rs | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/jsparagus-emitter/src/control_structures.rs')
-rw-r--r-- | third_party/rust/jsparagus-emitter/src/control_structures.rs | 708 |
1 files changed, 708 insertions, 0 deletions
diff --git a/third_party/rust/jsparagus-emitter/src/control_structures.rs b/third_party/rust/jsparagus-emitter/src/control_structures.rs new file mode 100644 index 0000000000..f19f423434 --- /dev/null +++ b/third_party/rust/jsparagus-emitter/src/control_structures.rs @@ -0,0 +1,708 @@ +use crate::ast_emitter::AstEmitter; +use crate::emitter::EmitError; +use crate::emitter::InstructionWriter; +use crate::emitter_scope::{EmitterScope, EmitterScopeDepth}; +use ast::source_atom_set::SourceAtomSetIndex; +use stencil::bytecode_offset::{BytecodeOffset, BytecodeOffsetDiff}; + +// Control structures + +#[derive(Debug)] +pub enum JumpKind { + Coalesce, + LogicalAnd, + LogicalOr, + JumpIfFalse, + Goto, +} + +trait Jump { + fn jump_kind(&mut self) -> &JumpKind { + &JumpKind::Goto + } + + fn should_fallthrough(&mut self) -> bool { + // a fallthrough occurs if the jump is a conditional jump and if the + // condition doesn't met, the execution goes to the next opcode + // instead of the target of the jump. + match self.jump_kind() { + JumpKind::Coalesce { .. } + | JumpKind::LogicalOr { .. } + | JumpKind::LogicalAnd { .. } + | JumpKind::JumpIfFalse { .. } => true, + + JumpKind::Goto { .. } => false, + } + } + + fn emit_jump(&mut self, emitter: &mut AstEmitter) { + // in the c++ bytecode emitter, the jumplist is emitted + // and four bytes are used in order to save memory. We are not using that + // here, so instead we are using a placeholder offset set to 0, which will + // be updated later in patch_and_emit_jump_target. + let placeholder_offset = BytecodeOffsetDiff::uninitialized(); + match self.jump_kind() { + JumpKind::Coalesce { .. } => { + emitter.emit.coalesce(placeholder_offset); + } + JumpKind::LogicalOr { .. } => { + emitter.emit.or_(placeholder_offset); + } + JumpKind::LogicalAnd { .. } => { + emitter.emit.and_(placeholder_offset); + } + JumpKind::JumpIfFalse { .. } => { + emitter.emit.jump_if_false(placeholder_offset); + } + JumpKind::Goto { .. } => { + emitter.emit.goto_(placeholder_offset); + } + } + + // The JITs rely on a jump target being emitted after the + // conditional jump + if self.should_fallthrough() { + emitter.emit.jump_target(); + } + } +} + +#[derive(Debug)] +#[must_use] +pub struct JumpPatchEmitter { + offsets: Vec<BytecodeOffset>, + depth: usize, +} + +impl JumpPatchEmitter { + pub fn patch_merge(self, emitter: &mut AstEmitter) { + emitter.emit.emit_jump_target_and_patch(&self.offsets); + + // If the previous opcode fall-through, it should have the same stack + // depth. + debug_assert!(emitter.emit.stack_depth() == self.depth); + } + + pub fn patch_not_merge(self, emitter: &mut AstEmitter) { + emitter.emit.emit_jump_target_and_patch(&self.offsets); + // If the previous opcode doesn't fall-through, overwrite the stack + // depth. + emitter.emit.set_stack_depth(self.depth); + } +} + +// Struct for emitting bytecode for forward jump. +#[derive(Debug)] +pub struct ForwardJumpEmitter { + pub jump: JumpKind, +} +impl Jump for ForwardJumpEmitter { + fn jump_kind(&mut self) -> &JumpKind { + &self.jump + } +} + +impl ForwardJumpEmitter { + pub fn emit(&mut self, emitter: &mut AstEmitter) -> JumpPatchEmitter { + let offsets = vec![emitter.emit.bytecode_offset()]; + self.emit_jump(emitter); + let depth = emitter.emit.stack_depth(); + + JumpPatchEmitter { offsets, depth } + } +} + +pub trait Breakable { + fn register_break(&mut self, offset: BytecodeOffset); + fn emit_break_target_and_patch(&mut self, emit: &mut InstructionWriter); +} + +pub trait Continuable { + fn register_continue(&mut self, offset: BytecodeOffset); + + fn emit_continue_target_and_patch(&mut self, emit: &mut InstructionWriter); +} + +#[derive(Debug, PartialEq)] +pub struct LoopControl { + enclosing_emitter_scope_depth: EmitterScopeDepth, + breaks: Vec<BytecodeOffset>, + continues: Vec<BytecodeOffset>, + head: BytecodeOffset, +} + +impl Breakable for LoopControl { + fn register_break(&mut self, offset: BytecodeOffset) { + // offset points to the location of the jump, which will need to be updated + // once we emit the jump target in emit_jump_target_and_patch + self.breaks.push(offset); + } + + fn emit_break_target_and_patch(&mut self, emit: &mut InstructionWriter) { + emit.emit_jump_target_and_patch(&self.breaks); + } +} + +impl Continuable for LoopControl { + fn register_continue(&mut self, offset: BytecodeOffset) { + // offset points to the location of the jump, which will need to be updated + // once we emit the jump target in emit_jump_target_and_patch + self.continues.push(offset); + } + + fn emit_continue_target_and_patch(&mut self, emit: &mut InstructionWriter) { + emit.emit_jump_target_and_patch(&self.continues); + } +} + +impl LoopControl { + pub fn new( + emit: &mut InstructionWriter, + depth: u8, + enclosing_emitter_scope_depth: EmitterScopeDepth, + ) -> Self { + let offset = LoopControl::open_loop(emit, depth); + Self { + enclosing_emitter_scope_depth, + breaks: Vec::new(), + continues: Vec::new(), + head: offset, + } + } + + fn open_loop(emit: &mut InstructionWriter, depth: u8) -> BytecodeOffset { + // Insert a Nop if needed to ensure the script does not start with a + // JSOp::LoopHead. This avoids JIT issues with prologue code + try notes + // or OSR. See bug 1602390 and bug 1602681. + let mut offset = emit.bytecode_offset(); + if offset.offset == 0 { + emit.nop(); + offset = emit.bytecode_offset(); + } + // emit the jump target for the loop head + emit.loop_head(depth); + offset + } + + pub fn close_loop(&mut self, emit: &mut InstructionWriter) { + let offset = emit.bytecode_offset(); + let diff_to_head = self.head.diff_from(offset); + + emit.goto_(diff_to_head); + } +} + +#[derive(Debug, PartialEq)] +pub struct LabelControl { + enclosing_emitter_scope_depth: EmitterScopeDepth, + name: SourceAtomSetIndex, + breaks: Vec<BytecodeOffset>, + head: BytecodeOffset, +} + +impl Breakable for LabelControl { + fn register_break(&mut self, offset: BytecodeOffset) { + // offset points to the location of the jump, which will need to be updated + // once we emit the jump target in emit_jump_target_and_patch + self.breaks.push(offset); + } + + fn emit_break_target_and_patch(&mut self, emit: &mut InstructionWriter) { + if !self.breaks.is_empty() { + emit.emit_jump_target_and_patch(&self.breaks); + } + } +} + +impl LabelControl { + pub fn new( + name: SourceAtomSetIndex, + emit: &mut InstructionWriter, + enclosing_emitter_scope_depth: EmitterScopeDepth, + ) -> Self { + let offset = emit.bytecode_offset(); + Self { + enclosing_emitter_scope_depth, + name, + head: offset, + breaks: Vec::new(), + } + } +} + +#[derive(Debug, PartialEq)] +pub enum Control { + Loop(LoopControl), + Label(LabelControl), +} + +impl Control { + fn enclosing_emitter_scope_depth(&self) -> EmitterScopeDepth { + match self { + Control::Loop(control) => control.enclosing_emitter_scope_depth, + Control::Label(control) => control.enclosing_emitter_scope_depth, + } + } +} + +// Compared to C++ impl, this uses explicit stack struct, +// given Rust cannot store a reference of stack-allocated object into +// another object that has longer-lifetime. +pub struct ControlStructureStack { + control_stack: Vec<Control>, +} + +impl ControlStructureStack { + pub fn new() -> Self { + Self { + control_stack: Vec::new(), + } + } + + pub fn open_loop( + &mut self, + emit: &mut InstructionWriter, + enclosing_emitter_scope_depth: EmitterScopeDepth, + ) { + let depth = (self.control_stack.len() + 1) as u8; + + let new_loop = Control::Loop(LoopControl::new(emit, depth, enclosing_emitter_scope_depth)); + + self.control_stack.push(new_loop); + } + + pub fn open_label( + &mut self, + name: SourceAtomSetIndex, + emit: &mut InstructionWriter, + enclosing_emitter_scope_depth: EmitterScopeDepth, + ) { + let new_label = LabelControl::new(name, emit, enclosing_emitter_scope_depth); + self.control_stack.push(Control::Label(new_label)); + } + + pub fn register_break(&mut self, offset: BytecodeOffset) { + let innermost = self.innermost(); + + match innermost { + Control::Label(control) => control.register_break(offset), + Control::Loop(control) => control.register_break(offset), + } + } + + pub fn register_continue(&mut self, offset: BytecodeOffset) { + let innermost = self.innermost(); + match innermost { + Control::Label(_) => panic!( + "Should not register continue on a label. This should be caught by early errors." + ), + Control::Loop(control) => control.register_continue(offset), + } + } + + pub fn register_labelled_break(&mut self, label: SourceAtomSetIndex, offset: BytecodeOffset) { + match self.find_labelled_control(label) { + Control::Label(control) => control.register_break(offset), + Control::Loop(control) => control.register_break(offset), + } + } + + pub fn register_labelled_continue( + &mut self, + label: SourceAtomSetIndex, + offset: BytecodeOffset, + ) { + if let Some(control) = self.find_labelled_loop(label) { + control.register_continue(offset); + } else { + panic!( + "A labelled continue was passed, but no label was found. This should be caught by early errors" + ) + } + } + + pub fn find_labelled_loop(&mut self, label: SourceAtomSetIndex) -> Option<&mut LoopControl> { + let label_index = self.find_labelled_index(label); + // To find the associated loop for a label, we can take the label's index + 1, as the + // associated loop should always be in the position after the label. + let control = self.control_stack.get_mut(label_index + 1); + match control { + Some(Control::Loop(control)) => Some(control), + _ => None, + } + } + + pub fn find_labelled_control(&mut self, label: SourceAtomSetIndex) -> &mut Control { + self.control_stack + .iter_mut() + .find(|control| match control { + Control::Label(control) => { + if control.name == label { + return true; + } + false + } + _ => false, + }) + .expect("there should be a control with this label") + } + + pub fn find_labelled_index(&mut self, label: SourceAtomSetIndex) -> usize { + self.control_stack + .iter() + .position(|control| match control { + Control::Label(control) => { + if control.name == label { + return true; + } + false + } + _ => false, + }) + .expect("there should be a control with this label") + } + + pub fn emit_continue_target_and_patch(&mut self, emit: &mut InstructionWriter) { + let innermost = self.innermost(); + match innermost { + Control::Label(_) => panic!( + "Should not emit continue on a label. This should be caught by JS early errors" + ), + Control::Loop(control) => control.emit_continue_target_and_patch(emit), + } + } + + fn pop_control(&mut self) -> Control { + self.control_stack + .pop() + .expect("There should be at least one control structure") + } + + pub fn close_loop(&mut self, emit: &mut InstructionWriter) { + let mut innermost = self.pop_control(); + match innermost { + Control::Label(_) => panic!("Tried to close a loop, found a label."), + Control::Loop(ref mut control) => { + control.close_loop(emit); + control.emit_break_target_and_patch(emit); + } + } + } + + pub fn close_label(&mut self, emit: &mut InstructionWriter) { + let mut innermost = self.pop_control(); + match innermost { + Control::Label(ref mut control) => control.emit_break_target_and_patch(emit), + Control::Loop(_) => panic!("Tried to close a label, found a loop."), + } + } + + pub fn innermost(&mut self) -> &mut Control { + self.control_stack + .last_mut() + .expect("There should be at least one loop") + } +} + +struct RegisteredJump<F1> +where + F1: Fn(&mut AstEmitter, BytecodeOffset), +{ + kind: JumpKind, + // This callback registers the bytecode offset of the jump in a list of bytecode offsets + // associated with a loop or a label. + register_offset: F1, +} + +impl<F1> Jump for RegisteredJump<F1> +where + F1: Fn(&mut AstEmitter, BytecodeOffset), +{ + fn jump_kind(&mut self) -> &JumpKind { + &self.kind + } +} + +impl<F1> RegisteredJump<F1> +where + F1: Fn(&mut AstEmitter, BytecodeOffset), +{ + pub fn emit(&mut self, emitter: &mut AstEmitter) { + let offset = emitter.emit.bytecode_offset(); + self.emit_jump(emitter); + (self.register_offset)(emitter, offset); + } +} + +// Struct for multiple jumps that point to the same target. Examples are breaks and loop conditions. +pub struct BreakEmitter { + pub label: Option<SourceAtomSetIndex>, +} + +impl BreakEmitter { + pub fn emit(&mut self, emitter: &mut AstEmitter) { + NonLocalExitControl { + registered_jump: RegisteredJump { + kind: JumpKind::Goto, + register_offset: |emitter, offset| match self.label { + Some(label) => emitter.control_stack.register_labelled_break(label, offset), + None => emitter.control_stack.register_break(offset), + }, + }, + } + .emit(emitter, self.label); + } +} + +pub struct ContinueEmitter { + pub label: Option<SourceAtomSetIndex>, +} + +impl ContinueEmitter { + pub fn emit(&mut self, emitter: &mut AstEmitter) { + NonLocalExitControl { + registered_jump: RegisteredJump { + kind: JumpKind::Goto, + register_offset: |emitter, offset| match self.label { + Some(label) => emitter + .control_stack + .register_labelled_continue(label, offset), + None => emitter.control_stack.register_continue(offset), + }, + }, + } + .emit(emitter, self.label); + } +} + +pub struct WhileEmitter<F1, F2> +where + F1: Fn(&mut AstEmitter) -> Result<(), EmitError>, + F2: Fn(&mut AstEmitter) -> Result<(), EmitError>, +{ + pub enclosing_emitter_scope_depth: EmitterScopeDepth, + pub test: F1, + pub block: F2, +} +impl<F1, F2> WhileEmitter<F1, F2> +where + F1: Fn(&mut AstEmitter) -> Result<(), EmitError>, + F2: Fn(&mut AstEmitter) -> Result<(), EmitError>, +{ + pub fn emit(&mut self, emitter: &mut AstEmitter) -> Result<(), EmitError> { + emitter + .control_stack + .open_loop(&mut emitter.emit, self.enclosing_emitter_scope_depth); + + (self.test)(emitter)?; + + // add a registered jump for the conditional statement + RegisteredJump { + kind: JumpKind::JumpIfFalse, + register_offset: |emitter, offset| emitter.control_stack.register_break(offset), + } + .emit(emitter); + + (self.block)(emitter)?; + + emitter + .control_stack + .emit_continue_target_and_patch(&mut emitter.emit); + + // Merge point + emitter.control_stack.close_loop(&mut emitter.emit); + Ok(()) + } +} + +pub struct DoWhileEmitter<F1, F2> +where + F1: Fn(&mut AstEmitter) -> Result<(), EmitError>, + F2: Fn(&mut AstEmitter) -> Result<(), EmitError>, +{ + pub enclosing_emitter_scope_depth: EmitterScopeDepth, + pub block: F2, + pub test: F1, +} +impl<F1, F2> DoWhileEmitter<F1, F2> +where + F1: Fn(&mut AstEmitter) -> Result<(), EmitError>, + F2: Fn(&mut AstEmitter) -> Result<(), EmitError>, +{ + pub fn emit(&mut self, emitter: &mut AstEmitter) -> Result<(), EmitError> { + emitter + .control_stack + .open_loop(&mut emitter.emit, self.enclosing_emitter_scope_depth); + + (self.block)(emitter)?; + + emitter + .control_stack + .emit_continue_target_and_patch(&mut emitter.emit); + + (self.test)(emitter)?; + + // add a registered jump for the conditional statement + RegisteredJump { + kind: JumpKind::JumpIfFalse, + register_offset: |emitter, offset| emitter.control_stack.register_break(offset), + } + .emit(emitter); + + // Merge point after cond fails + emitter.control_stack.close_loop(&mut emitter.emit); + Ok(()) + } +} + +pub struct CForEmitter<'a, CondT, ExprT, InitFn, TestFn, UpdateFn, BlockFn> +where + InitFn: Fn(&mut AstEmitter, &CondT) -> Result<(), EmitError>, + TestFn: Fn(&mut AstEmitter, &ExprT) -> Result<(), EmitError>, + UpdateFn: Fn(&mut AstEmitter, &ExprT) -> Result<(), EmitError>, + BlockFn: Fn(&mut AstEmitter) -> Result<(), EmitError>, +{ + pub enclosing_emitter_scope_depth: EmitterScopeDepth, + pub maybe_init: &'a Option<CondT>, + pub maybe_test: &'a Option<ExprT>, + pub maybe_update: &'a Option<ExprT>, + pub init: InitFn, + pub test: TestFn, + pub update: UpdateFn, + pub block: BlockFn, +} +impl<'a, CondT, ExprT, InitFn, TestFn, UpdateFn, BlockFn> + CForEmitter<'a, CondT, ExprT, InitFn, TestFn, UpdateFn, BlockFn> +where + InitFn: Fn(&mut AstEmitter, &CondT) -> Result<(), EmitError>, + TestFn: Fn(&mut AstEmitter, &ExprT) -> Result<(), EmitError>, + UpdateFn: Fn(&mut AstEmitter, &ExprT) -> Result<(), EmitError>, + BlockFn: Fn(&mut AstEmitter) -> Result<(), EmitError>, +{ + pub fn emit(&mut self, emitter: &mut AstEmitter) -> Result<(), EmitError> { + // Initialize the variable either by running an expression or assigning + // ie) for(foo(); <test>; <update>) or for(var x = 0; <test>; <update) + if let Some(init) = self.maybe_init { + (self.init)(emitter, init)?; + } + + // Emit loop head + emitter + .control_stack + .open_loop(&mut emitter.emit, self.enclosing_emitter_scope_depth); + + // if there is a test condition (ie x < 3) emit it + if let Some(test) = self.maybe_test { + (self.test)(emitter, &test)?; + + // add a registered jump for the conditional statement + RegisteredJump { + kind: JumpKind::JumpIfFalse, + register_offset: |emitter, offset| emitter.control_stack.register_break(offset), + } + .emit(emitter); + } + + // emit the body of the for loop. + (self.block)(emitter)?; + + // emit the target for any continues emitted in the body before evaluating + // the update (if there is one) and continuing from the top of the loop. + emitter + .control_stack + .emit_continue_target_and_patch(&mut emitter.emit); + + if let Some(update) = self.maybe_update { + (self.update)(emitter, &update)?; + } + + // Merge point after test fails (or there is a break statement) + emitter.control_stack.close_loop(&mut emitter.emit); + Ok(()) + } +} + +pub struct LabelEmitter<F1> +where + F1: Fn(&mut AstEmitter) -> Result<(), EmitError>, +{ + pub enclosing_emitter_scope_depth: EmitterScopeDepth, + pub name: SourceAtomSetIndex, + pub body: F1, +} + +impl<F1> LabelEmitter<F1> +where + F1: Fn(&mut AstEmitter) -> Result<(), EmitError>, +{ + pub fn emit(&mut self, emitter: &mut AstEmitter) -> Result<(), EmitError> { + emitter.control_stack.open_label( + self.name, + &mut emitter.emit, + self.enclosing_emitter_scope_depth, + ); + + (self.body)(emitter)?; + + emitter.control_stack.close_label(&mut emitter.emit); + Ok(()) + } +} + +pub struct NonLocalExitControl<F1> +where + F1: Fn(&mut AstEmitter, BytecodeOffset), +{ + registered_jump: RegisteredJump<F1>, +} + +impl<F1> NonLocalExitControl<F1> +where + F1: Fn(&mut AstEmitter, BytecodeOffset), +{ + pub fn emit(&mut self, emitter: &mut AstEmitter, label: Option<SourceAtomSetIndex>) { + // Step 1: find the enclosing emitter scope + let enclosing_emitter_scope_depth = match label { + Some(label) => emitter + .control_stack + .find_labelled_control(label) + .enclosing_emitter_scope_depth(), + None => emitter + .control_stack + .innermost() + .enclosing_emitter_scope_depth(), + }; + + // Step 2: find the current emitter scope + let mut parent_scope_note_index = emitter.scope_stack.get_current_scope_note_index(); + + // Step 3: iterate over scopes that have been entered since the + // enclosing scope, add a scope note hole for each one as we exit + let mut holes = Vec::new(); + for item in emitter + .scope_stack + .walk_up_to_including(enclosing_emitter_scope_depth) + { + // We're entering `item.outer` as a scope hole of `item.inner`. + + let hole_scope_note_index = match item.inner { + EmitterScope::Global(_) => panic!("global shouldn't be enclosed by other scope"), + EmitterScope::Lexical(scope) => emitter.emit.enter_scope_hole_from_lexical( + &item.outer.scope_note_index(), + parent_scope_note_index, + scope.has_environment_object(), + ), + }; + holes.push(hole_scope_note_index); + parent_scope_note_index = Some(hole_scope_note_index); + } + + // Step 4: perform the jump + self.registered_jump.emit(emitter); + + // Step 5: close each scope hole after the jump + for hole_scope_note_index in holes.iter() { + emitter.emit.leave_scope_hole(*hole_scope_note_index); + } + } +} |