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 and assume_alignement op
254 .Case<memref::DimOp, memref::AssumeAlignmentOp>(
255 [](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 os << "sel(";
2128 os << emitter.getOrCreateName(rhs);
2129 os << ", ";
2130 os << emitter.getOrCreateName(lhs);
2131 os << ", ";
2132 os << emitter.getOrCreateName(sel);
2133 os << ")";
2134
2135 return success();
2136}
2137
2138// Generate the extract elem intrinsic
2139static LogicalResult printOperation(CppEmitter &emitter,
2140 aievec::ExtElemOp extElemOp) {
2141 Value source = extElemOp.getSource();
2142 Value index = extElemOp.getIndex();
2143
2144 raw_indented_ostream &os = emitter.ostream();
2145
2146 // Generate the initialization for the result
2147 if (failed(emitter.emitAssignPrefix(*extElemOp)))
2148 return failure();
2149
2150 // source should have already been emitted
2151 if (!emitter.hasValueInScope(source))
2152 return failure();
2153
2154 os << "extract_elem";
2155 os << "(";
2156 // Print the source and index
2157 os << emitter.getOrCreateName(source);
2158 os << ", ";
2159 os << emitter.getOrCreateName(index);
2160 os << ")";
2161
2162 return success();
2163}
2164
2165// Generate the transfer write op
2166static LogicalResult printOperation(CppEmitter &emitter,
2167 vector::TransferWriteOp writeOp) {
2168 Value source = writeOp.getSource();
2169 Value vector = writeOp.getVector();
2170
2171 // If the aray, or the vector being outputted is not already emitted,
2172 // error out
2173 if (!emitter.hasValueInScope(source) || !emitter.hasValueInScope(vector))
2174 return failure();
2175
2176 // Construct the access expression using memref shape and indices
2177 std::string access;
2178 auto indices = writeOp.getIndices();
2179 if (failed(createLinearizedAccess(emitter, source, indices, access)))
2180 return failure();
2181
2182 raw_indented_ostream &os = emitter.ostream();
2183
2184 os << "*(";
2185 if (failed(emitter.emitType(writeOp->getLoc(), vector.getType())))
2186 return failure();
2187 os << " *)";
2188 os << "(";
2189 os << emitter.getOrCreateName(source);
2190 if (!access.empty())
2191 os << " + " << access;
2192 os << ")";
2193 os << " = ";
2194 os << emitter.getOrCreateName(vector);
2195
2196 return success();
2197}
2198
2199// Generate the memref store op
2200static LogicalResult printOperation(CppEmitter &emitter,
2201 memref::StoreOp storeOp) {
2202 Value value = storeOp.getValue();
2203 Value memref = storeOp.getMemref();
2204
2205 // If the value, or the memref being outputted is not already emitted,
2206 // error out
2207 if (!emitter.hasValueInScope(value) || !emitter.hasValueInScope(memref))
2208 return failure();
2209
2210 raw_indented_ostream &os = emitter.ostream();
2211
2212 os << "*(";
2213 if (failed(emitter.emitType(
2214 storeOp->getLoc(),
2215 cast<MemRefType>(memref.getType()).getElementType())))
2216 return failure();
2217 os << " *)";
2218 os << emitter.getOrCreateName(memref);
2219 os << " = ";
2220 os << emitter.getOrCreateName(value);
2221
2222 return success();
2223}
2224
2225// Print an operation by forwarding the value to the next op
2226template <typename OpTy>
2227static LogicalResult printValueForwardOperation(CppEmitter &emitter, OpTy op) {
2228 Value source = op.getSrc();
2229
2230 // If the memref being outputted is not already emitted,
2231 // error out
2232 if (!emitter.hasValueInScope(source))
2233 return failure();
2234
2235 if (failed(emitter.emitAssignPrefix(*op)))
2236 return failure();
2237
2238 raw_indented_ostream &os = emitter.ostream();
2239 os << emitter.getOrCreateName(source);
2240
2241 return success();
2242}
2243
2244// Print an expand shape by forwarding the value to the next op
2245static LogicalResult printOperation(CppEmitter &emitter,
2246 memref::ExpandShapeOp expandShapeOp) {
2247 return printValueForwardOperation<memref::ExpandShapeOp>(emitter,
2248 expandShapeOp);
2249}
2250
2251// Print a collapse shape by forwarding the value to the next op
2252static LogicalResult printOperation(CppEmitter &emitter,
2253 memref::CollapseShapeOp collapseShapeOp) {
2254 return printValueForwardOperation<memref::CollapseShapeOp>(emitter,
2255 collapseShapeOp);
2256}
2257
2258static LogicalResult printConstantOp(CppEmitter &emitter, Operation *operation,
2259 Attribute value) {
2260 OpResult result = operation->getResult(0);
2261
2262 // Only emit an assignment as the variable was already declared when printing
2263 // the FuncOp.
2264 if (emitter.shouldDeclareVariablesAtTop()) {
2265 // Skip the assignment if the emitc.constant has no value.
2266 if (auto oAttr = llvm::dyn_cast<emitc::OpaqueAttr>(value))
2267 if (oAttr.getValue().empty())
2268 return success();
2269
2270 if (failed(emitter.emitVariableAssignment(result)))
2271 return failure();
2272 return emitter.emitAttribute(operation->getLoc(), value);
2273 }
2274
2275 // Emit a variable declaration for an emitc.constant op without value.
2276 if (auto oAttr = llvm::dyn_cast<emitc::OpaqueAttr>(value))
2277 if (oAttr.getValue().empty())
2278 // The semicolon gets printed by the emitOperation function.
2279 return emitter.emitVariableDeclaration(result,
2280 /*trailingSemicolon=*/false);
2281
2282 // Emit a variable declaration.
2283 if (failed(emitter.emitAssignPrefix(*operation)))
2284 return failure();
2285 return emitter.emitAttribute(operation->getLoc(), value);
2286}
2287
2288static LogicalResult printOperation(CppEmitter &emitter,
2289 emitc::ConstantOp constantOp) {
2290 Operation *operation = constantOp.getOperation();
2291 Attribute value = constantOp.getValue();
2292 return printConstantOp(emitter, operation, value);
2293}
2294
2295static LogicalResult printOperation(CppEmitter &emitter,
2296 arith::ConstantOp constantOp) {
2297 Operation *operation = constantOp.getOperation();
2298 Attribute value = constantOp.getValue();
2299 return printConstantOp(emitter, operation, value);
2300}
2301
2302static LogicalResult printOperation(CppEmitter &emitter,
2303 cf::BranchOp branchOp) {
2304 raw_ostream &os = emitter.ostream();
2305 Block &successor = *branchOp.getSuccessor();
2306
2307 for (auto pair : zip(branchOp.getOperands(), successor.getArguments())) {
2308 Value &operand = std::get<0>(pair);
2309 BlockArgument &argument = std::get<1>(pair);
2310 os << emitter.getOrCreateName(argument) << " = "
2311 << emitter.getOrCreateName(operand) << ";\n";
2312 }
2313
2314 os << "goto ";
2315 if (!emitter.hasBlockLabel(successor))
2316 return branchOp.emitOpError("unable to find label for successor block");
2317 os << emitter.getOrCreateName(successor);
2318 return success();
2319}
2320
2321static LogicalResult printOperation(CppEmitter &emitter,
2322 cf::CondBranchOp condBranchOp) {
2323 raw_indented_ostream &os = emitter.ostream();
2324 Block &trueSuccessor = *condBranchOp.getTrueDest();
2325 Block &falseSuccessor = *condBranchOp.getFalseDest();
2326
2327 os << "if (" << emitter.getOrCreateName(condBranchOp.getCondition())
2328 << ") {\n";
2329
2330 os.indent();
2331
2332 // If condition is true.
2333 for (auto pair :
2334 zip(condBranchOp.getTrueOperands(), trueSuccessor.getArguments())) {
2335 Value &operand = std::get<0>(pair);
2336 BlockArgument &argument = std::get<1>(pair);
2337 os << emitter.getOrCreateName(argument) << " = "
2338 << emitter.getOrCreateName(operand) << ";\n";
2339 }
2340
2341 os << "goto ";
2342 if (!emitter.hasBlockLabel(trueSuccessor))
2343 return condBranchOp.emitOpError("unable to find label for successor block");
2344 os << emitter.getOrCreateName(trueSuccessor) << ";\n";
2345 os.unindent() << "} else {\n";
2346 os.indent();
2347 // If condition is false.
2348 for (auto pair :
2349 zip(condBranchOp.getFalseOperands(), falseSuccessor.getArguments())) {
2350 Value &operand = std::get<0>(pair);
2351 BlockArgument &argument = std::get<1>(pair);
2352 os << emitter.getOrCreateName(argument) << " = "
2353 << emitter.getOrCreateName(operand) << ";\n";
2354 }
2355
2356 os << "goto ";
2357 if (!emitter.hasBlockLabel(falseSuccessor))
2358 return condBranchOp.emitOpError()
2359 << "unable to find label for successor block";
2360 os << emitter.getOrCreateName(falseSuccessor) << ";\n";
2361 os.unindent() << "}";
2362
2363 return success();
2364}
2365
2366static LogicalResult printOperation(CppEmitter &emitter, func::CallOp callOp) {
2367 if (failed(emitter.emitAssignPrefix(*callOp.getOperation())))
2368 return failure();
2369
2370 raw_ostream &os = emitter.ostream();
2371 os << callOp.getCallee() << "(";
2372 if (failed(emitter.emitOperands(*callOp.getOperation())))
2373 return failure();
2374 os << ")";
2375
2376 return success();
2377}
2378
2379static LogicalResult printOperation(CppEmitter &emitter,
2380 emitc::CallOpaqueOp callOp) {
2381 raw_ostream &os = emitter.ostream();
2382 Operation &op = *callOp.getOperation();
2383 if (callOp.getCallee() == "getTanhBf16" ||
2384 callOp.getCallee() == "getSqrtBf16" ||
2385 callOp.getCallee() == "getRsqrtBf16" ||
2386 callOp.getCallee() == "getErfBf16" || callOp.getCallee() == "getAbs" ||
2387 callOp.getCallee() == "getSigmoidBf16" ||
2388 callOp.getCallee() == "getCeilBf16" ||
2389 callOp.getCallee() == "getFloorBf16") {
2390 if (failed(emitter.emitAssignPrefix(op, /*isAcc*/ false)))
2391 return failure();
2392 } else if (failed(emitter.emitAssignPrefix(op, /*isAcc*/ true)))
2393 return failure();
2394
2395 os << callOp.getCallee();
2396
2397 auto emitArgs = [&](Attribute attr) -> LogicalResult {
2398 // Index attributes are treated specially as operand index.
2399 if (auto t = llvm::dyn_cast<IntegerAttr>(attr))
2400 if (t.getType().isIndex()) {
2401 int64_t idx = t.getInt();
2402 if (idx < 0 || idx >= op.getNumOperands())
2403 return op.emitOpError("invalid operand index");
2404 if (!emitter.hasValueInScope(op.getOperand(idx)))
2405 return op.emitOpError("operand ")
2406 << idx << "'s value not defined in scope";
2407 os << emitter.getOrCreateName(op.getOperand(idx));
2408 return success();
2409 }
2410 if (failed(emitter.emitAttribute(op.getLoc(), attr)))
2411 return failure();
2412
2413 return success();
2414 };
2415
2416 if (callOp.getTemplateArgs()) {
2417 os << "<";
2418 if (failed(
2419 interleaveCommaWithError(*callOp.getTemplateArgs(), os, emitArgs)))
2420 return failure();
2421 os << ">";
2422 }
2423
2424 os << "(";
2425
2426 LogicalResult emittedArgs =
2427 callOp.getArgs()
2428 ? interleaveCommaWithError(*callOp.getArgs(), os, emitArgs)
2429 : emitter.emitOperands(op);
2430 if (failed(emittedArgs))
2431 return failure();
2432 os << ")";
2433
2434 return success();
2435}
2436
2437static LogicalResult printOperation(CppEmitter &emitter,
2438 emitc::ApplyOp applyOp) {
2439 raw_ostream &os = emitter.ostream();
2440
2441 if (Operation &op = *applyOp.getOperation();
2442 failed(emitter.emitAssignPrefix(op)))
2443 return failure();
2444 os << applyOp.getApplicableOperator();
2445 os << emitter.getOrCreateName(applyOp.getOperand());
2446
2447 return success();
2448}
2449
2450static LogicalResult printOperation(CppEmitter &emitter,
2451 emitc::IncludeOp includeOp) {
2452 raw_ostream &os = emitter.ostream();
2453
2454 os << "#include ";
2455 if (includeOp.getIsStandardInclude())
2456 os << "<" << includeOp.getInclude() << ">";
2457 else
2458 os << "\"" << includeOp.getInclude() << "\"";
2459
2460 return success();
2461}
2462
2463static LogicalResult printOperation(CppEmitter &emitter, scf::ForOp forOp) {
2464 raw_indented_ostream &os = emitter.ostream();
2465
2466 OperandRange operands = forOp.getInitArgs();
2467 Block::BlockArgListType iterArgs = forOp.getRegionIterArgs();
2468 Operation::result_range results = forOp.getResults();
2469
2470 if (!emitter.shouldDeclareVariablesAtTop())
2471 for (OpResult result : results)
2472 if (failed(emitter.emitVariableDeclaration(result,
2473 /*trailingSemicolon=*/true)))
2474 return failure();
2475
2476 for (auto pair : zip(iterArgs, operands)) {
2477 if (failed(emitter.emitType(forOp.getLoc(), std::get<0>(pair).getType())))
2478 return failure();
2479 os << " " << emitter.getOrCreateName(std::get<0>(pair)) << " = ";
2480 os << emitter.getOrCreateName(std::get<1>(pair)) << ";";
2481 os << "\n";
2482 }
2483
2484 os << "for (";
2485 if (failed(
2486 emitter.emitType(forOp.getLoc(), forOp.getInductionVar().getType())))
2487 return failure();
2488
2489 os << " ";
2490 os << emitter.getOrCreateName(forOp.getInductionVar());
2491 os << " = ";
2492 os << emitter.getOrCreateName(forOp.getLowerBound());
2493 os << "; ";
2494 os << emitter.getOrCreateName(forOp.getInductionVar());
2495 os << " < ";
2496 os << emitter.getOrCreateName(forOp.getUpperBound());
2497 os << "; ";
2498 os << emitter.getOrCreateName(forOp.getInductionVar());
2499 os << " += ";
2500 os << emitter.getOrCreateName(forOp.getStep());
2501 os << ")\n";
2502 os << "chess_prepare_for_pipelining\n";
2503 // Try to find the upper bound and step of the for operator.
2504 // If the bounds are found, print them
2505 if (auto [constantLoopBound, tripCount] = getTripCount(forOp);
2506 constantLoopBound) {
2507 auto [constantStep, step] = getStep(forOp);
2508 int64_t lb =
2509 constantStep && step > 0 ? llvm::divideFloorSigned(tripCount, step) : 1;
2510 int64_t ub =
2511 constantStep && step > 0 ? llvm::divideCeilSigned(tripCount, step) : 0;
2512 os << "chess_loop_range(";
2513 os << std::to_string(lb);
2514 os << ", ";
2515 if (constantStep && step > 0)
2516 os << std::to_string(ub);
2517 os << ")\n";
2518 }
2519 os << "{\n";
2520 os.indent();
2521
2522 Region &forRegion = forOp.getRegion();
2523 auto regionOps = forRegion.getOps();
2524
2525 // We skip the trailing yield op because this updates the result variables
2526 // of the for op in the generated code. Instead we update the iterArgs at
2527 // the end of a loop iteration and set the result variables after the for
2528 // loop.
2529 for (auto it = regionOps.begin(); std::next(it) != regionOps.end(); ++it) {
2530 if (bool trailingSemicolon =
2531 !isa<scf::IfOp, scf::ForOp, cf::CondBranchOp>(*it);
2532 failed(emitter.emitOperation(*it, trailingSemicolon)))
2533 return failure();
2534 }
2535
2536 Operation *yieldOp = forRegion.getBlocks().front().getTerminator();
2537 // Copy yield operands into iterArgs at the end of a loop iteration.
2538 for (auto pair : zip(iterArgs, yieldOp->getOperands())) {
2539 BlockArgument iterArg = std::get<0>(pair);
2540 Value operand = std::get<1>(pair);
2541 os << emitter.getOrCreateName(iterArg) << " = "
2542 << emitter.getOrCreateName(operand) << ";\n";
2543 }
2544
2545 os.unindent() << "}";
2546
2547 // Copy iterArgs into results after the for loop.
2548 for (auto pair : zip(results, iterArgs)) {
2549 OpResult result = std::get<0>(pair);
2550 BlockArgument iterArg = std::get<1>(pair);
2551 os << "\n"
2552 << emitter.getOrCreateName(result) << " = "
2553 << emitter.getOrCreateName(iterArg) << ";";
2554 }
2555
2556 return success();
2557}
2558
2559static LogicalResult printOperation(CppEmitter &emitter, scf::IfOp ifOp) {
2560 raw_indented_ostream &os = emitter.ostream();
2561
2562 if (!emitter.shouldDeclareVariablesAtTop())
2563 for (OpResult result : ifOp.getResults())
2564 if (failed(emitter.emitVariableDeclaration(result,
2565 /*trailingSemicolon=*/true)))
2566 return failure();
2567
2568 os << "if (";
2569 if (failed(emitter.emitOperands(*ifOp.getOperation())))
2570 return failure();
2571 os << ") {\n";
2572 os.indent();
2573
2574 Region &thenRegion = ifOp.getThenRegion();
2575 // Note: This prints a superfluous semicolon if the terminating yield op has
2576 // zero results.
2577 for (Operation &op : thenRegion.getOps())
2578 if (failed(emitter.emitOperation(op, /*trailingSemicolon=*/true)))
2579 return failure();
2580
2581 os.unindent() << "}";
2582
2583 if (Region &elseRegion = ifOp.getElseRegion(); !elseRegion.empty()) {
2584 os << " else {\n";
2585 os.indent();
2586
2587 // Note: This prints a superfluous semicolon if the terminating yield op
2588 // has zero results.
2589 for (Operation &op : elseRegion.getOps())
2590 if (failed(emitter.emitOperation(op, /*trailingSemicolon=*/true)))
2591 return failure();
2592
2593 os.unindent() << "}";
2594 }
2595
2596 return success();
2597}
2598
2599static LogicalResult printOperation(CppEmitter &emitter, scf::YieldOp yieldOp) {
2600 raw_ostream &os = emitter.ostream();
2601 Operation &parentOp = *yieldOp.getOperation()->getParentOp();
2602
2603 if (yieldOp.getNumOperands() != parentOp.getNumResults())
2604 return yieldOp.emitError("number of operands does not to match the number "
2605 "of the parent op's results");
2606
2607 if (failed(interleaveWithError(
2608 llvm::zip(parentOp.getResults(), yieldOp.getOperands()),
2609 [&](auto pair) -> LogicalResult {
2610 auto result = std::get<0>(pair);
2611 auto operand = std::get<1>(pair);
2612 os << emitter.getOrCreateName(result) << " = ";
2613
2614 if (!emitter.hasValueInScope(operand))
2615 return yieldOp.emitError("operand value not in scope");
2616 os << emitter.getOrCreateName(operand);
2617 return success();
2618 },
2619 [&] { os << ";\n"; })))
2620 return failure();
2621
2622 return success();
2623}
2624
2625static LogicalResult printOperation(CppEmitter &emitter,
2626 func::ReturnOp returnOp) {
2627 raw_ostream &os = emitter.ostream();
2628 os << "return";
2629 switch (returnOp.getNumOperands()) {
2630 case 0:
2631 return success();
2632 case 1:
2633 os << " " << emitter.getOrCreateName(returnOp.getOperand(0));
2634 return success(emitter.hasValueInScope(returnOp.getOperand(0)));
2635 default:
2636 os << " std::make_tuple(";
2637 if (failed(emitter.emitOperandsAndAttributes(*returnOp.getOperation())))
2638 return failure();
2639 os << ")";
2640 }
2641
2642 return success();
2643}
2644
2645static LogicalResult printOperation(CppEmitter &emitter, ModuleOp moduleOp) {
2646 CppEmitter::Scope scope(emitter);
2647
2648 for (Operation &op : moduleOp)
2649 if (failed(emitter.emitOperation(op, /*trailingSemicolon=*/false)))
2650 return failure();
2651
2652 return success();
2653}
2654
2655static LogicalResult printOperation(CppEmitter &emitter,
2656 func::FuncOp functionOp) {
2657 // We need to declare variables at top if the function has multiple blocks.
2658 if (!emitter.shouldDeclareVariablesAtTop() &&
2659 functionOp.getBlocks().size() > 1)
2660 return functionOp.emitOpError(
2661 "with multiple blocks needs variables declared at top");
2662
2663 CppEmitter::Scope scope(emitter);
2664
2665 // Find any memref dim op in the function, and parse the dimension of each
2666 // dynamic shaped memref
2667 if (failed(parseMemRefDynamicDims(emitter, functionOp)))
2668 return failure();
2669
2670 raw_indented_ostream &os = emitter.ostream();
2671 if (failed(emitter.emitTypes(functionOp.getLoc(),
2672 functionOp.getFunctionType().getResults())))
2673 return failure();
2674 os << " " << functionOp.getName();
2675
2676 os << "(";
2677 if (functionOp.isDeclaration()) {
2678 if (failed(interleaveCommaWithError(
2679 functionOp.getArgumentTypes(), os, [&](Type type) -> LogicalResult {
2680 if (failed(emitter.emitType(functionOp.getLoc(), type)))
2681 return failure();
2682 // If it is a memref argument, we need to check if it has dynamic
2683 // shape. If so, the dimensions have to be printed out
2684 if (auto argType = dyn_cast<MemRefType>(type))
2685 for (unsigned dim = 0; dim < argType.getRank(); ++dim)
2686 if (argType.isDynamicDim(dim))
2687 os << ", size_t";
2688 return success();
2689 })))
2690 return failure();
2691 os << ");\n";
2692 return success();
2693 }
2694
2695 if (failed(interleaveCommaWithError(
2696 functionOp.getArguments(), os,
2697 [&](BlockArgument arg) -> LogicalResult {
2698 if (failed(emitter.emitType(functionOp.getLoc(), arg.getType())))
2699 return failure();
2700 os << " " << emitter.getOrCreateName(arg);
2701 // If it is a memref argument, we need to check if it has dynamic
2702 // shape. If so, the dimensions have to be printed out
2703 if (failed(printMemRefDims(emitter, arg)))
2704 return failure();
2705 return success();
2706 })))
2707 return failure();
2708
2709 os << ") {\n";
2710 os.indent();
2711 if (emitter.shouldDeclareVariablesAtTop()) {
2712 // Declare all variables that hold op results including those from nested
2713 // regions.
2714 WalkResult result =
2715 functionOp.walk<WalkOrder::PreOrder>([&](Operation *op) -> WalkResult {
2716 for (OpResult result : op->getResults()) {
2717 if (failed(emitter.emitVariableDeclaration(
2718 result, /*trailingSemicolon=*/true)))
2719 return {
2720 op->emitError("unable to declare result variable for op")};
2721 }
2722 return WalkResult::advance();
2723 });
2724 if (result.wasInterrupted())
2725 return failure();
2726 }
2727
2728 Region::BlockListType &blocks = functionOp.getBlocks();
2729 // Create label names for basic blocks.
2730 for (Block &block : blocks)
2731 emitter.getOrCreateName(block);
2732
2733 // Declare variables for basic block arguments.
2734 for (auto it = std::next(blocks.begin()); it != blocks.end(); ++it) {
2735 Block &block = *it;
2736 for (BlockArgument &arg : block.getArguments()) {
2737 if (emitter.hasValueInScope(arg))
2738 return functionOp.emitOpError(" block argument #")
2739 << arg.getArgNumber() << " is out of scope";
2740 if (failed(
2741 emitter.emitType(block.getParentOp()->getLoc(), arg.getType())))
2742 return failure();
2743 os << " " << emitter.getOrCreateName(arg) << ";\n";
2744 }
2745 }
2746
2747 for (Block &block : blocks) {
2748 // Only print a label if there is more than one block.
2749 if (blocks.size() > 1)
2750 if (failed(emitter.emitLabel(block)))
2751 return failure();
2752 for (Operation &op : block.getOperations()) {
2753 // When generating code for an scf.if or std.cond_br op no semicolon needs
2754 // to be printed after the closing brace.
2755 // When generating code for an scf.for op, printing a trailing semicolon
2756 // is handled within the printOperation function.
2757 if (bool trailingSemicolon =
2758 !isa<scf::IfOp, scf::ForOp, cf::CondBranchOp>(op);
2759 failed(emitter.emitOperation(
2760 op, /*trailingSemicolon=*/trailingSemicolon)))
2761 return failure();
2762 }
2763 }
2764 os.unindent() << "}\n";
2765
2766 return success();
2767}
2768
2769static LogicalResult printOperation(CppEmitter &emitter,
2770 aievec::MatMulOp matmulOp) {
2771 auto lhs = matmulOp.getLhs();
2772 auto rhs = matmulOp.getRhs();
2773 auto acc = matmulOp.getAcc();
2774
2775 // The sources should have already been emitted
2776 if (!emitter.hasValueInScope(lhs) || !emitter.hasValueInScope(rhs) ||
2777 !emitter.hasValueInScope(acc))
2778 return failure();
2779
2780 auto lhsName = printConversionTo512bit(emitter, lhs);
2781 auto rhsName = printConversionTo512bit(emitter, rhs);
2782
2783 raw_indented_ostream &os = emitter.ostream();
2784
2785 StringRef accName = emitter.getOrCreateName(acc);
2786
2787 auto lhsShape = cast<VectorType>(lhs.getType()).getShape();
2788 auto rhsShape = cast<VectorType>(rhs.getType()).getShape();
2789 os << accName << " = mac_" << lhsShape[0] << "x" << lhsShape[1] << "_"
2790 << rhsShape[0] << "x" << rhsShape[1] << "(";
2791 os << lhsName << ", " << rhsName << ", " << accName << ")";
2792
2793 // Finally, set the name of the result to the accumulator's name
2794 emitter.setName(matmulOp.getResult(), accName);
2795
2796 return success();
2797}
2798
2799CppEmitter::CppEmitter(raw_ostream &os, bool declareVariablesAtTop, bool aie2)
2800 : os(os), declareVariablesAtTop(declareVariablesAtTop), aie2_(aie2) {
2801 valueInScopeCount.push(0);
2802 labelInScopeCount.push(0);
2803}
2804
2805/// Return the existing or a new name for a Value.
2806StringRef CppEmitter::getOrCreateName(Value val, std::string prefix) {
2807 if (!valueMapper.count(val))
2808 valueMapper.insert(val,
2809 formatv("{0}{1}", prefix, ++valueInScopeCount.top()));
2810 return *valueMapper.begin(val);
2811}
2812
2813/// Set the name of a value to an existing name
2814void CppEmitter::setName(Value val, StringRef name) {
2815 valueMapper.insert(val, name.str());
2816}
2817
2818/// Get a new name that is not associated with any value
2819std::string CppEmitter::getNewName(std::string prefix) {
2820 std::string ret = formatv("{0}{1}", prefix, ++valueInScopeCount.top());
2821 return ret;
2822}
2823
2824/// Given a dynamic shaped memref, set its size at position 'index' to
2825// parameter 'result'
2826void CppEmitter::setMemRefDimParam(Value memref, unsigned index,
2827 const std::string &parameter) {
2828 auto p = std::make_pair(memref, index);
2829 assert(!paramIndexMapper.count(p) && "memref dimension already set");
2830 paramIndexMapper[p] = parameter;
2831}
2832
2833/// Return the memref parameteric dimension size at given index
2834StringRef CppEmitter::getMemRefDimParam(Value memref, unsigned index) {
2835 auto p = std::make_pair(memref, index);
2836 assert(paramIndexMapper.count(p) && "memref dimension not found");
2837 return paramIndexMapper[p];
2838}
2839
2840/// Return true if the specified dim of memref has a parameter
2841/// associated with it
2842bool CppEmitter::isMemRefDimParam(Value memref, unsigned index) {
2843 assert([&] {
2844 auto type = llvm::dyn_cast<MemRefType>(memref.getType());
2845 if (!(type && type.isDynamicDim(index))) {
2846 printf("the dimension size at index is not dynamic\n");
2847 return false;
2848 }
2849 return true;
2850 }());
2851
2852 auto p = std::make_pair(memref, index);
2853 return paramIndexMapper.count(p);
2854}
2855
2856/// Return the existing or a new label for a Block.
2857StringRef CppEmitter::getOrCreateName(Block &block, std::string prefix) {
2858 if (!blockMapper.count(&block))
2859 blockMapper.insert(&block,
2860 formatv("{0}{1}", prefix, ++labelInScopeCount.top()));
2861 return *blockMapper.begin(&block);
2862}
2863
2864bool CppEmitter::shouldMapToUnsigned(IntegerType::SignednessSemantics val) {
2865 switch (val) {
2866 case IntegerType::Signless:
2867 case IntegerType::Signed:
2868 return false;
2869 case IntegerType::Unsigned:
2870 return true;
2871 }
2872 llvm::report_fatal_error("Unexpected IntegerType::SignednessSemantics");
2873}
2874
2875bool CppEmitter::hasValueInScope(Value val) { return valueMapper.count(val); }
2876
2877bool CppEmitter::hasBlockLabel(Block &block) {
2878 return blockMapper.count(&block);
2879}
2880
2881// Check whether the int type dense value has a splat value and get the int
2882// value as a string.
2883template <typename ElTy>
2884static std::string getSplatValueOfIntDense(DenseIntElementsAttr dense) {
2885 ElTy splatVal = dense.getSplatValue<ElTy>();
2886 return std::to_string(splatVal);
2887}
2888
2889// Get the first float value of a dense type value as a string.
2890static std::string getSplatValueOfFloatDense(DenseFPElementsAttr dense,
2891 bool isBFloat = false) {
2892 auto apFloat = dense.getSplatValue<APFloat>();
2893 float splatVal = apFloat.convertToFloat();
2894 std::string firstValue = std::to_string(splatVal);
2895
2896 if (apFloat.isPosInfinity())
2897 if (isBFloat)
2898 // TODO: Clean this up; emitting largest finite value in lieu of infinity;
2899 // system headers do not provide a simple way to initialize a bfloat16 to
2900 // infinity.
2901 firstValue = std::to_string(0x1.FEp+127f);
2902 else
2903 firstValue = std::to_string(std::numeric_limits<float>::max());
2904 else if (apFloat.isNegInfinity())
2905 if (isBFloat)
2906 firstValue = std::to_string(-0x1.FEp+127f);
2907 else
2908 firstValue = std::to_string(std::numeric_limits<float>::lowest());
2909 else if (!apFloat.isNonZero())
2910 firstValue = "0";
2911
2912 return firstValue;
2913}
2914
2915LogicalResult CppEmitter::emitAttribute(Location loc, Attribute attr) {
2916 auto printInt = [&](const APInt &val, bool isUnsigned) {
2917 if (val.getBitWidth() == 1)
2918 if (val.getBoolValue())
2919 os << "true";
2920 else
2921 os << "false";
2922 else {
2923 SmallString<128> strValue;
2924 val.toString(strValue, 10, !isUnsigned, false);
2925 os << strValue;
2926 }
2927 };
2928
2929 auto printFloat = [&](const APFloat &val) {
2930 if (val.isFinite()) {
2931 SmallString<128> strValue;
2932 // Use default values of toString except don't truncate zeros.
2933 val.toString(strValue, 0, 0, false);
2934 switch (llvm::APFloatBase::SemanticsToEnum(val.getSemantics())) {
2935 case llvm::APFloatBase::S_IEEEsingle:
2936 os << "(float)";
2937 break;
2938 case llvm::APFloatBase::S_IEEEdouble:
2939 os << "(double)";
2940 break;
2941 default:
2942 break;
2943 }
2944 os << strValue;
2945 } else if (val.isNaN())
2946 os << "NAN";
2947 else if (val.isInfinity()) {
2948 if (val.isNegative())
2949 os << "-";
2950 os << "INFINITY";
2951 }
2952 };
2953
2954 // Print floating point attributes.
2955 if (auto fAttr = llvm::dyn_cast<FloatAttr>(attr)) {
2956 printFloat(fAttr.getValue());
2957 return success();
2958 }
2959
2960 if (auto dense = llvm::dyn_cast<DenseFPElementsAttr>(attr)) {
2961 if (aie2() && dense.isSplat()) {
2962 if (auto vType = llvm::dyn_cast<VectorType>(dense.getType()))
2963 if (auto fType = llvm::dyn_cast<FloatType>(vType.getElementType())) {
2964 unsigned width = fType.getWidth();
2965 std::string splatValue;
2966 if (width == 32)
2967 splatValue = getSplatValueOfFloatDense(dense);
2968 else if (width == 16)
2969 splatValue = getSplatValueOfFloatDense(dense, /*isBFloat*/ true);
2970
2971 if (width == 32 || (width == 16 && getVectorLaneSize(vType) == 32))
2972 if (splatValue == "0") {
2973 os << "broadcast_zero_";
2974 if (failed(emitType(loc, fType)))
2975 return failure();
2976 os << "()";
2977 } else {
2978 os << "broadcast_to_";
2979 if (failed(emitType(loc, vType)))
2980 return failure();
2981 os << "((";
2982 if (failed(emitType(loc, fType)))
2983 return failure();
2984 os << ")";
2985 os << splatValue;
2986 os << ")";
2987 }
2988 else if (width == 16 && getVectorLaneSize(vType) == 16) {
2989 os << "extract_v16bfloat16(";
2990 if (splatValue == "0")
2991 os << "broadcast_zero_bfloat16()";
2992 else {
2993 os << "broadcast_to_v32bfloat16";
2994 os << "((";
2995 if (failed(emitType(loc, fType)))
2996 return failure();
2997 os << ")";
2998 os << splatValue;
2999 os << ")";
3000 }
3001 os << ", 0)";
3002 }
3003 }
3004 // TODO: Deal with multiple dense value case for AIE2.
3005 } else {
3006 os << '{';
3007 interleaveComma(dense, os, [&](const APFloat &val) { printFloat(val); });
3008 os << '}';
3009 }
3010 return success();
3011 }
3012
3013 // Print integer attributes.
3014 if (auto iAttr = llvm::dyn_cast<IntegerAttr>(attr)) {
3015 if (auto iType = llvm::dyn_cast<IntegerType>(iAttr.getType())) {
3016 printInt(iAttr.getValue(), shouldMapToUnsigned(iType.getSignedness()));
3017 return success();
3018 }
3019 if (llvm::dyn_cast<IndexType>(iAttr.getType())) {
3020 printInt(iAttr.getValue(), false);
3021 return success();
3022 }
3023 }
3024
3025 if (auto dense = llvm::dyn_cast<DenseIntElementsAttr>(attr)) {
3026 if (auto tType = llvm::dyn_cast<TensorType>(dense.getType())) {
3027 if (auto iType = llvm::dyn_cast<IntegerType>(tType.getElementType())) {
3028 os << '{';
3029 interleaveComma(dense, os, [&](const APInt &val) {
3030 printInt(val, shouldMapToUnsigned(iType.getSignedness()));
3031 });
3032 os << '}';
3033 return success();
3034 }
3035 if (llvm::dyn_cast<IndexType>(tType.getElementType())) {
3036 os << '{';
3037 interleaveComma(dense, os,
3038 [&](const APInt &val) { printInt(val, false); });
3039 os << '}';
3040 return success();
3041 }
3042 }
3043
3044 if (auto vType = llvm::dyn_cast<VectorType>(dense.getType())) {
3045 if (auto iType = llvm::dyn_cast<IntegerType>(vType.getElementType())) {
3046 unsigned width = iType.getWidth();
3047 if (llvm::all_of(dense, [](const APInt &val) { return val == 0; })) {
3048 if (aie2()) {
3049 if (width * getVectorLaneSize(vType) == 1024) {
3050 os << "concat(broadcast_zero_s" << width << "(), broadcast_zero_s"
3051 << width << "())";
3052 return success();
3053 }
3054 os << "broadcast_zero_s";
3055 os << width;
3056 } else {
3057 os << "null_";
3058 if (failed(emitType(loc, vType)))
3059 return failure();
3060 }
3061 os << "()";
3062 return success();
3063 }
3064
3065 if (aie2() && dense.isSplat()) {
3066 std::string splatValue;
3067 if (width == 32)
3068 splatValue = getSplatValueOfIntDense<int32_t>(dense);
3069 else if (width == 16)
3070 splatValue = getSplatValueOfIntDense<int16_t>(dense);
3071 else if (width == 8)
3072 splatValue = getSplatValueOfIntDense<int8_t>(dense);
3073 os << "broadcast_to_";
3074 if (failed(emitType(loc, vType)))
3075 return failure();
3076 os << "((";
3077 if (failed(emitType(loc, iType)))
3078 return failure();
3079 os << ")";
3080 os << splatValue;
3081 os << ")";
3082 // TODO: Handle multiple dense value case in AIE2.
3083 } else {
3084 os << '{';
3085 interleaveComma(dense, os, [&](const APInt &val) {
3086 printInt(val, shouldMapToUnsigned(iType.getSignedness()));
3087 });
3088 os << '}';
3089 }
3090 return success();
3091 }
3092 if (llvm::dyn_cast<IndexType>(vType.getElementType())) {
3093 os << '{';
3094 interleaveComma(dense, os,
3095 [&](const APInt &val) { printInt(val, false); });
3096 os << '}';
3097 return success();
3098 }
3099 }
3100 }
3101
3102 // Print opaque attributes.
3103 if (auto oAttr = llvm::dyn_cast<emitc::OpaqueAttr>(attr)) {
3104 os << oAttr.getValue();
3105 return success();
3106 }
3107
3108 // Print symbolic reference attributes.
3109 if (auto sAttr = llvm::dyn_cast<SymbolRefAttr>(attr)) {
3110 if (sAttr.getNestedReferences().size() > 1)
3111 return emitError(loc, "attribute has more than 1 nested reference");
3112 os << sAttr.getRootReference().getValue();
3113 return success();
3114 }
3115
3116 // Print type attributes.
3117 if (auto type = llvm::dyn_cast<TypeAttr>(attr))
3118 return emitType(loc, type.getValue());
3119
3120 return emitError(loc, "cannot emit attribute of type ") << attr;
3121}
3122
3123LogicalResult CppEmitter::emitOperands(Operation &op) {
3124 auto emitOperandName = [&](Value result) -> LogicalResult {
3125 if (!hasValueInScope(result))
3126 return op.emitOpError() << "operand value not in scope";
3127 os << getOrCreateName(result);
3128 return success();
3129 };
3130 return interleaveCommaWithError(op.getOperands(), os, emitOperandName);
3131}
3132
3133LogicalResult
3134CppEmitter::emitOperandsAndAttributes(Operation &op,
3135 ArrayRef<StringRef> exclude) {
3136 if (failed(emitOperands(op)))
3137 return failure();
3138 // Insert comma in between operands and non-filtered attributes if needed.
3139 if (op.getNumOperands() > 0)
3140 for (NamedAttribute attr : op.getAttrs())
3141 if (!is_contained(exclude, attr.getName().strref())) {
3142 os << ", ";
3143 break;
3144 }
3145 // Emit attributes.
3146 auto emitNamedAttribute = [&](NamedAttribute attr) -> LogicalResult {
3147 if (is_contained(exclude, attr.getName().strref()))
3148 return success();
3149 os << "/* " << attr.getName().getValue() << " */";
3150 if (failed(emitAttribute(op.getLoc(), attr.getValue())))
3151 return failure();
3152 return success();
3153 };
3154
3155 return interleaveCommaWithError(op.getAttrs(), os, emitNamedAttribute);
3156}
3157
3158LogicalResult CppEmitter::emitVariableAssignment(OpResult result) {
3159 if (!hasValueInScope(result)) {
3160 return result.getDefiningOp()->emitOpError(
3161 "result variable for the operation has not been declared");
3162 }
3163 os << getOrCreateName(result) << " = ";
3164
3165 return success();
3166}
3167
3168LogicalResult CppEmitter::emitVariableDeclaration(OpResult result,
3169 bool trailingSemicolon,
3170 bool isAcc) {
3171 if (hasValueInScope(result))
3172 return result.getDefiningOp()->emitError(
3173 "result variable for the operation already declared");
3174 if (failed(
3175 emitType(result.getOwner()->getLoc(), result.getType(), true, isAcc)))
3176 return failure();
3177 os << " " << getOrCreateName(result);
3178 if (trailingSemicolon)
3179 os << ";\n";
3180
3181 return success();
3182}
3183
3184LogicalResult CppEmitter::emitAssignPrefix(Operation &op, bool isAcc) {
3185 switch (op.getNumResults()) {
3186 case 0:
3187 break;
3188 case 1: {
3189 OpResult result = op.getResult(0);
3190 if (shouldDeclareVariablesAtTop()) {
3191 if (failed(emitVariableAssignment(result)))
3192 return failure();
3193 } else {
3194 if (failed(emitVariableDeclaration(result, /*trailingSemicolon=*/false,
3195 isAcc)))
3196 return failure();
3197 os << " = ";
3198 }
3199 break;
3200 }
3201 default:
3202 if (!shouldDeclareVariablesAtTop())
3203 for (OpResult result : op.getResults())
3204 if (failed(emitVariableDeclaration(result, /*trailingSemicolon=*/true)))
3205 return failure();
3206
3207 os << "std::tie(";
3208 interleaveComma(op.getResults(), os,
3209 [&](Value result) { os << getOrCreateName(result); });
3210 os << ") = ";
3211 }
3212 return success();
3213}
3214
3215LogicalResult CppEmitter::emitLabel(Block &block) {
3216 if (!hasBlockLabel(block))
3217 return block.getParentOp()->emitError("label for block not found");
3218 // FIXME: Add feature in `raw_indented_ostream` to ignore indent for block
3219 // label instead of using `getOStream`.
3220 os.getOStream() << getOrCreateName(block) << ":\n";
3221 return success();
3222}
3223
3224LogicalResult CppEmitter::emitOperation(Operation &op, bool trailingSemicolon) {
3225 // Some operations in AIE become nops. Check if this operation must be skipped
3226 // from codegen
3227 if (skippedOp(&op, *this))
3228 return success();
3229
3230 LogicalResult status =
3231 TypeSwitch<Operation *, LogicalResult>(&op)
3232 // EmitC ops.
3233 .Case<emitc::ApplyOp, emitc::CallOpaqueOp, emitc::ConstantOp>(
3234 [&](auto op) { return printOperation(*this, op); })
3235 .Case<emitc::IncludeOp>([&](auto op) {
3236 if (StringRef name = op.getInclude(); !includeNames.count(name)) {
3237 includeNames.insert(name);
3238 return printOperation(*this, op);
3239 }
3240 return success();
3241 })
3242 // SCF ops.
3243 .Case<scf::ForOp, scf::IfOp, scf::YieldOp>(
3244 [&](auto op) { return printOperation(*this, op); })
3245 // Standard ops.
3246 .Case<cf::BranchOp, func::CallOp, cf::CondBranchOp, func::FuncOp,
3247 ModuleOp, func::ReturnOp>(
3248 [&](auto op) { return printOperation(*this, op); })
3249 // Arith ops.
3250 .Case<arith::ConstantOp>(
3251 [&](auto op) { return printOperation(*this, op); })
3252 // Extra ops added for AIE
3253 // Arith ops.
3254 .Case<arith::AddIOp>(
3255 [&](auto op) { return printOperation<arith::AddIOp>(*this, op); })
3256 .Case<arith::AddFOp>(
3257 [&](auto op) { return printOperation<arith::AddFOp>(*this, op); })
3258 .Case<arith::MulIOp>(
3259 [&](auto op) { return printOperation<arith::MulIOp>(*this, op); })
3260 .Case<arith::MulFOp>(
3261 [&](auto op) { return printOperation<arith::MulFOp>(*this, op); })
3262 .Case<arith::SubIOp>(
3263 [&](auto op) { return printOperation<arith::SubIOp>(*this, op); })
3264 .Case<arith::SubFOp>(
3265 [&](auto op) { return printOperation<arith::SubFOp>(*this, op); })
3266 .Case<arith::DivSIOp>([&](auto op) {
3267 return printOperation<arith::DivSIOp>(*this, op);
3268 })
3269 .Case<arith::DivUIOp>([&](auto op) {
3270 return printOperation<arith::DivUIOp>(*this, op);
3271 })
3272 .Case<arith::DivFOp>(
3273 [&](auto op) { return printOperation<arith::DivFOp>(*this, op); })
3274 .Case<arith::RemSIOp>([&](auto op) {
3275 return printOperation<arith::RemSIOp>(*this, op);
3276 })
3277 .Case<arith::CmpIOp>(
3278 [&](auto op) { return printOperation<arith::CmpIOp>(*this, op); })
3279 .Case<arith::SelectOp>(
3280 [&](auto op) { return printOperation(*this, op); })
3281 // Vector ops.
3282 .Case<vector::TransferWriteOp>(
3283 [&](auto op) { return printOperation(*this, op); })
3284 // Memref ops.
3285 .Case<memref::StoreOp, memref::ExpandShapeOp,
3286 memref::CollapseShapeOp>(
3287 [&](auto op) { return printOperation(*this, op); })
3288 // AievecAie1 ops
3289 .Case<aievec::aie1::AddOp, aievec::aie1::SubOp, aievec::aie1::FMAOp,
3290 aievec::aie1::MulOp, aievec::aie1::SelectOp,
3291 aievec::aie1::ExtOp>(
3292 [&](auto op) { return printOperation(*this, op); })
3293 // Aievec ops
3294 .Case<AddElemOp, ConcatOp, ExtOp, PackOp, SRSOp, SubElemOp, UPDOp,
3295 UPSOp, FMAElemOp, MulElemOp, BroadcastOp, BroadcastScalarOp,
3296 MulConvOp, FMAConvOp, ShiftOp, ShuffleOp, CastOp, MinOp, MaxOp,
3297 NegOp, CmpOp, SelOp, ExtElemOp, BxorOp, BnegOp, BandOp, BorOp,
3298 UnpackOp, MatMulOp, LegacyShuffleOp>(
3299 [&](auto op) { return printOperation(*this, op); })
3300 .Default([&](Operation *) {
3301 return op.emitOpError("unable to find printer for op");
3302 });
3303
3304 if (failed(status))
3305 return failure();
3306 os << (trailingSemicolon ? ";\n" : "\n");
3307
3308 return success();
3309}
3310
3311std::optional<std::string>
3312CppEmitter::genCppTypeName(Type type, bool stdintType, bool isAcc) {
3313 std::stringstream ss;
3314 if (auto iType = dyn_cast<IntegerType>(type)) {
3315 switch (iType.getWidth()) {
3316 case 1:
3317 return "bool";
3318 case 8:
3319 case 16:
3320 case 32:
3321 case 64:
3322 if (shouldMapToUnsigned(iType.getSignedness()))
3323 ss << "uint" << iType.getWidth() << (stdintType ? "_t" : "");
3324 else
3325 ss << "int" << iType.getWidth() << (stdintType ? "_t" : "");
3326 return ss.str();
3327 case 48:
3328 case 80:
3329 ss << "acc" << iType.getWidth();
3330 return ss.str();
3331 default:
3332 return {};
3333 }
3334 }
3335 if (auto fType = dyn_cast<FloatType>(type)) {
3336 switch (fType.getWidth()) {
3337 case 16:
3338 return "bfloat16";
3339 case 32:
3340 return "float";
3341 case 64:
3342 return "double";
3343 default:
3344 return {};
3345 }
3346 }
3347 if (auto iType = dyn_cast<IndexType>(type))
3348 return "size_t";
3349
3350 if (auto tType = dyn_cast<TensorType>(type)) {
3351 if (!tType.hasRank())
3352 return {};
3353 if (!tType.hasStaticShape())
3354 return {};
3355 ss << "Tensor<";
3356 auto nestedTypeName = genCppTypeName(tType.getElementType());
3357 if (!nestedTypeName)
3358 return {};
3359 ss << *nestedTypeName;
3360 auto shape = tType.getShape();
3361 for (auto dimSize : shape) {
3362 ss << ", ";
3363 ss << dimSize;
3364 }
3365 ss << ">";
3366 return ss.str();
3367 }
3368 if (auto tType = dyn_cast<TupleType>(type)) {
3369 ss << "std::tuple<";
3370 bool itrleaveFailed = false;
3371 llvm::interleave(
3372 tType.getTypes(),
3373 [&](Type type) {
3374 auto optTyNameStr = genCppTypeName(type);
3375 if (optTyNameStr)
3376 ss << *optTyNameStr;
3377 else
3378 itrleaveFailed = true;
3379 },
3380 [&]() { ss << ", "; });
3381 ss << ">";
3382 if (!itrleaveFailed)
3383 return ss.str();
3384 return {};
3385 }
3386 if (auto oType = dyn_cast<emitc::OpaqueType>(type)) {
3387 ss << oType.getValue().str();
3388 return ss.str();
3389 }
3390 // Types added for AIE
3391 // MemRefType: printed as 'eltType'*
3392 if (auto tType = dyn_cast<MemRefType>(type)) {
3393 auto elemTyStrOpt = genCppTypeName(tType.getElementType());
3394 if (!elemTyStrOpt)
3395 return {};
3396 ss << *elemTyStrOpt << " * restrict";
3397 return ss.str();
3398 }
3399 // VectorType: printed as v'lane''eltType'
3400 if (auto tType = dyn_cast<VectorType>(type)) {
3401 Type eltType = tType.getElementType();
3402 // Flatten multidimensional vectors
3403 auto vShape = tType.getShape();
3404 int64_t numElems = std::accumulate(vShape.begin(), vShape.end(), 1,
3405 std::multiplies<int64_t>());
3406 ss << "v" << std::to_string(numElems);
3407
3408 int64_t iElTyBitWidth = 0;
3409 auto iElTy = dyn_cast<IntegerType>(eltType);
3410 if (iElTy)
3411 iElTyBitWidth = iElTy.getWidth();
3412 if (aie2() && (isAcc || iElTyBitWidth == 64)) {
3413 if (iElTy) {
3414 // AIE2 has `ups_to_v16acc32`, `ups_to_v16acc64`, `ups_to_v32acc32`
3415 // intrinsics
3416 if ((numElems == 16 && iElTyBitWidth == 64) ||
3417 (numElems == 32 && iElTyBitWidth == 32) ||
3418 (numElems == 16 && iElTyBitWidth == 32)) {
3419 ss << "acc" << iElTyBitWidth;
3420 return ss.str();
3421 }
3422 return {};
3423 }
3424 if (isa<FloatType>(eltType)) {
3425 // AIE2 only has a `ups_to_v16accfloat` intrinsic
3426 ss << "accfloat";
3427 return ss.str();
3428 }
3429 }
3430 auto elTyNameOpt = genCppTypeName(eltType, false);
3431 if (!elTyNameOpt)
3432 return {};
3433 ss << *elTyNameOpt;
3434 return ss.str();
3435 }
3436 return {};
3437}
3438
3439LogicalResult CppEmitter::emitType(Location loc, Type type, bool stdintType,
3440 bool isAcc) {
3441 auto typeName = genCppTypeName(type, stdintType, isAcc);
3442 if (!typeName)
3443 return emitError(loc, "cannot emit type ") << type;
3444 os << *typeName;
3445 return success();
3446}
3447
3448LogicalResult CppEmitter::emitTypes(Location loc, ArrayRef<Type> types) {
3449 switch (types.size()) {
3450 case 0:
3451 os << "void";
3452 return success();
3453 case 1:
3454 return emitType(loc, types.front());
3455 default:
3456 return emitTupleType(loc, types);
3457 }
3458}
3459
3460LogicalResult CppEmitter::emitTupleType(Location loc, ArrayRef<Type> types) {
3461 os << "std::tuple<";
3462 if (failed(interleaveCommaWithError(
3463 types, os, [&](Type type) { return emitType(loc, type); })))
3464 return failure();
3465 os << ">";
3466 return success();
3467}
3468
3469LogicalResult aievec::translateAIEVecToCpp(Operation *op, bool aie2,
3470 raw_ostream &os) {
3471 CppEmitter emitter(os, false, aie2);
3472 return emitter.emitOperation(*op, /*trailingSemicolon=*/false);
3473}
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.
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