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