Skip to content

verif to smt conversion#549

Draft
iangneal wants to merge 35 commits into
mainfrom
iangneal/init-verif-to-smt
Draft

verif to smt conversion#549
iangneal wants to merge 35 commits into
mainfrom
iangneal/init-verif-to-smt

Conversation

@iangneal

@iangneal iangneal commented Jun 17, 2026

Copy link
Copy Markdown
Contributor
  • Adds pass for fixpoint scalarization
  • Adds a pass for converting scalarized verif contracts to smt
  • Adds a pass pipeline for scalarizing and converting verif to smt
  • Generalizes some function and call specific conversions to accept contracts and includes, since contracts are function-like and includes are call-like

@iangneal iangneal requested a review from a team as a code owner June 17, 2026 00:28
@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Test Results

0 files   -   2  0 suites   - 2   0s ⏱️ - 5m 12s
0 tests  - 259  0 ✅  - 253  0 💤  -  6  0 ❌ ±0 
0 runs   - 518  0 ✅  - 506  0 💤  - 12  0 ❌ ±0 

Results for commit 06f2bf6. ± Comparison against base commit 3196c35.

♻️ This comment has been updated with latest results.

Comment on lines +157 to +167
def AggregateScalarizationPass : LLZKPass<"llzk-aggregate-scalarization"> {
let summary =
"Repeatedly scalarize array/pod aggregates until the module stabilizes";
let description = [{
Repeatedly runs the existing array-to-scalar and pod-to-scalar passes,
followed by canonicalization, until the module reaches a fixpoint. This is
intended as a reusable preprocessing step for pipelines that need
array/pod-free IR.
}];
let options = [];
}

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.

I've been thinking about this a bit and I think we can augment array-to-scalar to split arrays within a pod (just create N pod records, similar to how splitting an array-type struct member) so that a single pass of array-to-scalar then pod-to-scalar can remove all.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Since that'll take a bit more effort, can we add that as a TODO and keep this solution for now?

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.

sure

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.

Update, Raghav needed all pod types removed without also removing array types so this PR increases the capabilities of pod-to-scalar such that running it should be able to remove all pod.type even within an array so pipelines can just run it before array-to-scalar.

@tim-hoffman

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1febe215c7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/Transforms/LLZKVerifToSmtPass.cpp Outdated
Comment thread lib/Transforms/LLZKVerifToSmtPass.cpp Outdated
Comment thread lib/Transforms/LLZKVerifToSmtPass.cpp Outdated
Comment thread lib/Transforms/LLZKVerifToSmtPass.cpp Outdated
Comment thread lib/Transforms/LLZKVerifToSmtPass.cpp Outdated
Comment thread lib/Transforms/LLZKVerifToSmtPass.cpp Outdated
Comment thread lib/Transforms/LLZKVerifToSmtPass.cpp
Comment thread lib/Transforms/LLZKVerifToSmtPass.cpp
Comment thread lib/Transforms/LLZKVerifToSmtPass.cpp
Comment thread lib/Transforms/LLZKAggregateScalarizationPass.cpp

@tim-hoffman tim-hoffman left a comment

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.

I have a few files to go through still but here's my feedback so far

Comment thread include/llzk/Transforms/LLZKConversionUtils.h Outdated
Comment thread include/llzk/Transforms/LLZKTransformationPasses.td Outdated
Comment thread include/llzk/Transforms/LLZKTransformationPasses.td Outdated
Comment thread lib/Dialect/Array/Transforms/ArrayToScalarPass.cpp Outdated
Comment thread lib/Dialect/Array/Transforms/ArrayToScalarPass.cpp Outdated
Comment thread lib/Dialect/POD/Transforms/PodToScalarPass.cpp Outdated
Comment thread lib/Transforms/LLZKAggregateScalarizationPass.cpp Outdated
Comment thread lib/Transforms/LLZKAggregateScalarizationPass.cpp Outdated
Comment thread test/Conversions/verif_to_smt_scalar_rejects_aggregates.llzk Outdated
@iangneal

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 375f7a624e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/Transforms/LLZKVerifToSmtPass.cpp Outdated
Comment thread lib/Transforms/LLZKVerifToSmtPass.cpp Outdated
@iangneal

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ab98bea16b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/Transforms/LLZKVerifToSmtPass.cpp Outdated
Comment thread lib/Transforms/LLZKVerifToSmtPass.cpp Outdated
@iangneal

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 38478589cd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/Transforms/LLZKVerifToSmtPass.cpp Outdated

@0xddom 0xddom left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The verif to smt pass needs some work. A cleaner approach is to let the different dialects define how to lower to smt, similar to how lowering to LLVM works in upstream MLIR. The pass' name hints that is going to lower only operations from the verif dialect, but it lowers everything that is meant to be lowered to smt. The pass may probably be better renamed to -llzk-to-smt.

The current lowering logic is pretty fragile. If we add new ops (like bool.forall coming soon) then we need to change this pass as well to support it. If the implementation for converting from LLZK to SMT is defined by the ops or the dialect, then we only need to change the dialect or the op, a change probably closer to where the other changes that break the pass are happening.

The code as it stands right now is a good prototype for getting the demo working for yesterday but that's about it.

Comment on lines 209 to +227
@@ -201,7 +224,7 @@ class FunctionTypeConverter {
public:
virtual ~FunctionTypeConverter() = default;

void convert(function::FuncDefOp op, mlir::RewriterBase &rewriter) {
template <typename FunctionLikeOp> void convert(FunctionLikeOp op, mlir::RewriterBase &rewriter) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Change the docstring of the class now that it works on any function-like thing

Comment thread include/llzk/Util/Walk.h
Comment on lines +40 to +44
template <typename MatchType, typename NextMatchType, typename... MatchTypes, typename R>
inline static bool walkContains(R &root) {
return root
.walk([](mlir::Operation *op) {
return llvm::isa<MatchType, NextMatchType, MatchTypes...>(op) ? mlir::WalkResult::interrupt()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why do you need typename MatchType, typename NextMatchType? That forces callers to check for at least 2 possibilities.

Comment on lines +326 to +330
static constexpr bool supportsResultAttrs() {
return requires(FunctionLikeOp op, ArrayAttr attrs) {
op.getResAttrsAttr();
op.setResAttrsAttr(attrs);
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This pattern is used in LLZKConversionUtils.h as well so we could move the function there to share it.

void rewrite(CallOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override {
// Create new CallOp with split results first so, then process its inputs to split types
CallOp newCall = newCallOpWithSplitResults(op, adaptor, rewriter);
rewriter.setInsertionPoint(newCall);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is such that whatever is created next in processInputOperands is inserted before newCall, right? Don't we need to recover the previous insertion point after that? Otherwise it will continue inserting before the call unless the conversion engine saves the insertion point internally before calling a pattern's rewrite function.

Comment on lines +158 to +193
if (auto structDef = dyn_cast<StructDefOp>(op)) {
llvm::SmallVector<Type> memberTypes;
for (MemberDefOp member : structDef.getMemberDefs()) {
memberTypes.push_back(member.getType());
}
if (failed(collectAggregateProfileForTypes(memberTypes, structDef, tables, profile))) {
return WalkResult::interrupt();
}
} else if (auto funcDef = dyn_cast<FuncDefOp>(op)) {
llvm::SmallVector<Type> types;
llvm::append_range(types, funcDef.getArgumentTypes());
llvm::append_range(types, funcDef.getResultTypes());
if (failed(collectAggregateProfileForTypes(types, funcDef, tables, profile))) {
return WalkResult::interrupt();
}
} else if (auto contract = dyn_cast<ContractOp>(op)) {
llvm::SmallVector<Type> types;
llvm::append_range(types, contract.getArgumentTypes());
llvm::append_range(types, contract.getResultTypes());
if (failed(collectAggregateProfileForTypes(types, contract, tables, profile))) {
return WalkResult::interrupt();
}
} else if (auto createArray = dyn_cast<CreateArrayOp>(op)) {
if (failed(collectAggregateProfileForTypes(
ArrayRef<Type> {createArray.getType()}, createArray, tables, profile
))) {
return WalkResult::interrupt();
}
} else if (auto newPod = dyn_cast<NewPodOp>(op)) {
if (failed(collectAggregateProfileForTypes(
ArrayRef<Type> {newPod.getType()}, newPod, tables, profile
))) {
return WalkResult::interrupt();
}
}
return WalkResult::advance();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You can use llvm::TypeSwitch here instead of chaining if-then-elses

Comment on lines +1302 to +1307
/// Convert a felt constant attribute into the equivalent SMT integer literal.
IntegerAttr toIntAttr(FeltConstantOp op) {
return IntegerAttr::get(
builder.getContext(), toAPSInt(toDynamicAPInt(op.getValue().getValue()))
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The docstring and the function argument are at odds with each other. Is the function meant to take an op or an attribute?

Comment on lines +1423 to +1445
/// Lower signed division or remainder over field elements via signed representatives.
Value emitSignedDivOrRem(Location loc, Value lhs, Value rhs, FeltType type, bool isDiv) {
Value signedLhs = emitSignedRepresentative(loc, lhs, type);
Value signedRhs = emitSignedRepresentative(loc, rhs, type);
Value quotient = emitTruncatingSignedDivision(loc, signedLhs, signedRhs);
if (isDiv) {
return emitCanonical(loc, quotient, type);
}
Value product =
builder.create<smt::IntMulOp>(loc, ValueRange {signedRhs, quotient}).getResult();
Value remainder = builder.create<smt::IntSubOp>(loc, signedLhs, product).getResult();
return emitCanonical(loc, remainder, type);
}

/// Lower signed integer division over field elements.
Value emitSignedIntDivValue(Location loc, Value lhs, Value rhs, FeltType type) {
return emitSignedDivOrRem(loc, lhs, rhs, type, /*isDiv=*/true);
}

/// Lower signed remainder over field elements.
Value emitSignedModValue(Location loc, Value lhs, Value rhs, FeltType type) {
return emitSignedDivOrRem(loc, lhs, rhs, type, /*isDiv=*/false);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

emitSignedDivOrRem doesn't seem to be used anywhere else so this looks over-complicated for no reason

Comment on lines +1614 to +1615
if (op == constrainFunc.getOperation() || isa<ReturnOp, scf::YieldOp>(op) ||
isa<constrain::EmitEqualityOp>(op) || isa<CallOp>(op)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
if (op == constrainFunc.getOperation() || isa<ReturnOp, scf::YieldOp>(op) ||
isa<constrain::EmitEqualityOp>(op) || isa<CallOp>(op)) {
if (op == constrainFunc.getOperation() || isa<ReturnOp, scf::YieldOp, constrain::EmitEqualityOp, CallOp>(op)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The organization in this file is confusing me

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Are we planning to support lowering the loop invariant ops in this PR?

@iangneal

Copy link
Copy Markdown
Contributor Author

The verif to smt pass needs some work. A cleaner approach is to let the different dialects define how to lower to smt, similar to how lowering to LLVM works in upstream MLIR. The pass' name hints that is going to lower only operations from the verif dialect, but it lowers everything that is meant to be lowered to smt. The pass may probably be better renamed to -llzk-to-smt.

Problem is we already have a lowering pass called that, but it's specifically for @Product aligned programs. We can rename that pass, perhaps, but that would require an LLEQ change. @raghav198

Otherwise I agree.

The code as it stands right now is a good prototype for getting the demo working for yesterday but that's about it.

Correct. I think there are some PRs that actually need to be split off from this one and merged separately or removed. For example, there's a scalarization workaround that I believe needs to be reverted once #565 is merged.

I'm just going to mark this as a draft until I'm done restructuring this, but I haven't had a chance to yet.

@iangneal iangneal marked this pull request as draft June 23, 2026 15:09
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.

3 participants