CIRCT 24.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
28#include "mlir/Conversion/ArithToLLVM/ArithToLLVM.h"
29#include "mlir/Conversion/ControlFlowToLLVM/ControlFlowToLLVM.h"
30#include "mlir/Conversion/FuncToLLVM/ConvertFuncToLLVM.h"
31#include "mlir/Conversion/IndexToLLVM/IndexToLLVM.h"
32#include "mlir/Conversion/LLVMCommon/ConversionTarget.h"
33#include "mlir/Conversion/LLVMCommon/TypeConverter.h"
34#include "mlir/Conversion/SCFToControlFlow/SCFToControlFlow.h"
35#include "mlir/Conversion/UBToLLVM/UBToLLVM.h"
36#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
37#include "mlir/Dialect/Func/IR/FuncOps.h"
38#include "mlir/Dialect/Index/IR/IndexOps.h"
39#include "mlir/Dialect/LLVMIR/FunctionCallUtils.h"
40#include "mlir/Dialect/LLVMIR/LLVMAttrs.h"
41#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
42#include "mlir/Dialect/SCF/IR/SCF.h"
43#include "mlir/IR/Builders.h"
44#include "mlir/IR/BuiltinDialect.h"
45#include "mlir/Interfaces/DataLayoutInterfaces.h"
46#include "mlir/Pass/Pass.h"
47#include "mlir/Transforms/DialectConversion.h"
48#include "llvm/Support/Debug.h"
49#include "llvm/Support/FormatVariadic.h"
50
51#include <cstddef>
52
53#define DEBUG_TYPE "lower-arc-to-llvm"
54
55namespace circt {
56#define GEN_PASS_DEF_LOWERARCTOLLVM
57#include "circt/Conversion/Passes.h.inc"
58} // namespace circt
59
60using namespace mlir;
61using namespace circt;
62using namespace arc;
63using namespace hw;
64using namespace runtime;
65
66//===----------------------------------------------------------------------===//
67// Lowering Patterns
68//===----------------------------------------------------------------------===//
69
70static llvm::Twine evalSymbolFromModelName(StringRef modelName) {
71 return modelName + "_eval";
72}
73
74namespace {
75
76struct ModelOpLowering : public OpConversionPattern<arc::ModelOp> {
77 using OpConversionPattern::OpConversionPattern;
78 LogicalResult
79 matchAndRewrite(arc::ModelOp op, OpAdaptor adaptor,
80 ConversionPatternRewriter &rewriter) const final {
81 {
82 IRRewriter::InsertionGuard guard(rewriter);
83 rewriter.setInsertionPointToEnd(&op.getBodyBlock());
84 func::ReturnOp::create(rewriter, op.getLoc());
85 }
86 auto funcName =
87 rewriter.getStringAttr(evalSymbolFromModelName(op.getName()));
88 auto funcType =
89 rewriter.getFunctionType(op.getBody().getArgumentTypes(), {});
90 auto func =
91 mlir::func::FuncOp::create(rewriter, op.getLoc(), funcName, funcType);
92 rewriter.inlineRegionBefore(op.getRegion(), func.getBody(), func.end());
93 rewriter.eraseOp(op);
94 return success();
95 }
96};
97
98struct AllocStorageOpLowering
99 : public OpConversionPattern<arc::AllocStorageOp> {
100 using OpConversionPattern::OpConversionPattern;
101 LogicalResult
102 matchAndRewrite(arc::AllocStorageOp op, OpAdaptor adaptor,
103 ConversionPatternRewriter &rewriter) const final {
104 auto type = typeConverter->convertType(op.getType());
105 if (!op.getOffset().has_value())
106 return failure();
107 rewriter.replaceOpWithNewOp<LLVM::GEPOp>(op, type, rewriter.getI8Type(),
108 adaptor.getInput(),
109 LLVM::GEPArg(*op.getOffset()));
110 return success();
111 }
112};
113
114template <class ConcreteOp>
115struct AllocStateLikeOpLowering : public OpConversionPattern<ConcreteOp> {
117 using OpConversionPattern<ConcreteOp>::typeConverter;
118 using OpAdaptor = typename ConcreteOp::Adaptor;
119
120 LogicalResult
121 matchAndRewrite(ConcreteOp op, OpAdaptor adaptor,
122 ConversionPatternRewriter &rewriter) const final {
123 // Get a pointer to the correct offset in the storage.
124 auto offsetAttr = op->template getAttrOfType<IntegerAttr>("offset");
125 if (!offsetAttr)
126 return failure();
127 Value ptr = LLVM::GEPOp::create(
128 rewriter, op->getLoc(), adaptor.getStorage().getType(),
129 rewriter.getI8Type(), adaptor.getStorage(),
130 LLVM::GEPArg(offsetAttr.getValue().getZExtValue()));
131 rewriter.replaceOp(op, ptr);
132 return success();
133 }
134};
135
136struct StateReadOpLowering : public OpConversionPattern<arc::StateReadOp> {
137 using OpConversionPattern::OpConversionPattern;
138 LogicalResult
139 matchAndRewrite(arc::StateReadOp op, OpAdaptor adaptor,
140 ConversionPatternRewriter &rewriter) const final {
141 // Loading an ArrayRef is a no-op as ArrayRefs are accessed by reference.
142 if (isa<ArrayRefType>(op.getType())) {
143 rewriter.replaceOp(op, adaptor.getState());
144 return success();
145 }
146
147 auto type = typeConverter->convertType(op.getType());
148 rewriter.replaceOpWithNewOp<LLVM::LoadOp>(op, type, adaptor.getState());
149 return success();
150 }
151};
152
153struct StateWriteOpLowering : public OpConversionPattern<arc::StateWriteOp> {
154 using OpConversionPattern::OpConversionPattern;
155 LogicalResult
156 matchAndRewrite(arc::StateWriteOp op, OpAdaptor adaptor,
157 ConversionPatternRewriter &rewriter) const final {
158 if (!isa<ArrayRefType>(op.getValue().getType())) {
159 rewriter.replaceOpWithNewOp<LLVM::StoreOp>(op, adaptor.getValue(),
160 adaptor.getState());
161 return success();
162 }
163
164 int numBytes = op.getState().getType().getByteWidth();
165 Value size = LLVM::ConstantOp::create(rewriter, op.getLoc(),
166 rewriter.getI64Type(), numBytes);
167 rewriter.replaceOpWithNewOp<LLVM::MemcpyOp>(
168 op, adaptor.getState(), adaptor.getValue(), size, /*volatile=*/false);
169 return success();
170 }
171};
172
173struct AsContextOpLowering : public OpConversionPattern<arc::AsContextOp> {
174 using OpConversionPattern::OpConversionPattern;
175 LogicalResult
176 matchAndRewrite(arc::AsContextOp op, OpAdaptor adaptor,
177 ConversionPatternRewriter &rewriter) const final {
178 // Context, root storage and instances resolve to the same pointer
179 rewriter.replaceOp(op, adaptor.getInput());
180 return llvm::success();
181 }
182};
183
184//===----------------------------------------------------------------------===//
185// Time Operations Lowering
186//===----------------------------------------------------------------------===//
187
188struct CurrentTimeOpLowering : public OpConversionPattern<arc::CurrentTimeOp> {
189 using OpConversionPattern::OpConversionPattern;
190 LogicalResult
191 matchAndRewrite(arc::CurrentTimeOp op, OpAdaptor adaptor,
192 ConversionPatternRewriter &rewriter) const final {
193 // Time is stored at offset 0 in storage (no offset needed).
194 Value ptr = adaptor.getArcContext();
195 rewriter.replaceOpWithNewOp<LLVM::LoadOp>(op, rewriter.getI64Type(), ptr);
196 return success();
197 }
198};
199
200// Lower `llhd.constant_time` to an `i64` LLVM constant holding the time in
201// femtoseconds. Time attributes with non-zero delta or epsilon, units smaller
202// than `fs`, or values that overflow `i64` femtoseconds are rejected.
203struct ConstantTimeOpLowering
204 : public OpConversionPattern<llhd::ConstantTimeOp> {
205 using OpConversionPattern::OpConversionPattern;
206 LogicalResult
207 matchAndRewrite(llhd::ConstantTimeOp op, OpAdaptor adaptor,
208 ConversionPatternRewriter &rewriter) const final {
209 auto attr = op.getValue();
210 if (attr.getDelta() != 0 || attr.getEpsilon() != 0)
211 return rewriter.notifyMatchFailure(
212 op, "non-zero delta or epsilon time components are not supported");
213 uint64_t value = attr.getTime();
214 StringRef unit = attr.getTimeUnit();
215 uint64_t scale;
216 if (unit == "fs")
217 scale = 1;
218 else if (unit == "ps")
219 scale = 1'000ULL;
220 else if (unit == "ns")
221 scale = 1'000'000ULL;
222 else if (unit == "us")
223 scale = 1'000'000'000ULL;
224 else if (unit == "ms")
225 scale = 1'000'000'000'000ULL;
226 else if (unit == "s")
227 scale = 1'000'000'000'000'000ULL;
228 else
229 return rewriter.notifyMatchFailure(
230 op, "time units smaller than `fs` are not supported");
231 if (value > std::numeric_limits<uint64_t>::max() / scale)
232 return rewriter.notifyMatchFailure(
233 op, "time value does not fit into `i64` femtoseconds");
234 rewriter.replaceOpWithNewOp<LLVM::ConstantOp>(op, rewriter.getI64Type(),
235 value * scale);
236 return success();
237 }
238};
239
240// `llhd.int_to_time` is a no-op
241struct IntToTimeOpLowering : public OpConversionPattern<llhd::IntToTimeOp> {
242 using OpConversionPattern::OpConversionPattern;
243 LogicalResult
244 matchAndRewrite(llhd::IntToTimeOp op, OpAdaptor adaptor,
245 ConversionPatternRewriter &rewriter) const final {
246 rewriter.replaceOp(op, adaptor.getInput());
247 return success();
248 }
249};
250
251// `llhd.time_to_int` is a no-op
252struct TimeToIntOpLowering : public OpConversionPattern<llhd::TimeToIntOp> {
253 using OpConversionPattern::OpConversionPattern;
254 LogicalResult
255 matchAndRewrite(llhd::TimeToIntOp op, OpAdaptor adaptor,
256 ConversionPatternRewriter &rewriter) const final {
257 rewriter.replaceOp(op, adaptor.getInput());
258 return success();
259 }
260};
261
262//===----------------------------------------------------------------------===//
263// Memory and Storage Lowering
264//===----------------------------------------------------------------------===//
265
266struct AllocMemoryOpLowering : public OpConversionPattern<arc::AllocMemoryOp> {
267 using OpConversionPattern::OpConversionPattern;
268 LogicalResult
269 matchAndRewrite(arc::AllocMemoryOp op, OpAdaptor adaptor,
270 ConversionPatternRewriter &rewriter) const final {
271 auto offsetAttr = op->getAttrOfType<IntegerAttr>("offset");
272 if (!offsetAttr)
273 return failure();
274 Value ptr = LLVM::GEPOp::create(
275 rewriter, op.getLoc(), adaptor.getStorage().getType(),
276 rewriter.getI8Type(), adaptor.getStorage(),
277 LLVM::GEPArg(offsetAttr.getValue().getZExtValue()));
278
279 rewriter.replaceOp(op, ptr);
280 return success();
281 }
282};
283
284struct StorageGetOpLowering : public OpConversionPattern<arc::StorageGetOp> {
285 using OpConversionPattern::OpConversionPattern;
286 LogicalResult
287 matchAndRewrite(arc::StorageGetOp op, OpAdaptor adaptor,
288 ConversionPatternRewriter &rewriter) const final {
289 Value offset = LLVM::ConstantOp::create(
290 rewriter, op.getLoc(), rewriter.getI32Type(), op.getOffsetAttr());
291 Value ptr = LLVM::GEPOp::create(
292 rewriter, op.getLoc(), adaptor.getStorage().getType(),
293 rewriter.getI8Type(), adaptor.getStorage(), offset);
294 rewriter.replaceOp(op, ptr);
295 return success();
296 }
297};
298
299struct MemoryAccess {
300 Value ptr;
301 Value withinBounds;
302};
303
304static MemoryAccess prepareMemoryAccess(Location loc, Value memory,
305 Value address, MemoryType type,
306 ConversionPatternRewriter &rewriter) {
307 auto zextAddrType = rewriter.getIntegerType(
308 cast<IntegerType>(address.getType()).getWidth() + 1);
309 Value addr = LLVM::ZExtOp::create(rewriter, loc, zextAddrType, address);
310 Value addrLimit = LLVM::ConstantOp::create(
311 rewriter, loc, zextAddrType,
312 rewriter.getIntegerAttr(zextAddrType, type.getNumWords()));
313 Value withinBounds = LLVM::ICmpOp::create(
314 rewriter, loc, LLVM::ICmpPredicate::ult, addr, addrLimit);
315 Value ptr = LLVM::GEPOp::create(
316 rewriter, loc, LLVM::LLVMPointerType::get(memory.getContext()),
317 rewriter.getIntegerType(type.getStride() * 8), memory, ValueRange{addr});
318 return {ptr, withinBounds};
319}
320
321struct MemoryReadOpLowering : public OpConversionPattern<arc::MemoryReadOp> {
322 using OpConversionPattern::OpConversionPattern;
323 LogicalResult
324 matchAndRewrite(arc::MemoryReadOp op, OpAdaptor adaptor,
325 ConversionPatternRewriter &rewriter) const final {
326 auto type = typeConverter->convertType(op.getType());
327 auto memoryType = cast<MemoryType>(op.getMemory().getType());
328 auto access =
329 prepareMemoryAccess(op.getLoc(), adaptor.getMemory(),
330 adaptor.getAddress(), memoryType, rewriter);
331
332 // Only attempt to read the memory if the address is within bounds,
333 // otherwise produce a zero value.
334 rewriter.replaceOpWithNewOp<scf::IfOp>(
335 op, access.withinBounds,
336 [&](auto &builder, auto loc) {
337 Value loadOp = LLVM::LoadOp::create(
338 builder, loc, memoryType.getWordType(), access.ptr);
339 scf::YieldOp::create(builder, loc, loadOp);
340 },
341 [&](auto &builder, auto loc) {
342 Value zeroValue = LLVM::ConstantOp::create(
343 builder, loc, type, builder.getIntegerAttr(type, 0));
344 scf::YieldOp::create(builder, loc, zeroValue);
345 });
346 return success();
347 }
348};
349
350struct MemoryWriteOpLowering : public OpConversionPattern<arc::MemoryWriteOp> {
351 using OpConversionPattern::OpConversionPattern;
352 LogicalResult
353 matchAndRewrite(arc::MemoryWriteOp op, OpAdaptor adaptor,
354 ConversionPatternRewriter &rewriter) const final {
355 auto access = prepareMemoryAccess(
356 op.getLoc(), adaptor.getMemory(), adaptor.getAddress(),
357 cast<MemoryType>(op.getMemory().getType()), rewriter);
358 auto enable = access.withinBounds;
359
360 // Only attempt to write the memory if the address is within bounds.
361 rewriter.replaceOpWithNewOp<scf::IfOp>(
362 op, enable, [&](auto &builder, auto loc) {
363 LLVM::StoreOp::create(builder, loc, adaptor.getData(), access.ptr);
364 scf::YieldOp::create(builder, loc);
365 });
366 return success();
367 }
368};
369
370/// A dummy lowering for clock gates to an AND gate.
371struct ClockGateOpLowering : public OpConversionPattern<seq::ClockGateOp> {
372 using OpConversionPattern::OpConversionPattern;
373 LogicalResult
374 matchAndRewrite(seq::ClockGateOp op, OpAdaptor adaptor,
375 ConversionPatternRewriter &rewriter) const final {
376 rewriter.replaceOpWithNewOp<LLVM::AndOp>(op, adaptor.getInput(),
377 adaptor.getEnable());
378 return success();
379 }
380};
381
382/// Lower 'seq.clock_inv x' to 'llvm.xor x true'
383struct ClockInvOpLowering : public OpConversionPattern<seq::ClockInverterOp> {
384 using OpConversionPattern::OpConversionPattern;
385 LogicalResult
386 matchAndRewrite(seq::ClockInverterOp op, OpAdaptor adaptor,
387 ConversionPatternRewriter &rewriter) const final {
388 auto constTrue = LLVM::ConstantOp::create(rewriter, op->getLoc(),
389 rewriter.getI1Type(), 1);
390 rewriter.replaceOpWithNewOp<LLVM::XOrOp>(op, adaptor.getInput(), constTrue);
391 return success();
392 }
393};
394
395struct ZeroCountOpLowering : public OpConversionPattern<arc::ZeroCountOp> {
396 using OpConversionPattern::OpConversionPattern;
397 LogicalResult
398 matchAndRewrite(arc::ZeroCountOp op, OpAdaptor adaptor,
399 ConversionPatternRewriter &rewriter) const override {
400 // Use poison when input is zero.
401 IntegerAttr isZeroPoison = rewriter.getBoolAttr(true);
402
403 if (op.getPredicate() == arc::ZeroCountPredicate::leading) {
404 rewriter.replaceOpWithNewOp<LLVM::CountLeadingZerosOp>(
405 op, adaptor.getInput().getType(), adaptor.getInput(), isZeroPoison);
406 return success();
407 }
408
409 rewriter.replaceOpWithNewOp<LLVM::CountTrailingZerosOp>(
410 op, adaptor.getInput().getType(), adaptor.getInput(), isZeroPoison);
411 return success();
412 }
413};
414
415struct SeqConstClockLowering : public OpConversionPattern<seq::ConstClockOp> {
416 using OpConversionPattern::OpConversionPattern;
417 LogicalResult
418 matchAndRewrite(seq::ConstClockOp op, OpAdaptor adaptor,
419 ConversionPatternRewriter &rewriter) const override {
420 rewriter.replaceOpWithNewOp<LLVM::ConstantOp>(
421 op, rewriter.getI1Type(), static_cast<int64_t>(op.getValue()));
422 return success();
423 }
424};
425
426template <typename OpTy>
427struct ReplaceOpWithInputPattern : public OpConversionPattern<OpTy> {
429 using OpAdaptor = typename OpTy::Adaptor;
430 LogicalResult
431 matchAndRewrite(OpTy op, OpAdaptor adaptor,
432 ConversionPatternRewriter &rewriter) const override {
433 rewriter.replaceOp(op, adaptor.getInput());
434 return success();
435 }
436};
437
438} // namespace
439
440//===----------------------------------------------------------------------===//
441// Simulation Orchestration Lowering Patterns
442//===----------------------------------------------------------------------===//
443
444namespace {
445
446struct ModelInfoMap {
447 size_t numStateBytes;
448 llvm::DenseMap<StringRef, StateInfo> states;
449 mlir::FlatSymbolRefAttr initialFnSymbol;
450 mlir::FlatSymbolRefAttr finalFnSymbol;
451};
452
453template <typename OpTy>
454struct ModelAwarePattern : public OpConversionPattern<OpTy> {
455 ModelAwarePattern(const TypeConverter &typeConverter, MLIRContext *context,
456 llvm::DenseMap<StringRef, ModelInfoMap> &modelInfo)
457 : OpConversionPattern<OpTy>(typeConverter, context),
458 modelInfo(modelInfo) {}
459
460protected:
461 Value createPtrToPortState(ConversionPatternRewriter &rewriter, Location loc,
462 Value state, const StateInfo &port) const {
463 MLIRContext *ctx = rewriter.getContext();
464 return LLVM::GEPOp::create(rewriter, loc, LLVM::LLVMPointerType::get(ctx),
465 IntegerType::get(ctx, 8), state,
466 LLVM::GEPArg(port.offset));
467 }
468
469 llvm::DenseMap<StringRef, ModelInfoMap> &modelInfo;
470};
471
472/// Lowers SimInstantiateOp to a malloc and memset call. This pattern will
473/// mutate the global module.
474struct SimInstantiateOpLowering
475 : public ModelAwarePattern<arc::SimInstantiateOp> {
476 using ModelAwarePattern::ModelAwarePattern;
477
478 LogicalResult
479 matchAndRewrite(arc::SimInstantiateOp op, OpAdaptor adaptor,
480 ConversionPatternRewriter &rewriter) const final {
481 auto modelIt = modelInfo.find(
482 cast<SimModelInstanceType>(op.getBody().getArgument(0).getType())
483 .getModel()
484 .getValue());
485 ModelInfoMap &model = modelIt->second;
486
487 bool useRuntime = op.getRuntimeModel().has_value();
488
489 ModuleOp moduleOp = op->getParentOfType<ModuleOp>();
490 if (!moduleOp)
491 return failure();
492
493 ConversionPatternRewriter::InsertionGuard guard(rewriter);
494
495 // FIXME: like the rest of MLIR, this assumes sizeof(intptr_t) ==
496 // sizeof(size_t) on the target architecture.
497 Type convertedIndex = typeConverter->convertType(rewriter.getIndexType());
498 Location loc = op.getLoc();
499 Value allocated;
500
501 if (useRuntime) {
502 // The instance is using the runtime library
503 auto ptrTy = LLVM::LLVMPointerType::get(getContext());
504
505 Value runtimeArgs;
506 // If present, materialize the runtime argument string on the stack
507 if (op.getRuntimeArgs().has_value()) {
508 SmallVector<int8_t> argStringVec(op.getRuntimeArgsAttr().begin(),
509 op.getRuntimeArgsAttr().end());
510 argStringVec.push_back('\0');
511 auto strAttr = mlir::DenseElementsAttr::get(
512 mlir::RankedTensorType::get({(int64_t)argStringVec.size()},
513 rewriter.getI8Type()),
514 llvm::ArrayRef(argStringVec));
515
516 auto arrayCst = LLVM::ConstantOp::create(
517 rewriter, loc,
518 LLVM::LLVMArrayType::get(rewriter.getI8Type(), argStringVec.size()),
519 strAttr);
520 auto cst1 = LLVM::ConstantOp::create(rewriter, loc,
521 rewriter.getI32IntegerAttr(1));
522 runtimeArgs = LLVM::AllocaOp::create(rewriter, loc, ptrTy,
523 arrayCst.getType(), cst1);
524 LLVM::LifetimeStartOp::create(rewriter, loc, runtimeArgs);
525 LLVM::StoreOp::create(rewriter, loc, arrayCst, runtimeArgs);
526 } else {
527 runtimeArgs = LLVM::ZeroOp::create(rewriter, loc, ptrTy).getResult();
528 }
529 // Call the state allocation function
530 auto rtModelPtr = LLVM::AddressOfOp::create(rewriter, loc, ptrTy,
531 op.getRuntimeModelAttr())
532 .getResult();
533 allocated =
534 LLVM::CallOp::create(rewriter, loc, {ptrTy},
535 runtime::APICallbacks::symNameAllocInstance,
536 {rtModelPtr, runtimeArgs})
537 .getResult();
538
539 if (op.getRuntimeArgs().has_value())
540 LLVM::LifetimeEndOp::create(rewriter, loc, runtimeArgs);
541
542 } else {
543 // The instance is not using the runtime library
544 FailureOr<LLVM::LLVMFuncOp> mallocFunc =
545 LLVM::lookupOrCreateMallocFn(rewriter, moduleOp, convertedIndex);
546 if (failed(mallocFunc))
547 return mallocFunc;
548
549 Value numStateBytes = LLVM::ConstantOp::create(
550 rewriter, loc, convertedIndex, model.numStateBytes);
551 allocated = LLVM::CallOp::create(rewriter, loc, mallocFunc.value(),
552 ValueRange{numStateBytes})
553 .getResult();
554 Value zero =
555 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI8Type(), 0);
556 LLVM::MemsetOp::create(rewriter, loc, allocated, zero, numStateBytes,
557 false);
558 }
559
560 // Call the model's 'initial' function if present.
561 if (model.initialFnSymbol) {
562 auto initialFnType = LLVM::LLVMFunctionType::get(
563 LLVM::LLVMVoidType::get(op.getContext()),
564 {LLVM::LLVMPointerType::get(op.getContext())});
565 LLVM::CallOp::create(rewriter, loc, initialFnType, model.initialFnSymbol,
566 ValueRange{allocated});
567 }
568
569 // Call the runtime's 'onInitialized' function if present.
570 if (useRuntime)
571 LLVM::CallOp::create(rewriter, loc, TypeRange{},
572 runtime::APICallbacks::symNameOnInitialized,
573 {allocated});
574
575 // Execute the body.
576 rewriter.inlineBlockBefore(&adaptor.getBody().getBlocks().front(), op,
577 {allocated});
578
579 // Call the model's 'final' function if present.
580 if (model.finalFnSymbol) {
581 auto finalFnType = LLVM::LLVMFunctionType::get(
582 LLVM::LLVMVoidType::get(op.getContext()),
583 {LLVM::LLVMPointerType::get(op.getContext())});
584 LLVM::CallOp::create(rewriter, loc, finalFnType, model.finalFnSymbol,
585 ValueRange{allocated});
586 }
587
588 if (useRuntime) {
589 LLVM::CallOp::create(rewriter, loc, TypeRange{},
590 runtime::APICallbacks::symNameDeleteInstance,
591 {allocated});
592 } else {
593 FailureOr<LLVM::LLVMFuncOp> freeFunc =
594 LLVM::lookupOrCreateFreeFn(rewriter, moduleOp);
595 if (failed(freeFunc))
596 return freeFunc;
597
598 LLVM::CallOp::create(rewriter, loc, freeFunc.value(),
599 ValueRange{allocated});
600 }
601
602 rewriter.eraseOp(op);
603 return success();
604 }
605};
606
607struct SimSetInputOpLowering : public ModelAwarePattern<arc::SimSetInputOp> {
608 using ModelAwarePattern::ModelAwarePattern;
609
610 LogicalResult
611 matchAndRewrite(arc::SimSetInputOp op, OpAdaptor adaptor,
612 ConversionPatternRewriter &rewriter) const final {
613 auto modelIt =
614 modelInfo.find(cast<SimModelInstanceType>(op.getInstance().getType())
615 .getModel()
616 .getValue());
617 ModelInfoMap &model = modelIt->second;
618
619 auto portIt = model.states.find(op.getInput());
620 if (portIt == model.states.end()) {
621 // If the port is not found in the state, it means the model does not
622 // actually use it. Thus this operation is a no-op.
623 rewriter.eraseOp(op);
624 return success();
625 }
626
627 StateInfo &port = portIt->second;
628 Value statePtr = createPtrToPortState(rewriter, op.getLoc(),
629 adaptor.getInstance(), port);
630 rewriter.replaceOpWithNewOp<LLVM::StoreOp>(op, adaptor.getValue(),
631 statePtr);
632
633 return success();
634 }
635};
636
637struct SimGetPortOpLowering : public ModelAwarePattern<arc::SimGetPortOp> {
638 using ModelAwarePattern::ModelAwarePattern;
639
640 LogicalResult
641 matchAndRewrite(arc::SimGetPortOp op, OpAdaptor adaptor,
642 ConversionPatternRewriter &rewriter) const final {
643 auto modelIt =
644 modelInfo.find(cast<SimModelInstanceType>(op.getInstance().getType())
645 .getModel()
646 .getValue());
647 ModelInfoMap &model = modelIt->second;
648
649 auto type = typeConverter->convertType(op.getValue().getType());
650 if (!type)
651 return failure();
652 auto portIt = model.states.find(op.getPort());
653 if (portIt == model.states.end()) {
654 // If the port is not found in the state, it means the model does not
655 // actually set it. Thus this operation returns 0.
656 rewriter.replaceOpWithNewOp<LLVM::ConstantOp>(op, type, 0);
657 return success();
658 }
659
660 StateInfo &port = portIt->second;
661 Value statePtr = createPtrToPortState(rewriter, op.getLoc(),
662 adaptor.getInstance(), port);
663 rewriter.replaceOpWithNewOp<LLVM::LoadOp>(op, type, statePtr);
664
665 return success();
666 }
667};
668
669struct SimStepOpLowering : public ModelAwarePattern<arc::SimStepOp> {
670 using ModelAwarePattern::ModelAwarePattern;
671
672 LogicalResult
673 matchAndRewrite(arc::SimStepOp op, OpAdaptor adaptor,
674 ConversionPatternRewriter &rewriter) const final {
675 StringRef modelName = cast<SimModelInstanceType>(op.getInstance().getType())
676 .getModel()
677 .getValue();
678
679 if (adaptor.getTimePostIncrement()) {
680 // Increment time after step
681 OpBuilder::InsertionGuard g(rewriter);
682 rewriter.setInsertionPointAfter(op);
683 auto arcContext =
684 arc::AsContextOp::create(rewriter, op.getLoc(), op.getInstance());
685 auto oldTime =
686 arc::CurrentTimeOp::create(rewriter, op.getLoc(), arcContext);
687 auto newTime = LLVM::AddOp::create(rewriter, op.getLoc(), oldTime,
688 adaptor.getTimePostIncrement());
689 arc::SimSetTimeOp::create(rewriter, op.getLoc(), op.getInstance(),
690 newTime);
691 }
692
693 StringAttr evalFunc =
694 rewriter.getStringAttr(evalSymbolFromModelName(modelName));
695 rewriter.replaceOpWithNewOp<LLVM::CallOp>(op, mlir::TypeRange(), evalFunc,
696 adaptor.getInstance());
697
698 return success();
699 }
700};
701
702// Stores the simulation time (i64 femtoseconds) to byte offset 0 in the
703// model instance's state storage.
704struct SimSetTimeOpLowering : public OpConversionPattern<arc::SimSetTimeOp> {
705 using OpConversionPattern::OpConversionPattern;
706
707 LogicalResult
708 matchAndRewrite(arc::SimSetTimeOp op, OpAdaptor adaptor,
709 ConversionPatternRewriter &rewriter) const final {
710 // Time is stored at offset 0 in the instance storage.
711 rewriter.replaceOpWithNewOp<LLVM::StoreOp>(op, adaptor.getTime(),
712 adaptor.getInstance());
713 return success();
714 }
715};
716
717// Global string constants in the module.
718class StringCache {
719public:
720 Value getOrCreate(OpBuilder &b, StringRef formatStr) {
721 auto it = cache.find(formatStr);
722 if (it != cache.end()) {
723 return LLVM::AddressOfOp::create(b, b.getUnknownLoc(), it->second);
724 }
725
726 Location loc = b.getUnknownLoc();
727 LLVM::GlobalOp global;
728 {
729 OpBuilder::InsertionGuard guard(b);
730 ModuleOp m =
731 b.getInsertionBlock()->getParent()->getParentOfType<ModuleOp>();
732 b.setInsertionPointToStart(m.getBody());
733
734 SmallVector<char> strVec(formatStr.begin(), formatStr.end());
735 strVec.push_back(0);
736
737 auto name = llvm::formatv("_arc_str_{0}", cache.size()).str();
738 auto globalType = LLVM::LLVMArrayType::get(b.getI8Type(), strVec.size());
739 global = LLVM::GlobalOp::create(b, loc, globalType, /*isConstant=*/true,
740 LLVM::Linkage::Internal,
741 /*name=*/name, b.getStringAttr(strVec),
742 /*alignment=*/0);
743 }
744
745 cache[formatStr] = global;
746 return LLVM::AddressOfOp::create(b, loc, global);
747 }
748
749private:
750 llvm::StringMap<LLVM::GlobalOp> cache;
751};
752
753FailureOr<LLVM::CallOp> emitPrintfCall(OpBuilder &builder, Location loc,
754 StringCache &cache, StringRef formatStr,
755 ValueRange args) {
756 ModuleOp moduleOp =
757 builder.getInsertionBlock()->getParent()->getParentOfType<ModuleOp>();
758 // Lookup or create printf function symbol.
759 MLIRContext *ctx = builder.getContext();
760 auto printfFunc = LLVM::lookupOrCreateFn(builder, moduleOp, "printf",
761 LLVM::LLVMPointerType::get(ctx),
762 LLVM::LLVMVoidType::get(ctx), true);
763 if (failed(printfFunc))
764 return printfFunc;
765
766 Value formatStrPtr = cache.getOrCreate(builder, formatStr);
767 SmallVector<Value> argsVec(1, formatStrPtr);
768 argsVec.append(args.begin(), args.end());
769 return LLVM::CallOp::create(builder, loc, printfFunc.value(), argsVec);
770}
771
772/// Lowers SimEmitValueOp to a printf call. The integer will be printed in its
773/// entirety if it is of size up to size_t, and explicitly truncated otherwise.
774/// This pattern will mutate the global module.
775struct SimEmitValueOpLowering
776 : public OpConversionPattern<arc::SimEmitValueOp> {
777 SimEmitValueOpLowering(const TypeConverter &typeConverter,
778 MLIRContext *context, StringCache &formatStringCache)
779 : OpConversionPattern(typeConverter, context),
780 formatStringCache(formatStringCache) {}
781
782 LogicalResult
783 matchAndRewrite(arc::SimEmitValueOp op, OpAdaptor adaptor,
784 ConversionPatternRewriter &rewriter) const final {
785 auto valueType = dyn_cast<IntegerType>(adaptor.getValue().getType());
786 if (!valueType)
787 return failure();
788
789 Location loc = op.getLoc();
790
791 ModuleOp moduleOp = op->getParentOfType<ModuleOp>();
792 if (!moduleOp)
793 return failure();
794
795 SmallVector<Value> printfVariadicArgs;
796 SmallString<16> printfFormatStr;
797 int remainingBits = valueType.getWidth();
798 Value value = adaptor.getValue();
799
800 // Assumes the target platform uses 64bit for long long ints (%llx
801 // formatter).
802 constexpr llvm::StringRef intFormatter = "llx";
803 auto intType = IntegerType::get(getContext(), 64);
804 Value shiftValue = LLVM::ConstantOp::create(
805 rewriter, loc, rewriter.getIntegerAttr(valueType, intType.getWidth()));
806
807 if (valueType.getWidth() < intType.getWidth()) {
808 int width = llvm::divideCeil(valueType.getWidth(), 4);
809 printfFormatStr = llvm::formatv("%0{0}{1}", width, intFormatter);
810 printfVariadicArgs.push_back(
811 LLVM::ZExtOp::create(rewriter, loc, intType, value));
812 } else {
813 // Process the value in 64 bit chunks, starting from the least significant
814 // bits. Since we append chunks in low-to-high order, we reverse the
815 // vector to print them in the correct high-to-low order.
816 int otherChunkWidth = intType.getWidth() / 4;
817 int firstChunkWidth =
818 llvm::divideCeil(valueType.getWidth() % intType.getWidth(), 4);
819 if (firstChunkWidth == 0) { // print the full 64-bit hex or a subset.
820 firstChunkWidth = otherChunkWidth;
821 }
822
823 std::string firstChunkFormat =
824 llvm::formatv("%0{0}{1}", firstChunkWidth, intFormatter);
825 std::string otherChunkFormat =
826 llvm::formatv("%0{0}{1}", otherChunkWidth, intFormatter);
827
828 for (int i = 0; remainingBits > 0; ++i) {
829 // Append 64-bit chunks to the printf arguments, in low-to-high
830 // order. The integer is printed in hex format with zero padding.
831 printfVariadicArgs.push_back(
832 LLVM::TruncOp::create(rewriter, loc, intType, value));
833
834 // Zero-padded format specifier for fixed width, e.g. %01llx for 4 bits.
835 printfFormatStr.append(i == 0 ? firstChunkFormat : otherChunkFormat);
836
837 value =
838 LLVM::LShrOp::create(rewriter, loc, value, shiftValue).getResult();
839 remainingBits -= intType.getWidth();
840 }
841 }
842
843 std::reverse(printfVariadicArgs.begin(), printfVariadicArgs.end());
844
845 SmallString<16> formatStr = adaptor.getValueName();
846 formatStr.append(" = ");
847 formatStr.append(printfFormatStr);
848 formatStr.append("\n");
849
850 auto callOp = emitPrintfCall(rewriter, op->getLoc(), formatStringCache,
851 formatStr, printfVariadicArgs);
852 if (failed(callOp))
853 return failure();
854 rewriter.replaceOp(op, *callOp);
855
856 return success();
857 }
858
859 StringCache &formatStringCache;
860};
861
862//===----------------------------------------------------------------------===//
863// `sim` dialect lowerings
864//===----------------------------------------------------------------------===//
865
866// Helper struct to hold the format string and arguments for arcRuntimeFormat.
867struct FormatInfo {
868 SmallVector<FmtDescriptor> descriptors;
869 SmallVector<Value> args;
870};
871
872// Copies the given integer value into an alloca, returning a pointer to it.
873//
874// The alloca is rounded up to a 64-bit boundary and is written as little-endian
875// words of size 64-bits, to be compatible with the constructor of APInt.
876static Value reg2mem(ConversionPatternRewriter &rewriter, Location loc,
877 Value value) {
878 // Round up the type size to a 64-bit boundary.
879 int64_t origBitwidth = cast<IntegerType>(value.getType()).getWidth();
880 int64_t bitwidth = llvm::divideCeil(origBitwidth, 64) * 64;
881 int64_t numWords = bitwidth / 64;
882
883 // Create an alloca for the rounded up type.
884 LLVM::ConstantOp alloca_size =
885 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(), numWords);
886 auto ptrType = LLVM::LLVMPointerType::get(rewriter.getContext());
887 auto allocaOp = LLVM::AllocaOp::create(rewriter, loc, ptrType,
888 rewriter.getI64Type(), alloca_size);
889 LLVM::LifetimeStartOp::create(rewriter, loc, allocaOp);
890
891 // Copy `value` into the alloca, 64-bits at a time from the least significant
892 // bits first.
893 for (int64_t wordIdx = 0; wordIdx < numWords; ++wordIdx) {
894 Value cst = LLVM::ConstantOp::create(
895 rewriter, loc, rewriter.getIntegerType(origBitwidth), wordIdx * 64);
896 Value v = LLVM::LShrOp::create(rewriter, loc, value, cst);
897 if (origBitwidth > 64) {
898 v = LLVM::TruncOp::create(rewriter, loc, rewriter.getI64Type(), v);
899 } else if (origBitwidth < 64) {
900 v = LLVM::ZExtOp::create(rewriter, loc, rewriter.getI64Type(), v);
901 }
902 Value gep = LLVM::GEPOp::create(rewriter, loc, ptrType,
903 rewriter.getI64Type(), allocaOp, {wordIdx});
904 LLVM::StoreOp::create(rewriter, loc, v, gep);
905 }
906
907 return allocaOp;
908}
909
910// Statically folds a value of type sim::FormatStringType to a FormatInfo.
911static FailureOr<FormatInfo>
912foldFormatString(ConversionPatternRewriter &rewriter, Value fstringValue,
913 StringCache &cache) {
914 Operation *op = fstringValue.getDefiningOp();
915 return llvm::TypeSwitch<Operation *, FailureOr<FormatInfo>>(op)
916 .Case<sim::FormatCharOp>(
917 [&](sim::FormatCharOp op) -> FailureOr<FormatInfo> {
918 FmtDescriptor d = FmtDescriptor::createChar();
919 return FormatInfo{{d}, {op.getValue()}};
920 })
921 .Case<sim::FormatDecOp>([&](sim::FormatDecOp op)
922 -> FailureOr<FormatInfo> {
923 FmtDescriptor d = FmtDescriptor::createInt(
924 op.getValue().getType().getWidth(), 10, op.getIsLeftAligned(),
925 op.getSpecifierWidth().value_or(-1), op.getPaddingChar(), false,
926 op.getIsSigned());
927 return FormatInfo{{d}, {reg2mem(rewriter, op.getLoc(), op.getValue())}};
928 })
929 .Case<sim::FormatHexOp>([&](sim::FormatHexOp op)
930 -> FailureOr<FormatInfo> {
931 FmtDescriptor d = FmtDescriptor::createInt(
932 op.getValue().getType().getWidth(), 16, op.getIsLeftAligned(),
933 op.getSpecifierWidth().value_or(-1), op.getPaddingChar(),
934 op.getIsHexUppercase(), false);
935 return FormatInfo{{d}, {reg2mem(rewriter, op.getLoc(), op.getValue())}};
936 })
937 .Case<sim::FormatOctOp>([&](sim::FormatOctOp op)
938 -> FailureOr<FormatInfo> {
939 FmtDescriptor d = FmtDescriptor::createInt(
940 op.getValue().getType().getWidth(), 8, op.getIsLeftAligned(),
941 op.getSpecifierWidth().value_or(-1), op.getPaddingChar(), false,
942 false);
943 return FormatInfo{{d}, {reg2mem(rewriter, op.getLoc(), op.getValue())}};
944 })
945 .Case<sim::FormatBinOp>([&](sim::FormatBinOp op)
946 -> FailureOr<FormatInfo> {
947 FmtDescriptor d = FmtDescriptor::createInt(
948 op.getValue().getType().getWidth(), 2, op.getIsLeftAligned(),
949 op.getSpecifierWidth().value_or(-1), op.getPaddingChar(), false,
950 false);
951 return FormatInfo{{d}, {reg2mem(rewriter, op.getLoc(), op.getValue())}};
952 })
953 .Case<sim::FormatLiteralOp>(
954 [&](sim::FormatLiteralOp op) -> FailureOr<FormatInfo> {
955 if (op.getLiteral().size() < 8 &&
956 op.getLiteral().find('\0') == StringRef::npos) {
957 // We can use the small string optimization.
958 FmtDescriptor d =
959 FmtDescriptor::createSmallLiteral(op.getLiteral());
960 return FormatInfo{{d}, {}};
961 }
962 FmtDescriptor d =
963 FmtDescriptor::createLiteral(op.getLiteral().size());
964 Value value = cache.getOrCreate(rewriter, op.getLiteral());
965 return FormatInfo{{d}, {value}};
966 })
967 .Case<sim::FormatStringConcatOp>(
968 [&](sim::FormatStringConcatOp op) -> FailureOr<FormatInfo> {
969 auto fmt = foldFormatString(rewriter, op.getInputs()[0], cache);
970 if (failed(fmt))
971 return failure();
972 for (auto input : op.getInputs().drop_front()) {
973 auto next = foldFormatString(rewriter, input, cache);
974 if (failed(next))
975 return failure();
976 fmt->descriptors.append(next->descriptors);
977 fmt->args.append(next->args);
978 }
979 return fmt;
980 })
981 .Default(
982 [](Operation *op) -> FailureOr<FormatInfo> { return failure(); });
983}
984
985FailureOr<LLVM::CallOp> emitFmtCall(OpBuilder &builder, Location loc,
986 StringCache &stringCache,
987 ArrayRef<FmtDescriptor> descriptors,
988 ValueRange args, Value stream = {}) {
989 ModuleOp moduleOp =
990 builder.getInsertionBlock()->getParent()->getParentOfType<ModuleOp>();
991 MLIRContext *ctx = builder.getContext();
992 auto ptrType = LLVM::LLVMPointerType::get(ctx);
993 SmallVector<Type, 2> paramTypes;
994 StringRef symbolName = runtime::APICallbacks::symNameFormat;
995 if (stream) {
996 symbolName = runtime::APICallbacks::symNameFormatToStream;
997 paramTypes.push_back(ptrType);
998 }
999 paramTypes.push_back(ptrType);
1000
1001 auto func = LLVM::lookupOrCreateFn(builder, moduleOp, symbolName, paramTypes,
1002 LLVM::LLVMVoidType::get(ctx), true);
1003 if (failed(func))
1004 return func;
1005
1006 StringRef rawDescriptors(reinterpret_cast<const char *>(descriptors.data()),
1007 descriptors.size() * sizeof(FmtDescriptor));
1008 Value fmtPtr = stringCache.getOrCreate(builder, rawDescriptors);
1009
1010 SmallVector<Value> argsVec;
1011 if (stream)
1012 argsVec.push_back(stream);
1013 argsVec.push_back(fmtPtr);
1014 argsVec.append(args.begin(), args.end());
1015 auto result = LLVM::CallOp::create(builder, loc, func.value(), argsVec);
1016
1017 for (Value arg : args) {
1018 Operation *definingOp = arg.getDefiningOp();
1019 if (auto alloca = dyn_cast_if_present<LLVM::AllocaOp>(definingOp)) {
1020 LLVM::LifetimeEndOp::create(builder, loc, arg);
1021 }
1022 }
1023
1024 return result;
1025}
1026
1027template <typename SourceOp>
1028struct SimStreamOpLowering : public OpConversionPattern<SourceOp> {
1029 SimStreamOpLowering(const TypeConverter &typeConverter, MLIRContext *context,
1030 StringRef symbolName)
1031 : OpConversionPattern<SourceOp>(typeConverter, context),
1032 symbolName(symbolName) {}
1033
1034 LogicalResult
1035 matchAndRewrite(SourceOp op, typename SourceOp::Adaptor adaptor,
1036 ConversionPatternRewriter &rewriter) const override {
1037 ModuleOp moduleOp = op->template getParentOfType<ModuleOp>();
1038 if (!moduleOp)
1039 return failure();
1040
1041 auto ptrType = LLVM::LLVMPointerType::get(rewriter.getContext());
1042 auto func =
1043 LLVM::lookupOrCreateFn(rewriter, moduleOp, symbolName, {}, ptrType);
1044 if (failed(func))
1045 return failure();
1046
1047 auto call =
1048 LLVM::CallOp::create(rewriter, op.getLoc(), func.value(), ValueRange{});
1049 rewriter.replaceOp(op, call.getResults());
1050 return success();
1051 }
1052
1053 StringRef symbolName;
1054};
1055
1056struct SimPrintFormattedProcOpLowering
1057 : public OpConversionPattern<sim::PrintFormattedProcOp> {
1058 SimPrintFormattedProcOpLowering(const TypeConverter &typeConverter,
1059 MLIRContext *context,
1060 StringCache &stringCache)
1061 : OpConversionPattern<sim::PrintFormattedProcOp>(typeConverter, context),
1062 stringCache(stringCache) {}
1063
1064 LogicalResult
1065 matchAndRewrite(sim::PrintFormattedProcOp op, OpAdaptor adaptor,
1066 ConversionPatternRewriter &rewriter) const override {
1067 auto formatInfo = foldFormatString(rewriter, op.getInput(), stringCache);
1068 if (failed(formatInfo))
1069 return rewriter.notifyMatchFailure(op, "unsupported format string");
1070
1071 // Add the end descriptor.
1072 formatInfo->descriptors.push_back(FmtDescriptor());
1073
1074 auto result =
1075 emitFmtCall(rewriter, op.getLoc(), stringCache, formatInfo->descriptors,
1076 formatInfo->args, adaptor.getStream());
1077 if (failed(result))
1078 return failure();
1079 rewriter.replaceOp(op, result.value());
1080
1081 return success();
1082 }
1083
1084 StringCache &stringCache;
1085};
1086
1087struct TerminateOpLowering : public OpConversionPattern<arc::TerminateOp> {
1088 using OpConversionPattern::OpConversionPattern;
1089
1090 LogicalResult
1091 matchAndRewrite(arc::TerminateOp op, OpAdaptor adaptor,
1092 ConversionPatternRewriter &rewriter) const override {
1093 auto loc = op.getLoc();
1094
1095 auto i8Type = rewriter.getI8Type();
1096 auto ptrType = LLVM::LLVMPointerType::get(rewriter.getContext());
1097
1098 Value flagPtr = LLVM::GEPOp::create(
1099 rewriter, loc, ptrType, i8Type, adaptor.getArcContext(),
1100 ArrayRef<LLVM::GEPArg>{arc::kTerminateFlagOffset});
1101
1102 uint8_t statusCode = op.getSuccess() ? 1 : 2;
1103 Value codeVal = LLVM::ConstantOp::create(
1104 rewriter, loc, i8Type, rewriter.getI8IntegerAttr(statusCode));
1105
1106 LLVM::StoreOp::create(rewriter, loc, codeVal, flagPtr);
1107
1108 rewriter.eraseOp(op);
1109 return success();
1110 }
1111};
1112
1113// Loads the next wakeup time (i64 femtoseconds) from the model's storage at
1114// `kNextWakeupOffset`.
1115struct GetNextWakeupOpLowering
1116 : public OpConversionPattern<arc::GetNextWakeupOp> {
1117 using OpConversionPattern::OpConversionPattern;
1118
1119 LogicalResult
1120 matchAndRewrite(arc::GetNextWakeupOp op, OpAdaptor adaptor,
1121 ConversionPatternRewriter &rewriter) const override {
1122 auto loc = op.getLoc();
1123 auto ptrType = LLVM::LLVMPointerType::get(rewriter.getContext());
1124 Value slotPtr = LLVM::GEPOp::create(
1125 rewriter, loc, ptrType, rewriter.getI8Type(), adaptor.getArcContext(),
1126 ArrayRef<LLVM::GEPArg>{arc::kNextWakeupOffset});
1127 rewriter.replaceOpWithNewOp<LLVM::LoadOp>(op, rewriter.getI64Type(),
1128 slotPtr);
1129 return success();
1130 }
1131};
1132
1133// Stores the next wakeup time (i64 femtoseconds) to the model's storage at
1134// `kNextWakeupOffset`.
1135struct SetNextWakeupOpLowering
1136 : public OpConversionPattern<arc::SetNextWakeupOp> {
1137 using OpConversionPattern::OpConversionPattern;
1138
1139 LogicalResult
1140 matchAndRewrite(arc::SetNextWakeupOp op, OpAdaptor adaptor,
1141 ConversionPatternRewriter &rewriter) const override {
1142 auto loc = op.getLoc();
1143 auto ptrType = LLVM::LLVMPointerType::get(rewriter.getContext());
1144 Value slotPtr = LLVM::GEPOp::create(
1145 rewriter, loc, ptrType, rewriter.getI8Type(), adaptor.getArcContext(),
1146 ArrayRef<LLVM::GEPArg>{arc::kNextWakeupOffset});
1147 rewriter.replaceOpWithNewOp<LLVM::StoreOp>(op, adaptor.getTime(), slotPtr);
1148 return success();
1149 }
1150};
1151
1152} // namespace
1153
1154static LogicalResult convert(arc::ExecuteOp op, arc::ExecuteOp::Adaptor adaptor,
1155 ConversionPatternRewriter &rewriter,
1156 const TypeConverter &converter) {
1157 // Convert the argument types in the body blocks.
1158 if (failed(rewriter.convertRegionTypes(&op.getBody(), converter)))
1159 return failure();
1160
1161 // Split the block at the current insertion point such that we can branch into
1162 // the `arc.execute` body region, and have `arc.output` branch back to the
1163 // point after the `arc.execute`.
1164 auto *blockBefore = rewriter.getInsertionBlock();
1165 auto *blockAfter =
1166 rewriter.splitBlock(blockBefore, rewriter.getInsertionPoint());
1167
1168 // Branch to the entry block.
1169 rewriter.setInsertionPointToEnd(blockBefore);
1170 mlir::cf::BranchOp::create(rewriter, op.getLoc(), &op.getBody().front(),
1171 adaptor.getInputs());
1172
1173 // Make all `arc.output` terminators branch to the block after the
1174 // `arc.execute` op.
1175 for (auto &block : op.getBody()) {
1176 auto outputOp = dyn_cast<arc::OutputOp>(block.getTerminator());
1177 if (!outputOp)
1178 continue;
1179 rewriter.setInsertionPointToEnd(&block);
1180 rewriter.replaceOpWithNewOp<mlir::cf::BranchOp>(outputOp, blockAfter,
1181 outputOp.getOperands());
1182 }
1183
1184 // Inline the body region between the before and after blocks.
1185 rewriter.inlineRegionBefore(op.getBody(), blockAfter);
1186
1187 // Add arguments to the block after the `arc.execute`, replace the op's
1188 // results with the arguments, then perform block signature conversion.
1189 SmallVector<Value> args;
1190 args.reserve(op.getNumResults());
1191 for (auto result : op.getResults())
1192 args.push_back(blockAfter->addArgument(result.getType(), result.getLoc()));
1193 rewriter.replaceOp(op, args);
1194 auto conversion = converter.convertBlockSignature(blockAfter);
1195 if (!conversion)
1196 return failure();
1197 rewriter.applySignatureConversion(blockAfter, *conversion, &converter);
1198 return success();
1199}
1200
1201//===----------------------------------------------------------------------===//
1202// Runtime Implementation
1203//===----------------------------------------------------------------------===//
1204
1205template <typename T, typename = std::enable_if_t<std::is_integral<T>::value>>
1206static LLVM::GlobalOp
1207buildGlobalConstantIntArray(OpBuilder &builder, Location loc, Twine symName,
1208 SmallVectorImpl<T> &data,
1209 unsigned alignment = alignof(T)) {
1210 auto intType = builder.getIntegerType(8 * sizeof(T));
1211 Attribute denseAttr = mlir::DenseElementsAttr::get(
1212 mlir::RankedTensorType::get({(int64_t)data.size()}, intType),
1213 llvm::ArrayRef(data));
1214 auto globalOp = LLVM::GlobalOp::create(
1215 builder, loc, LLVM::LLVMArrayType::get(intType, data.size()),
1216 /*isConstant=*/true, LLVM::Linkage::Internal,
1217 builder.getStringAttr(symName), denseAttr);
1218 globalOp.setAlignmentAttr(builder.getI64IntegerAttr(alignment));
1219 return globalOp;
1220}
1221
1222// Construct a raw constant byte array from a vector of struct values
1223template <typename T>
1224static LLVM::GlobalOp
1225buildGlobalConstantRuntimeStructArray(OpBuilder &builder, Location loc,
1226 Twine symName,
1227 SmallVectorImpl<T> &array) {
1228 assert(!array.empty());
1229 static_assert(std::is_standard_layout<T>(),
1230 "Runtime struct must have standard layout");
1231 int64_t numBytes = sizeof(T) * array.size();
1232 Attribute denseAttr = mlir::DenseElementsAttr::get(
1233 mlir::RankedTensorType::get({numBytes}, builder.getI8Type()),
1234 llvm::ArrayRef(reinterpret_cast<uint8_t *>(array.data()), numBytes));
1235 auto globalOp = LLVM::GlobalOp::create(
1236 builder, loc, LLVM::LLVMArrayType::get(builder.getI8Type(), numBytes),
1237 /*isConstant=*/true, LLVM::Linkage::Internal,
1238 builder.getStringAttr(symName), denseAttr, alignof(T));
1239 return globalOp;
1240}
1241
1243 : public OpConversionPattern<arc::RuntimeModelOp> {
1244 using OpConversionPattern::OpConversionPattern;
1245
1246 static constexpr uint64_t runtimeApiVersion = ARC_RUNTIME_API_VERSION;
1247
1248 // Build the constant ArcModelTraceInfo struct and its members
1249 LLVM::GlobalOp
1250 buildTraceInfoStruct(arc::RuntimeModelOp &op,
1251 ConversionPatternRewriter &rewriter) const {
1252 if (!op.getTraceTaps().has_value() || op.getTraceTaps()->empty())
1253 return {};
1254 // Construct the array of tap names/aliases
1255 SmallVector<char> namesArray;
1256 SmallVector<ArcTraceTap> tapArray;
1257 tapArray.reserve(op.getTraceTaps()->size());
1258 for (auto attr : op.getTraceTapsAttr()) {
1259 auto tap = cast<TraceTapAttr>(attr);
1260 assert(!tap.getNames().empty() &&
1261 "Expected trace tap to have at least one name");
1262 for (auto alias : tap.getNames()) {
1263 auto aliasStr = cast<StringAttr>(alias);
1264 namesArray.append(aliasStr.begin(), aliasStr.end());
1265 namesArray.push_back('\0');
1266 }
1267 ArcTraceTap tapStruct;
1268 tapStruct.stateOffset = tap.getStateOffset();
1269 tapStruct.nameOffset = namesArray.size() - 1;
1270 tapStruct.typeBits = tap.getSigType().getValue().getIntOrFloatBitWidth();
1271 tapStruct.reserved = 0;
1272 tapArray.emplace_back(tapStruct);
1273 }
1274 auto ptrTy = LLVM::LLVMPointerType::get(getContext());
1275 auto namesGlobal = buildGlobalConstantIntArray(
1276 rewriter, op.getLoc(), "_arc_tap_names_" + op.getModelName(),
1277 namesArray);
1278 auto traceTapsArrayGlobal = buildGlobalConstantRuntimeStructArray(
1279 rewriter, op.getLoc(), "_arc_trace_taps_" + op.getModelName(),
1280 tapArray);
1281
1282 //
1283 // struct ArcModelTraceInfo {
1284 // uint64_t numTraceTaps;
1285 // struct ArcTraceTap *traceTaps;
1286 // const char *traceTapNames;
1287 // uint64_t traceBufferCapacity;
1288 // };
1289 //
1290 auto traceInfoStructType = LLVM::LLVMStructType::getLiteral(
1291 getContext(),
1292 {rewriter.getI64Type(), ptrTy, ptrTy, rewriter.getI64Type()});
1293 static_assert(sizeof(ArcModelTraceInfo) == 32 &&
1294 "Unexpected size of ArcModelTraceInfo struct");
1295
1296 auto globalSymName =
1297 rewriter.getStringAttr("_arc_trace_info_" + op.getModelName());
1298 auto traceInfoGlobalOp = LLVM::GlobalOp::create(
1299 rewriter, op.getLoc(), traceInfoStructType,
1300 /*isConstant=*/false, LLVM::Linkage::Internal, globalSymName,
1301 Attribute{}, alignof(ArcModelTraceInfo));
1302 OpBuilder::InsertionGuard g(rewriter);
1303
1304 // Struct Initializer
1305 Region &initRegion = traceInfoGlobalOp.getInitializerRegion();
1306 Block *initBlock = rewriter.createBlock(&initRegion);
1307 rewriter.setInsertionPointToStart(initBlock);
1308
1309 auto numTraceTapsCst = LLVM::ConstantOp::create(
1310 rewriter, op.getLoc(), rewriter.getI64IntegerAttr(tapArray.size()));
1311 auto traceTapArrayAddr =
1312 LLVM::AddressOfOp::create(rewriter, op.getLoc(), traceTapsArrayGlobal);
1313 auto tapNameArrayAddr =
1314 LLVM::AddressOfOp::create(rewriter, op.getLoc(), namesGlobal);
1315 auto bufferCapacityCst = LLVM::ConstantOp::create(
1316 rewriter, op.getLoc(),
1317 rewriter.getI64IntegerAttr(runtime::defaultTraceBufferCapacity));
1318
1319 Value initStruct =
1320 LLVM::PoisonOp::create(rewriter, op.getLoc(), traceInfoStructType);
1321
1322 // Field: uint64_t numTraceTaps
1323 initStruct =
1324 LLVM::InsertValueOp::create(rewriter, op.getLoc(), initStruct,
1325 numTraceTapsCst, ArrayRef<int64_t>{0});
1326 static_assert(offsetof(ArcModelTraceInfo, numTraceTaps) == 0,
1327 "Unexpected offset of field numTraceTaps");
1328 // Field: struct ArcTraceTap *traceTaps
1329 initStruct =
1330 LLVM::InsertValueOp::create(rewriter, op.getLoc(), initStruct,
1331 traceTapArrayAddr, ArrayRef<int64_t>{1});
1332 static_assert(offsetof(ArcModelTraceInfo, traceTaps) == 8,
1333 "Unexpected offset of field traceTaps");
1334 // Field: const char *traceTapNames
1335 initStruct =
1336 LLVM::InsertValueOp::create(rewriter, op.getLoc(), initStruct,
1337 tapNameArrayAddr, ArrayRef<int64_t>{2});
1338 static_assert(offsetof(ArcModelTraceInfo, traceTapNames) == 16,
1339 "Unexpected offset of field traceTapNames");
1340 // Field: uint64_t traceBufferCapacity
1341 initStruct =
1342 LLVM::InsertValueOp::create(rewriter, op.getLoc(), initStruct,
1343 bufferCapacityCst, ArrayRef<int64_t>{3});
1344 static_assert(offsetof(ArcModelTraceInfo, traceBufferCapacity) == 24,
1345 "Unexpected offset of field traceBufferCapacity");
1346 LLVM::ReturnOp::create(rewriter, op.getLoc(), initStruct);
1347
1348 return traceInfoGlobalOp;
1349 }
1350
1351 // Create a global LLVM struct containing the RuntimeModel metadata
1352 LogicalResult
1353 matchAndRewrite(arc::RuntimeModelOp op, OpAdaptor adaptor,
1354 ConversionPatternRewriter &rewriter) const final {
1355
1356 auto ptrTy = LLVM::LLVMPointerType::get(getContext());
1357 auto modelInfoStructType = LLVM::LLVMStructType::getLiteral(
1358 getContext(),
1359 {rewriter.getI64Type(), rewriter.getI64Type(), ptrTy, ptrTy});
1360 static_assert(sizeof(ArcRuntimeModelInfo) == 32 &&
1361 "Unexpected size of ArcRuntimeModelInfo struct");
1362
1363 rewriter.setInsertionPoint(op);
1364 auto traceInfoGlobal = buildTraceInfoStruct(op, rewriter);
1365
1366 // Construct the Model Name String GlobalOp
1367 SmallVector<char, 16> modNameArray(op.getModelName().begin(),
1368 op.getModelName().end());
1369 modNameArray.push_back('\0');
1370 auto nameGlobalType =
1371 LLVM::LLVMArrayType::get(rewriter.getI8Type(), modNameArray.size());
1372 auto globalSymName =
1373 rewriter.getStringAttr("_arc_mod_name_" + op.getModelName());
1374 auto nameGlobal = LLVM::GlobalOp::create(
1375 rewriter, op.getLoc(), nameGlobalType, /*isConstant=*/true,
1376 LLVM::Linkage::Internal,
1377 /*name=*/globalSymName, rewriter.getStringAttr(modNameArray),
1378 /*alignment=*/0);
1379
1380 // Construct the Model Info Struct GlobalOp
1381 // Note: The struct is supposed to be constant at runtime, but contains the
1382 // relocatable address of another symbol, so it should not be placed in the
1383 // "rodata" section.
1384 auto modInfoGlobalOp =
1385 LLVM::GlobalOp::create(rewriter, op.getLoc(), modelInfoStructType,
1386 /*isConstant=*/false, LLVM::Linkage::External,
1387 op.getSymName(), Attribute{});
1388
1389 // Struct Initializer
1390 Region &initRegion = modInfoGlobalOp.getInitializerRegion();
1391 Block *initBlock = rewriter.createBlock(&initRegion);
1392 rewriter.setInsertionPointToStart(initBlock);
1393 auto apiVersionCst = LLVM::ConstantOp::create(
1394 rewriter, op.getLoc(), rewriter.getI64IntegerAttr(runtimeApiVersion));
1395 auto numStateBytesCst = LLVM::ConstantOp::create(rewriter, op.getLoc(),
1396 op.getNumStateBytesAttr());
1397 auto nameAddr =
1398 LLVM::AddressOfOp::create(rewriter, op.getLoc(), nameGlobal);
1399 Value traceInfoPtr;
1400 if (traceInfoGlobal)
1401 traceInfoPtr =
1402 LLVM::AddressOfOp::create(rewriter, op.getLoc(), traceInfoGlobal);
1403 else
1404 traceInfoPtr = LLVM::ZeroOp::create(rewriter, op.getLoc(), ptrTy);
1405
1406 Value initStruct =
1407 LLVM::PoisonOp::create(rewriter, op.getLoc(), modelInfoStructType);
1408
1409 // Field: uint64_t apiVersion
1410 initStruct = LLVM::InsertValueOp::create(
1411 rewriter, op.getLoc(), initStruct, apiVersionCst, ArrayRef<int64_t>{0});
1412 static_assert(offsetof(ArcRuntimeModelInfo, apiVersion) == 0,
1413 "Unexpected offset of field apiVersion");
1414 // Field: uint64_t numStateBytes
1415 initStruct =
1416 LLVM::InsertValueOp::create(rewriter, op.getLoc(), initStruct,
1417 numStateBytesCst, ArrayRef<int64_t>{1});
1418 static_assert(offsetof(ArcRuntimeModelInfo, numStateBytes) == 8,
1419 "Unexpected offset of field numStateBytes");
1420 // Field: const char *modelName
1421 initStruct = LLVM::InsertValueOp::create(rewriter, op.getLoc(), initStruct,
1422 nameAddr, ArrayRef<int64_t>{2});
1423 static_assert(offsetof(ArcRuntimeModelInfo, modelName) == 16,
1424 "Unexpected offset of field modelName");
1425 // Field: struct ArcModelTraceInfo *traceInfo
1426 initStruct = LLVM::InsertValueOp::create(
1427 rewriter, op.getLoc(), initStruct, traceInfoPtr, ArrayRef<int64_t>{3});
1428 static_assert(offsetof(ArcRuntimeModelInfo, traceInfo) == 24,
1429 "Unexpected offset of field traceInfo");
1430
1431 LLVM::ReturnOp::create(rewriter, op.getLoc(), initStruct);
1432
1433 rewriter.replaceOp(op, modInfoGlobalOp);
1434 return success();
1435 }
1436};
1437
1438//===----------------------------------------------------------------------===//
1439// ArrayRef patterns
1440//===----------------------------------------------------------------------===//
1441
1442size_t computeByteWidth(ArrayRefType type) {
1443 auto bitWidth = computeLLVMBitWidth(type);
1444 assert(bitWidth.has_value());
1445 return llvm::divideCeil(*bitWidth, 8);
1446}
1447
1448// Computes the padded bytewidth (stride) of each element.
1449size_t computeElementByteWidth(ArrayRefType arrayRefType) {
1450 auto arrayBitWidth = computeLLVMBitWidth(arrayRefType);
1451 assert(arrayBitWidth.has_value());
1452 assert(arrayRefType.getNumElements() > 0 &&
1453 "Cannot compute stride for zero sized array");
1454 size_t elementBitWidth = *arrayBitWidth / arrayRefType.getNumElements();
1455 return llvm::divideCeil(elementBitWidth, 8);
1456}
1457
1458struct ArrayRefAllocOpLowering : public OpConversionPattern<ArrayRefAllocOp> {
1459 using OpConversionPattern::OpConversionPattern;
1460
1461 LogicalResult
1462 matchAndRewrite(ArrayRefAllocOp op, OpAdaptor adaptor,
1463 ConversionPatternRewriter &rewriter) const override {
1464 auto ptrTy = LLVM::LLVMPointerType::get(getContext());
1465 auto i8Ty = rewriter.getI8Type();
1466 ArrayRefType arrayRefType = op.getType();
1467 size_t byteWidth = computeByteWidth(arrayRefType);
1468 auto size = LLVM::ConstantOp::create(rewriter, op.getLoc(),
1469 rewriter.getI64Type(), byteWidth);
1470
1471 size_t alignment = computeAllocaAlignment(arrayRefType, op);
1472 auto alloc = LLVM::AllocaOp::create(rewriter, op.getLoc(), ptrTy, i8Ty,
1473 size, alignment);
1474
1475 if (op.getInitAttr()) {
1476 ArrayAttr initAttr = op.getInitAttr();
1477 if (isZero(initAttr)) {
1478 auto i8Ty = rewriter.getI8Type();
1479 auto zero = LLVM::ConstantOp::create(rewriter, op.getLoc(), i8Ty, 0);
1480 LLVM::MemsetOp::create(rewriter, op.getLoc(), alloc, zero, size,
1481 /*isVolatile=*/false);
1482 } else {
1483 initializeArray(rewriter, op.getLoc(), alloc, initAttr, arrayRefType);
1484 }
1485 }
1486
1487 rewriter.replaceOp(op, alloc);
1488 return success();
1489 }
1490
1491 // Computes the required alignment for an AllocaOp of the given type.
1492 // c.f. HWToLLVM.cpp.
1493 size_t computeAllocaAlignment(ArrayRefType type, Operation *op) const {
1494 if (alignmentCache.count(type)) {
1495 return alignmentCache[type];
1496 }
1497 auto dl = DataLayout::closest(op);
1498 auto hwType =
1499 hw::ArrayType::get(type.getElementType(), type.getNumElements());
1500 auto llvmType = getTypeConverter()->convertType(hwType);
1501 auto alignment =
1502 static_cast<unsigned>(dl.getTypePreferredAlignment(llvmType));
1503 alignment = std::max(4u, alignment);
1504 alignmentCache[type] = alignment;
1505 return alignment;
1506 }
1507
1508 static bool isZero(Attribute attr) {
1509 if (auto intAttr = dyn_cast<IntegerAttr>(attr))
1510 return intAttr.getValue().isZero();
1511 if (auto arrayAttr = dyn_cast<ArrayAttr>(attr))
1512 return llvm::all_of(arrayAttr, [](Attribute a) { return isZero(a); });
1513 return false;
1514 }
1515
1516 void initializeArray(ConversionPatternRewriter &rewriter, Location loc,
1517 Value alloc, ArrayAttr initAttr,
1518 ArrayRefType arrayRefType) const {
1519 size_t elemByteWidth = computeElementByteWidth(arrayRefType);
1520 Type ptrTy = LLVM::LLVMPointerType::get(getContext());
1521 Type i8Ty = rewriter.getI8Type();
1522 for (unsigned i = 0; i < arrayRefType.getNumElements(); ++i) {
1523 unsigned elemIndex = arrayRefType.getNumElements() - i - 1;
1524 Value elemOffset = LLVM::ConstantOp::create(
1525 rewriter, loc, rewriter.getI64Type(), elemIndex * elemByteWidth);
1526 auto elemAddr =
1527 LLVM::GEPOp::create(rewriter, loc, ptrTy, i8Ty, alloc, elemOffset);
1528 auto elem = LLVM::ConstantOp::create(
1529 rewriter, loc, arrayRefType.getElementType(), initAttr[i]);
1530 LLVM::StoreOp::create(rewriter, loc, elem, elemAddr);
1531 }
1532 }
1533
1534private:
1535 mutable DenseMap<ArrayRefType, size_t> alignmentCache;
1536};
1537
1538struct ArrayRefCreateOpLowering : public OpConversionPattern<ArrayRefCreateOp> {
1539 using OpConversionPattern::OpConversionPattern;
1540
1541 LogicalResult
1542 matchAndRewrite(ArrayRefCreateOp op, OpAdaptor adaptor,
1543 ConversionPatternRewriter &rewriter) const override {
1544 ArrayRefType arrayRefType = cast<ArrayRefType>(op.getType());
1545 Value alloc = adaptor.getInput();
1546 auto ptrTy = LLVM::LLVMPointerType::get(getContext());
1547 auto i8Ty = rewriter.getI8Type();
1548 size_t elemByteWidth = computeElementByteWidth(arrayRefType);
1549 auto elements = adaptor.getElements();
1550 for (unsigned i = 0; i < elements.size(); ++i) {
1551 // Note: hardcoded for little endian targets.
1552 unsigned elemIndex = arrayRefType.getNumElements() - i - 1;
1553 Value elemOffset =
1554 LLVM::ConstantOp::create(rewriter, op.getLoc(), rewriter.getI64Type(),
1555 elemIndex * elemByteWidth);
1556 auto elemAddr = LLVM::GEPOp::create(rewriter, op.getLoc(), ptrTy, i8Ty,
1557 alloc, elemOffset);
1558 LLVM::StoreOp::create(rewriter, op.getLoc(), elements[i], elemAddr);
1559 }
1560 rewriter.replaceOp(op, alloc);
1561 return success();
1562 }
1563};
1564
1565struct ArrayRefGetOpLowering : public OpConversionPattern<ArrayRefGetOp> {
1566 using OpConversionPattern::OpConversionPattern;
1567
1568 LogicalResult
1569 matchAndRewrite(ArrayRefGetOp op, OpAdaptor adaptor,
1570 ConversionPatternRewriter &rewriter) const override {
1571 auto loc = op.getLoc();
1572 ArrayRefType arrayRefType = cast<ArrayRefType>(op.getInput().getType());
1573 auto ptrTy = LLVM::LLVMPointerType::get(getContext());
1574 auto i8Ty = rewriter.getI8Type();
1575 auto i64Ty = rewriter.getI64Type();
1576 size_t elemByteWidth = computeElementByteWidth(arrayRefType);
1577 assert(!isa<ArrayRefType>(arrayRefType.getElementType()));
1578
1579 Value stride =
1580 LLVM::ConstantOp::create(rewriter, loc, i64Ty, elemByteWidth);
1581 Value byteOffset =
1582 LLVM::MulOp::create(rewriter, loc, adaptor.getIndex(), stride);
1583 // Defend against out-of-bounds accesses. What we return is undefined in the
1584 // case of OOB.
1585 size_t lastElementByteOffset =
1586 elemByteWidth * (arrayRefType.getNumElements() - 1);
1587 Value lastElementByteOffsetVal =
1588 LLVM::ConstantOp::create(rewriter, loc, i64Ty, lastElementByteOffset);
1589 Value clampedOffset = LLVM::UMinOp::create(rewriter, loc, i64Ty, byteOffset,
1590 lastElementByteOffsetVal);
1591 auto elemAddr = LLVM::GEPOp::create(rewriter, loc, ptrTy, i8Ty,
1592 adaptor.getInput(), clampedOffset);
1593 Value loaded = LLVM::LoadOp::create(
1594 rewriter, loc, typeConverter->convertType(op.getValue().getType()),
1595 elemAddr);
1596 rewriter.replaceOp(op, loaded);
1597 return success();
1598 }
1599};
1600
1601struct ArrayRefInjectOpLowering : public OpConversionPattern<ArrayRefInjectOp> {
1602 using OpConversionPattern::OpConversionPattern;
1603
1604 LogicalResult
1605 matchAndRewrite(ArrayRefInjectOp op, OpAdaptor adaptor,
1606 ConversionPatternRewriter &rewriter) const override {
1607 auto loc = op.getLoc();
1608 ArrayRefType arrayRefType = cast<ArrayRefType>(op.getInput().getType());
1609 assert(!isa<ArrayRefType>(arrayRefType.getElementType()));
1610 auto ptrTy = LLVM::LLVMPointerType::get(getContext());
1611 auto i8Ty = rewriter.getI8Type();
1612 auto i64Ty = rewriter.getI64Type();
1613 size_t byteWidth = computeByteWidth(arrayRefType);
1614 size_t elemByteWidth = computeElementByteWidth(arrayRefType);
1615
1616 Value stride =
1617 LLVM::ConstantOp::create(rewriter, loc, i64Ty, elemByteWidth);
1618 Value byteOffset =
1619 LLVM::MulOp::create(rewriter, loc, adaptor.getIndex(), stride);
1620 Value totalSize = LLVM::ConstantOp::create(rewriter, loc, i64Ty, byteWidth);
1621 // Defend against out-of-bounds accesses. We must avoid corrupting the
1622 // array.
1623 Value isInbounds = LLVM::ICmpOp::create(
1624 rewriter, loc, LLVM::ICmpPredicate::ult, byteOffset, totalSize);
1625 scf::IfOp::create(rewriter, loc, isInbounds, [&](OpBuilder &b, Location) {
1626 auto elemAddr = LLVM::GEPOp::create(b, loc, ptrTy, i8Ty,
1627 adaptor.getInput(), byteOffset);
1628 LLVM::StoreOp::create(b, loc, adaptor.getElement(), elemAddr);
1629 scf::YieldOp::create(b, loc);
1630 });
1631
1632 // Inject is pure; returns the same pointer (input buffer is modified
1633 // in-place and the pointer is forwarded as the result).
1634 rewriter.replaceOp(op, adaptor.getInput());
1635 return success();
1636 }
1637};
1638
1639struct ArrayRefSliceOpLowering : public OpConversionPattern<ArrayRefSliceOp> {
1640 using OpConversionPattern::OpConversionPattern;
1641
1642 LogicalResult
1643 matchAndRewrite(ArrayRefSliceOp op, OpAdaptor adaptor,
1644 ConversionPatternRewriter &rewriter) const override {
1645 auto loc = op.getLoc();
1646 // The result type is the sub-array type; use its element size.
1647 ArrayRefType inputType = cast<ArrayRefType>(op.getInput().getType());
1648 ArrayRefType resultType = cast<ArrayRefType>(op.getOutput().getType());
1649 auto ptrTy = LLVM::LLVMPointerType::get(getContext());
1650 auto i8Ty = rewriter.getI8Type();
1651 auto i64Ty = rewriter.getI64Type();
1652 size_t elemByteWidth = computeElementByteWidth(resultType);
1653
1654 // Ensure the slice doesn't go out of bounds.
1655 size_t maxLowIndex =
1656 inputType.getNumElements() - resultType.getNumElements();
1657 Value maxLowIndexVal =
1658 LLVM::ConstantOp::create(rewriter, loc, i64Ty, maxLowIndex);
1659 Value clampedLowIndex = LLVM::UMinOp::create(
1660 rewriter, loc, i64Ty, adaptor.getLowIndex(), maxLowIndexVal);
1661
1662 // Byte offset = lowIndex * elemByteWidth.
1663 Value stride =
1664 LLVM::ConstantOp::create(rewriter, loc, i64Ty, elemByteWidth);
1665 Value byteOffset =
1666 LLVM::MulOp::create(rewriter, loc, clampedLowIndex, stride);
1667 auto sliceAddr = LLVM::GEPOp::create(rewriter, loc, ptrTy, i8Ty,
1668 adaptor.getInput(), byteOffset);
1669 rewriter.replaceOp(op, sliceAddr);
1670 return success();
1671 }
1672};
1673
1674struct ArrayRefCopyOpLowering : public OpConversionPattern<ArrayRefCopyOp> {
1675 using OpConversionPattern::OpConversionPattern;
1676
1677 LogicalResult
1678 matchAndRewrite(ArrayRefCopyOp op, OpAdaptor adaptor,
1679 ConversionPatternRewriter &rewriter) const override {
1680 auto loc = op.getLoc();
1681 ArrayRefType arrayRefType = cast<ArrayRefType>(op.getInput().getType());
1682 auto i64Ty = rewriter.getI64Type();
1683 size_t byteWidth = computeByteWidth(arrayRefType);
1684 Value size = LLVM::ConstantOp::create(rewriter, loc, i64Ty, byteWidth);
1685 // Use a memmove rather than a memcpy just in case the arrays alias.
1686 LLVM::MemmoveOp::create(rewriter, loc, adaptor.getInput(),
1687 adaptor.getSource(), size,
1688 /*isVolatile=*/false);
1689 rewriter.replaceOp(op, adaptor.getInput());
1690 return success();
1691 }
1692};
1693
1694static Value loadArrayRefAsArray(ImplicitLocOpBuilder &builder, Value arrayRef,
1695 ArrayRefType arrayRefType,
1696 LLVM::LLVMArrayType llvmType) {
1697 auto i8Ty = builder.getI8Type();
1698 auto ptrTy = LLVM::LLVMPointerType::get(builder.getContext());
1699 size_t elemByteWidth = computeElementByteWidth(arrayRefType);
1700 Value v = LLVM::PoisonOp::create(builder, llvmType);
1701 int32_t size = arrayRefType.getNumElements();
1702 for (int32_t i = 0; i < size; i++) {
1703 int32_t byteOffset = i * elemByteWidth;
1704 Value gep = LLVM::GEPOp::create(builder, ptrTy, i8Ty, arrayRef,
1705 LLVM::GEPArg{byteOffset});
1706 Value load = LLVM::LoadOp::create(builder, llvmType.getElementType(), gep);
1707 v = LLVM::InsertValueOp::create(builder, v, load, i);
1708 }
1709 return v;
1710}
1711
1712static void storeArrayAsArrayRef(ImplicitLocOpBuilder &builder, Value array,
1713 Value arrayRef, ArrayRefType arrayRefType) {
1714 auto i8Ty = builder.getI8Type();
1715 auto ptrTy = LLVM::LLVMPointerType::get(builder.getContext());
1716 size_t elemByteWidth = computeElementByteWidth(arrayRefType);
1717 int32_t size = arrayRefType.getNumElements();
1718 for (int32_t i = 0; i < size; i++) {
1719 int32_t byteOffset = i * elemByteWidth;
1720 Value gep = LLVM::GEPOp::create(builder, ptrTy, i8Ty, arrayRef,
1721 LLVM::GEPArg{byteOffset});
1722 Value val = LLVM::ExtractValueOp::create(builder, array, i);
1723 LLVM::StoreOp::create(builder, val, gep);
1724 }
1725}
1726
1728 : public OpConversionPattern<UnrealizedConversionCastOp> {
1729 using OpConversionPattern::OpConversionPattern;
1730
1731 LogicalResult
1732 matchAndRewrite(UnrealizedConversionCastOp op, OpAdaptor adaptor,
1733 ConversionPatternRewriter &rewriter) const override {
1734 if (!isa<ArrayRefType>(op.getOperand(0).getType()) ||
1735 !isa<LLVM::LLVMArrayType>(op.getResult(0).getType())) {
1736 return failure();
1737 }
1738
1739 ImplicitLocOpBuilder b(op.getLoc(), rewriter);
1740 Value loaded = loadArrayRefAsArray(
1741 b, adaptor.getInputs().front(),
1742 cast<ArrayRefType>(op.getOperand(0).getType()),
1743 cast<LLVM::LLVMArrayType>(op.getResult(0).getType()));
1744 rewriter.replaceOp(op, loaded);
1745 return success();
1746 }
1747};
1748
1750 : public OpConversionPattern<ArrayRefToArrayOp> {
1751 using OpConversionPattern::OpConversionPattern;
1752
1753 LogicalResult
1754 matchAndRewrite(ArrayRefToArrayOp op, OpAdaptor adaptor,
1755 ConversionPatternRewriter &rewriter) const override {
1756 Type resultType = getTypeConverter()->convertType(op.getResult().getType());
1757 ImplicitLocOpBuilder b(op.getLoc(), rewriter);
1758 Value loaded = loadArrayRefAsArray(
1759 b, adaptor.getInput(), cast<ArrayRefType>(op.getInput().getType()),
1760 cast<LLVM::LLVMArrayType>(resultType));
1761 rewriter.replaceOp(op, loaded);
1762 return success();
1763 }
1764};
1765
1767 : public OpConversionPattern<ArrayRefFromArrayOp> {
1768 using OpConversionPattern::OpConversionPattern;
1769
1770 LogicalResult
1771 matchAndRewrite(ArrayRefFromArrayOp op, OpAdaptor adaptor,
1772 ConversionPatternRewriter &rewriter) const override {
1773 ImplicitLocOpBuilder b(op.getLoc(), rewriter);
1774 storeArrayAsArrayRef(b, adaptor.getArray(), adaptor.getInput(),
1775 cast<ArrayRefType>(op.getInput().getType()));
1776 rewriter.replaceOp(op, adaptor.getInput());
1777 return success();
1778 }
1779};
1780
1781//===----------------------------------------------------------------------===//
1782// Pass Implementation
1783//===----------------------------------------------------------------------===//
1784
1785namespace {
1786struct LowerArcToLLVMPass
1787 : public circt::impl::LowerArcToLLVMBase<LowerArcToLLVMPass> {
1788 void runOnOperation() override;
1789};
1790} // namespace
1791
1792void LowerArcToLLVMPass::runOnOperation() {
1793 // Add `dereferenceable(<N>)` attributes to all function arguments that take
1794 // ArrayRefTypes.
1795 for (func::FuncOp func : getOperation().getOps<func::FuncOp>()) {
1796 for (int i = 0, e = func.getNumArguments(); i != e; ++i) {
1797 if (auto arrayRefType =
1798 dyn_cast<ArrayRefType>(func.getArgumentTypes()[i])) {
1799 size_t byteWidth = computeByteWidth(arrayRefType);
1800 Builder builder(&getContext());
1801 func.setArgAttr(i, LLVM::LLVMDialect::getDereferenceableAttrName(),
1802 builder.getI64IntegerAttr(byteWidth));
1803 }
1804 }
1805 }
1806
1807 // Collect the symbols in the root op such that the HW-to-LLVM lowering can
1808 // create LLVM globals with non-colliding names.
1809 Namespace globals;
1810 SymbolCache cache;
1811 cache.addDefinitions(getOperation());
1812 globals.add(cache);
1813
1814 // Setup the conversion target. Explicitly mark `scf.yield` legal since it
1815 // does not have a conversion itself, which would cause it to fail
1816 // legalization and for the conversion to abort. (It relies on its parent op's
1817 // conversion to remove it.)
1818 LLVMConversionTarget target(getContext());
1819 target.addLegalOp<mlir::ModuleOp>();
1820 target.addLegalOp<scf::YieldOp>(); // quirk of SCF dialect conversion
1821
1822 // Mark sim::Format*Op as legal. These are not converted to LLVM, but the
1823 // lowering of sim::PrintFormattedOp walks them to build up its format string.
1824 // They are all marked Pure so are removed after the conversion.
1825 target.addLegalOp<sim::FormatLiteralOp, sim::FormatDecOp, sim::FormatHexOp,
1826 sim::FormatBinOp, sim::FormatOctOp, sim::FormatCharOp,
1827 sim::FormatStringConcatOp>();
1828
1829 // Setup the arc dialect type conversion.
1830 LLVMTypeConverter converter(&getContext());
1831 converter.addConversion([&](seq::ClockType type) {
1832 return IntegerType::get(type.getContext(), 1);
1833 });
1834 converter.addConversion([&](StorageType type) {
1835 return LLVM::LLVMPointerType::get(type.getContext());
1836 });
1837 converter.addConversion([&](ContextType type) {
1838 return LLVM::LLVMPointerType::get(type.getContext());
1839 });
1840 converter.addConversion([&](MemoryType type) {
1841 return LLVM::LLVMPointerType::get(type.getContext());
1842 });
1843 converter.addConversion([&](StateType type) {
1844 return LLVM::LLVMPointerType::get(type.getContext());
1845 });
1846 converter.addConversion([&](SimModelInstanceType type) {
1847 return LLVM::LLVMPointerType::get(type.getContext());
1848 });
1849 converter.addConversion([&](sim::FormatStringType type) {
1850 return LLVM::LLVMPointerType::get(type.getContext());
1851 });
1852 converter.addConversion([&](sim::OutputStreamType type) {
1853 return LLVM::LLVMPointerType::get(type.getContext());
1854 });
1855 converter.addConversion([&](llhd::TimeType type) {
1856 // LLHD time is represented as i64 femtoseconds.
1857 return IntegerType::get(type.getContext(), 64);
1858 });
1859 converter.addConversion([&](ArrayRefType type) {
1860 return LLVM::LLVMPointerType::get(type.getContext());
1861 });
1862
1863 // Convert an UnrealizedConversionCastOp from !arc.arrayref<T> to
1864 // !llvm.array<T>. These are inserted by the InsertRuntime pass.
1865 target.addDynamicallyLegalOp<UnrealizedConversionCastOp>([&](Operation *op) {
1866 Type src = op->getOperand(0).getType();
1867 Type dst = op->getResult(0).getType();
1868 bool needsConvert = isa<ArrayRefType>(src) && isa<LLVM::LLVMArrayType>(dst);
1869 return !needsConvert;
1870 });
1871
1872 // Setup the conversion patterns.
1873 ConversionPatternSet patterns(&getContext(), converter);
1874
1875 // MLIR patterns.
1876 populateSCFToControlFlowConversionPatterns(patterns);
1877 populateFuncToLLVMConversionPatterns(converter, patterns);
1878 cf::populateControlFlowToLLVMConversionPatterns(converter, patterns);
1879 arith::populateArithToLLVMConversionPatterns(converter, patterns);
1880 index::populateIndexToLLVMConversionPatterns(converter, patterns);
1881 ub::populateUBToLLVMConversionPatterns(converter, patterns);
1882 populateAnyFunctionOpInterfaceTypeConversionPattern(patterns, converter);
1883
1884 // CIRCT patterns.
1885 DataLayout layout = DataLayout::closest(getOperation());
1886 DenseMap<std::pair<Type, ArrayAttr>, LLVM::GlobalOp> constAggregateGlobalsMap;
1887 populateHWToLLVMTypeConversions(converter, layout);
1888 std::optional<HWToLLVMArraySpillCache> spillCacheOpt =
1890 {
1891 OpBuilder spillBuilder(getOperation());
1892 spillCacheOpt->spillNonHWOps(spillBuilder, converter, getOperation());
1893 }
1894 populateHWToLLVMConversionPatterns(converter, patterns, globals,
1895 constAggregateGlobalsMap, spillCacheOpt);
1896
1899
1900 // Arc patterns.
1901 // clang-format off
1902 patterns.add<
1903 AllocMemoryOpLowering,
1904 AllocStateLikeOpLowering<arc::AllocStateOp>,
1905 AllocStateLikeOpLowering<arc::RootInputOp>,
1906 AllocStateLikeOpLowering<arc::RootOutputOp>,
1907 AllocStorageOpLowering,
1908 AsContextOpLowering,
1909 ClockGateOpLowering,
1910 ClockInvOpLowering,
1911 ConstantTimeOpLowering,
1912 CurrentTimeOpLowering,
1913 GetNextWakeupOpLowering,
1914 IntToTimeOpLowering,
1915 MemoryReadOpLowering,
1916 MemoryWriteOpLowering,
1917 ModelOpLowering,
1918 ReplaceOpWithInputPattern<seq::ToClockOp>,
1919 ReplaceOpWithInputPattern<seq::FromClockOp>,
1921 SeqConstClockLowering,
1922 SetNextWakeupOpLowering,
1923 SimSetTimeOpLowering,
1924 StateReadOpLowering,
1925 StateWriteOpLowering,
1926 StorageGetOpLowering,
1927 TerminateOpLowering,
1928 TimeToIntOpLowering,
1929 ZeroCountOpLowering,
1939 >(converter, &getContext());
1940 // clang-format on
1941 patterns.add<ExecuteOp>(convert);
1942
1943 StringCache stringCache;
1944 patterns.add<SimEmitValueOpLowering, SimPrintFormattedProcOpLowering>(
1945 converter, &getContext(), stringCache);
1946 patterns.add<SimStreamOpLowering<sim::StdoutStreamOp>>(
1947 converter, &getContext(), runtime::APICallbacks::symNameGetStdoutStream);
1948 patterns.add<SimStreamOpLowering<sim::StderrStreamOp>>(
1949 converter, &getContext(), runtime::APICallbacks::symNameGetStderrStream);
1950
1951 auto &modelInfo = getAnalysis<ModelInfoAnalysis>();
1952 llvm::DenseMap<StringRef, ModelInfoMap> modelMap(modelInfo.infoMap.size());
1953 for (auto &[_, modelInfo] : modelInfo.infoMap) {
1954 llvm::DenseMap<StringRef, StateInfo> states(modelInfo.states.size());
1955 for (StateInfo &stateInfo : modelInfo.states)
1956 states.insert({stateInfo.name, stateInfo});
1957 modelMap.insert(
1958 {modelInfo.name,
1959 ModelInfoMap{modelInfo.numStateBytes, std::move(states),
1960 modelInfo.initialFnSym, modelInfo.finalFnSym}});
1961 }
1962
1963 patterns.add<SimInstantiateOpLowering, SimSetInputOpLowering,
1964 SimGetPortOpLowering, SimStepOpLowering>(
1965 converter, &getContext(), modelMap);
1966
1967 // Apply the conversion.
1968 ConversionConfig config;
1969 config.allowPatternRollback = false;
1970 if (failed(applyFullConversion(getOperation(), target, std::move(patterns),
1971 config)))
1972 signalPassFailure();
1973}
1974
1975std::unique_ptr<OperationPass<ModuleOp>> circt::createLowerArcToLLVMPass() {
1976 return std::make_unique<LowerArcToLLVMPass>();
1977}
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 Value loadArrayRefAsArray(ImplicitLocOpBuilder &builder, Value arrayRef, ArrayRefType arrayRefType, LLVM::LLVMArrayType llvmType)
size_t computeByteWidth(ArrayRefType type)
static llvm::Twine evalSymbolFromModelName(StringRef modelName)
size_t computeElementByteWidth(ArrayRefType arrayRefType)
static void storeArrayAsArrayRef(ImplicitLocOpBuilder &builder, Value array, Value arrayRef, ArrayRefType arrayRefType)
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
#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, mlir::DataLayout &layout)
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
void initializeArray(ConversionPatternRewriter &rewriter, Location loc, Value alloc, ArrayAttr initAttr, ArrayRefType arrayRefType) const
size_t computeAllocaAlignment(ArrayRefType type, Operation *op) const
LogicalResult matchAndRewrite(ArrayRefAllocOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
static bool isZero(Attribute attr)
DenseMap< ArrayRefType, size_t > alignmentCache
LogicalResult matchAndRewrite(ArrayRefCopyOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(ArrayRefCreateOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(ArrayRefFromArrayOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(ArrayRefGetOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(ArrayRefInjectOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(ArrayRefSliceOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(ArrayRefToArrayOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(UnrealizedConversionCastOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
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:48