diff --git a/tools/hls-fuzzer/AST.h b/tools/hls-fuzzer/AST.h index 85630e4d19..ac3329d12a 100644 --- a/tools/hls-fuzzer/AST.h +++ b/tools/hls-fuzzer/AST.h @@ -298,6 +298,9 @@ class Expression { /// Returns the type of the given expression. ScalarType getType() const; + template + friend struct llvm::simplify_type; + private: std::shared_ptr expression; }; @@ -883,4 +886,14 @@ struct llvm::simplify_type { } }; +template <> +struct llvm::simplify_type { + using SimpleType = const dynamatic::ast::Expression::Variant; + + static SimpleType & + getSimplifiedValue(const dynamatic::ast::Expression &expression) { + return *expression.expression; + } +}; + #endif diff --git a/tools/hls-fuzzer/BasicCGenerator.cpp b/tools/hls-fuzzer/BasicCGenerator.cpp index 069faaf910..21f6bb6afe 100644 --- a/tools/hls-fuzzer/BasicCGenerator.cpp +++ b/tools/hls-fuzzer/BasicCGenerator.cpp @@ -534,11 +534,7 @@ gen::BasicCGenerator::generateArrayAssignmentStatement( }, /*index=*/ [&](OpaqueContext &&context) { - auto [expression, outputContext] = - generateExpression(std::move(context)); - expression = safeCastAsNeeded( - /*to=*/ast::PrimitiveType::UInt32, std::move(expression)); - return std::pair(std::move(expression), std::move(outputContext)); + return generateExpression(std::move(context)); }, /*value=*/ [&](OpaqueContext &&context) { @@ -547,6 +543,8 @@ gen::BasicCGenerator::generateArrayAssignmentStatement( /*constructor=*/ [&](ast::ArrayParameter &¶m, ast::Expression &&index, ast::Expression &&value) { + index = safeCastAsNeeded( + /*to=*/ast::PrimitiveType::UInt32, std::move(index)); index = ast::BinaryExpression{std::move(index), ast::BinaryExpression::BitAnd, ast::Constant{static_cast( diff --git a/tools/hls-fuzzer/CMakeLists.txt b/tools/hls-fuzzer/CMakeLists.txt index 9f29141bb9..f22078d544 100644 --- a/tools/hls-fuzzer/CMakeLists.txt +++ b/tools/hls-fuzzer/CMakeLists.txt @@ -30,6 +30,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/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/DynamaticTypeSystem.h b/tools/hls-fuzzer/targets/DynamaticTypeSystem.h index 8f078f271b..3be92b92ef 100644 --- a/tools/hls-fuzzer/targets/DynamaticTypeSystem.h +++ b/tools/hls-fuzzer/targets/DynamaticTypeSystem.h @@ -15,7 +15,7 @@ struct DynamaticTypingContext { /// Expression must be of an integer type. IntegerRequired, MAX_VALUE = IntegerRequired, - } constraint; + } constraint = Unconstrained; }; /// Custom type system that avoids expressions that dynamatic is known not to @@ -27,8 +27,6 @@ struct DynamaticTypingContext { class DynamaticTypeSystem final : public TypeSystem { public: - explicit DynamaticTypeSystem(Randomly &) {} - TransferFnArray getFunctionTransferFns() override { return { /*return type=*/copyFromInput(), diff --git a/tools/hls-fuzzer/targets/LSQNoDepTypeSystem.cpp b/tools/hls-fuzzer/targets/LSQNoDepTypeSystem.cpp new file mode 100644 index 0000000000..6301a5f428 --- /dev/null +++ b/tools/hls-fuzzer/targets/LSQNoDepTypeSystem.cpp @@ -0,0 +1,34 @@ +#include "LSQNoDepTypeSystem.h" + +dynamatic::gen::TransferFnArray dynamatic:: + gen::detail::LSQNoDepTypeSystemInner::getArrayReadExpressionTransferFns() { + return { + copyFromInput(), + TransferFn( + [](LSQNoDepContext context) { + context.inArrayReadIndexExpression = true; + return context; + }), + copyInputToOutput(), + }; +} + +dynamatic::gen::TransferFnArray +dynamatic::gen::detail::LSQNoDepTypeSystemInner:: + getArrayAssignmentStatementTransferFns() { + return { + copyFromInput(), + copyFromInput(), + TransferFn( + [](const LSQNoDepContext &, const ast::ArrayParameter ¶meter, + const LSQNoDepContext &, const ast::Expression &index) { + return LSQNoDepContext{¶meter, + // Successful cast guaranteed by every other + // kind of expression being discarded. + &llvm::cast(index)}; + }), + copyInputToOutput(), + }; +} diff --git a/tools/hls-fuzzer/targets/LSQNoDepTypeSystem.h b/tools/hls-fuzzer/targets/LSQNoDepTypeSystem.h new file mode 100644 index 0000000000..cbdbc94647 --- /dev/null +++ b/tools/hls-fuzzer/targets/LSQNoDepTypeSystem.h @@ -0,0 +1,131 @@ +#ifndef DYNAMATIC_HLS_FUZZER_TARGETS_LSQNODEPTYPESYSTEM +#define DYNAMATIC_HLS_FUZZER_TARGETS_LSQNODEPTYPESYSTEM + +#include "DynamaticTypeSystem.h" +#include "hls-fuzzer/ConjunctionTypeSystem.h" +#include "hls-fuzzer/LimitTypeSystem.h" +#include "hls-fuzzer/TypeSystem.h" + +namespace dynamatic::gen { + +struct LSQNoDepContext { + /// The array parameter + index variable that is being written to in the + /// current statement, or nullptr if not. + const ast::ArrayParameter *arrayWritten = nullptr; + const ast::Variable *indexVariable = nullptr; + /// True iff the context is within the sub-tree of the indexing expression of + /// an array read expression. + bool inArrayReadIndexExpression = false; +}; + +namespace detail { + +/// Subtype system used for the LSQ related logic. +class LSQNoDepTypeSystemInner final + : public TypeSystem { +public: + static bool discardReturnType(const ast::ReturnType &returnType, + const LSQNoDepContext &) { + // Force a void return function. + return returnType != ast::ReturnType{ast::VoidType{}}; + } + + static bool discardArrayReadExpression(const LSQNoDepContext &context) { + return !context.arrayWritten || !context.indexVariable || + context.inArrayReadIndexExpression; + } + + TransferFnArray + getArrayReadExpressionTransferFns() override; + + TransferFnArray + getArrayAssignmentStatementTransferFns() override; + + static bool discardStructuredForStatement(const LSQNoDepContext &) { + // Only array assignment statements should be allowed. + return true; + } + + static bool discardBinaryExpression(ast::BinaryExpression::Op, + const LSQNoDepContext &context) { + return context.inArrayReadIndexExpression || !context.indexVariable; + } + + static bool discardUnaryExpression(ast::UnaryExpression::Op, + const LSQNoDepContext &context) { + return context.inArrayReadIndexExpression || !context.indexVariable; + } + + static bool + discardExistingScalarParameter(const ast::ScalarParameter ¶meter, + const LSQNoDepContext &context) { + // In an indexing expression, the scalar parameter name must match the index + // variable. + if (!context.inArrayReadIndexExpression) + return false; + + return parameter.getName() != context.indexVariable->name; + } + + static bool discardFreshScalarParameter(const LSQNoDepContext &context) { + return context.inArrayReadIndexExpression; + } + + static bool + discardExistingArrayParameter(const ast::ArrayParameter ¶meter, + const LSQNoDepContext &context) { + if (!context.arrayWritten) + return false; + + return parameter.getName() != context.arrayWritten->getName(); + } + + static bool discardFreshArrayParameter(const LSQNoDepContext &context) { + return context.arrayWritten; + } + + static bool discardCastExpression(const LSQNoDepContext &context) { + return context.inArrayReadIndexExpression || !context.indexVariable; + } + + static bool discardConditionalExpression(const LSQNoDepContext &context) { + return context.inArrayReadIndexExpression || !context.indexVariable; + } + + std::optional discardConstant(const ast::Constant &constant, + const LSQNoDepContext &context) { + if (context.inArrayReadIndexExpression || !context.indexVariable) + return std::nullopt; + + return TypeSystem::discardConstant(constant, context); + } +}; + +} // 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 ConjunctionTypeSystemBase { +public: + explicit LSQNoDepTypeSystem() + : ConjunctionTypeSystemBase(detail::LSQNoDepTypeSystemInner(), + DynamaticTypeSystem(), + LimitTypeSystem(/*maxExpressionDepth=*/8, + /*maxTotalStatements=*/1)) {} +}; + +} // 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..102865ba44 --- /dev/null +++ b/tools/hls-fuzzer/targets/LSQOptimizationsTarget.cpp @@ -0,0 +1,53 @@ +#include "LSQOptimizationsTarget.h" + +#include "LSQNoDepTypeSystem.h" +#include "TargetUtils.h" +#include "hls-fuzzer/BasicCGenerator.h" +#include "hls-fuzzer/TargetRegistry.h" + +namespace { +REGISTER_TARGET("lsq-elision", 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; + gen::BasicCGenerator generator(random, typeSystem); + generator.generate(os, functionName); +} + +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 diff --git a/tools/hls-fuzzer/targets/RandomCTarget.cpp b/tools/hls-fuzzer/targets/RandomCTarget.cpp index 5c8c43f7c2..0f3a6b3ca0 100644 --- a/tools/hls-fuzzer/targets/RandomCTarget.cpp +++ b/tools/hls-fuzzer/targets/RandomCTarget.cpp @@ -33,15 +33,8 @@ RandomCTarget::createWorker(const Options &options, Randomly randomly) const { void RandomCWorker::generate(llvm::raw_ostream &os, llvm::StringRef functionName) { gen::ConjunctionTypeSystem - dynamaticTypeSystem{gen::DynamaticTypeSystem(random), - gen::LimitTypeSystem()}; - gen::BasicCGenerator generator( - random, dynamaticTypeSystem, - /*entryContext=*/ - { - {gen::DynamaticTypingContext::Unconstrained}, - {}, - }); + dynamaticTypeSystem{gen::DynamaticTypeSystem(), gen::LimitTypeSystem()}; + gen::BasicCGenerator generator(random, dynamaticTypeSystem); generator.generate(os, functionName); }