From 4e0aa877ab8d40662199e4941dbc5da524753e49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20B=C3=B6ck?= Date: Thu, 9 Apr 2026 13:56:18 +0200 Subject: [PATCH] [hls-fuzzer] Add simplest non-functional LSQ fuzzer This PR adds an initial fuzzer for testing LSQ elision in Dynamatic. It uses a type system that is specifically designed to not require any memory ordering. The logic currently used is very simple: Only a single Write-after-Read statement is generated and the write operation is either data- or control dependent on the read operation. Support for more complicated constructs will be added in future PRs. For now this fuzzer already finds suboptimal code in milliseconds. --- tools/hls-fuzzer/AST.h | 10 +- tools/hls-fuzzer/BasicCGenerator.cpp | 47 +--- tools/hls-fuzzer/CMakeLists.txt | 2 + tools/hls-fuzzer/ConjunctionTypeSystem.h | 216 ++++++++++++++++++ tools/hls-fuzzer/TypeSystem.cpp | 2 + tools/hls-fuzzer/TypeSystem.h | 127 +++++++++- tools/hls-fuzzer/oracles/CMakeLists.txt | 10 + tools/hls-fuzzer/oracles/check-no-lsq.cpp | 44 ++++ .../hls-fuzzer/targets/LSQNoDepTypeSystem.cpp | 63 +++++ tools/hls-fuzzer/targets/LSQNoDepTypeSystem.h | 82 +++++++ .../targets/LSQOptimizationsTarget.cpp | 67 ++++++ .../targets/LSQOptimizationsTarget.h | 16 ++ 12 files changed, 640 insertions(+), 46 deletions(-) create mode 100644 tools/hls-fuzzer/ConjunctionTypeSystem.h create mode 100644 tools/hls-fuzzer/oracles/check-no-lsq.cpp create mode 100644 tools/hls-fuzzer/targets/LSQNoDepTypeSystem.cpp create mode 100644 tools/hls-fuzzer/targets/LSQNoDepTypeSystem.h create mode 100644 tools/hls-fuzzer/targets/LSQOptimizationsTarget.cpp create mode 100644 tools/hls-fuzzer/targets/LSQOptimizationsTarget.h diff --git a/tools/hls-fuzzer/AST.h b/tools/hls-fuzzer/AST.h index b4c3636fd6..1940efc7e8 100644 --- a/tools/hls-fuzzer/AST.h +++ b/tools/hls-fuzzer/AST.h @@ -543,7 +543,15 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &os, const ArrayParameter ¶meter); /// Tag type representing the 'void' type from C. -struct VoidType {}; +struct VoidType { + friend bool operator==(const VoidType &lhs, const VoidType &rhs) { + return true; + } + + friend bool operator!=(const VoidType &lhs, const VoidType &rhs) { + return !(lhs == rhs); + } +}; inline llvm::raw_ostream &operator<<(llvm::raw_ostream &os, const VoidType &) { return os << "void"; diff --git a/tools/hls-fuzzer/BasicCGenerator.cpp b/tools/hls-fuzzer/BasicCGenerator.cpp index 74a742b789..344b60c59f 100644 --- a/tools/hls-fuzzer/BasicCGenerator.cpp +++ b/tools/hls-fuzzer/BasicCGenerator.cpp @@ -377,25 +377,11 @@ gen::BasicCGenerator::generateReturnType(const OpaqueContext &context) const { "It must always be possible to generate a return type"); } -constexpr std::size_t MAX_STATEMENTS = 10; - std::vector gen::BasicCGenerator::generateStatementList(const OpaqueContext &context) { - std::vector result; - // TODO: Type systems should have better control over the number of - // statements and in what order they are generated. - // Right now they are always generated last statement to first. - std::size_t numStatements = random.getInteger(0, MAX_STATEMENTS); - result.reserve(numStatements); - for (std::size_t i = 0; i < numStatements; i++) { - std::optional maybeStat = generateStatement(context); - if (!maybeStat) - break; - - result.push_back(std::move(*maybeStat)); - } - std::reverse(result.begin(), result.end()); - return result; + return typeSystem.generateStatementListOpaque( + context, + [=](const OpaqueContext &context) { return generateStatement(context); }); } std::optional @@ -406,25 +392,14 @@ gen::BasicCGenerator::generateStatement(const OpaqueContext &context) { std::optional gen::BasicCGenerator::generateArrayAssignmentStatement( const OpaqueContext &context) { - auto conclusion = typeSystem.checkArrayAssignmentStatementOpaque(context); - if (!conclusion) - return std::nullopt; - - auto &&[param, index, value] = *conclusion; - std::optional parameter = generateArrayParameter(param); - if (!parameter) - return std::nullopt; - - ast::Expression castAsNeeded = safeCastAsNeeded(ast::PrimitiveType::UInt32, - generateExpression(index, 0)); - castAsNeeded = ast::BinaryExpression{ - std::move(castAsNeeded), ast::BinaryExpression::BitAnd, - ast::Constant{static_cast(parameter->getDimension() - 1)}}; - return ast::ArrayAssignmentStatement{ - parameter->getName().str(), - castAsNeeded, - generateExpression(value, 0), - }; + return typeSystem.generateArrayAssignmentStatementOpaque( + context, + [=](const OpaqueContext &context) { + return generateArrayParameter(context); + }, + [=](const OpaqueContext &context) { + return generateExpression(context, 0); + }); } ast::Function gen::BasicCGenerator::generate(std::string_view functionName) { diff --git a/tools/hls-fuzzer/CMakeLists.txt b/tools/hls-fuzzer/CMakeLists.txt index 06f3acdf94..e4a299e1d9 100644 --- a/tools/hls-fuzzer/CMakeLists.txt +++ b/tools/hls-fuzzer/CMakeLists.txt @@ -29,6 +29,8 @@ add_llvm_executable(hls-fuzzer targets/BitwidthOptimizationsTarget.cpp targets/BitwidthTypeSystem.cpp targets/DynamaticTypeSystem.cpp + targets/LSQNoDepTypeSystem.cpp + targets/LSQOptimizationsTarget.cpp targets/RandomCTarget.cpp targets/TargetUtils.cpp ) diff --git a/tools/hls-fuzzer/ConjunctionTypeSystem.h b/tools/hls-fuzzer/ConjunctionTypeSystem.h new file mode 100644 index 0000000000..d37e5e6e44 --- /dev/null +++ b/tools/hls-fuzzer/ConjunctionTypeSystem.h @@ -0,0 +1,216 @@ +#ifndef HLS_FUZZER_CONJUNCTION_TYPE_SYSTEM +#define HLS_FUZZER_CONJUNCTION_TYPE_SYSTEM + +#include "TypeSystem.h" +#include + +namespace dynamatic::gen { + +/// Base class for creating type systems by combining multiple independent type +/// systems. +/// The derived class should be specified as the 'Self' parameter while the +/// subtype systems should be specified as 'SubTypeSystem' parameter. +/// +/// The typing context of the type system is a tuple of all contexts of the +/// subtype systems. All 'check*' methods have a default implementation which +/// calls all 'check*' methods of all subtype systems. +/// If any of the methods return an empty optional, the AST node is discarded. +/// +/// Note that 'generate*' methods of subtype systems do *not* compose and are +/// completely ignored. +/// These have to be reimplemented in the class deriving from +/// 'ConjunctionTypeSystem'. +template +class ConjunctionTypeSystem + : public TypeSystem, Self> { +public: + using Base = TypeSystem, Self>; + using Context = typename Base::Context; + + explicit ConjunctionTypeSystem(Randomly &randomly) + : Base(randomly), typeSystems(SubTypeSystem(randomly)...) {} + + // Default implementations of all 'check*' methods follow. + + ConclusionOf checkFunction(const Context &context) { + // Functions cannot be discarded hence the optional will never be empty. + return *combineChecks( + [&](auto &&typeSystem, auto &&context) { + return typeSystem.checkFunction(context); + }, + context); + } + + std::optional> + checkBinaryExpression(ast::BinaryExpression::Op op, const Context &context) { + return combineChecks( + [&](auto &&typeSystem, auto &&context) { + return typeSystem.checkBinaryExpression(op, context); + }, + context); + } + + std::optional> + checkCastExpression(const Context &context) { + return combineChecks( + [&](auto &&typeSystem, auto &&context) { + return typeSystem.checkCastExpression(context); + }, + context); + } + + std::optional> + checkConditionalExpression(const Context &context) { + return combineChecks( + [&](auto &&typeSystem, auto &&context) { + return typeSystem.checkConditionalExpression(context); + }, + context); + } + + std::optional> + checkVariable(const Context &context) { + return combineChecks( + [&](auto &&typeSystem, auto &&context) { + return typeSystem.checkVariable(context); + }, + context); + } + + std::optional> + checkScalarParameter(const ast::ScalarParameter &scalarParameter, + const Context &context) { + return combineChecks( + [&](auto &&typeSystem, auto &&context) { + return typeSystem.checkScalarParameter(scalarParameter, context); + }, + context); + } + + std::optional> + checkArrayParameter(const ast::ArrayParameter &arrayParameter, + const Context &context) { + return combineChecks( + [&](auto &&typeSystem, auto &&context) { + return typeSystem.checkArrayParameter(arrayParameter, context); + }, + context); + } + + std::optional> + checkScalarType(const ast::ScalarType &scalarType, const Context &context) { + return combineChecks( + [&](auto &&typeSystem, auto &&context) { + return typeSystem.checkScalarType(scalarType, context); + }, + context); + } + + std::optional> + checkReturnType(const ast::ReturnType &returnType, const Context &context) { + return combineChecks( + [&](auto &&typeSystem, auto &&context) { + return typeSystem.checkReturnType(returnType, context); + }, + context); + } + + std::optional> + checkArrayReadExpression(const Context &context) { + return combineChecks( + [&](auto &&typeSystem, auto &&context) { + return typeSystem.checkArrayReadExpression(context); + }, + context); + } + + std::optional> + checkArrayAssignmentStatement(const Context &context) { + return combineChecks( + [&](auto &&typeSystem, auto &&context) { + return typeSystem.checkArrayAssignmentStatement(context); + }, + context); + } + +protected: + /// Convenience template method which combines the result of calling + /// 'checkCallback' with a specific subtype system and corresponding context + /// with 'context' as input context. + /// 'checkCallback' should be generic and allow any type as the first + /// typesystem parameter and any context as the second parameter. + /// 'ASTNode' is the node being type checked and whose conclusion type should + /// be returned. + /// + /// Returns an empty optional if any of the subtype systems returns an empty + /// optional. + template + std::optional> + combineChecks(F &&checkCallback, const Context &context) { + // Call the check method on every type system. + // Effectively does a 'zip' operation on both the 'typeSystems' tuple and + // the 'context' tuple. + auto subChecks = std::apply( + [&](auto... indices) { + return std::make_tuple(std::optional{ + checkCallback(std::get(typeSystems), + std::get(context))}...); + }, + getIndicesTuple()); + + // Check whether any of them are empty. + if (std::apply( + [](auto &&...optionals) { + return (false || ... || !optionals.has_value()); + }, + subChecks)) + return std::nullopt; + + if constexpr (std::is_same_v, Context>) { + // Conclusion type is just 'Context'. Construct it by dereferencing the + // optionals. + return std::apply( + [&](auto &&...subChecks) { + return std::make_tuple(std::move(*subChecks)...); + }, + std::move(subChecks)); + } else { + // Conclusion type is a tuple of 'Context's. + // We need to combine all the sub-conclusion types by creating tuples + // (Context's) for every element of the conclusion type. + return std::apply( + [&](auto &&...subChecks) { + return std::apply( + [&](auto... indices) { + return ConclusionOf{[&](auto index) { + // For the element at the given index in the conclusion + // type, create a Context from all the sub-conclusion types. + return std::make_tuple( + get(std::move(*subChecks))...); + }(indices)...}; + }, + // Indices for every element of the conclusion type. + getIndicesTuple( + std::make_index_sequence< + std::tuple_size_v>>{})); + }, + std::move(subChecks)); + } + } + + std::tuple typeSystems; + +private: + template + constexpr auto getIndicesTuple(std::index_sequence) { + return std::tuple{std::integral_constant{}...}; + } + + constexpr auto getIndicesTuple() { + return getIndicesTuple(std::index_sequence_for{}); + } +}; + +} // namespace dynamatic::gen + +#endif diff --git a/tools/hls-fuzzer/TypeSystem.cpp b/tools/hls-fuzzer/TypeSystem.cpp index a47a6a5b5e..bf199ecf68 100644 --- a/tools/hls-fuzzer/TypeSystem.cpp +++ b/tools/hls-fuzzer/TypeSystem.cpp @@ -1,3 +1,5 @@ #include "TypeSystem.h" dynamatic::gen::AbstractTypeSystem::~AbstractTypeSystem() {} + +dynamatic::gen::NoopTypeSystem::~NoopTypeSystem() = default; diff --git a/tools/hls-fuzzer/TypeSystem.h b/tools/hls-fuzzer/TypeSystem.h index 714d6a68f0..7fcdb71bcb 100644 --- a/tools/hls-fuzzer/TypeSystem.h +++ b/tools/hls-fuzzer/TypeSystem.h @@ -116,6 +116,16 @@ class AbstractTypeSystem { virtual std::optional< ConclusionOf> checkArrayAssignmentStatementOpaque(const OpaqueContext &context) = 0; + + virtual std::optional + generateArrayAssignmentStatementOpaque( + const OpaqueContext &context, + GenerateCallback generateArrayParameter, + GenerateCallback generateExpression) = 0; + + virtual std::vector generateStatementListOpaque( + const OpaqueContext &context, + GenerateCallback generateStatement) = 0; }; /// CRTP-Base class for all implementations of a type system. @@ -199,6 +209,8 @@ class TypeSystem : public AbstractTypeSystem { public: explicit TypeSystem(Randomly &random) : random(random) {} + using Context = TypingContext; + /// The conclusion type of 'ASTNode' with the given context. template using ConclusionOf = ConclusionOf; @@ -254,7 +266,8 @@ class TypeSystem : public AbstractTypeSystem { }) .Case([&](const ast::ScalarType *scalar) -> std::optional> { - if (!self().checkScalarType(*scalar, context)) + if (std::optional optional = self().checkScalarType(*scalar, context); + !optional) return std::nullopt; return ConclusionOf{}; @@ -263,7 +276,9 @@ class TypeSystem : public AbstractTypeSystem { std::optional> checkConstant(const ast::Constant &constant, const TypingContext &context) { - if (!self().checkScalarType(constant.getType(), context)) + if (std::optional optional = + self().checkScalarType(constant.getType(), context); + !optional) return std::nullopt; return constant; @@ -272,7 +287,9 @@ class TypeSystem : public AbstractTypeSystem { std::optional> checkScalarParameter(const ast::ScalarParameter ¶meter, const TypingContext &context) { - if (!self().checkScalarType(parameter.getDataType(), context)) + if (std::optional optional = + self().checkScalarType(parameter.getDataType(), context); + !optional) return std::nullopt; return context; @@ -298,7 +315,9 @@ class TypeSystem : public AbstractTypeSystem { std::optional> checkExistingArrayParameter(const ast::ArrayParameter ¶meter, const TypingContext &context) { - if (!self().checkScalarType(parameter.getElementType(), context)) + if (std::optional optional = + self().checkScalarType(parameter.getElementType(), context); + !optional) return std::nullopt; return ConclusionOf{}; @@ -327,6 +346,18 @@ class TypeSystem : public AbstractTypeSystem { return {context, context, context}; } + std::optional generateArrayAssignmentStatement( + const TypingContext &context, + GenerateCallback + generateArrayParameter, + GenerateCallback generateExpression); + + /// Generate a list of statements from the given context. + /// Default implementation generates a random amount of statements. + std::vector generateStatementList( + const TypingContext &context, + GenerateCallback generateStatement); + // Implementations of the virtual methods in 'AbstractTypeSystem'. // These are automatically implemented to unbox the 'TypingContext's out of // the opaque contexts, calling the corresponding non-opaque 'check*' method @@ -424,6 +455,23 @@ class TypeSystem : public AbstractTypeSystem { self().checkArrayAssignmentStatement(context.cast())); } + std::optional + generateArrayAssignmentStatementOpaque( + const OpaqueContext &context, + GenerateCallback generateArrayParameter, + GenerateCallback generateExpression) final { + return self().generateArrayAssignmentStatement( + convert(context), convert(generateArrayParameter), + convert(generateExpression)); + } + + std::vector generateStatementListOpaque( + const OpaqueContext &context, + GenerateCallback generateStatement) final { + return convert(self().generateStatementList(convert(context), + convert(generateStatement))); + } + private: Self &self() { return static_cast(*this); } @@ -501,6 +549,8 @@ class TypeSystem : public AbstractTypeSystem { class NoopTypeSystem : public TypeSystem { public: using TypeSystem::TypeSystem; + + ~NoopTypeSystem() override; }; /// Convenience type system that disallows every AST constructs (besides @@ -508,7 +558,8 @@ class NoopTypeSystem : public TypeSystem { template class DisallowByDefaultTypeSystem : public TypeSystem { public: - using TypeSystem::TypeSystem; + using Base = TypeSystem; + using Base::Base; static std::optional> checkBinaryExpression(ast::BinaryExpression::Op, const TypingContext &) { @@ -561,10 +612,10 @@ class DisallowByDefaultTypeSystem : public TypeSystem { return std::nullopt; } - static std::optional< - ConclusionOf> - checkArrayAssignmentStatement(const TypingContext &) { - return std::nullopt; + static std::vector generateStatementList( + const TypingContext &, + typename Base::template GenerateCallback) { + return {}; } }; @@ -608,6 +659,64 @@ TypeSystem::generateArrayReadExpression( ast::Constant{static_cast(mask)}}}; } +template +std::optional +TypeSystem::generateArrayAssignmentStatement( + const TypingContext &context, + GenerateCallback generateArrayParameter, + GenerateCallback generateExpression) { + std::optional conclusion = self().checkArrayAssignmentStatement(context); + if (!conclusion) + return std::nullopt; + + auto &&[param, index, value] = *conclusion; + std::optional parameter = generateArrayParameter(param); + if (!parameter) + return std::nullopt; + + assert(llvm::isPowerOf2_64(parameter->getDimension()) && + "default implementation depends on dimensions being powers of 2"); + + std::optional maybeIndexExpression = + generateExpression(index); + if (!maybeIndexExpression) + return std::nullopt; + + std::optional valueExpression = generateExpression(value); + if (!valueExpression) + return std::nullopt; + + return ast::ArrayAssignmentStatement{ + parameter->getName().str(), + ast::BinaryExpression{std::move(*maybeIndexExpression), + ast::BinaryExpression::BitAnd, + ast::Constant{static_cast( + parameter->getDimension() - 1)}}, + std::move(*valueExpression), + }; +} + +template +std::vector +TypeSystem::generateStatementList( + const TypingContext &context, + GenerateCallback generateStatement) { + constexpr std::size_t MAX_STATEMENTS = 10; + + std::vector result; + std::size_t numStatements = random.getInteger(0, MAX_STATEMENTS); + result.reserve(numStatements); + for (std::size_t i = 0; i < numStatements; i++) { + std::optional maybeStat = generateStatement(context); + if (!maybeStat) + break; + + result.push_back(std::move(*maybeStat)); + } + std::reverse(result.begin(), result.end()); + return result; +} + } // namespace dynamatic::gen #endif diff --git a/tools/hls-fuzzer/oracles/CMakeLists.txt b/tools/hls-fuzzer/oracles/CMakeLists.txt index 33cfdb6f19..7b57f45014 100644 --- a/tools/hls-fuzzer/oracles/CMakeLists.txt +++ b/tools/hls-fuzzer/oracles/CMakeLists.txt @@ -1,7 +1,17 @@ add_llvm_executable(hls-fuzzer-check-bitwidth check-bitwidth.cpp + PARTIAL_SOURCES_INTENDED ) llvm_update_compile_flags(hls-fuzzer-check-bitwidth) target_link_libraries(hls-fuzzer-check-bitwidth PRIVATE DynamaticHandshake MLIRParser) add_dependencies(hls-fuzzer hls-fuzzer-check-bitwidth) + +add_llvm_executable(hls-fuzzer-check-no-lsq + check-no-lsq.cpp + PARTIAL_SOURCES_INTENDED +) +llvm_update_compile_flags(hls-fuzzer-check-no-lsq) +target_link_libraries(hls-fuzzer-check-no-lsq PRIVATE DynamaticHandshake MLIRParser) + +add_dependencies(hls-fuzzer hls-fuzzer-check-no-lsq) diff --git a/tools/hls-fuzzer/oracles/check-no-lsq.cpp b/tools/hls-fuzzer/oracles/check-no-lsq.cpp new file mode 100644 index 0000000000..caa4116a32 --- /dev/null +++ b/tools/hls-fuzzer/oracles/check-no-lsq.cpp @@ -0,0 +1,44 @@ + +#include "dynamatic/Dialect/Handshake/HandshakeDialect.h" +#include "mlir/Tools/ParseUtilities.h" + +#include "llvm/Support/SourceMgr.h" +#include "llvm/Support/raw_ostream.h" + +#include "dynamatic/Dialect/Handshake/HandshakeOps.h" + +using namespace mlir; +using namespace dynamatic; + +int main(int argc, char **argv) { + if (argc != 2) { + llvm::errs() << "expected exactly one argument\n"; + return -1; + } + + StringRef mlirFile = argv[1]; + llvm::ErrorOr> buffer = + llvm::MemoryBuffer::getFileOrSTDIN(mlirFile, true); + if (!buffer) { + llvm::errs() << "failed to open" << mlirFile << "\n"; + return -1; + } + + auto sourceMgr = std::make_shared(); + sourceMgr->AddNewSourceBuffer(std::move(*buffer), SMLoc()); + DialectRegistry registry; + registry.insert(); + MLIRContext context(registry); + ParserConfig config(&context); + OwningOpRef module = + parseSourceFileForTool(sourceMgr, config, true); + + WalkResult result = module->walk([&](handshake::LSQOp) { + llvm::errs() << "IR must not contain an LSQ\n"; + return WalkResult::interrupt(); + }); + if (result.wasInterrupted()) + return -1; + + return 0; +} diff --git a/tools/hls-fuzzer/targets/LSQNoDepTypeSystem.cpp b/tools/hls-fuzzer/targets/LSQNoDepTypeSystem.cpp new file mode 100644 index 0000000000..4594c510dc --- /dev/null +++ b/tools/hls-fuzzer/targets/LSQNoDepTypeSystem.cpp @@ -0,0 +1,63 @@ +#include "LSQNoDepTypeSystem.h" + +std::optional +dynamatic::gen::LSQNoDepTypeSystem::generateArrayReadExpression( + const Context &context, GenerateCallback, + GenerateCallback) { + auto &lsqNoDepContext = std::get(context); + if (!lsqNoDepContext.elementWritten) + return std::nullopt; + + // Make sure the type system allows us to even read from this array parameter. + if (!checkExistingArrayParameter(*lsqNoDepContext.elementWritten->first, + context)) + return std::nullopt; + + // We only generate array reads from the array being written to at the + // moment. + return ast::ArrayReadExpression{ + lsqNoDepContext.elementWritten->first->getElementType(), + lsqNoDepContext.elementWritten->first->getName().str(), + lsqNoDepContext.elementWritten->second}; +} + +std::optional +dynamatic::gen::LSQNoDepTypeSystem::generateArrayAssignmentStatement( + const Context &context, + GenerateCallback generateArrayParameter, + GenerateCallback generateExpression) { + auto conclusion = checkArrayAssignmentStatement(context); + if (!conclusion) + return std::nullopt; + auto &&[param, index, value] = std::move(*conclusion); + + std::optional parameter = generateArrayParameter(param); + if (!parameter) + return std::nullopt; + + assert(llvm::isPowerOf2_64(parameter->getDimension()) && + "default implementation depends on dimensions being powers of 2"); + + std::optional maybeIndexingExpression = + generateExpression(index); + if (!maybeIndexingExpression) + return std::nullopt; + + ast::BinaryExpression indexExpression{ + std::move(*maybeIndexingExpression), ast::BinaryExpression::BitAnd, + ast::Constant{static_cast(parameter->getDimension() - 1)}}; + + // Potentially generate a data-dependent RAW dependency by requiring + // array-read expressions to read from the array we're writing to. + auto &lsqNoDepContext = std::get(value); + lsqNoDepContext.elementWritten.emplace(&*parameter, indexExpression); + std::optional valueExpression = generateExpression(value); + if (!valueExpression) + return std::nullopt; + + return ast::ArrayAssignmentStatement{ + parameter->getName().str(), + indexExpression, + std::move(*valueExpression), + }; +} diff --git a/tools/hls-fuzzer/targets/LSQNoDepTypeSystem.h b/tools/hls-fuzzer/targets/LSQNoDepTypeSystem.h new file mode 100644 index 0000000000..fee6e662cf --- /dev/null +++ b/tools/hls-fuzzer/targets/LSQNoDepTypeSystem.h @@ -0,0 +1,82 @@ +#ifndef DYNAMATIC_HLS_FUZZER_TARGETS_LSQNODEPTYPESYSTEM +#define DYNAMATIC_HLS_FUZZER_TARGETS_LSQNODEPTYPESYSTEM + +#include "DynamaticTypeSystem.h" +#include "hls-fuzzer/ConjunctionTypeSystem.h" +#include "hls-fuzzer/TypeSystem.h" + +namespace dynamatic::gen { + +struct LSQNoDepContext { + /// The array parameter + index that is being written to in the current + /// statement, or empty if not. + /// Correctness relies on the fact that the indexing expression cannot have + /// side effects beyond reading array values. + std::optional> + elementWritten = std::nullopt; +}; + +namespace detail { + +/// Subtype system used in conjunction with the Dynamatic type system. +/// 'check*' methods should be implemented here for composability. +class LSQNoDepTypeSystemInner final + : public TypeSystem { +public: + using TypeSystem::TypeSystem; + + static std::optional> + checkReturnType(const ast::ReturnType &returnType, const LSQNoDepContext &) { + // Force a void return function. + if (returnType != ast::ReturnType{ast::VoidType{}}) + return std::nullopt; + + return ConclusionOf{}; + } +}; + +} // namespace detail + +/// Type system used to generate code that requires no LSQ to enforce ordering +/// constraints of memory. +/// Concretely this means that: +/// * For any Write-after-Read (WAR), the write operation must be data dependent +/// on the read, guaranteed not to alias OR have a control dependency on the +/// read. +/// * For any Write-after-Write (WAW) or Read-after-Write (RAW) the operations +/// must not alias. +/// +/// The current implementation only ever generates a single WAR construct where +/// the write is data dependent or control dependent on the read. +class LSQNoDepTypeSystem final + : public ConjunctionTypeSystem { +public: + using ConjunctionTypeSystem::ConjunctionTypeSystem; + + std::optional + generateArrayReadExpression(const Context &context, + GenerateCallback, + GenerateCallback); + + std::optional generateArrayAssignmentStatement( + const Context &context, + GenerateCallback generateArrayParameter, + GenerateCallback generateExpression); + + static std::vector generateStatementList( + const Context &context, + GenerateCallback generateStatement) { + // Generate exactly one statement for now. + // This makes it such that we do not have to reason between array-accesses + // in other statements. + std::optional statement = generateStatement(context); + assert(statement && "it must always be possible to generate a statement"); + return {std::move(*statement)}; + } +}; + +} // namespace dynamatic::gen + +#endif diff --git a/tools/hls-fuzzer/targets/LSQOptimizationsTarget.cpp b/tools/hls-fuzzer/targets/LSQOptimizationsTarget.cpp new file mode 100644 index 0000000000..e8512ec358 --- /dev/null +++ b/tools/hls-fuzzer/targets/LSQOptimizationsTarget.cpp @@ -0,0 +1,67 @@ +#include "LSQOptimizationsTarget.h" + +#include "LSQNoDepTypeSystem.h" +#include "TargetUtils.h" +#include "hls-fuzzer/BasicCGenerator.h" +#include "hls-fuzzer/TargetRegistry.h" + +namespace { +REGISTER_TARGET("lsq-optimizations", dynamatic::LSQOptimizationsTarget); +} // namespace + +using namespace dynamatic; + +namespace { +class LSQOptimizationsWorker : public AbstractWorker { +public: + explicit LSQOptimizationsWorker(const Options &options, Randomly &&random) + : AbstractWorker(options, std::move(random)) {} + + void generate(llvm::raw_ostream &os, llvm::StringRef functionName) override; + + VerificationResult + verify(const std::filesystem::path &sourceFile) const override; +}; + +} // namespace + +std::unique_ptr +LSQOptimizationsTarget::createWorker(const Options &options, + Randomly randomly) const { + return std::make_unique(options, std::move(randomly)); +} + +void LSQOptimizationsWorker::generate(llvm::raw_ostream &os, + llvm::StringRef functionName) { + gen::LSQNoDepTypeSystem typeSystem(random); + gen::BasicCGenerator generator( + random, typeSystem, + /*entryContext=*/ + {gen::LSQNoDepContext{}, + // Randomly decide + {random.fromEnum()}}); + + ast::Function function = generator.generate(functionName); + os << R"( +#include +#include +#include "dynamatic/Integration.h" + +)"; + os << function << '\n'; + os << generator.generateTestBench(function); +} + +constexpr std::string_view ORACLE_EXECUTABLE = "hls-fuzzer-check-no-lsq"; +constexpr std::string_view COMPILATION_IR_OUTPUT = + "./out/comp/handshake_export.mlir"; + +AbstractWorker::VerificationResult +LSQOptimizationsWorker::verify(const std::filesystem::path &sourceFile) const { + return performNonFunctionalTesting( + sourceFile, options.dynamaticExecutablePath, + (std::filesystem::path(options.executablePath).parent_path() / + ORACLE_EXECUTABLE) + .string(), + {COMPILATION_IR_OUTPUT}); +} diff --git a/tools/hls-fuzzer/targets/LSQOptimizationsTarget.h b/tools/hls-fuzzer/targets/LSQOptimizationsTarget.h new file mode 100644 index 0000000000..b563d953a9 --- /dev/null +++ b/tools/hls-fuzzer/targets/LSQOptimizationsTarget.h @@ -0,0 +1,16 @@ +#ifndef DYNAMATIC_HLS_FUZZER_TARGETS_LSQOPTIMIZATIONSTARGET +#define DYNAMATIC_HLS_FUZZER_TARGETS_LSQOPTIMIZATIONSTARGET + +#include "hls-fuzzer/AbstractTarget.h" + +namespace dynamatic { + +class LSQOptimizationsTarget : public AbstractTarget { +public: + std::unique_ptr + createWorker(const Options &options, Randomly randomly) const override; +}; + +} // namespace dynamatic + +#endif