// 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 #include #include #include #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> 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& InputTypes() const { return *input_types_; } void SetInputTypes(const Stack& input_types); void Retype() { Stack current_stack = InputTypes(); for (const Instruction& instruction : instructions()) { instruction.TypeInstruction(¤t_stack, cfg_); } } std::vector& instructions() { return instructions_; } const std::vector& 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& input_definitions, Worklist* 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& InputDefinitions() const { DCHECK(HasInputDefinitions()); return *input_definitions_; } bool IsDead() const { return !HasInputDefinitions(); } private: ControlFlowGraph* cfg_; std::vector instructions_; base::Optional> input_types_; base::Optional> input_definitions_; const size_t id_; bool is_deferred_; }; class ControlFlowGraph { public: explicit ControlFlowGraph(Stack input_types) { start_ = NewBlock(std::move(input_types), false); PlaceBlock(start_); } Block* NewBlock(base::Optional> 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 void UnplaceBlockIf(UnaryPredicate&& predicate) { auto newEnd = std::remove_if(placed_blocks_.begin(), placed_blocks_.end(), std::forward(predicate)); placed_blocks_.erase(newEnd, placed_blocks_.end()); } Block* start() const { return start_; } base::Optional 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& 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 blocks_; Block* start_; std::vector placed_blocks_; base::Optional end_; base::Optional return_type_; size_t next_block_id_ = 0; }; class CfgAssembler { public: explicit CfgAssembler(Stack 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> 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& 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 type); void Poke(StackRange destination, StackRange origin, base::Optional 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 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 saved_stack_; Block* saved_block_; }; } // namespace torque } // namespace internal } // namespace v8 #endif // V8_TORQUE_CFG_H_