mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 19:52:45 +00:00 
			
		
		
		
	LibJS: Add basic support for while loops in the bytecode engine
This introduces two new instructions: Jump and JumpIfFalse. Jumps are made to a Bytecode::Label, which is a simple object that represents a location in the bytecode stream. Note that you may not always know the target of a jump when adding the jump instruction itself, but we can just update the instruction later on during codegen once we know where the jump target is. The Bytecode::Interpreter now implements jumping via a jump slot that gets checked after each instruction to see if a jump is pending. If not, we just increment the PC as usual.
This commit is contained in:
		
							parent
							
								
									91640d0727
								
							
						
					
					
						commit
						6ae9346cd3
					
				
					 9 changed files with 128 additions and 1 deletions
				
			
		|  | @ -370,6 +370,7 @@ public: | |||
| 
 | ||||
|     virtual Value execute(Interpreter&, GlobalObject&) const override; | ||||
|     virtual void dump(int indent) const override; | ||||
|     virtual Optional<Bytecode::Register> generate_bytecode(Bytecode::Generator&) const override; | ||||
| 
 | ||||
| private: | ||||
|     NonnullRefPtr<Expression> m_test; | ||||
|  |  | |||
|  | @ -90,4 +90,16 @@ Optional<Bytecode::Register> AssignmentExpression::generate_bytecode(Bytecode::G | |||
|     TODO(); | ||||
| } | ||||
| 
 | ||||
| Optional<Bytecode::Register> WhileStatement::generate_bytecode(Bytecode::Generator& generator) const | ||||
| { | ||||
|     auto test_label = generator.make_label(); | ||||
|     auto test_result_reg = m_test->generate_bytecode(generator); | ||||
|     VERIFY(test_result_reg.has_value()); | ||||
|     auto& test_jump = generator.emit<Bytecode::Op::JumpIfFalse>(*test_result_reg); | ||||
|     auto body_result_reg = m_body->generate_bytecode(generator); | ||||
|     generator.emit<Bytecode::Op::Jump>(test_label); | ||||
|     test_jump.set_target(generator.make_label()); | ||||
|     return body_result_reg; | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -42,4 +42,9 @@ Register Generator::allocate_register() | |||
|     return Register { m_next_register++ }; | ||||
| } | ||||
| 
 | ||||
| Label Generator::make_label() const | ||||
| { | ||||
|     return Label { m_block->instructions().size() }; | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <AK/OwnPtr.h> | ||||
| #include <LibJS/Bytecode/Label.h> | ||||
| #include <LibJS/Forward.h> | ||||
| 
 | ||||
| namespace JS::Bytecode { | ||||
|  | @ -26,6 +27,8 @@ public: | |||
|         return *ptr; | ||||
|     } | ||||
| 
 | ||||
|     Label make_label() const; | ||||
| 
 | ||||
| private: | ||||
|     Generator(); | ||||
|     ~Generator(); | ||||
|  |  | |||
|  | @ -27,8 +27,16 @@ void Interpreter::run(Bytecode::Block const& block) | |||
| 
 | ||||
|     m_registers.resize(block.register_count()); | ||||
| 
 | ||||
|     for (auto& instruction : block.instructions()) | ||||
|     size_t pc = 0; | ||||
|     while (pc < block.instructions().size()) { | ||||
|         auto& instruction = block.instructions()[pc]; | ||||
|         instruction.execute(*this); | ||||
|         if (m_pending_jump.has_value()) { | ||||
|             pc = m_pending_jump.release_value(); | ||||
|             continue; | ||||
|         } | ||||
|         ++pc; | ||||
|     } | ||||
| 
 | ||||
|     dbgln("Bytecode::Interpreter did run block {:p}", &block); | ||||
|     for (size_t i = 0; i < m_registers.size(); ++i) { | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ | |||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <LibJS/Bytecode/Label.h> | ||||
| #include <LibJS/Bytecode/Register.h> | ||||
| #include <LibJS/Forward.h> | ||||
| #include <LibJS/Heap/Cell.h> | ||||
|  | @ -25,10 +26,13 @@ public: | |||
| 
 | ||||
|     Value& reg(Register const& r) { return m_registers[r.index()]; } | ||||
| 
 | ||||
|     void jump(Label const& label) { m_pending_jump = label.address(); } | ||||
| 
 | ||||
| private: | ||||
|     VM& m_vm; | ||||
|     GlobalObject& m_global_object; | ||||
|     Vector<Value> m_registers; | ||||
|     Optional<size_t> m_pending_jump; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  |  | |||
							
								
								
									
										34
									
								
								Userland/Libraries/LibJS/Bytecode/Label.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								Userland/Libraries/LibJS/Bytecode/Label.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,34 @@ | |||
| /*
 | ||||
|  * Copyright (c) 2021, Andreas Kling <kling@serenityos.org> | ||||
|  * | ||||
|  * SPDX-License-Identifier: BSD-2-Clause | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <AK/Format.h> | ||||
| 
 | ||||
| namespace JS::Bytecode { | ||||
| 
 | ||||
| class Label { | ||||
| public: | ||||
|     explicit Label(size_t address) | ||||
|         : m_address(address) | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     size_t address() const { return m_address; } | ||||
| 
 | ||||
| private: | ||||
|     size_t m_address { 0 }; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| template<> | ||||
| struct AK::Formatter<JS::Bytecode::Label> : AK::Formatter<FormatString> { | ||||
|     void format(FormatBuilder& builder, JS::Bytecode::Label const& value) | ||||
|     { | ||||
|         return AK::Formatter<FormatString>::format(builder, "@{}", value.address()); | ||||
|     } | ||||
| }; | ||||
|  | @ -46,6 +46,19 @@ void SetVariable::execute(Bytecode::Interpreter& interpreter) const | |||
|     interpreter.vm().set_variable(m_identifier, interpreter.reg(m_src), interpreter.global_object()); | ||||
| } | ||||
| 
 | ||||
| void Jump::execute(Bytecode::Interpreter& interpreter) const | ||||
| { | ||||
|     interpreter.jump(m_target); | ||||
| } | ||||
| 
 | ||||
| void JumpIfFalse::execute(Bytecode::Interpreter& interpreter) const | ||||
| { | ||||
|     VERIFY(m_target.has_value()); | ||||
|     auto result = interpreter.reg(m_result); | ||||
|     if (!result.as_bool()) | ||||
|         interpreter.jump(m_target.value()); | ||||
| } | ||||
| 
 | ||||
| String Load::to_string() const | ||||
| { | ||||
|     return String::formatted("Load dst:{}, value:{}", m_dst, m_value.to_string_without_side_effects()); | ||||
|  | @ -81,4 +94,16 @@ String SetVariable::to_string() const | |||
|     return String::formatted("SetVariable identifier:{}, src:{}", m_identifier, m_src); | ||||
| } | ||||
| 
 | ||||
| String Jump::to_string() const | ||||
| { | ||||
|     return String::formatted("Jump {}", m_target); | ||||
| } | ||||
| 
 | ||||
| String JumpIfFalse::to_string() const | ||||
| { | ||||
|     if (m_target.has_value()) | ||||
|         return String::formatted("JumpIfFalse result:{}, target:{}", m_result, m_target.value()); | ||||
|     return String::formatted("JumpIfFalse result:{}, target:<empty>", m_result); | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ | |||
| 
 | ||||
| #include <AK/FlyString.h> | ||||
| #include <LibJS/Bytecode/Instruction.h> | ||||
| #include <LibJS/Bytecode/Label.h> | ||||
| #include <LibJS/Bytecode/Register.h> | ||||
| #include <LibJS/Heap/Cell.h> | ||||
| #include <LibJS/Runtime/Value.h> | ||||
|  | @ -139,4 +140,38 @@ private: | |||
|     FlyString m_identifier; | ||||
| }; | ||||
| 
 | ||||
| class Jump final : public Instruction { | ||||
| public: | ||||
|     explicit Jump(Label target) | ||||
|         : m_target(target) | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     virtual ~Jump() override { } | ||||
|     virtual void execute(Bytecode::Interpreter&) const override; | ||||
|     virtual String to_string() const override; | ||||
| 
 | ||||
| private: | ||||
|     Label m_target; | ||||
| }; | ||||
| 
 | ||||
| class JumpIfFalse final : public Instruction { | ||||
| public: | ||||
|     explicit JumpIfFalse(Register result, Optional<Label> target = {}) | ||||
|         : m_result(result) | ||||
|         , m_target(move(target)) | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     void set_target(Optional<Label> target) { m_target = move(target); } | ||||
| 
 | ||||
|     virtual ~JumpIfFalse() override { } | ||||
|     virtual void execute(Bytecode::Interpreter&) const override; | ||||
|     virtual String to_string() const override; | ||||
| 
 | ||||
| private: | ||||
|     Register m_result; | ||||
|     Optional<Label> m_target; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Andreas Kling
						Andreas Kling