[AIG][CAPI] Add C API for LongestPathAnalysis (#8652)

This commit introduces C API bindings for AIG LongestPathAnalysis and LongestPathCollection, enabling longest path analysis of AIG circuits from C and other languages.

The API uses JSON serialization for path data exchange, providing a stable interface while the underlying data structures evolve. Paths are automatically sorted by delay in descending order for efficient critical path analysis.
This commit is contained in:
Hideto Ueno 2025-07-04 19:28:56 -07:00 committed by GitHub
parent 796f83ba94
commit 4bb54cdf9d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 349 additions and 6 deletions

View File

@ -18,6 +18,56 @@ extern "C" {
MLIR_DECLARE_CAPI_DIALECT_REGISTRATION(AIG, aig);
MLIR_CAPI_EXPORTED void registerAIGPasses(void);
#define DEFINE_C_API_STRUCT(name, storage) \
struct name { \
storage *ptr; \
}; \
typedef struct name name
//===----------------------------------------------------------------------===//
// LongestPathAnalysis
//===----------------------------------------------------------------------===//
// Opaque handle to LongestPathAnalysis
DEFINE_C_API_STRUCT(AIGLongestPathAnalysis, void);
// Opaque handle to LongestPathCollection
DEFINE_C_API_STRUCT(AIGLongestPathCollection, void);
#undef DEFINE_C_API_STRUCT
// Create a LongestPathAnalysis for the given module
MLIR_CAPI_EXPORTED AIGLongestPathAnalysis
aigLongestPathAnalysisCreate(MlirOperation module, bool traceDebugPoints);
// Destroy a LongestPathAnalysis
MLIR_CAPI_EXPORTED void
aigLongestPathAnalysisDestroy(AIGLongestPathAnalysis analysis);
MLIR_CAPI_EXPORTED AIGLongestPathCollection aigLongestPathAnalysisGetAllPaths(
AIGLongestPathAnalysis analysis, MlirStringRef moduleName,
bool elaboratePaths);
//===----------------------------------------------------------------------===//
// LongestPathCollection
//===----------------------------------------------------------------------===//
// Check if the collection is valid
MLIR_CAPI_EXPORTED bool
aigLongestPathCollectionIsNull(AIGLongestPathCollection collection);
// Destroy a LongestPathCollection
MLIR_CAPI_EXPORTED void
aigLongestPathCollectionDestroy(AIGLongestPathCollection collection);
// Get the number of paths in the collection
MLIR_CAPI_EXPORTED size_t
aigLongestPathCollectionGetSize(AIGLongestPathCollection collection);
// Get a specific path from the collection as JSON
MLIR_CAPI_EXPORTED MlirStringRef aigLongestPathCollectionGetPath(
AIGLongestPathCollection collection, int pathIndex);
#ifdef __cplusplus
}
#endif

View File

@ -9,10 +9,113 @@
#include "circt-c/Dialect/AIG.h"
#include "circt/Dialect/AIG/AIGDialect.h"
#include "circt/Dialect/AIG/AIGPasses.h"
#include "circt/Dialect/AIG/Analysis/LongestPathAnalysis.h"
#include "mlir-c/BuiltinAttributes.h"
#include "mlir-c/IR.h"
#include "mlir-c/Support.h"
#include "mlir/CAPI/IR.h"
#include "mlir/CAPI/Registration.h"
#include "mlir/CAPI/Support.h"
#include "mlir/Pass/AnalysisManager.h"
#include "llvm/Support/JSON.h"
using namespace circt;
using namespace circt::aig;
MLIR_DEFINE_CAPI_DIALECT_REGISTRATION(AIG, aig, circt::aig::AIGDialect)
void registerAIGPasses() { circt::aig::registerPasses(); }
// Wrapper struct to hold both the analysis and the analysis manager
struct LongestPathAnalysisWrapper {
std::unique_ptr<mlir::ModuleAnalysisManager> analysisManager;
std::unique_ptr<LongestPathAnalysis> analysis;
};
DEFINE_C_API_PTR_METHODS(AIGLongestPathAnalysis, LongestPathAnalysisWrapper)
DEFINE_C_API_PTR_METHODS(AIGLongestPathCollection, LongestPathCollection)
//===----------------------------------------------------------------------===//
// LongestPathAnalysis C API
//===----------------------------------------------------------------------===//
AIGLongestPathAnalysis aigLongestPathAnalysisCreate(MlirOperation module,
bool traceDebugPoints) {
auto *op = unwrap(module);
auto *wrapper = new LongestPathAnalysisWrapper();
wrapper->analysisManager =
std::make_unique<mlir::ModuleAnalysisManager>(op, nullptr);
mlir::AnalysisManager am = *wrapper->analysisManager;
if (traceDebugPoints)
wrapper->analysis = std::make_unique<LongestPathAnalysisWithTrace>(op, am);
else
wrapper->analysis = std::make_unique<LongestPathAnalysis>(op, am);
return wrap(wrapper);
}
void aigLongestPathAnalysisDestroy(AIGLongestPathAnalysis analysis) {
delete unwrap(analysis);
}
AIGLongestPathCollection
aigLongestPathAnalysisGetAllPaths(AIGLongestPathAnalysis analysis,
MlirStringRef moduleName,
bool elaboratePaths) {
auto *wrapper = unwrap(analysis);
auto *lpa = wrapper->analysis.get();
auto moduleNameAttr = StringAttr::get(lpa->getContext(), unwrap(moduleName));
auto *collection = new LongestPathCollection(lpa->getContext());
if (!lpa->isAnalysisAvailable(moduleNameAttr) ||
failed(
lpa->getAllPaths(moduleNameAttr, collection->paths, elaboratePaths)))
return {nullptr};
collection->sortInDescendingOrder();
return wrap(collection);
}
// ===----------------------------------------------------------------------===//
// LongestPathCollection
// ===----------------------------------------------------------------------===//
bool aigLongestPathCollectionIsNull(AIGLongestPathCollection collection) {
return !collection.ptr;
}
void aigLongestPathCollectionDestroy(AIGLongestPathCollection collection) {
delete unwrap(collection);
}
size_t aigLongestPathCollectionGetSize(AIGLongestPathCollection collection) {
auto *wrapper = unwrap(collection);
return wrapper->paths.size();
}
MlirStringRef
aigLongestPathCollectionGetPath(AIGLongestPathCollection collection,
int pathIndex) {
auto *wrapper = unwrap(collection);
// Check if pathIndex is valid
if (pathIndex < 0 || pathIndex >= static_cast<int>(wrapper->paths.size()))
return wrap(llvm::StringRef(""));
// Convert the specific path to JSON
// FIXME: Avoid converting to JSON and then back to string. Use native
// CAPI instead once data structure is stabilized.
llvm::json::Value pathJson = toJSON(wrapper->paths[pathIndex]);
std::string jsonStr;
llvm::raw_string_ostream os(jsonStr);
os << pathJson;
auto ctx = wrap(wrapper->getContext());
// Use MLIR StringAttr to manage the string lifetime.
// FIXME: This is safe but expensive. Consider manually managing the string
// lifetime.
MlirAttribute strAttr =
mlirStringAttrGet(ctx, mlirStringRefCreateFromCString(os.str().c_str()));
return mlirStringAttrGetValue(strAttr);
}

View File

@ -110,3 +110,18 @@ target_link_libraries(circt-capi-rtgtest-test
MLIRCAPIIR
CIRCTCAPIRTGTest
)
add_llvm_executable(circt-capi-aig-test
PARTIAL_SOURCES_INTENDED
aig.c
)
llvm_update_compile_flags(circt-capi-aig-test)
target_link_libraries(circt-capi-aig-test
PRIVATE
MLIRCAPIIR
CIRCTCAPIAIG
CIRCTCAPIHW
CIRCTCAPISeq
)

174
test/CAPI/aig.c Normal file
View File

@ -0,0 +1,174 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
/* RUN: circt-capi-aig-test 2>&1 | FileCheck %s
*/
#include "circt-c/Dialect/AIG.h"
#include "circt-c/Dialect/HW.h"
#include "circt-c/Dialect/Seq.h"
#include "mlir-c/BuiltinAttributes.h"
#include "mlir-c/BuiltinTypes.h"
#include "mlir-c/IR.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void testAIGDialectRegistration(void) {
MlirContext ctx = mlirContextCreate();
mlirDialectHandleRegisterDialect(mlirGetDialectHandle__aig__(), ctx);
MlirDialect aig =
mlirContextGetOrLoadDialect(ctx, mlirStringRefCreateFromCString("aig"));
// CHECK: AIG dialect registration: PASS
if (!mlirDialectIsNull(aig))
printf("AIG dialect registration: PASS\n");
mlirContextDestroy(ctx);
}
void testLongestPathAnalysis(void) {
MlirContext ctx = mlirContextCreate();
mlirDialectHandleLoadDialect(mlirGetDialectHandle__aig__(), ctx);
mlirDialectHandleLoadDialect(mlirGetDialectHandle__hw__(), ctx);
mlirDialectHandleLoadDialect(mlirGetDialectHandle__seq__(), ctx);
// clang-format off
const char *moduleStr =
"hw.module private @ch(in %c : !seq.clock, in %a: i1, out x: i1) {\n"
" %p = seq.compreg %q, %c : i1\n"
" %q = aig.and_inv %p, %p {sv.namehint = \"q\"}: i1\n"
" hw.output %p: i1\n"
"}\n"
"hw.module private @top(in %c : !seq.clock, in %a: i1) {\n"
" %0 = hw.instance \"i1\" @ch(c: %c: !seq.clock, a: %a: i1) -> (x: i1)\n"
" %1 = hw.instance \"i2\" @ch(c: %c: !seq.clock, a: %a: i1) -> (x: i1)\n"
"}\n";
// clang-format on
MlirModule module =
mlirModuleCreateParse(ctx, mlirStringRefCreateFromCString(moduleStr));
MlirOperation moduleOp = mlirModuleGetOperation(module);
// Test with debug points enabled
{
AIGLongestPathAnalysis analysis =
aigLongestPathAnalysisCreate(moduleOp, true);
MlirStringRef moduleName = mlirStringRefCreateFromCString("top");
AIGLongestPathCollection collection1 =
aigLongestPathAnalysisGetAllPaths(analysis, moduleName, true);
AIGLongestPathCollection collection2 =
aigLongestPathAnalysisGetAllPaths(analysis, moduleName, false);
size_t pathCount = aigLongestPathCollectionGetSize(collection1);
printf("Path count with elaboration: %zu\n", pathCount);
// CHECK: Path count with elaboration: 2
pathCount = aigLongestPathCollectionGetSize(collection2);
printf("Path count without elaboration: %zu\n", pathCount);
// CHECK: Path count without elaboration: 1
// Test getting individual paths
// Test multiple path access
MlirStringRef pathJson = aigLongestPathCollectionGetPath(collection1, 1);
printf("Path: %.*s\n", (int)pathJson.length, pathJson.data);
// CHECK: Path: {"fan_out":{"bit_pos":
// CHECK-SAME: "history":[{
// Cleanup
aigLongestPathCollectionDestroy(collection1);
aigLongestPathCollectionDestroy(collection2);
aigLongestPathAnalysisDestroy(analysis);
printf("LongestPathAnalysis with debug points: PASS\n");
// CHECK: LongestPathAnalysis with debug points: PASS
}
// Test without debug points
{
AIGLongestPathAnalysis analysis =
aigLongestPathAnalysisCreate(moduleOp, false);
MlirStringRef moduleName = mlirStringRefCreateFromCString("top");
AIGLongestPathCollection collection =
aigLongestPathAnalysisGetAllPaths(analysis, moduleName, true);
MlirStringRef pathJson = aigLongestPathCollectionGetPath(collection, 1);
printf("Path: %.*s\n", (int)pathJson.length, pathJson.data);
// CHECK: "history":[]
// Cleanup
aigLongestPathCollectionDestroy(collection);
aigLongestPathAnalysisDestroy(analysis);
printf("LongestPathAnalysis without debug points: PASS\n");
// CHECK: LongestPathAnalysis without debug points: PASS
}
mlirModuleDestroy(module);
mlirContextDestroy(ctx);
}
void testErrorHandling(void) {
MlirContext ctx = mlirContextCreate();
mlirDialectHandleLoadDialect(mlirGetDialectHandle__aig__(), ctx);
mlirDialectHandleLoadDialect(mlirGetDialectHandle__hw__(), ctx);
const char *moduleStr =
"hw.module @test(in %a: i1, in %b: i1, out out: i1) {\n"
" hw.output %a : i1\n"
"}\n";
MlirModule module =
mlirModuleCreateParse(ctx, mlirStringRefCreateFromCString(moduleStr));
MlirOperation moduleOp = mlirModuleGetOperation(module);
AIGLongestPathAnalysis analysis =
aigLongestPathAnalysisCreate(moduleOp, true);
MlirStringRef invalidModuleName = mlirStringRefCreateFromCString("unknown");
AIGLongestPathCollection invalidCollection =
aigLongestPathAnalysisGetAllPaths(analysis, invalidModuleName, true);
if (aigLongestPathCollectionIsNull(invalidCollection)) {
// CHECK: Invalid module name handling: PASS
printf("Invalid module name handling: PASS\n");
}
MlirStringRef moduleName = mlirStringRefCreateFromCString("test");
AIGLongestPathCollection collection =
aigLongestPathAnalysisGetAllPaths(analysis, moduleName, true);
MlirStringRef invalidPath = aigLongestPathCollectionGetPath(collection, 100);
if (invalidPath.length == 0) {
printf("Invalid path index handling: PASS\n");
// CHECK: Invalid path index handling: PASS
}
// Cleanup
aigLongestPathCollectionDestroy(collection);
aigLongestPathAnalysisDestroy(analysis);
mlirModuleDestroy(module);
mlirContextDestroy(ctx);
}
int main(void) {
testAIGDialectRegistration();
testLongestPathAnalysis();
testErrorHandling();
printf("=== All tests completed ===\n");
// CHECK: === All tests completed ===
return 0;
}

View File

@ -18,6 +18,7 @@ set(CIRCT_TEST_DEPENDS
FileCheck count not
split-file
arcilator
circt-capi-aig-test
circt-capi-ir-test
circt-capi-om-test
circt-capi-firrtl-test

View File

@ -58,12 +58,12 @@ tool_dirs = [
config.circt_tools_dir, config.mlir_tools_dir, config.llvm_tools_dir
]
tools = [
'arcilator', 'circt-as', 'circt-capi-ir-test', 'circt-capi-om-test',
'circt-capi-firrtl-test', 'circt-capi-firtool-test',
'circt-capi-rtg-pipelines-test', 'circt-capi-rtg-test',
'circt-capi-rtgtest-test', 'circt-dis', 'circt-lec', 'circt-reduce',
'circt-synth', 'circt-test', 'circt-translate', 'firld', 'firtool',
'hlstool', 'om-linker', 'kanagawatool'
'arcilator', 'circt-as', 'circt-bmc', 'circt-capi-aig-test',
'circt-capi-ir-test', 'circt-capi-om-test', 'circt-capi-firrtl-test',
'circt-capi-firtool-test', 'circt-capi-rtg-pipelines-test',
'circt-capi-rtg-test', 'circt-capi-rtgtest-test', 'circt-dis', 'circt-lec',
'circt-reduce', 'circt-synth', 'circt-test', 'circt-translate', 'firld',
'firtool', 'hlstool', 'om-linker', 'kanagawatool'
]
if "CIRCT_OPT_CHECK_IR_ROUNDTRIP" in os.environ: