Skip to content

OpenFHE emitter: tensor.from_elements with a 0-d result fails to translate #3624

Open
gauthamajoy/heir
#1

Description

@gauthamajoy

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.cc

Output:

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/...:all
Executed 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

  1. j2kun commented on Nov 10, 2026

    @j2kun
    Collaborator

    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!

  2. gauthamajoy commented on Nov 11, 2026

    @gauthamajoy
    Author

    Closed accidentally by a merge into my fork’s main. Reopening; the fix is in #3631

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions