MLIR-AIE
TranslateAIEVecToCpp.cpp
Go to the documentation of this file.
1//===- TranslateAIEVecToCpp.cpp - AIE vector dialect to C++ -----*- C++ -*-===//
2//
3// This file is licensed 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// (c) Copyright 2022 Xilinx Inc.
8// (c) Copyright 2023 Advanced Micro Devices, Inc.
9//
10//===----------------------------------------------------------------------===//
11// This file defines helpers to emit C++ code for AIE vector dialect.
12//===----------------------------------------------------------------------===//
13
15
19
20#include "mlir/Dialect/Arith/IR/Arith.h"
21#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
22#include "mlir/Dialect/EmitC/IR/EmitC.h"
23#include "mlir/Dialect/Func/IR/FuncOps.h"
24#include "mlir/Dialect/Index/IR/IndexOps.h"
25#include "mlir/Dialect/MemRef/IR/MemRef.h"
26#include "mlir/Dialect/SCF/IR/SCF.h"
27#include "mlir/Dialect/Vector/IR/VectorOps.h"
28#include "mlir/IR/BuiltinOps.h"
29#include "mlir/IR/BuiltinTypes.h"
30#include "mlir/IR/Operation.h"
31#include "mlir/Support/IndentedOstream.h"
32
33#include "llvm/ADT/ScopedHashTable.h"
34#include "llvm/ADT/SmallSet.h"
35#include "llvm/ADT/StringRef.h"
36#include "llvm/ADT/TypeSwitch.h"
37#include "llvm/Support/CommandLine.h"
38#include "llvm/Support/Debug.h"
39#include "llvm/Support/FormatVariadic.h"
40#include "llvm/Support/MathExtras.h"
41
42#include <limits>
43#include <numeric>
44#include <optional>
45#include <sstream>
46#include <stack>
47
48#define DEBUG_TYPE "aievec-to-cpp"
49
50using namespace mlir;
51using namespace xilinx;
52using namespace xilinx::aievec;
53using llvm::formatv;
54
55/// Convenience functions to produce interleaved output with functions returning
56/// a LogicalResult. This is different than those in STLExtras as functions used
57/// on each element doesn't return a string.
58template <typename ForwardIterator, typename UnaryFunctor,
59 typename NullaryFunctor>
60LogicalResult interleaveWithError(ForwardIterator begin, ForwardIterator end,
61 UnaryFunctor eachFn,
62 NullaryFunctor betweenFn) {
63 if (begin == end)
64 return success();
65 if (failed(eachFn(*begin)))
66 return failure();
67 ++begin;
68 for (; begin != end; ++begin) {
69 betweenFn();
70 if (failed(eachFn(*begin)))
71 return failure();
72 }
73 return success();
74}
75
76template <typename Container, typename UnaryFunctor, typename NullaryFunctor>
77LogicalResult interleaveWithError(const Container &c, UnaryFunctor eachFn,
78 NullaryFunctor betweenFn) {
79 return interleaveWithError(c.begin(), c.end(), eachFn, betweenFn);
80}
81
82template <typename Container, typename UnaryFunctor>
83LogicalResult interleaveCommaWithError(const Container &c, raw_ostream &os,
84 UnaryFunctor eachFn) {
85 return interleaveWithError(c.begin(), c.end(), eachFn, [&] { os << ", "; });
86}
87
88namespace {
89/// Emitter that uses dialect specific emitters to emit C++ code.
90struct CppEmitter {
91 explicit CppEmitter(raw_ostream &os, bool declareVariablesAtTop, bool aie2);
92
93 /// Emits attribute or returns failure.
94 LogicalResult emitAttribute(Location loc, Attribute attr);
95
96 /// Emits operation 'op' with/without training semicolon or returns failure.
97 LogicalResult emitOperation(Operation &op, bool trailingSemicolon);
98
99 /// Generate the C++ type name for a given MLIR type. Name generation can
100 /// fail, returning a value-less optional string. stdintType is true when the
101 /// type is from stdint.h, and isAcc is true if we want to generate a name
102 /// for a vector type that should be stored in an accumulator.
103 std::optional<std::string> genCppTypeName(Type type, bool stdintType = true,
104 bool isAcc = false);
105
106 /// Emits type 'type' or returns failure. stdintType is true when the
107 /// type is from stdint.h
108 LogicalResult emitType(Location loc, Type type, bool stdintType = true,
109 bool isAcc = false);
110
111 /// Emits array of types as a std::tuple of the emitted types.
112 /// - emits void for an empty array;
113 /// - emits the type of the only element for arrays of size one;
114 /// - emits a std::tuple otherwise;
115 LogicalResult emitTypes(Location loc, ArrayRef<Type> types);
116
117 /// Emits array of types as a std::tuple of the emitted types independently of
118 /// the array size.
119 LogicalResult emitTupleType(Location loc, ArrayRef<Type> types);
120
121 /// Emits an assignment for a variable which has been declared previously.
122 LogicalResult emitVariableAssignment(OpResult result);
123
124 /// Emits a variable declaration for a result of an operation.
125 LogicalResult emitVariableDeclaration(OpResult result, bool trailingSemicolon,
126 bool isAcc = false);
127
128 /// Emits the variable declaration and assignment prefix for 'op'.
129 /// - emits separate variable followed by std::tie for multi-valued operation;
130 /// - emits single type followed by variable for single result;
131 /// - emits nothing if no value produced by op;
132 /// Emits final '=' operator where a type is produced. Returns failure if
133 /// any result type could not be converted.
134 LogicalResult emitAssignPrefix(Operation &op, bool isAcc = false);
135
136 /// Emits a label for the block.
137 LogicalResult emitLabel(Block &block);
138
139 /// Emits the operands and atttributes of the operation. All operands are
140 /// emitted first and then all attributes in alphabetical order.
141 LogicalResult emitOperandsAndAttributes(Operation &op,
142 ArrayRef<StringRef> exclude = {});
143
144 /// Emits the operands of the operation. All operands are emitted in order.
145 LogicalResult emitOperands(Operation &op);
146
147 /// Return the existing or a new name for a Value.
148 StringRef getOrCreateName(Value val, std::string prefix = "v");
149
150 /// Set the name of the value to an existing name
151 void setName(Value val, StringRef name);
152
153 /// Return a new name that is not associated with any value
154 std::string getNewName(std::string prefix = "v");
155
156 // Set the dim size at position index of the memref to the parameter
157 void setMemRefDimParam(Value memref, unsigned index,
158 const std::string &parameter);
159
160 // For the dynamic shaped memref, return the parametric size at index
161 StringRef getMemRefDimParam(Value memref, unsigned index);
162
163 // Return true if the specified dim of memref is parametric
164 bool isMemRefDimParam(Value memref, unsigned index);
165
166 /// Return the existing or a new label of a Block.
167 StringRef getOrCreateName(Block &block, std::string prefix = "label");
168
169 /// Whether to map an mlir integer to a unsigned integer in C++.
170 bool shouldMapToUnsigned(IntegerType::SignednessSemantics val);
171
172 /// RAII helper function to manage entering/exiting C++ scopes.
173 struct Scope {
174 Scope(CppEmitter &emitter)
175 : valueMapperScope(emitter.valueMapper),
176 blockMapperScope(emitter.blockMapper), emitter(emitter) {
177 emitter.valueInScopeCount.push(emitter.valueInScopeCount.top());
178 emitter.labelInScopeCount.push(emitter.labelInScopeCount.top());
179 }
180 ~Scope() {
181 emitter.valueInScopeCount.pop();
182 emitter.labelInScopeCount.pop();
183 }
184
185 private:
186 llvm::ScopedHashTableScope<Value, std::string> valueMapperScope;
187 llvm::ScopedHashTableScope<Block *, std::string> blockMapperScope;
188 CppEmitter &emitter;
189 };
190
191 /// Returns wether the Value is assigned to a C++ variable in the scope.
192 bool hasValueInScope(Value val);
193
194 // Returns whether a label is assigned to the block.
195 bool hasBlockLabel(Block &block);
196
197 /// Returns the output stream.
198 raw_indented_ostream &ostream() { return os; }
199
200 /// Returns if all variables for op results and basic block arguments need to
201 /// be declared at the beginning of a function.
202 bool shouldDeclareVariablesAtTop() { return declareVariablesAtTop; }
203
204 bool aie2() { return aie2_; }
205
206private:
207 using ValueMapper = llvm::ScopedHashTable<Value, std::string>;
208 using BlockMapper = llvm::ScopedHashTable<Block *, std::string>;
209
210 /// Output stream to emit to.
211 raw_indented_ostream os;
212
213 /// Boolean to enforce that all variables for op results and block
214 /// arguments are declared at the beginning of the function. This also
215 /// includes results from ops located in nested regions.
216 bool declareVariablesAtTop;
217
218 /// Map from value to name of C++ variable that contain the name.
219 ValueMapper valueMapper;
220
221 /// Map from block to name of C++ label.
222 BlockMapper blockMapper;
223
224 /// Map from a dynamic memref index to the parameter
225 DenseMap<std::pair<Value, unsigned>, std::string> paramIndexMapper;
226
227 /// The number of values in the current scope. This is used to declare the
228 /// names of values in a scope.
229 std::stack<int64_t> valueInScopeCount;
230 std::stack<int64_t> labelInScopeCount;
231
232 llvm::SmallSet<StringRef, 16> includeNames;
233
234 bool aie2_;
235};
236} // namespace
237
238//===----------------------------------------------------------------------===//
239// Helper Routines
240//===----------------------------------------------------------------------===//
241
242// Return true if this op should be skipped in codegen. Ops like memref::DimOp,
243// aievec::srs and aievec::ups for fp operands fall in this category.
244// Certain ops should only be emitted if they are used in the computation of an
245// op that is not skipped. An example of such an op is the index defining op for
246// memref::DimOp. Since DimOp is skipped, we don't need to generate the index
247// defining op. If checkStrongLiveness is true, then also skip such ops.
248static bool skippedOp(Operation *op, CppEmitter &emitter,
249 bool checkStrongLiveness = true) {
250 // Ops that must be skipped:
251 bool skip =
252 TypeSwitch<Operation *, bool>(op)
253 // skip op 1 : all dim op, assume_alignment op, alloc and dealloc ops
254 .Case<memref::DimOp, memref::AssumeAlignmentOp, memref::AllocOp,
255 memref::DeallocOp>([](auto op) { return true; })
256 // skip op 2 : some aievec::srs for float types
257 .Case<aievec::SRSOp>([&](auto srsOp) {
258 // Get the datatype of the source accumulator and result vector
259 auto accType = cast<VectorType>(srsOp.getSource().getType());
260 Type eltType = accType.getElementType();
261 // If the underlying element types are float, then we do not really
262 // need an srs op if source of srsOp has only one use.
263 Value source = srsOp.getSource();
264 if (!emitter.aie2() && llvm::isa<FloatType>(eltType) &&
265 source.getDefiningOp()->hasOneUse()) {
266 StringRef srcName = emitter.getOrCreateName(source);
267 emitter.setName(srsOp->getResult(0), srcName);
268 return true;
269 }
270 return false;
271 })
272 // skip op 3 : some aievec::ups for float ops
273 .Case<aievec::UPSOp>([&](auto upsOp) {
274 // Get the datatype of the source vector and result accumulator
275 auto accType = cast<VectorType>(upsOp.getResult().getType());
276 Type eltType = accType.getElementType();
277 // If the underlying element types are float, then we do not really
278 // need a ups op if the source accumulator has only one use.
279 Value source = upsOp.getSource();
280 if (!emitter.aie2() && llvm::isa<FloatType>(eltType) &&
281 source.getDefiningOp()->hasOneUse()) {
282 StringRef srcName = emitter.getOrCreateName(source);
283 emitter.setName(upsOp->getResult(0), srcName);
284 return true;
285 }
286 return false;
287 })
288 // skip op 4 : some aievec::cast, when it represents a move to/from
289 // accumulator, but the type is necessarily an
290 // accumulator.
291 .Case<aievec::CastOp>([&](auto castOp) {
292 Value source = castOp.getSource();
293 auto srcVTy = cast<VectorType>(source.getType());
294 auto resVTy = cast<VectorType>(castOp.getResult().getType());
295 if (srcVTy.getElementType() == resVTy.getElementType()) {
296 auto iElTy = dyn_cast<IntegerType>(srcVTy.getElementType());
297 if (iElTy && iElTy.getWidth() == 64) {
298 StringRef srcName = emitter.getOrCreateName(source);
299 emitter.setName(castOp->getResult(0), srcName);
300 return true;
301 }
302 }
303 return false;
304 })
305 // skip op 5: ignore casts between index and integer types.
306 .Case<arith::IndexCastOp, arith::IndexCastUIOp, index::CastSOp,
307 index::CastUOp>([&](auto idxCastOp) {
308 Value source = idxCastOp->getOperand(0);
309 StringRef srcName = emitter.getOrCreateName(source);
310 emitter.setName(idxCastOp->getResult(0), srcName);
311 return true;
312 })
313 // skip op 6: ignore vector shape cast operations.
314 .Case<vector::ShapeCastOp>([&](auto castOp) {
315 Value source = castOp.getSource();
316 StringRef srcName = emitter.getOrCreateName(source);
317 emitter.setName(castOp.getResult(), srcName);
318 return true;
319 })
320 // skip op 7: ignore unrealized conversion casts. These will come
321 // from non-C types interfacing with emitc for math ops.
322 .Case<UnrealizedConversionCastOp>([&](auto uccOp) {
323 auto inputs = uccOp.getInputs();
324 auto outputs = uccOp.getOutputs();
325 if (inputs.size() > 1 || inputs.size() > 1)
326 return false;
327 StringRef inputName = emitter.getOrCreateName(inputs[0]);
328 emitter.setName(outputs[0], inputName);
329 return true;
330 })
331 .Default([&](Operation *) { return false; });
332
333 // Ops whose strong liveness must be determined
334 checkStrongLiveness &= isa<arith::ConstantOp>(op);
335
336 // If we already know that this op must be skipped, or that don't need to
337 // check strong liveness of the op, we are done
338 if (skip || !checkStrongLiveness)
339 return skip;
340
341 // We need to check if this op is strongly live. i.e., its result is used in
342 // an op that is not skipped. We iterate over all its immediate users, and
343 // return false if any of them is not skipped in codegen.
344 for (auto user : op->getUsers()) {
345 if (!skippedOp(user, emitter, false))
346 return false;
347 }
348 return true;
349}
350
351// Print the memref dims, if the memref has dynamic shape
352static LogicalResult parseMemRefDynamicDims(CppEmitter &emitter,
353 func::FuncOp func) {
354 // Step1: Walk over all the operations that are memref dimOp
355 func.walk([&](Operation *Op) {
356 if (auto op = dyn_cast<memref::DimOp>(Op)) {
357 // Extract the source memref, result, and index
358 Value source = op.getSource();
359 Value result = op.getResult();
360 auto indexOp = dyn_cast<arith::ConstantOp>(op.getIndex().getDefiningOp());
361 assert(indexOp && "Failed to get the index value of dimOp");
362 // Get the constant index value
363 APInt idxVal = llvm::cast<IntegerAttr>(indexOp.getValue()).getValue();
364 unsigned index = idxVal.getZExtValue();
365 // Assign a printable name to the result
366 StringRef name = emitter.getOrCreateName(result, "m");
367 emitter.setMemRefDimParam(source, index, name.str());
368 }
369 });
370
371 // Step2: Iterate over all the block arguments, and make sure that the memref
372 // args have a parameter associated with the dynamic sized dimension
373 for (BlockArgument arg : func.getArguments()) {
374 auto argType = llvm::dyn_cast<MemRefType>(arg.getType());
375 if (!argType)
376 continue;
377 for (unsigned dim = 0; dim < argType.getRank(); ++dim) {
378 if (argType.isDynamicDim(dim)) {
379 // If the dynamic dim size is not already parametrized, assign it one
380 if (!emitter.isMemRefDimParam(arg, dim)) {
381 std::string name = emitter.getNewName("m");
382 emitter.setMemRefDimParam(arg, dim, name);
383 }
384 }
385 }
386 }
387 return success();
388}
389
390// Print the memref dims, if the memref has dynamic shape
391static LogicalResult printMemRefDims(CppEmitter &emitter, BlockArgument arg) {
392 raw_indented_ostream &os = emitter.ostream();
393 if (auto argType = llvm::dyn_cast<MemRefType>(arg.getType())) {
394 for (unsigned dim = 0; dim < argType.getRank(); ++dim) {
395 if (argType.isDynamicDim(dim)) {
396 StringRef param = emitter.getMemRefDimParam(arg, dim);
397 os << ", size_t " << param;
398 }
399 }
400 }
401 return success();
402}
403
404// Get the linearized access for the source memref
405static LogicalResult createLinearizedAccess(CppEmitter &emitter, Value source,
406 SmallVector<Value, 4> indices,
407 std::string &access) {
408 auto memRefType = llvm::dyn_cast<MemRefType>(source.getType());
409 assert(memRefType &&
410 "cannot creating linearized expression for non-memref type");
411 ArrayRef<int64_t> stride = memRefType.getShape();
412
413 // The stride and indices size must match
414 if (stride.size() != indices.size() ||
415 static_cast<int64_t>(stride.size()) != memRefType.getRank())
416 return failure();
417
418 // A stride contains two parts:
419 int64_t numPart = 1; // for static shaped dims
420 std::string paramPart; // for dynamic shaped dims
421
422 SmallVector<std::string, 4> accessVec;
423 for (int dim = memRefType.getRank() - 1; dim >= 0; --dim) {
424 // All the indices in the access expression must already be emitted
425 if (!emitter.hasValueInScope(indices[dim]))
426 return failure();
427
428 // Form the access string for this dimension
429 std::string cur;
430 if (!paramPart.empty())
431 cur = paramPart + "*";
432 if (numPart > 1)
433 cur += std::to_string(numPart) + "*";
434 cur += emitter.getOrCreateName(indices[dim]);
435 accessVec.push_back(cur);
436
437 // Now update the numPart and paramPart to form the stride for the next
438 // dimension
439 if (memRefType.isDynamicDim(dim)) {
440 StringRef param = emitter.getMemRefDimParam(source, dim);
441 paramPart = param.str() + (paramPart.empty() ? "" : "*" + paramPart);
442 } else
443 numPart *= stride[dim];
444 }
445 // All the strides are in accessVec. Compose them
446 while (!accessVec.empty()) {
447 access += (access.empty() ? "" : "+") + accessVec.back();
448 accessVec.pop_back();
449 }
450 // If the access is empty, make '0' as default access
451 if (access.empty())
452 access = "0";
453
454 return success();
455}
456
457// Return true if the array accessed by this value is readonly
458static bool isReadOnly(Value read) {
459 return std::none_of(
460 read.getUsers().begin(), read.getUsers().end(),
461 [](auto *user) { return isa<vector::TransferWriteOp>(user); });
462}
463
464//===----------------------------------------------------------------------===//
465// Print non-AIE dialect ops
466//===----------------------------------------------------------------------===//
467
468// Get the loop trip count of the for operator
469static std::pair<bool, int64_t> getTripCount(scf::ForOp forOp) {
470 // If the upper and lower bounds are constant values, return the difference.
471 auto lb = forOp.getLowerBound().getDefiningOp<arith::ConstantOp>();
472 if (auto ub = forOp.getUpperBound().getDefiningOp<arith::ConstantOp>();
473 lb && ub) {
474 APInt ubValue = llvm::cast<IntegerAttr>(ub.getValue()).getValue();
475 APInt lbValue = llvm::cast<IntegerAttr>(lb.getValue()).getValue();
476 return std::make_pair(true,
477 ubValue.getSExtValue() - lbValue.getSExtValue());
478 }
479 return std::make_pair(false, 0);
480}
481
482// Get the loop step size of the for operator
483static std::pair<bool, int64_t> getStep(scf::ForOp forOp) {
484 if (auto step = forOp.getStep().getDefiningOp<arith::ConstantOp>()) {
485 APInt stepValue = llvm::cast<IntegerAttr>(step.getValue()).getValue();
486 return std::make_pair(true, stepValue.getSExtValue());
487 }
488 return std::make_pair(false, 0);
489}
490
491// Return the operator string of the Arith dialect binary operator
492template <typename T>
493static StringRef getOperator(T binOp) {
494 if (isa<arith::AddIOp>(binOp) || isa<arith::AddFOp>(binOp))
495 return " + ";
496 if (isa<arith::MulIOp>(binOp) || isa<arith::MulFOp>(binOp))
497 return " * ";
498 if (isa<arith::SubIOp>(binOp) || isa<arith::SubFOp>(binOp))
499 return " - ";
500 if (isa<arith::DivFOp>(binOp) || isa<arith::DivUIOp>(binOp) ||
501 isa<arith::DivSIOp>(binOp))
502 return " / ";
503 if (isa<arith::RemSIOp>(binOp))
504 return " % ";
505 if (isa<arith::CmpIOp>(binOp)) {
506 auto cmpOp = cast<arith::CmpIOp>(binOp);
507 switch (cmpOp.getPredicate()) {
508 case arith::CmpIPredicate::eq:
509 return " == ";
510 case arith::CmpIPredicate::ne:
511 return " != ";
512 case arith::CmpIPredicate::sge:
513 case arith::CmpIPredicate::uge:
514 return " >= ";
515 case arith::CmpIPredicate::sgt:
516 case arith::CmpIPredicate::ugt:
517 return " > ";
518 case arith::CmpIPredicate::sle:
519 case arith::CmpIPredicate::ule:
520 return " <= ";
521 case arith::CmpIPredicate::slt:
522 case arith::CmpIPredicate::ult:
523 return " < ";
524 }
525 }
526 llvm_unreachable("Cannot print the operation of binary operator");
527}
528
529// Print the Arith dialect binary operation
530template <typename T>
531static LogicalResult printOperation(CppEmitter &emitter, T binOp) {
532 if (failed(emitter.emitAssignPrefix(*binOp)))
533 return failure();
534 raw_indented_ostream &os = emitter.ostream();
535 auto lhs = binOp.getLhs();
536 if (!emitter.hasValueInScope(lhs))
537 return failure();
538 os << emitter.getOrCreateName(lhs);
539 os << getOperator(binOp);
540 auto rhs = binOp.getRhs();
541 if (!emitter.hasValueInScope(rhs))
542 return failure();
543 os << emitter.getOrCreateName(rhs);
544
545 return success();
546}
547
548// Print the ternary operation
549static LogicalResult printOperation(CppEmitter &emitter,
550 arith::SelectOp selectOp) {
551 if (failed(emitter.emitAssignPrefix(*selectOp)))
552 return failure();
553
554 auto cond = selectOp.getCondition();
555 if (!emitter.hasValueInScope(cond))
556 return failure();
557 auto tVal = selectOp.getTrueValue();
558 if (!emitter.hasValueInScope(tVal))
559 return failure();
560 auto fVal = selectOp.getFalseValue();
561 if (!emitter.hasValueInScope(fVal))
562 return failure();
563
564 raw_indented_ostream &os = emitter.ostream();
565 os << emitter.getOrCreateName(cond) << " ? " << emitter.getOrCreateName(tVal)
566 << " : " << emitter.getOrCreateName(fVal);
567
568 return success();
569}
570
571//===----------------------------------------------------------------------===//
572// Print AIE dialect ops
573//===----------------------------------------------------------------------===//
574
575// Print the AIE dialect UPD op
576static LogicalResult printOperation(CppEmitter &emitter, aievec::UPDOp updOp) {
577 Value source = updOp.getSource();
578 // If the source is not already emitted, error out
579 if (!emitter.hasValueInScope(source))
580 return failure();
581
582 // Construct the access expression using memref shape and indices
583 auto indices = updOp.getIndices();
584 std::string access;
585 if (failed(createLinearizedAccess(emitter, source, indices, access)))
586 return failure();
587
588 raw_indented_ostream &os = emitter.ostream();
589 Value result = updOp.getResult();
590 auto resultType = llvm::cast<VectorType>(result.getType());
591 int32_t vecSizeInBits = getVectorSizeInBits(resultType);
592 int32_t elementSizeInBits = getElementSizeInBits(resultType);
593
594 // If the UPD op had an offset, add it to the access expr
595 if (updOp.getOffset() != 0) {
596 if (std::abs(updOp.getOffset()) % elementSizeInBits)
597 return failure();
598 int32_t updOffset = updOp.getOffset() / elementSizeInBits;
599 access += updOffset > 0 ? " + " : " - ";
600 access += std::to_string(std::abs(updOffset));
601 }
602
603 // If the vector size to be loaded is less than or equal to 256, we
604 // can just do a direct memory copy. If the translation is for AIE2,
605 // this number should be doubled
606 if (vecSizeInBits <= (emitter.aie2() ? 1024 : 256)) {
607 // Print the lhs
608 if (failed(emitter.emitAssignPrefix(*updOp)))
609 return failure();
610 os << "*(";
611 if (failed(emitter.emitType(updOp->getLoc(), resultType)))
612 return failure();
613 os << " *)";
614 os << "(";
615 os << emitter.getOrCreateName(source);
616 if (!access.empty())
617 os << " + " << access;
618 os << ")";
619 } else {
620 Value vector = updOp.getVector();
621 // If this is the first upd op (between idx=0 and idx=1), then generate
622 // declaration
623 if (!vector) {
624 if (!emitter.shouldDeclareVariablesAtTop()) {
625 if (failed(emitter.emitVariableDeclaration(updOp->getResult(0), true)))
626 return failure();
627 }
628 } else {
629 if (!emitter.hasValueInScope(vector))
630 return failure();
631 emitter.setName(updOp->getResult(0), emitter.getOrCreateName(vector));
632 }
633
634 // The granularity of upd is 128/256/512 for 256/512/1024 bit values
635 int32_t granularity = vecSizeInBits == 256 ? 128
636 : vecSizeInBits == 512 ? 256
637 : 512;
638 // Create a vector type with number of lanes halved of the result
639 unsigned lanes = getVectorLaneSize(resultType);
640 assert(lanes % 2 == 0 &&
641 "The number of vector lanes of UPD result is not even");
642 SmallVector<int64_t, 4> updShape = {lanes / 2};
643 VectorType updType = VectorType::get(updShape, resultType.getElementType());
644
645 if (!emitter.hasValueInScope(result))
646 return failure();
647 // If the source array of upd is read-only, load from restrict pointer
648 bool readOnly = isReadOnly(source);
649 std::string restrictPrefix =
650 readOnly ? "r_" + emitter.getOrCreateName(result).str() + "_" : "";
651 // Create a restrict pointer
652 if (readOnly && !vector) {
653 if (failed(emitter.emitType(updOp->getLoc(), source.getType())))
654 return failure();
655 os << " " << restrictPrefix << emitter.getOrCreateName(source);
656 os << " = ";
657 os << emitter.getOrCreateName(source);
658 os << ";\n";
659 }
660 os << emitter.getOrCreateName(result);
661 os << " = ";
662 os << (granularity == 128 ? "upd_v"
663 : granularity == 256 ? "upd_w"
664 : "upd_x");
665 os << "(";
666 os << emitter.getOrCreateName(result);
667 os << ", ";
668 os << std::to_string(updOp.getIndex());
669 os << ", ";
670 os << "*(";
671 if (failed(emitter.emitType(updOp->getLoc(), updType)))
672 return failure();
673 os << " *)";
674 os << "(";
675 os << restrictPrefix << emitter.getOrCreateName(source);
676 if (!access.empty())
677 os << " + " << access;
678 os << ")";
679 os << ")";
680 }
681
682 return success();
683}
684
685// Print the UPS intrinsic
686static LogicalResult printOperation(CppEmitter &emitter, aievec::UPSOp upsOp) {
687 Value source = upsOp.getSource();
688 int32_t shift = upsOp.getShift();
689
690 raw_indented_ostream &os = emitter.ostream();
691
692 // Generate the initialization for the accumulator
693 if (failed(emitter.emitAssignPrefix(*upsOp, /*isAcc=*/true)))
694 return failure();
695
696 // The source vector should have already been emitted
697 if (!emitter.hasValueInScope(source))
698 return failure();
699
700 auto accType = llvm::cast<VectorType>(upsOp.getResult().getType());
701 unsigned lanes = getVectorLaneSize(accType);
702 Type eltType = accType.getElementType();
703
704 // If the underlying element types are float, then we do not really need a
705 // ups op. We can simply generate an assignment
706 if (!emitter.aie2() && llvm::isa<FloatType>(eltType)) {
707 os << emitter.getOrCreateName(source);
708 return success();
709 }
710
711 // Determine if it is lups or ups based on accumulator type
712 auto iType = llvm::dyn_cast<IntegerType>(eltType);
713 auto fType = llvm::dyn_cast<FloatType>(eltType);
714 if (iType) {
715 if (iType.getWidth() == 80)
716 os << "l";
717 }
718
719 if (iType && emitter.aie2()) {
720 os << "ups_to_v" << lanes << "acc" << iType.getWidth();
721 } else if (fType && emitter.aie2()) {
722 os << "ups_to_v16accfloat";
723 } else {
724 os << "ups";
725 }
726
727 os << "(";
728 os << emitter.getOrCreateName(source);
729 if (!(fType && emitter.aie2())) {
730 os << ", ";
731 os << std::to_string(shift);
732 }
733 os << ")";
734
735 return success();
736}
737
738// Generate the cast intrinsic for AIE2
739static LogicalResult printOperation(CppEmitter &emitter,
740 aievec::CastOp castOp) {
741 if (!emitter.aie2()) {
742 return failure();
743 }
744
745 // The source should have already been emitted
746 Value source = castOp.getSource();
747 if (!emitter.hasValueInScope(source))
748 return failure();
749
750 bool isResAcc = castOp.getIsResAcc();
751
752 // Generate the initialization for the vector
753 if (failed(emitter.emitAssignPrefix(*castOp, /*isAcc=*/isResAcc)))
754 return failure();
755
756 // Get the datatype of the source and result vector
757 auto resType = llvm::cast<VectorType>(castOp->getResult(0).getType());
758 Type eltType = resType.getElementType();
759 unsigned lanes = getVectorLaneSize(resType);
760
761 raw_indented_ostream &os = emitter.ostream();
762
763 unsigned width;
764 if (isResAcc) {
765 if (llvm::isa<FloatType>(eltType))
766 os << "v" << lanes << "accfloat";
767 else {
768 width = getElementSizeInBits(resType);
769 os << "v" << lanes << "acc" << width;
770 }
771 } else if (llvm::isa<FloatType>(eltType)) {
772 width = llvm::cast<FloatType>(eltType).getWidth();
773 os << "v" << lanes;
774 if (width == 16)
775 os << "bfloat16";
776 else
777 os << "float";
778 } else {
779 width = getElementSizeInBits(resType);
780 os << "v" << lanes << "int" << width;
781 }
782 os << "(";
783 os << emitter.getOrCreateName(source);
784 os << ")";
785 return success();
786}
787
788// Generate the unpack intrinsic for AIE2
789static LogicalResult printOperation(CppEmitter &emitter,
790 aievec::UnpackOp unpackOp) {
791
792 // The source should have already been emitted
793 Value source = unpackOp.getSource();
794 if (!emitter.hasValueInScope(source))
795 return failure();
796
797 // Generate the initialization for the vector
798 if (failed(emitter.emitAssignPrefix(*unpackOp, /*isAcc=*/false)))
799 return failure();
800
801 raw_indented_ostream &os = emitter.ostream();
802
803 os << "unpack(";
804 os << emitter.getOrCreateName(source);
805 os << ")";
806 return success();
807}
808
809// Generate the srs intrinsic
810static LogicalResult printOperation(CppEmitter &emitter, aievec::SRSOp srsOp) {
811 Value source = srsOp.getSource();
812 Value shift = srsOp.getShift();
813
814 // Get the datatype of the source accumulator and result vector
815 auto accType = llvm::cast<VectorType>(srsOp.getSource().getType());
816 auto resType = llvm::cast<VectorType>(srsOp->getResult(0).getType());
817 Type eltType = accType.getElementType();
818 unsigned lanes = getVectorLaneSize(resType);
819
820 raw_indented_ostream &os = emitter.ostream();
821
822 // Generate the initialization for the vector
823 if (failed(emitter.emitAssignPrefix(*srsOp)))
824 return failure();
825
826 // The source accumulator should have already been emitted
827 if (!emitter.hasValueInScope(source))
828 return failure();
829
830 // If the underlying element types are float, then we do not really need an
831 // srs op. We can simply generate an assignment
832 if (llvm::isa<FloatType>(eltType)) {
833 if (emitter.aie2()) {
834 if (unsigned width = getElementSizeInBits(resType); width == 32)
835 os << "srs";
836 else if (width == 16)
837 os << "to_v16bfloat16";
838 os << "(";
839 os << emitter.getOrCreateName(source);
840 os << ")";
841 } else
842 os << emitter.getOrCreateName(source);
843
844 return success();
845 }
846
847 // Otheriwse, get the datatype width of the source accumulator and result
848 // vector
849 unsigned resultWidth = getElementSizeInBits(accType);
850 unsigned resWidth = getElementSizeInBits(resType);
851 unsigned srcWidth = 0;
852 if (auto iType = llvm::dyn_cast<IntegerType>(eltType))
853 srcWidth = iType.getWidth();
854
855 // Based on the datatypes, generate srs version
856 if ((srcWidth == 80 && resultWidth == 64) ||
857 (srcWidth == 48 && resultWidth == 32))
858 os << "l";
859 else if (srcWidth == 48 && resultWidth == 8)
860 os << "b";
861
862 if (emitter.aie2())
863 os << "srs_to_v" << std::to_string(lanes) << "int"
864 << std::to_string(resWidth);
865 else
866 os << "srs";
867
868 os << "(";
869 os << emitter.getOrCreateName(source);
870 os << ", ";
871 if (llvm::cast<IntegerType>(srsOp.getShift().getType()).getWidth() != 32)
872 os << "(int32_t)";
873 os << emitter.getOrCreateName(shift);
874 os << ")";
875
876 return success();
877}
878
879// Generate the broadcast intrinsic
880static LogicalResult printOperation(CppEmitter &emitter,
881 aievec::BroadcastOp broadcastOp) {
882 Value source = broadcastOp.getSource();
883 int8_t idx = broadcastOp.getIdx();
884
885 raw_indented_ostream &os = emitter.ostream();
886
887 // Generate the initialization for the vector
888 if (failed(emitter.emitAssignPrefix(*broadcastOp)))
889 return failure();
890
891 // The source vector should have already been emitted
892 if (!emitter.hasValueInScope(source))
893 return failure();
894
895 os << "broadcast_elem";
896 os << "(";
897 os << emitter.getOrCreateName(source);
898 os << ", ";
899 os << std::to_string(idx);
900 os << ")";
901
902 return success();
903}
904
905// Generate the broadcast_scalar intrinsic
906static LogicalResult
907printOperation(CppEmitter &emitter,
908 aievec::BroadcastScalarOp broadcastScalarOp) {
909 auto source = broadcastScalarOp.getSource();
910 auto resType =
911 llvm::cast<VectorType>(broadcastScalarOp.getResult().getType());
912 unsigned width = getElementSizeInBits(resType);
913 unsigned lanes = getVectorLaneSize(resType);
914 raw_indented_ostream &os = emitter.ostream();
915
916 // Generate the initialization for the vector
917 if (failed(emitter.emitAssignPrefix(*broadcastScalarOp)))
918 return failure();
919
920 Type eltType = resType.getElementType();
921 os << "broadcast_to_v";
922 if (llvm::isa<IntegerType>(eltType)) {
923 os << lanes << "int";
924 os << width;
925 } else if (width == 16)
926 os << lanes << "bfloat16";
927 else
928 os << lanes << "float";
929 os << "(" << emitter.getOrCreateName(source) << ")";
930
931 return success();
932}
933
934// Generate the ext intrinsic
935template <typename T>
936static LogicalResult printExtOperation(CppEmitter &emitter, T extOp) {
937 Value source = extOp.getSource();
938 int8_t index = extOp.getIndex();
939
940 raw_indented_ostream &os = emitter.ostream();
941
942 // Generate the initialization for the result
943 if (failed(emitter.emitAssignPrefix(*extOp)))
944 return failure();
945
946 if (!emitter.hasValueInScope(source))
947 return failure();
948
949 auto resType = llvm::cast<VectorType>(extOp.getResult().getType());
950 Type eltType = resType.getElementType();
951 unsigned lanes = getVectorLaneSize(resType);
952 unsigned resWidth = getElementSizeInBits(resType);
953
954 // Print the version of ext for AIE2
955 if (emitter.aie2()) {
956 os << "extract_v" << std::to_string(lanes);
957 if (llvm::isa<IntegerType>(eltType))
958 os << "int" << std::to_string(resWidth);
959 else if (resWidth == 16)
960 os << "bfloat16";
961 else
962 os << "float";
963 } else {
964 // Print the version of ext for aie1
965 int32_t vecSizeInBits = getVectorSizeInBits(resType);
966 assert(vecSizeInBits == 128 || vecSizeInBits == 256 ||
967 vecSizeInBits == 512);
968 os << (vecSizeInBits == 128 ? "ext_v"
969 : vecSizeInBits == 256 ? "ext_w"
970 : "ext_x");
971 }
972 os << "(";
973 // The source accumulator should have already been emitted
974 os << emitter.getOrCreateName(source);
975 os << ", ";
976 os << std::to_string(index);
977 os << ")";
978
979 return success();
980}
981
982// Generate the aie2 ext intrinsic
983static LogicalResult printOperation(CppEmitter &emitter, aievec::ExtOp extOp) {
984 if (!emitter.aie2())
985 return failure();
986 return printExtOperation<aievec::ExtOp>(emitter, extOp);
987}
988
989// Generate the aie1 ext intrinsic
990static LogicalResult printOperation(CppEmitter &emitter,
991 aievec::aie1::ExtOp extOp) {
992 if (emitter.aie2())
993 return failure();
994 return printExtOperation<aievec::aie1::ExtOp>(emitter, extOp);
995}
996
997// Generate the concat intrinsic
998static LogicalResult printOperation(CppEmitter &emitter,
999 aievec::ConcatOp concatOp) {
1000 SmallVector<Value> sources = concatOp.getSources();
1001
1002 raw_indented_ostream &os = emitter.ostream();
1003
1004 // Generate the initialization for the result
1005 if (failed(emitter.emitAssignPrefix(*concatOp)))
1006 return failure();
1007
1008 os << "concat";
1009 os << "(";
1010 // Print the sources sources
1011 bool first = true;
1012 for (auto source : sources) {
1013 // source should have already been emitted
1014 if (!emitter.hasValueInScope(source))
1015 return failure();
1016 if (!first)
1017 os << ", ";
1018 os << emitter.getOrCreateName(source);
1019 first = false;
1020 }
1021 os << ")";
1022
1023 return success();
1024}
1025
1026// Generate the shift intrinsic
1027static LogicalResult printOperation(CppEmitter &emitter,
1028 aievec::ShiftOp shiftOp) {
1029 Value lhs = shiftOp.getLhs();
1030 Value rhs = shiftOp.getRhs();
1031 Value shift = shiftOp.getShift();
1032 bool isAcc = shiftOp.getIsAcc();
1033
1034 raw_indented_ostream &os = emitter.ostream();
1035
1036 // Generate the initialization for the result
1037 if (failed(emitter.emitAssignPrefix(*shiftOp, isAcc)))
1038 return failure();
1039
1040 os << "shift_bytes";
1041 os << "(";
1042 // Print the lhs, rhs and shift
1043 if (!emitter.hasValueInScope(lhs) || !emitter.hasValueInScope(rhs))
1044 return failure();
1045 os << emitter.getOrCreateName(lhs);
1046 os << ", ";
1047 os << emitter.getOrCreateName(rhs);
1048 os << ", static_cast<uint32_t>(";
1049
1050 if (!emitter.hasValueInScope(shift))
1051 return failure();
1052 os << emitter.getOrCreateName(shift);
1053 os << "))";
1054
1055 return success();
1056}
1057
1058// Generate the shuffle intrinsic
1059static LogicalResult printOperation(CppEmitter &emitter,
1060 aievec::ShuffleOp shuffleOp) {
1061 Value lhs = shuffleOp.getLhs();
1062 Value rhs = shuffleOp.getRhs();
1063 aievec::ShuffleMode mode = shuffleOp.getMode();
1064
1065 raw_indented_ostream &os = emitter.ostream();
1066
1067 // Generate the initialization for the result
1068 if (failed(emitter.emitAssignPrefix(*shuffleOp)))
1069 return failure();
1070
1071 os << "shuffle";
1072 os << "(";
1073 if (!emitter.hasValueInScope(lhs))
1074 return failure();
1075 os << emitter.getOrCreateName(lhs);
1076 os << ", ";
1077 if (rhs) {
1078 if (!emitter.hasValueInScope(rhs))
1079 return failure();
1080 os << emitter.getOrCreateName(rhs);
1081 os << ", ";
1082 }
1083 os << "eShuffleMode::shuffle_T" << stringifyEnum(mode).substr(1);
1084 os << ")";
1085
1086 return success();
1087}
1088
1089// Generate the shuffle intrinsic
1090static LogicalResult printOperation(CppEmitter &emitter,
1091 aievec::LegacyShuffleOp shuffleOp) {
1092 Value source = shuffleOp.getSource();
1093 unsigned mode = shuffleOp.getMode();
1094
1095 raw_indented_ostream &os = emitter.ostream();
1096
1097 // Generate the initialization for the result
1098 if (failed(emitter.emitAssignPrefix(*shuffleOp)))
1099 return failure();
1100
1101 os << "shuffle";
1102 os << "(";
1103 // Print the source and mode
1104 // source should have already been emitted
1105 if (!emitter.hasValueInScope(source))
1106 return failure();
1107 os << emitter.getOrCreateName(source);
1108 os << ", ";
1109 os << std::to_string(mode);
1110 os << ")";
1111
1112 return success();
1113}
1114
1115// Generate the select intrinsic
1116static LogicalResult printOperation(CppEmitter &emitter,
1117 aievec::aie1::SelectOp selectOp) {
1118 Value xbuff = selectOp.getXbuff();
1119 assert(xbuff && "xbuff empty in select op");
1120
1121 raw_indented_ostream &os = emitter.ostream();
1122
1123 // Generate the initialization for the result
1124 if (failed(emitter.emitAssignPrefix(*selectOp)))
1125 return failure();
1126
1127 // Determine if we want to geneate select32, or select16, or select8
1128 auto xbuffType = llvm::cast<VectorType>(selectOp.getXbuff().getType());
1129 int32_t elementSizeInBits = getElementSizeInBits(xbuffType);
1130 assert(elementSizeInBits == 16 || elementSizeInBits == 32 ||
1131 elementSizeInBits == 64);
1132 // Print name
1133 os << (elementSizeInBits == 16 ? "select32"
1134 : elementSizeInBits == 32 ? "select16"
1135 : "select8");
1136 os << "(";
1137 // Print select bits
1138 assert(!selectOp.getSelect().empty());
1139 os << selectOp.getSelect();
1140 // xbuff should have already been emitted
1141 if (!emitter.hasValueInScope(xbuff))
1142 return failure();
1143 // Print xbuff
1144 os << ", ";
1145 os << emitter.getOrCreateName(xbuff);
1146 // Print attributes related to lower lane selection
1147 if (!selectOp.getXstart().empty())
1148 os << ", " << selectOp.getXstart();
1149 if (!selectOp.getXoffsets().empty())
1150 os << ", " << selectOp.getXoffsets();
1151 if (!selectOp.getXoffsetsHi().empty())
1152 os << ", " << selectOp.getXoffsetsHi();
1153 if (!selectOp.getXsquare().empty())
1154 os << ", " << selectOp.getXsquare();
1155 // If ybuff is not null, print it
1156 if (selectOp.getYbuff()) {
1157 Value ybuff = selectOp.getYbuff();
1158 // ybuff should have already been emitted
1159 if (!emitter.hasValueInScope(ybuff))
1160 return failure();
1161 // Print ybuff
1162 os << ", ";
1163 os << emitter.getOrCreateName(ybuff);
1164 }
1165 // Print attributes related to higher lane selection
1166 if (!selectOp.getYstart().empty())
1167 os << ", " << selectOp.getYstart();
1168 if (!selectOp.getYoffsets().empty())
1169 os << ", " << selectOp.getYoffsets();
1170 if (!selectOp.getYoffsetsHi().empty())
1171 os << ", " << selectOp.getYoffsetsHi();
1172 if (!selectOp.getYsquare().empty())
1173 os << ", " << selectOp.getYsquare();
1174 os << ")";
1175
1176 return success();
1177}
1178
1179// Generate the pack intrinsic
1180static LogicalResult printOperation(CppEmitter &emitter,
1181 aievec::PackOp packOp) {
1182 Value source = packOp.getSource();
1183
1184 raw_indented_ostream &os = emitter.ostream();
1185
1186 // Generate the initialization for the result
1187 if (failed(emitter.emitAssignPrefix(*packOp)))
1188 return failure();
1189
1190 // Determine the flavor of result
1191 auto sourceType = llvm::cast<VectorType>(packOp.getSource().getType());
1192 Type scalarType = sourceType.getElementType();
1193 os << (scalarType.isUnsignedInteger() ? "upack" : "pack");
1194 os << "(";
1195 // source should have already been emitted
1196 if (!emitter.hasValueInScope(source))
1197 return failure();
1198 os << emitter.getOrCreateName(source);
1199 os << ")";
1200
1201 return success();
1202}
1203
1204// Print lhs or rhs operand of add/sub intrinsic
1205template <typename T>
1206static LogicalResult printAddOrSubOperand(CppEmitter &emitter, T op,
1207 unsigned opNum) {
1208 // We currently only support printing operands 0 and 1
1209 if (opNum > 1)
1210 return failure();
1211
1212 // The operand should have already been emitted
1213 Value operand = opNum == 0 ? op.getLhs() : op.getRhs();
1214 if (!emitter.hasValueInScope(operand))
1215 return failure();
1216
1217 raw_indented_ostream &os = emitter.ostream();
1218
1219 StringRef start = op.getStart(opNum);
1220 StringRef offset = op.getOffset(opNum);
1221 StringRef offsetHi = op.getOffsetHi(opNum);
1222 StringRef square = op.getSquare(opNum);
1223
1224 os << emitter.getOrCreateName(operand);
1225 if (!start.empty())
1226 os << ", " << start;
1227 if (!offset.empty())
1228 os << ", " << offset;
1229 if (!offsetHi.empty())
1230 os << ", " << offsetHi;
1231 if (!square.empty())
1232 os << ", " << square;
1233
1234 return success();
1235}
1236
1237// Print lhs or rhs operand of min/max intrinsic
1238template <typename T>
1239static LogicalResult printMinMaxOperand(CppEmitter &emitter, T op,
1240 unsigned opNum) {
1241 // We currently only support printing operands 0 and 1
1242 if (opNum > 1)
1243 return failure();
1244
1245 // The operand should have already been emitted
1246 Value operand = opNum == 0 ? op.getLhs() : op.getRhs();
1247 if (!emitter.hasValueInScope(operand))
1248 return failure();
1249
1250 raw_indented_ostream &os = emitter.ostream();
1251 os << emitter.getOrCreateName(operand);
1252
1253 return success();
1254}
1255
1256// Print lhs or rhs operand of add_elem/sub_elem intrinsic
1257template <typename T>
1258static LogicalResult printAddElemOrSubElemOperand(CppEmitter &emitter, T op,
1259 unsigned opNum) {
1260 // We currently only support printing operands 0 and 1
1261 if (opNum > 1)
1262 return failure();
1263
1264 // The operand should have already been emitted
1265 Value operand = opNum == 0 ? op.getLhs() : op.getRhs();
1266 if (!emitter.hasValueInScope(operand))
1267 return failure();
1268
1269 raw_indented_ostream &os = emitter.ostream();
1270 os << emitter.getOrCreateName(operand);
1271
1272 return success();
1273}
1274
1275// Print lhs or rhs operand of mul/mac intrinsic
1276template <typename T>
1277static LogicalResult printFMAOrMulOperand(CppEmitter &emitter, T op,
1278 unsigned opNum) {
1279 // We currently only support printing operands 0 and 1
1280 if (opNum > 1)
1281 return failure();
1282
1283 // The operand should have already been emitted
1284 Value operand = opNum == 0 ? op.getLhs() : op.getRhs();
1285 if (!emitter.hasValueInScope(operand))
1286 return failure();
1287
1288 raw_indented_ostream &os = emitter.ostream();
1289
1290 StringRef start = op.getStart(opNum);
1291 StringRef offset = op.getOffset(opNum);
1292 StringRef offsetHi = op.getOffsetHi(opNum);
1293 StringRef step = op.getStep(opNum);
1294 StringRef square = op.getSquare(opNum);
1295
1296 os << emitter.getOrCreateName(operand);
1297 if (!start.empty())
1298 os << ", " << start;
1299 if (!offset.empty())
1300 os << ", " << offset;
1301 if (!offsetHi.empty())
1302 os << ", " << offsetHi;
1303 if (!step.empty())
1304 os << ", " << step;
1305 if (!square.empty())
1306 os << ", " << square;
1307
1308 return success();
1309}
1310
1311// Print lhs or rhs operand of mul_elem/mac_elem intrinsic
1312template <typename T>
1313static LogicalResult printFMAOrMulElemOperand(CppEmitter &emitter, T op,
1314 Type iType, int32_t size,
1315 unsigned opNum) {
1316 // We currently only support printing operands 0 and 1
1317 if (opNum > 1)
1318 return failure();
1319
1320 // The operand should have already been emitted
1321 Value operand = opNum == 0 ? op.getLhs() : op.getRhs();
1322 if (!emitter.hasValueInScope(operand))
1323 return failure();
1324
1325 raw_indented_ostream &os = emitter.ostream();
1326 os << emitter.getOrCreateName(operand);
1327 if (size == 32 && iType)
1328 os << ", " << (opNum == 0 ? "undef_v16int32()" : "broadcast_zero_s32()");
1329
1330 return success();
1331}
1332
1333// Print lhs or rhs operand of mul_conv/mac_conv intrinsic
1334template <typename T>
1335static LogicalResult printFMAOrMulConvOperand(CppEmitter &emitter, T op,
1336 unsigned opNum) {
1337 // We currently only support printing operands 0 and 1
1338 if (opNum > 1)
1339 return failure();
1340
1341 // The operand should have already been emitted
1342 Value operand = opNum == 0 ? op.getLhs() : op.getRhs();
1343 if (!emitter.hasValueInScope(operand))
1344 return failure();
1345
1346 raw_indented_ostream &os = emitter.ostream();
1347 os << emitter.getOrCreateName(operand);
1348
1349 return success();
1350}
1351
1352// Generate the Mul op
1353static LogicalResult printOperation(CppEmitter &emitter,
1354 aievec::aie1::MulOp mulOp) {
1355 auto lhs = mulOp.getLhs();
1356 auto rhs = mulOp.getRhs();
1357
1358 // The sources should have already been emitted
1359 if (!emitter.hasValueInScope(lhs) || !emitter.hasValueInScope(rhs))
1360 return failure();
1361
1362 // Determine if the mul scheme is simple or complex
1363 bool simpleScheme = mulOp.getStart(0).empty();
1364
1365 std::string opname;
1366 // Create opname based on the result type
1367 auto resType = llvm::cast<VectorType>(mulOp.getResult().getType());
1368 Type eltType = resType.getElementType();
1369 if (!simpleScheme) {
1370 if (auto iType = llvm::dyn_cast<IntegerType>(eltType)) {
1371 if (iType.getWidth() == 80)
1372 opname = "l";
1373 } else if (llvm::isa<FloatType>(eltType))
1374 opname = "fp";
1375 }
1376
1377 opname += "mul";
1378 if (!simpleScheme && !llvm::isa<FloatType>(eltType))
1379 opname += std::to_string(getVectorLaneSize(resType));
1380
1381 raw_indented_ostream &os = emitter.ostream();
1382
1383 // Generate the initialization for the accumulator
1384 if (failed(emitter.emitAssignPrefix(*mulOp)))
1385 return failure();
1386
1387 os << opname;
1388 os << "(";
1389 if (failed(printFMAOrMulOperand<aievec::aie1::MulOp>(emitter, mulOp, 0)))
1390 return failure();
1391 os << ", ";
1392 if (failed(printFMAOrMulOperand<aievec::aie1::MulOp>(emitter, mulOp, 1)))
1393 return failure();
1394 os << ")";
1395
1396 return success();
1397}
1398// convert operand to 512 bits
1399static std::string printConversionTo512bit(CppEmitter &emitter, Value v) {
1400 std::string vName = emitter.getOrCreateName(v).str();
1401 auto vTy = cast<VectorType>(v.getType());
1402 auto vShape = vTy.getShape();
1403 int64_t elemBitWidth = vTy.getElementTypeBitWidth();
1404 int64_t numElems = std::accumulate(vShape.begin(), vShape.end(), 1,
1405 std::multiplies<int64_t>());
1406 int64_t vBitWidth = numElems * elemBitWidth;
1407 if (vBitWidth >= 512)
1408 return vName;
1409
1410 int64_t newNumElems = 512 / elemBitWidth;
1411
1412 std::string vNewName = emitter.getNewName();
1413 raw_indented_ostream &os = emitter.ostream();
1414 auto newVecTy = VectorType::get({512 / elemBitWidth}, vTy.getElementType());
1415 auto newTyName = *(
1416 emitter.genCppTypeName(newVecTy, /*stdintType=*/false, /*isAcc=*/false));
1417 auto oldTyName =
1418 *(emitter.genCppTypeName(vTy, /*stdintType=*/false, /*isAcc=*/false));
1419
1420 os << newTyName << " " << vNewName << " = concat(";
1421 if (newNumElems / numElems == 4) {
1422 os << "concat(" << vName << ", undef_" << oldTyName << "())";
1423 oldTyName = *(emitter.genCppTypeName(
1424 VectorType::get({256 / elemBitWidth}, vTy.getElementType())));
1425 } else {
1426 os << vName;
1427 }
1428 os << ", undef_" << oldTyName << "());\n";
1429 return vNewName;
1430}
1431
1432// Generate the MulElem op
1433static LogicalResult printOperation(CppEmitter &emitter,
1434 aievec::MulElemOp mulElemOp) {
1435 auto lhs = mulElemOp.getLhs();
1436 auto rhs = mulElemOp.getRhs();
1437
1438 // The sources should have already been emitted
1439 if (!emitter.hasValueInScope(lhs) || !emitter.hasValueInScope(rhs))
1440 return failure();
1441
1442 auto lhsName = printConversionTo512bit(emitter, lhs);
1443 auto rhsName = printConversionTo512bit(emitter, rhs);
1444
1445 std::string opname = "mul_elem";
1446
1447 // Create opname based on the source type
1448 auto lhsType = llvm::cast<VectorType>(mulElemOp.getLhs().getType());
1449 Type eltType = lhsType.getElementType();
1450 int32_t lsize = getElementSizeInBits(lhsType);
1451 auto iType = llvm::dyn_cast<IntegerType>(eltType);
1452
1453 if (iType) {
1454 if (lsize == 32)
1455 opname += "_16_2";
1456 else if (lsize == 16)
1457 opname += "_32";
1458 else if (lsize == 8)
1459 opname += "_32_2";
1460 } else if (llvm::isa<FloatType>(eltType)) {
1461 if (lsize == 32)
1462 opname += "_16";
1463 else if (lsize == 16)
1464 opname += "_16_2";
1465 }
1466
1467 raw_indented_ostream &os = emitter.ostream();
1468
1469 // Generate the initialization for the accumulator
1470 if (failed(emitter.emitAssignPrefix(*mulElemOp, true /*isAcc*/)))
1471 return failure();
1472
1473 os << opname;
1474 os << "(" << lhsName;
1475 if ((lsize == 32) && iType)
1476 os << " ,"
1477 << "undef_v16int32()";
1478 os << " ," << rhsName;
1479 if ((lsize == 32) && iType)
1480 os << " , "
1481 << "broadcast_zero_s32()";
1482 os << ")";
1483 return success();
1484}
1485
1486// Generate the MulConv op
1487static LogicalResult printOperation(CppEmitter &emitter,
1488 aievec::MulConvOp mulConvOp) {
1489 auto lhs = mulConvOp.getLhs();
1490 auto rhs = mulConvOp.getRhs();
1491
1492 // The sources should have already been emitted
1493 if (!emitter.hasValueInScope(lhs) || !emitter.hasValueInScope(rhs))
1494 return failure();
1495
1496 // Create opname based on the source type
1497 auto lhsType = llvm::cast<VectorType>(mulConvOp.getLhs().getType());
1498 Type eltType = lhsType.getElementType();
1499 int32_t lsize = getElementSizeInBits(lhsType);
1500 auto iType = llvm::dyn_cast<IntegerType>(eltType);
1501
1502 // Only support int16 and int8 cases
1503 if (!iType || !(lsize == 16 || lsize == 8)) {
1504 return failure();
1505 }
1506
1507 int32_t M = mulConvOp.getM();
1508 int32_t N = mulConvOp.getN();
1509 std::string opname =
1510 "mul_conv_" + std::to_string(M) + "x" + std::to_string(N);
1511
1512 raw_indented_ostream &os = emitter.ostream();
1513
1514 // Generate the initialization for the accumulator
1515 if (failed(emitter.emitAssignPrefix(*mulConvOp, true /*isAcc*/)))
1516 return failure();
1517
1518 os << opname;
1519 os << "(";
1520
1521 if (failed(
1522 printFMAOrMulConvOperand<aievec::MulConvOp>(emitter, mulConvOp, 0)))
1523 return failure();
1524 os << ", ";
1525 if (failed(
1526 printFMAOrMulConvOperand<aievec::MulConvOp>(emitter, mulConvOp, 1)))
1527 return failure();
1528 os << ")";
1529
1530 return success();
1531}
1532
1533// Generate the Add op
1534static LogicalResult printOperation(CppEmitter &emitter,
1535 aievec::aie1::AddOp addOp) {
1536 auto lhs = addOp.getLhs();
1537 auto rhs = addOp.getRhs();
1538
1539 // The sources should have already been emitted
1540 if (!emitter.hasValueInScope(lhs) || !emitter.hasValueInScope(rhs))
1541 return failure();
1542
1543 raw_indented_ostream &os = emitter.ostream();
1544
1545 // Generate the initialization for the result
1546 if (failed(emitter.emitAssignPrefix(*addOp)))
1547 return failure();
1548
1549 // Get the scalar type of result vector
1550 auto resultType = llvm::cast<VectorType>(addOp.getResult().getType());
1551 unsigned lanes = getVectorLaneSize(resultType);
1552 Type elementType = resultType.getElementType();
1553 bool floatType = llvm::isa<FloatType>(elementType);
1554
1555 // Detemine if the add scheme is simple or complex
1556
1557 if (addOp.getStart(0).empty()) {
1558 // Handle float type operation
1559 if (floatType) {
1560 os << "fpadd";
1561 os << "(";
1562 os << emitter.getOrCreateName(lhs);
1563 os << ", ";
1564 os << emitter.getOrCreateName(rhs);
1565 os << ")";
1566 }
1567 // Otherwise we can simply print this as overloaded +
1568 else {
1569 os << emitter.getOrCreateName(lhs);
1570 os << " + ";
1571 os << emitter.getOrCreateName(rhs);
1572 }
1573 return success();
1574 }
1575 // Otherwise this is complex scheme
1576 os << (floatType ? "fpadd" : "add" + std::to_string(lanes));
1577 os << "(";
1578 if (failed(printAddOrSubOperand<aievec::aie1::AddOp>(emitter, addOp, 0)))
1579 return failure();
1580 os << ", ";
1581 if (failed(printAddOrSubOperand<aievec::aie1::AddOp>(emitter, addOp, 1)))
1582 return failure();
1583 os << ")";
1584
1585 return success();
1586}
1587
1588// Generate the Sub op
1589static LogicalResult printOperation(CppEmitter &emitter,
1590 aievec::aie1::SubOp subOp) {
1591 auto lhs = subOp.getLhs();
1592 auto rhs = subOp.getRhs();
1593
1594 // The sources should have already been emitted
1595 if (!emitter.hasValueInScope(lhs) || !emitter.hasValueInScope(rhs))
1596 return failure();
1597
1598 raw_indented_ostream &os = emitter.ostream();
1599
1600 // Generate the initialization for the result
1601 if (failed(emitter.emitAssignPrefix(*subOp)))
1602 return failure();
1603
1604 // Get the scalar type of result vector
1605 auto resultType = llvm::cast<VectorType>(subOp.getResult().getType());
1606 unsigned lanes = getVectorLaneSize(resultType);
1607 Type elementType = resultType.getElementType();
1608 bool floatType = llvm::isa<FloatType>(elementType);
1609
1610 // Detemine if the sub scheme is simple or complex
1611
1612 if (subOp.getStart(0).empty()) {
1613 // Handle float type operation
1614 if (floatType) {
1615 os << "fpsub";
1616 os << "(";
1617 os << emitter.getOrCreateName(lhs);
1618 os << ", ";
1619 os << emitter.getOrCreateName(rhs);
1620 os << ")";
1621 }
1622 // Otherwise we can simply print this as overloaded -
1623 else {
1624 os << emitter.getOrCreateName(lhs);
1625 os << " - ";
1626 os << emitter.getOrCreateName(rhs);
1627 }
1628 return success();
1629 }
1630 // Otherwise this is complex scheme
1631 os << (floatType ? "fpsub" : "sub" + std::to_string(lanes));
1632 os << "(";
1633 if (failed(printAddOrSubOperand<aievec::aie1::SubOp>(emitter, subOp, 0)))
1634 return failure();
1635 os << ", ";
1636 if (failed(printAddOrSubOperand<aievec::aie1::SubOp>(emitter, subOp, 1)))
1637 return failure();
1638 os << ")";
1639
1640 return success();
1641}
1642
1643// Generate the Min op
1644static LogicalResult printOperation(CppEmitter &emitter, aievec::MinOp minOp) {
1645 auto lhs = minOp.getLhs();
1646 auto rhs = minOp.getRhs();
1647
1648 // The sources should have already been emitted
1649 if (!emitter.hasValueInScope(lhs) || !emitter.hasValueInScope(rhs))
1650 return failure();
1651
1652 raw_indented_ostream &os = emitter.ostream();
1653
1654 // Generate the initialization for the result
1655 if (failed(emitter.emitAssignPrefix(*minOp)))
1656 return failure();
1657
1658 os << "min(";
1659 if (failed(printMinMaxOperand<aievec::MinOp>(emitter, minOp, 0)))
1660 return failure();
1661 os << ", ";
1662 if (failed(printMinMaxOperand<aievec::MinOp>(emitter, minOp, 1)))
1663 return failure();
1664 os << ")";
1665
1666 return success();
1667}
1668
1669// Generate the Max op
1670static LogicalResult printOperation(CppEmitter &emitter, aievec::MaxOp maxOp) {
1671 auto lhs = maxOp.getLhs();
1672 auto rhs = maxOp.getRhs();
1673
1674 // The sources should have already been emitted
1675 if (!emitter.hasValueInScope(lhs) || !emitter.hasValueInScope(rhs))
1676 return failure();
1677
1678 raw_indented_ostream &os = emitter.ostream();
1679
1680 // Generate the initialization for the result
1681 if (failed(emitter.emitAssignPrefix(*maxOp)))
1682 return failure();
1683
1684 os << "max(";
1685 if (failed(printMinMaxOperand<aievec::MaxOp>(emitter, maxOp, 0)))
1686 return failure();
1687 os << ", ";
1688 if (failed(printMinMaxOperand<aievec::MaxOp>(emitter, maxOp, 1)))
1689 return failure();
1690 os << ")";
1691
1692 return success();
1693}
1694
1695// Generate the Neg op
1696static LogicalResult printOperation(CppEmitter &emitter, aievec::NegOp negOp) {
1697 auto src = negOp.getSource();
1698
1699 // The source should have already been emitted
1700 if (!emitter.hasValueInScope(src))
1701 return failure();
1702
1703 raw_indented_ostream &os = emitter.ostream();
1704
1705 // Generate the initialization for the result
1706 if (failed(emitter.emitAssignPrefix(*negOp, true /*isAcc*/)))
1707 return failure();
1708
1709 os << "neg(";
1710 os << emitter.getOrCreateName(src);
1711 os << ")";
1712
1713 return success();
1714}
1715
1716// Generate the Bneg op
1717static LogicalResult printOperation(CppEmitter &emitter,
1718 aievec::BnegOp bnegOp) {
1719 auto src = bnegOp.getSource();
1720
1721 // The source should have already been emitted
1722 if (!emitter.hasValueInScope(src))
1723 return failure();
1724
1725 raw_indented_ostream &os = emitter.ostream();
1726
1727 // Generate the initialization for the result
1728 if (failed(emitter.emitAssignPrefix(*bnegOp)))
1729 return failure();
1730
1731 os << "bneg(";
1732 os << emitter.getOrCreateName(src);
1733 os << ")";
1734
1735 return success();
1736}
1737
1738// Generate the Bxor op
1739static LogicalResult printOperation(CppEmitter &emitter, aievec::BxorOp xorOp) {
1740 auto lhs = xorOp.getLhs();
1741 auto rhs = xorOp.getRhs();
1742
1743 // The source should have already been emitted
1744 if (!emitter.hasValueInScope(lhs) || !emitter.hasValueInScope(rhs))
1745 return failure();
1746
1747 raw_indented_ostream &os = emitter.ostream();
1748
1749 // Generate the initialization for the result
1750 if (failed(emitter.emitAssignPrefix(*xorOp)))
1751 return failure();
1752
1753 os << "bxor(";
1754 os << emitter.getOrCreateName(lhs);
1755 os << ", ";
1756 os << emitter.getOrCreateName(rhs);
1757 os << ")";
1758
1759 return success();
1760}
1761
1762// Generate the Band op
1763static LogicalResult printOperation(CppEmitter &emitter, aievec::BandOp andOp) {
1764 auto lhs = andOp.getLhs();
1765 auto rhs = andOp.getRhs();
1766
1767 // The source should have already been emitted
1768 if (!emitter.hasValueInScope(lhs) || !emitter.hasValueInScope(rhs))
1769 return failure();
1770
1771 raw_indented_ostream &os = emitter.ostream();
1772
1773 // Generate the initialization for the result
1774 if (failed(emitter.emitAssignPrefix(*andOp)))
1775 return failure();
1776
1777 os << "band(";
1778 os << emitter.getOrCreateName(lhs);
1779 os << ", ";
1780 os << emitter.getOrCreateName(rhs);
1781 os << ")";
1782
1783 return success();
1784}
1785
1786// Generate the Bor op
1787static LogicalResult printOperation(CppEmitter &emitter, aievec::BorOp orOp) {
1788 auto lhs = orOp.getLhs();
1789 auto rhs = orOp.getRhs();
1790
1791 // The source should have already been emitted
1792 if (!emitter.hasValueInScope(lhs) || !emitter.hasValueInScope(rhs))
1793 return failure();
1794
1795 raw_indented_ostream &os = emitter.ostream();
1796
1797 // Generate the initialization for the result
1798 if (failed(emitter.emitAssignPrefix(*orOp)))
1799 return failure();
1800
1801 os << "bor(";
1802 os << emitter.getOrCreateName(lhs);
1803 os << ", ";
1804 os << emitter.getOrCreateName(rhs);
1805 os << ")";
1806
1807 return success();
1808}
1809
1810// Generate the AddElem op
1811static LogicalResult printOperation(CppEmitter &emitter,
1812 aievec::AddElemOp addElemOp) {
1813 auto lhs = addElemOp.getLhs();
1814 auto rhs = addElemOp.getRhs();
1815
1816 // The sources should have already been emitted
1817 if (!emitter.hasValueInScope(lhs) || !emitter.hasValueInScope(rhs))
1818 return failure();
1819
1820 raw_indented_ostream &os = emitter.ostream();
1821
1822 // Generate the initialization for the result
1823 // FIXME: move the logic to the op creation and add isAcc to the op attribute
1824 bool isAcc = false;
1825 auto resType = cast<VectorType>(addElemOp.getResult().getType());
1826 auto resElemType = resType.getElementType();
1827 unsigned resBitWidth = resElemType.getIntOrFloatBitWidth();
1828 unsigned resLaneSize = getVectorLaneSize(resType);
1829 if (isa<FloatType>(resElemType) || resBitWidth * resLaneSize == 1024)
1830 isAcc = true;
1831
1832 if (failed(emitter.emitAssignPrefix(*addElemOp, /*isAcc=*/isAcc)))
1833 return failure();
1834
1835 os << "add(";
1836 if (failed(printAddElemOrSubElemOperand<aievec::AddElemOp>(emitter, addElemOp,
1837 0)))
1838 return failure();
1839 os << ", ";
1840 if (failed(printAddElemOrSubElemOperand<aievec::AddElemOp>(emitter, addElemOp,
1841 1)))
1842 return failure();
1843 os << ")";
1844
1845 return success();
1846}
1847
1848// Generate the SubElem op
1849static LogicalResult printOperation(CppEmitter &emitter,
1850 aievec::SubElemOp subElemOp) {
1851 auto lhs = subElemOp.getLhs();
1852 auto rhs = subElemOp.getRhs();
1853
1854 // The sources should have already been emitted
1855 if (!emitter.hasValueInScope(lhs) || !emitter.hasValueInScope(rhs))
1856 return failure();
1857
1858 raw_indented_ostream &os = emitter.ostream();
1859
1860 // Generate the initialization for the result
1861 // FIXME: move the logic to the op creation and add isAcc to the op attribute
1862 bool isAcc = false;
1863 auto resType = cast<VectorType>(subElemOp.getResult().getType());
1864 auto resElemType = resType.getElementType();
1865 unsigned resBitWidth = resElemType.getIntOrFloatBitWidth();
1866 unsigned resLaneSize = getVectorLaneSize(resType);
1867 if (isa<FloatType>(resElemType) || resBitWidth * resLaneSize == 1024)
1868 isAcc = true;
1869
1870 if (failed(emitter.emitAssignPrefix(*subElemOp, /*isAcc=*/isAcc)))
1871 return failure();
1872
1873 os << "sub(";
1874 if (failed(printAddElemOrSubElemOperand<aievec::SubElemOp>(emitter, subElemOp,
1875 0)))
1876 return failure();
1877 os << ", ";
1878 if (failed(printAddElemOrSubElemOperand<aievec::SubElemOp>(emitter, subElemOp,
1879 1)))
1880 return failure();
1881 os << ")";
1882
1883 return success();
1884}
1885
1886// Generate the FMA op
1887static LogicalResult printOperation(CppEmitter &emitter,
1888 aievec::aie1::FMAOp fmaOp) {
1889 auto acc = fmaOp.getAcc();
1890 auto lhs = fmaOp.getLhs();
1891 auto rhs = fmaOp.getRhs();
1892
1893 // The sources should have already been emitted
1894 if (!emitter.hasValueInScope(acc) || !emitter.hasValueInScope(lhs) ||
1895 !emitter.hasValueInScope(rhs))
1896 return failure();
1897
1898 // Detemine if the mul scheme is simple or complex
1899 bool simpleScheme = fmaOp.getStart(0).empty();
1900
1901 std::string opname;
1902 // Create opname based on the result type
1903 auto resType = llvm::cast<VectorType>(fmaOp.getResult().getType());
1904 Type eltType = resType.getElementType();
1905 if (!simpleScheme) {
1906 if (auto iType = llvm::dyn_cast<IntegerType>(eltType)) {
1907 if (iType.getWidth() == 80)
1908 opname = "l";
1909 } else if (llvm::isa<FloatType>(eltType))
1910 opname = "fp";
1911 }
1912
1913 opname += fmaOp.getFmsub() ? "msc" : "mac";
1914 if (!simpleScheme && !llvm::isa<FloatType>(eltType))
1915 opname += std::to_string(getVectorLaneSize(resType));
1916
1917 raw_indented_ostream &os = emitter.ostream();
1918
1919 StringRef accName = emitter.getOrCreateName(acc);
1920 os << accName;
1921 os << " = ";
1922 os << opname;
1923 os << "(";
1924 os << accName;
1925 os << ", ";
1926 if (failed(printFMAOrMulOperand<aievec::aie1::FMAOp>(emitter, fmaOp, 0)))
1927 return failure();
1928 os << ", ";
1929 if (failed(printFMAOrMulOperand<aievec::aie1::FMAOp>(emitter, fmaOp, 1)))
1930 return failure();
1931 os << ")";
1932
1933 // Finally, set the name of the result to the accumulator's name
1934 emitter.setName(fmaOp->getResult(0), accName);
1935
1936 return success();
1937}
1938
1939// Generate the FMAElem op
1940static LogicalResult printOperation(CppEmitter &emitter,
1941 aievec::FMAElemOp fmaElemOp) {
1942 auto acc = fmaElemOp.getAcc();
1943 auto lhs = fmaElemOp.getLhs();
1944 auto rhs = fmaElemOp.getRhs();
1945
1946 // The sources should have already been emitted
1947 if (!emitter.hasValueInScope(acc) || !emitter.hasValueInScope(lhs) ||
1948 !emitter.hasValueInScope(rhs))
1949 return failure();
1950
1951 std::string opname = fmaElemOp.getFmsub() ? "msc_elem" : "mac_elem";
1952 // Create opname based on the lhs and rhs type
1953 auto lhsType = llvm::cast<VectorType>(fmaElemOp.getLhs().getType());
1954 Type eltType = lhsType.getElementType();
1955 int32_t lsize = getElementSizeInBits(lhsType);
1956 auto iType = llvm::dyn_cast<IntegerType>(eltType);
1957
1958 if (iType) {
1959 if (lsize == 32)
1960 opname += "_16_2";
1961 else if (lsize == 16)
1962 opname += "_32";
1963 else if (lsize == 8)
1964 opname += "_32_2";
1965 } else if (llvm::isa<FloatType>(eltType)) {
1966 if (lsize == 32)
1967 opname += "_16";
1968 else if (lsize == 16)
1969 opname += "_16_2";
1970 }
1971
1972 raw_indented_ostream &os = emitter.ostream();
1973
1974 StringRef accName = emitter.getOrCreateName(acc);
1975 os << accName;
1976 os << " = ";
1977 os << opname;
1978 os << "(";
1979 if (failed(printFMAOrMulElemOperand<aievec::FMAElemOp>(emitter, fmaElemOp,
1980 iType, lsize, 1)))
1981 return failure();
1982 os << ", ";
1983 if (failed(printFMAOrMulElemOperand<aievec::FMAElemOp>(emitter, fmaElemOp,
1984 iType, lsize, 0)))
1985 return failure();
1986 os << ", ";
1987 os << accName;
1988 os << ")";
1989
1990 // Finally, set the name of the result to the accumulator's name
1991 emitter.setName(fmaElemOp->getResult(0), accName);
1992
1993 return success();
1994}
1995
1996// Generate the FMAConv op
1997static LogicalResult printOperation(CppEmitter &emitter,
1998 aievec::FMAConvOp fmaConvOp) {
1999 auto acc = fmaConvOp.getAcc();
2000 auto lhs = fmaConvOp.getLhs();
2001 auto rhs = fmaConvOp.getRhs();
2002
2003 // The sources should have already been emitted
2004 if (!emitter.hasValueInScope(acc) || !emitter.hasValueInScope(lhs) ||
2005 !emitter.hasValueInScope(rhs))
2006 return failure();
2007
2008 std::string opname = fmaConvOp.getFmsub() ? "msc_conv" : "mac_conv";
2009 // Create opname based on the lhs and rhs type
2010 auto lhsType = llvm::cast<VectorType>(fmaConvOp.getLhs().getType());
2011 Type eltType = lhsType.getElementType();
2012 int32_t lsize = getElementSizeInBits(lhsType);
2013 auto iType = llvm::dyn_cast<IntegerType>(eltType);
2014
2015 // Only support int16 and int8 cases
2016 if (!iType || !(lsize == 16 || lsize == 8))
2017 return failure();
2018
2019 int32_t M = fmaConvOp.getM();
2020 int32_t N = fmaConvOp.getN();
2021 opname += "_" + std::to_string(M) + "x" + std::to_string(N);
2022
2023 raw_indented_ostream &os = emitter.ostream();
2024
2025 StringRef accName = emitter.getOrCreateName(acc);
2026 os << accName;
2027 os << " = ";
2028 os << opname;
2029 os << "(";
2030 if (failed(
2031 printFMAOrMulConvOperand<aievec::FMAConvOp>(emitter, fmaConvOp, 0)))
2032 return failure();
2033 os << ", ";
2034 if (failed(
2035 printFMAOrMulConvOperand<aievec::FMAConvOp>(emitter, fmaConvOp, 1)))
2036 return failure();
2037 os << ", ";
2038 os << accName;
2039 os << ")";
2040
2041 // Finally, set the name of the result to the accumulator's name
2042 emitter.setName(fmaConvOp->getResult(0), accName);
2043
2044 return success();
2045}
2046
2047// Generate the comparison intrinsics(eq, ne, lt, le, gt, ge) for AIE2
2048static LogicalResult printOperation(CppEmitter &emitter, aievec::CmpOp cmpOp) {
2049 if (!emitter.aie2())
2050 return failure();
2051
2052 // The lhs and rhs should have already been emitted
2053 Value lhs = cmpOp.getLhs();
2054 Value rhs = cmpOp.getRhs();
2055
2056 if (!emitter.hasValueInScope(lhs) || !emitter.hasValueInScope(rhs))
2057 return failure();
2058
2059 // Generate the initialization for the vector
2060 if (failed(emitter.emitAssignPrefix(*cmpOp)))
2061 return failure();
2062
2063 raw_indented_ostream &os = emitter.ostream();
2064
2065 StringRef pred = cmpOp.getPred();
2066 if (pred == "eq")
2067 os << "eq";
2068 else if (pred == "ne")
2069 os << "ne";
2070 else if (pred == "slt" || pred == "ult")
2071 os << "lt";
2072 else if (pred == "sle" || pred == "ule")
2073 os << "le";
2074 else if (pred == "sgt" || pred == "ugt")
2075 os << "gt";
2076 else if (pred == "sge" || pred == "uge")
2077 os << "ge";
2078 else
2079 return failure();
2080
2081 os << "(";
2082 auto vType = llvm::cast<VectorType>(lhs.getType());
2083
2084 if (Type eltType = vType.getElementType();
2085 llvm::isa<IntegerType>(eltType) &&
2086 (pred == "ult" || pred == "ule" || pred == "ugt" || pred == "uge")) {
2087 unsigned lanes = getVectorLaneSize(vType);
2088 unsigned width = getElementSizeInBits(vType);
2089 os << "v" << std::to_string(lanes) << "uint" << std::to_string(width);
2090 os << "(";
2091 os << emitter.getOrCreateName(lhs);
2092 os << "), ";
2093 os << "v" << std::to_string(lanes) << "uint" << std::to_string(width);
2094 os << "(";
2095 os << emitter.getOrCreateName(rhs);
2096 os << ")";
2097 } else {
2098 os << emitter.getOrCreateName(lhs);
2099 os << ", ";
2100 os << emitter.getOrCreateName(rhs);
2101 }
2102 os << ")";
2103
2104 return success();
2105}
2106
2107// Generate the sel intrinsic for AIE2
2108static LogicalResult printOperation(CppEmitter &emitter, aievec::SelOp selOp) {
2109 if (!emitter.aie2())
2110 return failure();
2111
2112 // The lhs, rhs and sel should have already been emitted
2113 Value lhs = selOp.getLhs();
2114 Value rhs = selOp.getRhs();
2115 Value sel = selOp.getSel();
2116
2117 if (!emitter.hasValueInScope(lhs) || !emitter.hasValueInScope(rhs) ||
2118 !emitter.hasValueInScope(sel))
2119 return failure();
2120
2121 // Generate the initialization for the vector
2122 if (failed(emitter.emitAssignPrefix(*selOp)))
2123 return failure();
2124
2125 raw_indented_ostream &os = emitter.ostream();
2126
2127 // Chess sel(a, b, mask): bit=0 selects a, bit=1 selects b.
2128 // aievec.sel(lhs, rhs, mask): bit=0 selects lhs, bit=1 selects rhs.
2129 // Pass lhs as first arg and rhs as second to preserve semantics.
2130 os << "sel(";
2131 os << emitter.getOrCreateName(lhs);
2132 os << ", ";
2133 os << emitter.getOrCreateName(rhs);
2134 os << ", ";
2135 os << emitter.getOrCreateName(sel);
2136 os << ")";
2137
2138 return success();
2139}
2140
2141// Generate the extract elem intrinsic
2142static LogicalResult printOperation(CppEmitter &emitter,
2143 aievec::ExtElemOp extElemOp) {
2144 Value source = extElemOp.getSource();
2145 Value index = extElemOp.getIndex();
2146
2147 raw_indented_ostream &os = emitter.ostream();
2148
2149 // Generate the initialization for the result
2150 if (failed(emitter.emitAssignPrefix(*extElemOp)))
2151 return failure();
2152
2153 // source should have already been emitted
2154 if (!emitter.hasValueInScope(source))
2155 return failure();
2156
2157 os << "extract_elem";
2158 os << "(";
2159 // Print the source and index
2160 os << emitter.getOrCreateName(source);
2161 os << ", ";
2162 os << emitter.getOrCreateName(index);
2163 os << ")";
2164
2165 return success();
2166}
2167
2168// Generate the transfer write op
2169static LogicalResult printOperation(CppEmitter &emitter,
2170 vector::TransferWriteOp writeOp) {
2171 Value source = writeOp.getBase();
2172 Value vector = writeOp.getVector();
2173
2174 // If the aray, or the vector being outputted is not already emitted,
2175 // error out
2176 if (!emitter.hasValueInScope(source) || !emitter.hasValueInScope(vector))
2177 return failure();
2178
2179 // Construct the access expression using memref shape and indices
2180 std::string access;
2181 auto indices = writeOp.getIndices();
2182 if (failed(createLinearizedAccess(emitter, source, indices, access)))
2183 return failure();
2184
2185 raw_indented_ostream &os = emitter.ostream();
2186
2187 os << "*(";
2188 if (failed(emitter.emitType(writeOp->getLoc(), vector.getType())))
2189 return failure();
2190 os << " *)";
2191 os << "(";
2192 os << emitter.getOrCreateName(source);
2193 if (!access.empty())
2194 os << " + " << access;
2195 os << ")";
2196 os << " = ";
2197 os << emitter.getOrCreateName(vector);
2198
2199 return success();
2200}
2201
2202// Generate the memref store op
2203static LogicalResult printOperation(CppEmitter &emitter,
2204 memref::StoreOp storeOp) {
2205 Value value = storeOp.getValue();
2206 Value memref = storeOp.getMemref();
2207
2208 // If the value, or the memref being outputted is not already emitted,
2209 // error out
2210 if (!emitter.hasValueInScope(value) || !emitter.hasValueInScope(memref))
2211 return failure();
2212
2213 raw_indented_ostream &os = emitter.ostream();
2214
2215 os << "*(";
2216 if (failed(emitter.emitType(
2217 storeOp->getLoc(),
2218 cast<MemRefType>(memref.getType()).getElementType())))
2219 return failure();
2220 os << " *)";
2221 os << emitter.getOrCreateName(memref);
2222 os << " = ";
2223 os << emitter.getOrCreateName(value);
2224
2225 return success();
2226}
2227
2228// Print an operation by forwarding the value to the next op
2229template <typename OpTy>
2230static LogicalResult printValueForwardOperation(CppEmitter &emitter, OpTy op) {
2231 Value source = op.getSrc();
2232
2233 // If the memref being outputted is not already emitted,
2234 // error out
2235 if (!emitter.hasValueInScope(source))
2236 return failure();
2237
2238 if (failed(emitter.emitAssignPrefix(*op)))
2239 return failure();
2240
2241 raw_indented_ostream &os = emitter.ostream();
2242 os << emitter.getOrCreateName(source);
2243
2244 return success();
2245}
2246
2247// Print an expand shape by forwarding the value to the next op
2248static LogicalResult printOperation(CppEmitter &emitter,
2249 memref::ExpandShapeOp expandShapeOp) {
2250 return printValueForwardOperation<memref::ExpandShapeOp>(emitter,
2251 expandShapeOp);
2252}
2253
2254// Print a collapse shape by forwarding the value to the next op
2255static LogicalResult printOperation(CppEmitter &emitter,
2256 memref::CollapseShapeOp collapseShapeOp) {
2257 return printValueForwardOperation<memref::CollapseShapeOp>(emitter,
2258 collapseShapeOp);
2259}
2260
2261static LogicalResult printConstantOp(CppEmitter &emitter, Operation *operation,
2262 Attribute value) {
2263 OpResult result = operation->getResult(0);
2264
2265 // Only emit an assignment as the variable was already declared when printing
2266 // the FuncOp.
2267 if (emitter.shouldDeclareVariablesAtTop()) {
2268 // Skip the assignment if the emitc.constant has no value.
2269 if (auto oAttr = llvm::dyn_cast<emitc::OpaqueAttr>(value))
2270 if (oAttr.getValue().empty())
2271 return success();
2272
2273 if (failed(emitter.emitVariableAssignment(result)))
2274 return failure();
2275 return emitter.emitAttribute(operation->getLoc(), value);
2276 }
2277
2278 // Emit a variable declaration for an emitc.constant op without value.
2279 if (auto oAttr = llvm::dyn_cast<emitc::OpaqueAttr>(value))
2280 if (oAttr.getValue().empty())
2281 // The semicolon gets printed by the emitOperation function.
2282 return emitter.emitVariableDeclaration(result,
2283 /*trailingSemicolon=*/false);
2284
2285 // Emit a variable declaration.
2286 if (failed(emitter.emitAssignPrefix(*operation)))
2287 return failure();
2288 return emitter.emitAttribute(operation->getLoc(), value);
2289}
2290
2291static LogicalResult printOperation(CppEmitter &emitter,
2292 emitc::ConstantOp constantOp) {
2293 Operation *operation = constantOp.getOperation();
2294 Attribute value = constantOp.getValue();
2295 return printConstantOp(emitter, operation, value);
2296}
2297
2298static LogicalResult printOperation(CppEmitter &emitter,
2299 arith::ConstantOp constantOp) {
2300 Operation *operation = constantOp.getOperation();
2301 Attribute value = constantOp.getValue();
2302 return printConstantOp(emitter, operation, value);
2303}
2304
2305static LogicalResult printOperation(CppEmitter &emitter,
2306 cf::BranchOp branchOp) {
2307 raw_ostream &os = emitter.ostream();
2308 Block &successor = *branchOp.getSuccessor();
2309
2310 for (auto pair : zip(branchOp.getOperands(), successor.getArguments())) {
2311 Value &operand = std::get<0>(pair);
2312 BlockArgument &argument = std::get<1>(pair);
2313 os << emitter.getOrCreateName(argument) << " = "
2314 << emitter.getOrCreateName(operand) << ";\n";
2315 }
2316
2317 os << "goto ";
2318 if (!emitter.hasBlockLabel(successor))
2319 return branchOp.emitOpError("unable to find label for successor block");
2320 os << emitter.getOrCreateName(successor);
2321 return success();
2322}
2323
2324static LogicalResult printOperation(CppEmitter &emitter,
2325 cf::CondBranchOp condBranchOp) {
2326 raw_indented_ostream &os = emitter.ostream();
2327 Block &trueSuccessor = *condBranchOp.getTrueDest();
2328 Block &falseSuccessor = *condBranchOp.getFalseDest();
2329
2330 os << "if (" << emitter.getOrCreateName(condBranchOp.getCondition())
2331 << ") {\n";
2332
2333 os.indent();
2334
2335 // If condition is true.
2336 for (auto pair :
2337 zip(condBranchOp.getTrueOperands(), trueSuccessor.getArguments())) {
2338 Value &operand = std::get<0>(pair);
2339 BlockArgument &argument = std::get<1>(pair);
2340 os << emitter.getOrCreateName(argument) << " = "
2341 << emitter.getOrCreateName(operand) << ";\n";
2342 }
2343
2344 os << "goto ";
2345 if (!emitter.hasBlockLabel(trueSuccessor))
2346 return condBranchOp.emitOpError("unable to find label for successor block");
2347 os << emitter.getOrCreateName(trueSuccessor) << ";\n";
2348 os.unindent() << "} else {\n";
2349 os.indent();
2350 // If condition is false.
2351 for (auto pair :
2352 zip(condBranchOp.getFalseOperands(), falseSuccessor.getArguments())) {
2353 Value &operand = std::get<0>(pair);
2354 BlockArgument &argument = std::get<1>(pair);
2355 os << emitter.getOrCreateName(argument) << " = "
2356 << emitter.getOrCreateName(operand) << ";\n";
2357 }
2358
2359 os << "goto ";
2360 if (!emitter.hasBlockLabel(falseSuccessor))
2361 return condBranchOp.emitOpError()
2362 << "unable to find label for successor block";
2363 os << emitter.getOrCreateName(falseSuccessor) << ";\n";
2364 os.unindent() << "}";
2365
2366 return success();
2367}
2368
2369static LogicalResult printOperation(CppEmitter &emitter, func::CallOp callOp) {
2370 if (failed(emitter.emitAssignPrefix(*callOp.getOperation())))
2371 return failure();
2372
2373 raw_ostream &os = emitter.ostream();
2374 os << callOp.getCallee() << "(";
2375 if (failed(emitter.emitOperands(*callOp.getOperation())))
2376 return failure();
2377 os << ")";
2378
2379 return success();
2380}
2381
2382static LogicalResult printOperation(CppEmitter &emitter,
2383 emitc::CallOpaqueOp callOp) {
2384 raw_ostream &os = emitter.ostream();
2385 Operation &op = *callOp.getOperation();
2386 if (callOp.getCallee() == "getTanhBf16" ||
2387 callOp.getCallee() == "getSqrtBf16" ||
2388 callOp.getCallee() == "getRsqrtBf16" ||
2389 callOp.getCallee() == "getErfBf16" || callOp.getCallee() == "getAbs" ||
2390 callOp.getCallee() == "getSigmoidBf16" ||
2391 callOp.getCallee() == "getCeilBf16" ||
2392 callOp.getCallee() == "getFloorBf16") {
2393 if (failed(emitter.emitAssignPrefix(op, /*isAcc*/ false)))
2394 return failure();
2395 } else if (failed(emitter.emitAssignPrefix(op, /*isAcc*/ true)))
2396 return failure();
2397
2398 os << callOp.getCallee();
2399
2400 auto emitArgs = [&](Attribute attr) -> LogicalResult {
2401 // Index attributes are treated specially as operand index.
2402 if (auto t = llvm::dyn_cast<IntegerAttr>(attr))
2403 if (t.getType().isIndex()) {
2404 int64_t idx = t.getInt();
2405 if (idx < 0 || idx >= op.getNumOperands())
2406 return op.emitOpError("invalid operand index");
2407 if (!emitter.hasValueInScope(op.getOperand(idx)))
2408 return op.emitOpError("operand ")
2409 << idx << "'s value not defined in scope";
2410 os << emitter.getOrCreateName(op.getOperand(idx));
2411 return success();
2412 }
2413 if (failed(emitter.emitAttribute(op.getLoc(), attr)))
2414 return failure();
2415
2416 return success();
2417 };
2418
2419 if (callOp.getTemplateArgs()) {
2420 os << "<";
2421 if (failed(
2422 interleaveCommaWithError(*callOp.getTemplateArgs(), os, emitArgs)))
2423 return failure();
2424 os << ">";
2425 }
2426
2427 os << "(";
2428
2429 LogicalResult emittedArgs =
2430 callOp.getArgs()
2431 ? interleaveCommaWithError(*callOp.getArgs(), os, emitArgs)
2432 : emitter.emitOperands(op);
2433 if (failed(emittedArgs))
2434 return failure();
2435 os << ")";
2436
2437 return success();
2438}
2439
2440static LogicalResult printOperation(CppEmitter &emitter,
2441 emitc::ApplyOp applyOp) {
2442 raw_ostream &os = emitter.ostream();
2443
2444 if (Operation &op = *applyOp.getOperation();
2445 failed(emitter.emitAssignPrefix(op)))
2446 return failure();
2447 os << applyOp.getApplicableOperator();
2448 os << emitter.getOrCreateName(applyOp.getOperand());
2449
2450 return success();
2451}
2452
2453static LogicalResult printOperation(CppEmitter &emitter,
2454 emitc::IncludeOp includeOp) {
2455 raw_ostream &os = emitter.ostream();
2456
2457 os << "#include ";
2458 if (includeOp.getIsStandardInclude())
2459 os << "<" << includeOp.getInclude() << ">";
2460 else
2461 os << "\"" << includeOp.getInclude() << "\"";
2462
2463 return success();
2464}
2465
2466static LogicalResult printOperation(CppEmitter &emitter, scf::ForOp forOp) {
2467 raw_indented_ostream &os = emitter.ostream();
2468
2469 OperandRange operands = forOp.getInitArgs();
2470 Block::BlockArgListType iterArgs = forOp.getRegionIterArgs();
2471 Operation::result_range results = forOp.getResults();
2472
2473 if (!emitter.shouldDeclareVariablesAtTop())
2474 for (OpResult result : results)
2475 if (failed(emitter.emitVariableDeclaration(result,
2476 /*trailingSemicolon=*/true)))
2477 return failure();
2478
2479 for (auto pair : zip(iterArgs, operands)) {
2480 if (failed(emitter.emitType(forOp.getLoc(), std::get<0>(pair).getType())))
2481 return failure();
2482 os << " " << emitter.getOrCreateName(std::get<0>(pair)) << " = ";
2483 os << emitter.getOrCreateName(std::get<1>(pair)) << ";";
2484 os << "\n";
2485 }
2486
2487 os << "for (";
2488 if (failed(
2489 emitter.emitType(forOp.getLoc(), forOp.getInductionVar().getType())))
2490 return failure();
2491
2492 os << " ";
2493 os << emitter.getOrCreateName(forOp.getInductionVar());
2494 os << " = ";
2495 os << emitter.getOrCreateName(forOp.getLowerBound());
2496 os << "; ";
2497 os << emitter.getOrCreateName(forOp.getInductionVar());
2498 os << " < ";
2499 os << emitter.getOrCreateName(forOp.getUpperBound());
2500 os << "; ";
2501 os << emitter.getOrCreateName(forOp.getInductionVar());
2502 os << " += ";
2503 os << emitter.getOrCreateName(forOp.getStep());
2504 os << ")\n";
2505 os << "chess_prepare_for_pipelining\n";
2506 // Try to find the upper bound and step of the for operator.
2507 // If the bounds are found, print them
2508 if (auto [constantLoopBound, tripCount] = getTripCount(forOp);
2509 constantLoopBound) {
2510 auto [constantStep, step] = getStep(forOp);
2511 int64_t lb =
2512 constantStep && step > 0 ? llvm::divideFloorSigned(tripCount, step) : 1;
2513 int64_t ub =
2514 constantStep && step > 0 ? llvm::divideCeilSigned(tripCount, step) : 0;
2515 os << "chess_loop_range(";
2516 os << std::to_string(lb);
2517 os << ", ";
2518 if (constantStep && step > 0)
2519 os << std::to_string(ub);
2520 os << ")\n";
2521 }
2522 os << "{\n";
2523 os.indent();
2524
2525 Region &forRegion = forOp.getRegion();
2526 auto regionOps = forRegion.getOps();
2527
2528 // We skip the trailing yield op because this updates the result variables
2529 // of the for op in the generated code. Instead we update the iterArgs at
2530 // the end of a loop iteration and set the result variables after the for
2531 // loop.
2532 for (auto it = regionOps.begin(); std::next(it) != regionOps.end(); ++it) {
2533 if (bool trailingSemicolon =
2534 !isa<scf::IfOp, scf::ForOp, cf::CondBranchOp>(*it);
2535 failed(emitter.emitOperation(*it, trailingSemicolon)))
2536 return failure();
2537 }
2538
2539 Operation *yieldOp = forRegion.getBlocks().front().getTerminator();
2540 // Copy yield operands into iterArgs at the end of a loop iteration.
2541 for (auto pair : zip(iterArgs, yieldOp->getOperands())) {
2542 BlockArgument iterArg = std::get<0>(pair);
2543 Value operand = std::get<1>(pair);
2544 os << emitter.getOrCreateName(iterArg) << " = "
2545 << emitter.getOrCreateName(operand) << ";\n";
2546 }
2547
2548 os.unindent() << "}";
2549
2550 // Copy iterArgs into results after the for loop.
2551 for (auto pair : zip(results, iterArgs)) {
2552 OpResult result = std::get<0>(pair);
2553 BlockArgument iterArg = std::get<1>(pair);
2554 os << "\n"
2555 << emitter.getOrCreateName(result) << " = "
2556 << emitter.getOrCreateName(iterArg) << ";";
2557 }
2558
2559 return success();
2560}
2561
2562static LogicalResult printOperation(CppEmitter &emitter, scf::IfOp ifOp) {
2563 raw_indented_ostream &os = emitter.ostream();
2564
2565 if (!emitter.shouldDeclareVariablesAtTop())
2566 for (OpResult result : ifOp.getResults())
2567 if (failed(emitter.emitVariableDeclaration(result,
2568 /*trailingSemicolon=*/true)))
2569 return failure();
2570
2571 os << "if (";
2572 if (failed(emitter.emitOperands(*ifOp.getOperation())))
2573 return failure();
2574 os << ") {\n";
2575 os.indent();
2576
2577 Region &thenRegion = ifOp.getThenRegion();
2578 // Note: This prints a superfluous semicolon if the terminating yield op has
2579 // zero results.
2580 for (Operation &op : thenRegion.getOps())
2581 if (failed(emitter.emitOperation(op, /*trailingSemicolon=*/true)))
2582 return failure();
2583
2584 os.unindent() << "}";
2585
2586 if (Region &elseRegion = ifOp.getElseRegion(); !elseRegion.empty()) {
2587 os << " else {\n";
2588 os.indent();
2589
2590 // Note: This prints a superfluous semicolon if the terminating yield op
2591 // has zero results.
2592 for (Operation &op : elseRegion.getOps())
2593 if (failed(emitter.emitOperation(op, /*trailingSemicolon=*/true)))
2594 return failure();
2595
2596 os.unindent() << "}";
2597 }
2598
2599 return success();
2600}
2601
2602static LogicalResult printOperation(CppEmitter &emitter, scf::YieldOp yieldOp) {
2603 raw_ostream &os = emitter.ostream();
2604 Operation &parentOp = *yieldOp.getOperation()->getParentOp();
2605
2606 if (yieldOp.getNumOperands() != parentOp.getNumResults())
2607 return yieldOp.emitError("number of operands does not to match the number "
2608 "of the parent op's results");
2609
2610 if (failed(interleaveWithError(
2611 llvm::zip(parentOp.getResults(), yieldOp.getOperands()),
2612 [&](auto pair) -> LogicalResult {
2613 auto result = std::get<0>(pair);
2614 auto operand = std::get<1>(pair);
2615 os << emitter.getOrCreateName(result) << " = ";
2616
2617 if (!emitter.hasValueInScope(operand))
2618 return yieldOp.emitError("operand value not in scope");
2619 os << emitter.getOrCreateName(operand);
2620 return success();
2621 },
2622 [&] { os << ";\n"; })))
2623 return failure();
2624
2625 return success();
2626}
2627
2628static LogicalResult printOperation(CppEmitter &emitter,
2629 func::ReturnOp returnOp) {
2630 raw_ostream &os = emitter.ostream();
2631 os << "return";
2632 switch (returnOp.getNumOperands()) {
2633 case 0:
2634 return success();
2635 case 1:
2636 os << " " << emitter.getOrCreateName(returnOp.getOperand(0));
2637 return success(emitter.hasValueInScope(returnOp.getOperand(0)));
2638 default:
2639 os << " std::make_tuple(";
2640 if (failed(emitter.emitOperandsAndAttributes(*returnOp.getOperation())))
2641 return failure();
2642 os << ")";
2643 }
2644
2645 return success();
2646}
2647
2648static LogicalResult printOperation(CppEmitter &emitter, ModuleOp moduleOp) {
2649 CppEmitter::Scope scope(emitter);
2650
2651 for (Operation &op : moduleOp)
2652 if (failed(emitter.emitOperation(op, /*trailingSemicolon=*/false)))
2653 return failure();
2654
2655 return success();
2656}
2657
2658static LogicalResult printOperation(CppEmitter &emitter,
2659 func::FuncOp functionOp) {
2660 // We need to declare variables at top if the function has multiple blocks.
2661 if (!emitter.shouldDeclareVariablesAtTop() &&
2662 functionOp.getBlocks().size() > 1)
2663 return functionOp.emitOpError(
2664 "with multiple blocks needs variables declared at top");
2665
2666 CppEmitter::Scope scope(emitter);
2667
2668 // Find any memref dim op in the function, and parse the dimension of each
2669 // dynamic shaped memref
2670 if (failed(parseMemRefDynamicDims(emitter, functionOp)))
2671 return failure();
2672
2673 raw_indented_ostream &os = emitter.ostream();
2674 if (failed(emitter.emitTypes(functionOp.getLoc(),
2675 functionOp.getFunctionType().getResults())))
2676 return failure();
2677 os << " " << functionOp.getName();
2678
2679 os << "(";
2680 if (functionOp.isDeclaration()) {
2681 if (failed(interleaveCommaWithError(
2682 functionOp.getArgumentTypes(), os, [&](Type type) -> LogicalResult {
2683 if (failed(emitter.emitType(functionOp.getLoc(), type)))
2684 return failure();
2685 // If it is a memref argument, we need to check if it has dynamic
2686 // shape. If so, the dimensions have to be printed out
2687 if (auto argType = dyn_cast<MemRefType>(type))
2688 for (unsigned dim = 0; dim < argType.getRank(); ++dim)
2689 if (argType.isDynamicDim(dim))
2690 os << ", size_t";
2691 return success();
2692 })))
2693 return failure();
2694 os << ");\n";
2695 return success();
2696 }
2697
2698 if (failed(interleaveCommaWithError(
2699 functionOp.getArguments(), os,
2700 [&](BlockArgument arg) -> LogicalResult {
2701 if (failed(emitter.emitType(functionOp.getLoc(), arg.getType())))
2702 return failure();
2703 os << " " << emitter.getOrCreateName(arg);
2704 // If it is a memref argument, we need to check if it has dynamic
2705 // shape. If so, the dimensions have to be printed out
2706 if (failed(printMemRefDims(emitter, arg)))
2707 return failure();
2708 return success();
2709 })))
2710 return failure();
2711
2712 os << ") {\n";
2713 os.indent();
2714 if (emitter.shouldDeclareVariablesAtTop()) {
2715 // Declare all variables that hold op results including those from nested
2716 // regions.
2717 WalkResult result =
2718 functionOp.walk<WalkOrder::PreOrder>([&](Operation *op) -> WalkResult {
2719 for (OpResult result : op->getResults()) {
2720 if (failed(emitter.emitVariableDeclaration(
2721 result, /*trailingSemicolon=*/true)))
2722 return {
2723 op->emitError("unable to declare result variable for op")};
2724 }
2725 return WalkResult::advance();
2726 });
2727 if (result.wasInterrupted())
2728 return failure();
2729 }
2730
2731 Region::BlockListType &blocks = functionOp.getBlocks();
2732 // Create label names for basic blocks.
2733 for (Block &block : blocks)
2734 emitter.getOrCreateName(block);
2735
2736 // Declare variables for basic block arguments.
2737 for (auto it = std::next(blocks.begin()); it != blocks.end(); ++it) {
2738 Block &block = *it;
2739 for (BlockArgument &arg : block.getArguments()) {
2740 if (emitter.hasValueInScope(arg))
2741 return functionOp.emitOpError(" block argument #")
2742 << arg.getArgNumber() << " is out of scope";
2743 if (failed(
2744 emitter.emitType(block.getParentOp()->getLoc(), arg.getType())))
2745 return failure();
2746 os << " " << emitter.getOrCreateName(arg) << ";\n";
2747 }
2748 }
2749
2750 for (Block &block : blocks) {
2751 // Only print a label if there is more than one block.
2752 if (blocks.size() > 1)
2753 if (failed(emitter.emitLabel(block)))
2754 return failure();
2755 for (Operation &op : block.getOperations()) {
2756 // When generating code for an scf.if or std.cond_br op no semicolon needs
2757 // to be printed after the closing brace.
2758 // When generating code for an scf.for op, printing a trailing semicolon
2759 // is handled within the printOperation function.
2760 if (bool trailingSemicolon =
2761 !isa<scf::IfOp, scf::ForOp, cf::CondBranchOp>(op);
2762 failed(emitter.emitOperation(
2763 op, /*trailingSemicolon=*/trailingSemicolon)))
2764 return failure();
2765 }
2766 }
2767 os.unindent() << "}\n";
2768
2769 return success();
2770}
2771
2772static LogicalResult printOperation(CppEmitter &emitter,
2773 aievec::MatMulOp matmulOp) {
2774 auto lhs = matmulOp.getLhs();
2775 auto rhs = matmulOp.getRhs();
2776 auto acc = matmulOp.getAcc();
2777
2778 // The sources should have already been emitted
2779 if (!emitter.hasValueInScope(lhs) || !emitter.hasValueInScope(rhs) ||
2780 !emitter.hasValueInScope(acc))
2781 return failure();
2782
2783 auto lhsName = printConversionTo512bit(emitter, lhs);
2784 auto rhsName = printConversionTo512bit(emitter, rhs);
2785
2786 raw_indented_ostream &os = emitter.ostream();
2787
2788 StringRef accName = emitter.getOrCreateName(acc);
2789
2790 auto lhsShape = cast<VectorType>(lhs.getType()).getShape();
2791 auto rhsShape = cast<VectorType>(rhs.getType()).getShape();
2792 os << accName << " = mac_" << lhsShape[0] << "x" << lhsShape[1] << "_"
2793 << rhsShape[0] << "x" << rhsShape[1] << "(";
2794 os << lhsName << ", " << rhsName << ", " << accName << ")";
2795
2796 // Finally, set the name of the result to the accumulator's name
2797 emitter.setName(matmulOp.getResult(), accName);
2798
2799 return success();
2800}
2801
2802CppEmitter::CppEmitter(raw_ostream &os, bool declareVariablesAtTop, bool aie2)
2803 : os(os), declareVariablesAtTop(declareVariablesAtTop), aie2_(aie2) {
2804 valueInScopeCount.push(0);
2805 labelInScopeCount.push(0);
2806}
2807
2808/// Return the existing or a new name for a Value.
2809StringRef CppEmitter::getOrCreateName(Value val, std::string prefix) {
2810 if (!valueMapper.count(val))
2811 valueMapper.insert(val,
2812 formatv("{0}{1}", prefix, ++valueInScopeCount.top()));
2813 return *valueMapper.begin(val);
2814}
2815
2816/// Set the name of a value to an existing name
2817void CppEmitter::setName(Value val, StringRef name) {
2818 valueMapper.insert(val, name.str());
2819}
2820
2821/// Get a new name that is not associated with any value
2822std::string CppEmitter::getNewName(std::string prefix) {
2823 std::string ret = formatv("{0}{1}", prefix, ++valueInScopeCount.top());
2824 return ret;
2825}
2826
2827/// Given a dynamic shaped memref, set its size at position 'index' to
2828// parameter 'result'
2829void CppEmitter::setMemRefDimParam(Value memref, unsigned index,
2830 const std::string &parameter) {
2831 auto p = std::make_pair(memref, index);
2832 assert(!paramIndexMapper.count(p) && "memref dimension already set");
2833 paramIndexMapper[p] = parameter;
2834}
2835
2836/// Return the memref parameteric dimension size at given index
2837StringRef CppEmitter::getMemRefDimParam(Value memref, unsigned index) {
2838 auto p = std::make_pair(memref, index);
2839 assert(paramIndexMapper.count(p) && "memref dimension not found");
2840 return paramIndexMapper[p];
2841}
2842
2843/// Return true if the specified dim of memref has a parameter
2844/// associated with it
2845bool CppEmitter::isMemRefDimParam(Value memref, unsigned index) {
2846 assert([&] {
2847 auto type = llvm::dyn_cast<MemRefType>(memref.getType());
2848 if (!(type && type.isDynamicDim(index))) {
2849 printf("the dimension size at index is not dynamic\n");
2850 return false;
2851 }
2852 return true;
2853 }());
2854
2855 auto p = std::make_pair(memref, index);
2856 return paramIndexMapper.count(p);
2857}
2858
2859/// Return the existing or a new label for a Block.
2860StringRef CppEmitter::getOrCreateName(Block &block, std::string prefix) {
2861 if (!blockMapper.count(&block))
2862 blockMapper.insert(&block,
2863 formatv("{0}{1}", prefix, ++labelInScopeCount.top()));
2864 return *blockMapper.begin(&block);
2865}
2866
2867bool CppEmitter::shouldMapToUnsigned(IntegerType::SignednessSemantics val) {
2868 switch (val) {
2869 case IntegerType::Signless:
2870 case IntegerType::Signed:
2871 return false;
2872 case IntegerType::Unsigned:
2873 return true;
2874 }
2875 llvm::report_fatal_error("Unexpected IntegerType::SignednessSemantics");
2876}
2877
2878bool CppEmitter::hasValueInScope(Value val) { return valueMapper.count(val); }
2879
2880bool CppEmitter::hasBlockLabel(Block &block) {
2881 return blockMapper.count(&block);
2882}
2883
2884// Check whether the int type dense value has a splat value and get the int
2885// value as a string.
2886template <typename ElTy>
2887static std::string getSplatValueOfIntDense(DenseIntElementsAttr dense) {
2888 ElTy splatVal = dense.getSplatValue<ElTy>();
2889 return std::to_string(splatVal);
2890}
2891
2892// Get the first float value of a dense type value as a string.
2893static std::string getSplatValueOfFloatDense(DenseFPElementsAttr dense,
2894 bool isBFloat = false) {
2895 auto apFloat = dense.getSplatValue<APFloat>();
2896 float splatVal = apFloat.convertToFloat();
2897 std::string firstValue = std::to_string(splatVal);
2898
2899 if (apFloat.isPosInfinity())
2900 if (isBFloat)
2901 // TODO: Clean this up; emitting largest finite value in lieu of infinity;
2902 // system headers do not provide a simple way to initialize a bfloat16 to
2903 // infinity.
2904 firstValue = std::to_string(0x1.FEp+127f);
2905 else
2906 firstValue = std::to_string(std::numeric_limits<float>::max());
2907 else if (apFloat.isNegInfinity())
2908 if (isBFloat)
2909 firstValue = std::to_string(-0x1.FEp+127f);
2910 else
2911 firstValue = std::to_string(std::numeric_limits<float>::lowest());
2912 else if (!apFloat.isNonZero())
2913 firstValue = "0";
2914
2915 return firstValue;
2916}
2917
2918LogicalResult CppEmitter::emitAttribute(Location loc, Attribute attr) {
2919 auto printInt = [&](const APInt &val, bool isUnsigned) {
2920 if (val.getBitWidth() == 1)
2921 if (val.getBoolValue())
2922 os << "true";
2923 else
2924 os << "false";
2925 else {
2926 SmallString<128> strValue;
2927 val.toString(strValue, 10, !isUnsigned, false);
2928 os << strValue;
2929 }
2930 };
2931
2932 auto printFloat = [&](const APFloat &val) {
2933 if (val.isFinite()) {
2934 SmallString<128> strValue;
2935 // Use default values of toString except don't truncate zeros.
2936 val.toString(strValue, 0, 0, false);
2937 switch (llvm::APFloatBase::SemanticsToEnum(val.getSemantics())) {
2938 case llvm::APFloatBase::S_IEEEsingle:
2939 os << "(float)";
2940 break;
2941 case llvm::APFloatBase::S_IEEEdouble:
2942 os << "(double)";
2943 break;
2944 default:
2945 break;
2946 }
2947 os << strValue;
2948 } else if (val.isNaN())
2949 os << "NAN";
2950 else if (val.isInfinity()) {
2951 if (val.isNegative())
2952 os << "-";
2953 os << "INFINITY";
2954 }
2955 };
2956
2957 // Print floating point attributes.
2958 if (auto fAttr = llvm::dyn_cast<FloatAttr>(attr)) {
2959 printFloat(fAttr.getValue());
2960 return success();
2961 }
2962
2963 if (auto dense = llvm::dyn_cast<DenseFPElementsAttr>(attr)) {
2964 if (aie2() && dense.isSplat()) {
2965 if (auto vType = llvm::dyn_cast<VectorType>(dense.getType()))
2966 if (auto fType = llvm::dyn_cast<FloatType>(vType.getElementType())) {
2967 unsigned width = fType.getWidth();
2968 std::string splatValue;
2969 if (width == 32)
2970 splatValue = getSplatValueOfFloatDense(dense);
2971 else if (width == 16)
2972 splatValue = getSplatValueOfFloatDense(dense, /*isBFloat*/ true);
2973
2974 if (width == 32 || (width == 16 && getVectorLaneSize(vType) == 32))
2975 if (splatValue == "0") {
2976 os << "broadcast_zero_";
2977 if (failed(emitType(loc, fType)))
2978 return failure();
2979 os << "()";
2980 } else {
2981 os << "broadcast_to_";
2982 if (failed(emitType(loc, vType)))
2983 return failure();
2984 os << "((";
2985 if (failed(emitType(loc, fType)))
2986 return failure();
2987 os << ")";
2988 os << splatValue;
2989 os << ")";
2990 }
2991 else if (width == 16 && getVectorLaneSize(vType) == 16) {
2992 os << "extract_v16bfloat16(";
2993 if (splatValue == "0")
2994 os << "broadcast_zero_bfloat16()";
2995 else {
2996 os << "broadcast_to_v32bfloat16";
2997 os << "((";
2998 if (failed(emitType(loc, fType)))
2999 return failure();
3000 os << ")";
3001 os << splatValue;
3002 os << ")";
3003 }
3004 os << ", 0)";
3005 }
3006 }
3007 // TODO: Deal with multiple dense value case for AIE2.
3008 } else {
3009 os << '{';
3010 interleaveComma(dense, os, [&](const APFloat &val) { printFloat(val); });
3011 os << '}';
3012 }
3013 return success();
3014 }
3015
3016 // Print integer attributes.
3017 if (auto iAttr = llvm::dyn_cast<IntegerAttr>(attr)) {
3018 if (auto iType = llvm::dyn_cast<IntegerType>(iAttr.getType())) {
3019 printInt(iAttr.getValue(), shouldMapToUnsigned(iType.getSignedness()));
3020 return success();
3021 }
3022 if (llvm::dyn_cast<IndexType>(iAttr.getType())) {
3023 printInt(iAttr.getValue(), false);
3024 return success();
3025 }
3026 }
3027
3028 if (auto dense = llvm::dyn_cast<DenseIntElementsAttr>(attr)) {
3029 if (auto tType = llvm::dyn_cast<TensorType>(dense.getType())) {
3030 if (auto iType = llvm::dyn_cast<IntegerType>(tType.getElementType())) {
3031 os << '{';
3032 interleaveComma(dense, os, [&](const APInt &val) {
3033 printInt(val, shouldMapToUnsigned(iType.getSignedness()));
3034 });
3035 os << '}';
3036 return success();
3037 }
3038 if (llvm::dyn_cast<IndexType>(tType.getElementType())) {
3039 os << '{';
3040 interleaveComma(dense, os,
3041 [&](const APInt &val) { printInt(val, false); });
3042 os << '}';
3043 return success();
3044 }
3045 }
3046
3047 if (auto vType = llvm::dyn_cast<VectorType>(dense.getType())) {
3048 if (auto iType = llvm::dyn_cast<IntegerType>(vType.getElementType())) {
3049 unsigned width = iType.getWidth();
3050 if (llvm::all_of(dense, [](const APInt &val) { return val == 0; })) {
3051 if (aie2()) {
3052 if (width * getVectorLaneSize(vType) == 1024) {
3053 os << "concat(broadcast_zero_s" << width << "(), broadcast_zero_s"
3054 << width << "())";
3055 return success();
3056 }
3057 os << "broadcast_zero_s";
3058 os << width;
3059 } else {
3060 os << "null_";
3061 if (failed(emitType(loc, vType)))
3062 return failure();
3063 }
3064 os << "()";
3065 return success();
3066 }
3067
3068 if (aie2() && dense.isSplat()) {
3069 std::string splatValue;
3070 if (width == 32)
3071 splatValue = getSplatValueOfIntDense<int32_t>(dense);
3072 else if (width == 16)
3073 splatValue = getSplatValueOfIntDense<int16_t>(dense);
3074 else if (width == 8)
3075 splatValue = getSplatValueOfIntDense<int8_t>(dense);
3076 os << "broadcast_to_";
3077 if (failed(emitType(loc, vType)))
3078 return failure();
3079 os << "((";
3080 if (failed(emitType(loc, iType)))
3081 return failure();
3082 os << ")";
3083 os << splatValue;
3084 os << ")";
3085 // TODO: Handle multiple dense value case in AIE2.
3086 } else {
3087 os << '{';
3088 interleaveComma(dense, os, [&](const APInt &val) {
3089 printInt(val, shouldMapToUnsigned(iType.getSignedness()));
3090 });
3091 os << '}';
3092 }
3093 return success();
3094 }
3095 if (llvm::dyn_cast<IndexType>(vType.getElementType())) {
3096 os << '{';
3097 interleaveComma(dense, os,
3098 [&](const APInt &val) { printInt(val, false); });
3099 os << '}';
3100 return success();
3101 }
3102 }
3103 }
3104
3105 // Print opaque attributes.
3106 if (auto oAttr = llvm::dyn_cast<emitc::OpaqueAttr>(attr)) {
3107 os << oAttr.getValue();
3108 return success();
3109 }
3110
3111 // Print symbolic reference attributes.
3112 if (auto sAttr = llvm::dyn_cast<SymbolRefAttr>(attr)) {
3113 if (sAttr.getNestedReferences().size() > 1)
3114 return emitError(loc, "attribute has more than 1 nested reference");
3115 os << sAttr.getRootReference().getValue();
3116 return success();
3117 }
3118
3119 // Print type attributes.
3120 if (auto type = llvm::dyn_cast<TypeAttr>(attr))
3121 return emitType(loc, type.getValue());
3122
3123 return emitError(loc, "cannot emit attribute of type ") << attr;
3124}
3125
3126LogicalResult CppEmitter::emitOperands(Operation &op) {
3127 auto emitOperandName = [&](Value result) -> LogicalResult {
3128 if (!hasValueInScope(result))
3129 return op.emitOpError() << "operand value not in scope";
3130 os << getOrCreateName(result);
3131 return success();
3132 };
3133 return interleaveCommaWithError(op.getOperands(), os, emitOperandName);
3134}
3135
3136LogicalResult
3137CppEmitter::emitOperandsAndAttributes(Operation &op,
3138 ArrayRef<StringRef> exclude) {
3139 if (failed(emitOperands(op)))
3140 return failure();
3141 // Insert comma in between operands and non-filtered attributes if needed.
3142 if (op.getNumOperands() > 0)
3143 for (NamedAttribute attr : op.getAttrs())
3144 if (!is_contained(exclude, attr.getName().strref())) {
3145 os << ", ";
3146 break;
3147 }
3148 // Emit attributes.
3149 auto emitNamedAttribute = [&](NamedAttribute attr) -> LogicalResult {
3150 if (is_contained(exclude, attr.getName().strref()))
3151 return success();
3152 os << "/* " << attr.getName().getValue() << " */";
3153 if (failed(emitAttribute(op.getLoc(), attr.getValue())))
3154 return failure();
3155 return success();
3156 };
3157
3158 return interleaveCommaWithError(op.getAttrs(), os, emitNamedAttribute);
3159}
3160
3161LogicalResult CppEmitter::emitVariableAssignment(OpResult result) {
3162 if (!hasValueInScope(result)) {
3163 return result.getDefiningOp()->emitOpError(
3164 "result variable for the operation has not been declared");
3165 }
3166 os << getOrCreateName(result) << " = ";
3167
3168 return success();
3169}
3170
3171LogicalResult CppEmitter::emitVariableDeclaration(OpResult result,
3172 bool trailingSemicolon,
3173 bool isAcc) {
3174 if (hasValueInScope(result))
3175 return result.getDefiningOp()->emitError(
3176 "result variable for the operation already declared");
3177 if (failed(
3178 emitType(result.getOwner()->getLoc(), result.getType(), true, isAcc)))
3179 return failure();
3180 os << " " << getOrCreateName(result);
3181 if (trailingSemicolon)
3182 os << ";\n";
3183
3184 return success();
3185}
3186
3187LogicalResult CppEmitter::emitAssignPrefix(Operation &op, bool isAcc) {
3188 switch (op.getNumResults()) {
3189 case 0:
3190 break;
3191 case 1: {
3192 OpResult result = op.getResult(0);
3193 if (shouldDeclareVariablesAtTop()) {
3194 if (failed(emitVariableAssignment(result)))
3195 return failure();
3196 } else {
3197 if (failed(emitVariableDeclaration(result, /*trailingSemicolon=*/false,
3198 isAcc)))
3199 return failure();
3200 os << " = ";
3201 }
3202 break;
3203 }
3204 default:
3205 if (!shouldDeclareVariablesAtTop())
3206 for (OpResult result : op.getResults())
3207 if (failed(emitVariableDeclaration(result, /*trailingSemicolon=*/true)))
3208 return failure();
3209
3210 os << "std::tie(";
3211 interleaveComma(op.getResults(), os,
3212 [&](Value result) { os << getOrCreateName(result); });
3213 os << ") = ";
3214 }
3215 return success();
3216}
3217
3218LogicalResult CppEmitter::emitLabel(Block &block) {
3219 if (!hasBlockLabel(block))
3220 return block.getParentOp()->emitError("label for block not found");
3221 // FIXME: Add feature in `raw_indented_ostream` to ignore indent for block
3222 // label instead of using `getOStream`.
3223 os.getOStream() << getOrCreateName(block) << ":\n";
3224 return success();
3225}
3226
3227LogicalResult CppEmitter::emitOperation(Operation &op, bool trailingSemicolon) {
3228 // Some operations in AIE become nops. Check if this operation must be skipped
3229 // from codegen
3230 if (skippedOp(&op, *this))
3231 return success();
3232
3233 LogicalResult status =
3234 TypeSwitch<Operation *, LogicalResult>(&op)
3235 // EmitC ops.
3236 .Case<emitc::ApplyOp, emitc::CallOpaqueOp, emitc::ConstantOp>(
3237 [&](auto op) { return printOperation(*this, op); })
3238 .Case<emitc::IncludeOp>([&](auto op) {
3239 if (StringRef name = op.getInclude(); !includeNames.count(name)) {
3240 includeNames.insert(name);
3241 return printOperation(*this, op);
3242 }
3243 return success();
3244 })
3245 // SCF ops.
3246 .Case<scf::ForOp, scf::IfOp, scf::YieldOp>(
3247 [&](auto op) { return printOperation(*this, op); })
3248 // Standard ops.
3249 .Case<cf::BranchOp, func::CallOp, cf::CondBranchOp, func::FuncOp,
3250 ModuleOp, func::ReturnOp>(
3251 [&](auto op) { return printOperation(*this, op); })
3252 // Arith ops.
3253 .Case<arith::ConstantOp>(
3254 [&](auto op) { return printOperation(*this, op); })
3255 // Extra ops added for AIE
3256 // Arith ops.
3257 .Case<arith::AddIOp>(
3258 [&](auto op) { return printOperation<arith::AddIOp>(*this, op); })
3259 .Case<arith::AddFOp>(
3260 [&](auto op) { return printOperation<arith::AddFOp>(*this, op); })
3261 .Case<arith::MulIOp>(
3262 [&](auto op) { return printOperation<arith::MulIOp>(*this, op); })
3263 .Case<arith::MulFOp>(
3264 [&](auto op) { return printOperation<arith::MulFOp>(*this, op); })
3265 .Case<arith::SubIOp>(
3266 [&](auto op) { return printOperation<arith::SubIOp>(*this, op); })
3267 .Case<arith::SubFOp>(
3268 [&](auto op) { return printOperation<arith::SubFOp>(*this, op); })
3269 .Case<arith::DivSIOp>([&](auto op) {
3270 return printOperation<arith::DivSIOp>(*this, op);
3271 })
3272 .Case<arith::DivUIOp>([&](auto op) {
3273 return printOperation<arith::DivUIOp>(*this, op);
3274 })
3275 .Case<arith::DivFOp>(
3276 [&](auto op) { return printOperation<arith::DivFOp>(*this, op); })
3277 .Case<arith::RemSIOp>([&](auto op) {
3278 return printOperation<arith::RemSIOp>(*this, op);
3279 })
3280 .Case<arith::CmpIOp>(
3281 [&](auto op) { return printOperation<arith::CmpIOp>(*this, op); })
3282 .Case<arith::SelectOp>(
3283 [&](auto op) { return printOperation(*this, op); })
3284 // Vector ops.
3285 .Case<vector::TransferWriteOp>(
3286 [&](auto op) { return printOperation(*this, op); })
3287 // Memref ops.
3288 .Case<memref::StoreOp, memref::ExpandShapeOp,
3289 memref::CollapseShapeOp>(
3290 [&](auto op) { return printOperation(*this, op); })
3291 // AievecAie1 ops
3292 .Case<aievec::aie1::AddOp, aievec::aie1::SubOp, aievec::aie1::FMAOp,
3293 aievec::aie1::MulOp, aievec::aie1::SelectOp,
3294 aievec::aie1::ExtOp>(
3295 [&](auto op) { return printOperation(*this, op); })
3296 // Aievec ops
3297 .Case<AddElemOp, ConcatOp, ExtOp, PackOp, SRSOp, SubElemOp, UPDOp,
3298 UPSOp, FMAElemOp, MulElemOp, BroadcastOp, BroadcastScalarOp,
3299 MulConvOp, FMAConvOp, ShiftOp, ShuffleOp, CastOp, MinOp, MaxOp,
3300 NegOp, CmpOp, SelOp, ExtElemOp, BxorOp, BnegOp, BandOp, BorOp,
3301 UnpackOp, MatMulOp, LegacyShuffleOp>(
3302 [&](auto op) { return printOperation(*this, op); })
3303 .Default([&](Operation *) {
3304 return op.emitOpError("unable to find printer for op");
3305 });
3306
3307 if (failed(status))
3308 return failure();
3309 os << (trailingSemicolon ? ";\n" : "\n");
3310
3311 return success();
3312}
3313
3314std::optional<std::string>
3315CppEmitter::genCppTypeName(Type type, bool stdintType, bool isAcc) {
3316 std::stringstream ss;
3317 if (auto iType = dyn_cast<IntegerType>(type)) {
3318 switch (iType.getWidth()) {
3319 case 1:
3320 return "bool";
3321 case 8:
3322 case 16:
3323 case 32:
3324 case 64:
3325 if (shouldMapToUnsigned(iType.getSignedness()))
3326 ss << "uint" << iType.getWidth() << (stdintType ? "_t" : "");
3327 else
3328 ss << "int" << iType.getWidth() << (stdintType ? "_t" : "");
3329 return ss.str();
3330 case 48:
3331 case 80:
3332 ss << "acc" << iType.getWidth();
3333 return ss.str();
3334 default:
3335 return {};
3336 }
3337 }
3338 if (auto fType = dyn_cast<FloatType>(type)) {
3339 switch (fType.getWidth()) {
3340 case 16:
3341 return "bfloat16";
3342 case 32:
3343 return "float";
3344 case 64:
3345 return "double";
3346 default:
3347 return {};
3348 }
3349 }
3350 if (auto iType = dyn_cast<IndexType>(type))
3351 return "size_t";
3352
3353 if (auto tType = dyn_cast<TensorType>(type)) {
3354 if (!tType.hasRank())
3355 return {};
3356 if (!tType.hasStaticShape())
3357 return {};
3358 ss << "Tensor<";
3359 auto nestedTypeName = genCppTypeName(tType.getElementType());
3360 if (!nestedTypeName)
3361 return {};
3362 ss << *nestedTypeName;
3363 auto shape = tType.getShape();
3364 for (auto dimSize : shape) {
3365 ss << ", ";
3366 ss << dimSize;
3367 }
3368 ss << ">";
3369 return ss.str();
3370 }
3371 if (auto tType = dyn_cast<TupleType>(type)) {
3372 ss << "std::tuple<";
3373 bool itrleaveFailed = false;
3374 llvm::interleave(
3375 tType.getTypes(),
3376 [&](Type type) {
3377 auto optTyNameStr = genCppTypeName(type);
3378 if (optTyNameStr)
3379 ss << *optTyNameStr;
3380 else
3381 itrleaveFailed = true;
3382 },
3383 [&]() { ss << ", "; });
3384 ss << ">";
3385 if (!itrleaveFailed)
3386 return ss.str();
3387 return {};
3388 }
3389 if (auto oType = dyn_cast<emitc::OpaqueType>(type)) {
3390 ss << oType.getValue().str();
3391 return ss.str();
3392 }
3393 // Types added for AIE
3394 // MemRefType: printed as 'eltType'*
3395 if (auto tType = dyn_cast<MemRefType>(type)) {
3396 auto elemTyStrOpt = genCppTypeName(tType.getElementType());
3397 if (!elemTyStrOpt)
3398 return {};
3399 ss << *elemTyStrOpt << " * restrict";
3400 return ss.str();
3401 }
3402 // VectorType: printed as v'lane''eltType'
3403 if (auto tType = dyn_cast<VectorType>(type)) {
3404 Type eltType = tType.getElementType();
3405 // Flatten multidimensional vectors
3406 auto vShape = tType.getShape();
3407 int64_t numElems = std::accumulate(vShape.begin(), vShape.end(), 1,
3408 std::multiplies<int64_t>());
3409 ss << "v" << std::to_string(numElems);
3410
3411 int64_t iElTyBitWidth = 0;
3412 auto iElTy = dyn_cast<IntegerType>(eltType);
3413 if (iElTy)
3414 iElTyBitWidth = iElTy.getWidth();
3415 if (aie2() && (isAcc || iElTyBitWidth == 64)) {
3416 if (iElTy) {
3417 // AIE2 has `ups_to_v16acc32`, `ups_to_v16acc64`, `ups_to_v32acc32`
3418 // intrinsics
3419 if ((numElems == 16 && iElTyBitWidth == 64) ||
3420 (numElems == 32 && iElTyBitWidth == 32) ||
3421 (numElems == 16 && iElTyBitWidth == 32)) {
3422 ss << "acc" << iElTyBitWidth;
3423 return ss.str();
3424 }
3425 return {};
3426 }
3427 if (isa<FloatType>(eltType)) {
3428 // AIE2 only has a `ups_to_v16accfloat` intrinsic
3429 ss << "accfloat";
3430 return ss.str();
3431 }
3432 }
3433 auto elTyNameOpt = genCppTypeName(eltType, false);
3434 if (!elTyNameOpt)
3435 return {};
3436 ss << *elTyNameOpt;
3437 return ss.str();
3438 }
3439 return {};
3440}
3441
3442LogicalResult CppEmitter::emitType(Location loc, Type type, bool stdintType,
3443 bool isAcc) {
3444 auto typeName = genCppTypeName(type, stdintType, isAcc);
3445 if (!typeName)
3446 return emitError(loc, "cannot emit type ") << type;
3447 os << *typeName;
3448 return success();
3449}
3450
3451LogicalResult CppEmitter::emitTypes(Location loc, ArrayRef<Type> types) {
3452 switch (types.size()) {
3453 case 0:
3454 os << "void";
3455 return success();
3456 case 1:
3457 return emitType(loc, types.front());
3458 default:
3459 return emitTupleType(loc, types);
3460 }
3461}
3462
3463LogicalResult CppEmitter::emitTupleType(Location loc, ArrayRef<Type> types) {
3464 os << "std::tuple<";
3465 if (failed(interleaveCommaWithError(
3466 types, os, [&](Type type) { return emitType(loc, type); })))
3467 return failure();
3468 os << ">";
3469 return success();
3470}
3471
3472LogicalResult aievec::translateAIEVecToCpp(Operation *op, bool aie2,
3473 raw_ostream &os) {
3474 CppEmitter emitter(os, false, aie2);
3475 return emitter.emitOperation(*op, /*trailingSemicolon=*/false);
3476}
LogicalResult interleaveCommaWithError(const Container &c, raw_ostream &os, UnaryFunctor eachFn)
LogicalResult interleaveWithError(ForwardIterator begin, ForwardIterator end, UnaryFunctor eachFn, NullaryFunctor betweenFn)
Convenience functions to produce interleaved output with functions returning a LogicalResult.
std::shared_ptr< Value > value()
Definition cxxopts.hpp:1026
PathEndPoint src
mlir::LogicalResult translateAIEVecToCpp(mlir::Operation *op, bool aie2, mlir::raw_ostream &os)
Translates the AIE vector dialect MLIR to C++ code.
int32_t getVectorSizeInBits(mlir::VectorType type)
Definition AIEVecUtils.h:66
unsigned getVectorLaneSize(mlir::VectorType type)
Definition AIEVecUtils.h:55
int32_t getElementSizeInBits(mlir::VectorType type)
Definition AIEVecUtils.h:49