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> {
116 using OpConversionPattern<ConcreteOp>::OpConversionPattern;
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, 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)
211 return rewriter.notifyMatchFailure(
212 op, "non-zero delta time component is 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> {
428 using OpConversionPattern<OpTy>::OpConversionPattern;
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 =
1889 HWToLLVMArraySpillCache();
1890 {
1891 OpBuilder spillBuilder(getOperation());
1892 spillCacheOpt->spillNonHWOps(spillBuilder, converter, getOperation());
1893 }
1894 populateHWToLLVMConversionPatterns(converter, patterns, globals,
1895 constAggregateGlobalsMap, spillCacheOpt);
1896
1897 populateCombToArithConversionPatterns(converter, patterns);
1898 populateCombToLLVMConversionPatterns(converter, patterns);
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>,
1920 RuntimeModelOpLowering,
1921 SeqConstClockLowering,
1922 SetNextWakeupOpLowering,
1923 SimSetTimeOpLowering,
1924 StateReadOpLowering,
1925 StateWriteOpLowering,
1926 StorageGetOpLowering,
1927 TerminateOpLowering,
1928 TimeToIntOpLowering,
1929 ZeroCountOpLowering,
1930 ArrayRefCreateOpLowering,
1931 ArrayRefAllocOpLowering,
1932 ArrayRefGetOpLowering,
1933 ArrayRefInjectOpLowering,
1934 ArrayRefSliceOpLowering,
1935 ArrayRefCopyOpLowering,
1936 ArrayRefToLLVMArrayOpLowering,
1937 ArrayRefToArrayOpLowering,
1938 ArrayRefFromArrayOpLowering
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)
false return failure()
Parse an optional keyword or string and set instance into 'result'.
return success()
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
#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
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