Loading...
Searching...
No Matches
speculationExecutorEngine.h
Go to the documentation of this file.
1//
2// Copyright 2025 Pixar
3//
4// Licensed under the terms set forth in the LICENSE.txt file available at
5// https://openusd.org/license.
6//
7#ifndef PXR_EXEC_VDF_SPECULATION_EXECUTOR_ENGINE_H
8#define PXR_EXEC_VDF_SPECULATION_EXECUTOR_ENGINE_H
9
11
12#include "pxr/pxr.h"
13
15
20#include "pxr/exec/vdf/node.h"
23
25
26PXR_NAMESPACE_OPEN_SCOPE
27
29
39template <typename DataManagerType>
41 public VdfPullBasedExecutorEngine<DataManagerType>
42{
43 // Base type definition
44 typedef
46 Base;
47
48public:
49
53 const VdfSpeculationExecutorBase &speculationExecutor,
54 DataManagerType *dataManager) :
55 VdfPullBasedExecutorEngine<DataManagerType>(
56 speculationExecutor, dataManager),
57 _writeBackExecutor(const_cast<VdfExecutorInterface *>(
58 speculationExecutor.GetNonSpeculationParentExecutor())) {
59 TF_VERIFY(_writeBackExecutor);
60 }
61
66 const VdfSchedule &schedule,
67 const VdfRequest &computeRequest,
68 VdfExecutorErrorLogger *errorLogger) {
70 schedule, computeRequest, errorLogger,
71 [](const VdfMaskedOutput &, size_t){});
72 }
73
79 template <typename F>
80 void RunSchedule(
81 const VdfSchedule &schedule,
82 const VdfRequest &computeRequest,
83 VdfExecutorErrorLogger *errorLogger,
84 F &&callback);
85
86private:
87 // MSVC errors out if Base::ExecutionStageStart is accessed in the
88 // _OutputToExecute constructor, because the enumerator is protected in the
89 // base class. GCC and clang do not consider this an error. To work around
90 // this issue, we loft the enumerator into the namespace of the derived
91 // class where _OutputToExecute() has the proper privileges across all
92 // compilers.
93 static constexpr typename Base::_ExecutionStage _ExecutionStageStart =
94 Base::ExecutionStageStart;
95
96 // This struct contains the necessary state to compute an output.
97 //
98 struct _OutputToExecute {
99
100 // Constructor that takes schedule node and output.
101 _OutputToExecute(const VdfSchedule::OutputId &outputId) :
102 outputId(outputId),
103 stage(_ExecutionStageStart),
104 numPushed(0),
105 inputsSpeculate(false)
106 { }
107
108 // The schedule identifier for the output to execute.
109 VdfSchedule::OutputId outputId;
110
111 // The current phase of this output in the execution stack.
112 typename Base::_ExecutionStage stage;
113
114 // The number of inputs that this output is waiting on.
115 int numPushed;
116
117 // Whether or not our read inputs speculate.
118 bool inputsSpeculate;
119 };
120
121 // This method adds \p output to the \p outputs vector.
122 // Returns \c true if it added a new output and \c false otherwise.
123 //
124 bool _PushBackOutputForSpeculation(
125 std::vector< _OutputToExecute > *outputs,
126 const VdfOutput &output,
127 const VdfSchedule &schedule);
128
129 // Method that makes sure that data is available for the given \p output
130 // before returning.
131 //
132 void _ExecuteOutputForSpeculation(
133 const VdfEvaluationState &state,
134 const VdfOutput &output,
135 TfBits *executedNodes,
136 TfBits *speculatedNodes);
137
138 // Write the computed output back to the write-back executor.
139 void _WriteBackComputedOutput(
140 const VdfOutput &output,
141 const VdfSchedule::OutputId &outputId,
142 const VdfSchedule &schedule);
143
144 // The parent executor this speculation engine is going to write back to.
145 VdfExecutorInterface *_writeBackExecutor;
146
147};
148
150
151template <typename DataManagerType>
152bool
154 std::vector< _OutputToExecute > *outputs,
155 const VdfOutput &output,
156 const VdfSchedule &schedule)
157{
158 VdfSchedule::OutputId outputId = schedule.GetOutputId(output);
159
160 if (outputId.IsValid()) {
161 outputs->push_back(_OutputToExecute(outputId));
162 return true;
163 }
164
165 // The output to push is not actually scheduled, which guarantees
166 // that is value will never be needed by any computations. So
167 // just skip it.
168 return false;
169}
170
171template <typename DataManagerType>
172template <typename F>
173void
175 const VdfSchedule &schedule,
176 const VdfRequest &computeRequest,
177 VdfExecutorErrorLogger *errorLogger,
178 F &&callback)
179{
180 TRACE_FUNCTION();
181 TfAutoMallocTag tag("VdfSpeculationExecutorEngine::RunSchedule");
182
183 // Make sure the executor data manager is appropriately sized.
184 Base::_GetDataManager()->Resize(*schedule.GetNetwork());
185
186 const size_t numNodes = schedule.GetScheduleNodeVector().size();
187
188 // Has a bit set for any node that has already been run.
189 TfBits executedNodes(numNodes);
190
191 // Has a bit set for any node, which had one ore more inputs speculated.
192 TfBits speculatedNodes(numNodes);
193
194 // The persistent evaluation state
195 VdfEvaluationState state(Base::_GetExecutor(), schedule, errorLogger);
196
197 // Now execute the uncached, requested outputs.
198 VdfRequest::IndexedView requestView(computeRequest);
199 for (size_t i = 0; i < requestView.GetSize(); ++i) {
200 // Skip outputs not included in the request.
201 const VdfMaskedOutput *maskedOutput = requestView.Get(i);
202 if (!maskedOutput) {
203 continue;
204 }
205
206 // Skip outputs that have already been cached. However, we must invoke
207 // the callback to notify the client side that evaluation of the
208 // requested output has completed.
209 const VdfOutput &output = *maskedOutput->GetOutput();
210 const VdfMask &mask = maskedOutput->GetMask();
211 if (Base::_GetExecutor().GetOutputValue(output, mask)) {
212 callback(*maskedOutput, i);
213 continue;
214 }
215
216 _ExecuteOutputForSpeculation(
217 state, output, &executedNodes, &speculatedNodes);
218
219 // If we've been interrupted, bail out.
220 if (Base::_GetExecutor().HasBeenInterrupted()) {
221 break;
222 }
223
224 // Invoke the callback once the output has been evaluated, but only
225 // if the executor has not been interrupted.
226 else {
227 callback(*maskedOutput, i);
228 }
229 }
230}
231
232template <typename DataManagerType>
233void
235 const VdfEvaluationState &state,
236 const VdfOutput &output,
237 TfBits *executedNodes,
238 TfBits *speculatedNodes)
239{
240 TF_DEBUG(VDF_SEE_TRACE).Msg("{ SpeculationOutputExecuteBegin();\n");
241
242 // The current schedule
243 const VdfSchedule &schedule = state.GetSchedule();
244
245 // This is the stack of the outputs currently in the process of execution.
246 std::vector< _OutputToExecute > outputsStack;
247
248 // This is a stack used for the return values of outputs. A return value
249 // of true means that the output couldn't be evaluated due to speculation.
250 // XXX:optimization
251 // It's possible to get rid of this vector all together if outputs were
252 // allowed to write directly into their caller's stack space.
253 std::vector< bool > speculated;
254
255 // Add the initial output to start executing. This call will check for
256 // already cached values.
257 _PushBackOutputForSpeculation(&outputsStack, output, schedule);
258
259 bool hasBeenInterrupted = Base::_GetExecutor().HasBeenInterrupted();
260
261 while (!outputsStack.empty() && !hasBeenInterrupted) {
262
263 const VdfSchedule::OutputId &outputId = outputsStack.back().outputId;
264 const int scheduleNodeIndex = schedule.GetScheduleNodeIndex(outputId);
265 const VdfNode &node = *schedule.GetNode(outputId);
266 typename Base::_ExecutionStage stage = outputsStack.back().stage;
267 const size_t outputIndex = outputsStack.size() - 1;
268
269 bool affective = schedule.IsAffective(outputId);
270
271 // Pop all the return values from our inputs and check to see
272 // if any of them were 'true' (meaning that they hit a speculation
273 // path).
274 bool previousStageSpeculated = false;
275 while (outputsStack.back().numPushed) {
276 outputsStack.back().numPushed--;
277 previousStageSpeculated |= speculated.back();
278 speculated.pop_back();
279 }
280
281 switch (stage) {
282
283 case Base::ExecutionStageStart:
284
285 TF_DEBUG(VDF_SEE_TRACE)
286 .Msg("{ SpeculationBeginNode(%p, \"%s\");\n",
287 &node, node.GetDebugName().c_str());
288
289 // If this is the node that started the speculation, we need to
290 // skip it. Note that this means we encountered a true data
291 // dependency cycle and have a bad result. Additionally, we may
292 // write back the bad result to any parent executors.
293 if (static_cast<const VdfSpeculationExecutorBase &>(
294 Base::_GetExecutor()).IsSpeculatingNode(&node)) {
295 speculated.push_back(true);
296 outputsStack.pop_back();
297
298 TF_DEBUG(VDF_SEE_TRACE)
299 .Msg("SpeculationEndNodeSpeculationNode(); (cycle) }\n");
300
301 continue;
302 }
303
304 // If this node has already been executed, do not run it a second
305 // time. However, make sure to push the right value onto the
306 // speculated stack, based on whether the node had inputs we
307 // speculated about, the last time it was run.
308 if (executedNodes->IsSet(scheduleNodeIndex)) {
309 speculated.push_back(
310 speculatedNodes->IsSet(scheduleNodeIndex));
311 outputsStack.pop_back();
312
313 TF_DEBUG(VDF_SEE_TRACE)
314 .Msg("SpeculationEndNodeRedundantCompute(); }\n");
315
316 continue;
317 }
318
319 // If we are already cached for this output (or if our parent
320 // executor is), then we can provide a value, we can return early.
321 if (Base::_GetExecutor().GetOutputValue(
322 *schedule.GetOutput(outputId),
323 schedule.GetRequestMask(outputId))) {
324 speculated.push_back(false);
325 outputsStack.pop_back();
326
327 TF_DEBUG(VDF_SEE_TRACE)
328 .Msg("SpeculationEndNodeFoundCache(); }\n");
329
330 continue;
331 }
332
333 TF_DEV_AXIOM(outputsStack.back().numPushed == 0);
334
335 // The first stage of computation is to execute all the
336 // prerequisites for current output.
337 outputsStack.back().stage = Base::ExecutionStagePreRequisitesDone;
338
339 // Push back all the prerequisites
340 if (affective) {
341 for (const VdfScheduleInput &input : schedule.GetInputs(node)) {
342 if (input.input->GetSpec().IsPrerequisite()) {
343 const bool pushed = _PushBackOutputForSpeculation(
344 &outputsStack, *input.source, schedule);
345 outputsStack[outputIndex].numPushed += pushed;
346 }
347 }
348 }
349
350 // Little optimization to not go back to the top of the loop
351 // for no reason.
352 if (outputsStack[outputIndex].numPushed > 0) {
353 break;
354 } // else fall through to the next stage.
355
356
357 case Base::ExecutionStagePreRequisitesDone:
358
359
360 // Now that our prerequisites are done, unroll our return stack.
361
362
363 // Update whether or not our previousStageSpeculated
364 outputsStack.back().inputsSpeculate |= previousStageSpeculated;
365
366 // The second stage of computation is to use the prerequisites
367 // to determine what other inputs need to run to satisfy the
368 // current output.
369
370 // Mark that the next stage of computation
371 outputsStack.back().stage = Base::ExecutionStageReadsDone;
372
373 // Only need to run the reads of an output that will do something
374 // and if our pre-requisites were computed without speculation.
375 if (affective && !previousStageSpeculated) {
376
377 // Get the list of required inputs based on the prerequisite
378 // computations.
379 VdfRequiredInputsPredicate inputsPredicate =
380 node.GetRequiredInputsPredicate(VdfContext(state, node));
381
382 // Run the required reads first.
383 // Here we try to run the "read" inputs before the "read/write"
384 // inputs so that we can maximize the chance of being able to
385 // re-use the buffer.
386 if (inputsPredicate.HasRequiredReads()) {
387 for (const VdfScheduleInput &input :
388 schedule.GetInputs(node)) {
389 if (inputsPredicate.IsRequiredRead(*input.input)) {
390 const bool pushed = _PushBackOutputForSpeculation(
391 &outputsStack, *input.source, schedule);
392 outputsStack[outputIndex].numPushed += pushed;
393 }
394 }
395 }
396 }
397
398 // Little optimization to not go back to the top of the loop
399 // for no reason.
400 if (outputsStack[outputIndex].numPushed > 0) {
401 break;
402 } // else fall through to the next stage.
403
404 case Base::ExecutionStageReadsDone:
405
406 // Mark that the next stage of computation
407 outputsStack.back().stage = Base::ExecutionStageCompute;
408
409 // Mark whether or not our read inputs depend on a speculation.
410 outputsStack.back().inputsSpeculate |= previousStageSpeculated;
411
412 // Now run the read/writes last.
413 for (const VdfScheduleInput &input : schedule.GetInputs(node)) {
414 const VdfOutput *assocOutput =
415 input.input->GetAssociatedOutput();
416 if (!assocOutput) {
417 continue;
418 }
419
420 // Does this output have a pass-through scheduled?
421 const VdfSchedule::OutputId &assocOutputId =
422 schedule.GetOutputId(*assocOutput);
423 if (assocOutputId.IsValid()) {
424 if (const VdfOutput *fromBufferOutput =
425 schedule.GetFromBufferOutput(assocOutputId)) {
426 const bool pushed = _PushBackOutputForSpeculation(
427 &outputsStack, *fromBufferOutput, schedule);
428 outputsStack[outputIndex].numPushed += pushed;
429 continue;
430 }
431 }
432
433 // If the associated output is not scheduled, or it does not
434 // have a pass-through scheduled, we need to consider all
435 // connected source outputs!
436 const bool pushed = _PushBackOutputForSpeculation(
437 &outputsStack, *input.source, schedule);
438 outputsStack[outputIndex].numPushed += pushed;
439 }
440
441 // Little optimization to not go back to the top of the loop
442 // for no reason.
443 if (outputsStack[outputIndex].numPushed > 0) {
444 break;
445 } // else fall through to the next stage.
446
447
448 default:
449
450 // Mark whether or not our read/write inputs depend on
451 // a speculation.
452 outputsStack.back().inputsSpeculate |= previousStageSpeculated;
453
454 // Set a bit indicating that this node has been executed.
455 executedNodes->Set(scheduleNodeIndex);
456
457 // If any of our inputs speculated, there is nothing we can do.
458 // Skip this node, but make sure to still touch its outputs.
459 if (outputsStack.back().inputsSpeculate) {
460
461 TF_DEBUG(VDF_SEE_TRACE)
462 .Msg("SpeculationSkipNode (cycle) (\"%s\"); }\n",
463 node.GetDebugName().c_str());
464
465 // This node has speculated inputs
466 speculatedNodes->Set(scheduleNodeIndex);
467 speculated.push_back(true);
468
469 // Compute this node, if it is affective, or pass-through if any
470 // of the reads speculated.
471 } else if (affective) {
472 // None of our inputs speculated, we can just compute as
473 // normal.
474 Base::_ComputeNode(state, node);
475 speculated.push_back(false);
476
477 TF_DEBUG(VDF_SEE_TRACE)
478 .Msg("SpeculationComputedNode(\"%s\"); }\n",
479 node.GetDebugName().c_str());
480
481 // The node is not affective, and none of its reads or read/writes
482 // did speculate.
483 } else {
484 // None of the outputs on this node contribute to the
485 // results in the request, so we will skip over this node
486 // by passing through all the outputs with associated
487 // inputs and use the fallback value for all the outputs
488 // that don't.
489 TF_DEBUG(VDF_SEE_TRACE)
490 .Msg("SpeculationPassThrough(\"%s\"); }\n",
491 node.GetDebugName().c_str());
492
493 Base::_PassThroughNode(schedule, node);
494 speculated.push_back(false);
495
496 }
497
498 // Check interruption.
499 hasBeenInterrupted = Base::_GetExecutor().HasBeenInterrupted();
500
501 // Mark that we've visited these outputs in our parent
502 // executor. We need to tell the parent executor that
503 // we've visited this node so that we receive invalidation
504 // the next time it is required. If we don't mark the
505 // output as needing invalidation and the main executor
506 // never needs to execute it, then it will never get
507 // invalidated.
508 // Also write back any computed or pass-through data to the
509 // write back executor, so that the data can be picked up by
510 // another executor. Note, that we do NOT want to write back
511 // any data after interruption, because the buffers may
512 // contain junk.
513 VDF_FOR_EACH_SCHEDULED_OUTPUT_ID(outputId, schedule, node) {
514 const VdfOutput& output = *schedule.GetOutput(outputId);
515 if (_writeBackExecutor &&
516 !hasBeenInterrupted &&
517 !outputsStack.back().inputsSpeculate) {
518 _WriteBackComputedOutput(output, outputId, schedule);
519 }
520 Base::_GetExecutor()._TouchOutput(output);
521 }
522
523 outputsStack.pop_back();
524 }
525 }
526
527 TF_DEBUG(VDF_SEE_TRACE).Msg("SpeculationOutputExecuteEnd(); }\n");
528}
529
530template <typename DataManagerType>
531void
533 const VdfOutput &output,
534 const VdfSchedule::OutputId &outputId,
535 const VdfSchedule &schedule)
536{
537 // Retrieve the data handle.
538 const typename Base::_DataHandle dataHandle =
539 Base::_GetDataManager()->GetDataHandle(output.GetId());
540 if (!Base::_GetDataManager()->IsValidDataHandle(dataHandle)) {
541 return;
542 }
543
544 // Get the buffer data associated with the data handle.
545 VdfExecutorBufferData *bufferData =
546 Base::_GetDataManager()->GetBufferData(dataHandle);
547
548 // Get the output vector and computed output mask
549 const VdfVector *value = bufferData->GetExecutorCache();
550
551 // If the data is not available we are done. This can happen with
552 // node that manage their own buffers and choose to leave them empty.
553 if (!value) {
554 return;
555 }
556
557 const VdfMask &computedMask = bufferData->GetExecutorCacheMask();
558
559 // If the computed output mask is empty, we can bail out early. This may
560 // happen if, for example, the executor was interrupted and opted for not
561 // writing a computed output mask for the current node.
562 // Don't even bother writing back an all-zeros mask.
563 if (computedMask.IsEmpty() || computedMask.IsAllZeros()) {
564 return;
565 }
566
567 // If the output does not pass its data, we can write the full output value
568 // back to the write executor.
569 if (!output.GetAssociatedInput()) {
570 _writeBackExecutor->SetOutputValue(output, *value, computedMask);
571
572 // Reclaim locally, so that future cache lookups result in hits on
573 // the parent executor, but not the local executor.
574 //
575 // XXX
576 // This guards against client callbacks that mutate cached values (which
577 // is something we have encountered in practice), causing output values
578 // to change after the node has already run. By removing the buffer
579 // locally, we ensure that the next time we access the buffer we get it
580 // from the parent executor and modify it there. We would prefer to not
581 // support this client behavior, but for now, we choose to keep this,
582 // since it's not expensive, and safer.
583 bufferData->ResetExecutorCache();
584 }
585
586 // If the output passes its data, we may still be able to write back some
587 // or all of it.
588 else {
589 // If this output is not scheduled to pass its data, we can simply copy
590 // the entire executor cache. Alternatively, if the output is scheduled
591 // to pass its data, we can at least copy anything that will be kept at
592 // the output. Unless, however, invalidation entered somewhere between
593 // this output, and the output we are going to pass the data to. If this
594 // is the case, we want to write back the entire cache to the write back
595 // executor, making this algorithm the equivalent of mung buffer locking
596 // on the main executor!
597 const VdfMask *writeBackMask = &computedMask;
598 const VdfOutput *passToOutput = schedule.GetPassToOutput(outputId);
599 if (passToOutput &&
600 !_writeBackExecutor->HasInvalidationTimestampMismatch(
601 output, *passToOutput)) {
602 writeBackMask = &schedule.GetKeepMask(outputId);
603 }
604
605 if (!writeBackMask->IsEmpty()) {
606 _writeBackExecutor->SetOutputValue(output, *value, *writeBackMask);
607 }
608 }
609}
610
611PXR_NAMESPACE_CLOSE_SCOPE
612
613#endif
Fast bit array that keeps track of the number of bits set and can find the next set in a timely manne...
Definition bits.h:49
void Set(size_t index)
Sets bit # index to one.
Definition bits.h:377
bool IsSet(size_t index) const
Returns true, if bit # index is set.
Definition bits.h:412
Scoped (i.e.
Definition mallocTag.h:336
A context is the parameter bundle passed to callbacks of computations.
Definition context.h:40
This object holds state that remains persistent during one round of network evaluation.
const VdfSchedule & GetSchedule() const
The schedule used for evaluation.
This object is responsible for storing the executor buffer data, comprised of the executor cache vect...
void ResetExecutorCache(const VdfMask &mask)
Reset the executor cache without releasing any memory and set the executor cache mask to mask.
const VdfMask & GetExecutorCacheMask() const
Get the available mask.
VdfVector * GetExecutorCache() const
Returns the executor cache stored at this buffer data instance.
A client may instantiate an object of this class and set it in an executor, to collect errors that ma...
Abstract base class for classes that execute a VdfNetwork to compute a requested set of values.
A VdfMask is placed on connections to specify the data flowing through them.
Definition mask.h:37
bool IsAllZeros() const
Returns true if this mask has all entries unset.
Definition mask.h:206
bool IsEmpty() const
Returns true if this mask is empty, i.e.
Definition mask.h:168
Class to hold on to an externally owned output and a mask.
VdfOutput * GetOutput() const
Returns the VdfOutput.
const VdfMask & GetMask() const
Returns the VdfMask.
This is the base class for all nodes in a VdfNetwork.
Definition node.h:53
VDF_API const std::string GetDebugName() const
Returns the debug name for this node, if one is registered.
virtual VDF_API VdfRequiredInputsPredicate GetRequiredInputsPredicate(const VdfContext &context) const
Returns a predicate, determining whether a given input and its connections are required in order to f...
A VdfOutput represents an output on a node.
Definition output.h:32
This class is a collection of common functions used by pulled based executors.
_ExecutionStage
This enum describes the stages that a node goes through in execution.
This predicate determines whether a given input value is needed to fulfill the input dependencies req...
bool IsRequiredRead(const VdfInput &input) const
Is this input a required read? Note that read/writes as well as prerequisite inputs are not required ...
bool HasRequiredReads() const
Are any inputs required?
An OutputId is a small key object that, once obtained for a particular VdfOutput, can be used to quer...
Definition schedule.h:92
bool IsValid() const
Returns whether this OutputId can be used to make queries about an output's scheduling.
Definition schedule.h:98
Contains a specification of how to execute a particular VdfNetwork.
Definition schedule.h:42
const VdfNetwork * GetNetwork() const
Returns the network for this schedule.
Definition schedule.h:179
VDF_API OutputId GetOutputId(const VdfOutput &output) const
Returns a small, cheap OutputId, which can be passed to other Get* methods in this class to efficient...
VDF_API const VdfMask & GetRequestMask(const OutputId &outputId) const
Returns the request mask associated with the given OutputId.
VDF_API const VdfOutput * GetFromBufferOutput(const OutputId &outputId) const
Returns the "from buffer's" output associated with the given OutputId.
VDF_API InputsRange GetInputs(const VdfNode &node) const
Returns a range of inputs scheduled for the given node.
int GetScheduleNodeIndex(const OutputId &outputId) const
Returns the node index of the schedule node associated with the given outputId.
Definition schedule.h:515
VDF_API const VdfMask & GetKeepMask(const OutputId &outputId) const
Returns the keep mask associated with the given OutputId.
VDF_API const VdfOutput * GetPassToOutput(const OutputId &outputId) const
Returns the "pass to" output associated with the given OutputId.
VDF_API const VdfNode * GetNode(const OutputId &outputId) const
Returns the VdfNode that owns the VdfOutput associated with the given outputId.
ScheduleNodeVector & GetScheduleNodeVector()
Returns the vector of schedule nodes in this schedule.
Definition schedule.h:504
VDF_API bool IsAffective(const OutputId &outputId) const
Returns true if the output is expected to have an effect on its corresponding input,...
VDF_API const VdfOutput * GetOutput(const OutputId &outputId) const
Returns the scheduled VdfOutput associated with the given OutputId.
This class provides an executor engine to the speculation executor.
VdfSpeculationExecutorEngine(const VdfSpeculationExecutorBase &speculationExecutor, DataManagerType *dataManager)
Constructs an engine used by the speculation executor.
void RunSchedule(const VdfSchedule &schedule, const VdfRequest &computeRequest, VdfExecutorErrorLogger *errorLogger)
Executes the given schedule with a computeRequest and an optional /p errorLogger.
This class is used to abstract away knowledge of the cache data used for each node.
Definition vector.h:59
#define TF_DEBUG(enumVal)
Evaluate and print debugging message msg if enumVal is enabled for debugging.
Definition debug.h:501
#define TF_DEV_AXIOM(cond)
The same as TF_AXIOM, but compiled only in dev builds.
Definition diagnostic.h:206
#define TF_VERIFY(cond, format,...)
Checks a condition and reports an error if it evaluates false.
Definition diagnostic.h:267
This class contains scheduling information for an input.