Repository navigation
Description
heir-translate --emit-openfhe-pke fails on functions returning a 0-d tensor because printOperation(tensor::FromElementsOp) rejects any result whose rank isn't 1.
Version: Built from source at 0235c9d1edc8b35b6569ab52fca3d6319833e694 (nightly-2026.11.06). I first encountered this on an older build.
Environment: Ubuntu 24.04.5 LTS (x86_64), Bazel 8.4.0.
Reproducer
zero_d.mlir:
func.func @f(%arg0: tensor<f32> {secret.secret}) -> tensor<f32> {
%0 = arith.addf %arg0, %arg0 : tensor<f32>
return %0 : tensor<f32>
}heir-opt zero_d.mlir \
--annotate-module="backend=openfhe scheme=ckks" \
--mlir-to-ckks='min-slot-count=1024' \
--scheme-to-openfhe='entry-function=f' \
-o zero_d.openfhe.mlir
heir-translate zero_d.openfhe.mlir --emit-openfhe-pke -o zero_d.ccOutput:
zero_d.openfhe.mlir:33:22: error: Failed to translate op tensor.from_elements
%from_elements = tensor.from_elements %extracted_0 : tensor<f32>
The lowered IR contains a rank-0 tensor.from_elements in the generated client-side decrypt helper, f__decrypt__result0:
%extracted_0 = tensor.extract %0[%c0, %c0] : tensor<1x1024xf32>
%from_elements = tensor.from_elements %extracted_0 : tensor<f32>
return %from_elements : tensor<f32>Cause
OpenFhePkeEmitter.cpp, lines 1695–1697
if (result.getType().getRank() != 1) {
return failure();
}This returns failure() without an operation-specific diagnostic, so the translator reports a generic Failed to translate op tensor.from_elements error, followed by errors for the enclosing function and module.
The emitter flattens all tensors to std::vector (convertType in OpenFheUtils.cpp; see also the comments in the ExtractOp and SplatOp printers). #2924 added rank-0 handling to the tensor.extract and tensor.insert printers, which tests/Emitter/Openfhe/emit_0d_tensor.mlir tests, but from_elements seems to have been missed.
For a rank-0 result, the existing initializer emits a size-1 vector, which matches how extract/insert index it ([0]).
With the check removed locally, the generated zero_d.cc compiles against OpenFHE 1.6.1 and decrypts correctly (3.21 + 3.21 = 6.42). The existing emitter test suite also passes:
bazel test -c dbg --test_output=errors //tests/Emitter/...:allExecuted 68 out of 68 tests: 68 tests pass.
Proposed fix
Allow ranks 0 and 1. Keep rejecting rank ≥ 2, but with an emitOpError and a TODO(#1703), leaving multidimensional support to that issue.
Add a rank-0 from_elements case to emit_0d_tensor.mlir, plus a negative test for rank ≥ 2.
If this approach sounds right, I'd like to send the PR myself. I'm new to HEIR, so I'm happy to adjust it and join office hours first, as the contribution guidelines ask.
Context
I hit this while trying to remove a plaintext mask multiply from a CKKS dot-product kernel. When the kernel returns an extracted scalar, convert-to-ciphertext-semantics adds a mask multiply after the rotate-and-sum, which costs a level (depth 2 instead of 1).
Returning the 0-d tensor instead gives depth-1 IR with no mask in heir-opt's output, but translation then fails here.
AI use
The failure first appeared during an exploratory session with an AI coding agent. I did the root-cause analysis, patch, and testing myself, using ChatGPT as a guide, and used Claude to copy-edit this issue.
Activity
j2kun commentedon Nov 10, 2026
I think this is fair, though I will say the OpenFHE backend is slowly becoming deprecated in favor of GPU-enabled backends. Feel free to send a PR.
Hope to see you in office hours!
gauthamajoy commentedon Nov 11, 2026
Closed accidentally by a merge into my fork’s main. Reopening; the fix is in #3631