Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions tools/hls-fuzzer/BasicCGenerator.h
Original file line number Diff line number Diff line change
Expand Up @@ -200,19 +200,20 @@ 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;
}

// 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];
Expand Down
28 changes: 19 additions & 9 deletions tools/hls-fuzzer/LimitTypeSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,17 +135,27 @@ class LimitTypeSystem : public TypeSystem<LimitTypingContext, LimitTypeSystem> {
}

TransferFnArray<ast::StatementList> 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<ast::StatementList, ast::StatementList::STATEMENT>(),
copyFromInput<ast::StatementList>(),
/*statement list=*/copyFirstOf<ast::StatementList,
weak(ast::StatementList::STATEMENT),
INPUT_DEPENDENCY>(),
/*statement=*/
copyFirstOf<ast::StatementList,
weak(ast::StatementList::STATEMENT_LIST),
INPUT_DEPENDENCY>(),
/*output=*/
copyToOutput<ast::StatementList, ast::StatementList::STATEMENT_LIST>(),
OutputTransferFn<ast::StatementList>(
std::index_sequence<ast::StatementList::STATEMENT,
ast::StatementList::STATEMENT_LIST>{},
[](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;
}),
};
}

Expand Down
121 changes: 106 additions & 15 deletions tools/hls-fuzzer/TypeSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::size_t>::digits - 1));
Comment thread
zero9178 marked this conversation as resolved.
}

/// 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
Expand All @@ -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 <typename TypingContext, typename ASTNode, std::size_t... inputIndices>
class TransferFn {

template <typename Tuple, std::size_t current, std::size_t... remaining>
struct CalcCompFn {
using SubElementType = std::tuple_element_t<
std::min(unwrapWeak(current),
std::tuple_size_v<typename ASTNode::SubElements> - 1),
typename ASTNode::SubElements>;

// Recursive case.
using type = typename CalcCompFn<
decltype(std::tuple_cat(
Expand All @@ -155,12 +197,11 @@ class TransferFn {
std::tuple<const TypingContext &>,
// 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<isWeak(current), const TypingContext *,
const TypingContext &>,
const std::conditional_t<isWeak(current),
std::optional<SubElementType>,
SubElementType> &>>>())),
remaining...>::type;
};

Expand Down Expand Up @@ -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<Context, ast::BinaryExpression,
/// /*rhs=*/ast::BINARY_EXPRESSION::RHS, INPUT_DEPENDENCY>(
/// ast::BINARY_EXPRESSION::RHS, INPUT_DEPENDENCY>(
/// [](const Context& rhsContext, const ast::Expression& rhs,
/// const Context& inputContext) -> Context {
/// ...
/// }
/// )
/// Dependency<Context, ast::BinaryExpression,
/// weak(ast::BINARY_EXPRESSION::RHS), INPUT_DEPENDENCY>(
/// [](const Context* rhsContext, const std::optional<ast::Expression>& rhs,
/// const Context& inputContext) -> Context {
/// ...
/// }
/// )
///
/// The function should always return a 'TypingContext'. All parameters are
/// passed as const-references.
Expand All @@ -220,7 +272,7 @@ class TransferFn {
}

private:
static_assert(((inputIndices <
static_assert(((unwrapWeak(inputIndices) <
std::tuple_size_v<typename ASTNode::SubElements> ||
inputIndices == INPUT_DEPENDENCY) &&
...),
Expand Down Expand Up @@ -284,11 +336,19 @@ class OpaqueTransferFn {
*reinterpret_cast<const TypingContext *>(
std::get<std::tuple_size_v<ContextTuple> - 1>(contexts)));
} else {
// Subelement context + ASTNode.
return std::forward_as_tuple(
*reinterpret_cast<const TypingContext *>(
std::get<index>(contexts)),
*std::get<index>(subElements));
if constexpr (isWeak(index)) {
// Subelement context + ASTNode.
return std::make_tuple(
reinterpret_cast<const TypingContext *>(
std::get<unwrapWeak(index)>(contexts)),
std::cref(std::get<unwrapWeak(index)>(subElements)));
} else {
// Subelement context + ASTNode.
return std::forward_as_tuple(
*reinterpret_cast<const TypingContext *>(
std::get<index>(contexts)),
*std::get<index>(subElements));
}
}
}(std::integral_constant<std::size_t, inputIndices>{})...);

Expand Down Expand Up @@ -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 <typename ASTNode, std::size_t... indices>
static auto copyFirstOf() {
static_assert((!isWeak(indices) || ...),
"at least one of 'indices' must not be weak");

return TransferFn<ASTNode, indices...>([](auto &&...args) {
std::optional<TypingContext> result;
foreachInTuples(
[&](auto &&element) {
if (result)
return;

if constexpr (std::is_same_v<std::decay_t<decltype(element)>,
TypingContext>) {
result = element;
}
if constexpr (std::is_same_v<std::decay_t<decltype(element)>,
const TypingContext *>) {
if (element)
result = *element;
}
},
std::forward_as_tuple(std::forward<decltype(args)>(args)...));

return std::move(*result);
});
}

/// Returns a noop 'OutputTransferFn' that keeps the output context
/// equal to the input context.
template <typename ASTNode>
Expand Down
Loading