From 60c32dec510373d4b8b9b7ecb6a66208a1612b44 Mon Sep 17 00:00:00 2001 From: Leon Hielscher Date: Wed, 17 Dec 2025 16:20:24 +0100 Subject: [PATCH 1/2] [Arc] Implement InsertRutime pass --- include/circt/Dialect/Arc/ArcOps.td | 21 +++ include/circt/Dialect/Arc/ArcPasses.td | 9 + include/circt/Dialect/Arc/ModelInfo.h | 6 + lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp | 178 +++++++++++++++--- lib/Dialect/Arc/ArcOps.cpp | 49 ++++- lib/Dialect/Arc/ModelInfo.cpp | 22 +++ lib/Dialect/Arc/Transforms/CMakeLists.txt | 1 + lib/Dialect/Arc/Transforms/InsertRuntime.cpp | 145 ++++++++++++++ .../ArcToLLVM/lower-arc-to-llvm.mlir | 15 ++ test/Dialect/Arc/basic.mlir | 15 ++ test/Dialect/Arc/insert-runtime-errors.mlir | 6 + test/Dialect/Arc/insert-runtime.mlir | 81 ++++++++ test/Dialect/Arc/sim-errors.mlir | 19 ++ 13 files changed, 533 insertions(+), 34 deletions(-) create mode 100644 lib/Dialect/Arc/Transforms/InsertRuntime.cpp create mode 100644 test/Dialect/Arc/insert-runtime-errors.mlir create mode 100644 test/Dialect/Arc/insert-runtime.mlir diff --git a/include/circt/Dialect/Arc/ArcOps.td b/include/circt/Dialect/Arc/ArcOps.td index 0dbf356aa73a..1931575d9c8a 100644 --- a/include/circt/Dialect/Arc/ArcOps.td +++ b/include/circt/Dialect/Arc/ArcOps.td @@ -643,8 +643,16 @@ def SimInstantiateOp : ArcOp<"sim.instantiate", }]; let regions = (region SizedRegion<1>:$body); + let arguments = (ins OptionalAttr:$runtimeModel, + OptionalAttr:$runtimeArgs); + let hasRegionVerifier = 1; let hasCustomAssemblyFormat = 1; + + let builders = [OpBuilder<(ins), + [{ + build($_builder, $_state, FlatSymbolRefAttr{}, StringAttr{}); + }]>]; } def SimSetInputOp : ArcOp<"sim.set_input", @@ -949,4 +957,17 @@ def VectorizeReturnOp : ArcOp<"vectorize.return", [ let assemblyFormat = "operands attr-dict `:` qualified(type(operands))"; } +def RuntimeModelOp : ArcOp<"runtime.model", [ + HasParent<"::mlir::ModuleOp">, Pure, Symbol +]> { + let summary = "TODO"; + let arguments = (ins SymbolNameAttr:$sym_name, + StrAttr:$name, + I64Attr:$numStateBytes); + + let assemblyFormat = [{ + $sym_name $name `numStateBytes` $numStateBytes attr-dict + }]; +} + #endif // CIRCT_DIALECT_ARC_ARCOPS_TD diff --git a/include/circt/Dialect/Arc/ArcPasses.td b/include/circt/Dialect/Arc/ArcPasses.td index 356d389544e2..287f32a42ec1 100644 --- a/include/circt/Dialect/Arc/ArcPasses.td +++ b/include/circt/Dialect/Arc/ArcPasses.td @@ -144,6 +144,15 @@ def InferStateProperties : Pass<"arc-infer-state-properties", ]; } +def InsertRuntime : Pass<"arc-insert-runtime", "mlir::ModuleOp"> { + let summary = "TODO"; + let dependentDialects = ["mlir::LLVM::LLVMDialect"]; + let options = [ + Option<"extraArgs", "extra-args", "std::string", "", + "Extra arguments passed to the runtime when creating simulation instances"> + ]; +} + def IsolateClocks : Pass<"arc-isolate-clocks", "mlir::ModuleOp"> { let summary = "Group clocked operations into clock domains"; let constructor = "circt::arc::createIsolateClocksPass()"; diff --git a/include/circt/Dialect/Arc/ModelInfo.h b/include/circt/Dialect/Arc/ModelInfo.h index 6da58d10a852..84f42127bacf 100644 --- a/include/circt/Dialect/Arc/ModelInfo.h +++ b/include/circt/Dialect/Arc/ModelInfo.h @@ -13,6 +13,7 @@ #ifndef CIRCT_DIALECT_ARC_MODELINFO_H #define CIRCT_DIALECT_ARC_MODELINFO_H +#include "circt/Dialect/Arc/ArcOps.h" #include "mlir/IR/BuiltinOps.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/raw_ostream.h" @@ -48,6 +49,11 @@ struct ModelInfo { finalFnSym(finalFnSym) {} }; +struct ModelInfoAnalysis { + explicit ModelInfoAnalysis(Operation *container); + llvm::DenseMap infoMap; +}; + /// Collects information about states within the provided Arc model storage /// `storage`, assuming default `offset`, and adds it to `states`. mlir::LogicalResult collectStates(mlir::Value storage, unsigned offset, diff --git a/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp b/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp index d8cb46198855..3e8a635dc196 100644 --- a/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp +++ b/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp @@ -36,6 +36,9 @@ #include "llvm/Support/Debug.h" #include "llvm/Support/FormatVariadic.h" +#include "circt/Tools/arcilator/ArcRuntime/Common.h" +#include "circt/Tools/arcilator/ArcRuntime/JITBind.h" + #define DEBUG_TYPE "lower-arc-to-llvm" namespace circt { @@ -373,6 +376,8 @@ struct SimInstantiateOpLowering .getValue()); ModelInfoMap &model = modelIt->second; + bool useRuntime = op.getRuntimeModel().has_value(); + ModuleOp moduleOp = op->getParentOfType(); if (!moduleOp) return failure(); @@ -382,27 +387,63 @@ struct SimInstantiateOpLowering // FIXME: like the rest of MLIR, this assumes sizeof(intptr_t) == // sizeof(size_t) on the target architecture. Type convertedIndex = typeConverter->convertType(rewriter.getIndexType()); - - FailureOr mallocFunc = - LLVM::lookupOrCreateMallocFn(rewriter, moduleOp, convertedIndex); - if (failed(mallocFunc)) - return mallocFunc; - - FailureOr freeFunc = - LLVM::lookupOrCreateFreeFn(rewriter, moduleOp); - if (failed(freeFunc)) - return freeFunc; - Location loc = op.getLoc(); - Value numStateBytes = LLVM::ConstantOp::create( - rewriter, loc, convertedIndex, model.numStateBytes); - Value allocated = LLVM::CallOp::create(rewriter, loc, mallocFunc.value(), - ValueRange{numStateBytes}) - .getResult(); - Value zero = - LLVM::ConstantOp::create(rewriter, loc, rewriter.getI8Type(), 0); - LLVM::MemsetOp::create(rewriter, loc, allocated, zero, numStateBytes, - false); + Value allocated; + + if (useRuntime) { + auto ptrTy = LLVM::LLVMPointerType::get(getContext()); + + Value runtimeArgs; + if (op.getRuntimeArgs().has_value()) { + SmallVector argStringVec(op.getRuntimeArgsAttr().begin(), + op.getRuntimeArgsAttr().end()); + argStringVec.push_back('\0'); + auto strAttr = mlir::DenseElementsAttr::get( + mlir::RankedTensorType::get({(int64_t)argStringVec.size()}, + rewriter.getI8Type()), + llvm::ArrayRef(argStringVec)); + + auto arrayCst = LLVM::ConstantOp::create( + rewriter, loc, + LLVM::LLVMArrayType::get(rewriter.getI8Type(), argStringVec.size()), + strAttr); + auto cst1 = LLVM::ConstantOp::create(rewriter, loc, + rewriter.getI32IntegerAttr(1)); + runtimeArgs = LLVM::AllocaOp::create(rewriter, loc, ptrTy, + arrayCst.getType(), cst1); + LLVM::LifetimeStartOp::create(rewriter, loc, runtimeArgs); + LLVM::StoreOp::create(rewriter, loc, arrayCst, runtimeArgs); + } else { + runtimeArgs = LLVM::ZeroOp::create(rewriter, loc, ptrTy).getResult(); + } + auto rtModelPtr = LLVM::AddressOfOp::create(rewriter, loc, ptrTy, + op.getRuntimeModelAttr()) + .getResult(); + allocated = + LLVM::CallOp::create(rewriter, loc, {ptrTy}, + runtime::APICallbacks::symNameAllocInstance, + {rtModelPtr, runtimeArgs}) + .getResult(); + + if (op.getRuntimeArgs().has_value()) + LLVM::LifetimeEndOp::create(rewriter, loc, runtimeArgs); + + } else { + FailureOr mallocFunc = + LLVM::lookupOrCreateMallocFn(rewriter, moduleOp, convertedIndex); + if (failed(mallocFunc)) + return mallocFunc; + + Value numStateBytes = LLVM::ConstantOp::create( + rewriter, loc, convertedIndex, model.numStateBytes); + allocated = LLVM::CallOp::create(rewriter, loc, mallocFunc.value(), + ValueRange{numStateBytes}) + .getResult(); + Value zero = + LLVM::ConstantOp::create(rewriter, loc, rewriter.getI8Type(), 0); + LLVM::MemsetOp::create(rewriter, loc, allocated, zero, numStateBytes, + false); + } // Call the model's 'initial' function if present. if (model.initialFnSymbol) { @@ -426,10 +467,22 @@ struct SimInstantiateOpLowering ValueRange{allocated}); } - LLVM::CallOp::create(rewriter, loc, freeFunc.value(), - ValueRange{allocated}); - rewriter.eraseOp(op); + if (useRuntime) { + LLVM::CallOp::create(rewriter, loc, TypeRange{}, + runtime::APICallbacks::symNameDeleteInstance, + {allocated}) + .getResult(); + } else { + FailureOr freeFunc = + LLVM::lookupOrCreateFreeFn(rewriter, moduleOp); + if (failed(freeFunc)) + return freeFunc; + + LLVM::CallOp::create(rewriter, loc, freeFunc.value(), + ValueRange{allocated}); + } + rewriter.eraseOp(op); return success(); } }; @@ -679,6 +732,73 @@ static LogicalResult convert(arc::ExecuteOp op, arc::ExecuteOp::Adaptor adaptor, return success(); } +//===----------------------------------------------------------------------===// +// Runtime Implementation +//===----------------------------------------------------------------------===// + +struct RuntimeModelOpLowering + : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + static constexpr uint64_t runtimeApiVersion = ARC_RUNTIME_API_VERSION; + + LogicalResult + matchAndRewrite(arc::RuntimeModelOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const final { + + auto modelInfoStructType = LLVM::LLVMStructType::getLiteral( + getContext(), {rewriter.getI64Type(), rewriter.getI64Type(), + LLVM::LLVMPointerType::get(getContext())}); + + // Construct the Model Name String + rewriter.setInsertionPoint(op); + SmallVector modNameArray(op.getName().begin(), + op.getName().end()); + modNameArray.push_back('\0'); + auto nameGlobalType = + LLVM::LLVMArrayType::get(rewriter.getI8Type(), modNameArray.size()); + SmallString<16> globalSymName{"_arc_mod_name_"}; + globalSymName.append(op.getName()); + auto nameGlobal = LLVM::GlobalOp::create( + rewriter, op.getLoc(), nameGlobalType, /*isConstant=*/true, + LLVM::Linkage::Internal, + /*name=*/globalSymName, rewriter.getStringAttr(modNameArray), + /*alignment=*/0); + + // Construct the Model Info Struct + + auto modInfoGlobalOp = + LLVM::GlobalOp::create(rewriter, op.getLoc(), modelInfoStructType, + /*isConstant=*/false, LLVM::Linkage::External, + op.getSymName(), Attribute{}); + + Region &initRegion = modInfoGlobalOp.getInitializerRegion(); + Block *initBlock = rewriter.createBlock(&initRegion); + rewriter.setInsertionPointToStart(initBlock); + + auto apiVersionCst = LLVM::ConstantOp::create( + rewriter, op.getLoc(), rewriter.getI64IntegerAttr(runtimeApiVersion)); + auto numStateBytesCst = LLVM::ConstantOp::create(rewriter, op.getLoc(), + op.getNumStateBytesAttr()); + auto nameAddr = + LLVM::AddressOfOp::create(rewriter, op.getLoc(), nameGlobal); + + Value initStruct = + LLVM::PoisonOp::create(rewriter, op.getLoc(), modelInfoStructType); + initStruct = LLVM::InsertValueOp::create( + rewriter, op.getLoc(), initStruct, apiVersionCst, ArrayRef{0}); + initStruct = + LLVM::InsertValueOp::create(rewriter, op.getLoc(), initStruct, + numStateBytesCst, ArrayRef{1}); + initStruct = LLVM::InsertValueOp::create(rewriter, op.getLoc(), initStruct, + nameAddr, ArrayRef{2}); + LLVM::ReturnOp::create(rewriter, op.getLoc(), initStruct); + + rewriter.replaceOp(op, modInfoGlobalOp); + return success(); + } +}; + //===----------------------------------------------------------------------===// // Pass Implementation //===----------------------------------------------------------------------===// @@ -788,6 +908,7 @@ void LowerArcToLLVMPass::runOnOperation() { ModelOpLowering, ReplaceOpWithInputPattern, ReplaceOpWithInputPattern, + RuntimeModelOpLowering, SeqConstClockLowering, SimEmitValueOpLowering, StateReadOpLowering, @@ -798,14 +919,9 @@ void LowerArcToLLVMPass::runOnOperation() { // clang-format on patterns.add(convert); - SmallVector models; - if (failed(collectModels(getOperation(), models))) { - signalPassFailure(); - return; - } - - llvm::DenseMap modelMap(models.size()); - for (ModelInfo &modelInfo : models) { + auto &modelInfo = getAnalysis(); + llvm::DenseMap modelMap(modelInfo.infoMap.size()); + for (auto &[_, modelInfo] : modelInfo.infoMap) { llvm::DenseMap states(modelInfo.states.size()); for (StateInfo &stateInfo : modelInfo.states) states.insert({stateInfo.name, stateInfo}); diff --git a/lib/Dialect/Arc/ArcOps.cpp b/lib/Dialect/Arc/ArcOps.cpp index 26f71b294ad3..ed746a75946b 100644 --- a/lib/Dialect/Arc/ArcOps.cpp +++ b/lib/Dialect/Arc/ArcOps.cpp @@ -500,7 +500,19 @@ void SimInstantiateOp::print(OpAsmPrinter &p) { p << " " << modelType.getModel() << " as "; p.printRegionArgument(modelArg, {}, true); - p.printOptionalAttrDictWithKeyword(getOperation()->getAttrs()); + if (getRuntimeModel() || getRuntimeArgs()) { + p << " runtime "; + if (getRuntimeModel()) + p << getRuntimeModelAttr(); + p << "("; + if (getRuntimeArgs()) + p << getRuntimeArgsAttr(); + p << ")"; + } + + p.printOptionalAttrDictWithKeyword( + getOperation()->getAttrs(), + {getRuntimeModelAttrName(), getRuntimeArgsAttrName()}); p << " "; @@ -520,6 +532,24 @@ ParseResult SimInstantiateOp::parse(OpAsmParser &parser, if (failed(parser.parseArgument(modelArg, false, false))) return failure(); + if (succeeded(parser.parseOptionalKeyword("runtime"))) { + StringAttr runtimeSym; + StringAttr runtimeArgs; + auto symOpt = parser.parseOptionalSymbolName(runtimeSym); + if (parser.parseLParen()) + return failure(); + auto nameOpt = parser.parseOptionalAttribute(runtimeArgs); + if (parser.parseRParen()) + return failure(); + if (succeeded(symOpt)) + result.addAttribute( + SimInstantiateOp::getRuntimeModelAttrName(result.name), + FlatSymbolRefAttr::get(runtimeSym)); + if (nameOpt.has_value()) + result.addAttribute(SimInstantiateOp::getRuntimeArgsAttrName(result.name), + runtimeArgs); + } + if (failed(parser.parseOptionalAttrDictWithKeyword(result.attributes))) return failure(); @@ -547,15 +577,28 @@ LogicalResult SimInstantiateOp::verifyRegions() { LogicalResult SimInstantiateOp::verifySymbolUses(SymbolTableCollection &symbolTable) { + bool failed = false; Operation *moduleOp = getSupportedModuleOp( symbolTable, getOperation(), llvm::cast(getBody().getArgument(0).getType()) .getModel() .getAttr()); if (!moduleOp) - return failure(); + failed = true; + + if (getRuntimeModel().has_value()) { + Operation *runtimeModelOp = symbolTable.lookupNearestSymbolFrom( + getOperation(), getRuntimeModelAttr()); + if (!runtimeModelOp) { + emitOpError("runtime model not found"); + failed = true; + } else if (!isa(runtimeModelOp)) { + emitOpError("referenced runtime model is not a RuntimeModelOp"); + failed = true; + } + } - return success(); + return success(!failed); } //===----------------------------------------------------------------------===// diff --git a/lib/Dialect/Arc/ModelInfo.cpp b/lib/Dialect/Arc/ModelInfo.cpp index 84ba64b29228..584186eb02a5 100644 --- a/lib/Dialect/Arc/ModelInfo.cpp +++ b/lib/Dialect/Arc/ModelInfo.cpp @@ -186,3 +186,25 @@ void circt::arc::serializeModelInfoToJson(llvm::raw_ostream &outputStream, } }); } + +circt::arc::ModelInfoAnalysis::ModelInfoAnalysis(Operation *container) { + assert(container->getNumRegions() == 1 && "Expected single region"); + assert(container->getRegion(0).getBlocks().size() == 1 && + "Expected single body block"); + + for (auto modelOp : + container->getRegion(0).getBlocks().front().getOps()) { + auto storageArg = modelOp.getBody().getArgument(0); + auto storageType = cast(storageArg.getType()); + + SmallVector states; + if (failed(collectStates(storageArg, 0, states))) { + assert(false && "Failed to collect model states"); + continue; + } + llvm::sort(states, [](auto &a, auto &b) { return a.offset < b.offset; }); + infoMap.try_emplace(modelOp, std::string(modelOp.getName()), + storageType.getSize(), std::move(states), + modelOp.getInitialFnAttr(), modelOp.getFinalFnAttr()); + } +} diff --git a/lib/Dialect/Arc/Transforms/CMakeLists.txt b/lib/Dialect/Arc/Transforms/CMakeLists.txt index 2a5d3090574b..cdfb010a8f79 100644 --- a/lib/Dialect/Arc/Transforms/CMakeLists.txt +++ b/lib/Dialect/Arc/Transforms/CMakeLists.txt @@ -7,6 +7,7 @@ add_circt_dialect_library(CIRCTArcTransforms InferMemories.cpp InferStateProperties.cpp InlineArcs.cpp + InsertRuntime.cpp IsolateClocks.cpp LatencyRetiming.cpp LowerArcsToFuncs.cpp diff --git a/lib/Dialect/Arc/Transforms/InsertRuntime.cpp b/lib/Dialect/Arc/Transforms/InsertRuntime.cpp new file mode 100644 index 000000000000..3e8f1061f770 --- /dev/null +++ b/lib/Dialect/Arc/Transforms/InsertRuntime.cpp @@ -0,0 +1,145 @@ +//===- InsertRuntime.cpp --------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "circt/Dialect/Arc/ArcOps.h" +#include "circt/Dialect/Arc/ArcPasses.h" +#include "circt/Dialect/Arc/ModelInfo.h" +#include "mlir/Dialect/LLVMIR/LLVMDialect.h" +#include "mlir/Dialect/LLVMIR/LLVMTypes.h" + +#include "circt/Tools/arcilator/ArcRuntime/Common.h" +#include "circt/Tools/arcilator/ArcRuntime/JITBind.h" + +#include "mlir/Pass/Pass.h" + +#define DEBUG_TYPE "arc-insert-runtime" + +namespace circt { +namespace arc { +#define GEN_PASS_DEF_INSERTRUNTIME +#include "circt/Dialect/Arc/ArcPasses.h.inc" +} // namespace arc +} // namespace circt + +using namespace mlir; +using namespace circt; +using namespace arc; + +namespace { +struct InsertRuntimePass + : public arc::impl::InsertRuntimeBase { + using InsertRuntimeBase::InsertRuntimeBase; + + void runOnOperation() override; +}; +} // namespace + +struct RuntimeFuncOps { + LLVM::LLVMFuncOp allocInstance; + LLVM::LLVMFuncOp deleteInstance; + LLVM::LLVMFuncOp onEval; +}; + +static void buildInstanceLifecycleFnDecls(OpBuilder &builder, Location loc, + RuntimeFuncOps &funcOps) { + auto ptrTy = LLVM::LLVMPointerType::get(builder.getContext()); + auto voidTy = LLVM::LLVMVoidType::get(builder.getContext()); + // AllocInstance + funcOps.allocInstance = LLVM::LLVMFuncOp::create( + builder, loc, runtime::APICallbacks::symNameAllocInstance, + LLVM::LLVMFunctionType::get(ptrTy, {ptrTy, ptrTy})); + // DeleteInstance + funcOps.deleteInstance = LLVM::LLVMFuncOp::create( + builder, loc, runtime::APICallbacks::symNameDeleteInstance, + LLVM::LLVMFunctionType::get(voidTy, {ptrTy})); + // OnEval + funcOps.onEval = LLVM::LLVMFuncOp::create( + builder, loc, runtime::APICallbacks::symNameOnEval, + LLVM::LLVMFunctionType::get(voidTy, {ptrTy})); +} + +void InsertRuntimePass::runOnOperation() { + auto &modelInfo = getAnalysis(); + + OpBuilder builder(getOperation()); + builder.setInsertionPointToStart(getOperation().getBody()); + SmallDenseMap arcModelSymMap; + for (auto &[modelOp, modelInfo] : modelInfo.infoMap) { + auto symName = + builder.getStringAttr(Twine("arcRuntimeModel_") + modelInfo.name); + auto rtModelOp = + RuntimeModelOp::create(builder, modelOp.getLoc(), symName, + builder.getStringAttr(modelInfo.name), + static_cast(modelInfo.numStateBytes)); + arcModelSymMap.insert({modelOp.getSymNameAttr(), rtModelOp}); + } + + SmallVector instantiateOps; + getOperation().getBody()->walk([&](Operation *op) -> WalkResult { + if (auto instOp = dyn_cast(op)) { + // Don't touch instances which somehow already carry a runtime model + if (!instOp.getRuntimeModel()) + instantiateOps.push_back(instOp); + return WalkResult::skip(); + } + if (auto instOp = dyn_cast(op)) + return WalkResult::skip(); + return WalkResult::advance(); + }); + + RuntimeFuncOps funcOps; + if (!instantiateOps.empty()) + buildInstanceLifecycleFnDecls(builder, getOperation().getLoc(), funcOps); + + for (auto instantiateOp : instantiateOps) { + // Point the instance to the respective RuntimeModel + auto instanceModelSym = + llvm::cast( + instantiateOp.getBody().getArgument(0).getType()) + .getModel() + .getAttr(); + auto rtModelOp = arcModelSymMap.find(instanceModelSym); + if (rtModelOp == arcModelSymMap.end()) { + instantiateOp->emitOpError(" does not refer to a known Arc model."); + signalPassFailure(); + continue; + } + instantiateOp.setRuntimeModelAttr( + FlatSymbolRefAttr::get(rtModelOp->getSecond().getSymNameAttr())); + + // Add extra arguments + if (!extraArgs.empty()) { + StringAttr newArgs; + if (!instantiateOp.getRuntimeArgsAttr() || + instantiateOp.getRuntimeArgsAttr().getValue().empty()) + newArgs = StringAttr::get(&getContext(), Twine(extraArgs)); + else + newArgs = StringAttr::get(&getContext(), + Twine(instantiateOp.getRuntimeArgsAttr()) + + Twine(";") + Twine(extraArgs)); + instantiateOp.setRuntimeArgsAttr(newArgs); + } + // Insert onEval call + OpBuilder instBodyBuilder(instantiateOp); + instBodyBuilder.setInsertionPointToStart( + &instantiateOp.getBody().getBlocks().front()); + auto runtimeInst = + UnrealizedConversionCastOp::create( + instBodyBuilder, instantiateOp.getLoc(), + LLVM::LLVMPointerType::get(instBodyBuilder.getContext()), + instantiateOp.getBody().getArgument(0)) + .getResult(0); + + instantiateOp.getBody().getBlocks().front().walk([&](SimStepOp stepOp) { + instBodyBuilder.setInsertionPoint(stepOp); + LLVM::CallOp::create(instBodyBuilder, stepOp.getLoc(), funcOps.onEval, + {runtimeInst}); + }); + } + markAnalysesPreserved(); +} diff --git a/test/Conversion/ArcToLLVM/lower-arc-to-llvm.mlir b/test/Conversion/ArcToLLVM/lower-arc-to-llvm.mlir index 41acb45b9dcb..d932e7f36803 100644 --- a/test/Conversion/ArcToLLVM/lower-arc-to-llvm.mlir +++ b/test/Conversion/ArcToLLVM/lower-arc-to-llvm.mlir @@ -308,4 +308,19 @@ func.func @issue9171(%arg0: !arc.state>, %idx: i2) -> (i1) { return %get : i1 } +// CHECK-LABEL: llvm.mlir.global +// CHECK-SAME: internal constant @[[NAMESYM:.+]]("fooModelName\00") +// CHECK: llvm.mlir.global external @arcRuntimeModel_fooModelSym() {addr_space = 0 : i32} : !llvm.struct<(i64, i64, ptr)> { +// CHECK: %0 = llvm.mlir.constant(0 : i64) : i64 +// CHECK: %1 = llvm.mlir.constant(1234567 : i64) : i64 +// CHECK: %2 = llvm.mlir.addressof @[[NAMESYM]] : !llvm.ptr +// CHECK: %3 = llvm.mlir.poison : !llvm.struct<(i64, i64, ptr)> +// CHECK: %4 = llvm.insertvalue %0, %3[0] : !llvm.struct<(i64, i64, ptr)> +// CHECK: %5 = llvm.insertvalue %1, %4[1] : !llvm.struct<(i64, i64, ptr)> +// CHECK: %6 = llvm.insertvalue %2, %5[2] : !llvm.struct<(i64, i64, ptr)> +// CHECK: llvm.return %6 : !llvm.struct<(i64, i64, ptr)> +// CHECK: } + +arc.runtime.model @arcRuntimeModel_fooModelSym "fooModelName" numStateBytes 1234567 + func.func private @Dummy(%arg0: i42, %arg1: !hw.array<4xi19>, %arg2: !arc.storage) diff --git a/test/Dialect/Arc/basic.mlir b/test/Dialect/Arc/basic.mlir index e02afc3b383b..a51dae258bd3 100644 --- a/test/Dialect/Arc/basic.mlir +++ b/test/Dialect/Arc/basic.mlir @@ -357,6 +357,21 @@ func.func @with_attr() { return } +arc.runtime.model @rtFooModel "FooModel" numStateBytes 123 + +// CHECK-LABEL: func.func @with_rt +func.func @with_rt() { + // CHECK: arc.sim.instantiate @sim_test as %{{.*}} runtime @rtFooModel("args") { + // CHECK: arc.sim.instantiate @sim_test as %{{.*}} runtime @rtFooModel("args") attributes {foo = "foo"} { + // CHECK: arc.sim.instantiate @sim_test as %{{.*}} runtime @rtFooModel() { + // CHECK: arc.sim.instantiate @sim_test as %{{.*}} runtime ("args") { + arc.sim.instantiate @sim_test as %model runtime @rtFooModel("args") {} + arc.sim.instantiate @sim_test as %model runtime @rtFooModel("args") attributes {foo = "foo"} {} + arc.sim.instantiate @sim_test as %model runtime @rtFooModel() {} + arc.sim.instantiate @sim_test as %model runtime ("args") {} + return +} + // CHECK-LABEL: func.func @ReadsWrites( // CHECK-SAME: %arg0: !arc.state // CHECK-SAME: %arg1: i42 diff --git a/test/Dialect/Arc/insert-runtime-errors.mlir b/test/Dialect/Arc/insert-runtime-errors.mlir new file mode 100644 index 000000000000..63d5874ee56f --- /dev/null +++ b/test/Dialect/Arc/insert-runtime-errors.mlir @@ -0,0 +1,6 @@ +// RUN: circt-opt %s --arc-insert-runtime --verify-diagnostics + +hw.module @hwMod(in %foo: i1) {} + +// expected-error @+1 {{does not refer to a known Arc model.}} +arc.sim.instantiate @hwMod as %model {} diff --git a/test/Dialect/Arc/insert-runtime.mlir b/test/Dialect/Arc/insert-runtime.mlir new file mode 100644 index 000000000000..7e4c31e6365b --- /dev/null +++ b/test/Dialect/Arc/insert-runtime.mlir @@ -0,0 +1,81 @@ +// RUN: circt-opt %s --arc-insert-runtime | FileCheck %s --check-prefixes=CHECK,NOARGS +// RUN: circt-opt %s --arc-insert-runtime='extra-args="debug;bar"' | FileCheck %s --check-prefixes=CHECK,RTARGS + + +// CHECK-LABEL: arc.runtime.model @arcRuntimeModel_counter "counter" numStateBytes 4 +// CHECK-DAG: llvm.func @arcRuntimeIR_allocInstance(!llvm.ptr, !llvm.ptr) -> !llvm.ptr +// CHECK-DAG: llvm.func @arcRuntimeIR_deleteInstance(!llvm.ptr) +// CHECK-DAG: llvm.func @arcRuntimeIR_onEval(!llvm.ptr) + +arc.runtime.model @noTouchy "dontTouch" numStateBytes 4 + +arc.model @counter io !hw.modty { +^bb0(%arg0: !arc.storage<4>): + %c1_i8 = hw.constant 1 : i8 + %in_clk = arc.root_input "clk", %arg0 {offset = 0 : i32} : (!arc.storage<4>) -> !arc.state + %0 = arc.alloc_state %arg0 {offset = 1 : i32} : (!arc.storage<4>) -> !arc.state + %1 = arc.alloc_state %arg0 {offset = 2 : i32} : (!arc.storage<4>) -> !arc.state + %out_o = arc.root_output "o", %arg0 {offset = 3 : i32} : (!arc.storage<4>) -> !arc.state + %2 = arc.storage.get %arg0[0] : !arc.storage<4> -> !arc.state + %3 = arc.state_read %2 : + %4 = arc.storage.get %arg0[2] : !arc.storage<4> -> !arc.state + %5 = arc.state_read %4 : + arc.state_write %4 = %3 : + %6 = comb.xor %5, %3 : i1 + %7 = comb.and %6, %3 : i1 + scf.if %7 { + %11 = arc.storage.get %arg0[1] : !arc.storage<4> -> !arc.state + %12 = arc.state_read %11 : + %13 = comb.add %12, %c1_i8 : i8 + arc.state_write %11 = %13 : + } + %8 = arc.storage.get %arg0[1] : !arc.storage<4> -> !arc.state + %9 = arc.state_read %8 : + %10 = arc.storage.get %arg0[3] : !arc.storage<4> -> !arc.state + arc.state_write %10 = %9 : +} + +func.func @main() { + %zero = arith.constant 0 : i1 + %one = arith.constant 1 : i1 + %lb = arith.constant 0 : index + %ub = arith.constant 10 : index + %step = arith.constant 1 : index + + // NOARGS-LABEL: arc.sim.instantiate @counter as %arg0 runtime @arcRuntimeModel_counter() + // RTARGS-LABEL: arc.sim.instantiate @counter as %arg0 runtime @arcRuntimeModel_counter("debug;bar") + arc.sim.instantiate @counter as %model { + // CHECK: [[RTINST:%.*]] = builtin.unrealized_conversion_cast %arg0 : !arc.sim.instance<@counter> to !llvm.ptr + scf.for %i = %lb to %ub step %step { + // CHECK: arc.sim.set_input + // CHECK-NEXT: llvm.call @arcRuntimeIR_onEval([[RTINST]]) + // CHECK-NEXT: arc.sim.step + // CHECK-NEXT: arc.sim.set_input + // CHECK-NEXT: llvm.call @arcRuntimeIR_onEval([[RTINST]]) + // CHECK-NEXT: arc.sim.step + // CHECK-NOT: arcRuntimeIR_onEval + arc.sim.set_input %model, "clk" = %one : i1, !arc.sim.instance<@counter> + arc.sim.step %model : !arc.sim.instance<@counter> + arc.sim.set_input %model, "clk" = %zero : i1, !arc.sim.instance<@counter> + arc.sim.step %model : !arc.sim.instance<@counter> + } + } + + // NOARGS-LABEL: arc.sim.instantiate @counter as %arg0 runtime @arcRuntimeModel_counter("foo") + // RTARGS-LABEL: arc.sim.instantiate @counter as %arg0 runtime @arcRuntimeModel_counter("foo;debug;bar") + arc.sim.instantiate @counter as %model runtime ("foo") { + // CHECK: [[RTINST:%.*]] = builtin.unrealized_conversion_cast %arg0 : !arc.sim.instance<@counter> to !llvm.ptr + // CHECK-NEXT: llvm.call @arcRuntimeIR_onEval([[RTINST]]) + // CHECK-NEXT: arc.sim.step + // CHECK-NOT: arcRuntimeIR_onEval + arc.sim.step %model : !arc.sim.instance<@counter> + } + + // CHECK-LABEL: arc.sim.instantiate @counter as %arg0 runtime @noTouchy("foo") + // CHECK-NOT: arcRuntimeIR_onEval + arc.sim.instantiate @counter as %model runtime @noTouchy("foo") { + arc.sim.step %model : !arc.sim.instance<@counter> + } + return +} + diff --git a/test/Dialect/Arc/sim-errors.mlir b/test/Dialect/Arc/sim-errors.mlir index 77aa6254e9d7..4ca547edb77b 100644 --- a/test/Dialect/Arc/sim-errors.mlir +++ b/test/Dialect/Arc/sim-errors.mlir @@ -29,6 +29,14 @@ func.func @model_not_found() { arc.sim.instantiate @unknown as %model {} return } +// ----- + +func.func @model_and_runtime_not_found() { + // expected-error @+2 {{runtime model not found}} + // expected-error @+1 {{model not found}} + arc.sim.instantiate @unknown as %model runtime @unknown() {} + return +} // ----- @@ -44,6 +52,17 @@ func.func @port_not_found() { } return } +// ----- + +hw.module @id(in %i: i8, in %j: i8, out o: i8) { + hw.output %i : i8 +} + +func.func @no_runtime_model() { + // expected-error @+1 {{referenced runtime model is not a RuntimeModelOp}} + arc.sim.instantiate @id as %model runtime @id() {} + return +} // ----- From c4c1a5fa32eb8343fc3f544ecac9ee4dfd46eb5b Mon Sep 17 00:00:00 2001 From: Leon Hielscher Date: Fri, 19 Dec 2025 13:56:46 +0100 Subject: [PATCH 2/2] Add static asserts for field offsets --- lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp b/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp index 3e8a635dc196..d3221fcb619a 100644 --- a/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp +++ b/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp @@ -39,6 +39,8 @@ #include "circt/Tools/arcilator/ArcRuntime/Common.h" #include "circt/Tools/arcilator/ArcRuntime/JITBind.h" +#include + #define DEBUG_TYPE "lower-arc-to-llvm" namespace circt { @@ -470,8 +472,7 @@ struct SimInstantiateOpLowering if (useRuntime) { LLVM::CallOp::create(rewriter, loc, TypeRange{}, runtime::APICallbacks::symNameDeleteInstance, - {allocated}) - .getResult(); + {allocated}); } else { FailureOr freeFunc = LLVM::lookupOrCreateFreeFn(rewriter, moduleOp); @@ -749,6 +750,8 @@ struct RuntimeModelOpLowering auto modelInfoStructType = LLVM::LLVMStructType::getLiteral( getContext(), {rewriter.getI64Type(), rewriter.getI64Type(), LLVM::LLVMPointerType::get(getContext())}); + static_assert(sizeof(ArcRuntimeModelInfo) == 24 && + "Unexpected size of ArcRuntimeModelInfo struct"); // Construct the Model Name String rewriter.setInsertionPoint(op); @@ -785,13 +788,24 @@ struct RuntimeModelOpLowering Value initStruct = LLVM::PoisonOp::create(rewriter, op.getLoc(), modelInfoStructType); + + // Field: uint64_t apiVersion initStruct = LLVM::InsertValueOp::create( rewriter, op.getLoc(), initStruct, apiVersionCst, ArrayRef{0}); + static_assert(offsetof(ArcRuntimeModelInfo, apiVersion) == 0, + "Unexpected offset of field apiVersion"); + // Field: uint64_t numStateBytes initStruct = LLVM::InsertValueOp::create(rewriter, op.getLoc(), initStruct, numStateBytesCst, ArrayRef{1}); + static_assert(offsetof(ArcRuntimeModelInfo, numStateBytes) == 8, + "Unexpected offset of field numStateBytes"); + // Field: const char *modelName initStruct = LLVM::InsertValueOp::create(rewriter, op.getLoc(), initStruct, nameAddr, ArrayRef{2}); + static_assert(offsetof(ArcRuntimeModelInfo, modelName) == 16, + "Unexpected offset of field modelName"); + LLVM::ReturnOp::create(rewriter, op.getLoc(), initStruct); rewriter.replaceOp(op, modInfoGlobalOp);