Bug Summary

File:out/../deps/v8/src/compiler/memory-optimizer.cc
Warning:line 314, column 13
Value stored to 'reduction' during its initialization is never read

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-unknown-linux-gnu -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name memory-optimizer.cc -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 2 -pic-is-pie -mframe-pointer=all -relaxed-aliasing -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -ffunction-sections -fdata-sections -fcoverage-compilation-dir=/home/maurizio/node-v18.6.0/out -resource-dir /usr/local/lib/clang/16.0.0 -D _GLIBCXX_USE_CXX11_ABI=1 -D NODE_OPENSSL_CONF_NAME=nodejs_conf -D NODE_OPENSSL_HAS_QUIC -D V8_GYP_BUILD -D V8_TYPED_ARRAY_MAX_SIZE_IN_HEAP=64 -D __STDC_FORMAT_MACROS -D OPENSSL_NO_PINSHARED -D OPENSSL_THREADS -D V8_TARGET_ARCH_X64 -D V8_HAVE_TARGET_OS -D V8_TARGET_OS_LINUX -D V8_EMBEDDER_STRING="-node.8" -D ENABLE_DISASSEMBLER -D V8_PROMISE_INTERNAL_FIELD_COUNT=1 -D V8_SHORT_BUILTIN_CALLS -D OBJECT_PRINT -D V8_INTL_SUPPORT -D V8_ATOMIC_OBJECT_FIELD_WRITES -D V8_ENABLE_LAZY_SOURCE_POSITIONS -D V8_USE_SIPHASH -D V8_SHARED_RO_HEAP -D V8_WIN64_UNWINDING_INFO -D V8_ENABLE_REGEXP_INTERPRETER_THREADED_DISPATCH -D V8_SNAPSHOT_COMPRESSION -D V8_ENABLE_WEBASSEMBLY -D V8_ENABLE_JAVASCRIPT_PROMISE_HOOKS -D V8_ALLOCATION_FOLDING -D V8_ALLOCATION_SITE_TRACKING -D V8_SCRIPTORMODULE_LEGACY_LIFETIME -D V8_ADVANCED_BIGINT_ALGORITHMS -D UCONFIG_NO_SERVICE=1 -D U_ENABLE_DYLOAD=0 -D U_STATIC_IMPLEMENTATION=1 -D U_HAVE_STD_STRING=1 -D UCONFIG_NO_BREAK_ITERATION=0 -I ../deps/v8 -I ../deps/v8/include -I /home/maurizio/node-v18.6.0/out/Release/obj/gen/generate-bytecode-output-root -I /home/maurizio/node-v18.6.0/out/Release/obj/gen -I ../deps/icu-small/source/i18n -I ../deps/icu-small/source/common -internal-isystem /usr/lib/gcc/x86_64-redhat-linux/8/../../../../include/c++/8 -internal-isystem /usr/lib/gcc/x86_64-redhat-linux/8/../../../../include/c++/8/x86_64-redhat-linux -internal-isystem /usr/lib/gcc/x86_64-redhat-linux/8/../../../../include/c++/8/backward -internal-isystem /usr/local/lib/clang/16.0.0/include -internal-isystem /usr/local/include -internal-isystem /usr/lib/gcc/x86_64-redhat-linux/8/../../../../x86_64-redhat-linux/include -internal-externc-isystem /include -internal-externc-isystem /usr/include -O3 -Wno-unused-parameter -Wno-return-type -std=gnu++17 -fdeprecated-macro -fdebug-compilation-dir=/home/maurizio/node-v18.6.0/out -ferror-limit 19 -fno-rtti -fgnuc-version=4.2.1 -vectorize-loops -vectorize-slp -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /tmp/scan-build-2022-08-22-142216-507842-1 -x c++ ../deps/v8/src/compiler/memory-optimizer.cc
1// Copyright 2016 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "src/compiler/memory-optimizer.h"
6
7#include "src/base/logging.h"
8#include "src/codegen/interface-descriptors.h"
9#include "src/codegen/tick-counter.h"
10#include "src/compiler/js-graph.h"
11#include "src/compiler/linkage.h"
12#include "src/compiler/node-matchers.h"
13#include "src/compiler/node-properties.h"
14#include "src/compiler/node.h"
15#include "src/roots/roots-inl.h"
16
17namespace v8 {
18namespace internal {
19namespace compiler {
20
21namespace {
22
23bool CanAllocate(const Node* node) {
24 switch (node->opcode()) {
25 case IrOpcode::kAbortCSADcheck:
26 case IrOpcode::kBitcastTaggedToWord:
27 case IrOpcode::kBitcastWordToTagged:
28 case IrOpcode::kComment:
29 case IrOpcode::kDebugBreak:
30 case IrOpcode::kDeoptimizeIf:
31 case IrOpcode::kDeoptimizeUnless:
32 case IrOpcode::kEffectPhi:
33 case IrOpcode::kIfException:
34 case IrOpcode::kLoad:
35 case IrOpcode::kLoadImmutable:
36 case IrOpcode::kLoadElement:
37 case IrOpcode::kLoadField:
38 case IrOpcode::kLoadFromObject:
39 case IrOpcode::kLoadImmutableFromObject:
40 case IrOpcode::kLoadLane:
41 case IrOpcode::kLoadTransform:
42 case IrOpcode::kMemoryBarrier:
43 case IrOpcode::kProtectedLoad:
44 case IrOpcode::kProtectedStore:
45 case IrOpcode::kRetain:
46 case IrOpcode::kStackPointerGreaterThan:
47 case IrOpcode::kStaticAssert:
48 // TODO(turbofan): Store nodes might do a bump-pointer allocation.
49 // We should introduce a special bump-pointer store node to
50 // differentiate that.
51 case IrOpcode::kStore:
52 case IrOpcode::kStoreElement:
53 case IrOpcode::kStoreField:
54 case IrOpcode::kStoreLane:
55 case IrOpcode::kStoreToObject:
56 case IrOpcode::kInitializeImmutableInObject:
57 case IrOpcode::kUnalignedLoad:
58 case IrOpcode::kUnalignedStore:
59 case IrOpcode::kUnreachable:
60 case IrOpcode::kUnsafePointerAdd:
61 case IrOpcode::kWord32AtomicAdd:
62 case IrOpcode::kWord32AtomicAnd:
63 case IrOpcode::kWord32AtomicCompareExchange:
64 case IrOpcode::kWord32AtomicExchange:
65 case IrOpcode::kWord32AtomicLoad:
66 case IrOpcode::kWord32AtomicOr:
67 case IrOpcode::kWord32AtomicPairAdd:
68 case IrOpcode::kWord32AtomicPairAnd:
69 case IrOpcode::kWord32AtomicPairCompareExchange:
70 case IrOpcode::kWord32AtomicPairExchange:
71 case IrOpcode::kWord32AtomicPairLoad:
72 case IrOpcode::kWord32AtomicPairOr:
73 case IrOpcode::kWord32AtomicPairStore:
74 case IrOpcode::kWord32AtomicPairSub:
75 case IrOpcode::kWord32AtomicPairXor:
76 case IrOpcode::kWord32AtomicStore:
77 case IrOpcode::kWord32AtomicSub:
78 case IrOpcode::kWord32AtomicXor:
79 case IrOpcode::kWord64AtomicAdd:
80 case IrOpcode::kWord64AtomicAnd:
81 case IrOpcode::kWord64AtomicCompareExchange:
82 case IrOpcode::kWord64AtomicExchange:
83 case IrOpcode::kWord64AtomicLoad:
84 case IrOpcode::kWord64AtomicOr:
85 case IrOpcode::kWord64AtomicStore:
86 case IrOpcode::kWord64AtomicSub:
87 case IrOpcode::kWord64AtomicXor:
88 return false;
89
90 case IrOpcode::kCall:
91 return !(CallDescriptorOf(node->op())->flags() &
92 CallDescriptor::kNoAllocate);
93 default:
94 break;
95 }
96 return true;
97}
98
99Node* SearchAllocatingNode(Node* start, Node* limit, Zone* temp_zone) {
100 ZoneQueue<Node*> queue(temp_zone);
101 ZoneSet<Node*> visited(temp_zone);
102 visited.insert(limit);
103 queue.push(start);
104
105 while (!queue.empty()) {
106 Node* const current = queue.front();
107 queue.pop();
108 if (visited.find(current) == visited.end()) {
109 visited.insert(current);
110
111 if (CanAllocate(current)) {
112 return current;
113 }
114
115 for (int i = 0; i < current->op()->EffectInputCount(); ++i) {
116 queue.push(NodeProperties::GetEffectInput(current, i));
117 }
118 }
119 }
120 return nullptr;
121}
122
123bool CanLoopAllocate(Node* loop_effect_phi, Zone* temp_zone) {
124 Node* const control = NodeProperties::GetControlInput(loop_effect_phi);
125 // Start the effect chain walk from the loop back edges.
126 for (int i = 1; i < control->InputCount(); ++i) {
127 if (SearchAllocatingNode(loop_effect_phi->InputAt(i), loop_effect_phi,
128 temp_zone) != nullptr) {
129 return true;
130 }
131 }
132 return false;
133}
134
135Node* EffectPhiForPhi(Node* phi) {
136 Node* control = NodeProperties::GetControlInput(phi);
137 for (Node* use : control->uses()) {
138 if (use->opcode() == IrOpcode::kEffectPhi) {
139 return use;
140 }
141 }
142 return nullptr;
143}
144
145void WriteBarrierAssertFailed(Node* node, Node* object, const char* name,
146 Zone* temp_zone) {
147 std::stringstream str;
148 str << "MemoryOptimizer could not remove write barrier for node #"
149 << node->id() << "\n";
150 str << " Run mksnapshot with --csa-trap-on-node=" << name << ","
151 << node->id() << " to break in CSA code.\n";
152 Node* object_position = object;
153 if (object_position->opcode() == IrOpcode::kPhi) {
154 object_position = EffectPhiForPhi(object_position);
155 }
156 Node* allocating_node = nullptr;
157 if (object_position && object_position->op()->EffectOutputCount() > 0) {
158 allocating_node = SearchAllocatingNode(node, object_position, temp_zone);
159 }
160 if (allocating_node) {
161 str << "\n There is a potentially allocating node in between:\n";
162 str << " " << *allocating_node << "\n";
163 str << " Run mksnapshot with --csa-trap-on-node=" << name << ","
164 << allocating_node->id() << " to break there.\n";
165 if (allocating_node->opcode() == IrOpcode::kCall) {
166 str << " If this is a never-allocating runtime call, you can add an "
167 "exception to Runtime::MayAllocate.\n";
168 }
169 } else {
170 str << "\n It seems the store happened to something different than a "
171 "direct "
172 "allocation:\n";
173 str << " " << *object << "\n";
174 str << " Run mksnapshot with --csa-trap-on-node=" << name << ","
175 << object->id() << " to break there.\n";
176 }
177 FATAL("%s", str.str().c_str())V8_Fatal("%s", str.str().c_str());
178}
179
180} // namespace
181
182MemoryOptimizer::MemoryOptimizer(
183 JSGraph* jsgraph, Zone* zone,
184 MemoryLowering::AllocationFolding allocation_folding,
185 const char* function_debug_name, TickCounter* tick_counter)
186 : graph_assembler_(jsgraph, zone),
187 memory_lowering_(jsgraph, zone, &graph_assembler_, allocation_folding,
188 WriteBarrierAssertFailed, function_debug_name),
189 jsgraph_(jsgraph),
190 empty_state_(AllocationState::Empty(zone)),
191 pending_(zone),
192 tokens_(zone),
193 zone_(zone),
194 tick_counter_(tick_counter) {}
195
196void MemoryOptimizer::Optimize() {
197 EnqueueUses(graph()->start(), empty_state());
198 while (!tokens_.empty()) {
199 Token const token = tokens_.front();
200 tokens_.pop();
201 VisitNode(token.node, token.state);
202 }
203 DCHECK(pending_.empty())((void) 0);
204 DCHECK(tokens_.empty())((void) 0);
205}
206
207void MemoryOptimizer::VisitNode(Node* node, AllocationState const* state) {
208 tick_counter_->TickAndMaybeEnterSafepoint();
209 DCHECK(!node->IsDead())((void) 0);
210 DCHECK_LT(0, node->op()->EffectInputCount())((void) 0);
211 switch (node->opcode()) {
212 case IrOpcode::kAllocate:
213 // Allocate nodes were purged from the graph in effect-control
214 // linearization.
215 UNREACHABLE()V8_Fatal("unreachable code");
216 case IrOpcode::kAllocateRaw:
217 return VisitAllocateRaw(node, state);
218 case IrOpcode::kCall:
219 return VisitCall(node, state);
220 case IrOpcode::kLoadFromObject:
221 case IrOpcode::kLoadImmutableFromObject:
222 return VisitLoadFromObject(node, state);
223 case IrOpcode::kLoadElement:
224 return VisitLoadElement(node, state);
225 case IrOpcode::kLoadField:
226 return VisitLoadField(node, state);
227 case IrOpcode::kStoreToObject:
228 case IrOpcode::kInitializeImmutableInObject:
229 return VisitStoreToObject(node, state);
230 case IrOpcode::kStoreElement:
231 return VisitStoreElement(node, state);
232 case IrOpcode::kStoreField:
233 return VisitStoreField(node, state);
234 case IrOpcode::kStore:
235 return VisitStore(node, state);
236 default:
237 if (!CanAllocate(node)) {
238 // These operations cannot trigger GC.
239 return VisitOtherEffect(node, state);
240 }
241 }
242 DCHECK_EQ(0, node->op()->EffectOutputCount())((void) 0);
243}
244
245bool MemoryOptimizer::AllocationTypeNeedsUpdateToOld(Node* const node,
246 const Edge edge) {
247 // Test to see if we need to update the AllocationType.
248 if (node->opcode() == IrOpcode::kStoreField && edge.index() == 1) {
249 Node* parent = node->InputAt(0);
250 if (parent->opcode() == IrOpcode::kAllocateRaw &&
251 AllocationTypeOf(parent->op()) == AllocationType::kOld) {
252 return true;
253 }
254 }
255
256 return false;
257}
258
259void MemoryOptimizer::ReplaceUsesAndKillNode(Node* node, Node* replacement) {
260 // Replace all uses of node and kill the node to make sure we don't leave
261 // dangling dead uses.
262 DCHECK_NE(replacement, node)((void) 0);
263 NodeProperties::ReplaceUses(node, replacement, graph_assembler_.effect(),
264 graph_assembler_.control());
265 node->Kill();
266}
267
268void MemoryOptimizer::VisitAllocateRaw(Node* node,
269 AllocationState const* state) {
270 DCHECK_EQ(IrOpcode::kAllocateRaw, node->opcode())((void) 0);
271 const AllocateParameters& allocation = AllocateParametersOf(node->op());
272 AllocationType allocation_type = allocation.allocation_type();
273
274 // Propagate tenuring from outer allocations to inner allocations, i.e.
275 // when we allocate an object in old space and store a newly allocated
276 // child object into the pretenured object, then the newly allocated
277 // child object also should get pretenured to old space.
278 if (allocation_type == AllocationType::kOld) {
279 for (Edge const edge : node->use_edges()) {
280 Node* const user = edge.from();
281 if (user->opcode() == IrOpcode::kStoreField && edge.index() == 0) {
282 Node* child = user->InputAt(1);
283 if (child->opcode() == IrOpcode::kAllocateRaw &&
284 AllocationTypeOf(child->op()) == AllocationType::kYoung) {
285 NodeProperties::ChangeOp(child, node->op());
286 break;
287 }
288 }
289 }
290 } else {
291 DCHECK_EQ(AllocationType::kYoung, allocation_type)((void) 0);
292 for (Edge const edge : node->use_edges()) {
293 Node* const user = edge.from();
294 if (AllocationTypeNeedsUpdateToOld(user, edge)) {
295 allocation_type = AllocationType::kOld;
296 break;
297 }
298 }
299 }
300
301 Reduction reduction = memory_lowering()->ReduceAllocateRaw(
302 node, allocation_type, allocation.allow_large_objects(), &state);
303 CHECK(reduction.Changed() && reduction.replacement() != node)do { if ((__builtin_expect(!!(!(reduction.Changed() &&
reduction.replacement() != node)), 0))) { V8_Fatal("Check failed: %s."
, "reduction.Changed() && reduction.replacement() != node"
); } } while (false)
;
304
305 ReplaceUsesAndKillNode(node, reduction.replacement());
306
307 EnqueueUses(state->effect(), state);
308}
309
310void MemoryOptimizer::VisitLoadFromObject(Node* node,
311 AllocationState const* state) {
312 DCHECK(node->opcode() == IrOpcode::kLoadFromObject ||((void) 0)
313 node->opcode() == IrOpcode::kLoadImmutableFromObject)((void) 0);
314 Reduction reduction = memory_lowering()->ReduceLoadFromObject(node);
Value stored to 'reduction' during its initialization is never read
315 EnqueueUses(node, state);
316 if (V8_MAP_PACKING_BOOLfalse && reduction.replacement() != node) {
317 ReplaceUsesAndKillNode(node, reduction.replacement());
318 }
319}
320
321void MemoryOptimizer::VisitStoreToObject(Node* node,
322 AllocationState const* state) {
323 DCHECK(node->opcode() == IrOpcode::kStoreToObject ||((void) 0)
324 node->opcode() == IrOpcode::kInitializeImmutableInObject)((void) 0);
325 memory_lowering()->ReduceStoreToObject(node, state);
326 EnqueueUses(node, state);
327}
328
329void MemoryOptimizer::VisitLoadElement(Node* node,
330 AllocationState const* state) {
331 DCHECK_EQ(IrOpcode::kLoadElement, node->opcode())((void) 0);
332 memory_lowering()->ReduceLoadElement(node);
333 EnqueueUses(node, state);
334}
335
336void MemoryOptimizer::VisitLoadField(Node* node, AllocationState const* state) {
337 DCHECK_EQ(IrOpcode::kLoadField, node->opcode())((void) 0);
338 Reduction reduction = memory_lowering()->ReduceLoadField(node);
339 DCHECK(reduction.Changed())((void) 0);
340 // In case of replacement, the replacement graph should not require futher
341 // lowering, so we can proceed iterating the graph from the node uses.
342 EnqueueUses(node, state);
343
344 // Node can be replaced under two cases:
345 // 1. V8_SANDBOXED_EXTERNAL_POINTERS_BOOL is enabled and loading an external
346 // pointer value.
347 // 2. V8_MAP_PACKING_BOOL is enabled.
348 DCHECK_IMPLIES(!V8_SANDBOXED_EXTERNAL_POINTERS_BOOL && !V8_MAP_PACKING_BOOL,((void) 0)
349 reduction.replacement() == node)((void) 0);
350 if ((V8_SANDBOXED_EXTERNAL_POINTERS_BOOLfalse || V8_MAP_PACKING_BOOLfalse) &&
351 reduction.replacement() != node) {
352 ReplaceUsesAndKillNode(node, reduction.replacement());
353 }
354}
355
356void MemoryOptimizer::VisitStoreElement(Node* node,
357 AllocationState const* state) {
358 DCHECK_EQ(IrOpcode::kStoreElement, node->opcode())((void) 0);
359 memory_lowering()->ReduceStoreElement(node, state);
360 EnqueueUses(node, state);
361}
362
363void MemoryOptimizer::VisitStoreField(Node* node,
364 AllocationState const* state) {
365 DCHECK_EQ(IrOpcode::kStoreField, node->opcode())((void) 0);
366 memory_lowering()->ReduceStoreField(node, state);
367 EnqueueUses(node, state);
368}
369void MemoryOptimizer::VisitStore(Node* node, AllocationState const* state) {
370 DCHECK_EQ(IrOpcode::kStore, node->opcode())((void) 0);
371 memory_lowering()->ReduceStore(node, state);
372 EnqueueUses(node, state);
373}
374
375void MemoryOptimizer::VisitCall(Node* node, AllocationState const* state) {
376 DCHECK_EQ(IrOpcode::kCall, node->opcode())((void) 0);
377 // If the call can allocate, we start with a fresh state.
378 if (!(CallDescriptorOf(node->op())->flags() & CallDescriptor::kNoAllocate)) {
379 state = empty_state();
380 }
381 EnqueueUses(node, state);
382}
383
384void MemoryOptimizer::VisitOtherEffect(Node* node,
385 AllocationState const* state) {
386 EnqueueUses(node, state);
387}
388
389MemoryOptimizer::AllocationState const* MemoryOptimizer::MergeStates(
390 AllocationStates const& states) {
391 // Check if all states are the same; or at least if all allocation
392 // states belong to the same allocation group.
393 AllocationState const* state = states.front();
394 MemoryLowering::AllocationGroup* group = state->group();
395 for (size_t i = 1; i < states.size(); ++i) {
396 if (states[i] != state) state = nullptr;
397 if (states[i]->group() != group) group = nullptr;
398 }
399 if (state == nullptr) {
400 if (group != nullptr) {
401 // We cannot fold any more allocations into this group, but we can still
402 // eliminate write barriers on stores to this group.
403 // TODO(bmeurer): We could potentially just create a Phi here to merge
404 // the various tops; but we need to pay special attention not to create
405 // an unschedulable graph.
406 state = AllocationState::Closed(group, nullptr, zone());
407 } else {
408 // The states are from different allocation groups.
409 state = empty_state();
410 }
411 }
412 return state;
413}
414
415void MemoryOptimizer::EnqueueMerge(Node* node, int index,
416 AllocationState const* state) {
417 DCHECK_EQ(IrOpcode::kEffectPhi, node->opcode())((void) 0);
418 int const input_count = node->InputCount() - 1;
419 DCHECK_LT(0, input_count)((void) 0);
420 Node* const control = node->InputAt(input_count);
421 if (control->opcode() == IrOpcode::kLoop) {
422 if (index == 0) {
423 if (CanLoopAllocate(node, zone())) {
424 // If the loop can allocate, we start with an empty state at the
425 // beginning.
426 EnqueueUses(node, empty_state());
427 } else {
428 // If the loop cannot allocate, we can just propagate the state from
429 // before the loop.
430 EnqueueUses(node, state);
431 }
432 } else {
433 // Do not revisit backedges.
434 }
435 } else {
436 DCHECK_EQ(IrOpcode::kMerge, control->opcode())((void) 0);
437 // Check if we already know about this pending merge.
438 NodeId const id = node->id();
439 auto it = pending_.find(id);
440 if (it == pending_.end()) {
441 // Insert a new pending merge.
442 it = pending_.insert(std::make_pair(id, AllocationStates(zone()))).first;
443 }
444 // Add the next input state.
445 it->second.push_back(state);
446 // Check if states for all inputs are available by now.
447 if (it->second.size() == static_cast<size_t>(input_count)) {
448 // All inputs to this effect merge are done, merge the states given all
449 // input constraints, drop the pending merge and enqueue uses of the
450 // EffectPhi {node}.
451 state = MergeStates(it->second);
452 EnqueueUses(node, state);
453 pending_.erase(it);
454 }
455 }
456}
457
458void MemoryOptimizer::EnqueueUses(Node* node, AllocationState const* state) {
459 for (Edge const edge : node->use_edges()) {
460 if (NodeProperties::IsEffectEdge(edge)) {
461 EnqueueUse(edge.from(), edge.index(), state);
462 }
463 }
464}
465
466void MemoryOptimizer::EnqueueUse(Node* node, int index,
467 AllocationState const* state) {
468 if (node->opcode() == IrOpcode::kEffectPhi) {
469 // An EffectPhi represents a merge of different effect chains, which
470 // needs special handling depending on whether the merge is part of a
471 // loop or just a normal control join.
472 EnqueueMerge(node, index, state);
473 } else {
474 Token token = {node, state};
475 tokens_.push(token);
476 }
477}
478
479Graph* MemoryOptimizer::graph() const { return jsgraph()->graph(); }
480
481} // namespace compiler
482} // namespace internal
483} // namespace v8