Parcourir la source

LibJS/Bytecode: Keep completion value of switch case if falling through

We do this by moving the `LoadImmediate undefined` instruction to a
separate basic block which jumps to the case's block unconditionally.
We enter a case initially using this wrapper, but when falling through,
we directly jump to the next case's block.
Daniel Bertalan il y a 2 ans
Parent
commit
50d6b1ba19
1 fichiers modifiés avec 13 ajouts et 6 suppressions
  1. 13 6
      Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp

+ 13 - 6
Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp

@@ -2196,7 +2196,7 @@ Bytecode::CodeGenerationErrorOr<void> SwitchStatement::generate_labelled_evaluat
     TRY(m_discriminant->generate_bytecode(generator));
     generator.emit<Bytecode::Op::Store>(discriminant_reg);
     Vector<Bytecode::BasicBlock&> case_blocks;
-    Bytecode::BasicBlock* default_block { nullptr };
+    Bytecode::BasicBlock* entry_block_for_default { nullptr };
     Bytecode::BasicBlock* next_test_block = &generator.make_block();
 
     auto has_lexical_declarations = this->has_lexical_declarations();
@@ -2207,22 +2207,30 @@ Bytecode::CodeGenerationErrorOr<void> SwitchStatement::generate_labelled_evaluat
 
     for (auto& switch_case : m_cases) {
         auto& case_block = generator.make_block();
+        auto& case_entry_block = generator.make_block();
         if (switch_case->test()) {
             generator.switch_to_basic_block(*next_test_block);
             TRY(switch_case->test()->generate_bytecode(generator));
             generator.emit<Bytecode::Op::StrictlyEquals>(discriminant_reg);
             next_test_block = &generator.make_block();
-            generator.emit<Bytecode::Op::JumpConditional>().set_targets(Bytecode::Label { case_block }, Bytecode::Label { *next_test_block });
+            generator.emit<Bytecode::Op::JumpConditional>().set_targets(Bytecode::Label { case_entry_block }, Bytecode::Label { *next_test_block });
         } else {
-            default_block = &case_block;
+            entry_block_for_default = &case_entry_block;
         }
+
+        // Initialize the completion value of the switch statement to empty. We can't do this in the case's basic block directly,
+        // as we must not clobber the possible non-empty completion value of the previous case when falling through.
+        generator.switch_to_basic_block(case_entry_block);
+        generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
+        generator.emit<Bytecode::Op::Jump>().set_targets(Bytecode::Label { case_block }, {});
+
         case_blocks.append(case_block);
     }
     generator.switch_to_basic_block(*next_test_block);
     auto& end_block = generator.make_block();
 
-    if (default_block != nullptr) {
-        generator.emit<Bytecode::Op::Jump>().set_targets(Bytecode::Label { *default_block }, {});
+    if (entry_block_for_default != nullptr) {
+        generator.emit<Bytecode::Op::Jump>().set_targets(Bytecode::Label { *entry_block_for_default }, {});
     } else {
         generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
         generator.emit<Bytecode::Op::Jump>().set_targets(Bytecode::Label { end_block }, {});
@@ -2232,7 +2240,6 @@ Bytecode::CodeGenerationErrorOr<void> SwitchStatement::generate_labelled_evaluat
     for (auto& switch_case : m_cases) {
         generator.switch_to_basic_block(*current_block);
 
-        generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
         for (auto& statement : switch_case->children()) {
             TRY(statement->generate_bytecode(generator));
             if (generator.is_current_block_terminated())