CIRCT 23.0.0git
Loading...
Searching...
No Matches
InsertRuntime.cpp
Go to the documentation of this file.
1//===- InsertRuntime.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
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"
19
20#include <filesystem>
21
22#define DEBUG_TYPE "arc-insert-runtime"
23
24namespace circt {
25namespace arc {
26#define GEN_PASS_DEF_INSERTRUNTIME
27#include "circt/Dialect/Arc/ArcPasses.h.inc"
28} // namespace arc
29} // namespace circt
30
31using namespace mlir;
32using namespace circt;
33using namespace arc;
34
35namespace {
36
37// API Helpers
38struct RuntimeFunction {
39 LLVM::LLVMFuncOp llvmFuncOp = {};
40 bool used = false;
41
42protected:
43 // Add attributes for passing the model state pointer to the runtime library
44 void setModelStateArgAttrs(OpBuilder &builder, unsigned argIndex,
45 bool isMutable) {
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());
52 if (!isMutable)
53 llvmFuncOp.setArgAttr(0, LLVM::LLVMDialect::getReadonlyAttrName(),
54 builder.getUnitAttr());
55 }
56};
57
58struct AllocInstanceFunction : public RuntimeFunction {
59 explicit AllocInstanceFunction(ImplicitLocOpBuilder &builder) {
60 /*
61 uint8_t *
62 arcRuntimeIR_allocInstance(const ArcRuntimeModelInfo *model, const char
63 *args);
64 */
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));
77 }
78};
79
80struct DeleteInstanceFunction : public RuntimeFunction {
81 explicit DeleteInstanceFunction(ImplicitLocOpBuilder &builder) {
82 /*
83 void arcRuntimeIR_deleteInstance(uint8_t *modelState);
84 */
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}));
90 }
91};
92
93struct OnEvalFunction : public RuntimeFunction {
94 explicit OnEvalFunction(ImplicitLocOpBuilder &builder) {
95 /*
96 void arcRuntimeIR_onEval(uint8_t *modelState);
97 */
98 auto ptrTy = LLVM::LLVMPointerType::get(builder.getContext());
99 auto voidTy = LLVM::LLVMVoidType::get(builder.getContext());
100 llvmFuncOp =
101 LLVM::LLVMFuncOp::create(builder, runtime::APICallbacks::symNameOnEval,
102 LLVM::LLVMFunctionType::get(voidTy, {ptrTy}));
103 setModelStateArgAttrs(builder, 0, true);
104 }
105};
106
107struct OnInitializedFunction : public RuntimeFunction {
108 explicit OnInitializedFunction(ImplicitLocOpBuilder &builder) {
109 /*
110 void arcRuntimeIR_onInitialized(uint8_t *modelState);
111 */
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);
118 }
119};
120
121struct SwapTraceBufferFunction : public RuntimeFunction {
122 explicit SwapTraceBufferFunction(ImplicitLocOpBuilder &builder) {
123 /*
124 uint64_t *arcRuntimeIR_swapTraceBuffer(const uint8_t *modelState);
125 */
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);
139 }
140};
141
142// Lowering Helpers
143
144struct RuntimeModelContext; // Forward declaration
145
146struct GlobalRuntimeContext {
147 GlobalRuntimeContext() = delete;
148
149 /// Constructs a global context and adds the available runtime API function
150 /// declarations to the MLIR module
151 explicit GlobalRuntimeContext(ModuleOp moduleOp)
152 : mlirModuleOp(moduleOp), globalBuilder(createBuilder(moduleOp)),
153 allocInstanceFn(globalBuilder), deleteInstanceFn(globalBuilder),
154 onEvalFn(globalBuilder), onInitializedFn(globalBuilder),
155 swapTraceBufferFn(globalBuilder) {}
156
157 /// Delete all API functions that are never called
158 void deleteUnusedFunctions() {
159 for (auto *fn : apiFunctions)
160 if (!fn->used)
161 fn->llvmFuncOp->erase();
162 }
163
164 /// Map a type to its corresponding data type in the trace buffer
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);
169 }
170
171 /// Add an Arc model to the global runtime context
172 void addModel(ModelOp &modelOp, const ModelInfo &modelInfo);
173 /// Build a RuntimeModelOp for each registered model
174 LogicalResult buildRuntimeModelOps();
175 /// Find and assign instances of the registered models within the root module
176 LogicalResult collectInstances();
177 /// Collect tapped StateWriteOps, assign them to their model, and build the
178 /// trace instrumentation functions for the required types
179 LogicalResult buildTraceInstrumentation();
180
181 /// Lookup the trace instrumentation function for the given (extended) type
182 LLVM::LLVMFuncOp getTraceInstrumentFn(Type ty) const {
183 assert(ty.getIntOrFloatBitWidth() % 64 == 0);
184 auto fn = traceInstrumentationFns.find(ty);
185 assert(fn != traceInstrumentationFns.end());
186 return fn->second;
187 }
188
189 /// The root module
190 ModuleOp mlirModuleOp;
191 /// Builder for global operations
192 ImplicitLocOpBuilder globalBuilder;
193
194 // API Functions
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,
202 &swapTraceBufferFn};
203
204 // Maps model symbol name to model context
206
207private:
208 static ImplicitLocOpBuilder createBuilder(ModuleOp &moduleOp) {
209 auto builder = ImplicitLocOpBuilder(moduleOp.getLoc(), moduleOp);
210 builder.setInsertionPointToStart(moduleOp.getBody());
211 return builder;
212 }
213 void buildTraceInstrumentationFn(Type ty);
214
215 SmallDenseMap<Type, LLVM::LLVMFuncOp> traceInstrumentationFns;
216};
217
218struct RuntimeModelContext {
219 RuntimeModelContext() = delete;
220 /// Construct the local context for an Arc model within the global context
221 RuntimeModelContext(GlobalRuntimeContext &globalContext, ModelOp &modelOp,
222 const ModelInfo &modelInfo)
223 : globalContext(globalContext), modelOp(modelOp), modelInfo(modelInfo) {}
224
225 /// Register an MLIR defined instance of our model
226 void addInstance(SimInstantiateOp &instantiateOp) {
227 assert(!instantiateOp.getRuntimeModelAttr());
228 assert(!!runtimeModelOp);
229 instantiateOp.setRuntimeModelAttr(
230 FlatSymbolRefAttr::get(runtimeModelOp.getSymNameAttr()));
231 instances.push_back(instantiateOp);
232 }
233
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);
240 }
241
242 bool hasTraceTaps() { return runtimeModelOp.getTraceTaps().has_value(); }
243
244 /// Insert calls to the trace instrumentation functions for tapped state
245 /// writes
246 LogicalResult insertTraceInstrumentation();
247 /// Insert runtime calls to the model and its instances
248 LogicalResult lower();
249
250 /// The global runtime context
251 GlobalRuntimeContext &globalContext;
252 /// This context's model
253 ModelOp modelOp;
254 /// Model metadata
255 const ModelInfo &modelInfo;
256 /// List of registered instances
257 SmallVector<SimInstantiateOp> instances;
258 /// The model's corresponding RuntimeModelOp
259 RuntimeModelOp runtimeModelOp;
260 // StateWrite ops referring to one of this model's trace taps
261 SmallVector<StateWriteOp> tappedWrites;
262
263private:
264 LogicalResult lowerInstance(SimInstantiateOp &instance);
265};
266struct InsertRuntimePass
267 : public arc::impl::InsertRuntimeBase<InsertRuntimePass> {
268 using InsertRuntimeBase::InsertRuntimeBase;
269
270 void runOnOperation() override;
271
272private:
273 SmallString<32> quoteAndEscapeRuntimeArgument(StringRef input) {
274 SmallString<32> result;
275 result += '"';
276 for (char c : input) {
277 if (c == '"' || c == '\\')
278 result += '\\';
279 result += c;
280 }
281 result += '"';
282 return result;
283 }
284
285 // Construct the runtime argument string for an instance
286 SmallString<32> buildArgString(unsigned instIdx, StringAttr existingArgs) {
287 SmallString<32> str;
288 if (existingArgs)
289 str.append(existingArgs);
290 // If requested, append the trace file name
291 if (!traceFileName.empty()) {
292 if (!str.empty())
293 str += ';';
294 str += "traceFile=";
295 // Create a unique per-instance file name by adding a suffix before the
296 // the file extension
297 if (instIdx == 0) {
298 str += quoteAndEscapeRuntimeArgument(traceFileName);
299 } else {
300 SmallString<32> fileNameWithSuffix;
301 auto extension =
302 std::filesystem::path(static_cast<std::string>(traceFileName))
303 .extension()
304 .string();
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);
311 }
312 }
313 // Append extra arguments from pass option
314 if (!extraArgs.empty()) {
315 if (!str.empty())
316 str += ';';
317 str.append(extraArgs);
318 }
319 return str;
320 }
321};
322
323} // namespace
324
325void GlobalRuntimeContext::addModel(ModelOp &modelOp,
326 const ModelInfo &modelInfo) {
327 auto newModel =
328 std::make_unique<RuntimeModelContext>(*this, modelOp, modelInfo);
329 models[modelOp.getNameAttr()] = std::move(newModel);
330}
331
332// Find all instances in the MLIR Module and assign them to their
333// respective Arc Model
334LogicalResult GlobalRuntimeContext::collectInstances() {
335 bool hasFailed = false;
336 mlirModuleOp.getBody()->walk([&](Operation *op) -> WalkResult {
337 if (auto instOp = dyn_cast<SimInstantiateOp>(op)) {
338 // Don't touch instances which somehow already carry a runtime model
339 if (instOp.getRuntimeModel())
340 return WalkResult::skip();
341 auto instanceModelSym = llvm::cast<SimModelInstanceType>(
342 instOp.getBody().getArgument(0).getType())
343 .getModel()
344 .getAttr();
345 auto modelContext = models.find(instanceModelSym);
346 if (modelContext == models.end()) {
347 hasFailed = true;
348 instOp->emitOpError(" does not refer to a known Arc model.");
349 } else {
350 modelContext->second->addInstance(instOp);
351 }
352 return WalkResult::skip();
353 }
354 if (auto instOp = dyn_cast<ModelOp>(op))
355 return WalkResult::skip();
356 return WalkResult::advance();
357 });
358 return success(!hasFailed);
359}
360
361LogicalResult GlobalRuntimeContext::buildTraceInstrumentation() {
362 if (llvm::none_of(
363 models, [](auto &modelIt) { return modelIt.second->hasTraceTaps(); }))
364 return success();
365
366 swapTraceBufferFn.used = true;
367 SetVector<Type> tappedTypes;
368
369 mlirModuleOp.getBody()->walk([&](StateWriteOp writeOp) {
370 if (!writeOp.getTraceTapModel().has_value())
371 return;
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());
377 else
378 writeOp->emitWarning("Tracing of non-integer type is not supported");
379 });
380
381 return success();
382}
383
384// Build a trace instrumentation function recording the change of a state
385// value to the trace buffer. Calls the runtime library if the current buffer
386// is running out of space.
387// Pseudocode of the constructed function:
388//
389//
390// void _arc_trace_instrument_i{BW}(uint8_t *modelState, uint64_t traceTapId,
391// uint{BW}_t newValue) {
392// // BB: "capcaityCheckBlock"
393// const uint32_t reqSize = {BW} / 64 + 1;
394// ArcState *runtimeState = (ArcState*)(modelState - sizeof(ArcState));
395// uint64_t *oldBuffer = runtimeState->traceBuffer;
396// const uint32_t oldSize = runtimeState->traceBufferSize;
397// uint32_t newSize = oldSize + reqSize;
398// uint64_t *storePtr = &oldBuffer[oldSize];
399// if (newSize >= runtime::defaultTraceBufferCapacity) [[unlikely]] {
400// // BB: "swapBufferBlock"
401// storePtr = arcRuntimeIR_swapTraceBuffer(modelState);
402// runtimeState->traceBuffer = storePtr;
403// newSize = reqSize;
404// }
405// // BB: "bufferStoreBlock"
406// storePtr[0] = traceTapId;
407// for (unsigned qword = 0; qword < {BW} / 64; ++qword) // Unrolled
408// storePtr[qword + 1] = (uint64_t)(newValue >> (64 * qword));
409// runtimeState->traceBufferSize = newSize;
410// }
411//
412
413void GlobalRuntimeContext::buildTraceInstrumentationFn(Type ty) {
414 assert(isa<IntegerType>(ty));
415 // Check if we've already built the function
416 auto traceTy = getTraceExtendedType(ty);
417 if (traceInstrumentationFns.contains(traceTy))
418 return;
419
420 // Build the function signature
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});
435
436 // Build the body of the function
437 OpBuilder::InsertionGuard g(globalBuilder);
438 auto *capcaityCheckBlock = funcOp.addEntryBlock(globalBuilder);
439 auto *swapBufferBlock = &funcOp.getRegion().emplaceBlock();
440 auto *bufferStoreBlock = &funcOp.getRegion().emplaceBlock();
441 // storePtr
442 bufferStoreBlock->addArgument(llvmPtrTy, globalBuilder.getLoc());
443 // newSize
444 bufferStoreBlock->addArgument(i32Ty, globalBuilder.getLoc());
445
446 // --- capcaityCheckBlock ---
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)))});
457 // > const uint32_t reqSize = {BW} / 64 + 1;
458 auto requiredSize = typeQWords + 1;
459 auto reqSizeCst = LLVM::ConstantOp::create(
460 globalBuilder, globalBuilder.getI32IntegerAttr(requiredSize));
461 // > uint64_t *oldBuffer = runtimeState->traceBuffer;
462 auto bufferPtrVal =
463 LLVM::LoadOp::create(globalBuilder, llvmPtrTy, bufferPtrPtr);
464 // > const uint32_t oldSize = runtimeState->traceBufferSize;
465 auto bufferSizeVal =
466 LLVM::LoadOp::create(globalBuilder, i32Ty, bufferSizePtr);
467 auto capacityConstant = LLVM::ConstantOp::create(
468 globalBuilder,
469 globalBuilder.getI32IntegerAttr(runtime::defaultTraceBufferCapacity));
470 // > uint32_t newSize = oldSize + reqSize;
471 auto newSizeVal =
472 LLVM::AddOp::create(globalBuilder, bufferSizeVal, reqSizeCst);
473 // > uint64_t *storePtr = &oldBuffer[oldSize];
474 auto storePtr =
475 LLVM::GEPOp::create(globalBuilder, llvmPtrTy, i64Ty, bufferPtrVal,
476 {LLVM::GEPArg(bufferSizeVal)});
477 // > if (newSize >= runtime::defaultTraceBufferCapacity) [[unlikely]]
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},
483 /*weights*/
484 std::pair<int32_t, int32_t>(0, std::numeric_limits<int32_t>::max()));
485
486 // --- swapBufferBlock ---
487 globalBuilder.setInsertionPointToStart(swapBufferBlock);
488 // > storePtr = arcRuntimeIR_swapTraceBuffer(modelState);
489 auto swapCall = LLVM::CallOp::create(
490 globalBuilder, swapTraceBufferFn.llvmFuncOp, {modelStatePtr});
491 // > runtimeState->traceBuffer = storePtr;
492 LLVM::StoreOp::create(globalBuilder, swapCall.getResult(), bufferPtrPtr);
493 LLVM::BrOp::create(globalBuilder, {swapCall.getResult(), reqSizeCst},
494 bufferStoreBlock);
495
496 // --- bufferStoreBlock ---
497 globalBuilder.setInsertionPointToStart(bufferStoreBlock);
498 // > storePtr[0] = traceTapId;
499 LLVM::StoreOp::create(globalBuilder, capcaityCheckBlock->getArgument(1),
500 bufferStoreBlock->getArgument(0));
501
502 // > for (unsigned qword = 0; qword < {BW} / 64; ++qword) // Unrolled
503 for (unsigned qWord = 0; qWord < typeQWords; ++qWord) {
504 // > storePtr[qword + 1] = (uint64_t)(newValue >> (64 * 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);
509 if (qWord > 0) {
510 auto shiftCst = LLVM::ConstantOp::create(
511 globalBuilder,
512 globalBuilder.getIntegerAttr(storeVal.getType(), qWord * 64));
513 storeVal = LLVM::LShrOp::create(globalBuilder, storeVal, shiftCst);
514 }
515 if (storeVal.getType() != i64Ty)
516 storeVal = LLVM::TruncOp::create(globalBuilder, i64Ty, storeVal);
517 LLVM::StoreOp::create(globalBuilder, storeVal, dataStorePtr);
518 }
519 // > runtimeState->traceBufferSize = newSize;
520 LLVM::StoreOp::create(globalBuilder, bufferStoreBlock->getArgument(1),
521 bufferSizePtr);
522 LLVM::ReturnOp::create(globalBuilder, Value{});
523}
524
525// Build the global RuntimeModelOp for each model
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({});
538 }
539 globalBuilder.setLoc(savedLoc);
540 return success();
541}
542
543// Lower the model and all of its instances
544LogicalResult RuntimeModelContext::lower() {
545 bool hasFailed = false;
546 for (auto &instance : instances)
547 if (failed(lowerInstance(instance)))
548 hasFailed = true;
549 if (failed(insertTraceInstrumentation()))
550 hasFailed = true;
551 return success(!hasFailed);
552}
553
554// Insert call to the trace instrumentation function to each tapped write
555LogicalResult RuntimeModelContext::insertTraceInstrumentation() {
556 if (!hasTraceTaps() || tappedWrites.empty())
557 return success();
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());
565 // Lookup the instrumentation function for the state's type
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);
572 // Strip the tap annotation
573 writeOp.setTraceTapIndex(std::nullopt);
574 writeOp.setTraceTapModel(std::nullopt);
575 // Test if the new value differs from the old value
576 auto oldRead = StateReadOp::create(builder, writeOp.getState());
577 auto hasChanged = LLVM::ICmpOp::create(builder, LLVM::ICmpPredicate::ne,
578 writeOp.getValue(), oldRead);
579 scf::IfOp::create(
580 builder, hasChanged, [&](OpBuilder scfBuilder, Location loc) {
581 // Pull the state write itself under the condition
582 scfBuilder.clone(*writeOp.getOperation());
583 // Invoke the instrumentation function
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),
589 {LLVM::GEPArg(-1 *
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)
596 .getResult();
597 LLVM::CallOp::create(scfBuilder, loc, instrumentFn,
598 {baseStatePtr, tapIdxCst, storeVal});
599 scf::YieldOp::create(builder, loc);
600 });
601 writeOp.erase();
602 }
603 tappedWrites.clear();
604 return success(!hasFailed);
605}
606
607LogicalResult RuntimeModelContext::lowerInstance(SimInstantiateOp &instance) {
608 // For now, these get invoked by the lowering of SimInstantiateOp
609 globalContext.allocInstanceFn.used = true;
610 globalContext.onInitializedFn.used = true;
611 globalContext.deleteInstanceFn.used = true;
612
613 // Insert onEval call for every step call
614 OpBuilder instBodyBuilder(instance);
615 instBodyBuilder.setInsertionPointToStart(
616 &instance.getBody().getBlocks().front());
617 auto runtimeInst =
618 UnrealizedConversionCastOp::create(
619 instBodyBuilder, instance.getLoc(),
620 LLVM::LLVMPointerType::get(instBodyBuilder.getContext()),
621 instance.getBody().getArgument(0))
622 .getResult(0);
623
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});
629 });
630
631 return success();
632}
633
634void InsertRuntimePass::runOnOperation() {
635 // Construct the global context and collect information on all
636 // models and instances
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())) {
644 signalPassFailure();
645 return;
646 }
647
648 // Lower all models
649 for (auto &[_, model] : globalContext->models) {
650 // If provided, append extra instance arguments
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);
656 }
657 }
658
659 if (failed(model->lower()))
660 signalPassFailure();
661 }
662
663 globalContext->deleteUnusedFunctions();
664 markAnalysesPreserved<ModelInfoAnalysis>();
665}
assert(baseType &&"element must be base type")
Definition arc.py:1
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.