Basic Support for Channels - #410
Conversation
| # This is a template that lists the available options when creating a changelog entry file | ||
| # Edit according to your changes, before marking a PR as ready for review. | ||
|
|
There was a problem hiding this comment.
| # This is a template that lists the available options when creating a changelog entry file | |
| # Edit according to your changes, before marking a PR as ready for review. |
| // | ||
| // Part of the LLZK Project, under the Apache License v2.0. | ||
| // See LICENSE.txt for license information. | ||
| // Copyright 2025 Veridise Inc. |
There was a problem hiding this comment.
| // Copyright 2025 Veridise Inc. | |
| // Copyright 2026 Project LLZK |
Applies throughout, I won't make this comment on every location where this change applies.
| The payload signature is declared explicitly on the channel definition. In | ||
| this task, the signature is a builtin tuple type whose elements are all | ||
| `!felt.type`. Multiplicity is not part of the declaration; it remains a | ||
| per-use operand on `channel.push` and `channel.pull`. |
There was a problem hiding this comment.
Some of this is repeated from the dialect description.
| equality constraints across interacting LLZK structs. A channel represents | ||
| an unordered multiset of row-local tuples rather than an ordered stream. | ||
| The payload signature is declared explicitly on the channel definition. In | ||
| this task, the signature is a builtin tuple type whose elements are all |
There was a problem hiding this comment.
"In this task" sounds odd.
| class ChannelUseOpBase<string mnemonic, list<Trait> traits = []> | ||
| : ChannelDialectOp< | ||
| mnemonic, traits#[AttrSizedOperandSegments, ConstraintGen, | ||
| DeclareOpInterfaceMethods<SymbolUserOpInterface>]> { |
There was a problem hiding this comment.
We might want to consider these to be memory-modifying ops with a memory effects trait.
| if (!rhs.tableOffset) { | ||
| return success(); | ||
| } | ||
| if (!lhs.tableOffset) { | ||
| lhs = rhs; | ||
| return success(); | ||
| } | ||
| if (lhs == rhs) { | ||
| return success(); | ||
| } |
There was a problem hiding this comment.
| if (!rhs.tableOffset) { | |
| return success(); | |
| } | |
| if (!lhs.tableOffset) { | |
| lhs = rhs; | |
| return success(); | |
| } | |
| if (lhs == rhs) { | |
| return success(); | |
| } | |
| if (!rhs.tableOffset || lhs == rhs) { | |
| return success(); | |
| } | |
| if (!lhs.tableOffset) { | |
| lhs = rhs; | |
| return success(); | |
| } |
| if (!rhs) { | ||
| out = lhs; | ||
| return success(); | ||
| } | ||
| if (*lhs == *rhs) { | ||
| out = lhs; | ||
| return success(); | ||
| } |
There was a problem hiding this comment.
| if (!rhs) { | |
| out = lhs; | |
| return success(); | |
| } | |
| if (*lhs == *rhs) { | |
| out = lhs; | |
| return success(); | |
| } | |
| if (!rhs || *lhs == *rhs) { | |
| out = lhs; | |
| return success(); | |
| } |
| ``` | ||
| }]; | ||
|
|
||
| let arguments = (ins SymbolNameAttr:$sym_name, TypeAttr:$message_type); |
There was a problem hiding this comment.
Try TypeAttrOf<TupleOf<[LLZK_FeltType]> to get automatic verification
There was a problem hiding this comment.
Alternatively, since it must be a tuple of felt, maybe the channel def could just specify an IndexAttr to denote the size of the tuple and reduce IR verbosity
| mnemonic, traits#[AttrSizedOperandSegments, ConstraintGen, | ||
| DeclareOpInterfaceMethods<SymbolUserOpInterface>]> { | ||
| let arguments = (ins FlatSymbolRefAttr:$channel_ref, | ||
| Variadic<AnyLLZKType>:$tuple, Optional<AnyLLZKType>:$mult); |
There was a problem hiding this comment.
Based on your impl of the channel def and manually implemented verifier for mult I think these should both specify just felt type values are allowed.
| Variadic<AnyLLZKType>:$tuple, Optional<AnyLLZKType>:$mult); | |
| Variadic<LLZK_FeltType>:$tuple, Optional<LLZK_FeltType>:$mult); |
| // expected-error@+1 {{'channel.push' op only valid within a 'function.def' with 'function.allow_constraint' attribute}} | ||
| channel.push @c (%x) : !felt.type |
There was a problem hiding this comment.
add same test for pull as well
| function.def @emit(%a: !struct.type<@RowA>, %b: !struct.type<@RowB>) attributes {function.allow_constraint} { | ||
| %x = struct.readm %a[@x] : !struct.type<@RowA>, !felt.type | ||
| %y = struct.readm %b[@y] : !struct.type<@RowB>, !felt.type | ||
| // expected-error@+1 {{'channel.push' op tuple operands must be derived from a single row context}} |
There was a problem hiding this comment.
What is "row context", a struct? Error message could be more helpful if in terms of LLZK concepts.
| DeclareOpInterfaceMethods<SymbolUserOpInterface>]> { | ||
| let arguments = (ins FlatSymbolRefAttr:$channel_ref, | ||
| Variadic<AnyLLZKType>:$tuple, Optional<AnyLLZKType>:$mult); | ||
| let hasCustomAssemblyFormat = 1; |
There was a problem hiding this comment.
why did let assemblyFormat = not work for these ops?
Summary
This draft PR adds initial IR support for channels as a new
channeldialect in the LLZK dialect family.Channels model unordered multiset-equality constraints for cross-struct / cross-chip interaction. This PR introduces the IR surface, parser/printer support, and verifier checks needed to represent channel interactions in LLZK. It does not implement backend lowering or the global multiset balance check.
What This PR Adds
channeldialect with:channel.defchannel.pushchannel.pullchannel.def!felt.typechannel.push/channel.pullas symbol-user ops with:function.allow_constraintDesign Notes
channeldialect, not as ops inside thellzkdialect.channel.defnow carries an explicit payload signature instead of inferring schema from use sites.channel.push/channel.pull; it is not part of the channel declaration.struct.readm,array.read, and field-nativefelt.add/felt.sub/felt.mul/felt.negOpen Questions