| // Copyright 2018 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef V8_TORQUE_CFG_H_ |
| #define V8_TORQUE_CFG_H_ |
| |
| #include <list> |
| #include <memory> |
| #include <unordered_map> |
| #include <vector> |
| |
| #include "src/torque/ast.h" |
| #include "src/torque/instructions.h" |
| #include "src/torque/source-positions.h" |
| #include "src/torque/types.h" |
| |
| namespace v8 { |
| namespace internal { |
| namespace torque { |
| |
| class ControlFlowGraph; |
| |
| class Block { |
| public: |
| explicit Block(ControlFlowGraph* cfg, size_t id, |
| base::Optional<Stack<const Type*>> input_types, |
| bool is_deferred) |
| : cfg_(cfg), |
| input_types_(std::move(input_types)), |
| id_(id), |
| is_deferred_(is_deferred) {} |
| void Add(Instruction instruction) { |
| DCHECK(!IsComplete()); |
| instructions_.push_back(std::move(instruction)); |
| } |
| |
| bool HasInputTypes() const { return input_types_ != base::nullopt; } |
| const Stack<const Type*>& InputTypes() const { return *input_types_; } |
| void SetInputTypes(const Stack<const Type*>& input_types); |
| void Retype() { |
| Stack<const Type*> current_stack = InputTypes(); |
| for (const Instruction& instruction : instructions()) { |
| instruction.TypeInstruction(¤t_stack, cfg_); |
| } |
| } |
| |
| std::vector<Instruction>& instructions() { return instructions_; } |
| const std::vector<Instruction>& instructions() const { return instructions_; } |
| bool IsComplete() const { |
| return !instructions_.empty() && instructions_.back()->IsBlockTerminator(); |
| } |
| size_t id() const { return id_; } |
| bool IsDeferred() const { return is_deferred_; } |
| |
| void MergeInputDefinitions(const Stack<DefinitionLocation>& input_definitions, |
| Worklist<Block*>* worklist) { |
| if (!input_definitions_) { |
| input_definitions_ = input_definitions; |
| if (worklist) worklist->Enqueue(this); |
| return; |
| } |
| |
| DCHECK_EQ(input_definitions_->Size(), input_definitions.Size()); |
| bool changed = false; |
| for (BottomOffset i = {0}; i < input_definitions.AboveTop(); ++i) { |
| auto& current = input_definitions_->Peek(i); |
| auto& input = input_definitions.Peek(i); |
| if (current == input) continue; |
| if (current == DefinitionLocation::Phi(this, i.offset)) continue; |
| input_definitions_->Poke(i, DefinitionLocation::Phi(this, i.offset)); |
| changed = true; |
| } |
| |
| if (changed && worklist) worklist->Enqueue(this); |
| } |
| bool HasInputDefinitions() const { |
| return input_definitions_ != base::nullopt; |
| } |
| const Stack<DefinitionLocation>& InputDefinitions() const { |
| DCHECK(HasInputDefinitions()); |
| return *input_definitions_; |
| } |
| |
| bool IsDead() const { return !HasInputDefinitions(); } |
| |
| private: |
| ControlFlowGraph* cfg_; |
| std::vector<Instruction> instructions_; |
| base::Optional<Stack<const Type*>> input_types_; |
| base::Optional<Stack<DefinitionLocation>> input_definitions_; |
| const size_t id_; |
| bool is_deferred_; |
| }; |
| |
| class ControlFlowGraph { |
| public: |
| explicit ControlFlowGraph(Stack<const Type*> input_types) { |
| start_ = NewBlock(std::move(input_types), false); |
| PlaceBlock(start_); |
| } |
| |
| Block* NewBlock(base::Optional<Stack<const Type*>> input_types, |
| bool is_deferred) { |
| blocks_.emplace_back(this, next_block_id_++, std::move(input_types), |
| is_deferred); |
| return &blocks_.back(); |
| } |
| void PlaceBlock(Block* block) { placed_blocks_.push_back(block); } |
| template <typename UnaryPredicate> |
| void UnplaceBlockIf(UnaryPredicate&& predicate) { |
| auto newEnd = std::remove_if(placed_blocks_.begin(), placed_blocks_.end(), |
| std::forward<UnaryPredicate>(predicate)); |
| placed_blocks_.erase(newEnd, placed_blocks_.end()); |
| } |
| Block* start() const { return start_; } |
| base::Optional<Block*> end() const { return end_; } |
| void set_end(Block* end) { end_ = end; } |
| void SetReturnType(const Type* t) { |
| if (!return_type_) { |
| return_type_ = t; |
| return; |
| } |
| if (t != *return_type_) { |
| ReportError("expected return type ", **return_type_, " instead of ", *t); |
| } |
| } |
| const std::vector<Block*>& blocks() const { return placed_blocks_; } |
| size_t NumberOfBlockIds() const { return next_block_id_; } |
| std::size_t ParameterCount() const { |
| return start_ ? start_->InputTypes().Size() : 0; |
| } |
| |
| private: |
| std::list<Block> blocks_; |
| Block* start_; |
| std::vector<Block*> placed_blocks_; |
| base::Optional<Block*> end_; |
| base::Optional<const Type*> return_type_; |
| size_t next_block_id_ = 0; |
| }; |
| |
| class CfgAssembler { |
| public: |
| explicit CfgAssembler(Stack<const Type*> input_types) |
| : current_stack_(std::move(input_types)), cfg_(current_stack_) {} |
| |
| const ControlFlowGraph& Result() { |
| if (!CurrentBlockIsComplete()) { |
| cfg_.set_end(current_block_); |
| } |
| OptimizeCfg(); |
| DCHECK(CfgIsComplete()); |
| ComputeInputDefinitions(); |
| return cfg_; |
| } |
| |
| Block* NewBlock( |
| base::Optional<Stack<const Type*>> input_types = base::nullopt, |
| bool is_deferred = false) { |
| return cfg_.NewBlock(std::move(input_types), is_deferred); |
| } |
| |
| bool CurrentBlockIsComplete() const { return current_block_->IsComplete(); } |
| bool CfgIsComplete() const { |
| return std::all_of( |
| cfg_.blocks().begin(), cfg_.blocks().end(), [this](Block* block) { |
| return (cfg_.end() && *cfg_.end() == block) || block->IsComplete(); |
| }); |
| } |
| |
| void Emit(Instruction instruction) { |
| instruction.TypeInstruction(¤t_stack_, &cfg_); |
| current_block_->Add(std::move(instruction)); |
| } |
| |
| const Stack<const Type*>& CurrentStack() const { return current_stack_; } |
| |
| StackRange TopRange(size_t slot_count) const { |
| return CurrentStack().TopRange(slot_count); |
| } |
| |
| void Bind(Block* block); |
| void Goto(Block* block); |
| // Goto block while keeping {preserved_slots} many slots on the top and |
| // deleting additional the slots below these to match the input type of the |
| // target block. |
| // Returns the StackRange of the preserved slots in the target block. |
| StackRange Goto(Block* block, size_t preserved_slots); |
| // The condition must be of type bool and on the top of stack. It is removed |
| // from the stack before branching. |
| void Branch(Block* if_true, Block* if_false); |
| // Delete the specified range of slots, moving upper slots to fill the gap. |
| void DeleteRange(StackRange range); |
| void DropTo(BottomOffset new_level); |
| StackRange Peek(StackRange range, base::Optional<const Type*> type); |
| void Poke(StackRange destination, StackRange origin, |
| base::Optional<const Type*> type); |
| void Print(std::string s); |
| void AssertionFailure(std::string message); |
| void Unreachable(); |
| void DebugBreak(); |
| |
| void PrintCurrentStack(std::ostream& s) { s << "stack: " << current_stack_; } |
| void OptimizeCfg(); |
| void ComputeInputDefinitions(); |
| |
| private: |
| friend class CfgAssemblerScopedTemporaryBlock; |
| Stack<const Type*> current_stack_; |
| ControlFlowGraph cfg_; |
| Block* current_block_ = cfg_.start(); |
| }; |
| |
| class CfgAssemblerScopedTemporaryBlock { |
| public: |
| CfgAssemblerScopedTemporaryBlock(CfgAssembler* assembler, Block* block) |
| : assembler_(assembler), saved_block_(block) { |
| saved_stack_ = block->InputTypes(); |
| DCHECK(!assembler->CurrentBlockIsComplete()); |
| std::swap(saved_block_, assembler->current_block_); |
| std::swap(saved_stack_, assembler->current_stack_); |
| assembler->cfg_.PlaceBlock(block); |
| } |
| |
| ~CfgAssemblerScopedTemporaryBlock() { |
| DCHECK(assembler_->CurrentBlockIsComplete()); |
| std::swap(saved_block_, assembler_->current_block_); |
| std::swap(saved_stack_, assembler_->current_stack_); |
| } |
| |
| private: |
| CfgAssembler* assembler_; |
| Stack<const Type*> saved_stack_; |
| Block* saved_block_; |
| }; |
| |
| } // namespace torque |
| } // namespace internal |
| } // namespace v8 |
| |
| #endif // V8_TORQUE_CFG_H_ |