v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
common-operator-reducer.cc
Go to the documentation of this file.
1// Copyright 2014 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
6
7#include <algorithm>
8#include <optional>
9
15#include "src/compiler/node.h"
18
19namespace v8 {
20namespace internal {
21namespace compiler {
22
24 Editor* editor, TFGraph* graph, JSHeapBroker* broker,
26 Zone* temp_zone, BranchSemantics default_branch_semantics)
27 : AdvancedReducer(editor),
28 graph_(graph),
30 common_(common),
31 machine_(machine),
32 dead_(graph->NewNode(common->Dead())),
33 zone_(temp_zone),
34 default_branch_semantics_(default_branch_semantics) {
36}
37
39 DisallowHeapAccessIf no_heap_access(broker() == nullptr);
40 switch (node->opcode()) {
41 case IrOpcode::kBranch:
42 return ReduceBranch(node);
43 case IrOpcode::kDeoptimizeIf:
44 case IrOpcode::kDeoptimizeUnless:
45 return ReduceDeoptimizeConditional(node);
46 case IrOpcode::kMerge:
47 return ReduceMerge(node);
48 case IrOpcode::kEffectPhi:
49 return ReduceEffectPhi(node);
50 case IrOpcode::kPhi:
51 return ReducePhi(node);
52 case IrOpcode::kReturn:
53 return ReduceReturn(node);
54 case IrOpcode::kSelect:
55 return ReduceSelect(node);
56 case IrOpcode::kSwitch:
57 return ReduceSwitch(node);
58 case IrOpcode::kStaticAssert:
59 return ReduceStaticAssert(node);
60 case IrOpcode::kTrapIf:
61 case IrOpcode::kTrapUnless:
62 return ReduceTrapConditional(node);
63 default:
64 break;
65 }
66 return NoChange();
67}
68
70 Node* const cond, BranchSemantics branch_semantics) {
71 Node* unwrapped = SkipValueIdentities(cond);
72 switch (unwrapped->opcode()) {
73 case IrOpcode::kInt32Constant: {
74 DCHECK_EQ(branch_semantics, BranchSemantics::kMachine);
75 Int32Matcher m(unwrapped);
76 return m.ResolvedValue() ? Decision::kTrue : Decision::kFalse;
77 }
78 case IrOpcode::kHeapConstant: {
79 if (branch_semantics == BranchSemantics::kMachine) {
80 return Decision::kTrue;
81 }
82 HeapObjectMatcher m(unwrapped);
83 std::optional<bool> maybe_result =
84 m.Ref(broker_).TryGetBooleanValue(broker());
85 if (!maybe_result.has_value()) return Decision::kUnknown;
86 return *maybe_result ? Decision::kTrue : Decision::kFalse;
87 }
88 default:
89 return Decision::kUnknown;
90 }
91}
92
94 DCHECK_EQ(IrOpcode::kBranch, node->opcode());
95 BranchSemantics branch_semantics = BranchSemanticsOf(node);
96 Node* const cond = node->InputAt(0);
97 // Swap IfTrue/IfFalse on {branch} if {cond} is a BooleanNot and use the input
98 // to BooleanNot as new condition for {branch}. Note we assume that {cond} was
99 // already properly optimized before we get here (as guaranteed by the graph
100 // reduction logic). The same applies if {cond} is a Select acting as boolean
101 // not (i.e. true being returned in the false case and vice versa).
102 if (cond->opcode() == IrOpcode::kBooleanNot ||
103 (cond->opcode() == IrOpcode::kSelect &&
104 DecideCondition(cond->InputAt(1), branch_semantics) ==
106 DecideCondition(cond->InputAt(2), branch_semantics) ==
108 for (Node* const use : node->uses()) {
109 switch (use->opcode()) {
110 case IrOpcode::kIfTrue:
111 NodeProperties::ChangeOp(use, common()->IfFalse());
112 break;
113 case IrOpcode::kIfFalse:
114 NodeProperties::ChangeOp(use, common()->IfTrue());
115 break;
116 default:
117 UNREACHABLE();
118 }
119 }
120 // Update the condition of {branch}. No need to mark the uses for revisit,
121 // since we tell the graph reducer that the {branch} was changed and the
122 // graph reduction logic will ensure that the uses are revisited properly.
123 node->ReplaceInput(0, cond->InputAt(0));
124 // Negate the hint for {branch}.
126 node, common()->Branch(NegateBranchHint(BranchHintOf(node->op()))));
127 return Changed(node);
128 }
129 Decision const decision = DecideCondition(cond, branch_semantics);
130 if (decision == Decision::kUnknown) return NoChange();
131 Node* const control = node->InputAt(1);
132 for (Node* const use : node->uses()) {
133 switch (use->opcode()) {
134 case IrOpcode::kIfTrue:
135 Replace(use, (decision == Decision::kTrue) ? control : dead());
136 break;
137 case IrOpcode::kIfFalse:
138 Replace(use, (decision == Decision::kFalse) ? control : dead());
139 break;
140 default:
141 UNREACHABLE();
142 }
143 }
144 return Replace(dead());
145}
146
148 DCHECK(node->opcode() == IrOpcode::kDeoptimizeIf ||
149 node->opcode() == IrOpcode::kDeoptimizeUnless);
150 bool condition_is_true = node->opcode() == IrOpcode::kDeoptimizeUnless;
153 Node* frame_state = NodeProperties::GetValueInput(node, 1);
154 Node* effect = NodeProperties::GetEffectInput(node);
155 Node* control = NodeProperties::GetControlInput(node);
156 // Swap DeoptimizeIf/DeoptimizeUnless on {node} if {cond} is a BooleaNot
157 // and use the input to BooleanNot as new condition for {node}. Note we
158 // assume that {cond} was already properly optimized before we get here
159 // (as guaranteed by the graph reduction logic).
160 if (condition->opcode() == IrOpcode::kBooleanNot) {
161 NodeProperties::ReplaceValueInput(node, condition->InputAt(0), 0);
163 node, condition_is_true
164 ? common()->DeoptimizeIf(p.reason(), p.feedback())
165 : common()->DeoptimizeUnless(p.reason(), p.feedback()));
166 return Changed(node);
167 }
168 Decision const decision =
170 if (decision == Decision::kUnknown) return NoChange();
171 if (condition_is_true == (decision == Decision::kTrue)) {
172 ReplaceWithValue(node, dead(), effect, control);
173 } else {
174 control = graph()->NewNode(common()->Deoptimize(p.reason(), p.feedback()),
175 frame_state, effect, control);
176 MergeControlToEnd(graph(), common(), control);
177 }
178 return Replace(dead());
179}
180
182 DCHECK_EQ(IrOpcode::kMerge, node->opcode());
183 //
184 // Check if this is a merge that belongs to an unused diamond, which means
185 // that:
186 //
187 // a) the {Merge} has no {Phi} or {EffectPhi} uses, and
188 // b) the {Merge} has two inputs, one {IfTrue} and one {IfFalse}, which are
189 // both owned by the Merge, and
190 // c) and the {IfTrue} and {IfFalse} nodes point to the same {Branch}.
191 //
192 if (node->InputCount() == 2) {
193 for (Node* const use : node->uses()) {
194 if (IrOpcode::IsPhiOpcode(use->opcode())) return NoChange();
195 }
196 Node* if_true = node->InputAt(0);
197 Node* if_false = node->InputAt(1);
198 if (if_true->opcode() != IrOpcode::kIfTrue) std::swap(if_true, if_false);
199 if (if_true->opcode() == IrOpcode::kIfTrue &&
200 if_false->opcode() == IrOpcode::kIfFalse &&
201 if_true->InputAt(0) == if_false->InputAt(0) && if_true->OwnedBy(node) &&
202 if_false->OwnedBy(node)) {
203 Node* const branch = if_true->InputAt(0);
204 DCHECK_EQ(IrOpcode::kBranch, branch->opcode());
205 DCHECK(branch->OwnedBy(if_true, if_false));
206 Node* const control = branch->InputAt(1);
207 // Mark the {branch} as {Dead}.
208 branch->TrimInputCount(0);
209 NodeProperties::ChangeOp(branch, common()->Dead());
210 return Replace(control);
211 }
212 }
213 return NoChange();
214}
215
216
218 DCHECK_EQ(IrOpcode::kEffectPhi, node->opcode());
219 Node::Inputs inputs = node->inputs();
220 int const effect_input_count = inputs.count() - 1;
221 DCHECK_LE(1, effect_input_count);
222 Node* const merge = inputs[effect_input_count];
224 DCHECK_EQ(effect_input_count, merge->InputCount());
225 Node* const effect = inputs[0];
226 DCHECK_NE(node, effect);
227 for (int i = 1; i < effect_input_count; ++i) {
228 Node* const input = inputs[i];
229 if (input == node) {
230 // Ignore redundant inputs.
231 DCHECK_EQ(IrOpcode::kLoop, merge->opcode());
232 continue;
233 }
234 if (input != effect) return NoChange();
235 }
236 // We might now be able to further reduce the {merge} node.
237 Revisit(merge);
238 return Replace(effect);
239}
240
241
243 DCHECK_EQ(IrOpcode::kPhi, node->opcode());
244 Node::Inputs inputs = node->inputs();
245 int const value_input_count = inputs.count() - 1;
246 DCHECK_LE(1, value_input_count);
247 Node* const merge = inputs[value_input_count];
249 DCHECK_EQ(value_input_count, merge->InputCount());
250 if (value_input_count == 2) {
251 // The following optimization tries to match `0 < v ? v : 0 - v`, which
252 // corresponds in Turbofan to something like:
253 //
254 // Branch(0 < v)
255 // / \
256 // / \
257 // v 0 - v
258 // \ /
259 // \ /
260 // phi(v, 0-v)
261 //
262 // And replace it by `fabs(v)`.
263 // TODO(dmercadier): it seems that these optimizations never kick in. While
264 // keeping them doesn't cost too much, we could consider removing them to
265 // simplify the code and not maintain unused pieces of code.
266 Node* vtrue = inputs[0];
267 Node* vfalse = inputs[1];
268 Node::Inputs merge_inputs = merge->inputs();
269 Node* if_true = merge_inputs[0];
270 Node* if_false = merge_inputs[1];
271 if (if_true->opcode() != IrOpcode::kIfTrue) {
272 std::swap(if_true, if_false);
273 std::swap(vtrue, vfalse);
274 }
275 if (if_true->opcode() == IrOpcode::kIfTrue &&
276 if_false->opcode() == IrOpcode::kIfFalse &&
277 if_true->InputAt(0) == if_false->InputAt(0)) {
278 Node* const branch = if_true->InputAt(0);
279 // Check that the branch is not dead already.
280 if (branch->opcode() != IrOpcode::kBranch) return NoChange();
281 Node* const cond = branch->InputAt(0);
282 if (cond->opcode() == IrOpcode::kFloat32LessThan) {
283 Float32BinopMatcher mcond(cond);
284 if (mcond.left().Is(0.0) && mcond.right().Equals(vtrue) &&
285 vfalse->opcode() == IrOpcode::kFloat32Sub) {
286 Float32BinopMatcher mvfalse(vfalse);
287 if (mvfalse.left().IsZero() && mvfalse.right().Equals(vtrue)) {
288 // We might now be able to further reduce the {merge} node.
289 Revisit(merge);
290 return Change(node, machine()->Float32Abs(), vtrue);
291 }
292 }
293 } else if (cond->opcode() == IrOpcode::kFloat64LessThan) {
294 Float64BinopMatcher mcond(cond);
295 if (mcond.left().Is(0.0) && mcond.right().Equals(vtrue) &&
296 vfalse->opcode() == IrOpcode::kFloat64Sub) {
297 Float64BinopMatcher mvfalse(vfalse);
298 if (mvfalse.left().IsZero() && mvfalse.right().Equals(vtrue)) {
299 // We might now be able to further reduce the {merge} node.
300 Revisit(merge);
301 return Change(node, machine()->Float64Abs(), vtrue);
302 }
303 }
304 } else if (cond->opcode() == IrOpcode::kInt32LessThan) {
305 Int32BinopMatcher mcond(cond);
306 if (mcond.left().Is(0) && mcond.right().Equals(vtrue) &&
307 (vfalse->opcode() == IrOpcode::kInt32Sub)) {
308 Int32BinopMatcher mvfalse(vfalse);
309 if (mvfalse.left().Is(0) && mvfalse.right().Equals(vtrue)) {
310 // We might now be able to further reduce the {merge} node.
311 Revisit(merge);
312
313 if (machine()->Word32Select().IsSupported()) {
314 // Select positive value with conditional move if is supported.
315 Node* abs = graph()->NewNode(machine()->Word32Select().op(), cond,
316 vtrue, vfalse);
317 return Replace(abs);
318 } else {
319 // Generate absolute integer value.
320 //
321 // let sign = input >> 31 in
322 // (input ^ sign) - sign
323 Node* sign = graph()->NewNode(
324 machine()->Word32Sar(), vtrue,
325 graph()->NewNode(common()->Int32Constant(31)));
326 Node* abs = graph()->NewNode(
327 machine()->Int32Sub(),
328 graph()->NewNode(machine()->Word32Xor(), vtrue, sign), sign);
329 return Replace(abs);
330 }
331 }
332 }
333 }
334 }
335 }
336 Node* const value = inputs[0];
337 DCHECK_NE(node, value);
338 for (int i = 1; i < value_input_count; ++i) {
339 Node* const input = inputs[i];
340 if (input == node) {
341 // Ignore redundant inputs.
342 DCHECK_EQ(IrOpcode::kLoop, merge->opcode());
343 continue;
344 }
345 if (input != value) return NoChange();
346 }
347 // We might now be able to further reduce the {merge} node.
348 Revisit(merge);
349 return Replace(value);
350}
351
353 DCHECK_EQ(IrOpcode::kReturn, node->opcode());
354 Node* effect = NodeProperties::GetEffectInput(node);
355 // TODO(mslekova): Port this to Turboshaft.
356 if (effect->opcode() == IrOpcode::kCheckpoint) {
357 // Any {Return} node can never be used to insert a deoptimization point,
358 // hence checkpoints can be cut out of the effect chain flowing into it.
359 effect = NodeProperties::GetEffectInput(effect);
361 return Changed(node).FollowedBy(ReduceReturn(node));
362 }
363 // TODO(ahaas): Extend the reduction below to multiple return values.
364 if (ValueInputCountOfReturn(node->op()) != 1) {
365 return NoChange();
366 }
367 Node* pop_count = NodeProperties::GetValueInput(node, 0);
368 Node* value = NodeProperties::GetValueInput(node, 1);
369 Node* control = NodeProperties::GetControlInput(node);
370 if (value->opcode() == IrOpcode::kPhi &&
371 NodeProperties::GetControlInput(value) == control &&
372 control->opcode() == IrOpcode::kMerge) {
373 // This optimization pushes {Return} nodes through merges. It checks that
374 // the return value is actually a {Phi} and the return control dependency
375 // is the {Merge} to which the {Phi} belongs.
376
377 // Value1 ... ValueN Control1 ... ControlN
378 // ^ ^ ^ ^
379 // | | | |
380 // +----+-----+ +------+-----+
381 // | |
382 // Phi --------------> Merge
383 // ^ ^
384 // | |
385 // | +-----------------+
386 // | |
387 // Return -----> Effect
388 // ^
389 // |
390 // End
391
392 // Now the effect input to the {Return} node can be either an {EffectPhi}
393 // hanging off the same {Merge}, or the effect chain doesn't depend on the
394 // {Phi} or the {Merge}, in which case we know that the effect input must
395 // somehow dominate all merged branches.
396
397 Node::Inputs control_inputs = control->inputs();
398 Node::Inputs value_inputs = value->inputs();
399 DCHECK_NE(0, control_inputs.count());
400 DCHECK_EQ(control_inputs.count(), value_inputs.count() - 1);
401 DCHECK_EQ(IrOpcode::kEnd, graph()->end()->opcode());
402 DCHECK_NE(0, graph()->end()->InputCount());
403 if (control->OwnedBy(node, value) && value->OwnedBy(node)) {
404 for (int i = 0; i < control_inputs.count(); ++i) {
405 // Create a new {Return} and connect it to {end}. We don't need to mark
406 // {end} as revisit, because we mark {node} as {Dead} below, which was
407 // previously connected to {end}, so we know for sure that at some point
408 // the reducer logic will visit {end} again.
409 Node* ret = graph()->NewNode(node->op(), pop_count, value_inputs[i],
410 effect, control_inputs[i]);
411 MergeControlToEnd(graph(), common(), ret);
412 }
413 // Mark the Merge {control} and Return {node} as {dead}.
414 Replace(control, dead());
415 return Replace(dead());
416 } else if (effect->opcode() == IrOpcode::kEffectPhi &&
417 NodeProperties::GetControlInput(effect) == control) {
418 Node::Inputs effect_inputs = effect->inputs();
419 DCHECK_EQ(control_inputs.count(), effect_inputs.count() - 1);
420 for (int i = 0; i < control_inputs.count(); ++i) {
421 // Create a new {Return} and connect it to {end}. We don't need to mark
422 // {end} as revisit, because we mark {node} as {Dead} below, which was
423 // previously connected to {end}, so we know for sure that at some point
424 // the reducer logic will visit {end} again.
425 Node* ret = graph()->NewNode(node->op(), pop_count, value_inputs[i],
426 effect_inputs[i], control_inputs[i]);
427 MergeControlToEnd(graph(), common(), ret);
428 }
429 // Mark the Merge {control} and Return {node} as {dead}.
430 Replace(control, dead());
431 return Replace(dead());
432 }
433 }
434 return NoChange();
435}
436
438 DCHECK_EQ(IrOpcode::kSelect, node->opcode());
439 Node* const cond = node->InputAt(0);
440 Node* const vtrue = node->InputAt(1);
441 Node* const vfalse = node->InputAt(2);
442 if (vtrue == vfalse) return Replace(vtrue);
444 case Decision::kTrue:
445 return Replace(vtrue);
446 case Decision::kFalse:
447 return Replace(vfalse);
449 break;
450 }
451 // The following optimization tries to replace `select(0 < v ? v : 0 - v)` by
452 // `fabs(v)`.
453 // TODO(dmercadier): it seems that these optimizations never kick in. While
454 // keeping them doesn't cost too much, we could consider removing them to
455 // simplify the code and not maintain unused pieces of code.
456 switch (cond->opcode()) {
457 case IrOpcode::kFloat32LessThan: {
458 Float32BinopMatcher mcond(cond);
459 if (mcond.left().Is(0.0) && mcond.right().Equals(vtrue) &&
460 vfalse->opcode() == IrOpcode::kFloat32Sub) {
461 Float32BinopMatcher mvfalse(vfalse);
462 if (mvfalse.left().IsZero() && mvfalse.right().Equals(vtrue)) {
463 return Change(node, machine()->Float32Abs(), vtrue);
464 }
465 }
466 break;
467 }
468 case IrOpcode::kFloat64LessThan: {
469 Float64BinopMatcher mcond(cond);
470 if (mcond.left().Is(0.0) && mcond.right().Equals(vtrue) &&
471 vfalse->opcode() == IrOpcode::kFloat64Sub) {
472 Float64BinopMatcher mvfalse(vfalse);
473 if (mvfalse.left().IsZero() && mvfalse.right().Equals(vtrue)) {
474 return Change(node, machine()->Float64Abs(), vtrue);
475 }
476 }
477 break;
478 }
479 default:
480 break;
481 }
482 return NoChange();
483}
484
486 DCHECK_EQ(IrOpcode::kSwitch, node->opcode());
487 Node* const switched_value = node->InputAt(0);
488 Node* const control = node->InputAt(1);
489
490 // Attempt to constant match the switched value against the IfValue cases. If
491 // no case matches, then use the IfDefault. We don't bother marking
492 // non-matching cases as dead code (same for an unused IfDefault), because the
493 // Switch itself will be marked as dead code.
494 Int32Matcher mswitched(switched_value);
495 if (mswitched.HasResolvedValue()) {
496 bool matched = false;
497
498 size_t const projection_count = node->op()->ControlOutputCount();
499 Node** projections = zone_->AllocateArray<Node*>(projection_count);
501 projection_count);
502 for (size_t i = 0; i < projection_count - 1; i++) {
503 Node* if_value = projections[i];
504 DCHECK_EQ(IrOpcode::kIfValue, if_value->opcode());
505 const IfValueParameters& p = IfValueParametersOf(if_value->op());
506 if (p.value() == mswitched.ResolvedValue()) {
507 matched = true;
508 Replace(if_value, control);
509 break;
510 }
511 }
512 if (!matched) {
513 Node* if_default = projections[projection_count - 1];
514 DCHECK_EQ(IrOpcode::kIfDefault, if_default->opcode());
515 Replace(if_default, control);
516 }
517 return Replace(dead());
518 }
519 return NoChange();
520}
521
523 DCHECK_EQ(IrOpcode::kStaticAssert, node->opcode());
524 Node* const cond = node->InputAt(0);
526 if (decision == Decision::kTrue) {
527 RelaxEffectsAndControls(node);
528 return Changed(node);
529 } else {
530 return NoChange();
531 }
532}
533
535 DCHECK(trap->opcode() == IrOpcode::kTrapIf ||
536 trap->opcode() == IrOpcode::kTrapUnless);
537 bool trapping_condition = trap->opcode() == IrOpcode::kTrapIf;
538 Node* const cond = trap->InputAt(0);
540
541 if (decision == Decision::kUnknown) {
542 return NoChange();
543 } else if ((decision == Decision::kTrue) == trapping_condition) {
544 // This will always trap. Mark its outputs as dead and connect it to
545 // graph()->end().
546 ReplaceWithValue(trap, dead(), dead(), dead());
547 Node* control = graph()->NewNode(common()->Throw(), trap, trap);
548 MergeControlToEnd(graph(), common(), control);
549 return Changed(trap);
550 } else {
551 // This will not trap, remove it by relaxing effect/control.
553 ReplaceWithValue(trap, dead());
554 trap->Kill();
555 // The argument below is irrelevant, picked {control} for debugging.
556 return Replace(control);
557 }
558}
559
561 Node* a) {
562 node->ReplaceInput(0, a);
563 node->TrimInputCount(1);
564 NodeProperties::ChangeOp(node, op);
565 return Changed(node);
566}
567
568
570 Node* b) {
571 node->ReplaceInput(0, a);
572 node->ReplaceInput(1, b);
573 node->TrimInputCount(2);
574 NodeProperties::ChangeOp(node, op);
575 return Changed(node);
576}
577
578} // namespace compiler
579} // namespace internal
580} // namespace v8
T * AllocateArray(size_t length)
Definition zone.h:127
BranchSemantics BranchSemanticsOf(const Node *branch)
CommonOperatorReducer(Editor *editor, TFGraph *graph, JSHeapBroker *broker, CommonOperatorBuilder *common, MachineOperatorBuilder *machine, Zone *temp_zone, BranchSemantics default_branch_semantics)
Reduction Change(Node *node, Operator const *op, Node *a)
Decision DecideCondition(Node *const cond, BranchSemantics branch_semantics)
const FeedbackSource & feedback() const
static bool IsMergeOpcode(Value value)
Definition opcodes.h:1404
static bool IsPhiOpcode(Value value)
Definition opcodes.h:1400
static void ChangeOp(Node *node, const Operator *new_op)
static void ReplaceEffectInput(Node *node, Node *effect, int index=0)
static Node * GetEffectInput(Node *node, int index=0)
static void CollectControlProjections(Node *node, Node **proj, size_t count)
static Node * GetValueInput(Node *node, int index)
static void SetType(Node *node, Type type)
static void ReplaceValueInput(Node *node, Node *value, int index)
static Node * GetControlInput(Node *node, int index=0)
constexpr IrOpcode::Value opcode() const
Definition node.h:52
Inputs inputs() const
Definition node.h:478
void TrimInputCount(int new_input_count)
Definition node.cc:262
const Operator * op() const
Definition node.h:50
int InputCount() const
Definition node.h:59
Node * InputAt(int index) const
Definition node.h:70
bool OwnedBy(Node const *owner) const
Definition node.cc:325
Node * NewNode(const Operator *op, int input_count, Node *const *inputs, bool incomplete=false)
Zone * zone_
JSHeapBroker *const broker_
int end
JSHeapBroker * broker
std::optional< OolTrapLabel > trap
int m
Definition mul-fft.cc:294
IfValueParameters const & IfValueParametersOf(const Operator *op)
int ValueInputCountOfReturn(Operator const *const op)
BranchHint BranchHintOf(const Operator *const op)
BranchHint NegateBranchHint(BranchHint hint)
DeoptimizeParameters const & DeoptimizeParametersOf(Operator const *const op)
Node * SkipValueIdentities(Node *node)
#define DCHECK_LE(v1, v2)
Definition logging.h:490
#define DCHECK_NE(v1, v2)
Definition logging.h:486
#define DCHECK(condition)
Definition logging.h:482
#define DCHECK_EQ(v1, v2)
Definition logging.h:485
TFGraph * graph_