From 4fa139b4b85b64f28953568ef8ea9f1fccad6280 Mon Sep 17 00:00:00 2001 From: Vyacheslav Egorov Date: Thu, 2 Aug 2018 10:51:53 +0000 Subject: [PATCH] [vm/compiler] Rework how logical expressions are compiled to IL. When logical expression is evaluated in the control context (e.g. if (cond) { ... }) avoid materializing a boolean value and then dispatching on it. Instead connect true and false successors directly to then and else branches. This CL also improves IL generated when logical expression is evaluated for value (e.g. x = (cond)): we similarly avoid materializing intermediate results and also avoid comparisons that are not needed, e.g. when evaluating x = A && B we construct graph x = A ? B : false, instead of x = A ? (B == true ? true : false) : false style of graph. Change-Id: I204d414cc6751949641b6c46423a6319f6e2d89b Reviewed-on: https://dart-review.googlesource.com/67562 Commit-Queue: Vyacheslav Egorov Reviewed-by: Alexander Markov --- runtime/platform/growable_array.h | 6 + .../frontend/base_flow_graph_builder.cc | 52 +++ .../frontend/base_flow_graph_builder.h | 64 +++- .../frontend/kernel_binary_flowgraph.cc | 308 +++++++++++++----- .../frontend/kernel_binary_flowgraph.h | 5 +- runtime/vm/compiler/frontend/kernel_to_il.h | 1 + 6 files changed, 340 insertions(+), 96 deletions(-) diff --git a/runtime/platform/growable_array.h b/runtime/platform/growable_array.h index f6c059865ce..75922621e3b 100644 --- a/runtime/platform/growable_array.h +++ b/runtime/platform/growable_array.h @@ -148,6 +148,12 @@ class BaseGrowableArray : public B { capacity_ = 0; } + T* begin() { return &data_[0]; } + const T* begin() const { return &data_[0]; } + + T* end() { return &data_[length_]; } + const T* end() const { return &data_[length_]; } + private: intptr_t length_; intptr_t capacity_; diff --git a/runtime/vm/compiler/frontend/base_flow_graph_builder.cc b/runtime/vm/compiler/frontend/base_flow_graph_builder.cc index 77d327ac3e4..84018011ca7 100644 --- a/runtime/vm/compiler/frontend/base_flow_graph_builder.cc +++ b/runtime/vm/compiler/frontend/base_flow_graph_builder.cc @@ -60,6 +60,53 @@ Fragment operator<<(const Fragment& fragment, Instruction* next) { return result; } +TestFragment::TestFragment(Instruction* entry, BranchInstr* branch) + : entry(entry), + true_successor_addresses(new SuccessorAddressArray(1)), + false_successor_addresses(new SuccessorAddressArray(1)) { + true_successor_addresses->Add(branch->true_successor_address()); + false_successor_addresses->Add(branch->false_successor_address()); +} + +void TestFragment::ConnectBranchesTo( + BaseFlowGraphBuilder* builder, + const TestFragment::SuccessorAddressArray& branches, + JoinEntryInstr* join) { + ASSERT(!branches.is_empty()); + for (auto branch : branches) { + *branch = builder->BuildTargetEntry(); + (*branch)->Goto(join); + } +} + +BlockEntryInstr* TestFragment::CreateSuccessorFor( + BaseFlowGraphBuilder* builder, + const TestFragment::SuccessorAddressArray& branches) { + ASSERT(!branches.is_empty()); + + if (branches.length() == 1) { + TargetEntryInstr* target = builder->BuildTargetEntry(); + *(branches[0]) = target; + return target; + } + + JoinEntryInstr* join = builder->BuildJoinEntry(); + ConnectBranchesTo(builder, branches, join); + return join; +} + +BlockEntryInstr* TestFragment::CreateTrueSuccessor( + BaseFlowGraphBuilder* builder) { + ASSERT(true_successor_addresses != nullptr); + return CreateSuccessorFor(builder, *true_successor_addresses); +} + +BlockEntryInstr* TestFragment::CreateFalseSuccessor( + BaseFlowGraphBuilder* builder) { + ASSERT(false_successor_addresses != nullptr); + return CreateSuccessorFor(builder, *false_successor_addresses); +} + Fragment BaseFlowGraphBuilder::LoadContextAt(int depth) { intptr_t delta = context_depth_ - depth; ASSERT(delta >= 0); @@ -376,6 +423,11 @@ void BaseFlowGraphBuilder::Push(Definition* definition) { Value::AddToList(new (Z) Value(definition), &stack_); } +Definition* BaseFlowGraphBuilder::Peek() { + ASSERT(stack_ != NULL); + return stack_->definition(); +} + Value* BaseFlowGraphBuilder::Pop() { ASSERT(stack_ != NULL); Value* value = stack_; diff --git a/runtime/vm/compiler/frontend/base_flow_graph_builder.h b/runtime/vm/compiler/frontend/base_flow_graph_builder.h index 50c87cb1684..90340d05fdf 100644 --- a/runtime/vm/compiler/frontend/base_flow_graph_builder.h +++ b/runtime/vm/compiler/frontend/base_flow_graph_builder.h @@ -19,16 +19,17 @@ class InlineExitCollector; namespace kernel { +class BaseFlowGraphBuilder; class TryCatchBlock; class Fragment { public: - Instruction* entry; - Instruction* current; + Instruction* entry = nullptr; + Instruction* current = nullptr; - Fragment() : entry(NULL), current(NULL) {} + Fragment() {} - Fragment(std::initializer_list list) : entry(NULL), current(NULL) { + Fragment(std::initializer_list list) { for (Fragment i : list) { *this += i; } @@ -40,7 +41,7 @@ class Fragment { Fragment(Instruction* entry, Instruction* current) : entry(entry), current(current) {} - bool is_open() { return entry == NULL || current != NULL; } + bool is_open() { return entry == nullptr || current != nullptr; } bool is_closed() { return !is_open(); } void Prepend(Instruction* start); @@ -54,6 +55,58 @@ class Fragment { Fragment operator+(const Fragment& first, const Fragment& second); Fragment operator<<(const Fragment& fragment, Instruction* next); +// IL fragment that performs some sort of test (comparison) and +// has a single entry and multiple true and false exits. +class TestFragment { + public: + BlockEntryInstr* CreateTrueSuccessor(BaseFlowGraphBuilder* builder); + BlockEntryInstr* CreateFalseSuccessor(BaseFlowGraphBuilder* builder); + + void IfTrueGoto(BaseFlowGraphBuilder* builder, JoinEntryInstr* join) { + ConnectBranchesTo(builder, *true_successor_addresses, join); + } + + // If negate is true then return negated fragment by flipping + // true and false successors. Otherwise return this fragment + // without change. + TestFragment Negate(bool negate) { + if (negate) { + return TestFragment(entry, false_successor_addresses, + true_successor_addresses); + } else { + return *this; + } + } + + typedef ZoneGrowableArray SuccessorAddressArray; + + // Create an empty fragment. + TestFragment() {} + + // Create a fragment with the given entry and true/false exits. + TestFragment(Instruction* entry, + SuccessorAddressArray* true_successor_addresses, + SuccessorAddressArray* false_successor_addresses) + : entry(entry), + true_successor_addresses(true_successor_addresses), + false_successor_addresses(false_successor_addresses) {} + + // Create a fragment with the given entry and a single branch as an exit. + TestFragment(Instruction* entry, BranchInstr* branch); + + void ConnectBranchesTo(BaseFlowGraphBuilder* builder, + const TestFragment::SuccessorAddressArray& branches, + JoinEntryInstr* join); + + BlockEntryInstr* CreateSuccessorFor( + BaseFlowGraphBuilder* builder, + const TestFragment::SuccessorAddressArray& branches); + + Instruction* entry = nullptr; + SuccessorAddressArray* true_successor_addresses = nullptr; + SuccessorAddressArray* false_successor_addresses = nullptr; +}; + typedef ZoneGrowableArray* ArgumentArray; class BaseFlowGraphBuilder { @@ -93,6 +146,7 @@ class BaseFlowGraphBuilder { StoreBarrierType emit_store_barrier = kEmitStoreBarrier); void Push(Definition* definition); + Definition* Peek(); Value* Pop(); Fragment Drop(); // Drop given number of temps from the stack but preserve top of the stack. diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc index 24e3fa69f5b..d9a20c9d7e7 100644 --- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc +++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc @@ -2385,15 +2385,72 @@ Fragment StreamingFlowGraphBuilder::ExitScope(intptr_t kernel_offset) { return flow_graph_builder_->ExitScope(kernel_offset); } -Fragment StreamingFlowGraphBuilder::TranslateCondition(bool* negate) { - *negate = PeekTag() == kNot; - if (*negate) { - SkipBytes(1); // Skip Not tag, thus go directly to the inner expression. +TestFragment StreamingFlowGraphBuilder::TranslateConditionForControl() { + // Skip all negations and go directly to the expression. + bool negate = false; + while (PeekTag() == kNot) { + SkipBytes(1); + negate = !negate; } - TokenPosition position = TokenPosition::kNoSource; - Fragment instructions = BuildExpression(&position); // read expression. - instructions += CheckBoolean(position); - return instructions; + + TestFragment result; + if (PeekTag() == kLogicalExpression) { + // Handle '&&' and '||' operators specially to implement short circuit + // evaluation. + SkipBytes(1); // tag. + + TestFragment left = TranslateConditionForControl(); + LogicalOperator op = static_cast(ReadByte()); + TestFragment right = TranslateConditionForControl(); + + result.entry = left.entry; + if (op == kAnd) { + left.CreateTrueSuccessor(flow_graph_builder_)->LinkTo(right.entry); + result.true_successor_addresses = right.true_successor_addresses; + result.false_successor_addresses = left.false_successor_addresses; + result.false_successor_addresses->AddArray( + *right.false_successor_addresses); + } else { + ASSERT(op == kOr); + left.CreateFalseSuccessor(flow_graph_builder_)->LinkTo(right.entry); + result.true_successor_addresses = left.true_successor_addresses; + result.true_successor_addresses->AddArray( + *right.true_successor_addresses); + result.false_successor_addresses = right.false_successor_addresses; + } + } else { + // Other expressions. + TokenPosition position = TokenPosition::kNoSource; + Fragment instructions = BuildExpression(&position); // read expression. + + // Check if the top of the stack is already a StrictCompare that + // can be merged with a branch. Otherwise compare TOS with + // true value and branch on that. + BranchInstr* branch; + if (stack()->definition()->IsStrictCompare() && + stack()->definition() == instructions.current) { + branch = new (Z) BranchInstr(Pop()->definition()->AsStrictCompare(), + flow_graph_builder_->GetNextDeoptId()); + branch->comparison()->ClearTempIndex(); + ASSERT(instructions.current->previous() != nullptr); + instructions.current = instructions.current->previous(); + } else { + instructions += CheckBoolean(position); + instructions += Constant(Bool::True()); + Value* right_value = Pop(); + Value* left_value = Pop(); + StrictCompareInstr* compare = new (Z) StrictCompareInstr( + TokenPosition::kNoSource, Token::kEQ_STRICT, left_value, right_value, + false, flow_graph_builder_->GetNextDeoptId()); + branch = + new (Z) BranchInstr(compare, flow_graph_builder_->GetNextDeoptId()); + } + instructions <<= branch; + + result = TestFragment(instructions.entry, branch); + } + + return result.Negate(negate); } const TypeArguments& StreamingFlowGraphBuilder::BuildTypeArguments() { @@ -3653,44 +3710,115 @@ Fragment StreamingFlowGraphBuilder::BuildNot(TokenPosition* position) { return instructions; } +// Translate the logical expression (lhs && rhs or lhs || rhs) in a context +// where a value is required. +// +// Translation accumulates short-circuit exits from logical +// subexpressions in the side_exits. These exits are expected to store +// true and false into :expr_temp. +// +// The result of evaluating the last +// expression in chain would be stored in :expr_temp directly to avoid +// generating graph like: +// +// if (v) :expr_temp = true; else :expr_temp = false; +// +// Outer negations are stripped and instead negation is passed down via +// negated parameter. +Fragment StreamingFlowGraphBuilder::TranslateLogicalExpressionForValue( + bool negated, + TestFragment* side_exits) { + TestFragment left = TranslateConditionForControl().Negate(negated); + LogicalOperator op = static_cast(ReadByte()); + if (negated) { + op = (op == kAnd) ? kOr : kAnd; + } + + // Short circuit the control flow after the left hand side condition. + if (op == kAnd) { + side_exits->false_successor_addresses->AddArray( + *left.false_successor_addresses); + } else { + side_exits->true_successor_addresses->AddArray( + *left.true_successor_addresses); + } + + // Skip negations of the right hand side. + while (PeekTag() == kNot) { + SkipBytes(1); + negated = !negated; + } + + Fragment right_value(op == kAnd + ? left.CreateTrueSuccessor(flow_graph_builder_) + : left.CreateFalseSuccessor(flow_graph_builder_)); + + if (PeekTag() == kLogicalExpression) { + SkipBytes(1); + // Handle nested logical expressions specially to avoid materializing + // intermediate boolean values. + right_value += TranslateLogicalExpressionForValue(negated, side_exits); + } else { + // Arbitrary expression on the right hand side. Translate it for value. + TokenPosition position = TokenPosition::kNoSource; + right_value += BuildExpression(&position); // read expression. + + // Check if the top of the stack is known to be a non-nullable boolean. + // Note that in strong mode we know that any value that reaches here + // is at least a nullable boolean - so there is no need to compare + // with true like in Dart 1. + Definition* top = stack()->definition(); + const bool is_bool = top->IsStrictCompare() || top->IsBooleanNegate(); + if (!is_bool) { + right_value += CheckBoolean(position); + if (!I->strong()) { + right_value += Constant(Bool::True()); + right_value += StrictCompare(Token::kEQ_STRICT); + } + } + if (negated) { + right_value += BooleanNegate(); + } + right_value += StoreLocal(TokenPosition::kNoSource, + parsed_function()->expression_temp_var()); + right_value += Drop(); + } + + return Fragment(left.entry, right_value.current); +} + Fragment StreamingFlowGraphBuilder::BuildLogicalExpression( TokenPosition* position) { if (position != NULL) *position = TokenPosition::kNoSource; - bool negate; - Fragment instructions = TranslateCondition(&negate); // read left. - - TargetEntryInstr* right_entry; - TargetEntryInstr* constant_entry; - LogicalOperator op = static_cast(ReadByte()); - - if (op == kAnd) { - instructions += BranchIfTrue(&right_entry, &constant_entry, negate); - } else { - instructions += BranchIfTrue(&constant_entry, &right_entry, negate); - } - - Value* top = stack(); - Fragment right_fragment(right_entry); - right_fragment += TranslateCondition(&negate); // read right. - - right_fragment += Constant(Bool::True()); - right_fragment += - StrictCompare(negate ? Token::kNE_STRICT : Token::kEQ_STRICT); - right_fragment += StoreLocal(TokenPosition::kNoSource, - parsed_function()->expression_temp_var()); - right_fragment += Drop(); - - ASSERT(top == stack()); - Fragment constant_fragment(constant_entry); - constant_fragment += Constant(Bool::Get(op == kOr)); - constant_fragment += StoreLocal(TokenPosition::kNoSource, - parsed_function()->expression_temp_var()); - constant_fragment += Drop(); + TestFragment exits; + exits.true_successor_addresses = new TestFragment::SuccessorAddressArray(2); + exits.false_successor_addresses = new TestFragment::SuccessorAddressArray(2); JoinEntryInstr* join = BuildJoinEntry(); - right_fragment += Goto(join); - constant_fragment += Goto(join); + Fragment instructions = + TranslateLogicalExpressionForValue(/*negated=*/false, &exits); + instructions += Goto(join); + + // Generate :expr_temp = true if needed and connect it to true side-exits. + if (!exits.true_successor_addresses->is_empty()) { + Fragment constant_fragment(exits.CreateTrueSuccessor(flow_graph_builder_)); + constant_fragment += Constant(Bool::Get(true)); + constant_fragment += StoreLocal(TokenPosition::kNoSource, + parsed_function()->expression_temp_var()); + constant_fragment += Drop(); + constant_fragment += Goto(join); + } + + // Generate :expr_temp = false if needed and connect it to false side-exits. + if (!exits.false_successor_addresses->is_empty()) { + Fragment constant_fragment(exits.CreateFalseSuccessor(flow_graph_builder_)); + constant_fragment += Constant(Bool::Get(false)); + constant_fragment += StoreLocal(TokenPosition::kNoSource, + parsed_function()->expression_temp_var()); + constant_fragment += Drop(); + constant_fragment += Goto(join); + } return Fragment(instructions.entry, join) + LoadLocal(parsed_function()->expression_temp_var()); @@ -3700,22 +3828,18 @@ Fragment StreamingFlowGraphBuilder::BuildConditionalExpression( TokenPosition* position) { if (position != NULL) *position = TokenPosition::kNoSource; - bool negate; - Fragment instructions = TranslateCondition(&negate); // read condition. - - TargetEntryInstr* then_entry; - TargetEntryInstr* otherwise_entry; - instructions += BranchIfTrue(&then_entry, &otherwise_entry, negate); + TestFragment condition = TranslateConditionForControl(); // read condition. Value* top = stack(); - Fragment then_fragment(then_entry); + Fragment then_fragment(condition.CreateTrueSuccessor(flow_graph_builder_)); then_fragment += BuildExpression(); // read then. then_fragment += StoreLocal(TokenPosition::kNoSource, parsed_function()->expression_temp_var()); then_fragment += Drop(); ASSERT(stack() == top); - Fragment otherwise_fragment(otherwise_entry); + Fragment otherwise_fragment( + condition.CreateFalseSuccessor(flow_graph_builder_)); otherwise_fragment += BuildExpression(); // read otherwise. otherwise_fragment += StoreLocal(TokenPosition::kNoSource, parsed_function()->expression_temp_var()); @@ -3728,7 +3852,7 @@ Fragment StreamingFlowGraphBuilder::BuildConditionalExpression( SkipOptionalDartType(); // read unused static type. - return Fragment(instructions.entry, join) + + return Fragment(condition.entry, join) + LoadLocal(parsed_function()->expression_temp_var()); } @@ -4413,31 +4537,28 @@ Fragment StreamingFlowGraphBuilder::BuildBreakStatement() { Fragment StreamingFlowGraphBuilder::BuildWhileStatement() { loop_depth_inc(); const TokenPosition position = ReadPosition(); // read position. + TestFragment condition = TranslateConditionForControl(); // read condition. + const Fragment body = BuildStatement(); // read body - bool negate; - Fragment condition = TranslateCondition(&negate); // read condition. - TargetEntryInstr* body_entry; - TargetEntryInstr* loop_exit; - condition += BranchIfTrue(&body_entry, &loop_exit, negate); - - Fragment body(body_entry); - body += BuildStatement(); // read body. + Fragment body_entry(condition.CreateTrueSuccessor(flow_graph_builder_)); + body_entry += body; Instruction* entry; - if (body.is_open()) { + if (body_entry.is_open()) { JoinEntryInstr* join = BuildJoinEntry(); - body += Goto(join); + body_entry += Goto(join); Fragment loop(join); loop += CheckStackOverflow(position); - loop += condition; - entry = new (Z) GotoInstr(join, Thread::Current()->GetNextDeoptId()); + loop.current->LinkTo(condition.entry); + + entry = Goto(join).entry; } else { entry = condition.entry; } loop_depth_dec(); - return Fragment(entry, loop_exit); + return Fragment(entry, condition.CreateFalseSuccessor(flow_graph_builder_)); } Fragment StreamingFlowGraphBuilder::BuildDoStatement() { @@ -4451,22 +4572,19 @@ Fragment StreamingFlowGraphBuilder::BuildDoStatement() { return body; } - bool negate; + TestFragment condition = TranslateConditionForControl(); + JoinEntryInstr* join = BuildJoinEntry(); Fragment loop(join); loop += CheckStackOverflow(position); loop += body; - loop += TranslateCondition(&negate); // read condition. - TargetEntryInstr* loop_repeat; - TargetEntryInstr* loop_exit; - loop += BranchIfTrue(&loop_repeat, &loop_exit, negate); + loop <<= condition.entry; - Fragment repeat(loop_repeat); - repeat += Goto(join); + condition.IfTrueGoto(flow_graph_builder_, join); loop_depth_dec(); return Fragment(new (Z) GotoInstr(join, Thread::Current()->GetNextDeoptId()), - loop_exit); + condition.CreateFalseSuccessor(flow_graph_builder_)); } Fragment StreamingFlowGraphBuilder::BuildForStatement() { @@ -4486,14 +4604,18 @@ Fragment StreamingFlowGraphBuilder::BuildForStatement() { declarations += BuildVariableDeclaration(); // read ith variable. } - bool negate = false; Tag tag = ReadTag(); // Read first part of condition. - Fragment condition = - tag == kNothing ? Constant(Bool::True()) - : TranslateCondition(&negate); // read rest of condition. - TargetEntryInstr* body_entry; - TargetEntryInstr* loop_exit; - condition += BranchIfTrue(&body_entry, &loop_exit, negate); + TestFragment condition; + BlockEntryInstr* body_entry; + BlockEntryInstr* loop_exit; + if (tag != kNothing) { + condition = TranslateConditionForControl(); + body_entry = condition.CreateTrueSuccessor(flow_graph_builder_); + loop_exit = condition.CreateFalseSuccessor(flow_graph_builder_); + } else { + body_entry = BuildJoinEntry(); + loop_exit = BuildJoinEntry(); + } Fragment updates; list_length = ReadListLength(); // read number of updates. @@ -4520,9 +4642,17 @@ Fragment StreamingFlowGraphBuilder::BuildForStatement() { Fragment loop(join); loop += CheckStackOverflow(position); - loop += condition; + if (condition.entry != nullptr) { + loop <<= condition.entry; + } else { + loop += Goto(body_entry->AsJoinEntry()); + } } else { - declarations += condition; + if (condition.entry != nullptr) { + declarations <<= condition.entry; + } else { + declarations += Goto(body_entry->AsJoinEntry()); + } } Fragment loop(declarations.entry, loop_exit); @@ -4830,17 +4960,15 @@ Fragment StreamingFlowGraphBuilder::BuildContinueSwitchStatement() { } Fragment StreamingFlowGraphBuilder::BuildIfStatement() { - bool negate; ReadPosition(); // read position. - Fragment instructions = TranslateCondition(&negate); // read condition. - TargetEntryInstr* then_entry; - TargetEntryInstr* otherwise_entry; - instructions += BranchIfTrue(&then_entry, &otherwise_entry, negate); - Fragment then_fragment(then_entry); + TestFragment condition = TranslateConditionForControl(); + + Fragment then_fragment(condition.CreateTrueSuccessor(flow_graph_builder_)); then_fragment += BuildStatement(); // read then. - Fragment otherwise_fragment(otherwise_entry); + Fragment otherwise_fragment( + condition.CreateFalseSuccessor(flow_graph_builder_)); otherwise_fragment += BuildStatement(); // read otherwise. if (then_fragment.is_open()) { @@ -4848,14 +4976,14 @@ Fragment StreamingFlowGraphBuilder::BuildIfStatement() { JoinEntryInstr* join = BuildJoinEntry(); then_fragment += Goto(join); otherwise_fragment += Goto(join); - return Fragment(instructions.entry, join); + return Fragment(condition.entry, join); } else { - return Fragment(instructions.entry, then_fragment.current); + return Fragment(condition.entry, then_fragment.current); } } else if (otherwise_fragment.is_open()) { - return Fragment(instructions.entry, otherwise_fragment.current); + return Fragment(condition.entry, otherwise_fragment.current); } else { - return instructions.closed(); + return Fragment(condition.entry, nullptr); } } diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h index 110a68eb522..57fac193750 100644 --- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h +++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h @@ -242,7 +242,8 @@ class StreamingFlowGraphBuilder : public KernelReaderHelper { intptr_t* num_context_variables = NULL); Fragment ExitScope(intptr_t kernel_offset); - Fragment TranslateCondition(bool* negate); + TestFragment TranslateConditionForControl(); + const TypeArguments& BuildTypeArguments(); Fragment BuildArguments(Array* argument_names, intptr_t* argument_count, @@ -280,6 +281,8 @@ class StreamingFlowGraphBuilder : public KernelReaderHelper { Fragment BuildConstructorInvocation(bool is_const, TokenPosition* position); Fragment BuildNot(TokenPosition* position); Fragment BuildLogicalExpression(TokenPosition* position); + Fragment TranslateLogicalExpressionForValue(bool negated, + TestFragment* side_exits); Fragment BuildConditionalExpression(TokenPosition* position); Fragment BuildStringConcatenation(TokenPosition* position); Fragment BuildIsExpression(TokenPosition* position); diff --git a/runtime/vm/compiler/frontend/kernel_to_il.h b/runtime/vm/compiler/frontend/kernel_to_il.h index c91ffaf803f..7ff9170eadf 100644 --- a/runtime/vm/compiler/frontend/kernel_to_il.h +++ b/runtime/vm/compiler/frontend/kernel_to_il.h @@ -22,6 +22,7 @@ class InlineExitCollector; namespace kernel { +class BaseFlowGraphBuilder; class StreamingFlowGraphBuilder; struct InferredTypeMetadata; class BreakableBlock;