15#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
16#include "mlir/Dialect/LLVMIR/LLVMTypes.h"
17#include "mlir/Dialect/SCF/IR/SCF.h"
18#include "mlir/Pass/Pass.h"
22#define DEBUG_TYPE "arc-insert-runtime"
26#define GEN_PASS_DEF_INSERTRUNTIME
27#include "circt/Dialect/Arc/ArcPasses.h.inc"
38struct RuntimeFunction {
39 LLVM::LLVMFuncOp llvmFuncOp = {};
44 void setModelStateArgAttrs(OpBuilder &builder,
unsigned argIndex,
46 llvmFuncOp.setArgAttr(0, LLVM::LLVMDialect::getNoCaptureAttrName(),
47 builder.getUnitAttr());
48 llvmFuncOp.setArgAttr(0, LLVM::LLVMDialect::getNoFreeAttrName(),
49 builder.getUnitAttr());
50 llvmFuncOp.setArgAttr(0, LLVM::LLVMDialect::getNoAliasAttrName(),
51 builder.getUnitAttr());
53 llvmFuncOp.setArgAttr(0, LLVM::LLVMDialect::getReadonlyAttrName(),
54 builder.getUnitAttr());
58struct AllocInstanceFunction :
public RuntimeFunction {
59 explicit AllocInstanceFunction(ImplicitLocOpBuilder &builder) {
65 auto ptrTy = LLVM::LLVMPointerType::get(builder.getContext());
66 llvmFuncOp = LLVM::LLVMFuncOp::create(
67 builder, runtime::APICallbacks::symNameAllocInstance,
68 LLVM::LLVMFunctionType::get(ptrTy, {ptrTy, ptrTy}));
69 llvmFuncOp.setResultAttr(0, LLVM::LLVMDialect::getNoAliasAttrName(),
70 builder.getUnitAttr());
71 llvmFuncOp.setResultAttr(0, LLVM::LLVMDialect::getNoUndefAttrName(),
72 builder.getUnitAttr());
73 llvmFuncOp.setResultAttr(0, LLVM::LLVMDialect::getNonNullAttrName(),
74 builder.getUnitAttr());
75 llvmFuncOp.setResultAttr(0, LLVM::LLVMDialect::getAlignAttrName(),
76 builder.getI64IntegerAttr(16));
80struct DeleteInstanceFunction :
public RuntimeFunction {
81 explicit DeleteInstanceFunction(ImplicitLocOpBuilder &builder) {
85 auto ptrTy = LLVM::LLVMPointerType::get(builder.getContext());
86 auto voidTy = LLVM::LLVMVoidType::get(builder.getContext());
87 llvmFuncOp = LLVM::LLVMFuncOp::create(
88 builder, runtime::APICallbacks::symNameDeleteInstance,
89 LLVM::LLVMFunctionType::get(voidTy, {ptrTy}));
93struct OnEvalFunction :
public RuntimeFunction {
94 explicit OnEvalFunction(ImplicitLocOpBuilder &builder) {
98 auto ptrTy = LLVM::LLVMPointerType::get(builder.getContext());
99 auto voidTy = LLVM::LLVMVoidType::get(builder.getContext());
101 LLVM::LLVMFuncOp::create(builder, runtime::APICallbacks::symNameOnEval,
102 LLVM::LLVMFunctionType::get(voidTy, {ptrTy}));
103 setModelStateArgAttrs(builder, 0,
true);
107struct OnInitializedFunction :
public RuntimeFunction {
108 explicit OnInitializedFunction(ImplicitLocOpBuilder &builder) {
112 auto ptrTy = LLVM::LLVMPointerType::get(builder.getContext());
113 auto voidTy = LLVM::LLVMVoidType::get(builder.getContext());
114 llvmFuncOp = LLVM::LLVMFuncOp::create(
115 builder, runtime::APICallbacks::symNameOnInitialized,
116 LLVM::LLVMFunctionType::get(voidTy, {ptrTy}));
117 setModelStateArgAttrs(builder, 0,
true);
121struct SwapTraceBufferFunction :
public RuntimeFunction {
122 explicit SwapTraceBufferFunction(ImplicitLocOpBuilder &builder) {
126 auto ptrTy = LLVM::LLVMPointerType::get(builder.getContext());
127 llvmFuncOp = LLVM::LLVMFuncOp::create(
128 builder, runtime::APICallbacks::symNameSwapTraceBuffer,
129 LLVM::LLVMFunctionType::get(ptrTy, {ptrTy}));
130 llvmFuncOp.setResultAttr(0, LLVM::LLVMDialect::getNoAliasAttrName(),
131 builder.getUnitAttr());
132 llvmFuncOp.setResultAttr(0, LLVM::LLVMDialect::getNoUndefAttrName(),
133 builder.getUnitAttr());
134 llvmFuncOp.setResultAttr(0, LLVM::LLVMDialect::getNonNullAttrName(),
135 builder.getUnitAttr());
136 llvmFuncOp.setResultAttr(0, LLVM::LLVMDialect::getAlignAttrName(),
137 builder.getI64IntegerAttr(8));
138 setModelStateArgAttrs(builder, 0,
false);
144struct RuntimeModelContext;
146struct GlobalRuntimeContext {
147 GlobalRuntimeContext() =
delete;
151 explicit GlobalRuntimeContext(ModuleOp moduleOp)
152 : mlirModuleOp(moduleOp), globalBuilder(createBuilder(moduleOp)),
153 allocInstanceFn(globalBuilder), deleteInstanceFn(globalBuilder),
154 onEvalFn(globalBuilder), onInitializedFn(globalBuilder),
155 swapTraceBufferFn(globalBuilder) {}
158 void deleteUnusedFunctions() {
159 for (
auto *fn : apiFunctions)
161 fn->llvmFuncOp->erase();
165 static Type getTraceExtendedType(Type stateType) {
166 auto numBits = stateType.getIntOrFloatBitWidth();
167 auto numQWords = std::max((numBits + 63) / 64, 1U);
168 return IntegerType::get(stateType.getContext(), numQWords * 64);
172 void addModel(ModelOp &modelOp,
const ModelInfo &modelInfo);
174 LogicalResult buildRuntimeModelOps();
176 LogicalResult collectInstances();
179 LogicalResult buildTraceInstrumentation();
182 LLVM::LLVMFuncOp getTraceInstrumentFn(Type ty)
const {
183 assert(ty.getIntOrFloatBitWidth() % 64 == 0);
184 auto fn = traceInstrumentationFns.find(ty);
185 assert(fn != traceInstrumentationFns.end());
190 ModuleOp mlirModuleOp;
192 ImplicitLocOpBuilder globalBuilder;
195 AllocInstanceFunction allocInstanceFn;
196 DeleteInstanceFunction deleteInstanceFn;
197 OnEvalFunction onEvalFn;
198 OnInitializedFunction onInitializedFn;
199 SwapTraceBufferFunction swapTraceBufferFn;
200 const std::array<RuntimeFunction *, 5> apiFunctions = {
201 &allocInstanceFn, &deleteInstanceFn, &onEvalFn, &onInitializedFn,
208 static ImplicitLocOpBuilder createBuilder(ModuleOp &moduleOp) {
209 auto builder = ImplicitLocOpBuilder(moduleOp.getLoc(), moduleOp);
210 builder.setInsertionPointToStart(moduleOp.getBody());
213 void buildTraceInstrumentationFn(Type ty);
218struct RuntimeModelContext {
219 RuntimeModelContext() =
delete;
221 RuntimeModelContext(GlobalRuntimeContext &globalContext, ModelOp &modelOp,
222 const ModelInfo &modelInfo)
223 : globalContext(globalContext), modelOp(modelOp), modelInfo(modelInfo) {}
226 void addInstance(SimInstantiateOp &instantiateOp) {
227 assert(!instantiateOp.getRuntimeModelAttr());
229 instantiateOp.setRuntimeModelAttr(
230 FlatSymbolRefAttr::get(runtimeModelOp.getSymNameAttr()));
231 instances.push_back(instantiateOp);
234 void addTappedStateWrite(StateWriteOp &writeOp) {
235 assert(writeOp.getTraceTapModel().has_value() &&
236 writeOp.getTraceTapIndex().has_value());
237 assert(modelOp.getSymNameAttr() ==
238 writeOp.getTraceTapModelAttr().getAttr());
239 tappedWrites.push_back(writeOp);
242 bool hasTraceTaps() {
return runtimeModelOp.getTraceTaps().has_value(); }
246 LogicalResult insertTraceInstrumentation();
248 LogicalResult lower();
251 GlobalRuntimeContext &globalContext;
255 const ModelInfo &modelInfo;
257 SmallVector<SimInstantiateOp> instances;
259 RuntimeModelOp runtimeModelOp;
261 SmallVector<StateWriteOp> tappedWrites;
264 LogicalResult lowerInstance(SimInstantiateOp &instance);
266struct InsertRuntimePass
267 :
public arc::impl::InsertRuntimeBase<InsertRuntimePass> {
268 using InsertRuntimeBase::InsertRuntimeBase;
270 void runOnOperation()
override;
273 SmallString<32> quoteAndEscapeRuntimeArgument(StringRef input) {
274 SmallString<32> result;
276 for (
char c : input) {
277 if (c ==
'"' || c ==
'\\')
286 SmallString<32> buildArgString(
unsigned instIdx, StringAttr existingArgs) {
289 str.append(existingArgs);
291 if (!traceFileName.empty()) {
298 str += quoteAndEscapeRuntimeArgument(traceFileName);
300 SmallString<32> fileNameWithSuffix;
302 std::filesystem::path(
static_cast<std::string
>(traceFileName))
305 fileNameWithSuffix +=
306 traceFileName.substr(0, traceFileName.size() - extension.size());
307 fileNameWithSuffix +=
'_';
308 fileNameWithSuffix += std::to_string(instIdx);
309 fileNameWithSuffix += extension;
310 str += quoteAndEscapeRuntimeArgument(fileNameWithSuffix);
314 if (!extraArgs.empty()) {
317 str.append(extraArgs);
325void GlobalRuntimeContext::addModel(ModelOp &modelOp,
326 const ModelInfo &modelInfo) {
328 std::make_unique<RuntimeModelContext>(*
this, modelOp, modelInfo);
329 models[modelOp.getNameAttr()] = std::move(newModel);
334LogicalResult GlobalRuntimeContext::collectInstances() {
335 bool hasFailed =
false;
336 mlirModuleOp.getBody()->walk([&](Operation *op) -> WalkResult {
337 if (
auto instOp = dyn_cast<SimInstantiateOp>(op)) {
339 if (instOp.getRuntimeModel())
340 return WalkResult::skip();
341 auto instanceModelSym = llvm::cast<SimModelInstanceType>(
342 instOp.getBody().getArgument(0).getType())
345 auto modelContext = models.find(instanceModelSym);
346 if (modelContext == models.end()) {
348 instOp->emitOpError(
" does not refer to a known Arc model.");
350 modelContext->second->addInstance(instOp);
352 return WalkResult::skip();
354 if (
auto instOp = dyn_cast<ModelOp>(op))
355 return WalkResult::skip();
356 return WalkResult::advance();
358 return success(!hasFailed);
361LogicalResult GlobalRuntimeContext::buildTraceInstrumentation() {
363 models, [](
auto &modelIt) {
return modelIt.second->hasTraceTaps(); }))
366 swapTraceBufferFn.used =
true;
367 SetVector<Type> tappedTypes;
369 mlirModuleOp.getBody()->walk([&](StateWriteOp writeOp) {
370 if (!writeOp.getTraceTapModel().has_value())
372 auto modelCtxt = models.find(writeOp.getTraceTapModelAttr().getAttr());
373 assert(modelCtxt != models.end() &&
"Unknown referenced model");
374 modelCtxt->second->addTappedStateWrite(writeOp);
375 if (isa<IntegerType>(writeOp.getValue().getType()))
376 buildTraceInstrumentationFn(writeOp.getValue().getType());
378 writeOp->emitWarning(
"Tracing of non-integer type is not supported");
413void GlobalRuntimeContext::buildTraceInstrumentationFn(Type ty) {
414 assert(isa<IntegerType>(ty));
416 auto traceTy = getTraceExtendedType(ty);
417 if (traceInstrumentationFns.contains(traceTy))
421 auto typeQWords = traceTy.getIntOrFloatBitWidth() / 64;
422 assert(traceTy.getIntOrFloatBitWidth() % 64 == 0);
423 auto *ctx = ty.getContext();
424 auto i64Ty = IntegerType::get(ctx, 64);
425 auto i32Ty = IntegerType::get(ctx, 32);
426 auto llvmPtrTy = LLVM::LLVMPointerType::get(ctx);
427 auto llvmFnTy = LLVM::LLVMFunctionType::get(LLVM::LLVMVoidType::get(ctx),
428 {llvmPtrTy, i64Ty, traceTy});
429 auto symName = StringAttr::get(
430 ctx,
"_arc_trace_instrument_i" + Twine(traceTy.getIntOrFloatBitWidth()));
431 auto funcOp = LLVM::LLVMFuncOp::create(globalBuilder, symName, llvmFnTy,
432 LLVM::Linkage::Private);
433 funcOp.setNoInline(
true);
434 traceInstrumentationFns.insert({traceTy, funcOp});
437 OpBuilder::InsertionGuard g(globalBuilder);
438 auto *capcaityCheckBlock = funcOp.addEntryBlock(globalBuilder);
439 auto *swapBufferBlock = &funcOp.getRegion().emplaceBlock();
440 auto *bufferStoreBlock = &funcOp.getRegion().emplaceBlock();
442 bufferStoreBlock->addArgument(llvmPtrTy, globalBuilder.getLoc());
444 bufferStoreBlock->addArgument(i32Ty, globalBuilder.getLoc());
447 globalBuilder.setInsertionPointToStart(capcaityCheckBlock);
448 auto modelStatePtr = capcaityCheckBlock->getArgument(0);
449 auto bufferPtrPtr = LLVM::GEPOp::create(
450 globalBuilder, llvmPtrTy, globalBuilder.getI8Type(), modelStatePtr,
451 {LLVM::GEPArg(static_cast<int>(offsetof(ArcState, traceBuffer)) -
452 static_cast<int>(sizeof(ArcState)))});
453 auto bufferSizePtr = LLVM::GEPOp::create(
454 globalBuilder, llvmPtrTy, globalBuilder.getI8Type(), modelStatePtr,
455 {LLVM::GEPArg(static_cast<int>(offsetof(ArcState, traceBufferSize)) -
456 static_cast<int>(sizeof(ArcState)))});
458 auto requiredSize = typeQWords + 1;
459 auto reqSizeCst = LLVM::ConstantOp::create(
460 globalBuilder, globalBuilder.getI32IntegerAttr(requiredSize));
463 LLVM::LoadOp::create(globalBuilder, llvmPtrTy, bufferPtrPtr);
466 LLVM::LoadOp::create(globalBuilder, i32Ty, bufferSizePtr);
467 auto capacityConstant = LLVM::ConstantOp::create(
469 globalBuilder.getI32IntegerAttr(runtime::defaultTraceBufferCapacity));
472 LLVM::AddOp::create(globalBuilder, bufferSizeVal, reqSizeCst);
475 LLVM::GEPOp::create(globalBuilder, llvmPtrTy, i64Ty, bufferPtrVal,
476 {LLVM::GEPArg(bufferSizeVal)});
478 auto needsSwap = LLVM::ICmpOp::create(globalBuilder, LLVM::ICmpPredicate::ugt,
479 newSizeVal, capacityConstant);
480 LLVM::CondBrOp::create(
481 globalBuilder, needsSwap, swapBufferBlock, {}, bufferStoreBlock,
482 {storePtr, newSizeVal},
484 std::pair<int32_t, int32_t>(0, std::numeric_limits<int32_t>::max()));
487 globalBuilder.setInsertionPointToStart(swapBufferBlock);
489 auto swapCall = LLVM::CallOp::create(
490 globalBuilder, swapTraceBufferFn.llvmFuncOp, {modelStatePtr});
492 LLVM::StoreOp::create(globalBuilder, swapCall.getResult(), bufferPtrPtr);
493 LLVM::BrOp::create(globalBuilder, {swapCall.getResult(), reqSizeCst},
497 globalBuilder.setInsertionPointToStart(bufferStoreBlock);
499 LLVM::StoreOp::create(globalBuilder, capcaityCheckBlock->getArgument(1),
500 bufferStoreBlock->getArgument(0));
503 for (
unsigned qWord = 0; qWord < typeQWords; ++qWord) {
505 auto dataStorePtr = LLVM::GEPOp::create(globalBuilder, llvmPtrTy, i64Ty,
506 bufferStoreBlock->getArgument(0),
507 {LLVM::GEPArg(qWord + 1)});
508 Value storeVal = capcaityCheckBlock->getArgument(2);
510 auto shiftCst = LLVM::ConstantOp::create(
512 globalBuilder.getIntegerAttr(storeVal.getType(), qWord * 64));
513 storeVal = LLVM::LShrOp::create(globalBuilder, storeVal, shiftCst);
515 if (storeVal.getType() != i64Ty)
516 storeVal = LLVM::TruncOp::create(globalBuilder, i64Ty, storeVal);
517 LLVM::StoreOp::create(globalBuilder, storeVal, dataStorePtr);
520 LLVM::StoreOp::create(globalBuilder, bufferStoreBlock->getArgument(1),
522 LLVM::ReturnOp::create(globalBuilder, Value{});
526LogicalResult GlobalRuntimeContext::buildRuntimeModelOps() {
527 auto savedLoc = globalBuilder.getLoc();
528 for (
auto &[_, model] : models) {
529 globalBuilder.setLoc(model->modelOp.getLoc());
530 auto symName = globalBuilder.getStringAttr(Twine(
"arcRuntimeModel_") +
531 model->modelInfo.name);
532 model->runtimeModelOp = RuntimeModelOp::create(
533 globalBuilder, symName,
534 globalBuilder.getStringAttr(model->modelInfo.name),
535 static_cast<uint64_t
>(model->modelInfo.numStateBytes),
536 model->modelOp.getTraceTapsAttr());
537 model->modelOp.setTraceTapsAttr({});
539 globalBuilder.setLoc(savedLoc);
544LogicalResult RuntimeModelContext::lower() {
545 bool hasFailed =
false;
546 for (
auto &instance : instances)
547 if (failed(lowerInstance(instance)))
549 if (failed(insertTraceInstrumentation()))
551 return success(!hasFailed);
555LogicalResult RuntimeModelContext::insertTraceInstrumentation() {
556 if (!hasTraceTaps() || tappedWrites.empty())
558 bool hasFailed =
false;
559 ImplicitLocOpBuilder builder(runtimeModelOp.getLoc(),
560 runtimeModelOp.getContext());
561 auto ptrTy = LLVM::LLVMPointerType::get(builder.getContext());
562 for (
auto writeOp : tappedWrites) {
563 builder.setInsertionPoint(writeOp);
564 builder.setLoc(writeOp.getLoc());
566 auto tapId = *writeOp.getTraceTapIndex();
567 assert(tapId < runtimeModelOp.getTraceTapsAttr().size());
568 auto tapAttr = cast<TraceTapAttr>(runtimeModelOp.getTraceTapsAttr()[tapId]);
569 auto traceTy = GlobalRuntimeContext::getTraceExtendedType(
570 writeOp.getValue().getType());
571 auto instrumentFn = globalContext.getTraceInstrumentFn(traceTy);
573 writeOp.setTraceTapIndex(std::nullopt);
574 writeOp.setTraceTapModel(std::nullopt);
576 auto oldRead = StateReadOp::create(builder, writeOp.getState());
577 auto hasChanged = LLVM::ICmpOp::create(builder, LLVM::ICmpPredicate::ne,
578 writeOp.getValue(), oldRead);
580 builder, hasChanged, [&](OpBuilder scfBuilder, Location loc) {
582 scfBuilder.clone(*writeOp.getOperation());
584 auto statePtrCast = UnrealizedConversionCastOp::create(
585 scfBuilder, loc, ptrTy, writeOp.getState());
586 auto baseStatePtr = LLVM::GEPOp::create(
587 scfBuilder, loc, ptrTy, scfBuilder.getI8Type(),
588 statePtrCast.getResult(0),
590 static_cast<int32_t>(tapAttr.getStateOffset()))});
591 auto tapIdxCst = LLVM::ConstantOp::create(
592 scfBuilder, loc, scfBuilder.getI64IntegerAttr(tapId));
593 Value storeVal = writeOp.getValue();
594 if (traceTy != storeVal.getType())
595 storeVal = LLVM::ZExtOp::create(scfBuilder, loc, traceTy, storeVal)
597 LLVM::CallOp::create(scfBuilder, loc, instrumentFn,
598 {baseStatePtr, tapIdxCst, storeVal});
599 scf::YieldOp::create(builder, loc);
603 tappedWrites.clear();
604 return success(!hasFailed);
607LogicalResult RuntimeModelContext::lowerInstance(SimInstantiateOp &instance) {
609 globalContext.allocInstanceFn.used =
true;
610 globalContext.onInitializedFn.used =
true;
611 globalContext.deleteInstanceFn.used =
true;
614 OpBuilder instBodyBuilder(instance);
615 instBodyBuilder.setInsertionPointToStart(
616 &instance.getBody().getBlocks().front());
618 UnrealizedConversionCastOp::create(
619 instBodyBuilder, instance.getLoc(),
620 LLVM::LLVMPointerType::get(instBodyBuilder.getContext()),
621 instance.getBody().getArgument(0))
624 instance.getBody().getBlocks().front().walk([&](SimStepOp stepOp) {
625 instBodyBuilder.setInsertionPoint(stepOp);
626 globalContext.onEvalFn.used =
true;
627 LLVM::CallOp::create(instBodyBuilder, stepOp.getLoc(),
628 globalContext.onEvalFn.llvmFuncOp, {runtimeInst});
634void InsertRuntimePass::runOnOperation() {
637 auto &modelInfo = getAnalysis<ModelInfoAnalysis>();
638 auto globalContext = std::make_unique<GlobalRuntimeContext>(getOperation());
639 for (
auto &[mOp, mInfo] : modelInfo.infoMap)
640 globalContext->addModel(mOp, mInfo);
641 if (failed(globalContext->buildRuntimeModelOps()) ||
642 failed(globalContext->buildTraceInstrumentation()) ||
643 failed(globalContext->collectInstances())) {
649 for (
auto &[_, model] : globalContext->models) {
651 if (!extraArgs.empty() || !traceFileName.empty()) {
652 for (
auto [idx, instance] :
llvm::enumerate(model->instances)) {
653 auto newArgs = buildArgString(idx, instance.getRuntimeArgsAttr());
654 auto newArgAttr = StringAttr::get(&getContext(), newArgs);
655 instance.setRuntimeArgsAttr(newArgAttr);
659 if (failed(model->lower()))
663 globalContext->deleteUnusedFunctions();
664 markAnalysesPreserved<ModelInfoAnalysis>();
assert(baseType &&"element must be base type")
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.