Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add loop and switch return values #2828

Merged
merged 2 commits into from
May 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions boa_ast/src/statement/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,17 @@ impl Statement {
_ => false,
}
}

/// Returns `true` if the statement returns a value.
#[inline]
#[must_use]
pub const fn returns_value(&self) -> bool {
match self {
Self::Block(block) if block.statement_list().statements().is_empty() => false,
Self::Empty | Self::Var(_) | Self::Break(_) | Self::Continue(_) => false,
_ => true,
}
}
}

impl ToIndentedString for Statement {
Expand Down
15 changes: 14 additions & 1 deletion boa_engine/src/bytecompiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -745,8 +745,17 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
/// Compile a [`StatementList`].
pub fn compile_statement_list(&mut self, list: &StatementList, use_expr: bool, block: bool) {
if use_expr {
let expr_index = list
let list_until_loop_exit: Vec<_> = list
.statements()
.iter()
.take_while(|item| {
!matches!(
item,
StatementListItem::Statement(Statement::Break(_) | Statement::Continue(_))
)
})
.collect();
let expr_index = list_until_loop_exit
.iter()
.rev()
.skip_while(|item| {
Expand All @@ -758,6 +767,10 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
})
.count();

if expr_index == 0 && !list.statements().is_empty() {
self.emit_opcode(Opcode::PushUndefined);
}

for (i, item) in list.statements().iter().enumerate() {
self.compile_stmt_list_item(item, i + 1 == expr_index, block);
}
Expand Down
24 changes: 17 additions & 7 deletions boa_engine/src/bytecompiler/statement/if.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,33 @@
use crate::bytecompiler::ByteCompiler;
use crate::{bytecompiler::ByteCompiler, vm::Opcode};
use boa_ast::statement::If;

impl ByteCompiler<'_, '_> {
pub(crate) fn compile_if(&mut self, node: &If, use_expr: bool) {
self.compile_expr(node.cond(), true);
let jelse = self.jump_if_false();

self.compile_stmt(node.body(), use_expr);
if !node.body().returns_value() {
self.emit_opcode(Opcode::PushUndefined);
}
self.compile_stmt(node.body(), true);

let exit = self.jump();
self.patch_jump(jelse);
match node.else_node() {
None => {
self.patch_jump(jelse);
self.emit_opcode(Opcode::PushUndefined);
}
Some(else_body) => {
let exit = self.jump();
self.patch_jump(jelse);
self.compile_stmt(else_body, use_expr);
self.patch_jump(exit);
if !else_body.returns_value() {
self.emit_opcode(Opcode::PushUndefined);
}
self.compile_stmt(else_body, true);
}
}
self.patch_jump(exit);

if !use_expr {
self.emit_opcode(Opcode::Pop);
}
}
}
10 changes: 5 additions & 5 deletions boa_engine/src/bytecompiler/statement/labelled.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,19 @@ impl ByteCompiler<'_, '_> {
match labelled.item() {
LabelledItem::Statement(stmt) => match stmt {
Statement::ForLoop(for_loop) => {
self.compile_for_loop(for_loop, Some(labelled.label()));
self.compile_for_loop(for_loop, Some(labelled.label()), use_expr);
}
Statement::ForInLoop(for_in_loop) => {
self.compile_for_in_loop(for_in_loop, Some(labelled.label()));
self.compile_for_in_loop(for_in_loop, Some(labelled.label()), use_expr);
}
Statement::ForOfLoop(for_of_loop) => {
self.compile_for_of_loop(for_of_loop, Some(labelled.label()));
self.compile_for_of_loop(for_of_loop, Some(labelled.label()), use_expr);
}
Statement::WhileLoop(while_loop) => {
self.compile_while_loop(while_loop, Some(labelled.label()));
self.compile_while_loop(while_loop, Some(labelled.label()), use_expr);
}
Statement::DoWhileLoop(do_while_loop) => {
self.compile_do_while_loop(do_while_loop, Some(labelled.label()));
self.compile_do_while_loop(do_while_loop, Some(labelled.label()), use_expr);
}
stmt => self.compile_stmt(stmt, use_expr),
},
Expand Down
114 changes: 83 additions & 31 deletions boa_engine/src/bytecompiler/statement/loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ use crate::{
};

impl ByteCompiler<'_, '_> {
pub(crate) fn compile_for_loop(&mut self, for_loop: &ForLoop, label: Option<Sym>) {
pub(crate) fn compile_for_loop(
&mut self,
for_loop: &ForLoop,
label: Option<Sym>,
use_expr: bool,
) {
self.push_compile_environment(false);
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
self.push_empty_loop_jump_control();
Expand Down Expand Up @@ -54,11 +59,8 @@ impl ByteCompiler<'_, '_> {
.expect("jump_control must exist as it was just pushed")
.set_start_address(start_address);

let (continue_start, continue_exit) =
self.emit_opcode_with_two_operands(Opcode::LoopContinue);

self.emit_opcode(Opcode::LoopContinue);
self.patch_jump_with_target(loop_start, start_address);
self.patch_jump_with_target(continue_start, start_address);

if let Some(final_expr) = for_loop.final_expr() {
self.compile_expr(final_expr, false);
Expand All @@ -73,7 +75,11 @@ impl ByteCompiler<'_, '_> {
}
let exit = self.jump_if_false();

self.compile_stmt(for_loop.body(), false);
if !for_loop.body().returns_value() {
self.emit_opcode(Opcode::PushUndefined);
}
self.compile_stmt(for_loop.body(), true);
self.emit_opcode(Opcode::LoopUpdateReturnValue);

self.emit(Opcode::Jump, &[start_address]);

Expand All @@ -83,13 +89,21 @@ impl ByteCompiler<'_, '_> {

self.patch_jump(exit);
self.patch_jump(loop_exit);
self.patch_jump(continue_exit);
self.pop_loop_control_info();
self.emit_opcode(Opcode::LoopEnd);

if !use_expr {
self.emit_opcode(Opcode::Pop);
}
self.emit_opcode(Opcode::PopEnvironment);
}

pub(crate) fn compile_for_in_loop(&mut self, for_in_loop: &ForInLoop, label: Option<Sym>) {
pub(crate) fn compile_for_in_loop(
&mut self,
for_in_loop: &ForInLoop,
label: Option<Sym>,
use_expr: bool,
) {
// Handle https://tc39.es/ecma262/#prod-annexB-ForInOfStatement
if let IterableLoopInitializer::Var(var) = for_in_loop.initializer() {
if let Binding::Identifier(ident) = var.binding() {
Expand Down Expand Up @@ -128,9 +142,7 @@ impl ByteCompiler<'_, '_> {
let (loop_start, exit_label) = self.emit_opcode_with_two_operands(Opcode::LoopStart);
let start_address = self.next_opcode_location();
self.push_loop_control_info_for_of_in_loop(label, start_address);
let (continue_label, cont_exit_label) =
self.emit_opcode_with_two_operands(Opcode::LoopContinue);
self.patch_jump_with_target(continue_label, start_address);
self.emit_opcode(Opcode::LoopContinue);
self.patch_jump_with_target(loop_start, start_address);

self.emit_opcode(Opcode::Pop); // pop the `done` value.
Expand Down Expand Up @@ -192,7 +204,11 @@ impl ByteCompiler<'_, '_> {
}
}

self.compile_stmt(for_in_loop.body(), false);
if !for_in_loop.body().returns_value() {
self.emit_opcode(Opcode::PushUndefined);
}
self.compile_stmt(for_in_loop.body(), true);
self.emit_opcode(Opcode::LoopUpdateReturnValue);

if let Some(iteration_environment) = iteration_environment {
let env_info = self.pop_compile_environment();
Expand All @@ -205,17 +221,30 @@ impl ByteCompiler<'_, '_> {

self.patch_jump(exit);
self.patch_jump(exit_label);
self.patch_jump(cont_exit_label);
self.pop_loop_control_info();
self.emit_opcode(Opcode::LoopEnd);
self.emit_opcode(Opcode::RotateRight);
self.emit_u8(4);
self.emit_opcode(Opcode::Pop);
self.emit_opcode(Opcode::Pop);
self.emit_opcode(Opcode::Pop);

let skip_early_exit = self.jump();
self.patch_jump(early_exit);
self.emit_opcode(Opcode::PushUndefined);
self.patch_jump(skip_early_exit);

if !use_expr {
self.emit_opcode(Opcode::Pop);
}
}

pub(crate) fn compile_for_of_loop(&mut self, for_of_loop: &ForOfLoop, label: Option<Sym>) {
pub(crate) fn compile_for_of_loop(
&mut self,
for_of_loop: &ForOfLoop,
label: Option<Sym>,
use_expr: bool,
) {
let initializer_bound_names = match for_of_loop.initializer() {
IterableLoopInitializer::Let(declaration)
| IterableLoopInitializer::Const(declaration) => bound_names(declaration),
Expand Down Expand Up @@ -248,10 +277,8 @@ impl ByteCompiler<'_, '_> {
let (loop_start, loop_exit) = self.emit_opcode_with_two_operands(Opcode::LoopStart);
let start_address = self.next_opcode_location();
self.push_loop_control_info_for_of_in_loop(label, start_address);
let (cont_label, cont_exit_label) =
self.emit_opcode_with_two_operands(Opcode::LoopContinue);
self.emit_opcode(Opcode::LoopContinue);
self.patch_jump_with_target(loop_start, start_address);
self.patch_jump_with_target(cont_label, start_address);

self.emit_opcode(Opcode::Pop); // pop the `done` value.
self.emit_opcode(Opcode::IteratorNext);
Expand Down Expand Up @@ -322,7 +349,11 @@ impl ByteCompiler<'_, '_> {
}
}

self.compile_stmt(for_of_loop.body(), false);
if !for_of_loop.body().returns_value() {
self.emit_opcode(Opcode::PushUndefined);
}
self.compile_stmt(for_of_loop.body(), true);
self.emit_opcode(Opcode::LoopUpdateReturnValue);

if let Some(iteration_environment) = iteration_environment {
let env_info = self.pop_compile_environment();
Expand All @@ -335,62 +366,83 @@ impl ByteCompiler<'_, '_> {

self.patch_jump(exit);
self.patch_jump(loop_exit);
self.patch_jump(cont_exit_label);
self.pop_loop_control_info();
self.emit_opcode(Opcode::LoopEnd);
self.emit_opcode(Opcode::RotateRight);
self.emit_u8(4);
self.iterator_close(for_of_loop.r#await());
if !use_expr {
self.emit_opcode(Opcode::Pop);
}
}

pub(crate) fn compile_while_loop(&mut self, while_loop: &WhileLoop, label: Option<Sym>) {
pub(crate) fn compile_while_loop(
&mut self,
while_loop: &WhileLoop,
label: Option<Sym>,
use_expr: bool,
) {
let (loop_start, loop_exit) = self.emit_opcode_with_two_operands(Opcode::LoopStart);
let start_address = self.next_opcode_location();
let (continue_start, continue_exit) =
self.emit_opcode_with_two_operands(Opcode::LoopContinue);

self.emit_opcode(Opcode::LoopContinue);
self.push_loop_control_info(label, start_address);
self.patch_jump_with_target(loop_start, start_address);
self.patch_jump_with_target(continue_start, start_address);

self.compile_expr(while_loop.condition(), true);
let exit = self.jump_if_false();
self.compile_stmt(while_loop.body(), false);

if !while_loop.body().returns_value() {
self.emit_opcode(Opcode::PushUndefined);
}
self.compile_stmt(while_loop.body(), true);
self.emit_opcode(Opcode::LoopUpdateReturnValue);

self.emit(Opcode::Jump, &[start_address]);

self.patch_jump(exit);
self.patch_jump(loop_exit);
self.patch_jump(continue_exit);
self.pop_loop_control_info();
self.emit_opcode(Opcode::LoopEnd);
if !use_expr {
self.emit_opcode(Opcode::Pop);
}
}

pub(crate) fn compile_do_while_loop(
&mut self,
do_while_loop: &DoWhileLoop,
label: Option<Sym>,
use_expr: bool,
) {
let (loop_start, loop_exit) = self.emit_opcode_with_two_operands(Opcode::LoopStart);
let initial_label = self.jump();

let start_address = self.next_opcode_location();

self.patch_jump_with_target(loop_start, start_address);
self.push_loop_control_info(label, start_address);

let condition_label_address = self.next_opcode_location();
let (continue_start, continue_exit) =
self.emit_opcode_with_two_operands(Opcode::LoopContinue);
self.patch_jump_with_target(continue_start, start_address);
self.emit_opcode(Opcode::LoopContinue);
self.compile_expr(do_while_loop.cond(), true);
let exit = self.jump_if_false();

self.patch_jump(initial_label);

self.compile_stmt(do_while_loop.body(), false);
if !do_while_loop.body().returns_value() {
self.emit_opcode(Opcode::PushUndefined);
}
self.compile_stmt(do_while_loop.body(), true);
self.emit_opcode(Opcode::LoopUpdateReturnValue);

self.emit(Opcode::Jump, &[condition_label_address]);
self.patch_jump(exit);
self.patch_jump(loop_exit);
self.patch_jump(continue_exit);

self.pop_loop_control_info();
self.emit_opcode(Opcode::LoopEnd);
if !use_expr {
self.emit_opcode(Opcode::Pop);
}
}
}
14 changes: 7 additions & 7 deletions boa_engine/src/bytecompiler/statement/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,19 @@ impl ByteCompiler<'_, '_> {
Statement::Var(var) => self.compile_var_decl(var),
Statement::If(node) => self.compile_if(node, use_expr),
Statement::ForLoop(for_loop) => {
self.compile_for_loop(for_loop, None);
self.compile_for_loop(for_loop, None, use_expr);
}
Statement::ForInLoop(for_in_loop) => {
self.compile_for_in_loop(for_in_loop, None);
self.compile_for_in_loop(for_in_loop, None, use_expr);
}
Statement::ForOfLoop(for_of_loop) => {
self.compile_for_of_loop(for_of_loop, None);
self.compile_for_of_loop(for_of_loop, None, use_expr);
}
Statement::WhileLoop(while_loop) => {
self.compile_while_loop(while_loop, None);
self.compile_while_loop(while_loop, None, use_expr);
}
Statement::DoWhileLoop(do_while_loop) => {
self.compile_do_while_loop(do_while_loop, None);
self.compile_do_while_loop(do_while_loop, None, use_expr);
}
Statement::Block(block) => {
self.compile_block(block, use_expr);
Expand All @@ -46,7 +46,7 @@ impl ByteCompiler<'_, '_> {
self.emit(Opcode::Throw, &[]);
}
Statement::Switch(switch) => {
self.compile_switch(switch);
self.compile_switch(switch, use_expr);
}
Statement::Return(ret) => {
if let Some(expr) = ret.target() {
Expand All @@ -58,7 +58,7 @@ impl ByteCompiler<'_, '_> {
}
Statement::Try(t) => self.compile_try(t, use_expr),
Statement::Expression(expr) => self.compile_expr(expr, use_expr),
Statement::With(with) => self.compile_with(with),
Statement::With(with) => self.compile_with(with, use_expr),
Statement::Empty => {}
}
}
Expand Down
Loading