diff --git a/tools/hls-fuzzer/BasicCGenerator.cpp b/tools/hls-fuzzer/BasicCGenerator.cpp index 37b06e155..069faaf91 100644 --- a/tools/hls-fuzzer/BasicCGenerator.cpp +++ b/tools/hls-fuzzer/BasicCGenerator.cpp @@ -52,12 +52,13 @@ static ast::Expression safeCastAsNeeded(const ast::ScalarType &to, } std::pair -gen::BasicCGenerator::generateReturnStatement(const OpaqueContext &context) { +gen::BasicCGenerator::generateReturnStatement(OpaqueContext &&context) { return *generateWithDependencies( - context, typeSystem.getReturnStatementTransferFns(), + std::move(context), typeSystem.getReturnStatementTransferFns(), /*return value=*/ - [&](const OpaqueContext &context) { - auto [expression, outputContext] = generateExpression(context); + [&](OpaqueContext &&context) { + auto [expression, outputContext] = + generateExpression(std::move(context)); if (maybeReturnType && llvm::isa(*maybeReturnType)) expression = safeCastAsNeeded(llvm::cast(*maybeReturnType), @@ -71,7 +72,7 @@ gen::BasicCGenerator::generateReturnStatement(const OpaqueContext &context) { } std::pair -gen::BasicCGenerator::generateExpression(const OpaqueContext &context) { +gen::BasicCGenerator::generateExpression(OpaqueContext &&context) { llvm::SmallVector> constructors = random.shuffle( expressionGenerators, typeSystem.getExpressionProbabilityTableOpaque(context), @@ -85,7 +86,7 @@ gen::BasicCGenerator::generateExpression(const OpaqueContext &context) { // Continuously generate an expression until one passes the type checker. for (Constructor &con : constructors) if (std::optional> result = - con(this, context)) + con(this, std::move(context))) return std::move(*result); llvm_unreachable("it should always be possible to generate an expression"); @@ -93,16 +94,20 @@ gen::BasicCGenerator::generateExpression(const OpaqueContext &context) { std::optional> gen::BasicCGenerator::generateBinaryExpression(ast::BinaryExpression::Op op, - const OpaqueContext &context) { + OpaqueContext &&context) { if (typeSystem.discardBinaryExpressionOpaque(op, context)) return std::nullopt; return generateWithDependencies( - context, typeSystem.getBinaryExpressionTransferFns(op), + std::move(context), typeSystem.getBinaryExpressionTransferFns(op), /*lhs=*/ - [&](const OpaqueContext &context) { return generateExpression(context); }, + [&](OpaqueContext &&context) { + return generateExpression(std::move(context)); + }, /*rhs=*/ - [&](const OpaqueContext &context) { return generateExpression(context); }, + [&](OpaqueContext &&context) { + return generateExpression(std::move(context)); + }, /*constructor=*/ [&](ast::Expression &&lhs, ast::Expression &&rhs) -> std::optional { @@ -182,14 +187,16 @@ gen::BasicCGenerator::generateBinaryExpression(ast::BinaryExpression::Op op, std::optional> gen::BasicCGenerator::generateUnaryExpression(ast::UnaryExpression::Op op, - const OpaqueContext &context) { + OpaqueContext &&context) { if (typeSystem.discardUnaryExpressionOpaque(op, context)) return std::nullopt; return generateWithDependencies( - context, typeSystem.getUnaryExpressionTransferFns(op), + std::move(context), typeSystem.getUnaryExpressionTransferFns(op), /*operand=*/ - [&](const OpaqueContext &context) { return generateExpression(context); }, + [&](OpaqueContext &&context) { + return generateExpression(std::move(context)); + }, /*constructor=*/ [&](ast::Expression &&operand) -> std::optional { return ast::UnaryExpression{op, std::move(operand)}; @@ -197,19 +204,24 @@ gen::BasicCGenerator::generateUnaryExpression(ast::UnaryExpression::Op op, } std::optional> -gen::BasicCGenerator::generateConditionalExpression( - const OpaqueContext &context) { +gen::BasicCGenerator::generateConditionalExpression(OpaqueContext &&context) { if (typeSystem.discardConditionalExpressionOpaque(context)) return std::nullopt; return generateWithDependencies( - context, typeSystem.getConditionalExpressionTransferFns(), + std::move(context), typeSystem.getConditionalExpressionTransferFns(), /*condition=*/ - [&](const OpaqueContext &context) { return generateExpression(context); }, + [&](OpaqueContext &&context) { + return generateExpression(std::move(context)); + }, /*true value=*/ - [&](const OpaqueContext &context) { return generateExpression(context); }, + [&](OpaqueContext &&context) { + return generateExpression(std::move(context)); + }, /*false value=*/ - [&](const OpaqueContext &context) { return generateExpression(context); }, + [&](OpaqueContext &&context) { + return generateExpression(std::move(context)); + }, /*constructor=*/ [&](ast::Expression &&cond, ast::Expression &&trueExpr, ast::Expression &&falseExpr) { @@ -219,16 +231,20 @@ gen::BasicCGenerator::generateConditionalExpression( } std::optional> -gen::BasicCGenerator::generateCastExpression(const OpaqueContext &context) { +gen::BasicCGenerator::generateCastExpression(OpaqueContext &&context) { if (typeSystem.discardCastExpressionOpaque(context)) return std::nullopt; return generateWithDependencies( - context, typeSystem.getCastExpressionTransferFns(), + std::move(context), typeSystem.getCastExpressionTransferFns(), /*data type=*/ - [&](const OpaqueContext &context) { return generateScalarType(context); }, + [&](OpaqueContext &&context) { + return generateScalarType(std::move(context)); + }, /*operand=*/ - [&](const OpaqueContext &context) { return generateExpression(context); }, + [&](OpaqueContext &&context) { + return generateExpression(std::move(context)); + }, /*constructor=*/ [](ast::ScalarType &&datatype, ast::Expression &&expression) { return ast::CastExpression{std::move(datatype), std::move(expression)}; @@ -268,7 +284,7 @@ ast::Constant gen::BasicCGenerator::getConstantForType( } std::optional> -gen::BasicCGenerator::generateConstant(const OpaqueContext &context) const { +gen::BasicCGenerator::generateConstant(OpaqueContext &&context) const { auto candidates = ast::PrimitiveType::ALL_PRIMITIVES; random.shuffle(candidates); @@ -276,25 +292,26 @@ gen::BasicCGenerator::generateConstant(const OpaqueContext &context) const { if (std::optional constant = typeSystem.discardConstantOpaque(getConstantForType(iter), context)) return generateWithDependencies( - context, typeSystem.getConstantTransferFns(), *constant); + std::move(context), typeSystem.getConstantTransferFns(), *constant); return std::nullopt; } std::optional> -gen::BasicCGenerator::generateArrayReadExpression( - const OpaqueContext &context) { +gen::BasicCGenerator::generateArrayReadExpression(OpaqueContext &&context) { if (typeSystem.discardArrayReadExpressionOpaque(context)) return std::nullopt; return generateWithDependencies( - context, typeSystem.getArrayReadExpressionTransferFns(), + std::move(context), typeSystem.getArrayReadExpressionTransferFns(), /*array parameter=*/ - [&](const OpaqueContext &context) { - return generateArrayParameter(context); + [&](OpaqueContext &&context) { + return generateArrayParameter(std::move(context)); }, /*index=*/ - [&](const OpaqueContext &context) { return generateExpression(context); }, + [&](OpaqueContext &&context) { + return generateExpression(std::move(context)); + }, /*constructor=*/ [&](ast::ArrayParameter &¶m, ast::Expression &&expression) { ast::ScalarType elementType = param.getElementType(); @@ -322,7 +339,7 @@ gen::BasicCGenerator::generateArrayReadExpression( } std::optional> -gen::BasicCGenerator::generateArrayParameter(const OpaqueContext &context) { +gen::BasicCGenerator::generateArrayParameter(OpaqueContext &&context) { // With a low chance, skip picking an existing parameter and try to generate // a new one. if (!random.getRatherLowProbabilityBool()) { @@ -335,17 +352,19 @@ gen::BasicCGenerator::generateArrayParameter(const OpaqueContext &context) { if (!typeSystem.discardExistingArrayParameterOpaque(candidateParam, context)) return generateWithDependencies( - context, typeSystem.getExistingArrayParameterTransferFns(), - candidateParam); + std::move(context), + typeSystem.getExistingArrayParameterTransferFns(), candidateParam); } if (typeSystem.discardFreshArrayParameterOpaque(context)) return std::nullopt; return generateWithDependencies( - context, typeSystem.getFreshArrayParameterTransferFns(), + std::move(context), typeSystem.getFreshArrayParameterTransferFns(), /*element type=*/ - [&](const OpaqueContext &context) { return generateScalarType(context); }, + [&](OpaqueContext &&context) { + return generateScalarType(std::move(context)); + }, /*constructor=*/ [&](ast::ScalarType &&elementType) { return arrayParameters.emplace_back( @@ -358,14 +377,14 @@ gen::BasicCGenerator::generateArrayParameter(const OpaqueContext &context) { } std::optional> -gen::BasicCGenerator::generateScalarParameter(const OpaqueContext &context) { +gen::BasicCGenerator::generateScalarParameter(OpaqueContext &&context) { if (typeSystem.discardVariableOpaque(context)) return std::nullopt; return generateWithDependencies( - context, typeSystem.getVariableTransferFns(), + std::move(context), typeSystem.getVariableTransferFns(), /*parameter=*/ - [&](const OpaqueContext &context) + [&](OpaqueContext &&context) -> std::optional> { std::array>()>, @@ -383,8 +402,8 @@ gen::BasicCGenerator::generateScalarParameter(const OpaqueContext &context) { for (const ast::ScalarParameter &iter : copy) if (!typeSystem.discardExistingScalarParameterOpaque(iter, context)) return generateWithDependencies( - context, typeSystem.getExistingScalarParameterTransferFns(), - iter); + std::move(context), + typeSystem.getExistingScalarParameterTransferFns(), iter); return std::nullopt; }; @@ -394,10 +413,11 @@ gen::BasicCGenerator::generateScalarParameter(const OpaqueContext &context) { return std::nullopt; return generateWithDependencies( - context, typeSystem.getFreshScalarParameterTransferFns(), + std::move(context), + typeSystem.getFreshScalarParameterTransferFns(), /*datatype=*/ - [&](const OpaqueContext &context) { - return generateScalarType(context); + [&](OpaqueContext &&context) { + return generateScalarType(std::move(context)); }, /*constructor=*/ [&](ast::ScalarType &&datatype) { @@ -425,7 +445,7 @@ gen::BasicCGenerator::generateScalarParameter(const OpaqueContext &context) { std::optional> gen::BasicCGenerator::generateScalarType( - const OpaqueContext &context, + OpaqueContext &&context, llvm::function_ref toExclude) const { auto candidates = ast::PrimitiveType::ALL_PRIMITIVES; random.shuffle(candidates); @@ -436,14 +456,14 @@ gen::BasicCGenerator::generateScalarType( if (!typeSystem.discardScalarTypeOpaque(iter, context)) return generateWithDependencies( - context, typeSystem.getScalarTypeTransferFns(), iter); + std::move(context), typeSystem.getScalarTypeTransferFns(), iter); } return std::nullopt; } std::pair -gen::BasicCGenerator::generateReturnType(const OpaqueContext &context) const { +gen::BasicCGenerator::generateReturnType(OpaqueContext &&context) const { // Candidates for return types are all primitive types as well as 'void'. // (i.e., one more than the number of primitive types). std::array @@ -454,26 +474,26 @@ gen::BasicCGenerator::generateReturnType(const OpaqueContext &context) const { for (const ast::ReturnType &iter : candidates) if (!typeSystem.discardReturnTypeOpaque(iter, context)) return generateWithDependencies( - context, typeSystem.getReturnTypeTransferFns(), iter); + std::move(context), typeSystem.getReturnTypeTransferFns(), iter); llvm::report_fatal_error( "It must always be possible to generate a return type"); } std::pair -gen::BasicCGenerator::generateStatementList(const OpaqueContext &context) { +gen::BasicCGenerator::generateStatementList(OpaqueContext &&context) { if (typeSystem.discardStatementListOpaque(context)) - return std::pair{ast::StatementList(), context}; + return std::pair{ast::StatementList(), std::move(context)}; return generateWithDependencies( - context, typeSystem.getStatementListTransferFns(), + std::move(context), typeSystem.getStatementListTransferFns(), /*statement list=*/ - [&](const OpaqueContext &context) { - return generateStatementList(context); + [&](OpaqueContext &&context) { + return generateStatementList(std::move(context)); }, /*statement=*/ - [&](const OpaqueContext &context) { - return generateStatement(context); + [&](OpaqueContext &&context) { + return generateStatement(std::move(context)); }, /*constructor=*/ [&](ast::StatementList &&statements, ast::Statement &&statement) { @@ -481,11 +501,11 @@ gen::BasicCGenerator::generateStatementList(const OpaqueContext &context) { result.push_back(std::move(statement)); return ast::StatementList(std::move(result)); }) - .value_or(std::pair{ast::StatementList(), context}); + .value_or(std::pair{ast::StatementList(), std::move(context)}); } std::optional> -gen::BasicCGenerator::generateStatement(const OpaqueContext &context) { +gen::BasicCGenerator::generateStatement(OpaqueContext &&context) { llvm::SmallVector> constructors = random.shuffle( statementGenerators, typeSystem.getStatementProbabilityTableOpaque(context), @@ -494,7 +514,7 @@ gen::BasicCGenerator::generateStatement(const OpaqueContext &context) { &ConstructorKeyPair::first); for (auto &iter : constructors) - if (auto result = iter(this, context)) + if (auto result = iter(this, std::move(context))) return result; return std::nullopt; @@ -502,25 +522,28 @@ gen::BasicCGenerator::generateStatement(const OpaqueContext &context) { std::optional> gen::BasicCGenerator::generateArrayAssignmentStatement( - const OpaqueContext &context) { + OpaqueContext &&context) { if (typeSystem.discardArrayAssignmentStatementOpaque(context)) return std::nullopt; return generateWithDependencies( - context, typeSystem.getArrayAssignmentStatementTransferFns(), + std::move(context), typeSystem.getArrayAssignmentStatementTransferFns(), /*array parameter=*/ - [&](const OpaqueContext &context) { - return generateArrayParameter(context); + [&](OpaqueContext &&context) { + return generateArrayParameter(std::move(context)); }, /*index=*/ - [&](const OpaqueContext &context) { - auto [expression, outputContext] = generateExpression(context); + [&](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)); }, /*value=*/ - [&](const OpaqueContext &context) { return generateExpression(context); }, + [&](OpaqueContext &&context) { + return generateExpression(std::move(context)); + }, /*constructor=*/ [&](ast::ArrayParameter &¶m, ast::Expression &&index, ast::Expression &&value) { @@ -537,21 +560,26 @@ gen::BasicCGenerator::generateArrayAssignmentStatement( } std::optional> -gen::BasicCGenerator::generateStructuredForStatement( - const OpaqueContext &context) { +gen::BasicCGenerator::generateStructuredForStatement(OpaqueContext &&context) { if (typeSystem.discardStructuredForStatementOpaque(context)) return std::nullopt; std::string varName = generateFreshVarName(); return generateWithDependencies( - context, typeSystem.getStructuredForStatementTransferFns(), - [&](const OpaqueContext &context) { return generateExpression(context); }, - [&](const OpaqueContext &context) { return generateExpression(context); }, - [&](const OpaqueContext &context) { return generateExpression(context); }, - [&](const OpaqueContext &context) { + std::move(context), typeSystem.getStructuredForStatementTransferFns(), + [&](OpaqueContext &&context) { + return generateExpression(std::move(context)); + }, + [&](OpaqueContext &&context) { + return generateExpression(std::move(context)); + }, + [&](OpaqueContext &&context) { + return generateExpression(std::move(context)); + }, + [&](OpaqueContext &&context) { auto scopeExit = pushNewScope(); addVariable(ast::PrimitiveType::UInt32, varName); - return generateStatementList(context); + return generateStatementList(std::move(context)); }, [&](ast::Expression &&start, ast::Expression &&end, ast::Expression &&step, ast::StatementList &&statements) { @@ -571,15 +599,15 @@ void gen::BasicCGenerator::initGenerators() { ast::Variable::Tag{}); for (auto op : enumRange()) { expressionGenerators.emplace_back( - [op](BasicCGenerator *self, const OpaqueContext &context) { - return self->generateBinaryExpression(op, context); + [op](BasicCGenerator *self, OpaqueContext &&context) { + return self->generateBinaryExpression(op, std::move(context)); }, op); } for (auto op : enumRange()) { expressionGenerators.emplace_back( - [op](BasicCGenerator *self, const OpaqueContext &context) { - return self->generateUnaryExpression(op, context); + [op](BasicCGenerator *self, OpaqueContext &&context) { + return self->generateUnaryExpression(op, std::move(context)); }, op); } @@ -620,20 +648,21 @@ ast::Function gen::BasicCGenerator::generateFunction(llvm::StringRef functionName) { return std::move( generateWithDependencies( - entryContext, typeSystem.getFunctionTransferFns(), + std::move(entryContext), typeSystem.getFunctionTransferFns(), /*return type=*/ - [&](const OpaqueContext &context) { - auto [returnType, outputContext] = generateReturnType(context); + [&](OpaqueContext &&context) { + auto [returnType, outputContext] = + generateReturnType(std::move(context)); maybeReturnType = returnType; return std::pair{std::move(returnType), std::move(outputContext)}; }, /*statement list=*/ - [&](const OpaqueContext &context) { - return generateStatementList(context); + [&](OpaqueContext &&context) { + return generateStatementList(std::move(context)); }, /*return statement=*/ - [&](const OpaqueContext &context) { - return generateReturnStatement(context); + [&](OpaqueContext &&context) { + return generateReturnStatement(std::move(context)); }, /*constructor=*/ [&](ast::ReturnType &&returnType, ast::StatementList &&statements, diff --git a/tools/hls-fuzzer/BasicCGenerator.h b/tools/hls-fuzzer/BasicCGenerator.h index 258bc2232..6812336e4 100644 --- a/tools/hls-fuzzer/BasicCGenerator.h +++ b/tools/hls-fuzzer/BasicCGenerator.h @@ -51,62 +51,61 @@ class BasicCGenerator { } std::pair - generateReturnStatement(const OpaqueContext &constraints); + generateReturnStatement(OpaqueContext &&context); std::pair - generateExpression(const OpaqueContext &context); + generateExpression(OpaqueContext &&context); std::optional> generateBinaryExpression(ast::BinaryExpression::Op op, - const OpaqueContext &constraints); + OpaqueContext &&context); std::optional> - generateUnaryExpression(ast::UnaryExpression::Op op, - const OpaqueContext &context); + generateUnaryExpression(ast::UnaryExpression::Op op, OpaqueContext &&context); std::optional> - generateConditionalExpression(const OpaqueContext &constraint); + generateConditionalExpression(OpaqueContext &&context); std::optional> - generateCastExpression(const OpaqueContext &constraint); + generateCastExpression(OpaqueContext &&context); ast::Constant getConstantForType(const ast::ScalarType &scalarType) const; std::optional> - generateConstant(const OpaqueContext &constraint) const; + generateConstant(OpaqueContext &&context) const; std::optional> - generateArrayReadExpression(const OpaqueContext &context); + generateArrayReadExpression(OpaqueContext &&context); std::optional> - generateArrayParameter(const OpaqueContext &context); + generateArrayParameter(OpaqueContext &&context); std::optional> - generateScalarParameter(const OpaqueContext &constraints); + generateScalarParameter(OpaqueContext &&context); /// Generates a scalar type or none if it was impossible to generate a scalar /// type in the given context. /// 'toExclude' may be supplied by the caller to further exclude some scalar /// types based on the given context. std::optional> generateScalarType( - const OpaqueContext &context, + OpaqueContext &&context, llvm::function_ref toExclude = nullptr) const; std::pair - generateReturnType(const OpaqueContext &context) const; + generateReturnType(OpaqueContext &&context) const; std::pair - generateStatementList(const OpaqueContext &context); + generateStatementList(OpaqueContext &&context); std::optional> - generateStatement(const OpaqueContext &context); + generateStatement(OpaqueContext &&context); std::optional> - generateArrayAssignmentStatement(const OpaqueContext &context); + generateArrayAssignmentStatement(OpaqueContext &&context); std::optional> - generateStructuredForStatement(const OpaqueContext &context); + generateStructuredForStatement(OpaqueContext &&context); Randomly &random; std::optional maybeReturnType; @@ -129,7 +128,7 @@ class BasicCGenerator { template using Constructor = std::function>( - BasicCGenerator *, const OpaqueContext &)>; + BasicCGenerator *, OpaqueContext &&)>; template using ConstructorKeyPair = std::pair, Key>; @@ -143,12 +142,6 @@ class BasicCGenerator { void initGenerators(); - /// Returns a tuple of 'std::integral_constant's for every element in 'is'. - template - constexpr static auto getIndicesTuple(std::index_sequence) { - return std::tuple{std::integral_constant{}...}; - } - template struct GenerateWithDependencies; @@ -160,24 +153,26 @@ class BasicCGenerator { template * = nullptr> std::pair - operator()(const OpaqueContext &inputContext, + operator()(OpaqueContext &&inputContext, const TransferFnArray &transferFunctions, ASTNode node) const { - return std::move(*(*this)(inputContext, transferFunctions, - [&] { return std::move(node); })); + return *(*this)(std::move(inputContext), transferFunctions, + [&] { return std::move(node); }); } std::optional> operator()( - const OpaqueContext &parentContext, + OpaqueContext &&parentContext, const TransferFnArray &transferFunctions, llvm::function_ref>( - OpaqueContext)>... generators, + OpaqueContext &&)>... generators, llvm::function_ref(SubElements &&...)> constructor) const { typename OpaqueTransferFn::SubElementsTuple subElements; - typename OpaqueTransferFn::ContextTuple contexts; - std::get(contexts) = parentContext; + std::array + 1> + contexts; + std::get(contexts) = std::move(parentContext); // Calculate a topological order between all dependencies. // To do so we use a worklist of elements whose dependencies are all @@ -201,34 +196,30 @@ class BasicCGenerator { // For a given node 'i', contains the number of incoming edges into 'i'. NodeList incomingEdgeCount{}; - std::apply( - [&](auto &&...elementIndices) { - ( - [&](auto elementIndex) { - constexpr std::size_t index = decltype(elementIndex){}; - auto &iter = std::get(transferFunctions); - - if (iter.getInputDependencies().empty() || - iter.getInputDependencies() == - llvm::ArrayRef{INPUT_DEPENDENCY}) { - // No dependency (besides the parent context which is - // satisfied). - worklist[workListSize++] = index; - return; - } - - // Build the outgoing edge list but do keep track of the - // number of incoming edges. - for (auto fromIndex : iter.getInputDependencies()) - if (fromIndex != INPUT_DEPENDENCY) { - forwardEdgeList[fromIndex] - [forwardEdgeCount[fromIndex]++] = index; - ++incomingEdgeCount[index]; - } - }(elementIndices), - ...); + foreachEnumerate( + [&](auto elementIndex, auto &&transferFn) { + constexpr std::size_t index = decltype(elementIndex){}; + if constexpr (index < sizeof...(SubElements)) { + if (transferFn.getInputDependencies().empty() || + transferFn.getInputDependencies() == + llvm::ArrayRef{INPUT_DEPENDENCY}) { + // No dependency (besides the parent context which is + // satisfied). + worklist[workListSize++] = index; + return; + } + + // Build the outgoing edge list but do keep track of the + // number of incoming edges. + for (auto fromIndex : transferFn.getInputDependencies()) + if (fromIndex != INPUT_DEPENDENCY) { + forwardEdgeList[fromIndex][forwardEdgeCount[fromIndex]++] = + index; + ++incomingEdgeCount[index]; + } + } }, - getIndicesTuple(std::index_sequence_for{})); + transferFunctions); std::size_t topoOrderSize = 0; NodeList topoOrder{}; @@ -263,11 +254,15 @@ class BasicCGenerator { auto &context = std::get(contexts); // First calculate the context for the subelement. - context.emplace( - std::get(transferFunctions)(subElements, contexts)); - - std::optional result = - std::get(std::make_tuple(generators...))(*context); + context = std::get(transferFunctions)( + subElements, mapTuplesIntoArray( + [](const OpaqueContext &context) { + return context.data(); + }, + contexts)); + + std::optional result = std::get( + std::make_tuple(generators...))(std::move(context)); if (!result) return false; @@ -281,8 +276,10 @@ class BasicCGenerator { getTupleOfIndices(std::index_sequence_for{})); // Discard this AST node if we failed to generate a subelement. - if (!success) + if (!success) { + parentContext = std::move(std::get(contexts)); return std::nullopt; + } } // Call the constructor with all subelements. @@ -291,12 +288,18 @@ class BasicCGenerator { std::optional astNode = std::apply( [&](auto &&...values) { return constructor(std::move(*values)...); }, std::move(subElements)); - if (!astNode) + if (!astNode) { + parentContext = std::move(std::get(contexts)); return std::nullopt; + } // Calculate the output context. - OpaqueContext outputContext = std::get>( - transferFunctions)(*astNode, contexts); + OpaqueContext outputContext = + std::get>(transferFunctions)( + *astNode, + mapTuplesIntoArray( + [](const OpaqueContext &context) { return context.data(); }, + contexts)); return std::pair{std::move(*astNode), std::move(outputContext)}; } }; @@ -307,15 +310,19 @@ class BasicCGenerator { /// (const OpaqueContext &parentContext, /// const DependencyArray &dependencies, /// llvm::function_ref< - /// std::optional(OpaqueContext)>... generators, + /// std::optional< + /// std::pair>(OpaqueContext&&)> + /// ... generators, /// llvm::function_ref(SubElements &&...)> /// constructor) -> std::optional /// where 'SubElements' are the subelements of 'ASTNode' specified in /// 'TypeSystemTraits::SubElements'. /// - /// 'parentContext' is the input context, 'generators' are callbacks to - /// generate every corresponding subelement of 'ASTNode' and 'constructor' - /// the final callback to construct 'ASTNode' from the subelements. + /// 'parentContext' is the input context and is guaranteed to have only been + /// moved from if this method returns a non-empty optional. + /// 'generators' are callbacks to generate every corresponding subelement of + /// 'ASTNode' and 'constructor' the final callback to construct 'ASTNode' + /// from the subelements. template constexpr static auto generateWithDependencies = GenerateWithDependencies{}; diff --git a/tools/hls-fuzzer/ConjunctionTypeSystem.h b/tools/hls-fuzzer/ConjunctionTypeSystem.h index 6c97d92b7..3f5b50745 100644 --- a/tools/hls-fuzzer/ConjunctionTypeSystem.h +++ b/tools/hls-fuzzer/ConjunctionTypeSystem.h @@ -461,12 +461,11 @@ class ConjunctionTypeSystemBase static auto contextTupleToSubContextTuple( const typename OpaqueTransferFn::ContextTuple &contexts) { return mapTuplesIntoArray( - [&](auto &&context) -> std::optional { + [&](auto &&context) -> const void * { if (!context) - return std::nullopt; + return nullptr; - return OpaqueContext( - std::get(context->template cast())); + return &std::get(*reinterpret_cast(context)); }, contexts); } diff --git a/tools/hls-fuzzer/TypeSystem.h b/tools/hls-fuzzer/TypeSystem.h index c00a3c3a7..ecbe9ed3a 100644 --- a/tools/hls-fuzzer/TypeSystem.h +++ b/tools/hls-fuzzer/TypeSystem.h @@ -22,17 +22,33 @@ namespace dynamatic::gen { /// For an explanation of contexts, see the doc string for 'TypeSystem'. class OpaqueContext { public: - template < - typename TypingContext, - std::enable_if_t< - !std::is_same_v> && - !std::is_same_v>> * = nullptr> - explicit OpaqueContext(TypingContext &&context) - : container(std::forward(context)) {} - + /// Constructs an empty opaque context. + /// 'data()' is guaranteed to return nullptr in this case. + OpaqueContext() = default; + + template >> * = nullptr> + explicit OpaqueContext(TypingContext &&context) { + if constexpr (sizeof(Derived>) <= + SMALL_BUFFER_OPT_BYTES) { + // Small object optimization. + auto &container = storage.emplace(); + new (container.storage.data()) Derived>( + std::forward(context)); + } else { + storage.emplace<0>(std::make_unique>>( + std::forward(context))); + } + } + + /// Casts this 'OpaqueContext' to 'TypingContext'. + /// It is undefined behaviour if this wasn't constructed with an instance of + /// 'TypingContext'. template const TypingContext &cast() const { - return *std::any_cast(&container); + assert(data() != nullptr); + return *reinterpret_cast(data()); } // Enable noop casts to 'OpaqueContext'. @@ -41,8 +57,71 @@ class OpaqueContext { return *this; } + /// Returns an opaque internal pointer to the storage. + /// The pointer can be 'reinterpret_cast'ed to the correct object type that + /// the 'OpaqueContext' was constructed with. + const void *data() const { + return std::visit( + [](auto &&arg) -> const void * { + const Base *base = arg.get(); + if (!base) + return nullptr; + + return base->pointer(); + }, + storage); + } + private: - std::any container; + struct Base { + virtual ~Base() = default; + + virtual void moveInto(void *destination) const = 0; + + virtual const void *pointer() const = 0; + }; + + template + struct Derived final : Base { + T data; + + explicit Derived(T &&data) : data(std::move(data)) {} + explicit Derived(const T &data) : data(data) {} + + void moveInto(void *destination) const override { + new (destination) Derived(std::move(data)); + } + + const void *pointer() const override { + return reinterpret_cast(&data); + } + }; + + constexpr static std::size_t SMALL_BUFFER_OPT_BYTES = 24; + + /// Container object for small object optimization. + struct Container { + alignas(std::max_align_t) + std::array storage{}; + + const Base *get() const { + return reinterpret_cast(storage.data()); + } + + Container() = default; + + ~Container() { get()->~Base(); } + + Container(Container &&rhs) noexcept { rhs.get()->moveInto(storage.data()); } + + Container &operator=(Container &&rhs) noexcept { + get()->~Base(); + rhs.get()->moveInto(storage.data()); + return *this; + } + }; + + std::variant, Container> storage; }; /// Sentinel value representing a dependency on the input context. @@ -170,20 +249,20 @@ class OpaqueTransferFn { public: /// Tuple of optionals of all subelements of this ASTNode. /// This is used to have one consistent API with which to call an - /// 'OpaqueDependency' to calculate a context. + /// 'OpaqueTransferFn' to calculate a context. /// Elements are optional, since they may not yet have been constructed. using SubElementsTuple = typename NonTerminalsTupleImpl::type; - /// Tuple of optionals of all contexts of this ASTNode. + /// Tuple of possibly null pointers of all contexts of this ASTNode. /// This is used to have one consistent API with which to call an - /// 'OpaqueDependency' to calculate a context. - /// Elements are optional, since they may not yet have been calculated. + /// 'OpaqueTransferFn' to calculate a context. + /// Elements may be null, since they may not yet have been calculated. using ContextTuple = - std::array, + std::array + 1>; - /// Constructs an 'OpaqueDependency' from a 'Dependency'. + /// Constructs an 'OpaqueTransferFn' from a 'Dependency'. template /*implicit*/ OpaqueTransferFn( TransferFn &&dep) @@ -202,12 +281,13 @@ class OpaqueTransferFn { if constexpr (index == INPUT_DEPENDENCY) { // Input context. return std::forward_as_tuple( - std::get - 1>(contexts) - ->template cast()); + *reinterpret_cast( + std::get - 1>(contexts))); } else { // Subelement context + ASTNode. return std::forward_as_tuple( - std::get(contexts)->template cast(), + *reinterpret_cast( + std::get(contexts)), *std::get(subElements)); } }(std::integral_constant{})...); @@ -341,8 +421,8 @@ class OutputTransferFn { std::decay_t>; - return std::get(contexts) - ->template cast(); + return *reinterpret_cast( + std::get(contexts)); }, getTupleOfIndices(std::index_sequence{})); @@ -437,71 +517,23 @@ using TransferFnArray = /// It also offers common and convenient default implementations of 'check*' /// and 'discard*' methods. class AbstractTypeSystem { -protected: - /// Returns an instance of 'TransferFn' which simply forwards the context - /// from the input to the subelement. - template - static auto copyFromInput() { - return copyFrom(); - } - - /// Returns an instance of 'TransferFn' which forwards the context - /// from the given index to the subelement. - template - static auto copyFrom() { - return TransferFn( - [](const OpaqueContext &context, auto &&...) { return context; }); - } - - /// Returns a noop 'OutputTransferFn' that keeps the output context - /// equal to the input context. - template - static auto copyInputToOutput() { - return copyToOutput(); - } - - /// Returns a 'OutputTransferFn' whose output context will be equivalent to - /// the output context of 'index' subelement. - template - static auto copyToOutput() { - return OutputTransferFn( - std::index_sequence{}, - [](const ASTNode &, const OpaqueContext &context) { return context; }); - } - public: virtual ~AbstractTypeSystem(); - virtual TransferFnArray getFunctionTransferFns() { - return { - /*return type=*/copyFromInput(), - /*statement list=*/copyFromInput(), - /*return statement=*/copyFromInput(), - /*output=*/copyInputToOutput(), - }; - } + virtual TransferFnArray getFunctionTransferFns() = 0; virtual TransferFnArray - getReturnStatementTransferFns() { - return { - /*return value=*/copyFromInput(), - /*output=*/copyInputToOutput(), - }; - } + getReturnStatementTransferFns() = 0; virtual bool discardScalarTypeOpaque(const ast::ScalarType &scalarType, const OpaqueContext &context) = 0; - virtual TransferFnArray getScalarTypeTransferFns() { - return /*output=*/copyInputToOutput(); - } + virtual TransferFnArray getScalarTypeTransferFns() = 0; virtual bool discardReturnTypeOpaque(const ast::ReturnType &, const OpaqueContext &context) = 0; - virtual TransferFnArray getReturnTypeTransferFns() { - return /*output=*/copyInputToOutput(); - } + virtual TransferFnArray getReturnTypeTransferFns() = 0; /// Returns true if the generator should discard this binary expression /// based on the given input context. @@ -509,156 +541,82 @@ class AbstractTypeSystem { const OpaqueContext &context) = 0; virtual TransferFnArray - getBinaryExpressionTransferFns(ast::BinaryExpression::Op op) { - // Default implementation: Simply propagates the context to the subelements. - return {/*lhs=*/copyFromInput(), - /*rhs=*/copyFromInput(), - /*output=*/copyInputToOutput()}; - } + getBinaryExpressionTransferFns(ast::BinaryExpression::Op op) = 0; virtual bool discardUnaryExpressionOpaque(ast::UnaryExpression::Op op, const OpaqueContext &context) = 0; virtual TransferFnArray - getUnaryExpressionTransferFns(ast::UnaryExpression::Op op) { - return { - /*operand=*/copyFromInput(), - /*output=*/copyInputToOutput(), - }; - } + getUnaryExpressionTransferFns(ast::UnaryExpression::Op op) = 0; virtual bool discardVariableOpaque(const OpaqueContext &context) = 0; - virtual TransferFnArray getVariableTransferFns() { - return { - /*parameter=*/copyFromInput(), - /*output=*/copyInputToOutput(), - }; - } + virtual TransferFnArray getVariableTransferFns() = 0; virtual bool discardCastExpressionOpaque(const OpaqueContext &context) = 0; - virtual TransferFnArray getCastExpressionTransferFns() { - return { - /*target type=*/copyFromInput(), - /*operand=*/copyFromInput(), - /*output=*/copyInputToOutput(), - }; - } + virtual TransferFnArray + getCastExpressionTransferFns() = 0; virtual bool discardConditionalExpressionOpaque(const OpaqueContext &context) = 0; virtual TransferFnArray - getConditionalExpressionTransferFns() { - // Default implementation: Simply propagates the context to the - // subelements. - return { - /*condition=*/copyFromInput(), - /*true value=*/copyFromInput(), - /*false value=*/copyFromInput(), - /*output=*/copyInputToOutput(), - }; - } + getConditionalExpressionTransferFns() = 0; virtual std::optional discardConstantOpaque(const ast::Constant &, const OpaqueContext &context) = 0; - virtual TransferFnArray getConstantTransferFns() { - return /*output=*/copyInputToOutput(); - } + virtual TransferFnArray getConstantTransferFns() = 0; virtual bool discardExistingScalarParameterOpaque(const ast::ScalarParameter &, const OpaqueContext &context) = 0; virtual TransferFnArray - getExistingScalarParameterTransferFns() { - return { - /*output=*/copyInputToOutput(), - }; - } + getExistingScalarParameterTransferFns() = 0; virtual bool discardFreshScalarParameterOpaque(const OpaqueContext &context) = 0; virtual TransferFnArray - getFreshScalarParameterTransferFns() { - return { - /*data type=*/copyFromInput(), - /*output=*/copyInputToOutput(), - }; - } + getFreshScalarParameterTransferFns() = 0; virtual bool discardArrayReadExpressionOpaque(const OpaqueContext &context) = 0; virtual TransferFnArray - getArrayReadExpressionTransferFns() { - return {/*array parameter=*/copyFromInput(), - /*index=*/copyFromInput(), - /*output=*/copyInputToOutput()}; - } + getArrayReadExpressionTransferFns() = 0; virtual bool discardExistingArrayParameterOpaque(const ast::ArrayParameter &, const OpaqueContext &context) = 0; virtual TransferFnArray - getExistingArrayParameterTransferFns() { - return { - /*output=*/copyInputToOutput(), - }; - } + getExistingArrayParameterTransferFns() = 0; virtual bool discardFreshArrayParameterOpaque(const OpaqueContext &context) = 0; virtual TransferFnArray - getFreshArrayParameterTransferFns() { - return { - /*element type=*/copyFromInput(), - /*output=*/copyInputToOutput(), - }; - } + getFreshArrayParameterTransferFns() = 0; virtual bool discardArrayAssignmentStatementOpaque(const OpaqueContext &context) = 0; virtual TransferFnArray - getArrayAssignmentStatementTransferFns() { - return TransferFnArray{ - /*array parameter=*/copyFromInput(), - /*index=*/copyFromInput(), - /*value=*/copyFromInput(), - /*output=*/copyInputToOutput(), - }; - } + getArrayAssignmentStatementTransferFns() = 0; virtual bool discardStatementListOpaque(const OpaqueContext &context) = 0; - virtual TransferFnArray getStatementListTransferFns() { - return TransferFnArray{ - /*statement list=*/copyFromInput(), - /*statement=*/copyFromInput(), - /*output=*/copyInputToOutput(), - }; - } + virtual TransferFnArray getStatementListTransferFns() = 0; virtual bool discardStructuredForStatementOpaque(const OpaqueContext &context) = 0; virtual TransferFnArray - getStructuredForStatementTransferFns() { - return { - /*start=*/copyFromInput(), - /*end=*/copyFromInput(), - /*step=*/copyFromInput(), - /*statements=*/copyFromInput(), - /*output=*/copyInputToOutput(), - }; - } + getStructuredForStatementTransferFns() = 0; using ExpressionKey = std::variant class TypeSystem : public AbstractTypeSystem { +protected: + /// Returns an instance of 'TransferFn' which simply forwards the context + /// from the input to the subelement. + template + static auto copyFromInput() { + return copyFrom(); + } + + /// Returns an instance of 'TransferFn' which forwards the context + /// from the given index to the subelement. + template + static auto copyFrom() { + return TransferFn( + [](const TypingContext &context, auto &&...) { return context; }); + } + + /// Returns a noop 'OutputTransferFn' that keeps the output context + /// equal to the input context. + template + static auto copyInputToOutput() { + return copyToOutput(); + } + + /// Returns a 'OutputTransferFn' whose output context will be equivalent to + /// the output context of 'index' subelement. + template + static auto copyToOutput() { + return OutputTransferFn( + std::index_sequence{}, + [](const ASTNode &, const TypingContext &context) { return context; }); + } + public: template using TransferFn = TransferFn; @@ -760,37 +750,105 @@ class TypeSystem : public AbstractTypeSystem { // since we use CRTP-techniques to call these. They may be but are not // required to be static. + TransferFnArray getFunctionTransferFns() override { + return { + /*return type=*/copyFromInput(), + /*statement list=*/copyFromInput(), + /*return statement=*/copyFromInput(), + /*output=*/copyInputToOutput(), + }; + } + + TransferFnArray + getReturnStatementTransferFns() override { + return { + /*return value=*/copyFromInput(), + /*output=*/copyInputToOutput(), + }; + } + + static bool discardScalarType(const ast::ScalarType &, + const TypingContext &) { + return false; + } + + TransferFnArray getScalarTypeTransferFns() override { + return /*output=*/copyInputToOutput(); + } + + bool discardReturnType(const ast::ReturnType &returnType, + const TypingContext &context) { + // Default implementation dispatches to 'checkScalarType'. + return llvm::TypeSwitch(returnType) + .Case([](const ast::VoidType *) { return false; }) + .Case([&](const ast::ScalarType *scalar) { + return self().discardScalarType(*scalar, context); + }); + } + + TransferFnArray getReturnTypeTransferFns() override { + return /*output=*/copyInputToOutput(); + } + static bool discardBinaryExpression(ast::BinaryExpression::Op, const TypingContext &) { return false; } + TransferFnArray + getBinaryExpressionTransferFns(ast::BinaryExpression::Op op) override { + // Default implementation: Simply propagates the context to the subelements. + return {/*lhs=*/copyFromInput(), + /*rhs=*/copyFromInput(), + /*output=*/copyInputToOutput()}; + } + static bool discardUnaryExpression(ast::UnaryExpression::Op, const TypingContext &) { return false; } + TransferFnArray + getUnaryExpressionTransferFns(ast::UnaryExpression::Op op) override { + return { + /*operand=*/copyFromInput(), + /*output=*/copyInputToOutput(), + }; + } + static bool discardVariable(const TypingContext &) { return false; } + TransferFnArray getVariableTransferFns() override { + return { + /*parameter=*/copyFromInput(), + /*output=*/copyInputToOutput(), + }; + } + static bool discardCastExpression(const TypingContext &) { return false; } - static bool discardConditionalExpression(const TypingContext &) { - return false; + TransferFnArray getCastExpressionTransferFns() override { + return { + /*target type=*/copyFromInput(), + /*operand=*/copyFromInput(), + /*output=*/copyInputToOutput(), + }; } - static bool discardScalarType(const ast::ScalarType &, - const TypingContext &) { + static bool discardConditionalExpression(const TypingContext &) { return false; } - bool discardReturnType(const ast::ReturnType &returnType, - const TypingContext &context) { - // Default implementation dispatches to 'checkScalarType'. - return llvm::TypeSwitch(returnType) - .Case([](const ast::VoidType *) { return false; }) - .Case([&](const ast::ScalarType *scalar) { - return self().discardScalarType(*scalar, context); - }); + TransferFnArray + getConditionalExpressionTransferFns() override { + // Default implementation: Simply propagates the context to the + // subelements. + return { + /*condition=*/copyFromInput(), + /*true value=*/copyFromInput(), + /*false value=*/copyFromInput(), + /*output=*/copyInputToOutput(), + }; } std::optional discardConstant(const ast::Constant &constant, @@ -801,38 +859,108 @@ class TypeSystem : public AbstractTypeSystem { return constant; } + TransferFnArray getConstantTransferFns() override { + return /*output=*/copyInputToOutput(); + } + bool discardExistingScalarParameter(const ast::ScalarParameter ¶meter, const TypingContext &context) { return self().discardScalarType(parameter.getDataType(), context); } + TransferFnArray + getExistingScalarParameterTransferFns() override { + return { + /*output=*/copyInputToOutput(), + }; + } + static bool discardFreshScalarParameter(const TypingContext &) { return false; } + TransferFnArray + getFreshScalarParameterTransferFns() override { + return { + /*data type=*/copyFromInput(), + /*output=*/copyInputToOutput(), + }; + } + static bool discardArrayReadExpression(const TypingContext &) { return false; } + TransferFnArray + getArrayReadExpressionTransferFns() override { + return {/*array parameter=*/copyFromInput(), + /*index=*/copyFromInput(), + /*output=*/copyInputToOutput()}; + } + bool discardExistingArrayParameter(const ast::ArrayParameter ¶meter, const TypingContext &context) { return self().discardScalarType(parameter.getElementType(), context); } + TransferFnArray + getExistingArrayParameterTransferFns() override { + return { + /*output=*/copyInputToOutput(), + }; + } + static bool discardFreshArrayParameter(const TypingContext &) { return false; } + TransferFnArray + getFreshArrayParameterTransferFns() override { + return { + /*element type=*/copyFromInput(), + /*output=*/copyInputToOutput(), + }; + } + static bool discardArrayAssignmentStatement(const TypingContext &) { return false; } + TransferFnArray + getArrayAssignmentStatementTransferFns() override { + return TransferFnArray{ + /*array parameter=*/copyFromInput(), + /*index=*/copyFromInput(), + /*value=*/copyFromInput(), + /*output=*/copyInputToOutput(), + }; + } + static bool discardStatementList(const TypingContext &) { return false; } + TransferFnArray getStatementListTransferFns() override { + return TransferFnArray{ + /*statement list=*/copyFromInput(), + /*statement=*/copyFromInput(), + /*output=*/copyInputToOutput(), + }; + } + static bool discardStructuredForStatement(const TypingContext &) { return false; } + TransferFnArray + getStructuredForStatementTransferFns() override { + return { + /*start=*/copyFromInput(), + /*end=*/copyFromInput(), + /*step=*/copyFromInput(), + /*statements=*/copyFromInput(), + /*output=*/copyInputToOutput(), + }; + } + static ProbabilityTable getExpressionProbabilityTable(const TypingContext &) { return {}; diff --git a/tools/hls-fuzzer/Utils.h b/tools/hls-fuzzer/Utils.h index 93ed1b2a2..32b8dc171 100644 --- a/tools/hls-fuzzer/Utils.h +++ b/tools/hls-fuzzer/Utils.h @@ -125,6 +125,22 @@ void foreachInTuples(F &&f, First &&first, Others &&...others) { std::forward(first), std::forward(others)...); } +/// Applies the function 'f' to all elements in the tuples 'first' and 'others' +/// at the same time with an index as leading argument. +/// 'first' and 'others' are required to be of the same length. +/// 'f' is not expected to return any value. +template +void foreachEnumerate(F &&f, First &&first, Others &&...others) { + // Discard any results of 'f' and make the constructor a noop. + enumerateTuplesInto([](auto &&...) { return 0; }, + [&](auto &&...args) { + f(std::forward(args)...); + return 0; + }, + std::forward(first), + std::forward(others)...); +} + } // namespace dynamatic #endif