MLIR  22.0.0git
DeserializeOps.cpp
Go to the documentation of this file.
1 //===- DeserializeOps.cpp - MLIR SPIR-V Deserialization (Ops) -------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // This file defines the Deserializer methods for SPIR-V binary instructions.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "Deserializer.h"
14 
17 #include "mlir/IR/Builders.h"
18 #include "mlir/IR/Location.h"
20 #include "llvm/ADT/STLExtras.h"
21 #include "llvm/ADT/SmallVector.h"
22 #include "llvm/Support/Debug.h"
23 #include <optional>
24 
25 using namespace mlir;
26 
27 #define DEBUG_TYPE "spirv-deserialization"
28 
29 //===----------------------------------------------------------------------===//
30 // Utility Functions
31 //===----------------------------------------------------------------------===//
32 
33 /// Extracts the opcode from the given first word of a SPIR-V instruction.
34 static inline spirv::Opcode extractOpcode(uint32_t word) {
35  return static_cast<spirv::Opcode>(word & 0xffff);
36 }
37 
38 //===----------------------------------------------------------------------===//
39 // Instruction
40 //===----------------------------------------------------------------------===//
41 
42 Value spirv::Deserializer::getValue(uint32_t id) {
43  if (auto constInfo = getConstant(id)) {
44  // Materialize a `spirv.Constant` op at every use site.
45  return spirv::ConstantOp::create(opBuilder, unknownLoc, constInfo->second,
46  constInfo->first);
47  }
48  if (std::optional<std::pair<Attribute, Type>> constCompositeReplicateInfo =
49  getConstantCompositeReplicate(id)) {
50  return spirv::EXTConstantCompositeReplicateOp::create(
51  opBuilder, unknownLoc, constCompositeReplicateInfo->second,
52  constCompositeReplicateInfo->first);
53  }
54  if (auto varOp = getGlobalVariable(id)) {
55  auto addressOfOp =
56  spirv::AddressOfOp::create(opBuilder, unknownLoc, varOp.getType(),
57  SymbolRefAttr::get(varOp.getOperation()));
58  return addressOfOp.getPointer();
59  }
60  if (auto constOp = getSpecConstant(id)) {
61  auto referenceOfOp = spirv::ReferenceOfOp::create(
62  opBuilder, unknownLoc, constOp.getDefaultValue().getType(),
63  SymbolRefAttr::get(constOp.getOperation()));
64  return referenceOfOp.getReference();
65  }
66  if (SpecConstantCompositeOp specConstCompositeOp =
67  getSpecConstantComposite(id)) {
68  auto referenceOfOp = spirv::ReferenceOfOp::create(
69  opBuilder, unknownLoc, specConstCompositeOp.getType(),
70  SymbolRefAttr::get(specConstCompositeOp.getOperation()));
71  return referenceOfOp.getReference();
72  }
73  if (auto specConstCompositeReplicateOp =
74  getSpecConstantCompositeReplicate(id)) {
75  auto referenceOfOp = spirv::ReferenceOfOp::create(
76  opBuilder, unknownLoc, specConstCompositeReplicateOp.getType(),
77  SymbolRefAttr::get(specConstCompositeReplicateOp.getOperation()));
78  return referenceOfOp.getReference();
79  }
80  if (auto specConstOperationInfo = getSpecConstantOperation(id)) {
81  return materializeSpecConstantOperation(
82  id, specConstOperationInfo->enclodesOpcode,
83  specConstOperationInfo->resultTypeID,
84  specConstOperationInfo->enclosedOpOperands);
85  }
86  if (auto undef = getUndefType(id)) {
87  return spirv::UndefOp::create(opBuilder, unknownLoc, undef);
88  }
89  return valueMap.lookup(id);
90 }
91 
92 LogicalResult spirv::Deserializer::sliceInstruction(
93  spirv::Opcode &opcode, ArrayRef<uint32_t> &operands,
94  std::optional<spirv::Opcode> expectedOpcode) {
95  auto binarySize = binary.size();
96  if (curOffset >= binarySize) {
97  return emitError(unknownLoc, "expected ")
98  << (expectedOpcode ? spirv::stringifyOpcode(*expectedOpcode)
99  : "more")
100  << " instruction";
101  }
102 
103  // For each instruction, get its word count from the first word to slice it
104  // from the stream properly, and then dispatch to the instruction handler.
105 
106  uint32_t wordCount = binary[curOffset] >> 16;
107 
108  if (wordCount == 0)
109  return emitError(unknownLoc, "word count cannot be zero");
110 
111  uint32_t nextOffset = curOffset + wordCount;
112  if (nextOffset > binarySize)
113  return emitError(unknownLoc, "insufficient words for the last instruction");
114 
115  opcode = extractOpcode(binary[curOffset]);
116  operands = binary.slice(curOffset + 1, wordCount - 1);
117  curOffset = nextOffset;
118  return success();
119 }
120 
121 LogicalResult spirv::Deserializer::processInstruction(
122  spirv::Opcode opcode, ArrayRef<uint32_t> operands, bool deferInstructions) {
123  LLVM_DEBUG(logger.startLine() << "[inst] processing instruction "
124  << spirv::stringifyOpcode(opcode) << "\n");
125 
126  // First dispatch all the instructions whose opcode does not correspond to
127  // those that have a direct mirror in the SPIR-V dialect
128  switch (opcode) {
129  case spirv::Opcode::OpCapability:
130  return processCapability(operands);
131  case spirv::Opcode::OpExtension:
132  return processExtension(operands);
133  case spirv::Opcode::OpExtInst:
134  return processExtInst(operands);
135  case spirv::Opcode::OpExtInstImport:
136  return processExtInstImport(operands);
137  case spirv::Opcode::OpMemberName:
138  return processMemberName(operands);
139  case spirv::Opcode::OpMemoryModel:
140  return processMemoryModel(operands);
141  case spirv::Opcode::OpEntryPoint:
142  case spirv::Opcode::OpExecutionMode:
143  if (deferInstructions) {
144  deferredInstructions.emplace_back(opcode, operands);
145  return success();
146  }
147  break;
148  case spirv::Opcode::OpVariable:
149  if (isa<spirv::ModuleOp>(opBuilder.getBlock()->getParentOp())) {
150  return processGlobalVariable(operands);
151  }
152  break;
153  case spirv::Opcode::OpLine:
154  return processDebugLine(operands);
155  case spirv::Opcode::OpNoLine:
156  clearDebugLine();
157  return success();
158  case spirv::Opcode::OpName:
159  return processName(operands);
160  case spirv::Opcode::OpString:
161  return processDebugString(operands);
162  case spirv::Opcode::OpModuleProcessed:
163  case spirv::Opcode::OpSource:
164  case spirv::Opcode::OpSourceContinued:
165  case spirv::Opcode::OpSourceExtension:
166  // TODO: This is debug information embedded in the binary which should be
167  // translated into the spirv.module.
168  return success();
169  case spirv::Opcode::OpTypeVoid:
170  case spirv::Opcode::OpTypeBool:
171  case spirv::Opcode::OpTypeInt:
172  case spirv::Opcode::OpTypeFloat:
173  case spirv::Opcode::OpTypeVector:
174  case spirv::Opcode::OpTypeMatrix:
175  case spirv::Opcode::OpTypeArray:
176  case spirv::Opcode::OpTypeFunction:
177  case spirv::Opcode::OpTypeImage:
178  case spirv::Opcode::OpTypeSampledImage:
179  case spirv::Opcode::OpTypeRuntimeArray:
180  case spirv::Opcode::OpTypeStruct:
181  case spirv::Opcode::OpTypePointer:
182  case spirv::Opcode::OpTypeTensorARM:
183  case spirv::Opcode::OpTypeCooperativeMatrixKHR:
184  return processType(opcode, operands);
185  case spirv::Opcode::OpTypeForwardPointer:
186  return processTypeForwardPointer(operands);
187  case spirv::Opcode::OpConstant:
188  return processConstant(operands, /*isSpec=*/false);
189  case spirv::Opcode::OpSpecConstant:
190  return processConstant(operands, /*isSpec=*/true);
191  case spirv::Opcode::OpConstantComposite:
192  return processConstantComposite(operands);
193  case spirv::Opcode::OpConstantCompositeReplicateEXT:
194  return processConstantCompositeReplicateEXT(operands);
195  case spirv::Opcode::OpSpecConstantComposite:
196  return processSpecConstantComposite(operands);
197  case spirv::Opcode::OpSpecConstantCompositeReplicateEXT:
198  return processSpecConstantCompositeReplicateEXT(operands);
199  case spirv::Opcode::OpSpecConstantOp:
200  return processSpecConstantOperation(operands);
201  case spirv::Opcode::OpConstantTrue:
202  return processConstantBool(/*isTrue=*/true, operands, /*isSpec=*/false);
203  case spirv::Opcode::OpSpecConstantTrue:
204  return processConstantBool(/*isTrue=*/true, operands, /*isSpec=*/true);
205  case spirv::Opcode::OpConstantFalse:
206  return processConstantBool(/*isTrue=*/false, operands, /*isSpec=*/false);
207  case spirv::Opcode::OpSpecConstantFalse:
208  return processConstantBool(/*isTrue=*/false, operands, /*isSpec=*/true);
209  case spirv::Opcode::OpConstantNull:
210  return processConstantNull(operands);
211  case spirv::Opcode::OpDecorate:
212  return processDecoration(operands);
213  case spirv::Opcode::OpMemberDecorate:
214  return processMemberDecoration(operands);
215  case spirv::Opcode::OpFunction:
216  return processFunction(operands);
217  case spirv::Opcode::OpLabel:
218  return processLabel(operands);
219  case spirv::Opcode::OpBranch:
220  return processBranch(operands);
221  case spirv::Opcode::OpBranchConditional:
222  return processBranchConditional(operands);
223  case spirv::Opcode::OpSelectionMerge:
224  return processSelectionMerge(operands);
225  case spirv::Opcode::OpLoopMerge:
226  return processLoopMerge(operands);
227  case spirv::Opcode::OpPhi:
228  return processPhi(operands);
229  case spirv::Opcode::OpUndef:
230  return processUndef(operands);
231  default:
232  break;
233  }
234  return dispatchToAutogenDeserialization(opcode, operands);
235 }
236 
237 LogicalResult spirv::Deserializer::processOpWithoutGrammarAttr(
238  ArrayRef<uint32_t> words, StringRef opName, bool hasResult,
239  unsigned numOperands) {
240  SmallVector<Type, 1> resultTypes;
241  uint32_t valueID = 0;
242 
243  size_t wordIndex = 0;
244  if (hasResult) {
245  if (wordIndex >= words.size())
246  return emitError(unknownLoc,
247  "expected result type <id> while deserializing for ")
248  << opName;
249 
250  // Decode the type <id>
251  auto type = getType(words[wordIndex]);
252  if (!type)
253  return emitError(unknownLoc, "unknown type result <id>: ")
254  << words[wordIndex];
255  resultTypes.push_back(type);
256  ++wordIndex;
257 
258  // Decode the result <id>
259  if (wordIndex >= words.size())
260  return emitError(unknownLoc,
261  "expected result <id> while deserializing for ")
262  << opName;
263  valueID = words[wordIndex];
264  ++wordIndex;
265  }
266 
267  SmallVector<Value, 4> operands;
269 
270  // Decode operands
271  size_t operandIndex = 0;
272  for (; operandIndex < numOperands && wordIndex < words.size();
273  ++operandIndex, ++wordIndex) {
274  auto arg = getValue(words[wordIndex]);
275  if (!arg)
276  return emitError(unknownLoc, "unknown result <id>: ") << words[wordIndex];
277  operands.push_back(arg);
278  }
279  if (operandIndex != numOperands) {
280  return emitError(
281  unknownLoc,
282  "found less operands than expected when deserializing for ")
283  << opName << "; only " << operandIndex << " of " << numOperands
284  << " processed";
285  }
286  if (wordIndex != words.size()) {
287  return emitError(
288  unknownLoc,
289  "found more operands than expected when deserializing for ")
290  << opName << "; only " << wordIndex << " of " << words.size()
291  << " processed";
292  }
293 
294  // Attach attributes from decorations
295  if (decorations.count(valueID)) {
296  auto attrs = decorations[valueID].getAttrs();
297  attributes.append(attrs.begin(), attrs.end());
298  }
299 
300  // Create the op and update bookkeeping maps
301  Location loc = createFileLineColLoc(opBuilder);
302  OperationState opState(loc, opName);
303  opState.addOperands(operands);
304  if (hasResult)
305  opState.addTypes(resultTypes);
306  opState.addAttributes(attributes);
307  Operation *op = opBuilder.create(opState);
308  if (hasResult)
309  valueMap[valueID] = op->getResult(0);
310 
311  if (op->hasTrait<OpTrait::IsTerminator>())
312  clearDebugLine();
313 
314  return success();
315 }
316 
317 LogicalResult spirv::Deserializer::processUndef(ArrayRef<uint32_t> operands) {
318  if (operands.size() != 2) {
319  return emitError(unknownLoc, "OpUndef instruction must have two operands");
320  }
321  auto type = getType(operands[0]);
322  if (!type) {
323  return emitError(unknownLoc, "unknown type <id> with OpUndef instruction");
324  }
325  undefMap[operands[1]] = type;
326  return success();
327 }
328 
329 LogicalResult spirv::Deserializer::processExtInst(ArrayRef<uint32_t> operands) {
330  if (operands.size() < 4) {
331  return emitError(unknownLoc,
332  "OpExtInst must have at least 4 operands, result type "
333  "<id>, result <id>, set <id> and instruction opcode");
334  }
335  if (!extendedInstSets.count(operands[2])) {
336  return emitError(unknownLoc, "undefined set <id> in OpExtInst");
337  }
338  SmallVector<uint32_t, 4> slicedOperands;
339  slicedOperands.append(operands.begin(), std::next(operands.begin(), 2));
340  slicedOperands.append(std::next(operands.begin(), 4), operands.end());
341  return dispatchToExtensionSetAutogenDeserialization(
342  extendedInstSets[operands[2]], operands[3], slicedOperands);
343 }
344 
345 namespace mlir {
346 namespace spirv {
347 
348 template <>
349 LogicalResult
350 Deserializer::processOp<spirv::EntryPointOp>(ArrayRef<uint32_t> words) {
351  unsigned wordIndex = 0;
352  if (wordIndex >= words.size()) {
353  return emitError(unknownLoc,
354  "missing Execution Model specification in OpEntryPoint");
355  }
356  auto execModel = spirv::ExecutionModelAttr::get(
357  context, static_cast<spirv::ExecutionModel>(words[wordIndex++]));
358  if (wordIndex >= words.size()) {
359  return emitError(unknownLoc, "missing <id> in OpEntryPoint");
360  }
361  // Get the function <id>
362  auto fnID = words[wordIndex++];
363  // Get the function name
364  auto fnName = decodeStringLiteral(words, wordIndex);
365  // Verify that the function <id> matches the fnName
366  auto parsedFunc = getFunction(fnID);
367  if (!parsedFunc) {
368  return emitError(unknownLoc, "no function matching <id> ") << fnID;
369  }
370  if (parsedFunc.getName() != fnName) {
371  // The deserializer uses "spirv_fn_<id>" as the function name if the input
372  // SPIR-V blob does not contain a name for it. We should use a more clear
373  // indication for such case rather than relying on naming details.
374  if (!parsedFunc.getName().starts_with("spirv_fn_"))
375  return emitError(unknownLoc,
376  "function name mismatch between OpEntryPoint "
377  "and OpFunction with <id> ")
378  << fnID << ": " << fnName << " vs. " << parsedFunc.getName();
379  parsedFunc.setName(fnName);
380  }
381  SmallVector<Attribute, 4> interface;
382  while (wordIndex < words.size()) {
383  auto arg = getGlobalVariable(words[wordIndex]);
384  if (!arg) {
385  return emitError(unknownLoc, "undefined result <id> ")
386  << words[wordIndex] << " while decoding OpEntryPoint";
387  }
388  interface.push_back(SymbolRefAttr::get(arg.getOperation()));
389  wordIndex++;
390  }
391  spirv::EntryPointOp::create(
392  opBuilder, unknownLoc, execModel,
393  SymbolRefAttr::get(opBuilder.getContext(), fnName),
394  opBuilder.getArrayAttr(interface));
395  return success();
396 }
397 
398 template <>
399 LogicalResult
400 Deserializer::processOp<spirv::ExecutionModeOp>(ArrayRef<uint32_t> words) {
401  unsigned wordIndex = 0;
402  if (wordIndex >= words.size()) {
403  return emitError(unknownLoc,
404  "missing function result <id> in OpExecutionMode");
405  }
406  // Get the function <id> to get the name of the function
407  auto fnID = words[wordIndex++];
408  auto fn = getFunction(fnID);
409  if (!fn) {
410  return emitError(unknownLoc, "no function matching <id> ") << fnID;
411  }
412  // Get the Execution mode
413  if (wordIndex >= words.size()) {
414  return emitError(unknownLoc, "missing Execution Mode in OpExecutionMode");
415  }
416  auto execMode = spirv::ExecutionModeAttr::get(
417  context, static_cast<spirv::ExecutionMode>(words[wordIndex++]));
418 
419  // Get the values
420  SmallVector<Attribute, 4> attrListElems;
421  while (wordIndex < words.size()) {
422  attrListElems.push_back(opBuilder.getI32IntegerAttr(words[wordIndex++]));
423  }
424  auto values = opBuilder.getArrayAttr(attrListElems);
425  spirv::ExecutionModeOp::create(
426  opBuilder, unknownLoc,
427  SymbolRefAttr::get(opBuilder.getContext(), fn.getName()), execMode,
428  values);
429  return success();
430 }
431 
432 template <>
433 LogicalResult
434 Deserializer::processOp<spirv::FunctionCallOp>(ArrayRef<uint32_t> operands) {
435  if (operands.size() < 3) {
436  return emitError(unknownLoc,
437  "OpFunctionCall must have at least 3 operands");
438  }
439 
440  Type resultType = getType(operands[0]);
441  if (!resultType) {
442  return emitError(unknownLoc, "undefined result type from <id> ")
443  << operands[0];
444  }
445 
446  // Use null type to mean no result type.
447  if (isVoidType(resultType))
448  resultType = nullptr;
449 
450  auto resultID = operands[1];
451  auto functionID = operands[2];
452 
453  auto functionName = getFunctionSymbol(functionID);
454 
455  SmallVector<Value, 4> arguments;
456  for (auto operand : llvm::drop_begin(operands, 3)) {
457  auto value = getValue(operand);
458  if (!value) {
459  return emitError(unknownLoc, "unknown <id> ")
460  << operand << " used by OpFunctionCall";
461  }
462  arguments.push_back(value);
463  }
464 
465  auto opFunctionCall = spirv::FunctionCallOp::create(
466  opBuilder, unknownLoc, resultType,
467  SymbolRefAttr::get(opBuilder.getContext(), functionName), arguments);
468 
469  if (resultType)
470  valueMap[resultID] = opFunctionCall.getResult(0);
471  return success();
472 }
473 
474 template <>
475 LogicalResult
476 Deserializer::processOp<spirv::CopyMemoryOp>(ArrayRef<uint32_t> words) {
477  SmallVector<Type, 1> resultTypes;
478  size_t wordIndex = 0;
479  SmallVector<Value, 4> operands;
481 
482  if (wordIndex < words.size()) {
483  auto arg = getValue(words[wordIndex]);
484 
485  if (!arg) {
486  return emitError(unknownLoc, "unknown result <id> : ")
487  << words[wordIndex];
488  }
489 
490  operands.push_back(arg);
491  wordIndex++;
492  }
493 
494  if (wordIndex < words.size()) {
495  auto arg = getValue(words[wordIndex]);
496 
497  if (!arg) {
498  return emitError(unknownLoc, "unknown result <id> : ")
499  << words[wordIndex];
500  }
501 
502  operands.push_back(arg);
503  wordIndex++;
504  }
505 
506  bool isAlignedAttr = false;
507 
508  if (wordIndex < words.size()) {
509  auto attrValue = words[wordIndex++];
510  auto attr = opBuilder.getAttr<spirv::MemoryAccessAttr>(
511  static_cast<spirv::MemoryAccess>(attrValue));
512  attributes.push_back(
513  opBuilder.getNamedAttr(attributeName<MemoryAccess>(), attr));
514  isAlignedAttr = (attrValue == 2);
515  }
516 
517  if (isAlignedAttr && wordIndex < words.size()) {
518  attributes.push_back(opBuilder.getNamedAttr(
519  "alignment", opBuilder.getI32IntegerAttr(words[wordIndex++])));
520  }
521 
522  if (wordIndex < words.size()) {
523  auto attrValue = words[wordIndex++];
524  auto attr = opBuilder.getAttr<spirv::MemoryAccessAttr>(
525  static_cast<spirv::MemoryAccess>(attrValue));
526  attributes.push_back(opBuilder.getNamedAttr("source_memory_access", attr));
527  }
528 
529  if (wordIndex < words.size()) {
530  attributes.push_back(opBuilder.getNamedAttr(
531  "source_alignment", opBuilder.getI32IntegerAttr(words[wordIndex++])));
532  }
533 
534  if (wordIndex != words.size()) {
535  return emitError(unknownLoc,
536  "found more operands than expected when deserializing "
537  "spirv::CopyMemoryOp, only ")
538  << wordIndex << " of " << words.size() << " processed";
539  }
540 
541  Location loc = createFileLineColLoc(opBuilder);
542  spirv::CopyMemoryOp::create(opBuilder, loc, resultTypes, operands,
543  attributes);
544 
545  return success();
546 }
547 
548 template <>
549 LogicalResult Deserializer::processOp<spirv::GenericCastToPtrExplicitOp>(
550  ArrayRef<uint32_t> words) {
551  if (words.size() != 4) {
552  return emitError(unknownLoc,
553  "expected 4 words in GenericCastToPtrExplicitOp"
554  " but got : ")
555  << words.size();
556  }
557  SmallVector<Type, 1> resultTypes;
558  SmallVector<Value, 4> operands;
559  uint32_t valueID = 0;
560  auto type = getType(words[0]);
561 
562  if (!type)
563  return emitError(unknownLoc, "unknown type result <id> : ") << words[0];
564  resultTypes.push_back(type);
565 
566  valueID = words[1];
567 
568  auto arg = getValue(words[2]);
569  if (!arg)
570  return emitError(unknownLoc, "unknown result <id> : ") << words[2];
571  operands.push_back(arg);
572 
573  Location loc = createFileLineColLoc(opBuilder);
574  Operation *op = spirv::GenericCastToPtrExplicitOp::create(
575  opBuilder, loc, resultTypes, operands);
576  valueMap[valueID] = op->getResult(0);
577  return success();
578 }
579 
580 // Pull in auto-generated Deserializer::dispatchToAutogenDeserialization() and
581 // various Deserializer::processOp<...>() specializations.
582 #define GET_DESERIALIZATION_FNS
583 #include "mlir/Dialect/SPIRV/IR/SPIRVSerialization.inc"
584 
585 } // namespace spirv
586 } // namespace mlir
static spirv::Opcode extractOpcode(uint32_t word)
Extracts the opcode from the given first word of a SPIR-V instruction.
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition: Location.h:76
This class provides the API for ops that are known to be terminators.
Definition: OpDefinition.h:773
Operation is the basic unit of execution within MLIR.
Definition: Operation.h:88
bool hasTrait()
Returns true if the operation was registered with a particular trait, e.g.
Definition: Operation.h:749
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition: Operation.h:407
static Operation * create(Location location, OperationName name, TypeRange resultTypes, ValueRange operands, NamedAttrList &&attributes, OpaqueProperties properties, BlockRange successors, unsigned numRegions)
Create a new Operation with the specific fields.
Definition: Operation.cpp:66
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition: Types.h:74
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition: Value.h:96
StringRef decodeStringLiteral(ArrayRef< uint32_t > words, unsigned &wordIndex)
Decodes a string literal in words starting at wordIndex.
Include the generated interface declarations.
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition: Utils.cpp:304
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.
auto get(MLIRContext *context, Ts &&...params)
Helper method that injects context only if needed, this helps unify some of the attribute constructio...
This represents an operation in an abstracted form, suitable for use with the builder APIs.