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