CIRCT 22.0.0git
Loading...
Searching...
No Matches
LowerArcToLLVM.cpp
Go to the documentation of this file.
1//===- LowerArcToLLVM.cpp -------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
24#include "mlir/Conversion/ArithToLLVM/ArithToLLVM.h"
25#include "mlir/Conversion/ControlFlowToLLVM/ControlFlowToLLVM.h"
26#include "mlir/Conversion/FuncToLLVM/ConvertFuncToLLVM.h"
27#include "mlir/Conversion/IndexToLLVM/IndexToLLVM.h"
28#include "mlir/Conversion/LLVMCommon/ConversionTarget.h"
29#include "mlir/Conversion/LLVMCommon/TypeConverter.h"
30#include "mlir/Conversion/SCFToControlFlow/SCFToControlFlow.h"
31#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
32#include "mlir/Dialect/Func/IR/FuncOps.h"
33#include "mlir/Dialect/Index/IR/IndexOps.h"
34#include "mlir/Dialect/LLVMIR/FunctionCallUtils.h"
35#include "mlir/Dialect/LLVMIR/LLVMAttrs.h"
36#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
37#include "mlir/Dialect/SCF/IR/SCF.h"
38#include "mlir/IR/BuiltinDialect.h"
39#include "mlir/Pass/Pass.h"
40#include "mlir/Transforms/DialectConversion.h"
41#include "llvm/Support/Debug.h"
42#include "llvm/Support/FormatVariadic.h"
43
44#include <cstddef>
45
46#define DEBUG_TYPE "lower-arc-to-llvm"
47
48namespace circt {
49#define GEN_PASS_DEF_LOWERARCTOLLVM
50#include "circt/Conversion/Passes.h.inc"
51} // namespace circt
52
53using namespace mlir;
54using namespace circt;
55using namespace arc;
56using namespace hw;
57using namespace runtime;
58
59//===----------------------------------------------------------------------===//
60// Lowering Patterns
61//===----------------------------------------------------------------------===//
62
63static llvm::Twine evalSymbolFromModelName(StringRef modelName) {
64 return modelName + "_eval";
65}
66
67namespace {
68
69struct ModelOpLowering : public OpConversionPattern<arc::ModelOp> {
70 using OpConversionPattern::OpConversionPattern;
71 LogicalResult
72 matchAndRewrite(arc::ModelOp op, OpAdaptor adaptor,
73 ConversionPatternRewriter &rewriter) const final {
74 {
75 IRRewriter::InsertionGuard guard(rewriter);
76 rewriter.setInsertionPointToEnd(&op.getBodyBlock());
77 func::ReturnOp::create(rewriter, op.getLoc());
78 }
79 auto funcName =
80 rewriter.getStringAttr(evalSymbolFromModelName(op.getName()));
81 auto funcType =
82 rewriter.getFunctionType(op.getBody().getArgumentTypes(), {});
83 auto func =
84 mlir::func::FuncOp::create(rewriter, op.getLoc(), funcName, funcType);
85 rewriter.inlineRegionBefore(op.getRegion(), func.getBody(), func.end());
86 rewriter.eraseOp(op);
87 return success();
88 }
89};
90
91struct AllocStorageOpLowering
92 : public OpConversionPattern<arc::AllocStorageOp> {
93 using OpConversionPattern::OpConversionPattern;
94 LogicalResult
95 matchAndRewrite(arc::AllocStorageOp op, OpAdaptor adaptor,
96 ConversionPatternRewriter &rewriter) const final {
97 auto type = typeConverter->convertType(op.getType());
98 if (!op.getOffset().has_value())
99 return failure();
100 rewriter.replaceOpWithNewOp<LLVM::GEPOp>(op, type, rewriter.getI8Type(),
101 adaptor.getInput(),
102 LLVM::GEPArg(*op.getOffset()));
103 return success();
104 }
105};
106
107template <class ConcreteOp>
108struct AllocStateLikeOpLowering : public OpConversionPattern<ConcreteOp> {
110 using OpConversionPattern<ConcreteOp>::typeConverter;
111 using OpAdaptor = typename ConcreteOp::Adaptor;
112
113 LogicalResult
114 matchAndRewrite(ConcreteOp op, OpAdaptor adaptor,
115 ConversionPatternRewriter &rewriter) const final {
116 // Get a pointer to the correct offset in the storage.
117 auto offsetAttr = op->template getAttrOfType<IntegerAttr>("offset");
118 if (!offsetAttr)
119 return failure();
120 Value ptr = LLVM::GEPOp::create(
121 rewriter, op->getLoc(), adaptor.getStorage().getType(),
122 rewriter.getI8Type(), adaptor.getStorage(),
123 LLVM::GEPArg(offsetAttr.getValue().getZExtValue()));
124 rewriter.replaceOp(op, ptr);
125 return success();
126 }
127};
128
129struct StateReadOpLowering : public OpConversionPattern<arc::StateReadOp> {
130 using OpConversionPattern::OpConversionPattern;
131 LogicalResult
132 matchAndRewrite(arc::StateReadOp op, OpAdaptor adaptor,
133 ConversionPatternRewriter &rewriter) const final {
134 auto type = typeConverter->convertType(op.getType());
135 rewriter.replaceOpWithNewOp<LLVM::LoadOp>(op, type, adaptor.getState());
136 return success();
137 }
138};
139
140struct StateWriteOpLowering : public OpConversionPattern<arc::StateWriteOp> {
141 using OpConversionPattern::OpConversionPattern;
142 LogicalResult
143 matchAndRewrite(arc::StateWriteOp op, OpAdaptor adaptor,
144 ConversionPatternRewriter &rewriter) const final {
145 if (adaptor.getCondition()) {
146 rewriter.replaceOpWithNewOp<scf::IfOp>(
147 op, adaptor.getCondition(), [&](auto &builder, auto loc) {
148 LLVM::StoreOp::create(builder, loc, adaptor.getValue(),
149 adaptor.getState());
150 scf::YieldOp::create(builder, loc);
151 });
152 } else {
153 rewriter.replaceOpWithNewOp<LLVM::StoreOp>(op, adaptor.getValue(),
154 adaptor.getState());
155 }
156 return success();
157 }
158};
159
160struct AllocMemoryOpLowering : public OpConversionPattern<arc::AllocMemoryOp> {
161 using OpConversionPattern::OpConversionPattern;
162 LogicalResult
163 matchAndRewrite(arc::AllocMemoryOp op, OpAdaptor adaptor,
164 ConversionPatternRewriter &rewriter) const final {
165 auto offsetAttr = op->getAttrOfType<IntegerAttr>("offset");
166 if (!offsetAttr)
167 return failure();
168 Value ptr = LLVM::GEPOp::create(
169 rewriter, op.getLoc(), adaptor.getStorage().getType(),
170 rewriter.getI8Type(), adaptor.getStorage(),
171 LLVM::GEPArg(offsetAttr.getValue().getZExtValue()));
172
173 rewriter.replaceOp(op, ptr);
174 return success();
175 }
176};
177
178struct StorageGetOpLowering : public OpConversionPattern<arc::StorageGetOp> {
179 using OpConversionPattern::OpConversionPattern;
180 LogicalResult
181 matchAndRewrite(arc::StorageGetOp op, OpAdaptor adaptor,
182 ConversionPatternRewriter &rewriter) const final {
183 Value offset = LLVM::ConstantOp::create(
184 rewriter, op.getLoc(), rewriter.getI32Type(), op.getOffsetAttr());
185 Value ptr = LLVM::GEPOp::create(
186 rewriter, op.getLoc(), adaptor.getStorage().getType(),
187 rewriter.getI8Type(), adaptor.getStorage(), offset);
188 rewriter.replaceOp(op, ptr);
189 return success();
190 }
191};
192
193struct MemoryAccess {
194 Value ptr;
195 Value withinBounds;
196};
197
198static MemoryAccess prepareMemoryAccess(Location loc, Value memory,
199 Value address, MemoryType type,
200 ConversionPatternRewriter &rewriter) {
201 auto zextAddrType = rewriter.getIntegerType(
202 cast<IntegerType>(address.getType()).getWidth() + 1);
203 Value addr = LLVM::ZExtOp::create(rewriter, loc, zextAddrType, address);
204 Value addrLimit =
205 LLVM::ConstantOp::create(rewriter, loc, zextAddrType,
206 rewriter.getI32IntegerAttr(type.getNumWords()));
207 Value withinBounds = LLVM::ICmpOp::create(
208 rewriter, loc, LLVM::ICmpPredicate::ult, addr, addrLimit);
209 Value ptr = LLVM::GEPOp::create(
210 rewriter, loc, LLVM::LLVMPointerType::get(memory.getContext()),
211 rewriter.getIntegerType(type.getStride() * 8), memory, ValueRange{addr});
212 return {ptr, withinBounds};
213}
214
215struct MemoryReadOpLowering : public OpConversionPattern<arc::MemoryReadOp> {
216 using OpConversionPattern::OpConversionPattern;
217 LogicalResult
218 matchAndRewrite(arc::MemoryReadOp op, OpAdaptor adaptor,
219 ConversionPatternRewriter &rewriter) const final {
220 auto type = typeConverter->convertType(op.getType());
221 auto memoryType = cast<MemoryType>(op.getMemory().getType());
222 auto access =
223 prepareMemoryAccess(op.getLoc(), adaptor.getMemory(),
224 adaptor.getAddress(), memoryType, rewriter);
225
226 // Only attempt to read the memory if the address is within bounds,
227 // otherwise produce a zero value.
228 rewriter.replaceOpWithNewOp<scf::IfOp>(
229 op, access.withinBounds,
230 [&](auto &builder, auto loc) {
231 Value loadOp = LLVM::LoadOp::create(
232 builder, loc, memoryType.getWordType(), access.ptr);
233 scf::YieldOp::create(builder, loc, loadOp);
234 },
235 [&](auto &builder, auto loc) {
236 Value zeroValue = LLVM::ConstantOp::create(
237 builder, loc, type, builder.getI64IntegerAttr(0));
238 scf::YieldOp::create(builder, loc, zeroValue);
239 });
240 return success();
241 }
242};
243
244struct MemoryWriteOpLowering : public OpConversionPattern<arc::MemoryWriteOp> {
245 using OpConversionPattern::OpConversionPattern;
246 LogicalResult
247 matchAndRewrite(arc::MemoryWriteOp op, OpAdaptor adaptor,
248 ConversionPatternRewriter &rewriter) const final {
249 auto access = prepareMemoryAccess(
250 op.getLoc(), adaptor.getMemory(), adaptor.getAddress(),
251 cast<MemoryType>(op.getMemory().getType()), rewriter);
252 auto enable = access.withinBounds;
253 if (adaptor.getEnable())
254 enable = LLVM::AndOp::create(rewriter, op.getLoc(), adaptor.getEnable(),
255 enable);
256
257 // Only attempt to write the memory if the address is within bounds.
258 rewriter.replaceOpWithNewOp<scf::IfOp>(
259 op, enable, [&](auto &builder, auto loc) {
260 LLVM::StoreOp::create(builder, loc, adaptor.getData(), access.ptr);
261 scf::YieldOp::create(builder, loc);
262 });
263 return success();
264 }
265};
266
267/// A dummy lowering for clock gates to an AND gate.
268struct ClockGateOpLowering : public OpConversionPattern<seq::ClockGateOp> {
269 using OpConversionPattern::OpConversionPattern;
270 LogicalResult
271 matchAndRewrite(seq::ClockGateOp op, OpAdaptor adaptor,
272 ConversionPatternRewriter &rewriter) const final {
273 rewriter.replaceOpWithNewOp<LLVM::AndOp>(op, adaptor.getInput(),
274 adaptor.getEnable());
275 return success();
276 }
277};
278
279/// Lower 'seq.clock_inv x' to 'llvm.xor x true'
280struct ClockInvOpLowering : public OpConversionPattern<seq::ClockInverterOp> {
281 using OpConversionPattern::OpConversionPattern;
282 LogicalResult
283 matchAndRewrite(seq::ClockInverterOp op, OpAdaptor adaptor,
284 ConversionPatternRewriter &rewriter) const final {
285 auto constTrue = LLVM::ConstantOp::create(rewriter, op->getLoc(),
286 rewriter.getI1Type(), 1);
287 rewriter.replaceOpWithNewOp<LLVM::XOrOp>(op, adaptor.getInput(), constTrue);
288 return success();
289 }
290};
291
292struct ZeroCountOpLowering : public OpConversionPattern<arc::ZeroCountOp> {
293 using OpConversionPattern::OpConversionPattern;
294 LogicalResult
295 matchAndRewrite(arc::ZeroCountOp op, OpAdaptor adaptor,
296 ConversionPatternRewriter &rewriter) const override {
297 // Use poison when input is zero.
298 IntegerAttr isZeroPoison = rewriter.getBoolAttr(true);
299
300 if (op.getPredicate() == arc::ZeroCountPredicate::leading) {
301 rewriter.replaceOpWithNewOp<LLVM::CountLeadingZerosOp>(
302 op, adaptor.getInput().getType(), adaptor.getInput(), isZeroPoison);
303 return success();
304 }
305
306 rewriter.replaceOpWithNewOp<LLVM::CountTrailingZerosOp>(
307 op, adaptor.getInput().getType(), adaptor.getInput(), isZeroPoison);
308 return success();
309 }
310};
311
312struct SeqConstClockLowering : public OpConversionPattern<seq::ConstClockOp> {
313 using OpConversionPattern::OpConversionPattern;
314 LogicalResult
315 matchAndRewrite(seq::ConstClockOp op, OpAdaptor adaptor,
316 ConversionPatternRewriter &rewriter) const override {
317 rewriter.replaceOpWithNewOp<LLVM::ConstantOp>(
318 op, rewriter.getI1Type(), static_cast<int64_t>(op.getValue()));
319 return success();
320 }
321};
322
323template <typename OpTy>
324struct ReplaceOpWithInputPattern : public OpConversionPattern<OpTy> {
326 using OpAdaptor = typename OpTy::Adaptor;
327 LogicalResult
328 matchAndRewrite(OpTy op, OpAdaptor adaptor,
329 ConversionPatternRewriter &rewriter) const override {
330 rewriter.replaceOp(op, adaptor.getInput());
331 return success();
332 }
333};
334
335} // namespace
336
337//===----------------------------------------------------------------------===//
338// Simulation Orchestration Lowering Patterns
339//===----------------------------------------------------------------------===//
340
341namespace {
342
343struct ModelInfoMap {
344 size_t numStateBytes;
345 llvm::DenseMap<StringRef, StateInfo> states;
346 mlir::FlatSymbolRefAttr initialFnSymbol;
347 mlir::FlatSymbolRefAttr finalFnSymbol;
348};
349
350template <typename OpTy>
351struct ModelAwarePattern : public OpConversionPattern<OpTy> {
352 ModelAwarePattern(const TypeConverter &typeConverter, MLIRContext *context,
353 llvm::DenseMap<StringRef, ModelInfoMap> &modelInfo)
354 : OpConversionPattern<OpTy>(typeConverter, context),
355 modelInfo(modelInfo) {}
356
357protected:
358 Value createPtrToPortState(ConversionPatternRewriter &rewriter, Location loc,
359 Value state, const StateInfo &port) const {
360 MLIRContext *ctx = rewriter.getContext();
361 return LLVM::GEPOp::create(rewriter, loc, LLVM::LLVMPointerType::get(ctx),
362 IntegerType::get(ctx, 8), state,
363 LLVM::GEPArg(port.offset));
364 }
365
366 llvm::DenseMap<StringRef, ModelInfoMap> &modelInfo;
367};
368
369/// Lowers SimInstantiateOp to a malloc and memset call. This pattern will
370/// mutate the global module.
371struct SimInstantiateOpLowering
372 : public ModelAwarePattern<arc::SimInstantiateOp> {
373 using ModelAwarePattern::ModelAwarePattern;
374
375 LogicalResult
376 matchAndRewrite(arc::SimInstantiateOp op, OpAdaptor adaptor,
377 ConversionPatternRewriter &rewriter) const final {
378 auto modelIt = modelInfo.find(
379 cast<SimModelInstanceType>(op.getBody().getArgument(0).getType())
380 .getModel()
381 .getValue());
382 ModelInfoMap &model = modelIt->second;
383
384 bool useRuntime = op.getRuntimeModel().has_value();
385
386 ModuleOp moduleOp = op->getParentOfType<ModuleOp>();
387 if (!moduleOp)
388 return failure();
389
390 ConversionPatternRewriter::InsertionGuard guard(rewriter);
391
392 // FIXME: like the rest of MLIR, this assumes sizeof(intptr_t) ==
393 // sizeof(size_t) on the target architecture.
394 Type convertedIndex = typeConverter->convertType(rewriter.getIndexType());
395 Location loc = op.getLoc();
396 Value allocated;
397
398 if (useRuntime) {
399 // The instance is using the runtime library
400 auto ptrTy = LLVM::LLVMPointerType::get(getContext());
401
402 Value runtimeArgs;
403 // If present, materialize the runtime argument string on the stack
404 if (op.getRuntimeArgs().has_value()) {
405 SmallVector<int8_t> argStringVec(op.getRuntimeArgsAttr().begin(),
406 op.getRuntimeArgsAttr().end());
407 argStringVec.push_back('\0');
408 auto strAttr = mlir::DenseElementsAttr::get(
409 mlir::RankedTensorType::get({(int64_t)argStringVec.size()},
410 rewriter.getI8Type()),
411 llvm::ArrayRef(argStringVec));
412
413 auto arrayCst = LLVM::ConstantOp::create(
414 rewriter, loc,
415 LLVM::LLVMArrayType::get(rewriter.getI8Type(), argStringVec.size()),
416 strAttr);
417 auto cst1 = LLVM::ConstantOp::create(rewriter, loc,
418 rewriter.getI32IntegerAttr(1));
419 runtimeArgs = LLVM::AllocaOp::create(rewriter, loc, ptrTy,
420 arrayCst.getType(), cst1);
421 LLVM::LifetimeStartOp::create(rewriter, loc, runtimeArgs);
422 LLVM::StoreOp::create(rewriter, loc, arrayCst, runtimeArgs);
423 } else {
424 runtimeArgs = LLVM::ZeroOp::create(rewriter, loc, ptrTy).getResult();
425 }
426 // Call the state allocation function
427 auto rtModelPtr = LLVM::AddressOfOp::create(rewriter, loc, ptrTy,
428 op.getRuntimeModelAttr())
429 .getResult();
430 allocated =
431 LLVM::CallOp::create(rewriter, loc, {ptrTy},
432 runtime::APICallbacks::symNameAllocInstance,
433 {rtModelPtr, runtimeArgs})
434 .getResult();
435
436 if (op.getRuntimeArgs().has_value())
437 LLVM::LifetimeEndOp::create(rewriter, loc, runtimeArgs);
438
439 } else {
440 // The instance is not using the runtime library
441 FailureOr<LLVM::LLVMFuncOp> mallocFunc =
442 LLVM::lookupOrCreateMallocFn(rewriter, moduleOp, convertedIndex);
443 if (failed(mallocFunc))
444 return mallocFunc;
445
446 Value numStateBytes = LLVM::ConstantOp::create(
447 rewriter, loc, convertedIndex, model.numStateBytes);
448 allocated = LLVM::CallOp::create(rewriter, loc, mallocFunc.value(),
449 ValueRange{numStateBytes})
450 .getResult();
451 Value zero =
452 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI8Type(), 0);
453 LLVM::MemsetOp::create(rewriter, loc, allocated, zero, numStateBytes,
454 false);
455 }
456
457 // Call the model's 'initial' function if present.
458 if (model.initialFnSymbol) {
459 auto initialFnType = LLVM::LLVMFunctionType::get(
460 LLVM::LLVMVoidType::get(op.getContext()),
461 {LLVM::LLVMPointerType::get(op.getContext())});
462 LLVM::CallOp::create(rewriter, loc, initialFnType, model.initialFnSymbol,
463 ValueRange{allocated});
464 }
465
466 // Call the runtime's 'onInitialized' function if present.
467 if (useRuntime)
468 LLVM::CallOp::create(rewriter, loc, TypeRange{},
469 runtime::APICallbacks::symNameOnInitialized,
470 {allocated});
471
472 // Execute the body.
473 rewriter.inlineBlockBefore(&adaptor.getBody().getBlocks().front(), op,
474 {allocated});
475
476 // Call the model's 'final' function if present.
477 if (model.finalFnSymbol) {
478 auto finalFnType = LLVM::LLVMFunctionType::get(
479 LLVM::LLVMVoidType::get(op.getContext()),
480 {LLVM::LLVMPointerType::get(op.getContext())});
481 LLVM::CallOp::create(rewriter, loc, finalFnType, model.finalFnSymbol,
482 ValueRange{allocated});
483 }
484
485 if (useRuntime) {
486 LLVM::CallOp::create(rewriter, loc, TypeRange{},
487 runtime::APICallbacks::symNameDeleteInstance,
488 {allocated});
489 } else {
490 FailureOr<LLVM::LLVMFuncOp> freeFunc =
491 LLVM::lookupOrCreateFreeFn(rewriter, moduleOp);
492 if (failed(freeFunc))
493 return freeFunc;
494
495 LLVM::CallOp::create(rewriter, loc, freeFunc.value(),
496 ValueRange{allocated});
497 }
498
499 rewriter.eraseOp(op);
500 return success();
501 }
502};
503
504struct SimSetInputOpLowering : public ModelAwarePattern<arc::SimSetInputOp> {
505 using ModelAwarePattern::ModelAwarePattern;
506
507 LogicalResult
508 matchAndRewrite(arc::SimSetInputOp op, OpAdaptor adaptor,
509 ConversionPatternRewriter &rewriter) const final {
510 auto modelIt =
511 modelInfo.find(cast<SimModelInstanceType>(op.getInstance().getType())
512 .getModel()
513 .getValue());
514 ModelInfoMap &model = modelIt->second;
515
516 auto portIt = model.states.find(op.getInput());
517 if (portIt == model.states.end()) {
518 // If the port is not found in the state, it means the model does not
519 // actually use it. Thus this operation is a no-op.
520 rewriter.eraseOp(op);
521 return success();
522 }
523
524 StateInfo &port = portIt->second;
525 Value statePtr = createPtrToPortState(rewriter, op.getLoc(),
526 adaptor.getInstance(), port);
527 rewriter.replaceOpWithNewOp<LLVM::StoreOp>(op, adaptor.getValue(),
528 statePtr);
529
530 return success();
531 }
532};
533
534struct SimGetPortOpLowering : public ModelAwarePattern<arc::SimGetPortOp> {
535 using ModelAwarePattern::ModelAwarePattern;
536
537 LogicalResult
538 matchAndRewrite(arc::SimGetPortOp op, OpAdaptor adaptor,
539 ConversionPatternRewriter &rewriter) const final {
540 auto modelIt =
541 modelInfo.find(cast<SimModelInstanceType>(op.getInstance().getType())
542 .getModel()
543 .getValue());
544 ModelInfoMap &model = modelIt->second;
545
546 auto type = typeConverter->convertType(op.getValue().getType());
547 if (!type)
548 return failure();
549 auto portIt = model.states.find(op.getPort());
550 if (portIt == model.states.end()) {
551 // If the port is not found in the state, it means the model does not
552 // actually set it. Thus this operation returns 0.
553 rewriter.replaceOpWithNewOp<LLVM::ConstantOp>(op, type, 0);
554 return success();
555 }
556
557 StateInfo &port = portIt->second;
558 Value statePtr = createPtrToPortState(rewriter, op.getLoc(),
559 adaptor.getInstance(), port);
560 rewriter.replaceOpWithNewOp<LLVM::LoadOp>(op, type, statePtr);
561
562 return success();
563 }
564};
565
566struct SimStepOpLowering : public ModelAwarePattern<arc::SimStepOp> {
567 using ModelAwarePattern::ModelAwarePattern;
568
569 LogicalResult
570 matchAndRewrite(arc::SimStepOp op, OpAdaptor adaptor,
571 ConversionPatternRewriter &rewriter) const final {
572 StringRef modelName = cast<SimModelInstanceType>(op.getInstance().getType())
573 .getModel()
574 .getValue();
575
576 StringAttr evalFunc =
577 rewriter.getStringAttr(evalSymbolFromModelName(modelName));
578 rewriter.replaceOpWithNewOp<LLVM::CallOp>(op, mlir::TypeRange(), evalFunc,
579 adaptor.getInstance());
580
581 return success();
582 }
583};
584
585// Global string constants in the module.
586class StringCache {
587public:
588 Value getOrCreate(OpBuilder &b, StringRef formatStr) {
589 auto it = cache.find(formatStr);
590 if (it != cache.end()) {
591 return LLVM::AddressOfOp::create(b, b.getUnknownLoc(), it->second);
592 }
593
594 Location loc = b.getUnknownLoc();
595 LLVM::GlobalOp global;
596 {
597 OpBuilder::InsertionGuard guard(b);
598 ModuleOp m =
599 b.getInsertionBlock()->getParent()->getParentOfType<ModuleOp>();
600 b.setInsertionPointToStart(m.getBody());
601
602 SmallVector<char> strVec(formatStr.begin(), formatStr.end());
603 strVec.push_back(0);
604
605 auto name = llvm::formatv("_arc_str_{0}", cache.size()).str();
606 auto globalType = LLVM::LLVMArrayType::get(b.getI8Type(), strVec.size());
607 global = LLVM::GlobalOp::create(b, loc, globalType, /*isConstant=*/true,
608 LLVM::Linkage::Internal,
609 /*name=*/name, b.getStringAttr(strVec),
610 /*alignment=*/0);
611 }
612
613 cache[formatStr] = global;
614 return LLVM::AddressOfOp::create(b, loc, global);
615 }
616
617private:
618 llvm::StringMap<LLVM::GlobalOp> cache;
619};
620
621FailureOr<LLVM::CallOp> emitPrintfCall(OpBuilder &builder, Location loc,
622 StringCache &cache, StringRef formatStr,
623 ValueRange args) {
624 ModuleOp moduleOp =
625 builder.getInsertionBlock()->getParent()->getParentOfType<ModuleOp>();
626 // Lookup or create printf function symbol.
627 MLIRContext *ctx = builder.getContext();
628 auto printfFunc = LLVM::lookupOrCreateFn(builder, moduleOp, "printf",
629 LLVM::LLVMPointerType::get(ctx),
630 LLVM::LLVMVoidType::get(ctx), true);
631 if (failed(printfFunc))
632 return printfFunc;
633
634 Value formatStrPtr = cache.getOrCreate(builder, formatStr);
635 SmallVector<Value> argsVec(1, formatStrPtr);
636 argsVec.append(args.begin(), args.end());
637 return LLVM::CallOp::create(builder, loc, printfFunc.value(), argsVec);
638}
639
640/// Lowers SimEmitValueOp to a printf call. The integer will be printed in its
641/// entirety if it is of size up to size_t, and explicitly truncated otherwise.
642/// This pattern will mutate the global module.
643struct SimEmitValueOpLowering
644 : public OpConversionPattern<arc::SimEmitValueOp> {
645 SimEmitValueOpLowering(const TypeConverter &typeConverter,
646 MLIRContext *context, StringCache &formatStringCache)
647 : OpConversionPattern(typeConverter, context),
648 formatStringCache(formatStringCache) {}
649
650 LogicalResult
651 matchAndRewrite(arc::SimEmitValueOp op, OpAdaptor adaptor,
652 ConversionPatternRewriter &rewriter) const final {
653 auto valueType = dyn_cast<IntegerType>(adaptor.getValue().getType());
654 if (!valueType)
655 return failure();
656
657 Location loc = op.getLoc();
658
659 ModuleOp moduleOp = op->getParentOfType<ModuleOp>();
660 if (!moduleOp)
661 return failure();
662
663 SmallVector<Value> printfVariadicArgs;
664 SmallString<16> printfFormatStr;
665 int remainingBits = valueType.getWidth();
666 Value value = adaptor.getValue();
667
668 // Assumes the target platform uses 64bit for long long ints (%llx
669 // formatter).
670 constexpr llvm::StringRef intFormatter = "llx";
671 auto intType = IntegerType::get(getContext(), 64);
672 Value shiftValue = LLVM::ConstantOp::create(
673 rewriter, loc, rewriter.getIntegerAttr(valueType, intType.getWidth()));
674
675 if (valueType.getWidth() < intType.getWidth()) {
676 int width = llvm::divideCeil(valueType.getWidth(), 4);
677 printfFormatStr = llvm::formatv("%0{0}{1}", width, intFormatter);
678 printfVariadicArgs.push_back(
679 LLVM::ZExtOp::create(rewriter, loc, intType, value));
680 } else {
681 // Process the value in 64 bit chunks, starting from the least significant
682 // bits. Since we append chunks in low-to-high order, we reverse the
683 // vector to print them in the correct high-to-low order.
684 int otherChunkWidth = intType.getWidth() / 4;
685 int firstChunkWidth =
686 llvm::divideCeil(valueType.getWidth() % intType.getWidth(), 4);
687 if (firstChunkWidth == 0) { // print the full 64-bit hex or a subset.
688 firstChunkWidth = otherChunkWidth;
689 }
690
691 std::string firstChunkFormat =
692 llvm::formatv("%0{0}{1}", firstChunkWidth, intFormatter);
693 std::string otherChunkFormat =
694 llvm::formatv("%0{0}{1}", otherChunkWidth, intFormatter);
695
696 for (int i = 0; remainingBits > 0; ++i) {
697 // Append 64-bit chunks to the printf arguments, in low-to-high
698 // order. The integer is printed in hex format with zero padding.
699 printfVariadicArgs.push_back(
700 LLVM::TruncOp::create(rewriter, loc, intType, value));
701
702 // Zero-padded format specifier for fixed width, e.g. %01llx for 4 bits.
703 printfFormatStr.append(i == 0 ? firstChunkFormat : otherChunkFormat);
704
705 value =
706 LLVM::LShrOp::create(rewriter, loc, value, shiftValue).getResult();
707 remainingBits -= intType.getWidth();
708 }
709 }
710
711 std::reverse(printfVariadicArgs.begin(), printfVariadicArgs.end());
712
713 SmallString<16> formatStr = adaptor.getValueName();
714 formatStr.append(" = ");
715 formatStr.append(printfFormatStr);
716 formatStr.append("\n");
717
718 auto callOp = emitPrintfCall(rewriter, op->getLoc(), formatStringCache,
719 formatStr, printfVariadicArgs);
720 if (failed(callOp))
721 return failure();
722 rewriter.replaceOp(op, *callOp);
723
724 return success();
725 }
726
727 StringCache &formatStringCache;
728};
729
730//===----------------------------------------------------------------------===//
731// `sim` dialect lowerings
732//===----------------------------------------------------------------------===//
733
734// Helper struct to hold the format string and arguments for arcRuntimeFormat.
735struct FormatInfo {
736 SmallVector<FmtDescriptor> descriptors;
737 SmallVector<Value> args;
738};
739
740// Copies the given integer value into an alloca, returning a pointer to it.
741//
742// The alloca is rounded up to a 64-bit boundary and is written as little-endian
743// words of size 64-bits, to be compatible with the constructor of APInt.
744static Value reg2mem(ConversionPatternRewriter &rewriter, Location loc,
745 Value value) {
746 // Round up the type size to a 64-bit boundary.
747 int64_t origBitwidth = cast<IntegerType>(value.getType()).getWidth();
748 int64_t bitwidth = llvm::divideCeil(origBitwidth, 64) * 64;
749 int64_t numWords = bitwidth / 64;
750
751 // Create an alloca for the rounded up type.
752 LLVM::ConstantOp alloca_size =
753 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(), numWords);
754 auto ptrType = LLVM::LLVMPointerType::get(rewriter.getContext());
755 auto allocaOp = LLVM::AllocaOp::create(rewriter, loc, ptrType,
756 rewriter.getI64Type(), alloca_size);
757 LLVM::LifetimeStartOp::create(rewriter, loc, allocaOp);
758
759 // Copy `value` into the alloca, 64-bits at a time from the least significant
760 // bits first.
761 for (int64_t wordIdx = 0; wordIdx < numWords; ++wordIdx) {
762 Value cst = LLVM::ConstantOp::create(
763 rewriter, loc, rewriter.getIntegerType(origBitwidth), wordIdx * 64);
764 Value v = LLVM::LShrOp::create(rewriter, loc, value, cst);
765 if (origBitwidth > 64) {
766 v = LLVM::TruncOp::create(rewriter, loc, rewriter.getI64Type(), v);
767 } else if (origBitwidth < 64) {
768 v = LLVM::ZExtOp::create(rewriter, loc, rewriter.getI64Type(), v);
769 }
770 Value gep = LLVM::GEPOp::create(rewriter, loc, ptrType,
771 rewriter.getI64Type(), allocaOp, {wordIdx});
772 LLVM::StoreOp::create(rewriter, loc, v, gep);
773 }
774
775 return allocaOp;
776}
777
778// Statically folds a value of type sim::FormatStringType to a FormatInfo.
779static FailureOr<FormatInfo>
780foldFormatString(ConversionPatternRewriter &rewriter, Value fstringValue,
781 StringCache &cache) {
782 Operation *op = fstringValue.getDefiningOp();
783 return llvm::TypeSwitch<Operation *, FailureOr<FormatInfo>>(op)
784 .Case<sim::FormatCharOp>(
785 [&](sim::FormatCharOp op) -> FailureOr<FormatInfo> {
786 FmtDescriptor d = FmtDescriptor::createChar();
787 return FormatInfo{{d}, {op.getValue()}};
788 })
789 .Case<sim::FormatDecOp>([&](sim::FormatDecOp op)
790 -> FailureOr<FormatInfo> {
791 FmtDescriptor d = FmtDescriptor::createInt(
792 op.getValue().getType().getWidth(), 10, op.getIsLeftAligned(),
793 op.getSpecifierWidth().value_or(-1), false, op.getIsSigned());
794 return FormatInfo{{d}, {reg2mem(rewriter, op.getLoc(), op.getValue())}};
795 })
796 .Case<sim::FormatHexOp>([&](sim::FormatHexOp op)
797 -> FailureOr<FormatInfo> {
798 FmtDescriptor d = FmtDescriptor::createInt(
799 op.getValue().getType().getWidth(), 16, op.getIsLeftAligned(),
800 op.getSpecifierWidth().value_or(-1), op.getIsHexUppercase(), false);
801 return FormatInfo{{d}, {reg2mem(rewriter, op.getLoc(), op.getValue())}};
802 })
803 .Case<sim::FormatOctOp>([&](sim::FormatOctOp op)
804 -> FailureOr<FormatInfo> {
805 FmtDescriptor d = FmtDescriptor::createInt(
806 op.getValue().getType().getWidth(), 8, op.getIsLeftAligned(),
807 op.getSpecifierWidth().value_or(-1), false, false);
808 return FormatInfo{{d}, {reg2mem(rewriter, op.getLoc(), op.getValue())}};
809 })
810 .Case<sim::FormatLiteralOp>(
811 [&](sim::FormatLiteralOp op) -> FailureOr<FormatInfo> {
812 if (op.getLiteral().size() < 8 &&
813 op.getLiteral().find('\0') == StringRef::npos) {
814 // We can use the small string optimization.
815 FmtDescriptor d =
816 FmtDescriptor::createSmallLiteral(op.getLiteral());
817 return FormatInfo{{d}, {}};
818 }
819 FmtDescriptor d =
820 FmtDescriptor::createLiteral(op.getLiteral().size());
821 Value value = cache.getOrCreate(rewriter, op.getLiteral());
822 return FormatInfo{{d}, {value}};
823 })
824 .Case<sim::FormatStringConcatOp>(
825 [&](sim::FormatStringConcatOp op) -> FailureOr<FormatInfo> {
826 auto fmt = foldFormatString(rewriter, op.getInputs()[0], cache);
827 if (failed(fmt))
828 return failure();
829 for (auto input : op.getInputs().drop_front()) {
830 auto next = foldFormatString(rewriter, input, cache);
831 if (failed(next))
832 return failure();
833 fmt->descriptors.append(next->descriptors);
834 fmt->args.append(next->args);
835 }
836 return fmt;
837 })
838 .Default(
839 [](Operation *op) -> FailureOr<FormatInfo> { return failure(); });
840}
841
842FailureOr<LLVM::CallOp> emitFmtCall(OpBuilder &builder, Location loc,
843 StringCache &stringCache,
844 ArrayRef<FmtDescriptor> descriptors,
845 ValueRange args) {
846 ModuleOp moduleOp =
847 builder.getInsertionBlock()->getParent()->getParentOfType<ModuleOp>();
848 // Lookup or create the arcRuntimeFormat function symbol.
849 MLIRContext *ctx = builder.getContext();
850 auto func = LLVM::lookupOrCreateFn(
851 builder, moduleOp, runtime::APICallbacks::symNameFormat,
852 LLVM::LLVMPointerType::get(ctx), LLVM::LLVMVoidType::get(ctx), true);
853 if (failed(func))
854 return func;
855
856 StringRef rawDescriptors(reinterpret_cast<const char *>(descriptors.data()),
857 descriptors.size() * sizeof(FmtDescriptor));
858 Value fmtPtr = stringCache.getOrCreate(builder, rawDescriptors);
859
860 SmallVector<Value> argsVec(1, fmtPtr);
861 argsVec.append(args.begin(), args.end());
862 auto result = LLVM::CallOp::create(builder, loc, func.value(), argsVec);
863
864 for (Value arg : args) {
865 Operation *definingOp = arg.getDefiningOp();
866 if (auto alloca = dyn_cast_if_present<LLVM::AllocaOp>(definingOp)) {
867 LLVM::LifetimeEndOp::create(builder, loc, arg);
868 }
869 }
870
871 return result;
872}
873
874struct SimPrintFormattedProcOpLowering
875 : public OpConversionPattern<sim::PrintFormattedProcOp> {
876 SimPrintFormattedProcOpLowering(const TypeConverter &typeConverter,
877 MLIRContext *context,
878 StringCache &stringCache)
879 : OpConversionPattern<sim::PrintFormattedProcOp>(typeConverter, context),
880 stringCache(stringCache) {}
881
882 LogicalResult
883 matchAndRewrite(sim::PrintFormattedProcOp op, OpAdaptor adaptor,
884 ConversionPatternRewriter &rewriter) const override {
885 auto formatInfo = foldFormatString(rewriter, op.getInput(), stringCache);
886 if (failed(formatInfo))
887 return rewriter.notifyMatchFailure(op, "unsupported format string");
888
889 // Add the end descriptor.
890 formatInfo->descriptors.push_back(FmtDescriptor());
891
892 auto result = emitFmtCall(rewriter, op.getLoc(), stringCache,
893 formatInfo->descriptors, formatInfo->args);
894 if (failed(result))
895 return failure();
896 rewriter.replaceOp(op, result.value());
897
898 return success();
899 }
900
901 StringCache &stringCache;
902};
903
904} // namespace
905
906static LogicalResult convert(arc::ExecuteOp op, arc::ExecuteOp::Adaptor adaptor,
907 ConversionPatternRewriter &rewriter,
908 const TypeConverter &converter) {
909 // Convert the argument types in the body blocks.
910 if (failed(rewriter.convertRegionTypes(&op.getBody(), converter)))
911 return failure();
912
913 // Split the block at the current insertion point such that we can branch into
914 // the `arc.execute` body region, and have `arc.output` branch back to the
915 // point after the `arc.execute`.
916 auto *blockBefore = rewriter.getInsertionBlock();
917 auto *blockAfter =
918 rewriter.splitBlock(blockBefore, rewriter.getInsertionPoint());
919
920 // Branch to the entry block.
921 rewriter.setInsertionPointToEnd(blockBefore);
922 mlir::cf::BranchOp::create(rewriter, op.getLoc(), &op.getBody().front(),
923 adaptor.getInputs());
924
925 // Make all `arc.output` terminators branch to the block after the
926 // `arc.execute` op.
927 for (auto &block : op.getBody()) {
928 auto outputOp = dyn_cast<arc::OutputOp>(block.getTerminator());
929 if (!outputOp)
930 continue;
931 rewriter.setInsertionPointToEnd(&block);
932 rewriter.replaceOpWithNewOp<mlir::cf::BranchOp>(outputOp, blockAfter,
933 outputOp.getOperands());
934 }
935
936 // Inline the body region between the before and after blocks.
937 rewriter.inlineRegionBefore(op.getBody(), blockAfter);
938
939 // Add arguments to the block after the `arc.execute`, replace the op's
940 // results with the arguments, then perform block signature conversion.
941 SmallVector<Value> args;
942 args.reserve(op.getNumResults());
943 for (auto result : op.getResults())
944 args.push_back(blockAfter->addArgument(result.getType(), result.getLoc()));
945 rewriter.replaceOp(op, args);
946 auto conversion = converter.convertBlockSignature(blockAfter);
947 if (!conversion)
948 return failure();
949 rewriter.applySignatureConversion(blockAfter, *conversion, &converter);
950 return success();
951}
952
953//===----------------------------------------------------------------------===//
954// Runtime Implementation
955//===----------------------------------------------------------------------===//
956
957template <typename T, typename = std::enable_if_t<std::is_integral<T>::value>>
958static LLVM::GlobalOp
959buildGlobalConstantIntArray(OpBuilder &builder, Location loc, Twine symName,
960 SmallVectorImpl<T> &data,
961 unsigned alignment = alignof(T)) {
962 auto intType = builder.getIntegerType(8 * sizeof(T));
963 Attribute denseAttr = mlir::DenseElementsAttr::get(
964 mlir::RankedTensorType::get({(int64_t)data.size()}, intType),
965 llvm::ArrayRef(data));
966 auto globalOp = LLVM::GlobalOp::create(
967 builder, loc, LLVM::LLVMArrayType::get(intType, data.size()),
968 /*isConstant=*/true, LLVM::Linkage::Internal,
969 builder.getStringAttr(symName), denseAttr);
970 globalOp.setAlignmentAttr(builder.getI64IntegerAttr(alignment));
971 return globalOp;
972}
973
974// Construct a raw constant byte array from a vector of struct values
975template <typename T>
976static LLVM::GlobalOp
977buildGlobalConstantRuntimeStructArray(OpBuilder &builder, Location loc,
978 Twine symName,
979 SmallVectorImpl<T> &array) {
980 assert(!array.empty());
981 static_assert(std::is_standard_layout<T>(),
982 "Runtime struct must have standard layout");
983 int64_t numBytes = sizeof(T) * array.size();
984 Attribute denseAttr = mlir::DenseElementsAttr::get(
985 mlir::RankedTensorType::get({numBytes}, builder.getI8Type()),
986 llvm::ArrayRef(reinterpret_cast<uint8_t *>(array.data()), numBytes));
987 auto globalOp = LLVM::GlobalOp::create(
988 builder, loc, LLVM::LLVMArrayType::get(builder.getI8Type(), numBytes),
989 /*isConstant=*/true, LLVM::Linkage::Internal,
990 builder.getStringAttr(symName), denseAttr, alignof(T));
991 return globalOp;
992}
993
995 : public OpConversionPattern<arc::RuntimeModelOp> {
996 using OpConversionPattern::OpConversionPattern;
997
998 static constexpr uint64_t runtimeApiVersion = ARC_RUNTIME_API_VERSION;
999
1000 // Build the constant ArcModelTraceInfo struct and its members
1001 LLVM::GlobalOp
1002 buildTraceInfoStruct(arc::RuntimeModelOp &op,
1003 ConversionPatternRewriter &rewriter) const {
1004 if (!op.getTraceTaps().has_value() || op.getTraceTaps()->empty())
1005 return {};
1006 // Construct the array of tap names/aliases
1007 SmallVector<char> namesArray;
1008 SmallVector<ArcTraceTap> tapArray;
1009 tapArray.reserve(op.getTraceTaps()->size());
1010 for (auto attr : op.getTraceTapsAttr()) {
1011 auto tap = cast<TraceTapAttr>(attr);
1012 assert(!tap.getNames().empty() &&
1013 "Expected trace tap to have at least one name");
1014 for (auto alias : tap.getNames()) {
1015 auto aliasStr = cast<StringAttr>(alias);
1016 namesArray.append(aliasStr.begin(), aliasStr.end());
1017 namesArray.push_back('\0');
1018 }
1019 ArcTraceTap tapStruct;
1020 tapStruct.stateOffset = tap.getStateOffset();
1021 tapStruct.nameOffset = namesArray.size() - 1;
1022 tapStruct.typeBits = tap.getSigType().getValue().getIntOrFloatBitWidth();
1023 tapStruct.reserved = 0;
1024 tapArray.emplace_back(tapStruct);
1025 }
1026 auto ptrTy = LLVM::LLVMPointerType::get(getContext());
1027 auto namesGlobal = buildGlobalConstantIntArray(
1028 rewriter, op.getLoc(), "_arc_tap_names_" + op.getName(), namesArray);
1029 auto traceTapsArrayGlobal = buildGlobalConstantRuntimeStructArray(
1030 rewriter, op.getLoc(), "_arc_trace_taps_" + op.getName(), tapArray);
1031
1032 //
1033 // struct ArcModelTraceInfo {
1034 // uint64_t numTraceTaps;
1035 // struct ArcTraceTap *traceTaps;
1036 // const char *traceTapNames;
1037 // uint64_t traceBufferCapacity;
1038 // };
1039 //
1040 auto traceInfoStructType = LLVM::LLVMStructType::getLiteral(
1041 getContext(),
1042 {rewriter.getI64Type(), ptrTy, ptrTy, rewriter.getI64Type()});
1043 static_assert(sizeof(ArcModelTraceInfo) == 32 &&
1044 "Unexpected size of ArcModelTraceInfo struct");
1045
1046 auto globalSymName =
1047 rewriter.getStringAttr("_arc_trace_info_" + op.getName());
1048 auto traceInfoGlobalOp = LLVM::GlobalOp::create(
1049 rewriter, op.getLoc(), traceInfoStructType,
1050 /*isConstant=*/false, LLVM::Linkage::Internal, globalSymName,
1051 Attribute{}, alignof(ArcModelTraceInfo));
1052 OpBuilder::InsertionGuard g(rewriter);
1053
1054 // Struct Initializer
1055 Region &initRegion = traceInfoGlobalOp.getInitializerRegion();
1056 Block *initBlock = rewriter.createBlock(&initRegion);
1057 rewriter.setInsertionPointToStart(initBlock);
1058
1059 auto numTraceTapsCst = LLVM::ConstantOp::create(
1060 rewriter, op.getLoc(), rewriter.getI64IntegerAttr(tapArray.size()));
1061 auto traceTapArrayAddr =
1062 LLVM::AddressOfOp::create(rewriter, op.getLoc(), traceTapsArrayGlobal);
1063 auto tapNameArrayAddr =
1064 LLVM::AddressOfOp::create(rewriter, op.getLoc(), namesGlobal);
1065 auto bufferCapacityCst = LLVM::ConstantOp::create(
1066 rewriter, op.getLoc(),
1067 rewriter.getI64IntegerAttr(runtime::defaultTraceBufferCapacity));
1068
1069 Value initStruct =
1070 LLVM::PoisonOp::create(rewriter, op.getLoc(), traceInfoStructType);
1071
1072 // Field: uint64_t numTraceTaps
1073 initStruct =
1074 LLVM::InsertValueOp::create(rewriter, op.getLoc(), initStruct,
1075 numTraceTapsCst, ArrayRef<int64_t>{0});
1076 static_assert(offsetof(ArcModelTraceInfo, numTraceTaps) == 0,
1077 "Unexpected offset of field numTraceTaps");
1078 // Field: struct ArcTraceTap *traceTaps
1079 initStruct =
1080 LLVM::InsertValueOp::create(rewriter, op.getLoc(), initStruct,
1081 traceTapArrayAddr, ArrayRef<int64_t>{1});
1082 static_assert(offsetof(ArcModelTraceInfo, traceTaps) == 8,
1083 "Unexpected offset of field traceTaps");
1084 // Field: const char *traceTapNames
1085 initStruct =
1086 LLVM::InsertValueOp::create(rewriter, op.getLoc(), initStruct,
1087 tapNameArrayAddr, ArrayRef<int64_t>{2});
1088 static_assert(offsetof(ArcModelTraceInfo, traceTapNames) == 16,
1089 "Unexpected offset of field traceTapNames");
1090 // Field: uint64_t traceBufferCapacity
1091 initStruct =
1092 LLVM::InsertValueOp::create(rewriter, op.getLoc(), initStruct,
1093 bufferCapacityCst, ArrayRef<int64_t>{3});
1094 static_assert(offsetof(ArcModelTraceInfo, traceBufferCapacity) == 24,
1095 "Unexpected offset of field traceBufferCapacity");
1096 LLVM::ReturnOp::create(rewriter, op.getLoc(), initStruct);
1097
1098 return traceInfoGlobalOp;
1099 }
1100
1101 // Create a global LLVM struct containing the RuntimeModel metadata
1102 LogicalResult
1103 matchAndRewrite(arc::RuntimeModelOp op, OpAdaptor adaptor,
1104 ConversionPatternRewriter &rewriter) const final {
1105
1106 auto ptrTy = LLVM::LLVMPointerType::get(getContext());
1107 auto modelInfoStructType = LLVM::LLVMStructType::getLiteral(
1108 getContext(),
1109 {rewriter.getI64Type(), rewriter.getI64Type(), ptrTy, ptrTy});
1110 static_assert(sizeof(ArcRuntimeModelInfo) == 32 &&
1111 "Unexpected size of ArcRuntimeModelInfo struct");
1112
1113 rewriter.setInsertionPoint(op);
1114 auto traceInfoGlobal = buildTraceInfoStruct(op, rewriter);
1115
1116 // Construct the Model Name String GlobalOp
1117 SmallVector<char, 16> modNameArray(op.getName().begin(),
1118 op.getName().end());
1119 modNameArray.push_back('\0');
1120 auto nameGlobalType =
1121 LLVM::LLVMArrayType::get(rewriter.getI8Type(), modNameArray.size());
1122 auto globalSymName =
1123 rewriter.getStringAttr("_arc_mod_name_" + op.getName());
1124 auto nameGlobal = LLVM::GlobalOp::create(
1125 rewriter, op.getLoc(), nameGlobalType, /*isConstant=*/true,
1126 LLVM::Linkage::Internal,
1127 /*name=*/globalSymName, rewriter.getStringAttr(modNameArray),
1128 /*alignment=*/0);
1129
1130 // Construct the Model Info Struct GlobalOp
1131 // Note: The struct is supposed to be constant at runtime, but contains the
1132 // relocatable address of another symbol, so it should not be placed in the
1133 // "rodata" section.
1134 auto modInfoGlobalOp =
1135 LLVM::GlobalOp::create(rewriter, op.getLoc(), modelInfoStructType,
1136 /*isConstant=*/false, LLVM::Linkage::External,
1137 op.getSymName(), Attribute{});
1138
1139 // Struct Initializer
1140 Region &initRegion = modInfoGlobalOp.getInitializerRegion();
1141 Block *initBlock = rewriter.createBlock(&initRegion);
1142 rewriter.setInsertionPointToStart(initBlock);
1143 auto apiVersionCst = LLVM::ConstantOp::create(
1144 rewriter, op.getLoc(), rewriter.getI64IntegerAttr(runtimeApiVersion));
1145 auto numStateBytesCst = LLVM::ConstantOp::create(rewriter, op.getLoc(),
1146 op.getNumStateBytesAttr());
1147 auto nameAddr =
1148 LLVM::AddressOfOp::create(rewriter, op.getLoc(), nameGlobal);
1149 Value traceInfoPtr;
1150 if (traceInfoGlobal)
1151 traceInfoPtr =
1152 LLVM::AddressOfOp::create(rewriter, op.getLoc(), traceInfoGlobal);
1153 else
1154 traceInfoPtr = LLVM::ZeroOp::create(rewriter, op.getLoc(), ptrTy);
1155
1156 Value initStruct =
1157 LLVM::PoisonOp::create(rewriter, op.getLoc(), modelInfoStructType);
1158
1159 // Field: uint64_t apiVersion
1160 initStruct = LLVM::InsertValueOp::create(
1161 rewriter, op.getLoc(), initStruct, apiVersionCst, ArrayRef<int64_t>{0});
1162 static_assert(offsetof(ArcRuntimeModelInfo, apiVersion) == 0,
1163 "Unexpected offset of field apiVersion");
1164 // Field: uint64_t numStateBytes
1165 initStruct =
1166 LLVM::InsertValueOp::create(rewriter, op.getLoc(), initStruct,
1167 numStateBytesCst, ArrayRef<int64_t>{1});
1168 static_assert(offsetof(ArcRuntimeModelInfo, numStateBytes) == 8,
1169 "Unexpected offset of field numStateBytes");
1170 // Field: const char *modelName
1171 initStruct = LLVM::InsertValueOp::create(rewriter, op.getLoc(), initStruct,
1172 nameAddr, ArrayRef<int64_t>{2});
1173 static_assert(offsetof(ArcRuntimeModelInfo, modelName) == 16,
1174 "Unexpected offset of field modelName");
1175 // Field: struct ArcModelTraceInfo *traceInfo
1176 initStruct = LLVM::InsertValueOp::create(
1177 rewriter, op.getLoc(), initStruct, traceInfoPtr, ArrayRef<int64_t>{3});
1178 static_assert(offsetof(ArcRuntimeModelInfo, traceInfo) == 24,
1179 "Unexpected offset of field traceInfo");
1180
1181 LLVM::ReturnOp::create(rewriter, op.getLoc(), initStruct);
1182
1183 rewriter.replaceOp(op, modInfoGlobalOp);
1184 return success();
1185 }
1186};
1187
1188//===----------------------------------------------------------------------===//
1189// Pass Implementation
1190//===----------------------------------------------------------------------===//
1191
1192namespace {
1193struct LowerArcToLLVMPass
1194 : public circt::impl::LowerArcToLLVMBase<LowerArcToLLVMPass> {
1195 void runOnOperation() override;
1196};
1197} // namespace
1198
1199void LowerArcToLLVMPass::runOnOperation() {
1200 // Replace any `i0` values with an `hw.constant 0 : i0` to avoid later issues
1201 // in LLVM conversion.
1202 {
1203 DenseMap<Region *, hw::ConstantOp> zeros;
1204 getOperation().walk([&](Operation *op) {
1205 if (op->hasTrait<OpTrait::ConstantLike>())
1206 return;
1207 for (auto result : op->getResults()) {
1208 auto type = dyn_cast<IntegerType>(result.getType());
1209 if (!type || type.getWidth() != 0)
1210 continue;
1211 auto *region = op->getParentRegion();
1212 auto &zero = zeros[region];
1213 if (!zero) {
1214 auto builder = OpBuilder::atBlockBegin(&region->front());
1215 zero = hw::ConstantOp::create(builder, result.getLoc(),
1216 APInt::getZero(0));
1217 }
1218 result.replaceAllUsesWith(zero);
1219 }
1220 });
1221 }
1222
1223 // Collect the symbols in the root op such that the HW-to-LLVM lowering can
1224 // create LLVM globals with non-colliding names.
1225 Namespace globals;
1226 SymbolCache cache;
1227 cache.addDefinitions(getOperation());
1228 globals.add(cache);
1229
1230 // Setup the conversion target. Explicitly mark `scf.yield` legal since it
1231 // does not have a conversion itself, which would cause it to fail
1232 // legalization and for the conversion to abort. (It relies on its parent op's
1233 // conversion to remove it.)
1234 LLVMConversionTarget target(getContext());
1235 target.addLegalOp<mlir::ModuleOp>();
1236 target.addLegalOp<scf::YieldOp>(); // quirk of SCF dialect conversion
1237
1238 // Mark sim::Format*Op as legal. These are not converted to LLVM, but the
1239 // lowering of sim::PrintFormattedOp walks them to build up its format string.
1240 // They are all marked Pure so are removed after the conversion.
1241 target.addLegalOp<sim::FormatLiteralOp, sim::FormatDecOp, sim::FormatHexOp,
1242 sim::FormatBinOp, sim::FormatOctOp, sim::FormatCharOp,
1243 sim::FormatStringConcatOp>();
1244
1245 // Setup the arc dialect type conversion.
1246 LLVMTypeConverter converter(&getContext());
1247 converter.addConversion([&](seq::ClockType type) {
1248 return IntegerType::get(type.getContext(), 1);
1249 });
1250 converter.addConversion([&](StorageType type) {
1251 return LLVM::LLVMPointerType::get(type.getContext());
1252 });
1253 converter.addConversion([&](MemoryType type) {
1254 return LLVM::LLVMPointerType::get(type.getContext());
1255 });
1256 converter.addConversion([&](StateType type) {
1257 return LLVM::LLVMPointerType::get(type.getContext());
1258 });
1259 converter.addConversion([&](SimModelInstanceType type) {
1260 return LLVM::LLVMPointerType::get(type.getContext());
1261 });
1262 converter.addConversion([&](sim::FormatStringType type) {
1263 return LLVM::LLVMPointerType::get(type.getContext());
1264 });
1265
1266 // Setup the conversion patterns.
1267 ConversionPatternSet patterns(&getContext(), converter);
1268
1269 // MLIR patterns.
1270 populateSCFToControlFlowConversionPatterns(patterns);
1271 populateFuncToLLVMConversionPatterns(converter, patterns);
1272 cf::populateControlFlowToLLVMConversionPatterns(converter, patterns);
1273 arith::populateArithToLLVMConversionPatterns(converter, patterns);
1274 index::populateIndexToLLVMConversionPatterns(converter, patterns);
1275 populateAnyFunctionOpInterfaceTypeConversionPattern(patterns, converter);
1276
1277 // CIRCT patterns.
1278 DenseMap<std::pair<Type, ArrayAttr>, LLVM::GlobalOp> constAggregateGlobalsMap;
1280 std::optional<HWToLLVMArraySpillCache> spillCacheOpt =
1282 {
1283 OpBuilder spillBuilder(getOperation());
1284 spillCacheOpt->spillNonHWOps(spillBuilder, converter, getOperation());
1285 }
1286 populateHWToLLVMConversionPatterns(converter, patterns, globals,
1287 constAggregateGlobalsMap, spillCacheOpt);
1288
1291
1292 // Arc patterns.
1293 // clang-format off
1294 patterns.add<
1295 AllocMemoryOpLowering,
1296 AllocStateLikeOpLowering<arc::AllocStateOp>,
1297 AllocStateLikeOpLowering<arc::RootInputOp>,
1298 AllocStateLikeOpLowering<arc::RootOutputOp>,
1299 AllocStorageOpLowering,
1300 ClockGateOpLowering,
1301 ClockInvOpLowering,
1302 MemoryReadOpLowering,
1303 MemoryWriteOpLowering,
1304 ModelOpLowering,
1305 ReplaceOpWithInputPattern<seq::ToClockOp>,
1306 ReplaceOpWithInputPattern<seq::FromClockOp>,
1308 SeqConstClockLowering,
1309 StateReadOpLowering,
1310 StateWriteOpLowering,
1311 StorageGetOpLowering,
1312 ZeroCountOpLowering
1313 >(converter, &getContext());
1314 // clang-format on
1315 patterns.add<ExecuteOp>(convert);
1316
1317 StringCache stringCache;
1318 patterns.add<SimEmitValueOpLowering, SimPrintFormattedProcOpLowering>(
1319 converter, &getContext(), stringCache);
1320
1321 auto &modelInfo = getAnalysis<ModelInfoAnalysis>();
1322 llvm::DenseMap<StringRef, ModelInfoMap> modelMap(modelInfo.infoMap.size());
1323 for (auto &[_, modelInfo] : modelInfo.infoMap) {
1324 llvm::DenseMap<StringRef, StateInfo> states(modelInfo.states.size());
1325 for (StateInfo &stateInfo : modelInfo.states)
1326 states.insert({stateInfo.name, stateInfo});
1327 modelMap.insert(
1328 {modelInfo.name,
1329 ModelInfoMap{modelInfo.numStateBytes, std::move(states),
1330 modelInfo.initialFnSym, modelInfo.finalFnSym}});
1331 }
1332
1333 patterns.add<SimInstantiateOpLowering, SimSetInputOpLowering,
1334 SimGetPortOpLowering, SimStepOpLowering>(
1335 converter, &getContext(), modelMap);
1336
1337 // Apply the conversion.
1338 ConversionConfig config;
1339 config.allowPatternRollback = false;
1340 if (failed(applyFullConversion(getOperation(), target, std::move(patterns),
1341 config)))
1342 signalPassFailure();
1343}
1344
1345std::unique_ptr<OperationPass<ModuleOp>> circt::createLowerArcToLLVMPass() {
1346 return std::make_unique<LowerArcToLLVMPass>();
1347}
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
static LLVM::GlobalOp buildGlobalConstantIntArray(OpBuilder &builder, Location loc, Twine symName, SmallVectorImpl< T > &data, unsigned alignment=alignof(T))
static LLVM::GlobalOp buildGlobalConstantRuntimeStructArray(OpBuilder &builder, Location loc, Twine symName, SmallVectorImpl< T > &array)
static llvm::Twine evalSymbolFromModelName(StringRef modelName)
static LogicalResult convert(arc::ExecuteOp op, arc::ExecuteOp::Adaptor adaptor, ConversionPatternRewriter &rewriter, const TypeConverter &converter)
Extension of RewritePatternSet that allows adding matchAndRewrite functions with op adaptors and Conv...
A namespace that is used to store existing names and generate new names in some scope within the IR.
Definition Namespace.h:30
void add(mlir::ModuleOp module)
Definition Namespace.h:48
void addDefinitions(mlir::Operation *top)
Populate the symbol cache with all symbol-defining operations within the 'top' operation.
Definition SymCache.cpp:23
Default symbol cache implementation; stores associations between names (StringAttr's) to mlir::Operat...
Definition SymCache.h:85
create(data_type, value)
Definition hw.py:433
#define ARC_RUNTIME_API_VERSION
Version of the combined public and internal API.
Definition Common.h:27
Definition arc.py:1
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
void populateCombToArithConversionPatterns(TypeConverter &converter, RewritePatternSet &patterns)
void populateCombToLLVMConversionPatterns(mlir::LLVMTypeConverter &converter, RewritePatternSet &patterns)
Get the Comb to LLVM conversion patterns.
void populateHWToLLVMTypeConversions(mlir::LLVMTypeConverter &converter)
Get the HW to LLVM type conversions.
void populateHWToLLVMConversionPatterns(mlir::LLVMTypeConverter &converter, RewritePatternSet &patterns, Namespace &globals, DenseMap< std::pair< Type, ArrayAttr >, mlir::LLVM::GlobalOp > &constAggregateGlobalsMap, std::optional< HWToLLVMArraySpillCache > &spillCacheOpt)
Get the HW to LLVM conversion patterns.
std::unique_ptr< OperationPass< ModuleOp > > createLowerArcToLLVMPass()
Definition hw.py:1
Definition sim.py:1
Static information for a compiled hardware model, generated by the MLIR lowering.
Definition Common.h:70
uint32_t typeBits
Bit width of the traced signal.
Definition TraceTaps.h:28
uint64_t stateOffset
Byte offset of the traced value within the model state.
Definition TraceTaps.h:23
uint64_t nameOffset
Byte offset to the null terminator of this signal's last alias in the names array.
Definition TraceTaps.h:26
uint32_t reserved
Padding and reserved for future use.
Definition TraceTaps.h:30
LLVM::GlobalOp buildTraceInfoStruct(arc::RuntimeModelOp &op, ConversionPatternRewriter &rewriter) const
static constexpr uint64_t runtimeApiVersion
LogicalResult matchAndRewrite(arc::RuntimeModelOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const final
Helper class mapping array values (HW or LLVM Dialect) to pointers to buffers containing the array va...
Definition HWToLLVM.h:47