diff options
author | 2015-05-18 22:31:29 +0100 | |
---|---|---|
committer | 2015-05-29 14:15:04 +0100 | |
commit | d23eeef3492b53102eb8093524cf37e2b4c296db (patch) | |
tree | 57d3e9ab2853d5b8092568bb3d29bc850c113315 /compiler/optimizing | |
parent | a15c78d3cc28f514a482ffd792a767e97fe53c95 (diff) |
Support for inlining methods that call/throw.
Mostly fixes here and there to make it working.
Change-Id: I1b535e895105d78b65634636d675b818551f783e
Diffstat (limited to 'compiler/optimizing')
-rw-r--r-- | compiler/optimizing/builder.cc | 4 | ||||
-rw-r--r-- | compiler/optimizing/graph_visualizer.cc | 7 | ||||
-rw-r--r-- | compiler/optimizing/inliner.cc | 67 | ||||
-rw-r--r-- | compiler/optimizing/inliner.h | 2 | ||||
-rw-r--r-- | compiler/optimizing/nodes.cc | 23 | ||||
-rw-r--r-- | compiler/optimizing/nodes.h | 63 | ||||
-rw-r--r-- | compiler/optimizing/nodes_test.cc | 8 | ||||
-rw-r--r-- | compiler/optimizing/prepare_for_register_allocation.cc | 8 | ||||
-rw-r--r-- | compiler/optimizing/register_allocator.cc | 2 | ||||
-rw-r--r-- | compiler/optimizing/ssa_builder.cc | 3 | ||||
-rw-r--r-- | compiler/optimizing/ssa_liveness_analysis.h | 8 |
11 files changed, 149 insertions, 46 deletions
diff --git a/compiler/optimizing/builder.cc b/compiler/optimizing/builder.cc index f858f82a40..365599f904 100644 --- a/compiler/optimizing/builder.cc +++ b/compiler/optimizing/builder.cc @@ -652,8 +652,8 @@ bool HGraphBuilder::BuildInvoke(const Instruction& instruction, DCHECK((optimized_invoke_type == invoke_type) || (optimized_invoke_type != kDirect) || compiler_driver_->GetCompilerOptions().GetCompilePic()); bool is_recursive = - (target_method.dex_method_index == dex_compilation_unit_->GetDexMethodIndex()); - DCHECK(!is_recursive || (target_method.dex_file == dex_compilation_unit_->GetDexFile())); + (target_method.dex_method_index == outer_compilation_unit_->GetDexMethodIndex()) + && (target_method.dex_file == outer_compilation_unit_->GetDexFile()); if (optimized_invoke_type == kStatic) { ScopedObjectAccess soa(Thread::Current()); diff --git a/compiler/optimizing/graph_visualizer.cc b/compiler/optimizing/graph_visualizer.cc index 7da4f2d1fd..fd2e4e81df 100644 --- a/compiler/optimizing/graph_visualizer.cc +++ b/compiler/optimizing/graph_visualizer.cc @@ -280,6 +280,13 @@ class HGraphVisualizerPrinter : public HGraphVisitor { << instance_of->MustDoNullCheck() << std::noboolalpha; } + void VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) OVERRIDE { + StartAttributeStream("dex_file_index") << invoke->GetDexMethodIndex(); + StartAttributeStream("recursive") << std::boolalpha + << invoke->IsRecursive() + << std::noboolalpha; + } + bool IsPass(const char* name) { return strcmp(pass_name_, name) == 0; } diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc index 997f980f45..15f3deb174 100644 --- a/compiler/optimizing/inliner.cc +++ b/compiler/optimizing/inliner.cc @@ -36,8 +36,8 @@ namespace art { -static constexpr int kMaxInlineCodeUnits = 100; -static constexpr int kDepthLimit = 5; +static constexpr int kMaxInlineCodeUnits = 18; +static constexpr int kDepthLimit = 3; void HInliner::Run() { if (graph_->IsDebuggable()) { @@ -46,8 +46,15 @@ void HInliner::Run() { return; } const GrowableArray<HBasicBlock*>& blocks = graph_->GetReversePostOrder(); + HBasicBlock* next_block = blocks.Get(0); for (size_t i = 0; i < blocks.Size(); ++i) { - HBasicBlock* block = blocks.Get(i); + // Because we are changing the graph when inlining, we need to remember the next block. + // This avoids doing the inlining work again on the inlined blocks. + if (blocks.Get(i) != next_block) { + continue; + } + HBasicBlock* block = next_block; + next_block = (i == blocks.Size() - 1) ? nullptr : blocks.Get(i + 1); for (HInstruction* instruction = block->GetFirstInstruction(); instruction != nullptr;) { HInstruction* next = instruction->GetNext(); HInvokeStaticOrDirect* call = instruction->AsInvokeStaticOrDirect(); @@ -90,10 +97,10 @@ bool HInliner::TryInline(HInvoke* invoke_instruction, return false; } - bool can_use_dex_cache = true; + bool same_dex_file = true; const DexFile& outer_dex_file = *outer_compilation_unit_.GetDexFile(); if (resolved_method->GetDexFile()->GetLocation().compare(outer_dex_file.GetLocation()) != 0) { - can_use_dex_cache = false; + same_dex_file = false; } const DexFile::CodeItem* code_item = resolved_method->GetCodeItem(); @@ -140,7 +147,7 @@ bool HInliner::TryInline(HInvoke* invoke_instruction, return false; } - if (!TryBuildAndInline(resolved_method, invoke_instruction, method_index, can_use_dex_cache)) { + if (!TryBuildAndInline(resolved_method, invoke_instruction, method_index, same_dex_file)) { return false; } @@ -152,7 +159,7 @@ bool HInliner::TryInline(HInvoke* invoke_instruction, bool HInliner::TryBuildAndInline(Handle<mirror::ArtMethod> resolved_method, HInvoke* invoke_instruction, uint32_t method_index, - bool can_use_dex_cache) const { + bool same_dex_file) const { ScopedObjectAccess soa(Thread::Current()); const DexFile::CodeItem* code_item = resolved_method->GetCodeItem(); const DexFile& caller_dex_file = *caller_compilation_unit_.GetDexFile(); @@ -254,6 +261,31 @@ bool HInliner::TryBuildAndInline(Handle<mirror::ArtMethod> resolved_method, inliner.Run(); } + // TODO: We should abort only if all predecessors throw. However, + // HGraph::InlineInto currently does not handle an exit block with + // a throw predecessor. + HBasicBlock* exit_block = callee_graph->GetExitBlock(); + if (exit_block == nullptr) { + VLOG(compiler) << "Method " << PrettyMethod(method_index, caller_dex_file) + << " could not be inlined because it has an infinite loop"; + resolved_method->SetShouldNotInline(); + return false; + } + + bool has_throw_predecessor = false; + for (size_t i = 0, e = exit_block->GetPredecessors().Size(); i < e; ++i) { + if (exit_block->GetPredecessors().Get(i)->GetLastInstruction()->IsThrow()) { + has_throw_predecessor = true; + break; + } + } + if (has_throw_predecessor) { + VLOG(compiler) << "Method " << PrettyMethod(method_index, caller_dex_file) + << " could not be inlined because one branch always throws"; + resolved_method->SetShouldNotInline(); + return false; + } + HReversePostOrderIterator it(*callee_graph); it.Advance(); // Past the entry block, it does not contain instructions that prevent inlining. for (; !it.Done(); it.Advance()) { @@ -269,27 +301,24 @@ bool HInliner::TryBuildAndInline(Handle<mirror::ArtMethod> resolved_method, !instr_it.Done(); instr_it.Advance()) { HInstruction* current = instr_it.Current(); - if (current->IsSuspendCheck()) { - continue; - } - if (current->CanThrow()) { + if (current->IsInvokeInterface()) { + // Disable inlining of interface calls. The cost in case of entering the + // resolution conflict is currently too high. VLOG(compiler) << "Method " << PrettyMethod(method_index, caller_dex_file) - << " could not be inlined because " << current->DebugName() - << " can throw"; + << " could not be inlined because it has an interface call."; resolved_method->SetShouldNotInline(); return false; } - if (current->NeedsEnvironment()) { + if (!same_dex_file && current->NeedsEnvironment()) { VLOG(compiler) << "Method " << PrettyMethod(method_index, caller_dex_file) << " could not be inlined because " << current->DebugName() - << " needs an environment"; - resolved_method->SetShouldNotInline(); + << " needs an environment and is in a different dex file"; return false; } - if (!can_use_dex_cache && current->NeedsDexCache()) { + if (!same_dex_file && current->NeedsDexCache()) { VLOG(compiler) << "Method " << PrettyMethod(method_index, caller_dex_file) << " could not be inlined because " << current->DebugName() << " it is in a different dex file and requires access to the dex cache"; @@ -302,10 +331,6 @@ bool HInliner::TryBuildAndInline(Handle<mirror::ArtMethod> resolved_method, callee_graph->InlineInto(graph_, invoke_instruction); - if (callee_graph->HasBoundsChecks()) { - graph_->SetHasBoundsChecks(true); - } - return true; } diff --git a/compiler/optimizing/inliner.h b/compiler/optimizing/inliner.h index 1dbc7d392b..09a36c6659 100644 --- a/compiler/optimizing/inliner.h +++ b/compiler/optimizing/inliner.h @@ -51,7 +51,7 @@ class HInliner : public HOptimization { bool TryBuildAndInline(Handle<mirror::ArtMethod> resolved_method, HInvoke* invoke_instruction, uint32_t method_index, - bool can_use_dex_cache) const; + bool same_dex_file) const; const DexCompilationUnit& outer_compilation_unit_; const DexCompilationUnit& caller_compilation_unit_; diff --git a/compiler/optimizing/nodes.cc b/compiler/optimizing/nodes.cc index 80d4b4a863..06f6a7fd88 100644 --- a/compiler/optimizing/nodes.cc +++ b/compiler/optimizing/nodes.cc @@ -1314,6 +1314,29 @@ void HGraph::DeleteDeadBlock(HBasicBlock* block) { void HGraph::InlineInto(HGraph* outer_graph, HInvoke* invoke) { DCHECK(HasExitBlock()) << "Unimplemented scenario"; + // Update the environments in this graph to have the invoke's environment + // as parent. + { + HReversePostOrderIterator it(*this); + it.Advance(); // Skip the entry block, we do not need to update the entry's suspend check. + for (; !it.Done(); it.Advance()) { + HBasicBlock* block = it.Current(); + for (HInstructionIterator instr_it(block->GetInstructions()); + !instr_it.Done(); + instr_it.Advance()) { + HInstruction* current = instr_it.Current(); + if (current->NeedsEnvironment()) { + current->GetEnvironment()->SetAndCopyParentChain( + outer_graph->GetArena(), invoke->GetEnvironment()); + } + } + } + } + outer_graph->UpdateMaximumNumberOfOutVRegs(GetMaximumNumberOfOutVRegs()); + if (HasBoundsChecks()) { + outer_graph->SetHasBoundsChecks(true); + } + if (GetBlocks().Size() == 3) { // Simple case of an entry block, a body block, and an exit block. // Put the body block's instruction into `invoke`'s block. diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index 3144c5c193..005d50eea0 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -143,6 +143,7 @@ class HGraph : public ArenaObject<kArenaAllocMisc> { dex_file_(dex_file), method_idx_(method_idx), invoke_type_(invoke_type), + in_ssa_form_(false), should_generate_constructor_barrier_(should_generate_constructor_barrier), cached_null_constant_(nullptr), cached_int_constants_(std::less<int32_t>(), arena->Adapter()), @@ -174,6 +175,7 @@ class HGraph : public ArenaObject<kArenaAllocMisc> { // users remaining when being visited. if (!AnalyzeNaturalLoops()) return false; TransformToSsa(); + in_ssa_form_ = true; return true; } @@ -216,11 +218,16 @@ class HGraph : public ArenaObject<kArenaAllocMisc> { maximum_number_of_out_vregs_ = new_value; } + void UpdateMaximumNumberOfOutVRegs(uint16_t other_value) { + maximum_number_of_out_vregs_ = std::max(maximum_number_of_out_vregs_, other_value); + } + void UpdateTemporariesVRegSlots(size_t slots) { temporaries_vreg_slots_ = std::max(slots, temporaries_vreg_slots_); } size_t GetTemporariesVRegSlots() const { + DCHECK(!in_ssa_form_); return temporaries_vreg_slots_; } @@ -229,6 +236,7 @@ class HGraph : public ArenaObject<kArenaAllocMisc> { } uint16_t GetNumberOfVRegs() const { + DCHECK(!in_ssa_form_); return number_of_vregs_; } @@ -237,6 +245,7 @@ class HGraph : public ArenaObject<kArenaAllocMisc> { } uint16_t GetNumberOfLocalVRegs() const { + DCHECK(!in_ssa_form_); return number_of_vregs_ - number_of_in_vregs_; } @@ -381,6 +390,11 @@ class HGraph : public ArenaObject<kArenaAllocMisc> { // If inlined, this encodes how the callee is being invoked. const InvokeType invoke_type_; + // Whether the graph has been transformed to SSA form. Only used + // in debug mode to ensure we are not using properties only valid + // for non-SSA form (like the number of temporaries). + bool in_ssa_form_; + const bool should_generate_constructor_barrier_; // Cached constants. @@ -1121,14 +1135,16 @@ class HEnvironment : public ArenaObject<kArenaAllocMisc> { const DexFile& dex_file, uint32_t method_idx, uint32_t dex_pc, - InvokeType invoke_type) + InvokeType invoke_type, + HInstruction* holder) : vregs_(arena, number_of_vregs), locations_(arena, number_of_vregs), parent_(nullptr), dex_file_(dex_file), method_idx_(method_idx), dex_pc_(dex_pc), - invoke_type_(invoke_type) { + invoke_type_(invoke_type), + holder_(holder) { vregs_.SetSize(number_of_vregs); for (size_t i = 0; i < number_of_vregs; i++) { vregs_.Put(i, HUserRecord<HEnvironment*>()); @@ -1140,19 +1156,24 @@ class HEnvironment : public ArenaObject<kArenaAllocMisc> { } } - HEnvironment(ArenaAllocator* arena, const HEnvironment& to_copy) + HEnvironment(ArenaAllocator* arena, const HEnvironment& to_copy, HInstruction* holder) : HEnvironment(arena, to_copy.Size(), to_copy.GetDexFile(), to_copy.GetMethodIdx(), to_copy.GetDexPc(), - to_copy.GetInvokeType()) {} + to_copy.GetInvokeType(), + holder) {} void SetAndCopyParentChain(ArenaAllocator* allocator, HEnvironment* parent) { - parent_ = new (allocator) HEnvironment(allocator, *parent); - parent_->CopyFrom(parent); - if (parent->GetParent() != nullptr) { - parent_->SetAndCopyParentChain(allocator, parent->GetParent()); + if (parent_ != nullptr) { + parent_->SetAndCopyParentChain(allocator, parent); + } else { + parent_ = new (allocator) HEnvironment(allocator, *parent, holder_); + parent_->CopyFrom(parent); + if (parent->GetParent() != nullptr) { + parent_->SetAndCopyParentChain(allocator, parent->GetParent()); + } } } @@ -1202,6 +1223,10 @@ class HEnvironment : public ArenaObject<kArenaAllocMisc> { return dex_file_; } + HInstruction* GetHolder() const { + return holder_; + } + private: // Record instructions' use entries of this environment for constant-time removal. // It should only be called by HInstruction when a new environment use is added. @@ -1219,6 +1244,10 @@ class HEnvironment : public ArenaObject<kArenaAllocMisc> { const uint32_t dex_pc_; const InvokeType invoke_type_; + // The instruction that holds this environment. Only used in debug mode + // to ensure the graph is consistent. + HInstruction* const holder_; + friend class HInstruction; DISALLOW_COPY_AND_ASSIGN(HEnvironment); @@ -1425,13 +1454,18 @@ class HInstruction : public ArenaObject<kArenaAllocMisc> { HEnvironment* GetEnvironment() const { return environment_; } // Set the `environment_` field. Raw because this method does not // update the uses lists. - void SetRawEnvironment(HEnvironment* environment) { environment_ = environment; } + void SetRawEnvironment(HEnvironment* environment) { + DCHECK(environment_ == nullptr); + DCHECK_EQ(environment->GetHolder(), this); + environment_ = environment; + } // Set the environment of this instruction, copying it from `environment`. While // copying, the uses lists are being updated. void CopyEnvironmentFrom(HEnvironment* environment) { + DCHECK(environment_ == nullptr); ArenaAllocator* allocator = GetBlock()->GetGraph()->GetArena(); - environment_ = new (allocator) HEnvironment(allocator, *environment); + environment_ = new (allocator) HEnvironment(allocator, *environment, this); environment_->CopyFrom(environment); if (environment->GetParent() != nullptr) { environment_->SetAndCopyParentChain(allocator, environment->GetParent()); @@ -1440,8 +1474,9 @@ class HInstruction : public ArenaObject<kArenaAllocMisc> { void CopyEnvironmentFromWithLoopPhiAdjustment(HEnvironment* environment, HBasicBlock* block) { + DCHECK(environment_ == nullptr); ArenaAllocator* allocator = GetBlock()->GetGraph()->GetArena(); - environment_ = new (allocator) HEnvironment(allocator, *environment); + environment_ = new (allocator) HEnvironment(allocator, *environment, this); environment_->CopyFromWithLoopPhiAdjustment(environment, block); if (environment->GetParent() != nullptr) { environment_->SetAndCopyParentChain(allocator, environment->GetParent()); @@ -2420,6 +2455,12 @@ class HInvoke : public HInstruction { intrinsic_ = intrinsic; } + bool IsInlined() const { + return GetEnvironment()->GetParent() != nullptr; + } + + bool CanThrow() const OVERRIDE { return true; } + DECLARE_INSTRUCTION(Invoke); protected: diff --git a/compiler/optimizing/nodes_test.cc b/compiler/optimizing/nodes_test.cc index 782cde486a..fef77aa0ed 100644 --- a/compiler/optimizing/nodes_test.cc +++ b/compiler/optimizing/nodes_test.cc @@ -51,7 +51,7 @@ TEST(Node, RemoveInstruction) { exit_block->AddInstruction(new (&allocator) HExit()); HEnvironment* environment = new (&allocator) HEnvironment( - &allocator, 1, graph->GetDexFile(), graph->GetMethodIdx(), 0, kStatic); + &allocator, 1, graph->GetDexFile(), graph->GetMethodIdx(), 0, kStatic, null_check); null_check->SetRawEnvironment(environment); environment->SetRawEnvAt(0, parameter); parameter->AddEnvUseAt(null_check->GetEnvironment(), 0); @@ -132,7 +132,7 @@ TEST(Node, ParentEnvironment) { ASSERT_TRUE(parameter1->GetUses().HasOnlyOneUse()); HEnvironment* environment = new (&allocator) HEnvironment( - &allocator, 1, graph->GetDexFile(), graph->GetMethodIdx(), 0, kStatic); + &allocator, 1, graph->GetDexFile(), graph->GetMethodIdx(), 0, kStatic, with_environment); GrowableArray<HInstruction*> array(&allocator, 1); array.Add(parameter1); @@ -143,13 +143,13 @@ TEST(Node, ParentEnvironment) { ASSERT_TRUE(parameter1->GetEnvUses().HasOnlyOneUse()); HEnvironment* parent1 = new (&allocator) HEnvironment( - &allocator, 1, graph->GetDexFile(), graph->GetMethodIdx(), 0, kStatic); + &allocator, 1, graph->GetDexFile(), graph->GetMethodIdx(), 0, kStatic, nullptr); parent1->CopyFrom(array); ASSERT_EQ(parameter1->GetEnvUses().SizeSlow(), 2u); HEnvironment* parent2 = new (&allocator) HEnvironment( - &allocator, 1, graph->GetDexFile(), graph->GetMethodIdx(), 0, kStatic); + &allocator, 1, graph->GetDexFile(), graph->GetMethodIdx(), 0, kStatic, nullptr); parent2->CopyFrom(array); parent1->SetAndCopyParentChain(&allocator, parent2); diff --git a/compiler/optimizing/prepare_for_register_allocation.cc b/compiler/optimizing/prepare_for_register_allocation.cc index 538736be37..a249aa9711 100644 --- a/compiler/optimizing/prepare_for_register_allocation.cc +++ b/compiler/optimizing/prepare_for_register_allocation.cc @@ -88,7 +88,11 @@ void PrepareForRegisterAllocation::VisitInvokeStaticOrDirect(HInvokeStaticOrDire // The static call will initialize the class so there's no need for a clinit check if // it's the first user. - if (last_input == invoke->GetPrevious()) { + // There is one special case where we still need the clinit check, when inlining. Because + // currently the callee is responsible for reporting parameters to the GC, the code + // that walks the stack during `artQuickResolutionTrampoline` cannot be interrupted for GC. + // Therefore we cannot allocate any object in that code, including loading a new class. + if (last_input == invoke->GetPrevious() && !invoke->IsInlined()) { last_input->SetMustGenerateClinitCheck(false); } @@ -102,7 +106,7 @@ void PrepareForRegisterAllocation::VisitInvokeStaticOrDirect(HInvokeStaticOrDire // If the load class instruction is no longer used, remove it from // the graph. - if (!last_input->HasUses()) { + if (!last_input->HasUses() && !(last_input->MustGenerateClinitCheck() && invoke->IsInlined())) { last_input->GetBlock()->RemoveInstruction(last_input); } } diff --git a/compiler/optimizing/register_allocator.cc b/compiler/optimizing/register_allocator.cc index d4ff4d8dee..9a859bf35f 100644 --- a/compiler/optimizing/register_allocator.cc +++ b/compiler/optimizing/register_allocator.cc @@ -1583,7 +1583,7 @@ void RegisterAllocator::ConnectSiblings(LiveInterval* interval) { while (env_use != nullptr && env_use->GetPosition() <= range->GetEnd()) { DCHECK(current->CoversSlow(env_use->GetPosition()) || (env_use->GetPosition() == range->GetEnd())); - HEnvironment* environment = env_use->GetUser()->GetEnvironment(); + HEnvironment* environment = env_use->GetEnvironment(); environment->SetLocationAt(env_use->GetInputIndex(), source); env_use = env_use->GetNext(); } diff --git a/compiler/optimizing/ssa_builder.cc b/compiler/optimizing/ssa_builder.cc index c51d248765..c4612af393 100644 --- a/compiler/optimizing/ssa_builder.cc +++ b/compiler/optimizing/ssa_builder.cc @@ -548,7 +548,8 @@ void SsaBuilder::VisitInstruction(HInstruction* instruction) { GetGraph()->GetDexFile(), GetGraph()->GetMethodIdx(), instruction->GetDexPc(), - GetGraph()->GetInvokeType()); + GetGraph()->GetInvokeType(), + instruction); environment->CopyFrom(*current_locals_); instruction->SetRawEnvironment(environment); } diff --git a/compiler/optimizing/ssa_liveness_analysis.h b/compiler/optimizing/ssa_liveness_analysis.h index 4b19c5b46a..4cbe29ae42 100644 --- a/compiler/optimizing/ssa_liveness_analysis.h +++ b/compiler/optimizing/ssa_liveness_analysis.h @@ -117,6 +117,7 @@ class UsePosition : public ArenaObject<kArenaAllocMisc> { || user->IsPhi() || (GetPosition() == user->GetLifetimePosition() + 1) || (GetPosition() == user->GetLifetimePosition())); + DCHECK(environment == nullptr || user == nullptr); DCHECK(next_ == nullptr || next->GetPosition() >= GetPosition()); } @@ -128,6 +129,7 @@ class UsePosition : public ArenaObject<kArenaAllocMisc> { void SetNext(UsePosition* next) { next_ = next; } HInstruction* GetUser() const { return user_; } + HEnvironment* GetEnvironment() const { return environment_; } bool GetIsEnvironment() const { return environment_ != nullptr; } bool IsSynthesized() const { return user_ == nullptr; } @@ -280,7 +282,7 @@ class LiveInterval : public ArenaObject<kArenaAllocMisc> { } DCHECK(first_use_->GetPosition() + 1 == position); UsePosition* new_use = new (allocator_) UsePosition( - instruction, environment, input_index, position, cursor->GetNext()); + instruction, nullptr /* environment */, input_index, position, cursor->GetNext()); cursor->SetNext(new_use); if (first_range_->GetEnd() == first_use_->GetPosition()) { first_range_->end_ = position; @@ -290,10 +292,10 @@ class LiveInterval : public ArenaObject<kArenaAllocMisc> { if (is_environment) { first_env_use_ = new (allocator_) UsePosition( - instruction, environment, input_index, position, first_env_use_); + nullptr /* instruction */, environment, input_index, position, first_env_use_); } else { first_use_ = new (allocator_) UsePosition( - instruction, environment, input_index, position, first_use_); + instruction, nullptr /* environment */, input_index, position, first_use_); } if (is_environment && !keep_alive) { |