diff --git a/tools/hls-fuzzer/BasicCGenerator.h b/tools/hls-fuzzer/BasicCGenerator.h index 6812336e4..2242a5467 100644 --- a/tools/hls-fuzzer/BasicCGenerator.h +++ b/tools/hls-fuzzer/BasicCGenerator.h @@ -200,11 +200,12 @@ class BasicCGenerator { [&](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). + if (llvm::all_of( + transferFn.getInputDependencies(), [](std::size_t index) { + return index == INPUT_DEPENDENCY || isWeak(index); + })) { + // No dependency (besides the input context and weak ones which + // are satisfied by default). worklist[workListSize++] = index; return; } @@ -212,7 +213,7 @@ class BasicCGenerator { // Build the outgoing edge list but do keep track of the // number of incoming edges. for (auto fromIndex : transferFn.getInputDependencies()) - if (fromIndex != INPUT_DEPENDENCY) { + if (fromIndex != INPUT_DEPENDENCY && !isWeak(fromIndex)) { forwardEdgeList[fromIndex][forwardEdgeCount[fromIndex]++] = index; ++incomingEdgeCount[index]; diff --git a/tools/hls-fuzzer/LimitTypeSystem.h b/tools/hls-fuzzer/LimitTypeSystem.h index 21018141a..34f78157c 100644 --- a/tools/hls-fuzzer/LimitTypeSystem.h +++ b/tools/hls-fuzzer/LimitTypeSystem.h @@ -135,17 +135,27 @@ class LimitTypeSystem : public TypeSystem { } TransferFnArray getStatementListTransferFns() override { - // TODO: This needlessly forces in which order statements are to be - // generated! - // In reality, the type system does not care whether the statement - // gets generated first or the rest of the statement list, just that - // their respective statement number is propagated to the other. - // This is a missing feature! return { - copyFrom(), - copyFromInput(), + /*statement list=*/copyFirstOf(), + /*statement=*/ + copyFirstOf(), /*output=*/ - copyToOutput(), + OutputTransferFn( + std::index_sequence{}, + [](const ast::StatementList &, LimitTypingContext statement, + const LimitTypingContext &statementList) { + // Regardless of which of the two was generated first, we can + // extract the total number of statements by taking their maximum. + statement.totalNumberOfStatements = + std::max(statement.totalNumberOfStatements, + statementList.totalNumberOfStatements); + return statement; + }), }; } diff --git a/tools/hls-fuzzer/TypeSystem.h b/tools/hls-fuzzer/TypeSystem.h index ecbe9ed3a..7ccdd1f86 100644 --- a/tools/hls-fuzzer/TypeSystem.h +++ b/tools/hls-fuzzer/TypeSystem.h @@ -127,6 +127,32 @@ class OpaqueContext { /// Sentinel value representing a dependency on the input context. constexpr std::size_t INPUT_DEPENDENCY = -1; +/// Marks a dependency as weak. This is a noop for 'INPUT_DEPENDENCY' as it +/// cannot be weak. +/// See the 'TypeSystem' documentation for what 'weak' means. +constexpr std::size_t weak(std::size_t dependency) { + // We use the top bit being set as an encoding for a dependency being weak. + // Since 'INPUT_DEPENDENCY' is encoded as all 1s, this operation is also a + // noop for 'INPUT_DEPENDENCY'. + return dependency | (1ull << (std::numeric_limits::digits - 1)); +} + +/// Returns true if 'dependency' is weak. +/// See the 'TypeSystem' documentation for what 'weak' means. +constexpr bool isWeak(std::size_t dependency) { + return dependency != INPUT_DEPENDENCY && weak(dependency) == dependency; +} + +/// If 'dependency' is weak, then it returns the original non-weak dependency. +/// Otherwise, returns 'dependency'. +/// See the 'TypeSystem' documentation for what 'weak' means. +constexpr std::size_t unwrapWeak(std::size_t dependency) { + if (dependency == INPUT_DEPENDENCY) + return INPUT_DEPENDENCY; + + return dependency & ~weak(0); +} + /// Class responsible for telling the generator how to calculate the input /// 'TypingContext' for a given subelement of 'ASTNode'. /// The subelement whose input-context we are calculating for is given by its @@ -139,12 +165,28 @@ constexpr std::size_t INPUT_DEPENDENCY = -1; /// this instance depends on within 'ASTNode::SubElements'. /// The special value 'INPUT_DEPENDENCY' represents depending on the /// input-context of 'ASTNode'. -/// It is the user's responsibility to not create cyclic dependencies. +/// +/// Dependencies can additionally be marked 'weak'. In that case, the element +/// and context will be passed to the transfer function if and only if they +/// have been generated previously. Otherwise, an empty optional and nullptr +/// are passed for the AST-node and context of that dependency instead. +/// +/// This is the big difference to normal dependencies: They do not force an +/// AST-node to have been generated previously (i.e., do not participate in the +/// topological sort performed by the generator). This makes it legal to have +/// cycles involving weak dependencies. +/// +/// It is the user's responsibility to not create cyclic non-weak dependencies. template class TransferFn { template struct CalcCompFn { + using SubElementType = std::tuple_element_t< + std::min(unwrapWeak(current), + std::tuple_size_v - 1), + typename ASTNode::SubElements>; + // Recursive case. using type = typename CalcCompFn< decltype(std::tuple_cat( @@ -155,12 +197,11 @@ class TransferFn { std::tuple, // Add both the context and the ASTNode to the arguments. std::tuple< - const TypingContext &, - const std::tuple_element_t< - std::min(current, std::tuple_size_v< - typename ASTNode::SubElements> - - 1), - typename ASTNode::SubElements> &>>>())), + std::conditional_t, + const std::conditional_t, + SubElementType> &>>>())), remaining...>::type; }; @@ -191,17 +232,28 @@ class TransferFn { /// Specifically, for every element of 'inputIndices' and in the order as /// given in 'inputIndices', the arguments are: /// * The input 'TypingContext' if the value is 'INPUT_DEPENDENCY' - /// * The output 'TypingContext' of the 'i'th subelement of 'ASTNode' followed + /// * If 'i' is not weak, the output 'TypingContext' of the 'i'th subelement + /// of 'ASTNode' followed /// by the subelement's AST node itself. + /// * If 'i' is weak, a pointer to the output 'TypingContext' of the 'i'th + /// subelement of 'ASTNode' or null if not present, followed by an optional + /// of the subelement's AST node itself if already generated. /// /// Example: /// Dependency( + /// ast::BINARY_EXPRESSION::RHS, INPUT_DEPENDENCY>( /// [](const Context& rhsContext, const ast::Expression& rhs, /// const Context& inputContext) -> Context { /// ... /// } /// ) + /// Dependency( + /// [](const Context* rhsContext, const std::optional& rhs, + /// const Context& inputContext) -> Context { + /// ... + /// } + /// ) /// /// The function should always return a 'TypingContext'. All parameters are /// passed as const-references. @@ -220,7 +272,7 @@ class TransferFn { } private: - static_assert(((inputIndices < + static_assert(((unwrapWeak(inputIndices) < std::tuple_size_v || inputIndices == INPUT_DEPENDENCY) && ...), @@ -284,11 +336,19 @@ class OpaqueTransferFn { *reinterpret_cast( std::get - 1>(contexts))); } else { - // Subelement context + ASTNode. - return std::forward_as_tuple( - *reinterpret_cast( - std::get(contexts)), - *std::get(subElements)); + if constexpr (isWeak(index)) { + // Subelement context + ASTNode. + return std::make_tuple( + reinterpret_cast( + std::get(contexts)), + std::cref(std::get(subElements))); + } else { + // Subelement context + ASTNode. + return std::forward_as_tuple( + *reinterpret_cast( + std::get(contexts)), + *std::get(subElements)); + } } }(std::integral_constant{})...); @@ -721,6 +781,37 @@ class TypeSystem : public AbstractTypeSystem { [](const TypingContext &context, auto &&...) { return context; }); } + /// Returns an instance of 'TransferFn' which forwards the first present + /// context from the possibly-weak dependencies in 'indices'. + /// At least one dependency must not be weak. + template + static auto copyFirstOf() { + static_assert((!isWeak(indices) || ...), + "at least one of 'indices' must not be weak"); + + return TransferFn([](auto &&...args) { + std::optional result; + foreachInTuples( + [&](auto &&element) { + if (result) + return; + + if constexpr (std::is_same_v, + TypingContext>) { + result = element; + } + if constexpr (std::is_same_v, + const TypingContext *>) { + if (element) + result = *element; + } + }, + std::forward_as_tuple(std::forward(args)...)); + + return std::move(*result); + }); + } + /// Returns a noop 'OutputTransferFn' that keeps the output context /// equal to the input context. template