diff options
author | Nicolas Geoffray <ngeoffray@google.com> | 2015-02-03 18:12:44 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2015-02-03 18:12:44 +0000 |
commit | f9af19413333c271192c3b11425f865bd8054c0c (patch) | |
tree | fdef0e2d95396aedae484dc5163689f282a8dd12 | |
parent | bd17c3b46b035861ced434d6ed4d4fe183736e65 (diff) | |
parent | 276d9daaedfbff716339f94d55e6eff98b7434c6 (diff) | |
download | art-f9af19413333c271192c3b11425f865bd8054c0c.zip art-f9af19413333c271192c3b11425f865bd8054c0c.tar.gz art-f9af19413333c271192c3b11425f865bd8054c0c.tar.bz2 |
Merge "Inline methods with multiple blocks."
-rw-r--r-- | compiler/optimizing/graph_checker.cc | 3 | ||||
-rw-r--r-- | compiler/optimizing/inliner.cc | 12 | ||||
-rw-r--r-- | compiler/optimizing/nodes.cc | 266 | ||||
-rw-r--r-- | compiler/optimizing/nodes.h | 70 | ||||
-rw-r--r-- | compiler/optimizing/side_effects_analysis.cc | 5 | ||||
-rw-r--r-- | test/447-checker-inliner3/expected.txt | 0 | ||||
-rw-r--r-- | test/447-checker-inliner3/info.txt | 1 | ||||
-rw-r--r-- | test/447-checker-inliner3/src/Main.java | 77 |
8 files changed, 388 insertions, 46 deletions
diff --git a/compiler/optimizing/graph_checker.cc b/compiler/optimizing/graph_checker.cc index 35c5269..4ebb136 100644 --- a/compiler/optimizing/graph_checker.cc +++ b/compiler/optimizing/graph_checker.cc @@ -153,8 +153,9 @@ void GraphChecker::VisitInstruction(HInstruction* instruction) { ? use->GetBlock()->GetPhis() : use->GetBlock()->GetInstructions(); if (!list.Contains(use)) { - AddError(StringPrintf("User %d of instruction %d is not defined " + AddError(StringPrintf("User %s:%d of instruction %d is not defined " "in a basic block of the control-flow graph.", + use->DebugName(), use->GetId(), instruction->GetId())); } diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc index 513be7d..41e5164 100644 --- a/compiler/optimizing/inliner.cc +++ b/compiler/optimizing/inliner.cc @@ -35,7 +35,6 @@ namespace art { static constexpr int kMaxInlineCodeUnits = 100; -static constexpr int kMaxInlineNumberOfBlocks = 3; static constexpr int kDepthLimit = 5; void HInliner::Run() { @@ -140,13 +139,6 @@ bool HInliner::TryInline(HInvoke* invoke_instruction, return false; } - if (callee_graph->GetBlocks().Size() > kMaxInlineNumberOfBlocks) { - VLOG(compiler) << "Method " << PrettyMethod(method_index, outer_dex_file) - << " has too many blocks to be inlined: " - << callee_graph->GetBlocks().Size(); - return false; - } - if (!RegisterAllocator::CanAllocateRegistersFor(*callee_graph, compiler_driver_->GetInstructionSet())) { VLOG(compiler) << "Method " << PrettyMethod(method_index, outer_dex_file) @@ -200,6 +192,10 @@ bool HInliner::TryInline(HInvoke* invoke_instruction, !instr_it.Done(); instr_it.Advance()) { HInstruction* current = instr_it.Current(); + if (current->IsSuspendCheck()) { + continue; + } + if (current->CanThrow()) { VLOG(compiler) << "Method " << PrettyMethod(method_index, outer_dex_file) << " could not be inlined because " << current->DebugName() diff --git a/compiler/optimizing/nodes.cc b/compiler/optimizing/nodes.cc index 5fd75f6..f1868cb 100644 --- a/compiler/optimizing/nodes.cc +++ b/compiler/optimizing/nodes.cc @@ -292,6 +292,10 @@ bool HGraph::AnalyzeNaturalLoops() const { return true; } +void HLoopInformation::Add(HBasicBlock* block) { + blocks_.SetBit(block->GetBlockId()); +} + void HLoopInformation::PopulateRecursive(HBasicBlock* block) { if (blocks_.IsBitSet(block->GetBlockId())) { return; @@ -730,10 +734,121 @@ void HInstruction::MoveBefore(HInstruction* cursor) { } } -void HGraph::InlineInto(HGraph* outer_graph, HInvoke* invoke) { - // We currently only support graphs with one entry block, one body block, and one exit block. - DCHECK_EQ(GetBlocks().Size(), 3u); +HBasicBlock* HBasicBlock::SplitAfter(HInstruction* cursor) { + DCHECK(!cursor->IsControlFlow()); + DCHECK_NE(instructions_.last_instruction_, cursor); + DCHECK_EQ(cursor->GetBlock(), this); + + HBasicBlock* new_block = new (GetGraph()->GetArena()) HBasicBlock(GetGraph(), GetDexPc()); + new_block->instructions_.first_instruction_ = cursor->GetNext(); + new_block->instructions_.last_instruction_ = instructions_.last_instruction_; + cursor->next_->previous_ = nullptr; + cursor->next_ = nullptr; + instructions_.last_instruction_ = cursor; + + new_block->instructions_.SetBlockOfInstructions(new_block); + for (size_t i = 0, e = GetSuccessors().Size(); i < e; ++i) { + HBasicBlock* successor = GetSuccessors().Get(i); + new_block->successors_.Add(successor); + successor->predecessors_.Put(successor->GetPredecessorIndexOf(this), new_block); + } + successors_.Reset(); + + for (size_t i = 0, e = GetDominatedBlocks().Size(); i < e; ++i) { + HBasicBlock* dominated = GetDominatedBlocks().Get(i); + dominated->dominator_ = new_block; + new_block->dominated_blocks_.Add(dominated); + } + dominated_blocks_.Reset(); + return new_block; +} + +void HInstructionList::SetBlockOfInstructions(HBasicBlock* block) const { + for (HInstruction* current = first_instruction_; + current != nullptr; + current = current->GetNext()) { + current->SetBlock(block); + } +} + +void HInstructionList::AddAfter(HInstruction* cursor, const HInstructionList& instruction_list) { + DCHECK(Contains(cursor)); + if (!instruction_list.IsEmpty()) { + if (cursor == last_instruction_) { + last_instruction_ = instruction_list.last_instruction_; + } else { + cursor->next_->previous_ = instruction_list.last_instruction_; + } + instruction_list.last_instruction_->next_ = cursor->next_; + cursor->next_ = instruction_list.first_instruction_; + instruction_list.first_instruction_->previous_ = cursor; + } +} + +void HInstructionList::Add(const HInstructionList& instruction_list) { + DCHECK(!IsEmpty()); + AddAfter(last_instruction_, instruction_list); +} + +void HBasicBlock::MergeWith(HBasicBlock* other) { + DCHECK(successors_.IsEmpty()) << "Unimplemented block merge scenario"; + DCHECK(dominated_blocks_.IsEmpty()) << "Unimplemented block merge scenario"; + DCHECK(other->GetDominator()->IsEntryBlock() && other->GetGraph() != graph_) + << "Unimplemented block merge scenario"; + DCHECK(other->GetPhis().IsEmpty()); + + successors_.Reset(); + dominated_blocks_.Reset(); + instructions_.Add(other->GetInstructions()); + other->GetInstructions().SetBlockOfInstructions(this); + + while (!other->GetSuccessors().IsEmpty()) { + HBasicBlock* successor = other->GetSuccessors().Get(0); + successor->ReplacePredecessor(other, this); + } + + for (size_t i = 0, e = other->GetDominatedBlocks().Size(); i < e; ++i) { + HBasicBlock* dominated = other->GetDominatedBlocks().Get(i); + dominated_blocks_.Add(dominated); + dominated->SetDominator(this); + } + other->dominated_blocks_.Reset(); + other->dominator_ = nullptr; + other->graph_ = nullptr; +} + +void HBasicBlock::ReplaceWith(HBasicBlock* other) { + while (!GetPredecessors().IsEmpty()) { + HBasicBlock* predecessor = GetPredecessors().Get(0); + predecessor->ReplaceSuccessor(this, other); + } + while (!GetSuccessors().IsEmpty()) { + HBasicBlock* successor = GetSuccessors().Get(0); + successor->ReplacePredecessor(this, other); + } + for (size_t i = 0; i < dominated_blocks_.Size(); ++i) { + other->AddDominatedBlock(dominated_blocks_.Get(i)); + } + GetDominator()->ReplaceDominatedBlock(this, other); + other->SetDominator(GetDominator()); + dominator_ = nullptr; + graph_ = nullptr; +} + +// Create space in `blocks` for adding `number_of_new_blocks` entries +// starting at location `at`. Blocks after `at` are moved accordingly. +static void MakeRoomFor(GrowableArray<HBasicBlock*>* blocks, + size_t number_of_new_blocks, + size_t at) { + size_t old_size = blocks->Size(); + size_t new_size = old_size + number_of_new_blocks; + blocks->SetSize(new_size); + for (size_t i = old_size - 1, j = new_size - 1; i > at; --i, --j) { + blocks->Put(j, blocks->Get(i)); + } +} +void HGraph::InlineInto(HGraph* outer_graph, HInvoke* invoke) { // Walk over the entry block and: // - Move constants from the entry block to the outer_graph's entry block, // - Replace HParameterValue instructions with their real value. @@ -751,41 +866,122 @@ void HGraph::InlineInto(HGraph* outer_graph, HInvoke* invoke) { } } - // Insert the body's instructions except the last, just after the `invoke` - // instruction. - HBasicBlock* body = GetBlocks().Get(1); - DCHECK(!body->IsExitBlock()); - HInstruction* last = body->GetLastInstruction(); - HInstruction* first = body->GetFirstInstruction(); - - if (first != last) { - HInstruction* antelast = last->GetPrevious(); - - // Update the instruction list of the body to only contain the last - // instruction. - last->previous_ = nullptr; - body->instructions_.first_instruction_ = last; - body->instructions_.last_instruction_ = last; - - // Update the instruction list of the `invoke`'s block to now contain the - // body's instructions. - antelast->next_ = invoke->GetNext(); - antelast->next_->previous_ = antelast; - first->previous_ = invoke; - invoke->next_ = first; - - // Update the block pointer of all instructions. - for (HInstruction* current = antelast; current != invoke; current = current->GetPrevious()) { - current->SetBlock(invoke->GetBlock()); + if (GetBlocks().Size() == 3) { + // Simple case: Put the first block's instruction into `invoke`'s block. + HBasicBlock* body = GetBlocks().Get(1); + DCHECK(!body->IsExitBlock()); + HInstruction* last = body->GetLastInstruction(); + + invoke->GetBlock()->instructions_.AddAfter(invoke, body->GetInstructions()); + body->GetInstructions().SetBlockOfInstructions(invoke->GetBlock()); + + // Replace the invoke with the return value of the inlined graph. + if (last->IsReturn()) { + invoke->ReplaceWith(last->InputAt(0)); + } else { + DCHECK(last->IsReturnVoid()); } - } - // Replace the invoke with the return value of the inlined graph. - if (last->IsReturn()) { - invoke->ReplaceWith(last->InputAt(0)); - body->RemoveInstruction(last); + invoke->GetBlock()->RemoveInstruction(last); } else { - DCHECK(last->IsReturnVoid()); + // Need to inline multiple blocks. We split `invoke`'s block + // into two blocks, merge the first block of the inlined graph into + // the first half, and replace the exit block if the inlined graph + // with the second half. + ArenaAllocator* allocator = outer_graph->GetArena(); + HBasicBlock* at = invoke->GetBlock(); + HBasicBlock* to = at->SplitAfter(invoke); + + HBasicBlock* first = entry_block_->GetSuccessors().Get(0); + DCHECK(!first->IsInLoop()); + at->MergeWith(first); + exit_block_->ReplaceWith(to); + + // Update all predecessors of the exit block (now the `to` block) + // to not `HReturn` but `HGoto` instead. Also collect the return + // values if any, and potentially make it a phi if there are multiple + // predecessors. + HInstruction* return_value = nullptr; + for (size_t i = 0, e = to->GetPredecessors().Size(); i < e; ++i) { + HBasicBlock* predecessor = to->GetPredecessors().Get(i); + HInstruction* last = predecessor->GetLastInstruction(); + if (!last->IsReturnVoid()) { + if (return_value != nullptr) { + if (!return_value->IsPhi()) { + HPhi* phi = new (allocator) HPhi( + allocator, kNoRegNumber, to->GetPredecessors().Size(), invoke->GetType()); + return_value->AsPhi()->AddInput(return_value); + to->AddPhi(phi); + return_value = phi; + } + return_value->AsPhi()->AddInput(last->InputAt(0)); + } else { + return_value = last->InputAt(0); + } + } + predecessor->AddInstruction(new (allocator) HGoto()); + predecessor->RemoveInstruction(last); + } + + if (return_value != nullptr) { + invoke->ReplaceWith(return_value); + } + + // Update the meta information surrounding blocks: + // (1) the graph they are now in, + // (2) the reverse post order of that graph, + // (3) the potential loop information they are now in. + + // We don't add the entry block, the exit block, and the first block, which + // has been merged with `at`. + static constexpr int kNumberOfSkippedBlocksInCallee = 3; + + // We add the `to` block. + static constexpr int kNumberOfNewBlocksInCaller = 1; + size_t blocks_added = (reverse_post_order_.Size() - kNumberOfSkippedBlocksInCallee) + + kNumberOfNewBlocksInCaller; + + // Find the location of `at` in the outer graph's reverse post order. The new + // blocks will be added after it. + size_t index_of_at = 0; + while (outer_graph->reverse_post_order_.Get(index_of_at) != at) { + index_of_at++; + } + MakeRoomFor(&outer_graph->reverse_post_order_, blocks_added, index_of_at); + + // Do a reverse post order of the blocks in the callee and do (1), (2), + // and (3) to the blocks that apply. + HLoopInformation* info = at->GetLoopInformation(); + for (HReversePostOrderIterator it(*this); !it.Done(); it.Advance()) { + HBasicBlock* current = it.Current(); + if (current != exit_block_ && current != entry_block_ && current != first) { + DCHECK(!current->IsInLoop()); + DCHECK(current->GetGraph() == this); + current->SetGraph(outer_graph); + outer_graph->AddBlock(current); + outer_graph->reverse_post_order_.Put(++index_of_at, current); + if (info != nullptr) { + info->Add(current); + current->SetLoopInformation(info); + } + } + } + + // Do (1), (2), and (3) to `to`. + to->SetGraph(outer_graph); + outer_graph->AddBlock(to); + outer_graph->reverse_post_order_.Put(++index_of_at, to); + if (info != nullptr) { + info->Add(to); + to->SetLoopInformation(info); + if (info->IsBackEdge(at)) { + // Only `at` can become a back edge, as the inlined blocks + // are predecessors of `at`. + DCHECK_EQ(1u, info->NumberOfBackEdges()); + info->ClearBackEdges(); + info->AddBackEdge(to); + } + } } // Finally remove the invoke from the caller. diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index 6f7bc0c..30d869d 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -73,6 +73,15 @@ class HInstructionList { bool FoundBefore(const HInstruction* instruction1, const HInstruction* instruction2) const; + bool IsEmpty() const { return first_instruction_ == nullptr; } + void Clear() { first_instruction_ = last_instruction_ = nullptr; } + + // Update the block of all instructions to be `block`. + void SetBlockOfInstructions(HBasicBlock* block) const; + + void AddAfter(HInstruction* cursor, const HInstructionList& instruction_list); + void Add(const HInstructionList& instruction_list); + private: HInstruction* first_instruction_; HInstruction* last_instruction_; @@ -241,6 +250,10 @@ class HLoopInformation : public ArenaObject<kArenaAllocMisc> { return header_; } + void SetHeader(HBasicBlock* block) { + header_ = block; + } + HSuspendCheck* GetSuspendCheck() const { return suspend_check_; } void SetSuspendCheck(HSuspendCheck* check) { suspend_check_ = check; } bool HasSuspendCheck() const { return suspend_check_ != nullptr; } @@ -288,6 +301,8 @@ class HLoopInformation : public ArenaObject<kArenaAllocMisc> { const ArenaBitVector& GetBlocks() const { return blocks_; } + void Add(HBasicBlock* block); + private: // Internal recursive implementation of `Populate`. void PopulateRecursive(HBasicBlock* block); @@ -351,6 +366,7 @@ class HBasicBlock : public ArenaObject<kArenaAllocMisc> { } HGraph* GetGraph() const { return graph_; } + void SetGraph(HGraph* graph) { graph_ = graph; } int GetBlockId() const { return block_id_; } void SetBlockId(int id) { block_id_ = id; } @@ -358,6 +374,16 @@ class HBasicBlock : public ArenaObject<kArenaAllocMisc> { HBasicBlock* GetDominator() const { return dominator_; } void SetDominator(HBasicBlock* dominator) { dominator_ = dominator; } void AddDominatedBlock(HBasicBlock* block) { dominated_blocks_.Add(block); } + void ReplaceDominatedBlock(HBasicBlock* existing, HBasicBlock* new_block) { + for (size_t i = 0, e = dominated_blocks_.Size(); i < e; ++i) { + if (dominated_blocks_.Get(i) == existing) { + dominated_blocks_.Put(i, new_block); + return; + } + } + LOG(FATAL) << "Unreachable"; + UNREACHABLE(); + } int NumberOfBackEdges() const { return loop_information_ == nullptr @@ -384,10 +410,22 @@ class HBasicBlock : public ArenaObject<kArenaAllocMisc> { successors_.Put(successor_index, new_block); } + void ReplacePredecessor(HBasicBlock* existing, HBasicBlock* new_block) { + size_t predecessor_index = GetPredecessorIndexOf(existing); + DCHECK_NE(predecessor_index, static_cast<size_t>(-1)); + existing->RemoveSuccessor(this); + new_block->successors_.Add(this); + predecessors_.Put(predecessor_index, new_block); + } + void RemovePredecessor(HBasicBlock* block) { predecessors_.Delete(block); } + void RemoveSuccessor(HBasicBlock* block) { + successors_.Delete(block); + } + void ClearAllPredecessors() { predecessors_.Reset(); } @@ -422,6 +460,26 @@ class HBasicBlock : public ArenaObject<kArenaAllocMisc> { return -1; } + // Split the block into two blocks just after `cursor`. Returns the newly + // created block. Note that this method just updates raw block information, + // like predecessors, successors, dominators, and instruction list. It does not + // update the graph, reverse post order, loop information, nor make sure the + // blocks are consistent (for example ending with a control flow instruction). + HBasicBlock* SplitAfter(HInstruction* cursor); + + // Merge `other` at the end of `this`. Successors and dominated blocks of + // `other` are changed to be successors and dominated blocks of `this`. Note + // that this method does not update the graph, reverse post order, loop + // information, nor make sure the blocks are consistent (for example ending + // with a control flow instruction). + void MergeWith(HBasicBlock* other); + + // Replace `this` with `other`. Predecessors, successors, and dominated blocks + // of `this` are moved to `other`. + // Note that this method does not update the graph, reverse post order, loop + // information, nor make sure the blocks are consistent (for example ending + void ReplaceWith(HBasicBlock* other); + void AddInstruction(HInstruction* instruction); void RemoveInstruction(HInstruction* instruction); void InsertInstructionBefore(HInstruction* instruction, HInstruction* cursor); @@ -446,8 +504,9 @@ class HBasicBlock : public ArenaObject<kArenaAllocMisc> { return loop_information_; } - // Set the loop_information_ on this block. This method overrides the current + // Set the loop_information_ on this block. Overrides the current // loop_information if it is an outer loop of the passed loop information. + // Note that this method is called while creating the loop information. void SetInLoop(HLoopInformation* info) { if (IsLoopHeader()) { // Nothing to do. This just means `info` is an outer loop. @@ -465,6 +524,11 @@ class HBasicBlock : public ArenaObject<kArenaAllocMisc> { } } + // Raw update of the loop information. + void SetLoopInformation(HLoopInformation* info) { + loop_information_ = info; + } + bool IsInLoop() const { return loop_information_ != nullptr; } // Returns wheter this block dominates the blocked passed as parameter. @@ -482,7 +546,7 @@ class HBasicBlock : public ArenaObject<kArenaAllocMisc> { void SetIsCatchBlock() { is_catch_block_ = true; } private: - HGraph* const graph_; + HGraph* graph_; GrowableArray<HBasicBlock*> predecessors_; GrowableArray<HBasicBlock*> successors_; HInstructionList instructions_; @@ -2180,6 +2244,8 @@ class HTypeConversion : public HExpression<1> { DISALLOW_COPY_AND_ASSIGN(HTypeConversion); }; +static constexpr uint32_t kNoRegNumber = -1; + class HPhi : public HInstruction { public: HPhi(ArenaAllocator* arena, uint32_t reg_number, size_t number_of_inputs, Primitive::Type type) diff --git a/compiler/optimizing/side_effects_analysis.cc b/compiler/optimizing/side_effects_analysis.cc index 96e1c8f..ea1ca5a 100644 --- a/compiler/optimizing/side_effects_analysis.cc +++ b/compiler/optimizing/side_effects_analysis.cc @@ -19,6 +19,11 @@ namespace art { void SideEffectsAnalysis::Run() { + // Inlining might have created more blocks, so we need to increase the size + // if needed. + block_effects_.SetSize(graph_->GetBlocks().Size()); + loop_effects_.SetSize(graph_->GetBlocks().Size()); + if (kIsDebugBuild) { for (HReversePostOrderIterator it(*graph_); !it.Done(); it.Advance()) { HBasicBlock* block = it.Current(); diff --git a/test/447-checker-inliner3/expected.txt b/test/447-checker-inliner3/expected.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/447-checker-inliner3/expected.txt diff --git a/test/447-checker-inliner3/info.txt b/test/447-checker-inliner3/info.txt new file mode 100644 index 0000000..66a3270 --- /dev/null +++ b/test/447-checker-inliner3/info.txt @@ -0,0 +1 @@ +Tests inlining in the optimizing compiler. diff --git a/test/447-checker-inliner3/src/Main.java b/test/447-checker-inliner3/src/Main.java new file mode 100644 index 0000000..db4b236 --- /dev/null +++ b/test/447-checker-inliner3/src/Main.java @@ -0,0 +1,77 @@ +/* +* Copyright (C) 2014 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +public class Main { + + // CHECK-START: int Main.inlineIfThenElse() inliner (before) + // CHECK-DAG: [[Invoke:i\d+]] InvokeStaticOrDirect + // CHECK-DAG: Return [ [[Invoke]] ] + + // CHECK-START: int Main.inlineIfThenElse() inliner (after) + // CHECK-NOT: InvokeStaticOrDirect + + public static int inlineIfThenElse() { + return foo(true); + } + + private static int foo(boolean value) { + if (value) { + return 1; + } else { + return 0; + } + } + + // CHECK-START: int Main.inlineInLoop() inliner (before) + // CHECK-DAG: InvokeStaticOrDirect + + // CHECK-START: int Main.inlineInLoop() inliner (after) + // CHECK-NOT: InvokeStaticOrDirect + + public static int inlineInLoop() { + int result = 0; + for (int i = 0; i < 32; ++i) { + result += foo(i % 2 == 0); + } + return result; + } + + // CHECK-START: int Main.inlineInLoopHeader() inliner (before) + // CHECK-DAG: InvokeStaticOrDirect + + // CHECK-START: int Main.inlineInLoopHeader() inliner (after) + // CHECK-NOT: InvokeStaticOrDirect + + public static int inlineInLoopHeader() { + int result = 0; + for (int i = 0; i < foo(i % 2 == 0); ++i) { + result += 42; + } + return result; + } + + public static void main(String[] args) { + if (inlineIfThenElse() != 1) { + throw new Error("Expected 1"); + } + if (inlineInLoop() != 16) { + throw new Error("Expected 16"); + } + if (inlineInLoopHeader() != 42) { + throw new Error("Expected 16"); + } + } +} |