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