Skip to content

[hls-fuzzer] Implement support for output contexts#905

Open
zero9178 wants to merge 5 commits into
mainfrom
users/zero9179/output-transfer-fns
Open

[hls-fuzzer] Implement support for output contexts#905
zero9178 wants to merge 5 commits into
mainfrom
users/zero9179/output-transfer-fns

Conversation

@zero9178
Copy link
Copy Markdown
Collaborator

@zero9178 zero9178 commented May 8, 2026

Prior to this PR, contexts within an AST node generation could be forwarded between siblings elements and from parents to siblings. Consuming a siblings context incorrectly meant consuming its input context.

This PR properly implements the concept of an output context. After an AST element has been constructed an output context is derived from it and optionally the output contexts of its children. Consuming a sibling context now also means consuming its output context.

This enables the fully power of attributed grammars: Any information can now be propagated through the entire AST in any direction. This is especially useful for any typesystems that want to move information between statements.

As an inititial use case the dynamatic type system has been adapted to no longer rely on randomness in the type system to choose type constraints for subelements. Rather, in cases where either choice of integer or floating point are valid, but they do not need to match (e.g. a + requires both operands be integers or floats), we use the output context to generate the constraint that the actual AST node adheres to and forward it to the other operands.

Prior to this PR, contexts within an AST node generation could be forwarded between siblings elements and from parents to siblings. Consuming a siblings context incorrectly meant consuming its input context.

This PR properly implements the concept of an _output_ context. After an AST element has been constructed an output context is derived from it and optionally the output contexts of its children.
Consuming a sibling context now also means consuming its output context.

This enables the fully power of attributed grammars: Any information can now be propagated through the entire AST in any direction. This is especially useful for any typesystems that want to move information between statements.

As an inititial use case the dynamatic type system has been adapted to no longer rely on randomness in the type system to choose type constraints for subelements.
Rather, in cases where either choice of integer or floating point are valid, but they do not need to match (e.g. a `+` requires both operands be integers or floats), we use the output context to generate the constraint that the actual AST node adheres to and forward it to the other operands.
@zero9178 zero9178 requested a review from Jiahui17 May 8, 2026 10:59
@Jiahui17
Copy link
Copy Markdown
Member

Jiahui17 commented May 8, 2026

Random question:

if we do

copyOutputFromParent(this);

Can the compiler automatically deduce the type from "this"?

@zero9178
Copy link
Copy Markdown
Collaborator Author

zero9178 commented May 8, 2026

Random question:

if we do

copyOutputFromParent(this);

Can the compiler automatically deduce the type from "this"?

Yes, absolutely!

@Jiahui17
Copy link
Copy Markdown
Member

Question: I understood that a (input) transferFn can be configured with an evaluation order. For instance, we say for an array write like

A[I] = B;

We can specify that B must be generated after A. Does it have anything to do with the output context? Apparantly in the previous PR where transferFn was added this wasn't the case (because we didn't have output context).

@Jiahui17
Copy link
Copy Markdown
Member

Prior to this PR, contexts within an AST node generation could be forwarded between siblings elements and from parents to siblings. Consuming a siblings context incorrectly meant consuming its input context.

Reading the PR description again. does this mean that before we can only specify the order between siblings but there's no information flow?

@zero9178
Copy link
Copy Markdown
Collaborator Author

Question: I understood that a (input) transferFn can be configured with an evaluation order. For instance, we say for an array write like

A[I] = B;

We can specify that B must be generated after A. Does it have anything to do with the output context? Apparantly in the previous PR where transferFn was added this wasn't the case (because we didn't have output context).

No this functionality was independent of the output context functionality. Output contexts are always executed at the very end (hardcoded), so do not participate in the topological sort.

Prior to this PR, contexts within an AST node generation could be forwarded between siblings elements and from parents to siblings. Consuming a siblings context incorrectly meant consuming its input context.

Reading the PR description again. does this mean that before we can only specify the order between siblings but there's no information flow?

There was information flow, but the information flow was as-if copyOutputFromParent was used everywhere and was therefore more limited.

For your example above e.g. we could say that A depends on B. Prior to this PR we would then:

  1. Calculate the input context of B first
  2. Generate B
  3. Use both B and its input context to calculate the input context of A
  4. Generate A

Now we:

  1. Calculate the input context of B first
  2. Generate B and its output context
  3. Use both B and its output context to calculate the input context of A
  4. Generate A and its output context

This is why all generate functions now return a pair of contexts and AST nodes

@Jiahui17
Copy link
Copy Markdown
Member

Jiahui17 commented May 13, 2026

For instance, for the transferFns of conditional expr:

    return {
        TransferFn<ast::ConditionalExpression>(
            DynamaticTypingContext{DynamaticTypingContext::Unconstrained}),
        copyFromParent<ast::ConditionalExpression>(),
        // Forward choice of type made in the true expression into the false
        // expression.
        copyFrom<ast::ConditionalExpression,
                 ast::ConditionalExpression::TRUE_VAL>(),
        copyOutputFrom<ast::ConditionalExpression,
                       ast::ConditionalExpression::TRUE_VAL>(),
    };

Here, whenever we specify ast::ConditionalExpression::TRUE_VAL

    copyFrom<ast::ConditionalExpression,
             ast::ConditionalExpression::TRUE_VAL>(),

We always refer to the output context when generating the TRUE_VAL node?

@Jiahui17
Copy link
Copy Markdown
Member

image

Is what I drew here correct?

@zero9178
Copy link
Copy Markdown
Collaborator Author

For instance, for the transferFns of conditional expr:

    return {
        TransferFn<ast::ConditionalExpression>(
            DynamaticTypingContext{DynamaticTypingContext::Unconstrained}),
        copyFromParent<ast::ConditionalExpression>(),
        // Forward choice of type made in the true expression into the false
        // expression.
        copyFrom<ast::ConditionalExpression,
                 ast::ConditionalExpression::TRUE_VAL>(),
        copyOutputFrom<ast::ConditionalExpression,
                       ast::ConditionalExpression::TRUE_VAL>(),
    };

Here, whenever we specify ast::ConditionalExpression::TRUE_VAL

    copyFrom<ast::ConditionalExpression,
             ast::ConditionalExpression::TRUE_VAL>(),

We always refer to the output context when generating the TRUE_VAL node?

Correct!

image Is what I drew here correct?

The green arrow is if your TransferFn uses PARENT_DEPENDENCY in its dependency list. The output context can also choose which contexts it cares about. This is merely convenience in the function signature, it does not change anything about the ordering. In your case e.g. it could depend on every output context of subelement. The generated ASTNode is always passed as first parameter

@Jiahui17
Copy link
Copy Markdown
Member

Should we call the output context OffspringContext (or child context)? I still don't like the word output because it is used as the input of the transferFns.

I think calling it OffspringContext makes a good contrast with the parent context:

  • ParentContext: the context computed and passed from a parent node (through TransferFns).
  • OffspringContext: the context computed and passed from a child node (through
    OutputTransferFns).

Maybe for clarity, we can also rename TransferFns and OutputTransferFns:

  • TransferFn -> ToOffspringTransferFn
  • OutputTransferFn -> ToParentTransferFn

Some other alternative names from chat

  • UpwardTransferFn/DownwardTransferFn
  • ToParentTransferFn/ToChildTransferFn

@zero9178
Copy link
Copy Markdown
Collaborator Author

zero9178 commented May 15, 2026

Should we call the output context OffspringContext (or child context)? I still don't like the word output because it is used as the input of the transferFns.

I think calling it OffspringContext makes a good contrast with the parent context:

  • ParentContext: the context computed and passed from a parent node (through TransferFns).
  • OffspringContext: the context computed and passed from a child node (through
    OutputTransferFns).

Maybe for clarity, we can also rename TransferFns and OutputTransferFns:

  • TransferFn -> ToOffspringTransferFn
  • OutputTransferFn -> ToParentTransferFn

Some other alternative names from chat

  • UpwardTransferFn/DownwardTransferFn
  • ToParentTransferFn/ToChildTransferFn

Hmm not the biggest fan of the OffspringContext. It does not imply anything about the direction of where the context is flowing and is being used and sounds quite a lot like an input to me on first glance. (similar for others)

The fact that an output of one AST generation is also used as the input for another is not too crazy to me IMO. It is just like how the output (result) of a handshake.addi can be the input/operand of another operation like handshake.muli.

What I would suggest instead probably is to rename "parent" to "input" everywhere. That way we can simply say: "Every AST node generation has an input context and an output context and is a context -> (node, context) function (blackbox view)"

@Jiahui17
Copy link
Copy Markdown
Member

Hmm not the biggest fan of the OffspringContext. It does not imply anything about the direction of where the context is flowing and is being used and sounds quite a lot like an input to me on first glance. (similar for others)

Yeah this is a good argument for input/output

Just to clarify one more thing: Parent/Output is not a consistent naming:

  • Parent is referring to the AST hierarchy
  • and the Output is referring to the input/output of an AST generation function

@zero9178
Copy link
Copy Markdown
Collaborator Author

Hmm not the biggest fan of the OffspringContext. It does not imply anything about the direction of where the context is flowing and is being used and sounds quite a lot like an input to me on first glance. (similar for others)

Yeah this is a good argument for input/output

Just to clarify one more thing: Parent/Output is not a consistent naming:

  • Parent is referring to the AST hierarchy
  • and the Output is referring to the input/output of an AST generation function

Yes. In the context of the transfer functions I'd only use the "input" terminology. That we are referring to the input and output of a given AST node generation should be clear from the context (i.e., if we are in the getBinaryExpressionTransferFns method then obviously we are talking about the input and output for a binary expression). I don't think we'll be using the parent terminology anywhere else

@Jiahui17
Copy link
Copy Markdown
Member

What’s the difference between the OutputTransferFn cpp class vs. TransferFn? From the above I learned that an OutputTransferFn can depend on any output context as well as the input context, which sounds to me exactly the same as TransferFn

@Jiahui17
Copy link
Copy Markdown
Member

So I don’t get why they are two separate classes 🤔

@zero9178
Copy link
Copy Markdown
Collaborator Author

So I don’t get why they are two separate classes 🤔

The only reason is really the different signature of the function. For say the transfer functions of BinaryExpression, they can never receive the BinaryExpression after generation, only the Expression, lhs or rhs that they depend on.
E.g. for a TransferFn<BinaryExpression, BinaryExpression::LHS, BinaryExpression::RHS> needs a function with the signature (const TypingContext& lhsOutput, const Expression& lhsGenerated, const TypingContext& rhsOutput, const Expression&rhsGenerated) -> TypingContext.

An OutputTransferFn(std::index_sequence<BinaryExpression, BinaryExpression::LHS>{}, ...) requires a function with the signature (const BinaryExpression& resultAST, const TypingContext& lhsOutput, const TypingContext& rhsOutput) -> TypingContext

Comment on lines +256 to +258
/// It primarily differs from 'TransferFn' in that it receives a fully
/// constructed instance of 'ASTNode' rather than subelements and is always
/// executed last.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay now I get this. Let's document the discussion here (we need to also communicate to the reader why there is an extra AST node here (and it is impossible to have it in TransferFn))

@Jiahui17
Copy link
Copy Markdown
Member

Another question: in which situation that a transferFn needs the ast node at its input?

@Jiahui17
Copy link
Copy Markdown
Member

I thought since we make the context arbitrary powerful, we don’t need to additionally pass the node itself

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants