Disclaimer
the second and third patches produced with Claude Code, and a also use Claude Code to write some of this issue; I reproduced the failures and verified the full build and test suite locally.
The enable_yosys=0 configuration currently fails to build, and once it builds, three tests that need Yosys are not excluded from it.
There are three independent problems. All are below with repros and patches.
1. Missing includes in BooleanPipelineRegistration.cpp
bazel build --//:enable_yosys=0 //tools:heir-opt
lib/Pipelines/BooleanPipelineRegistration.cpp:160:17: error: no type named 'oneShotBufferize' in namespace 'mlir::heir'
lib/Pipelines/BooleanPipelineRegistration.cpp:179:28: error: use of undeclared identifier 'createRemoveUnusedMemRef'
lib/Pipelines/BooleanPipelineRegistration.cpp:201:14: error: use of undeclared identifier 'createRemoveUnusedMemRef'
The #else (no-Yosys) branch of mlirToCGGIPipeline uses oneShotBufferize (declared in lib/Pipelines/PipelineRegistration.h) and createRemoveUnusedMemRef (generated from lib/Transforms/UnusedMemRef/UnusedMemRef.h). Both includes were removed in 16e8134 ("e2e tests of debug.validate lowerings & OpenFHE emitter improvements", 2026-06-10), which looks like an include cleanup that only considered the Yosys-enabled preprocessor branch. The Bazel deps in lib/Pipelines/BUILD are already correct; only the #include lines are missing.
--- a/lib/Pipelines/BooleanPipelineRegistration.cpp
+++ b/lib/Pipelines/BooleanPipelineRegistration.cpp
@@
#include "lib/Dialect/Secret/Transforms/DistributeGeneric.h"
+#include "lib/Pipelines/PipelineRegistration.h"
#include "lib/Transforms/BooleanVectorizer/BooleanVectorizer.h"
@@
#include "lib/Transforms/TensorLinalgToAffineLoops/TensorLinalgToAffineLoops.h"
+#include "lib/Transforms/UnusedMemRef/UnusedMemRef.h"
2. heir-translate --emit-tfhe-rust-hl crashes on affine.yield with no operands
With the above applied, bazel build --//:enable_yosys=0 --build_tag_filters=-yosys //...:all fails in //tests/Examples/tfhe_rust_hl/cpu:add_round_key_test.heir_translate_rs:
heir-translate: llvm/include/llvm/ADT/ArrayRef.h:439:
T &llvm::MutableArrayRef<mlir::OpOperand>::operator[](size_t) const [T = mlir::OpOperand]:
Assertion `Index < this->size() && "Invalid index!"' failed.
Minimal repro (heir-translate --emit-tfhe-rust-hl):
module {
func.func @f(%arg0: !tfhe_rust.server_key, %arg1: memref<16x!tfhe_rust.eui8> {secret.secret}) -> memref<16x!tfhe_rust.eui8> {
%alloc = memref.alloc() : memref<16x!tfhe_rust.eui8>
affine.for %i = 0 to 16 {
%0 = memref.load %arg1[%i] : memref<16x!tfhe_rust.eui8>
memref.store %0, %alloc[%i] {lwe_annotation = "LWE"} : memref<16x!tfhe_rust.eui8>
}
return %alloc : memref<16x!tfhe_rust.eui8>
}
}
TfheRustHLEmitter::printOperation(affine::AffineYieldOp) guards on getNumResults(), but affine.yield is a terminator and never has results, so the guard is vacuous. It then unconditionally reads op->getOperand(0), which is out of range for a loop with no iter_args.
The sibling emitters are all fine: TfheRustBoolEmitter has the same vacuous guard but never dereferences an operand, OpenFhePkeEmitter iterates over getNumOperands(), and LattigoEmitter matches operands against the parent's iter_args.
This only shows up with Yosys disabled because the #else pipeline bufferizes to memref + affine.for, whereas the Yosys path stays on tensors and never reaches this emitter with an iter_args-free loop.
Patch below mirrors what printOperation(func::ReturnOp) already does in the same file; the tuple case matches the tuple accumulator that the affine.for emitter builds for multiple iter_args.
--- a/lib/Target/TfheRustHL/TfheRustHLEmitter.cpp
+++ b/lib/Target/TfheRustHL/TfheRustHLEmitter.cpp
@@ LogicalResult TfheRustHLEmitter::printOperation(affine::AffineYieldOp op) {
- if (op->getNumResults() != 0) {
- return op.emitOpError() << "AffineYieldOp has non-zero number of results";
- }
-
- os << variableNames->getNameForValue(op->getOperand(0)) << "\n";
+ // A loop with no iter_args yields nothing.
+ if (op->getNumOperands() == 0) {
+ return success();
+ }
+
+ if (op->getNumOperands() == 1) {
+ os << variableNames->getNameForValue(op->getOperand(0)) << "\n";
+ return success();
+ }
+
+ // Multiple iter_args are folded as a tuple.
+ os << "("
+ << commaSeparatedValues(
+ op->getOperands(),
+ [&](Value value) { return variableNames->getNameForValue(value); })
+ << ")\n";
return success();
}
3. Three Yosys-only lit tests are not excluded by --test_tag_filters=-yosys
With the two patches above, bazel build --//:enable_yosys=0 --build_tag_filters=-yosys //...:all succeeds, but bazel test with --test_tag_filters=-yosys still runs three tests that require Yosys:
//tests/Regression:issue_1086.mlir.test — its RUN line invokes --yosys-optimizer, which does not exist in this build (heir-opt: Unknown command line argument '--yosys-optimizer').
//tests/Examples/jaxite:add_one_lut3.mlir.test and :pmap_add_one_lut3.mlir.test — heir-opt --mlir-to-cggi --scheme-to-jaxite hits Unsupported cleartext bitwidth in jaxite, UNREACHABLE executed at lib/Dialect/CGGI/Conversions/CGGIToJaxite/CGGIToJaxite.cpp:51, because the no-Yosys --mlir-to-cggi does not produce the boolean form the Jaxite conversion expects.
In tests/Examples/jaxite/BUILD the e2e target already carries tags = ["yosys"], but the glob_lit_tests in the same file globs the same .mlir files with no tags_override, so the lit variant escapes the filter. There is no REQUIRES: yosys lit feature (tests/lit.cfg.py puts Yosys on PATH unconditionally), so the Bazel tag is the only mechanism available.
--- a/tests/Examples/jaxite/BUILD
+++ b/tests/Examples/jaxite/BUILD
@@ glob_lit_tests(
exclude = ["fully_connected.jaxite.mlir"],
+ tags_override = {
+ "add_one_lut3.mlir": ["yosys"],
+ "pmap_add_one_lut3.mlir": ["yosys"],
+ },
test_file_exts = ["mlir"],
)
--- a/tests/Regression/BUILD
+++ b/tests/Regression/BUILD
@@ tags_override = {
+ "issue_1086.mlir": ["yosys"],
"issue_2466.mlir": ["nofastbuild"],
},
Result
With all three patches:
bazel build --//:enable_yosys=0 --build_tag_filters=-yosys //...:all succeeds (10643 actions).
bazel test --//:enable_yosys=0 --build_tag_filters=-yosys --test_tag_filters=-yosys //...:all reports 991 of 991 tests passing.
The four tfhe_rust_hl e2e tests are included in that and pass, so the Rust emitted after patch 2 compiles and runs against tfhe-rs — not just "the emitter stops aborting".
One caveat on my numbers: I initially saw //tests/Examples/openfhe/ckks/batch_matmul:batch_matmul_test time out at 60s, but it passes in ~40s when not competing with other tests, so that was local contention at --jobs=6 rather than a real failure.
Disclaimer
the second and third patches produced with Claude Code, and a also use Claude Code to write some of this issue; I reproduced the failures and verified the full build and test suite locally.
The
enable_yosys=0configuration currently fails to build, and once it builds, three tests that need Yosys are not excluded from it.There are three independent problems. All are below with repros and patches.
1. Missing includes in
BooleanPipelineRegistration.cppThe
#else(no-Yosys) branch ofmlirToCGGIPipelineusesoneShotBufferize(declared inlib/Pipelines/PipelineRegistration.h) andcreateRemoveUnusedMemRef(generated fromlib/Transforms/UnusedMemRef/UnusedMemRef.h). Both includes were removed in 16e8134 ("e2e tests of debug.validate lowerings & OpenFHE emitter improvements", 2026-06-10), which looks like an include cleanup that only considered the Yosys-enabled preprocessor branch. The Bazel deps inlib/Pipelines/BUILDare already correct; only the#includelines are missing.2.
heir-translate --emit-tfhe-rust-hlcrashes onaffine.yieldwith no operandsWith the above applied,
bazel build --//:enable_yosys=0 --build_tag_filters=-yosys //...:allfails in//tests/Examples/tfhe_rust_hl/cpu:add_round_key_test.heir_translate_rs:Minimal repro (
heir-translate --emit-tfhe-rust-hl):TfheRustHLEmitter::printOperation(affine::AffineYieldOp)guards ongetNumResults(), butaffine.yieldis a terminator and never has results, so the guard is vacuous. It then unconditionally readsop->getOperand(0), which is out of range for a loop with noiter_args.The sibling emitters are all fine:
TfheRustBoolEmitterhas the same vacuous guard but never dereferences an operand,OpenFhePkeEmitteriterates overgetNumOperands(), andLattigoEmittermatches operands against the parent'siter_args.This only shows up with Yosys disabled because the
#elsepipeline bufferizes to memref +affine.for, whereas the Yosys path stays on tensors and never reaches this emitter with aniter_args-free loop.Patch below mirrors what
printOperation(func::ReturnOp)already does in the same file; the tuple case matches the tuple accumulator that theaffine.foremitter builds for multipleiter_args.3. Three Yosys-only lit tests are not excluded by
--test_tag_filters=-yosysWith the two patches above,
bazel build --//:enable_yosys=0 --build_tag_filters=-yosys //...:allsucceeds, butbazel testwith--test_tag_filters=-yosysstill runs three tests that require Yosys://tests/Regression:issue_1086.mlir.test— its RUN line invokes--yosys-optimizer, which does not exist in this build (heir-opt: Unknown command line argument '--yosys-optimizer').//tests/Examples/jaxite:add_one_lut3.mlir.testand:pmap_add_one_lut3.mlir.test—heir-opt --mlir-to-cggi --scheme-to-jaxitehitsUnsupported cleartext bitwidth in jaxite,UNREACHABLE executed at lib/Dialect/CGGI/Conversions/CGGIToJaxite/CGGIToJaxite.cpp:51, because the no-Yosys--mlir-to-cggidoes not produce the boolean form the Jaxite conversion expects.In
tests/Examples/jaxite/BUILDthe e2e target already carriestags = ["yosys"], but theglob_lit_testsin the same file globs the same.mlirfiles with notags_override, so the lit variant escapes the filter. There is noREQUIRES: yosyslit feature (tests/lit.cfg.pyputs Yosys onPATHunconditionally), so the Bazel tag is the only mechanism available.Result
With all three patches:
bazel build --//:enable_yosys=0 --build_tag_filters=-yosys //...:allsucceeds (10643 actions).bazel test --//:enable_yosys=0 --build_tag_filters=-yosys --test_tag_filters=-yosys //...:allreports 991 of 991 tests passing.The four
tfhe_rust_hle2e tests are included in that and pass, so the Rust emitted after patch 2 compiles and runs against tfhe-rs — not just "the emitter stops aborting".One caveat on my numbers: I initially saw
//tests/Examples/openfhe/ckks/batch_matmul:batch_matmul_testtime out at 60s, but it passes in ~40s when not competing with other tests, so that was local contention at--jobs=6rather than a real failure.