This section is where the HEIR documentation lives.
This is the multi-page printable view of this section. Click here to print.
Documentation
- 1: Getting Started
- 2: Contributing to HEIR
- 3: Development
- 4: Tutorials and Talks
- 5: Design
- 5.1: Data-oblivious Transformations
- 5.2: Secret
- 5.3: SIMD Optimizations
- 5.4: Optimizing relinearization
- 6: Pipelines
- 7: Dialects
- 7.1: BGV
- 7.2: CGGI
- 7.3: CKKS
- 7.4: Comb
- 7.5: Jaxite
- 7.6: Lattigo
- 7.7: LWE
- 7.8: Mgmt
- 7.9: ModArith
- 7.10: Openfhe
- 7.11: Polynomial
- 7.12: Random
- 7.13: RNS
- 7.14: Secret
- 7.15: TensorExt
- 7.16: TfheRust
- 7.17: TfheRustBool
- 8: Passes
1 - Getting Started
Getting HEIR
Using a pre-built nightly binary
HEIR releases a nightly binary for Linux x86-64. This is intended for testing compiler passes and not for production use.
wget https://github.com/google/heir/releases/download/nightly/heir-opt
chmod +x heir-opt
./heir-opt --help
Then you can run the examples below, replacing bazel run //tools:heir-opt --
with ./heir-opt
. HEIR also publishes heir-translate
and heir-lsp
in the
same way.
Running the nightly binary from a notebook
We publish an ipython extension heir-play that can be used in Jupyter or Colab notebooks.
%pip install heir-play
%load_ext heir_play
This will download the nightly release binaries to the system the notebook server is running on, then:
%%heir_opt --flag1 --flag2
# MLIR code here
Runs heir-opt
with the given command line flags on the MLIR code in the cell.
A cell magic is also available for heir-translate
as %%heir_translate
.
Building From Source
Prerequisites
- Git
- A C++ compiler and linker (clang and lld are recommended).
- Bazel via bazelisk, or version
>=5.5
- See Development for additional prerequisites for active development
Detailed Instructions
The first two requirements are frequently pre-installed or can be installed via the system package manager. For example, on Ubuntu, these can be installed withsudo apt-get update && sudo apt-get install clang lld
You can download the latest Bazelisk release, e.g., for linux-amd64 (see the Bazelisk Release Page for a list of available binaries):
wget -c https://github.com/bazelbuild/bazelisk/releases/latest/download/bazelisk-linux-amd64
mv bazelisk-linux-amd64 bazel
chmod +x bazel
You will then likely want to move bazel
to a location on your PATH, or add its
location to your PATH, e.g.:
mkdir ~/bin
echo 'export PATH=$PATH:~/bin' >> ~/.bashrc
mv bazel ~/bin/bazel
Note that on linux systems, your OS user must not be root
as bazel might
refuse to work if run as root.
Clone and build the project
You can clone and build HEIR from the terminal as described below. Please see Development for information on IDE configuration if you want to use an IDE to build HEIR.
git clone git@github.com:google/heir.git && cd heir
bazel build @heir//tools:heir-opt
Some passes in this repository require Yosys as a dependency
(--yosys-optimizer
). If you would like to skip Yosys and ABC compilation to
speed up builds, use the following build setting:
bazel build --//:enable_yosys=0 @heir//tools:heir-opt
Optional: Run the tests
bazel test @heir//...
Like above, run the following to skip tests that depend on Yosys:
bazel test --//:enable_yosys=0 --test_tag_filters=-yosys @heir//...
Using HEIR
Run the dot-product
example
The dot-product
program computes the dot product of two length-8 vectors of
16-bit integers (i16
in MLIR parlance). This example will showcase the OpenFHE
backend by manually calling the relevant compiler passes and setting up a C++
harness to call into the HEIR-generated functions.
Note: other backends are similar, but the different backends are in varying stages of development.
The input program is in tests/Examples/openfhe/dot_product_8.mlir
. Support for
standard input languages like C
and C++
are currently experimental at best,
but eventually we would use an MLIR-based tool to convert an input language to
MLIR like in that file. The program is below:
func.func @dot_product(%arg0: tensor<8xi16> {secret.secret}, %arg1: tensor<8xi16> {secret.secret}) -> i16 {
%c0 = arith.constant 0 : index
%c0_si16 = arith.constant 0 : i16
%0 = affine.for %arg2 = 0 to 8 iter_args(%iter = %c0_si16) -> (i16) {
%1 = tensor.extract %arg0[%arg2] : tensor<8xi16>
%2 = tensor.extract %arg1[%arg2] : tensor<8xi16>
%3 = arith.muli %1, %2 : i16
%4 = arith.addi %iter, %3 : i16
affine.yield %4 : i16
}
return %0 : i16
}
For an introduction to MLIR syntax, see the official docs or this blog post.
Now we run the heir-opt
command to optimize and compile the program.
bazel run //tools:heir-opt -- \
--mlir-to-openfhe-bgv='entry-function=dot_product ciphertext-degree=8' \
$PWD/tests/Examples/openfhe/dot_product_8.mlir > output.mlir
This produces a file in the openfhe
exit dialect (part of HEIR).
!Z1005037682689_i64_ = !mod_arith.int<1005037682689 : i64>
!Z1032955396097_i64_ = !mod_arith.int<1032955396097 : i64>
!Z1095233372161_i64_ = !mod_arith.int<1095233372161 : i64>
#polynomial_evaluation_encoding = #lwe.polynomial_evaluation_encoding<cleartext_start = 16, cleartext_bitwidth = 16>
!rns_L0_ = !rns.rns<!Z1095233372161_i64_>
!rns_L1_ = !rns.rns<!Z1095233372161_i64_, !Z1032955396097_i64_>
!rns_L2_ = !rns.rns<!Z1095233372161_i64_, !Z1032955396097_i64_, !Z1005037682689_i64_>
#ring_rns_L0_1_x8_ = #polynomial.ring<coefficientType = !rns_L0_, polynomialModulus = <1 + x**8>>
#ring_rns_L1_1_x8_ = #polynomial.ring<coefficientType = !rns_L1_, polynomialModulus = <1 + x**8>>
#ring_rns_L2_1_x8_ = #polynomial.ring<coefficientType = !rns_L2_, polynomialModulus = <1 + x**8>>
!rlwe_pt_L0_ = !lwe.rlwe_plaintext<encoding = #polynomial_evaluation_encoding, ring = #ring_rns_L0_1_x8_, underlying_type = i16>
!rlwe_pt_L1_ = !lwe.rlwe_plaintext<encoding = #polynomial_evaluation_encoding, ring = #ring_rns_L1_1_x8_, underlying_type = tensor<8xi16>>
!rlwe_pt_L2_ = !lwe.rlwe_plaintext<encoding = #polynomial_evaluation_encoding, ring = #ring_rns_L2_1_x8_, underlying_type = tensor<8xi16>>
#rlwe_params_L0_ = #lwe.rlwe_params<ring = #ring_rns_L0_1_x8_>
#rlwe_params_L1_ = #lwe.rlwe_params<ring = #ring_rns_L1_1_x8_>
#rlwe_params_L2_ = #lwe.rlwe_params<ring = #ring_rns_L2_1_x8_>
#rlwe_params_L2_D3_ = #lwe.rlwe_params<dimension = 3, ring = #ring_rns_L2_1_x8_>
!rlwe_ct_L0_ = !lwe.rlwe_ciphertext<encoding = #polynomial_evaluation_encoding, rlwe_params = #rlwe_params_L0_, underlying_type = i16>
!rlwe_ct_L1_ = !lwe.rlwe_ciphertext<encoding = #polynomial_evaluation_encoding, rlwe_params = #rlwe_params_L1_, underlying_type = tensor<8xi16>>
!rlwe_ct_L1_1 = !lwe.rlwe_ciphertext<encoding = #polynomial_evaluation_encoding, rlwe_params = #rlwe_params_L1_, underlying_type = i16>
!rlwe_ct_L2_ = !lwe.rlwe_ciphertext<encoding = #polynomial_evaluation_encoding, rlwe_params = #rlwe_params_L2_, underlying_type = tensor<8xi16>>
!rlwe_ct_L2_D3_ = !lwe.rlwe_ciphertext<encoding = #polynomial_evaluation_encoding, rlwe_params = #rlwe_params_L2_D3_, underlying_type = tensor<8xi16>>
module {
func.func @dot_product(%arg0: !openfhe.crypto_context, %arg1: !rlwe_ct_L2_, %arg2: !rlwe_ct_L2_) -> !rlwe_ct_L0_ {
%cst = arith.constant dense<[0, 0, 0, 0, 0, 0, 0, 1]> : tensor<8xi64>
%0 = openfhe.mul_no_relin %arg0, %arg1, %arg2 : (!openfhe.crypto_context, !rlwe_ct_L2_, !rlwe_ct_L2_) -> !rlwe_ct_L2_D3_
%1 = openfhe.relin %arg0, %0 : (!openfhe.crypto_context, !rlwe_ct_L2_D3_) -> !rlwe_ct_L2_
%2 = openfhe.rot %arg0, %1 {index = 4 : index} : (!openfhe.crypto_context, !rlwe_ct_L2_) -> !rlwe_ct_L2_
%3 = openfhe.add %arg0, %1, %2 : (!openfhe.crypto_context, !rlwe_ct_L2_, !rlwe_ct_L2_) -> !rlwe_ct_L2_
%4 = openfhe.rot %arg0, %3 {index = 2 : index} : (!openfhe.crypto_context, !rlwe_ct_L2_) -> !rlwe_ct_L2_
%5 = openfhe.add %arg0, %3, %4 : (!openfhe.crypto_context, !rlwe_ct_L2_, !rlwe_ct_L2_) -> !rlwe_ct_L2_
%6 = openfhe.rot %arg0, %5 {index = 1 : index} : (!openfhe.crypto_context, !rlwe_ct_L2_) -> !rlwe_ct_L2_
%7 = openfhe.add %arg0, %5, %6 : (!openfhe.crypto_context, !rlwe_ct_L2_, !rlwe_ct_L2_) -> !rlwe_ct_L2_
%8 = openfhe.mod_reduce %arg0, %7 : (!openfhe.crypto_context, !rlwe_ct_L2_) -> !rlwe_ct_L1_
%9 = openfhe.make_packed_plaintext %arg0, %cst : (!openfhe.crypto_context, tensor<8xi64>) -> !rlwe_pt_L1_
%10 = openfhe.mul_plain %arg0, %8, %9 : (!openfhe.crypto_context, !rlwe_ct_L1_, !rlwe_pt_L1_) -> !rlwe_ct_L1_
%11 = openfhe.rot %arg0, %10 {index = 7 : index} : (!openfhe.crypto_context, !rlwe_ct_L1_) -> !rlwe_ct_L1_
%12 = lwe.reinterpret_underlying_type %11 : !rlwe_ct_L1_ to !rlwe_ct_L1_1
%13 = openfhe.mod_reduce %arg0, %12 : (!openfhe.crypto_context, !rlwe_ct_L1_1) -> !rlwe_ct_L0_
return %13 : !rlwe_ct_L0_
}
func.func @dot_product__encrypt__arg0(%arg0: !openfhe.crypto_context, %arg1: tensor<8xi16>, %arg2: !openfhe.public_key) -> !rlwe_ct_L2_ {
...
}
func.func @dot_product__encrypt__arg1(%arg0: !openfhe.crypto_context, %arg1: tensor<8xi16>, %arg2: !openfhe.public_key) -> !rlwe_ct_L2_ {
...
}
func.func @dot_product__decrypt__result0(%arg0: !openfhe.crypto_context, %arg1: !rlwe_ct_L0_, %arg2: !openfhe.private_key) -> i16 {
...
}
func.func @dot_product__generate_crypto_context() -> !openfhe.crypto_context {
...
}
func.func @dot_product__configure_crypto_context(%arg0: !openfhe.crypto_context, %arg1: !openfhe.private_key) -> !openfhe.crypto_context {
...
}
}
Next, we use the heir-translate
tool to run code generation for the OpenFHE
pke
API.
bazel run //tools:heir-translate -- --emit-openfhe-pke-header $PWD/output.mlir > heir_output.h
bazel run //tools:heir-translate -- --emit-openfhe-pke $PWD/output.mlir > heir_output.cpp
The results:
// heir_output.h
#include "src/pke/include/openfhe.h" // from @openfhe
using namespace lbcrypto;
using CiphertextT = ConstCiphertext<DCRTPoly>;
using CCParamsT = CCParams<CryptoContextBGVRNS>;
using CryptoContextT = CryptoContext<DCRTPoly>;
using EvalKeyT = EvalKey<DCRTPoly>;
using PlaintextT = Plaintext;
using PrivateKeyT = PrivateKey<DCRTPoly>;
using PublicKeyT = PublicKey<DCRTPoly>;
CiphertextT dot_product(CryptoContextT v0, CiphertextT v1, CiphertextT v2);
CiphertextT dot_product__encrypt__arg0(CryptoContextT v18, std::vector<int16_t> v19, PublicKeyT v20);
CiphertextT dot_product__encrypt__arg1(CryptoContextT v24, std::vector<int16_t> v25, PublicKeyT v26);
int16_t dot_product__decrypt__result0(CryptoContextT v30, CiphertextT v31, PrivateKeyT v32);
CryptoContextT dot_product__generate_crypto_context();
CryptoContextT dot_product__configure_crypto_context(CryptoContextT v37, PrivateKeyT v38);
// heir_output.cpp
#include "src/pke/include/openfhe.h" // from @openfhe
using namespace lbcrypto;
using CiphertextT = ConstCiphertext<DCRTPoly>;
using CryptoContextT = CryptoContext<DCRTPoly>;
using EvalKeyT = EvalKey<DCRTPoly>;
using PlaintextT = Plaintext;
using PrivateKeyT = PrivateKey<DCRTPoly>;
using PublicKeyT = PublicKey<DCRTPoly>;
CiphertextT dot_product(CryptoContextT v0, CiphertextT v1, CiphertextT v2) {
std::vector<int64_t> v3 = {0, 0, 0, 0, 0, 0, 0, 1};
const auto& v4 = v0->EvalMultNoRelin(v1, v2);
const auto& v5 = v0->Relinearize(v4);
const auto& v6 = v0->EvalRotate(v5, 4);
const auto& v7 = v0->EvalAdd(v5, v6);
const auto& v8 = v0->EvalRotate(v7, 2);
const auto& v9 = v0->EvalAdd(v7, v8);
const auto& v10 = v0->EvalRotate(v9, 1);
const auto& v11 = v0->EvalAdd(v9, v10);
const auto& v12 = v0->ModReduce(v11);
auto v3_filled_n = v0->GetCryptoParameters()->GetElementParams()->GetRingDimension() / 2;
auto v3_filled = v3;
v3_filled.clear();
v3_filled.reserve(v3_filled_n);
for (auto i = 0; i < v3_filled_n; ++i) {
v3_filled.push_back(v3[i % v3.size()]);
}
const auto& v13 = v0->MakePackedPlaintext(v3_filled);
const auto& v14 = v0->EvalMult(v12, v13);
const auto& v15 = v0->EvalRotate(v14, 7);
const auto& v16 = v15;
const auto& v17 = v0->ModReduce(v16);
return v17;
}
CiphertextT dot_product__encrypt__arg0(CryptoContextT v24, std::vector<int16_t> v25, PublicKeyT v26) {
...
}
CiphertextT dot_product__encrypt__arg1(CryptoContextT v29, std::vector<int16_t> v30, PublicKeyT v31) {
...
}
int16_t dot_product__decrypt__result0(CryptoContextT v34, CiphertextT v35, PrivateKeyT v36) {
...
}
CryptoContextT dot_product__generate_crypto_context() {
...
}
CryptoContextT dot_product__configure_crypto_context(CryptoContextT v37, PrivateKeyT v38) {
...
}
At this point we can compile the program as we would a normal OpenFHE program. Note that the above two files just contain the compiled function and encryption/decryption helpers, and does not include any code that provides specific inputs or calls these functions.
Next we’ll create a harness that provides sample inputs, encrypts them, runs the compiled function, and decrypts the result. Once you have the generated header and cpp files, you can do this with any build system. We will use bazel for consistency.
Create a file called BUILD
in the same directory as the header and cpp files
above, with the following contents:
# A library build target that encapsulates the HEIR-generated code.
cc_library(
name = "dot_product_codegen",
srcs = ["heir_output.cpp"],
hdrs = ["heir_output.h"],
deps = ["@openfhe//:pke"],
)
# An executable build target that contains your main function and links
# against the above.
cc_binary(
name = "dot_product_main",
srcs = ["dot_product_main.cpp"],
deps = [
":dot_product_codegen",
"@openfhe//:pke",
"@openfhe//:core",
],
)
Where dot_product_main.cpp
is a new file containing
#include <cstdint>
#include <vector>
#include "src/pke/include/openfhe.h" // from @openfhe
#include "heir_output.h"
int main(int argc, char *argv[]) {
CryptoContext<DCRTPoly> cryptoContext = dot_product__generate_crypto_context();
KeyPair<DCRTPoly> keyPair;
keyPair = cryptoContext->KeyGen();
cryptoContext = dot_product__configure_crypto_context(cryptoContext, keyPair.secretKey);
std::vector<int16_t> arg0 = {1, 2, 3, 4, 5, 6, 7, 8};
std::vector<int16_t> arg1 = {2, 3, 4, 5, 6, 7, 8, 9};
int64_t expected = 240;
auto arg0Encrypted =
dot_product__encrypt__arg0(cryptoContext, arg0, keyPair.publicKey);
auto arg1Encrypted =
dot_product__encrypt__arg1(cryptoContext, arg1, keyPair.publicKey);
auto outputEncrypted =
dot_product(cryptoContext, arg0Encrypted, arg1Encrypted);
auto actual = dot_product__decrypt__result0(cryptoContext, outputEncrypted,
keyPair.secretKey);
std::cout << "Expected: " << expected << "\n";
std::cout << "Actual: " << actual << "\n";
return 0;
}
Then run and show the results:
$ bazel run dot_product_main
Expected: 240
Actual: 240
Optional: Run a custom heir-opt
pipeline
HEIR comes with two central binaries, heir-opt
for running optimization passes
and dialect conversions, and heir-translate
for backend code generation. To
see the list of available passes in each one, run the binary with --help
:
bazel run //tools:heir-opt -- --help
bazel run //tools:heir-translate -- --help
Once you’ve chosen a pass or --pass-pipeline
to run, execute it on the desired
file. For example, you can run a test file through heir-opt
to see its output.
Note that when the binary is run via bazel
, you must pass absolute paths to
input files. You can also access the underlying binary at
bazel-bin/tools/heir-opt
, provided it has already been built.
bazel run //tools:heir-opt -- \
--secret-to-cggi -cse \
$PWD/tests/Dialect/Secret/Conversions/secret_to_cggi/add_one.mlir
To convert an existing lit test to a bazel run
command for manual tweaking and
introspection (e.g., adding --debug
or --mlir-print-ir-after-all
to see how
he IR changes with each pass), use python scripts/lit_to_bazel.py
.
# after pip installing requirements-dev.txt
python scripts/lit_to_bazel.py tests/simd/box_blur_64x64.mlir
Which outputs
bazel run --noallow_analysis_cache_discard //tools:heir-opt -- \
--secretize --wrap-generic --canonicalize --cse --full-loop-unroll \
--insert-rotate --cse --canonicalize --collapse-insertion-chains \
--canonicalize --cse /path/to/heir/tests/simd/box_blur_64x64.mlir
2 - Contributing to HEIR
There are several ways to contribute to HEIR, including:
- Discussing high-level designs and questions on HEIR’s discussions page
- Improving or expanding HEIR’s documentation
- Contributing to HEIR’s code-base
- Discuss project direction at HEIR’s Working Group meetings
Ways to contribute
We welcome pull requests, and have tagged issues for newcomers:
- Good first issue
- Contributions welcome
- Research synthesis: determine what parts of recent FHE research papers can or should be ported to HEIR.
For new proposals, please open a GitHub issue or start a discussion for feedback.
Contributing code to HEIR
The following steps should look familiar to typical workflows for pull request contributions. Feel free to consult GitHub Help if you need more information using pull requests. HEIR-specific processes begin at the pull request review stage.
Setup
Fork the HEIR repository by clicking the Fork button on the repository page. This creates a copy of the HEIR repository on your own GitHub account, where you can make changes.
Setting up git to work with fork and upstream remotes.
If you have cloned your fork, you will want to add the HEIR repository as an upstream remote:git remote add upstream https://www.github.com/google/heir
Alternatively, if you have cloned the main HEIR repo, you can add your fork as a remote like this:
git remote rename origin upstream git remote add origin https://www.github.com/<USERNAME>/heir
Either way, you will want to create a development branch for your change:
git checkout -b name-of-change
In the remainder of this document, we will assume
origin
is your fork, andupstream
is the main HEIR repo.See Development for information on installing developer dependencies, building and running tests, and adding new dialects or passes.
Sign the Contributor License Agreement (CLA). If you are working on HEIR as part of your employment, you might have to instead sign a Corporate CLA. See more here.
Preparing a pull request
Sync your changes against the upstream HEIR repository, i.e., make sure your contributions are (re)based of the most recent
upstream/main
commit.Check HEIR’s lint and style checks by running the following from the top of the repository:
pre-commit run --all
Make sure tests are passing with the following:
bazel test @heir//...
Once you are ready with your change, create a commit, e.g.:
git add change.cpp git commit -m "Detailed commit message" git push --set-upstream origin name-of-change
Pull request review flow
- New PR:
- When a new PR is submitted, it is inspected for quality requirements, such as the CLA requirement, and a sufficient PR description.
- If the PR passes checks, we assign a reviewer. If not, we request additional changes to ensure the PR passes CI checks.
- Review
- A reviewer will check the PR and potentially request additional changes.
- If a change is needed, the contributor is requested to make a suggested change. Please make changes with additional commits to your PR, to ensure that the reviewer can easily see the diff.
- If all looks good, the reviewer will approve the PR.
- This cycle repeats itself until the PR is approved.
- Approved
- At this stage, you must squash your commits into a single commit.
- Once the PR is approved, a GitHub workflow will
check
your PR for multiple commits. You may use the
git rebase -i
to squash the commits. Pull requests must consist of a single git commit before merging.
- Pull Ready
- Once the PR is squashed into a single git commit, a maintainer will apply the
pull ready
label. - This initiates the internal code migration and presubmits.
- After the internal process is finished, the commit will be added to
main
and the PR closed as merged by that commit.
Internal review details
This diagram summarizes the GitHub/Google code synchronization process. This is largely automated by a Google-owned system called Copybara, the configuration for which is Google-internal. This system treats the Google-internal version of HEIR as the source of truth, and applies specified transformation rules to copy internal changes to GitHub and integrate external PRs internally.
Notable aspects:
- The final merged code may differ slightly from a PR. The changes are mainly to support stricter internal requirements for BUILD files that we cannot reproduce externally due to minor differences between Google’s internal build systems and bazel that we don’t know how to align. Sometimes they will also include additional code quality fixes suggested by internal static analyzers that do not exist outside of Google.
- Due to the above, signed commits with internal modifications will not maintain valid signatures after merging, which labels the commit with a warning.
- You will see various actions taken on GitHub that include
copybara
in the name, such as changes that originate from Google engineers doing various approved migrations (e.g., migrating HEIR to support changes in MLIR or abseil).
Why bother with Copybara?
tl;dr: Automatic syncing with upstream MLIR and associated code migration.
Until HEIR has a formal governance structure in place, Google engineers—specifically Asra Ali, Shruthi Gorantala, and Jeremy Kun—are the codebase stewards. Because the project is young and the team is small, we want to reduce our workload. One important aspect of that is keeping up to date with the upstream MLIR project and incorporating bug fixes and new features into HEIR. Google also wishes to stay up to date with MLIR and LLVM, and so it has tooling devoted to integrating new MLIR changes into Google’s monorepo every few hours. As part of that rotation, a set of approved internal projects that depend on MLIR (like TensorFlow) are patched to support breaking changes in MLIR. HEIR is one of those approved projects.
As shown in the previous section, the cost of this is that no change can go into HEIR without at least two Googlers approving it, and the project is held to a specific set of code quality standards, namely Google’s. We acknowledge these quirks, and look forward to the day when HEIR is useful enough and important enough that we can revisit this governance structure with the community.
3 - Development
IDE Configuration (VS Code)
While a wide variety of IDEs and editors can be used for HEIR development, we currently only provide support for VSCode.
Setup
For the best experience, we recommend following these steps:
Install and rename Buildifier:
You can download the latest Buildifier release, e.g., for linux-amd64 (see the Bazelisk Release Page for a list of available binaries):
wget -c https://github.com/bazelbuild/buildtools/releases/latest/download/buildifier-linux-amd64 mv buildifier-linux-amd64 buildifier chmod +x buildifier
Just as with bazel, you will want to move this somewhere on your PATH, e.g.:
mkdir ~/bin echo 'export PATH=$PATH:~/bin' >> ~/.bashrc mv buildifier ~/bin/buildifier
VS Code should automatically detect buildifier. If this is not successful, you can manually set the “Buildifier Executable” setting for the Bazel extension (
bazel.buildifierExecutable
).Disable the C/C++ (aka ‘cpptools’) extension (either completely, or in the current workspace).
Add the following snippet to your VS Code user settings found in .vscode/settings.json to enable autocomplete based on the compile_commands.json file.
"clangd.arguments": [ "--compile-commands-dir=${workspaceFolder}/", "--completion-style=detailed", "--query-driver=**" ],
To generate the
compile_commands.json
file, runbazel run @hedron_compile_commands//:refresh_all
This will need to be regenerated every time you want tooling to see new
BUILD
file changes.If you encounter errors like
*.h.inc
not found, or syntax errors inside these files, you may need to build those targets and then re-run therefresh_all
command above.It might be necesssary to add the path to your buildifier to VSCode, though it should be auto-detected.
- Open the heir folder in VSCode
- Go to ‘Settings’ and set it on the ‘Workspace’
- Search for “Bazel Buildifier Executable”
- Once you find it, write
[home-directory]/bin/buildifier
for your specific [home-directory].
Building, Testing, Running and Debugging with VSCode
Building
- Open the “Explorer” (File Overview) in the left panel.
- Find “Bazel Build Targets” towards the bottom of the “Explorer” panel and click the dropdown button.
- Unfold the heir folder
- Right-click on “//tools” and click the “Build Package Recursively” option
Testing
- Open the “Explorer” (File Overview) in the left panel.
- Find “Bazel Build Targets” towards the bottom of the “Explorer” panel and click the dropdown button.
- Unfold the heir folder
- Right-click on “//test” and click the “Test Package Recursively” option
Running and Debugging
Create a
launch.json
file in the.vscode
folder, changing the"name"
and"args"
as required:{ "version": "0.2.0", "configurations": [ { "name": "Debug Secret->BGV", "preLaunchTask": "build", "type": "lldb", "request": "launch", "program": "${workspaceFolder}/bazel-bin/tools/heir-opt", "args": [ "--secret-to-bgv", "--debug", "${workspaceFolder}/tests/secret_to_bgv/ops.mlir" ], "relativePathBase": "${workspaceFolder}", "sourceMap": { "proc/self/cwd": "${workspaceFolder}", "/proc/self/cwd": "${workspaceFolder}" } }, ] }
You can add as many different configurations as necessary.
Add Breakpoints to your program as desired.
Open the Run/Debug panel on the left, select the desired configuration and run/debug it.
- Note that you might have to hit “Enter” to proceed past the Bazel build. It might take several seconds between hitting “Enter” and the debug terminal opening.
Tips for working with Bazel
Avoiding rebuilds
Bazel is notoriously fickle when it comes to deciding whether a full rebuild is necessary, which is bad for HEIR because rebuilding LLVM from scratch takes 15 minutes or more.
The main things that cause a rebuild are:
- A change to the command-line flags passed to bazel, e.g.,
-c opt
vs-c dbg
for optimization level and debug symbols. - A change to the
.bazelrc
that implicitly causes a flag change. Note HEIR has its own project-specific.bazelrc
in the root directory. - A change to relevant command-line variables, such as
PATH
, which is avoided by theincompatible_strict_action_env
flag. Note activating a python virtualenv triggers aPATH
change.
Bazel compilation flags are set by default in the project root’s .bazelrc
in
such a way as to avoid rebuilds during development as much as possible. This
includes setting -c dbg
and --incompatible_strict_action_env
.
Pointing HEIR to a local clone of llvm-project
Occasionally changes in HEIR will need to be made in tandem with upstream
changes in MLIR. In particular, we occasionally find upstream bugs that only
occur with HEIR passes, and we are the primary owners/users of the upstream
polynomial
dialect.
To tell bazel
to use a local clone of llvm-project
instead of a pinned
commit hash, replace bazel/import_llvm.bzl
with the following file:
cat > bazel/import_llvm.bzl << EOF
"""Provides the repository macro to import LLVM."""
def import_llvm(name):
"""Imports LLVM."""
native.new_local_repository(
name = name,
# this BUILD file is intentionally empty, because the LLVM project
# internally contains a set of bazel BUILD files overlaying the project.
build_file_content = "# empty",
path = "/path/to/llvm-project",
)
EOF
The next bazel build
will require a full rebuild if the checked-out LLVM
commit differs from the pinned commit hash in bazel/import_llvm.bzl
.
Note that you cannot reuse the LLVM CMake build artifacts in the bazel build. Based on what you’re trying to do, this may require some extra steps.
- If you just want to run existing MLIR and HEIR tests against local
llvm-project
changes, you can run the tests from HEIR usingbazel test @llvm-project//mlir/...:all
. Newlit
tests can be added inllvm-project
’s existing directories and tested this way without a rebuild. - If you add new CMake targets in
llvm-project
, then to incorporate them into HEIR you need to add new bazel targets inllvm-project/utils/bazel/llvm-project-overlay/mlir/BUILD.bazel
. This is required if, for example, a new dialect or pass is added in MLIR upstream.
Send any upstream changes to HEIR-relevant MLIR files to @j2kun (Jeremy Kun) who has LLVM commit access and can also suggest additional MLIR reviewers.
Tips for building dependencies / useful external libraries
MLIR
Instructions for building MLIR can be found on the
Getting started page of the MLIR
website. The instructions there seem to work as written (tested on Ubuntu
22.04). However, the command shown in Unix-like compile/testing:
may require a
large amount of RAM. If building on a system with 16GB of RAM or less, and if
you don’t plan to target GPUs, you may want to replace the line
-DLLVM_TARGETS_TO_BUILD="Native;NVPTX;AMDGPU" \
with
-DLLVM_TARGETS_TO_BUILD="Native" \
OpenFHE
A simple way to build OpenFHE is to follow the instructions in the openfhe-configurator repository. This allows to build the library with or without support for the Intel HEXL library. First, clone the repository and configure it using:
git clone https://github.com/openfheorg/openfhe-configurator.git
cd openfhe-configurator
scripts/configure.sh
You will be asked whether to stage a vanilla OpenFHE build or add support for HEXL. You can then build the library using
./scripts/build-openfhe-development.sh
The build may fail on systems with less than 32GB or RAM due to parallel
compilation. You can disable it by editing
./scripts/build-openfhe-development.sh
and replacing
make -j || abort "Build of openfhe-development failed."
with
make || abort "Build of openfhe-development failed."
Compilation will be significantly slower but should then take less than 8GB of memory.
Creating a New Pass
The scripts/templates
folder contains Python scripts to create boilerplate for
new conversion or (dialect-specific) transform passes. These should be used when
the tablegen files containing existing pass definitions in the expected
filepaths are not already present. Otherwise, you should modify the existing
tablegen files directly.
Conversion Pass
To create a new conversion pass, run a command similar to the following:
python scripts/templates/templates.py new_conversion_pass \
--source_dialect_name=CGGI \
--source_dialect_namespace=cggi \
--source_dialect_mnemonic=cggi \
--target_dialect_name=TfheRust \
--target_dialect_namespace=tfhe_rust \
--target_dialect_mnemonic=tfhe_rust
In order to build the resulting code, you must fix the labeled FIXME
s in the
type converter and the op conversion patterns.
Transform Passes
To create a transform or rewrite pass that operates on a dialect, run a command similar to the following:
python scripts/templates/templates.py new_dialect_transform \
--pass_name=ForgetSecrets \
--pass_flag=forget-secrets \
--dialect_name=Secret \
--dialect_namespace=secret \
--force=false
If the transform does not operate from and to a specific dialect, use
python scripts/templates/templates.py new_transform \
--pass_name=ForgetSecrets \
--pass_flag=forget-secrets \
--force=false
Pre-Commit
We use pre-commit to manage a series of git pre-commit hooks for the project; for example, each time you commit code, the hooks will make sure that your C++ is formatted properly. If your code isn’t, the hook will format it, so when you try to commit the second time you’ll get past the hook.
All hooks are defined in .pre-commit-config.yaml
. To install these hooks, run
pip install -r requirements-dev.txt
Then install the hooks to run automatically on git commit
:
pre-commit install
To run them manually, run
pre-commit run --all-files
4 - Tutorials and Talks
A list of tutorials by the HEIR community. To add to this list, open an issue or submit a pull request on GitHub.
Talks
- HEIR: A foundation for FHE compilers (FHE.org 2023-10 meetup)
- How to Write Optimizations in HEIR (FHE.org 2024 conference tutorial) (slides)
MLIR tutorials
FHE math
5 - Design
This section contains documentation on the high level design of the project. Readers are intended to have basic familiarity with MLIR and FHE.
5.1 - Data-oblivious Transformations
A data-oblivious program is one that decouples data input from program execution. Such programs exhibit control-flow and memory access patterns that are independent of their input(s). This programming model, when applied to encrypted data, is necessary for expressing FHE programs. There are 3 major transformations applied to convert a conventional program into a data-oblivious program:
(1) If-Transformation
If-operations conditioned on inputs create data-dependent control-flow in
programs. scf.if
operations should at least define a ’then’ region (true path)
and always terminate with scf.yield
even when scf.if
doesn’t produce a
result. To convert a data-dependent scf.if
operation to an equivalent set of
data-oblivious operations in MLIR, we hoist all safely speculatable operations
in the scf.if
operation and convert the scf.yield
operation to an
arith.select
operation. The following code snippet demonstrates an application
of this transformation.
// Before applying If-transformation
func.func @my_function(%input : i1 {secret.secret}) -> () {
...
// Violation: %input is used as a condition causing a data-dependent branch
%result =`%input -> (i16) {
%a = arith.muli %b, %c : i16
scf.yield %a : i16
} else {
scf.yield %b : i16
}
...
}
// After applying If-transformation
func.func @my_function(%input : i16 {secret.secret}) -> (){
...
%a = arith.muli %b, %c : i16
%result = arith.select %input, %a, %b : i16
...
}
We implement a ConvertIfToSelect
pass that transforms operations with
secret-input conditions and with only Pure operations (i.e., operations that
have no memory side effect and are speculatable) in their body. This
transformation cannot be applied to operations when side effects are present in
only one of the two regions. Although possible, we currently do not support
transformations for operations where both regions have operations with matching
side effects. When side effects are present, the pass fails.
(2) Loop-Transformation
Loop statements with input-dependent conditions (bounds) and number of
iterations introduce data-dependent branches that violate data-obliviousness. To
convert such loops into a data-oblivious version, we replace input-dependent
conditionals (bounds) with static input-independent parameters (e.g. defining a
constant upper bound), and early-exits with update operations where the value
returned from the loop is selectively updated using conditional predication. In
MLIR, loops are expressed using either affine.for
, scf.for
or scf.while
operations.
[!NOTE] Early exiting from loops is not supported in
scf
andaffine
, so early exits are not supported in this pipeline. TODO(#922): support early exits
affine.for
: This operation lends itself well to expressing data oblivious programs because it requires constant loop bounds, eliminating input-dependent limits.
%sum_0 = arith.constant 0.0 : f32
// The for-loop's bound is a fixed constant
%sum = affine.for %i = 0 to 10 step 2
iter_args(%sum_iter = %sum_0) -> (f32) {
%t = affine.load %buffer[%i] : memref<1024xf32>
%sum_next = arith.addf %sum_iter, %input : f32
affine.yield %sum_next : f32
}
...
scf.for
: In contrast to affine.for, scf.for does allow input-dependent conditionals which does not adhere to data-obliviousness constraints. A solution to this could be to either have the programmer or the compiler specify an input-independent upper bound so we can transform the loop to use this upper bound and also carefully update values returned from the for-loop using conditional predication. Our current solution to this is for the programmer to add the lower bound and worst case upper bound in the static affine loop’sattributes
list.
func.func @my_function(%value: i32 {secret.secret}, %inputIndex: index {secret.secret}) -> i32 {
...
// Violation: for-loop uses %inputIndex as upper bound which causes a secret-dependent control-flow
%result = scf.for %iv = %begin to %inputIndex step %step_value iter_args(%arg1 = %value) -> i32 {
%output = arith.muli %arg1, %arg1 : i32
scf.yield %output : i32
}{lower = 0, upper = 32}
...
}
// After applying Loop-Transformation
func.func @my_function(%value: i32 {secret.secret}, %inputIndex: index {secret.secret}) -> i32 {
...
// Build for-loop using lower and upper values from the `attributes` list
%result = affine.for %iv = 0 to step 32 iter_args(%arg1 = %value) -> i32 {
%output = arith.muli %arg1, %agr1 : i32
%cond = arith.cmpi eq, %iv, %inputIndex : index
%newOutput = arith.select %cond, %output, %arg1
scf.yield %newOutput : i32
}
...
}
scf.while
: This operation represents a generic while/do-while loop that keeps iterating as long as a condition is met. An input-dependent while condition introduces a data-dependent control flow that violates data-oblivious constraints. For this transformation, the programmer needs to add themax_iter
attribute that describes the maximum number of iterations the loop runs which we then use the value to build our staticaffine.for
loop.
// Before applying Loop-Transformation
func.func @my_function(%input: i16 {secret.secret}){
%zero = arith.constant 0 : i16
%result = scf.while (%arg1 = %input) : (i16) -> i16 {
%cond = arith.cmpi slt, %arg1, %zero : i16
// Violation: scf.while uses %cond whose value depends on %input
scf.condition(%cond) %arg1 : i16
} do {
^bb0(%arg2: i16):
%mul = arith.muli %arg2, %arg2: i16
scf.yield %mul
} attributes {max_iter = 16 : i64}
...
return
}
// After applying Loop-Transformation
func.func @my_function(%input: i16 {secret.secret}){
%zero = arith.constant 0 : i16
%begin = arith.constant 1 : index
...
// Replace while-loop with a for-loop with a constant bound %MAX_ITER
%result = affine.for %iv = %0 to %16 step %step_value iter_args(%iter_arg = %input) -> i16 {
%cond = arith.cmpi slt, %iter_arg, %zero : i16
%mul = arith.muli %iter_arg, %iter_arg : i16
%output = arith.select %cond, %mul, %iter_arg
scf.yield %output
}{max_iter = 16 : i64}
...
return
}
(3) Access-Transformation
Input-dependent memory access cause data-dependent memory footprints. A naive
data-oblivious solution to this maybe doing read-write operations over the
entire data structure while only performing the desired save/update operation
for the index of interest. For simplicity, we only look at load/store operations
for tensors as they are well supported structures in high-level MLIR likely
emitted by most frontends. We drafted the following non-SIMD approach for this
transformation and defer SIMD optimizations to the heir-simd-vectorizer
pass:
// Before applying Access Transformation
func.func @my_function(%input: tensor<16xi32> {secret.secret}, %inputIndex: index {secret.secret}) {
...
%c_10 = arith.constant 10 : i32
// Violation: tensor.extract loads value at %inputIndex
%extractedValue = tensor.extract %input[%inputIndex] : tensor<16xi32>
%newValue = arith.addi %extractedValue, %c_10 : i32
// Violation: tensor.insert stores value at %inputIndex
%inserted = tensor.insert %newValue into %input[%inputIndex] : tensor<16xi32>
...
}
// After applying Non-SIMD Access Transformation
func.func @my_function(%input: tensor<16xi32> {secret.secret}, %inputIndex: index {secret.secret}) {
...
%c_10 = arith.constant 10 : i32
%i_0 = arith.constant 0 : index
%dummyValue = arith.constant 0 : i32
%extractedValue = affine.for %i=0 to 16 iter_args(%arg= %dummyValue) -> (i32) {
// 1. Check if %i matches %inputIndex
// 2. Extract value at %i
// 3. If %i matches %inputIndex, select %value extracted in (2), else select %dummyValue
// 4. Yield selected value
%cond = arith.cmpi eq, %i, %inputIndex : index
%value = tensor.extract %input[%i] : tensor<16xi32>
%selected = arith.select %cond, %value, %dummyValue : i32
affine.yield %selected : i32
}
%newValue = arith.addi %extractedValue, %c_10 : i32
%inserted = affine.for %i=0 to 16 iter_args(%inputArg = %input) -> tensor<16xi32> {
// 1. Check if %i matches the %inputIndex
// 2. Insert %newValue and produce %newTensor
// 3. If %i matches %inputIndex, select %newTensor, else select input tensor
// 4. Yield final tensor
%cond = arith.cmpi eq, %i, %inputIndex : index
%newTensor = tensor.insert %value into %inputArg[%i] : tensor<16xi32>
%finalTensor= arith.select %cond, %newTensor, %inputArg : tensor<16xi32>
affine.yield %finalTensor : tensor<16xi32>
}
...
}
More notes on these transformations
These 3 transformations have a cascading behavior where transformations can be applied progressively to achieve a data-oblivious program. The order of the transformations goes as follows:
- Access-Transformation (change data-dependent tensor accesses (reads-writes)
to use
affine.for
andscf.if
operations) -> Loop-Transformation (change data-dependent loops to use constant bounds and condition the loop’s yield results withscf.if
operation) -> If-Transformation (substitute data-dependent conditionals witharith.select
operation). - Besides that, when we apply non-SIMD Access-Transformation on multiple data-dependent tensor read-write operations over the same tensor, we can benefit from upstream affine transformations over the resulting multiple affine loops produced by the Access-Transformation to fuse these loops.
5.2 - Secret
The secret
dialect contains types
and operations to represent generic computations on secret data. It is intended
to be a high-level entry point for the HEIR compiler, agnostic of any particular
FHE scheme.
Most prior FHE compiler projects design their IR around a specific FHE scheme,
and provide dedicated IR types for the secret analogues of existing data types,
and/or dedicated operations on secret data types. For example, the Concrete
compiler has !FHE.eint<32>
for an encrypted 32-bit integer, and add_eint
and
similar ops. HECO has !fhe.secret<T>
that models a generic secret type, but
similarly defines fhe.add
and fhe.multiply
, and other projects are similar.
The problem with this approach is that it is difficult to incorporate the apply
upstream canonicalization and optimization passes to these ops. For example, the
arith
dialect in MLIR has
canonicalization patterns
that must be replicated to apply to FHE analogues. One of the goals of HEIR is
to reuse as much upstream infrastructure as possible, and so this led us to
design the secret
dialect to have both generic types and generic computations.
Thus, the secret
dialect has two main parts: a secret<T>
type that wraps any
other MLIR type T
, and a secret.generic
op that lifts any computation on
cleartext to the “corresponding” computation on secret data types.
Overview with BGV-style lowering pipeline
Here is an example of a program that uses secret
to lift a dot product
computation:
func.func @dot_product(
%arg0: !secret.secret<tensor<8xi16>>,
%arg1: !secret.secret<tensor<8xi16>>) -> !secret.secret<i16> {
%c0_i16 = arith.constant 0 : i16
%0 = secret.generic ins(%arg0, %arg1 : !secret.secret<tensor<8xi16>>, !secret.secret<tensor<8xi16>>) {
^bb0(%arg2: tensor<8xi16>, %arg3: tensor<8xi16>):
%1 = affine.for %arg4 = 0 to 8 iter_args(%arg5 = %c0_i16) -> (i16) {
%extracted = tensor.extract %arg2[%arg4] : tensor<8xi16>
%extracted_0 = tensor.extract %arg3[%arg4] : tensor<8xi16>
%2 = arith.muli %extracted, %extracted_0 : i16
%3 = arith.addi %arg5, %2 : i16
affine.yield %3 : i16
}
secret.yield %1 : i16
} -> !secret.secret<i16>
return %0 : !secret.secret<i16>
}
The operands to the generic
op are the secret data types, and the op contains
a single region, whose block arguments are the corresponding cleartext data
values. Then the region is free to perform any computation, and the values
passed to secret.yield
are lifted back to secret
types. Note that
secret.generic
is not isolated from its enclosing scope, so one may refer to
cleartext SSA values without adding them as generic operands and block
arguments.
Clearly secret.generic
does not actually do anything. It is not decrypting
data. It is merely describing the operation that one wishes to apply to the
secret data in more familiar terms. It is a structural operation, primarily used
to demarcate which operations involve secret operands and have secret results,
and group them for later optimization. The benefit of this is that one can write
optimization passes on types and ops that are not aware of secret
, and they
will naturally match on the bodies of generic
ops.
For example, here is what the above dot product computation looks like after
applying the -cse -canonicalize -heir-simd-vectorizer
passes, the
implementations of which do not depend on secret
or generic
.
func.func @dot_product(
%arg0: !secret.secret<tensor<8xi16>>,
%arg1: !secret.secret<tensor<8xi16>>) -> !secret.secret<i16> {
%c1 = arith.constant 1 : index
%c2 = arith.constant 2 : index
%c4 = arith.constant 4 : index
%c7 = arith.constant 7 : index
%0 = secret.generic ins(%arg0, %arg1 : !secret.secret<tensor<8xi16>>, !secret.secret<tensor<8xi16>>) {
^bb0(%arg2: tensor<8xi16>, %arg3: tensor<8xi16>):
%1 = arith.muli %arg2, %arg3 : tensor<8xi16>
%2 = tensor_ext.rotate %1, %c4 : tensor<8xi16>, index
%3 = arith.addi %1, %2 : tensor<8xi16>
%4 = tensor_ext.rotate %3, %c2 : tensor<8xi16>, index
%5 = arith.addi %3, %4 : tensor<8xi16>
%6 = tensor_ext.rotate %5, %c1 : tensor<8xi16>, index
%7 = arith.addi %5, %6 : tensor<8xi16>
%extracted = tensor.extract %7[%c7] : tensor<8xi16>
secret.yield %extracted : i16
} -> !secret.secret<i16>
return %0 : !secret.secret<i16>
}
The canonicalization patterns for secret.generic
apply a variety of
simplifications, such as:
- Removing any unused or non-secret arguments and return values.
- Hoisting operations in the body of a
generic
that only depend on cleartext values to the enclosing scope. - Removing any
generic
ops that use no secrets at all.
These can be used together with the
secret-distribute-generic
pass
to split an IR that contains a large generic
op into generic
ops that
contain a single op, which can then be lowered to a particular FHE scheme
dialect with dedicated ops. This makes lowering easier because it gives direct
access to the secret version of each type that is used as input to an individual
op.
As an example, a single-op secret might look like this (taken from the larger example below. Note the use of a cleartext from the enclosing scope, and the proximity of the secret type to the op to be lowered.
%c2 = arith.constant 2 : index
%3 = secret.generic ins(%2 : !secret.secret<tensor<8xi16>>) {
^bb0(%arg2: tensor<8xi16>):
%8 = tensor_ext.rotate %arg2, %c2 : tensor<8xi16>, index
secret.yield %8 : tensor<8xi16>
} -> !secret.secret<tensor<8xi16>>
For a larger example, applying --secret-distribute-generic --canonicalize
to
the IR above:
func.func @dot_product(%arg0: !secret.secret<tensor<8xi16>>, %arg1: !secret.secret<tensor<8xi16>>) -> !secret.secret<i16> {
%c1 = arith.constant 1 : index
%c2 = arith.constant 2 : index
%c4 = arith.constant 4 : index
%c7 = arith.constant 7 : index
%0 = secret.generic ins(%arg0, %arg1 : !secret.secret<tensor<8xi16>>, !secret.secret<tensor<8xi16>>) {
^bb0(%arg2: tensor<8xi16>, %arg3: tensor<8xi16>):
%8 = arith.muli %arg2, %arg3 : tensor<8xi16>
secret.yield %8 : tensor<8xi16>
} -> !secret.secret<tensor<8xi16>>
%1 = secret.generic ins(%0 : !secret.secret<tensor<8xi16>>) {
^bb0(%arg2: tensor<8xi16>):
%8 = tensor_ext.rotate %arg2, %c4 : tensor<8xi16>, index
secret.yield %8 : tensor<8xi16>
} -> !secret.secret<tensor<8xi16>>
%2 = secret.generic ins(%0, %1 : !secret.secret<tensor<8xi16>>, !secret.secret<tensor<8xi16>>) {
^bb0(%arg2: tensor<8xi16>, %arg3: tensor<8xi16>):
%8 = arith.addi %arg2, %arg3 : tensor<8xi16>
secret.yield %8 : tensor<8xi16>
} -> !secret.secret<tensor<8xi16>>
%3 = secret.generic ins(%2 : !secret.secret<tensor<8xi16>>) {
^bb0(%arg2: tensor<8xi16>):
%8 = tensor_ext.rotate %arg2, %c2 : tensor<8xi16>, index
secret.yield %8 : tensor<8xi16>
} -> !secret.secret<tensor<8xi16>>
%4 = secret.generic ins(%2, %3 : !secret.secret<tensor<8xi16>>, !secret.secret<tensor<8xi16>>) {
^bb0(%arg2: tensor<8xi16>, %arg3: tensor<8xi16>):
%8 = arith.addi %arg2, %arg3 : tensor<8xi16>
secret.yield %8 : tensor<8xi16>
} -> !secret.secret<tensor<8xi16>>
%5 = secret.generic ins(%4 : !secret.secret<tensor<8xi16>>) {
^bb0(%arg2: tensor<8xi16>):
%8 = tensor_ext.rotate %arg2, %c1 : tensor<8xi16>, index
secret.yield %8 : tensor<8xi16>
} -> !secret.secret<tensor<8xi16>>
%6 = secret.generic ins(%4, %5 : !secret.secret<tensor<8xi16>>, !secret.secret<tensor<8xi16>>) {
^bb0(%arg2: tensor<8xi16>, %arg3: tensor<8xi16>):
%8 = arith.addi %arg2, %arg3 : tensor<8xi16>
secret.yield %8 : tensor<8xi16>
} -> !secret.secret<tensor<8xi16>>
%7 = secret.generic ins(%6 : !secret.secret<tensor<8xi16>>) {
^bb0(%arg2: tensor<8xi16>):
%extracted = tensor.extract %arg2[%c7] : tensor<8xi16>
secret.yield %extracted : i16
} -> !secret.secret<i16>
return %7 : !secret.secret<i16>
}
And then lowering it to bgv
with --secret-to-bgv="poly-mod-degree=8"
(the
pass option matches the tensor size, but it is an unrealistic FHE polynomial
degree used here just for demonstration purposes). Note type annotations on ops
are omitted for brevity.
#encoding = #lwe.polynomial_evaluation_encoding<cleartext_start = 16, cleartext_bitwidth = 16>
#params = #lwe.rlwe_params<ring = <cmod=463187969, ideal=#_polynomial.polynomial<1 + x**8>>>
!ty1 = !lwe.rlwe_ciphertext<encoding=#encoding, rlwe_params=#params, underlying_type=tensor<8xi16>>
!ty2 = !lwe.rlwe_ciphertext<encoding=#encoding, rlwe_params=#params, underlying_type=i16>
func.func @dot_product(%arg0: !ty1, %arg1: !ty1) -> !ty2 {
%c1 = arith.constant 1 : index
%c2 = arith.constant 2 : index
%c4 = arith.constant 4 : index
%c7 = arith.constant 7 : index
%0 = bgv.mul %arg0, %arg1
%1 = bgv.relinearize %0 {from_basis = array<i32: 0, 1, 2>, to_basis = array<i32: 0, 1>}
%2 = bgv.rotate %1, %c4
%3 = bgv.add %1, %2
%4 = bgv.rotate %3, %c2
%5 = bgv.add %3, %4
%6 = bgv.rotate %5, %c1
%7 = bgv.add %5, %6
%8 = bgv.extract %7, %c7
return %8
}
Differences for CGGI-style pipeline
The tosa-to-boolean-tfhe
and related pipelines add a few additional steps. The
main goal here is to apply a hardware circuit optimizer to blocks of standard
MLIR code (inside secret.generic
ops) which converts the computation to an
optimized boolean circuit with a desired set of gates. Only then is
-secret-distribute-generic
applied to split the ops up and lower them to the
cggi
dialect. In particular, because passing an IR through the circuit
optimizer requires unrolling all loops, one useful thing you might want to do is
to optimize only the body of a for loop nest.
To accomplish this, we have two additional mechanisms. One is the pass option
ops-to-distribute
for -secret-distribute-generic
, which allows the user to
specify a list of ops that generic
should be split across, and all others left
alone. Specifying affine.for
here will pass generic
through the affine.for
loop, but leave its body intact. This can also be used with the -unroll-factor
option to the -yosys-optimizer
pass to partially unroll a loop nest and pass
the partially-unrolled body through the circuit optimizer.
The other mechanism is the secret.separator
op, which is a purely structural
op that demarcates the boundary of a subset of a block that should be jointly
optimized in the circuit optimizer.
For example, the following tosa
ops lower to multiple linalg instructions, and
hence multiple for loops, that we want to pass to a circuit optimizer as a unit.
The secret.separator
ops surrounding the op are preserved through the
lowering.
func.func @main(%arg0: tensor<1x1xi8> {secret.secret}) -> tensor<1x16xi32> {
secret.separator
%4 = "tosa.const"() {value = dense<[0, 0, -5438, -5515, -1352, -1500, -4152, -84, 3396, 0, 1981, -5581, 0, -6964, 3407, -7217]> : tensor<16xi32>} : () -> tensor<16xi32>
%5 = "tosa.const"() {value = dense<[[-9], [-54], [57], [71], [104], [115], [98], [99], [64], [-26], [127], [25], [-82], [68], [95], [86]]> : tensor<16x1xi8>} : () -> tensor<16x1xi8>
%6 = "tosa.fully_connected"(%arg0, %5, %4) {quantization_info = #tosa.conv_quant<input_zp = -128, weight_zp = 0>} : (tensor<1x1xi8>, tensor<16x1xi8>, tensor<16xi32>) -> tensor<1x16xi32>
secret.separator
return %6 : tensor<1x16xi32>
}
After running --tosa-to-boolean-tfhe
and dumping the IR after the linalg ops
are lowered to loops, we can see the secret.separator
ops enclose the lowered
ops, with the exception of some pure ops that are speculatively executed.
func.func @main(%arg0: memref<1x1xi8, strided<[?, ?], offset: ?>> {secret.secret}) -> memref<1x16xi32> {
%c-128_i32 = arith.constant -128 : i32
%0 = memref.get_global @__constant_16xi32 : memref<16xi32>
%1 = memref.get_global @__constant_16x1xi8 : memref<16x1xi8>
secret.separator
%alloc = memref.alloc() {alignment = 64 : i64} : memref<1x16xi8>
affine.for %arg1 = 0 to 1 {
affine.for %arg2 = 0 to 16 {
%2 = affine.load %1[%arg2, %arg1] : memref<16x1xi8>
affine.store %2, %alloc[%arg1, %arg2] : memref<1x16xi8>
}
}
%alloc_0 = memref.alloc() {alignment = 64 : i64} : memref<1x16xi32>
affine.for %arg1 = 0 to 1 {
affine.for %arg2 = 0 to 16 {
%2 = affine.load %0[%arg2] : memref<16xi32>
affine.store %2, %alloc_0[%arg1, %arg2] : memref<1x16xi32>
}
}
affine.for %arg1 = 0 to 1 {
affine.for %arg2 = 0 to 16 {
affine.for %arg3 = 0 to 1 {
%2 = affine.load %arg0[%arg1, %arg3] : memref<1x1xi8, strided<[?, ?], offset: ?>>
%3 = affine.load %alloc[%arg3, %arg2] : memref<1x16xi8>
%4 = affine.load %alloc_0[%arg1, %arg2] : memref<1x16xi32>
%5 = arith.extsi %2 : i8 to i32
%6 = arith.subi %5, %c-128_i32 : i32
%7 = arith.extsi %3 : i8 to i32
%8 = arith.muli %6, %7 : i32
%9 = arith.addi %4, %8 : i32
affine.store %9, %alloc_0[%arg1, %arg2] : memref<1x16xi32>
}
}
}
secret.separator
memref.dealloc %alloc : memref<1x16xi8>
return %alloc_0 : memref<1x16xi32>
}
We decided to use the separator
op over a few alternatives:
- Grouping by
secret.generic
: thesetosa
ops must be bufferized, butsecret
types cannot participate in bufferization (see the Limitations section). - Grouping by basic blocks:
secret.generic
is a single-block op with a yield terminator, and grouping by blocks would require us to change this. - Grouping by regions: SSA values generated by a region are not visible to the enclosing scope, so we would need to have the region-bearing op return values, which is tedious to organize.
- Attaching attributes to ops that should be grouped together: this would not be preserved by upstream lowerings and optimization passes.
generic
operands
secret.generic
takes any SSA values as legal operands. They may be secret
types or non-secret. Canonicalizing secret.generic
removes non-secret operands
and leaves them to be referenced via the enclosing scope (secret.generic
is
not IsolatedFromAbove
).
This may be unintuitive, as one might expect that only secret types are valid
arguments to secret.generic
, and that a verifier might assert non-secret args
are not present.
However, we allow non-secret operands because it provides a convenient scope
encapsulation mechanism, which is useful for the --yosys-optimizer
pass that
runs a circuit optimizer on individual secret.generic
ops and needs to have
access to all SSA values used as inputs. The following passes are related to
this functionality:
secret-capture-generic-ambient-scope
secret-generic-absorb-constants
secret-extract-generic-body
Due to the canonicalization rules for secret.generic
, anyone using these
passes as an IR organization mechanism must be sure not to canonicalize before
accomplishing the intended task.
Limitations
Bufferization
Secret types cannot participate in bufferization passes. In particular,
-one-shot-bufferize
hard-codes the notion of tensor and memref types, and so
it cannot currently operate on secret<tensor<...>>
or secret<memref<...>>
types, which prevents us from implementing a bufferization interface for
secret.generic
. This was part of the motivation to introduce
secret.separator
, because tosa
ops like a fully connected neural network
layer lower to multiple linalg ops, and these ops need to be bufferized before
they can be lowered further. However, we want to keep the lowered ops grouped
together for circuit optimization (e.g., fusing transposes and constant weights
into the optimized layer), but because of this limitation, we can’t simply wrap
the tosa
ops in a secret.generic
(bufferization would fail).
5.3 - SIMD Optimizations
HEIR includes a SIMD (Single Instruction, Multiple Data) optimizer which is designed to exploit the restricted SIMD parallelism most (Ring-LWE-based) FHE schemes support (also commonly known as “packing” or “batching”). Specifically, HEIR incorporates the “automated batching” optimizations (among many other things) from the HECO compiler. The following will assume basic familiarity with the FHE SIMD paradigm and the high-level goals of the optimization, and we refer to the associated HECO paper, slides, talk and additional resources on the Usenix'23 website for an introduction to the topic. This documentation will mostly focus on describing how the optimization is realized in HEIR (which differs somewhat from the original implementation) and how the optimization is intended to be used in an overall end-to-end compilation pipeline.
Representing FHE SIMD Operations
Following the design principle of maintaining programs in standard MLIR dialects
as long as possible (cf. the design rationale behind the
Secret Dialect), HEIR uses the MLIR
tensor
dialect and
ElementwiseMappable
operations from the MLIR
arith
dialect to represent HE
SIMD operations.
We do introduce the HEIR-specific
tensor_ext.rotate
operation, which represents a cyclical left-rotation of a tensor. Note that, as
the current SIMD vectorizer only supports one-dimensional tensors, the semantics
of this operation on multi-dimensional tensors are not (currently) defined.
For example, the common “rotate-and-reduce” pattern which results in each element containing the sum/product/etc of the original vector can be expressed as:
%tensor = tensor.from_elements %i1, %i2, %i3, %i4, %i5, %i6, %i7, %i8 : tensor<8xi16>
%0 = tensor_ext.rotate %tensor, %c4 : tensor<8xi16>, index
%1 = arith.addi %tensor, %0 : tensor<8xi16>
%2 = tensor_ext.rotate %1, %c2 : tensor<8xi16>, index
%3 = arith.addi %1, %2 : tensor<8xi16>
%4 = tensor_ext.rotate %3, %c1 : tensor<8xi16>, index
%5 = arith.addi %3, %4 : tensor<8xi16>
The %cN
and %iN
, which are defined as %cN = arith.constant N : index
and
%iN = arith.constant N : i16
, respectively, have been omitted for readability.
Intended Usage
The -heir-simd-vectorizer
pipeline transforms a program consisting of loops
and index-based accesses into tensors (e.g., tensor.extract
and
tensor.insert
) into one consisting of SIMD operations (including rotations) on
entire tensors. While its implementation does not depend on any FHE-specific
details or even the Secret dialect, this transformation is likely only useful
when lowering a high-level program to an arithmetic-circuit-based FHE scheme
(e.g., B/FV, BGV, or CKKS). The -mlir-to-openfhe-bgv
pipeline demonstrates the
intended flow: augmenting a high-level program with secret
annotations, then
applying the SIMD optimization (and any other high-level optimizations) before
lowering to BGV operations and then exiting to OpenFHE.
Warning The current SIMD vectorizer pipeline supports only one-dimensional tensors. As a workaround, one could reshape all multi-dimensional tensors into one-dimensional tensors, but MLIR/HEIR currently do not provide a pass to automate this process.
Since the optimization is based on heuristics, the resulting program might not be optimal or could even be worse than a trivial realization that does not use ciphertext packing. However, well-structured programs generally lower to reasonable batched solutions, even if they do not achieve optimal batching layouts. For common operations such as matrix-vector or matrix-matrix multiplications, state-of-the-art approaches require advanced packing schemes that might map elements into the ciphertext vector in non-trivial ways (e.g., diagonal-major and/or replicated). The current SIMD vectorizer will never change the arrangement of elements inside an input tensor and therefore cannot produce the optimal approaches for these operations.
Note, that the SIMD batching optimization is different from, and significantly
more complex than, the Straight Line Vectorizer (-straight-line-vectorize
pass), which simply groups
ElementwiseMappable
operations that agree in operation name and operand/result types into
vectorized/tensorized versions.
Implementation
Below, we give a brief overview over the implementation, with the goal of both improving maintainability/extensibility of the SIMD vectorizer and allowing advanced users to better understand why a certain program is transformed in the way it is.
Components
The -heir-simd-vectorizer
pipeline uses a combination of standard MLIR passes
(-canonicalize
,
-cse
,
-sccp
) and custom HEIR passes.
Some of these
(-apply-folders
,
-full-loop-unroll
)
might have applications outside the SIMD optimization, while others
(-insert-rotate
,
-collapse-insertion-chains
and
-rotate-and-reduce
)
are very specific to the FHE SIMD optimization. In addition, the passes make use
of the RotationAnalysis
and TargetSlotAnalysis
analyses.
High-Level Flow
Loop Unrolling (
-full-loop-unroll
): The implementation currently begins by unrolling all loops in the program to simplify the later passes. See #589 for a discussion on how this could be avoided.Canonicalization (
-apply-folders -canonicalize
): As the rotation-specific passes are very strict about the structure of the IR they operate on, we must first simplify away things such as tensors of constant values. For performance reasons (c.f. comments in theheirSIMDVectorizerPipelineBuilder
function inheir-opt.cpp
), this must be done by first applying folds before applying the full canonicalization.Main SIMD Rewrite (
-insert-rotate -cse -canonicalize -cse
): This pass rewrites arithmetic operations overtensor.extract
-ed operands into SIMD operations over the entire tensor, rotating the (full-tensor) operands so that the correct elements interact. For example, it will rewrite the following snippet (which computest2[4] = t0[3] + t1[5]
)%0 = tensor.extract %t0[%c3] : tensor<32xi16> %1 = tensor.extract %t1[%c5] : tensor<32xi16> %2 = arith.addi %0, %1 : i16 %3 = tensor.insert %2 into %t2[%c4] : tensor<32xi16>
to
%0 = tensor_ext.rotate %t0, %c31 : tensor<32xi16>, index %1 = tensor_ext.rotate %t1, %c1 : tensor<32xi16>, index %2 = arith.addi %0, %1 : tensor<32xi16>
i.e., rotating
t0
down by one (31 = -1 (mod 32)) andt1
up by one to bring the elements at index 3 and 5, respectively, to the “target” index 4. The pass uses theTargetSlotAnalysis
to identify the appropriate target index (or ciphertext “slot” in FHE-speak). See Insert Rotate Pass below for more details. This pass is roughly equivalent to the-batching
pass in the original HECO implementation.Doing this rewrite by itself does not represent an optimization, but if we consider what happens to the corresponding code for other indices (e.g.,
t2[5] = t0[4] + t1[6]
), we see that the pass transforms expressions with the same relative index offsets into the exact same set of rotations/SIMD operations, so the following Common Subexpression Elimination (CSE) will remove redundant computations. We apply CSE twice, once directly (which creates new opportunities for canonicalization and folding) and then again after that canonicalization. See TensorExt Canonicalization for a description of the rotation-specific canonocalization patterns).Cleanup of Redundant Insert/Extract (
-collapse-insertion-chains -sccp -canonicalize -cse
): Because the-insert-rotate
pass maintains the consistency of the IR, it emits atensor.extract
operation after the SIMD operation and uses that to replace the original operation (which is valid, as both produce the desired scalar result). As a consequence, the generated code for the snippet above is actually trailed by a (redundant) extract/insert:%extracted = tensor.extract %2[%c4] : tensor<32xi16> %inserted = tensor.insert %extracted into %t2[%c4] : tensor<32xi16>
In real code, this might generate a long series of such extraction/insertion operations, all extracting from the same (due to CSE) tensor and inserting into the same output tensor. Therefore, the
-collapse-insertion-chains
pass searches for such chains over entire tensors and collapses them. It supports not just chains where the indices match perfectly, but any chain where the relative offset is consistent across the tensor, issuing a rotation to realize the offset (if the offset is zero, the canonicalization will remove the redundant rotation). Note, that in HECO, insertion/extraction is handled differently, as HECO features acombine
operation modelling not just simple insertions (combine(%t0#j, %t1)
) but also more complex operations over slices of tensors (combine(%t0#[i,j], %t1)
). As a result, the equivalent pass in HECO (-combine-simplify
) instead joins differentcombine
operations, and a later fold removescombines
that replace the entire target tensor. See issue #512 for a discussion on why thecombine
operation is a more powerful framework and what would be necessary to port it to HEIR.Applying Rotate-and-Reduce Patterns (
-rotate-and-reduce -sccp -canonicalize -cse
): The rotate and reduce pattern (see Representing FHE SIMD Operations for an example) is an important aspect of accelerating SIMD-style operations in FHE, but it does not follow automatically from the batching rewrites applied so far. As a result, the-rotate-and-reduce
pass needs to search for sequences of arithmetic operations that correspond to the full folding of a tensor, i.e., patterns such ast[0]+(t[1]+(t[2]+t[3]+(...)))
, which currently uses a backwards search through the IR, but could be achieved more efficiently through a data flow analysis (c.f. issue #532). In HECO, rotate-and-reduce is handled differently, by identifying sequences of compatible operations prior to batching and rewriting them to “n-ary” operations. However, this approach requires non-standard arithmetic operations and is therefore not suitable for use in HEIR. However, there is likely still an opportunity to make the patterns in HEIR more robust/general (e.g., support constant scalar operands in the fold, or support non-full-tensor folds). See issue #522 for ideas on how to make the HEIR pattern more robust/more general.
Insert Rotate Pass
TODO(#721): Write a detailed description of the rotation insertion pass and the associated target slot analysis.
TensorExt Canonicalization
The
TensorExt (tensor_ext
) Dialect
includes a series of canonicalization rules that are essential to making
automatically generated rotation code efficient:
Rotation by zero:
rotate %t, 0
folds away to%t
Cyclical wraparound:
rotate %t, k
for $k > t.size$ can be simplified torotate %t, (k mod t.size)
Sequential rotation:
%0 = rotate %t, k
followed by%1 = rotate %0, l
is simplified torotate %t (k+l)
Extraction:
%0 = rotate %t, k
followed by%1 = tensor.extract %0[l]
is simplified totensor.extract %t[k+l]
Binary Arithmetic Ops: where both operands to a binary
arith
operation are rotations by the same amount, the rotation can be performed only once, on the result. For Example,%0 = rotate %t1, k %1 = rotate %t2, k %2 = arith.add %0, %1
can be simplified to
%0 = arith.add %t1, %t2 %1 = rotate %0, k
Sandwiched Binary Arithmetic Ops: If a rotation follows a binary
arith
operation which has rotation as its operands, the post-arith operation can be moved forward. For example,%0 = rotate %t1, x %1 = rotate %t2, y %2 = arith.add %0, %1 %3 = rotate %2, z
can be simplified to
%0 = rotate %t1, x + z %1 = rotate %t2, y + z %2 = arith.add %0, %1
Single-Use Arithmetic Ops: Finally, there is a pair of rules that do not eliminate rotations, but move rotations up in the IR, which can help in exposing further canonicalization and/or CSE opportunities. These only apply to
arith
operations with a single use, as they might otherwise increase the total number of rotations. For example,%0 = rotate %t1, k %2 = arith.add %0, %t2 %1 = rotate %2, l
can be equivalently rewritten as
%0 = rotate %t1, (k+l) %1 = rotate %t2, l %2 = arith.add %0, %1
and a similar pattern exists for situations where the rotation is the rhs operand of the arithmetic operation.
Note that the index computations in the patterns above (e.g., k+l
,
k mod t.size
are realized via emitting arith
operations. However, for
constant/compile-time-known indices, these will be subsequently constant-folded
away by the canonicalization pass.
5.4 - Optimizing relinearization
This document outlines the integer linear program model used in the
optimize-relinearization
pass.
Background
In vector/arithmetic FHE, RLWE ciphertexts often have the form $\mathbf{c} = (c_0, c_1)$, where the details of how $c_0$ and $c_1$ are computed depend on the specific scheme. However, in most of these schemes, the process of decryption can be thought of as taking a dot product between the vector $\mathbf{c}$ and a vector $(1, s)$ containing the secret key $s$ (followed by rounding).
In such schemes, the homomorphic multiplication of two ciphertexts $\mathbf{c} = (c_0, c_1)$ and $\mathbf{d} = (d_0, d_1)$ produces a ciphertext $\mathbf{f} = (f_0, f_1, f_2)$. This triple can be decrypted by taking a dot product with $(1, s, s^2)$.
With this in mind, each RLWE ciphertext $\mathbf{c}$ has an associated key basis, which is the vector $\mathbf{s_c}$ whose dot product with $\mathbf{c}$ decrypts it.
Usually a larger key basis is undesirable. For one, operations in a higher key basis are more expensive and have higher rates of noise growth. Repeated multiplications exponentially increase the length of the key basis. So to avoid this, an operation called relinearization was designed that converts a ciphertext from a given key basis back to $(1, s)$. Doing this requires a set of relinearization keys to be provided by the client and stored by the server.
In general, key bases can be arbitrary. Rotation of an RLWE ciphertext by a shift of $k$, for example, first applies the automorphism $x \mapsto x^k$. This converts the key basis from $(1, s)$ to $(1, s^k)$, and more generally maps $(1, s, s^2, \dots, s^d) \mapsto (1, s^k, s^{2k}, \dots, s^{kd})$. Most FHE implementations post-compose this automorphism with a key switching operation to return to the linear basis $(1, s)$. Similarly, multiplication can be defined for two key bases $(1, s^n)$ and $(1, s^m)$ (with $n < m$) to produce a key basis $(1, s^n, s^m, s^{n+m})$. By a combination of multiplications and rotations (without ever relinearizing or key switching), ciphertexts with a variety of strange key bases can be produced.
Most FHE implementations do not permit wild key bases because each key switch and relinearization operation (for each choice of key basis) requires additional secret key material to be stored by the server. Instead, they often enforce that rotation has key-switching built in, and multiplication relinearizes by default.
That said, many FHE implementations do allow for the relinearization operation to be deferred. A useful such situation is when a series of independent multiplications are performed, and the results are added together. Addition can operate in any key basis (though all inputs must have the same key basis), and so the relinearization op that follows each multiplication can be deferred until after the additions are complete, at which point there is only one relinearization to perform. This technique is usually called lazy relinearization. It has the benefit of avoiding expensive relinearization operations, as well as reducing noise growth, as relinearization adds noise to the ciphertext, which can further reduce the need for bootstrapping.
In much of the literature, lazy relinearization is applied manually. See for example Blatt-Gusev-Polyakov-Rohloff-Vaikuntanathan 2019 and Lee-Lee-Kim-Kim-No-Kang 2020. In some compiler projects, such as the EVA compiler relinearization is applied automatically via a heuristic, either “eagerly” (immediately after each multiplication op) or “lazily,” deferred as late as possible.
The optimize-relinearization
pass
In HEIR, relinearization placement is implemented via a mixed-integer linear program (ILP). It is intended to be more general than a lazy relinearization heuristic, and certain parameter settings of the ILP reproduce lazy relinearization.
The optimize-relinearization
pass starts by deleting all relinearization
operations from the IR, solves the ILP, and then inserts relinearization ops
according to the solution. This implies that the input IR to the ILP has no
relinearization ops in it already.
Model specification
The ILP model fits into a family of models that is sometimes called “state-dynamics” models, in that it has “state” variables that track a quantity that flows through a system, as well as “decision” variables that control decisions to change the state at particular points. A brief overview of state dynamics models can be found here
In this ILP, the “state” value is the degree of the key basis. I.e., rather than track the entire key basis, we assume the key basis always has the form $(1, s, s^2, \dots, s^k)$ and track the value $k$. The index tracking state is SSA value, and the decision variables are whether to relinearize.
Variables
Define the following variables:
- For each operation $o$, $R_o \in { 0, 1 }$ defines the decision to relinearize the result of operation $o$. Relinearization is applied if and only if $R_o = 1$.
- For each SSA value $v$, $\textup{KB}_v$ is a continuous variable representing the degree of the key basis of $v$. For example, if the key basis of a ciphertext is $(1, s)$, then $\textup{KB}_v = 1$. If $v$ is the result of an operation $o$, $\textup{KB}_v$ is the key basis of the result of $o$ after relinearization has been optionally applied to it, depending on the value of the decision variable $R_o$.
- For each SSA value $v$ that is an operation result, $\textup{KB}^{br}_v$ is
a continuous variable whose value represents the key basis degree of $v$
before relinearization is applied (
br
= “before relin”). These SSA values are mainly for after the model is solved and relinearization operations need to be inserted into the IR. Here, type conflicts require us to reconstruct the key basis degree, and saving the values allows us to avoid recomputing the values.
Each of the key-basis variables is bounded from above by a parameter
MAX_KEY_BASIS_DEGREE
that can be used to impose hard limits on the key basis
size, which may be required if generating code for a backend that does not
support operations over generalized key bases.
Objective
The objective is to minimize the number of relinearization operations, i.e., $\min \sum_o R_o$.
TODO(#1018): update docs when objective is generalized.
Constraints
The simple constraints are as follows:
- Initial key basis degree: For each block argument, $\textup{KB}_v$ is fixed
to equal the
dimension
parameter on the RLWE ciphertext type. - Operand agreement: For each operation with operand SSA values $v_1, \dots, v_k$, $\textup{KB}_{v_1} = \dots = \textup{KB}_{v_k}$, i.e., all key basis inputs must match.
- Special linearized ops:
bgv.rotate
andfunc.return
require linearized inputs, i.e., $\textup{KB}_{v_i} = 1$ for all inputs $v_i$ to these operations. - Before relinearization key basis: for each operation $o$ with operands $v_1, \dots, v_k$, constrain $\textup{KB}^{br}_{\textup{result}(o)} = f(\textup{KB}_{v_1}, \dots, \textup{KB}_{v_k})$, where $f$ is a statically known linear function. For multiplication $f$ it addition, and for all other ops it is the projection onto any input, since multiplication is the only op that increases the degree, and all operands are constrained to have equal degree.
The remaining constraints control the dynamics of how the key basis degree changes as relinearizations are inserted.
They can be thought of as implementing this (non-linear) constraint for each operation $o$:
\[ \textup{KB}_{\textup{result}(o)} = \begin{cases} \textup{KB}^{br}_{\textup{result(o)}} & \text{ if } R_o = 0 \ 1 & \text{ if } R_o = 1 \end{cases} \]
Note that $\textup{KB}^{br}_{\textup{result}(o)}$ is constrained by one of the simple constraints to be a linear expression containing key basis variables for the operands of $o$. The conditional above cannot be implemented directly in an ILP. Instead, one can implement it via four constraints that effectively linearize (in the sense of making non-linear constraints linear) the multiplexer formula
\[ \textup{KB}_{\textup{result}(o)} = (1 - R_o) \cdot \textup{KB}^{br}_{\textup{result}(o)} + R_o \cdot 1 \]
(Note the above is not linear because in includes the product of two variables.) The four constraints are:
\[ \begin{aligned} \textup{KB}_\textup{result}(o) &\geq \textup{ R}_o \\ \textup{KB}\_\textup{result}(o) &\leq 1 + C(1 – \textup{R}_o) \\ \textup{KB}_\textup{result}(o) &\geq \textup{KB}^{br}_{\textup{result}(o)} – C \textup{ R}_o \\ \textup{KB}_\textup{result}(o) &\leq \textup{KB}^{br}_{\textup{result}(o)} + C \textup{ R}_o \\ \end{aligned} \]
Here $C$ is a constant that can be set to any value larger than
MAX_KEY_BASIS_DEGREE
. We set it to 100.
Setting $R_o = 0$ makes constraints 1 and 2 trivially satisfied, while constraints 3 and 4 enforce the equality $\textup{KB}_{\textup{result}(o)} = \textup{KB}^{br}_{\textup{result}(o)}$. Likewise, setting $R_o = 1$ makes constraints 3 and 4 trivially satisfied, while constraints 1 and 2 enforce the equality $\textup{KB}_{\textup{result}(o)} = 1$.
Notes
- ILP performance scales roughly with the number of integer variables. The formulation above only requires the decision variable to be integer, and the initialization and constraints effectively force the key basis variables to be integer. As a result, the solve time of the above ILP should scale with the number of ciphertext-handling ops in the program.
6 - Pipelines
heir-opt
--heir-simd-vectorizer
Run scheme-agnostic passes to convert FHE programs that operate on scalar types to equivalent programs that operate on vectors.
This pass is intended to process FHE programs that are known to be good for
SIMD, but a specific FHE scheme has not yet been chosen. It expects to handle
arith
ops operating on tensor
types (with or without secret.generic
).
The pass unrolls all loops, then applies a series of passes that convert scalar operations on tensor elements to SIMD operations on full tensors. This uses the FHE computational model common to BGV, BFV, and CKKS, in which data is packed in polynomial ciphertexts, interpreted as vectors of individual data elements, and arithmetic can be applied across entire ciphertexts, with some limited support for rotations via automorphisms of the underlying ring.
Along the way, this pipeline applies heuristic optimizations to minimize the
number of rotations needed, relying on the implicit cost model that rotations
are generally expensive. The specific set of passes can be found in
tools/heir-opt.cpp
where the pipeline is defined.
--heir-tosa-to-arith
Lowers a TOSA MLIR model to func
, arith
, and memref
.
Lowers from TOSA through linalg
and affine
, and converts all tensors to
memrefs. Fully unrolls all loops, and forwards stores to subsequent loads
whenever possible. The output is suitable as an input to
heir-translate --emit-verilog
. Retains affine.load
and affine.store
ops
that cannot be removed (e.g., reading from the input and writing to the output,
or loading from a memref with a variable index).
The pass pipeline assumes that the input is a valid TOSA MLIR model with
stripped quantized types. The
iree-import-tflite tool can
lower a TFLite FlatBuffer to textual MLIR with --output-format=mlir-ir
. See
hello_world.tosa.mlir
for an example.
--yosys-optimizer
Uses Yosys to booleanize and optimize MLIR functions.
This pass pipeline requires inputs to be in standard MLIR (arith
, affine
,
func
, memref
). The pass imports the model to Yosys and runs passes to
booleanize the circuit and then uses ABC to perform optimizations. We use
standard LUT 3 cells. THe output of this pass includes arith
constants and
comb.truth_table
ops.
The pass requires that the environment variable HEIR_ABC_BINARY
contains the
location of the ABC binary and that HEIR_YOSYS_SCRIPTS_DIR
contains the
location of the Yosys’ techlib files that are needed to execute the path.
This pass can be disabled by defining HEIR_NO_YOSYS
; this will avoid Yosys
library and ABC binary compilation, and avoid registration of this pass.
--tosa-to-boolean-tfhe
This is an experimental pipeline for end-to-end private inference.
Converts a TOSA MLIR model to tfhe_rust dialect defined by HEIR. It converts a
tosa model to optimized boolean circuit using Yosys ABC optimizations. The
resultant optimized boolean circuit in comb dialect is then converted to cggi
and then to tfhe_rust exit dialect. This pipeline can be used with
heir-translate –emit-tfhe-rust to generate code for
tfhe-rs
FHE library.
The pass requires that the environment variable HEIR_ABC_BINARY
contains the
location of the ABC binary and that HEIR_YOSYS_SCRIPTS_DIR
contains the
location of the Yosys’ techlib files that are needed to execute the path.
heir-translate
--emit-tfhe-rust
Code generation for the tfhe-rs
FHE library.
The library is based on the CGGI cryptosystem, and so this pass is most useful
when paired with lowerings from the cggi
dialect.
The version of tfhe-rs
supported is defined in the
end to end tfhe_rust
tests.
Example input:
!sks = !tfhe_rust.server_key
!lut = !tfhe_rust.lookup_table
!eui3 = !tfhe_rust.eui3
func.func @test_apply_lookup_table(%sks : !sks, %lut: !lut, %input : !eui3) -> !eui3 {
%v1 = tfhe_rust.apply_lookup_table %sks, %input, %lut : (!sks, !eui3, !lut) -> !eui3
%v2 = tfhe_rust.add %sks, %input, %v1 : (!sks, !eui3, !eui3) -> !eui3
%c1 = arith.constant 1 : i8
%v3 = tfhe_rust.scalar_left_shift %sks, %v2, %c1 : (!sks, !eui3, i8) -> !eui3
%v4 = tfhe_rust.apply_lookup_table %sks, %v3, %lut : (!sks, !eui3, !lut) -> !eui3
return %v4 : !eui3
}
Example output:
use tfhe::shortint::prelude::*;
pub fn test_apply_lookup_table(
v9: &ServerKey,
v10: &LookupTableOwned,
v11: &Ciphertext,
) -> Ciphertext {
let v4 = v9.apply_lookup_table(&v11, &v10);
let v5 = v9.unchecked_add(&v11, &v4);
let v6 = 1;
let v7 = v9.scalar_left_shift(&v5, v6);
let v8 = v9.apply_lookup_table(&v7, &v10);
v8
}
Note, the chosen variable names are arbitrary, and the resulting program still must be integrated with a larger Rust program.
--emit-verilog
Code generation for verilog from arith
and memref
. Expects a single top
level func.func
op as the entry point, which is converted to the output
verilog module.
Example input:
module {
func.func @main(%arg0: i8) -> (i8) {
%c0 = arith.constant 0 : i32
%c1 = arith.constant 1 : i32
%c2 = arith.constant 2 : i32
%c3 = arith.constant 3 : i32
%0 = arith.extsi %arg0 : i8 to i32
%1 = arith.subi %0, %c1 : i32
%2 = arith.muli %1, %c2 : i32
%3 = arith.addi %2, %c3 : i32
%4 = arith.cmpi sge, %2, %c0 : i32
%5 = arith.select %4, %c1, %c2 : i32
%6 = arith.shrsi %3, %c1 : i32
%7 = arith.shrui %3, %c1 : i32
%out = arith.trunci %6 : i32 to i8
return %out : i8
}
}
Output:
module main(
input wire signed [7:0] arg1,
output wire signed [7:0] _out_
);
wire signed [31:0] v2;
wire signed [31:0] v3;
wire signed [31:0] v4;
wire signed [31:0] v5;
wire signed [31:0] v6;
wire signed [31:0] v7;
wire signed [31:0] v8;
wire signed [31:0] v9;
wire v10;
wire signed [31:0] v11;
wire signed [31:0] v12;
wire signed [31:0] v13;
wire signed [7:0] v14;
assign v2 = 0;
assign v3 = 1;
assign v4 = 2;
assign v5 = 3;
assign v6 = {{24{arg1[7]}}, arg1};
assign v7 = v6 - v3;
assign v8 = v7 * v4;
assign v9 = v8 + v5;
assign v10 = v8 >= v2;
assign v11 = v10 ? v3 : v4;
assign v12 = v9 >>> v3;
assign v13 = v9 >> v3;
assign v14 = v12[7:0];
assign _out_ = v14;
endmodule
--emit-metadata
Prints a json object describing the function signatures. Used for code
generation after --emit-verilog
.
Example input:
module {
func.func @main(%arg0: memref<80xi8>) -> memref<1x3x2x1xi8> {
%alloc_0 = memref.alloc() {alignment = 64 : i64} : memref<1x3x2x1xi8>
return %alloc_0 : memref<1x3x2x1xi8>
}
}
Example output:
{
"functions": [
{
"name": "main",
"params": [
{
"index": 0,
"type": {
"memref": {
"element_type": {
"integer": {
"is_signed": false,
"width": 8
}
},
"shape": [80]
}
}
}
],
"return_types": [{
"memref": {
"element_type": {
"integer": {
"is_signed": false,
"width": 8
}
},
"shape": [1, 3, 2, 1]
}
}]
}
]
}
7 - Dialects
This section contains the reference documentation for all of the dialects defined in HEIR.
7.1 - BGV
‘bgv’ Dialect
The BGV dialect defines the types and operations of the BGV cryptosystem.
BGV ops
bgv.add
(heir::bgv::AddOp)
Addition operation between ciphertexts.
Syntax:
operation ::= `bgv.add` operands attr-dict `:` qualified(type($output))
Traits: AlwaysSpeculatableImplTrait
, Commutative
, SameOperandsAndResultType
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
lhs | A ciphertext type |
rhs | A ciphertext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
bgv.add_plain
(heir::bgv::AddPlainOp)
Addition operation between ciphertext-plaintext.
Syntax:
operation ::= `bgv.add_plain` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
ciphertextInput | A ciphertext type |
plaintextInput | A plaintext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
bgv.extract
(heir::bgv::ExtractOp)
Extract the i-th element of a ciphertext.
Syntax:
operation ::= `bgv.extract` operands attr-dict `:` functional-type(operands, results)
While this operation is costly to compute in FHE, we represent it so we can implement efficient lowerings and folders.
This op can be implemented as a plaintext multiplication with a one-hot vector and a rotate into the zero-th index.
An extraction op’s input ciphertext type is asserted to have an underlying_type
corresponding to a ranked tensor type, and this op’s return type is
inferred to have the underlying_type
corresponding to the element type of
that tensor type.
Traits: AlwaysSpeculatableImplTrait
, SameOperandsAndResultRings
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
input | A ciphertext type |
offset | signless integer or index |
Results:
Result | Description |
---|---|
output | A ciphertext type |
bgv.modulus_switch
(heir::bgv::ModulusSwitchOp)
Lower the modulus level of the ciphertext.
Syntax:
operation ::= `bgv.modulus_switch` operands attr-dict `:` qualified(type($input)) `->` qualified(type($output))
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
to_ring | ::mlir::heir::polynomial::RingAttr | an attribute specifying a polynomial ring |
Operands:
Operand | Description |
---|---|
input | A ciphertext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
bgv.mul
(heir::bgv::MulOp)
Multiplication operation between ciphertexts.
Syntax:
operation ::= `bgv.mul` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
, Commutative
, InferTypeOpAdaptor
, SameOperandsAndResultRings
, SameTypeOperands
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
lhs | A ciphertext type |
rhs | A ciphertext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
bgv.mul_plain
(heir::bgv::MulPlainOp)
Multiplication operation between ciphertext-plaintext.
Syntax:
operation ::= `bgv.mul_plain` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
ciphertextInput | A ciphertext type |
plaintextInput | A plaintext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
bgv.negate
(heir::bgv::NegateOp)
Negate the coefficients of the ciphertext.
Syntax:
operation ::= `bgv.negate` operands attr-dict `:` qualified(type($output))
Traits: AlwaysSpeculatableImplTrait
, Involution
, SameOperandsAndResultType
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
input | A ciphertext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
bgv.relinearize
(heir::bgv::RelinearizeOp)
Relinearize the ciphertext.
Syntax:
operation ::= `bgv.relinearize` operands attr-dict `:` qualified(type($input)) `->` qualified(type($output))
This op takes integer array attributes from_basis
and to_basis
that are
used to indicate the key basis from which and to which the ciphertext is
encrypted against. A ciphertext is canonically encrypted against key basis
(1, s)
. After a multiplication, its size will increase and the basis will be
(1, s, s^2)
. The array that represents the key basis is constructed by
listing the powers of s
at each position of the array. For example, (1, s, s^2)
corresponds to [0, 1, 2]
, while (1, s^2)
corresponds to [0, 2]
.
Traits: InferTypeOpAdaptor
, SameOperandsAndResultRings
Interfaces: InferTypeOpInterface
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
from_basis | ::mlir::DenseI32ArrayAttr | i32 dense array attribute |
to_basis | ::mlir::DenseI32ArrayAttr | i32 dense array attribute |
Operands:
Operand | Description |
---|---|
input | A ciphertext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
bgv.rotate
(heir::bgv::RotateOp)
Rotate the coefficients of the ciphertext using a Galois automorphism.
Syntax:
operation ::= `bgv.rotate` operands attr-dict `:` qualified(type($input))
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
offset | ::mlir::IntegerAttr | An Attribute containing a integer value |
Operands:
Operand | Description |
---|---|
input | A ciphertext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
bgv.sub
(heir::bgv::SubOp)
Subtraction operation between ciphertexts.
Syntax:
operation ::= `bgv.sub` operands attr-dict `:` qualified(type($output))
Traits: AlwaysSpeculatableImplTrait
, SameOperandsAndResultType
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
lhs | A ciphertext type |
rhs | A ciphertext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
bgv.sub_plain
(heir::bgv::SubPlainOp)
Subtraction operation between ciphertext-plaintext.
Syntax:
operation ::= `bgv.sub_plain` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
ciphertextInput | A ciphertext type |
plaintextInput | A plaintext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
7.2 - CGGI
‘cggi’ Dialect
A dialect for types and operations in the CGGI cryptosystem
CGGI attributes
CGGIBoolGatesAttr
An attribute containing an array of strings to store bool gates
Syntax:
#cggi.cggi_bool_gates<
::llvm::ArrayRef<::mlir::heir::cggi::CGGIBoolGateEnumAttr> # gates
>
This attributes stores a list of integer identifiers for Boolean gates.
Uses following mapping: AND_GATE = 0; NAND_GATE = 1; OR_GATE = 2; NOR_GATE = 3; XOR_GATE = 4; XNOR_GATE = 5; NOT_GATE = 6;
Parameters:
Parameter | C++ type | Description |
---|---|---|
gates | ::llvm::ArrayRef<::mlir::heir::cggi::CGGIBoolGateEnumAttr> |
CGGIParamsAttr
Syntax:
#cggi.cggi_params<
::mlir::heir::lwe::RLWEParamsAttr, # rlweParams
unsigned, # bsk_noise_variance
unsigned, # bsk_gadget_base_log
unsigned, # bsk_gadget_num_levels
unsigned, # ksk_noise_variance
unsigned, # ksk_gadget_base_log
unsigned # ksk_gadget_num_levels
>
Parameters:
Parameter | C++ type | Description |
---|---|---|
rlweParams | ::mlir::heir::lwe::RLWEParamsAttr | |
bsk_noise_variance | unsigned | |
bsk_gadget_base_log | unsigned | |
bsk_gadget_num_levels | unsigned | |
ksk_noise_variance | unsigned | |
ksk_gadget_base_log | unsigned | |
ksk_gadget_num_levels | unsigned |
CGGI ops
cggi.and
(heir::cggi::AndOp)
Logical AND of two ciphertexts.
Syntax:
operation ::= `cggi.and` operands attr-dict `:` qualified(type($output))
Traits: AlwaysSpeculatableImplTrait
, Commutative
, Elementwise
, SameOperandsAndResultType
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
lhs | ciphertext-like |
rhs | ciphertext-like |
Results:
Result | Description |
---|---|
output | ciphertext-like |
cggi.lut2
(heir::cggi::Lut2Op)
A lookup table on two inputs.
Syntax:
operation ::= `cggi.lut2` operands attr-dict `:` qualified(type($output))
An op representing a lookup table applied to some number n
of ciphertexts
encrypting boolean input bits.
Over cleartext bits a, b, c
, using n = 3
for example, the operation
computed by this function can be interpreted as
truth_table >> {c, b, a}
where {c, b, a}
is the unsigned 3-bit integer with bits c, b, a
from most
significant bit to least-significant bit. The input are combined into a
single ciphertext input to the lookup table using products with plaintexts
and sums.
Traits: AlwaysSpeculatableImplTrait
, Commutative
, Elementwise
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, LUTOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
lookup_table | ::mlir::IntegerAttr | An Attribute containing a integer value |
Operands:
Operand | Description |
---|---|
b | ciphertext-like |
a | ciphertext-like |
Results:
Result | Description |
---|---|
output | ciphertext-like |
cggi.lut3
(heir::cggi::Lut3Op)
A lookup table on three inputs.
Syntax:
operation ::= `cggi.lut3` operands attr-dict `:` qualified(type($output))
An op representing a lookup table applied to some number n
of ciphertexts
encrypting boolean input bits.
Over cleartext bits a, b, c
, using n = 3
for example, the operation
computed by this function can be interpreted as
truth_table >> {c, b, a}
where {c, b, a}
is the unsigned 3-bit integer with bits c, b, a
from most
significant bit to least-significant bit. The input are combined into a
single ciphertext input to the lookup table using products with plaintexts
and sums.
Traits: AlwaysSpeculatableImplTrait
, Commutative
, Elementwise
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, LUTOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
lookup_table | ::mlir::IntegerAttr | An Attribute containing a integer value |
Operands:
Operand | Description |
---|---|
c | ciphertext-like |
b | ciphertext-like |
a | ciphertext-like |
Results:
Result | Description |
---|---|
output | ciphertext-like |
cggi.lut_lincomb
(heir::cggi::LutLinCombOp)
A variadic-input lookup table with inputs prepared via linear combination.
Syntax:
operation ::= `cggi.lut_lincomb` operands attr-dict `:` type($output)
An op representing a lookup table applied to an arbitrary number of input ciphertexts, which are combined according to a static linear combination attached to the op.
The user must ensure the chosen linear combination does not bleed error bits into the message space according to the underlying ciphertext’s encoding attributes. E.g., a bit_field_encoding with 3 cleartext bits cannot be multiplied by 16.
Example:
#encoding = #lwe.bit_field_encoding<cleartext_start=30, cleartext_bitwidth=3>
#params = #lwe.lwe_params<cmod=7917, dimension=4>
!ciphertext = !lwe.lwe_ciphertext<encoding = #encoding, lwe_params = #params>
%4 = cggi.lut_lincomb %c0, %c1, %c2, %c3 {coefficients = array<i32: 1, 2, 3, 2>, lookup_table = 68 : index} : !ciphertext
Represents applying the lut
68 >> (1 * c0 + 2 * c1 + 3 * c2 + 2 * c3)
Traits: AlwaysSpeculatableImplTrait
, Commutative
, Elementwise
, SameOperandsAndResultType
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, LUTOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
coefficients | ::mlir::DenseI32ArrayAttr | i32 dense array attribute |
lookup_table | ::mlir::IntegerAttr | An Attribute containing a integer value |
Operands:
Operand | Description |
---|---|
inputs | variadic of any type |
Results:
Result | Description |
---|---|
output | any type |
cggi.multi_lut_lincomb
(heir::cggi::MultiLutLinCombOp)
A multi-output version of lut_lincomb with one LUT per output.
Syntax:
operation ::= `cggi.multi_lut_lincomb` operands attr-dict `:` functional-type($inputs, $outputs)
An op representing multiple lookup tables applied to a shared input, which
is prepared via a static linear combination. This is equivalent to
cggi.lut_lincomb
, but where the linear combination is given to multiple
lookup tables, each producing a separate output.
This can be achieved by a special implementation of blind rotate in the CGGI scheme. See AutoHoG.
Example:
#encoding = #lwe.bit_field_encoding<cleartext_start=30, cleartext_bitwidth=3>
#params = #lwe.lwe_params<cmod=7917, dimension=4>
!ciphertext = !lwe.lwe_ciphertext<encoding = #encoding, lwe_params = #params>
%4 = cggi.multi_lut_lincomb %c0, %c1, %c2, %c3 {
coefficients = array<i32: 1, 2, 3, 2>,
lookup_tables = array<index: 68, 70, 4, 8>
} : (!ciphertext, !ciphertext, !ciphertext, !ciphertext) -> (!ciphertext, !ciphertext, !ciphertext, !ciphertext)
Represents applying the following LUTs. Performance-wise, this is comparable to applying a single LUT to a linear combination.
x = (1 * c0 + 2 * c1 + 3 * c2 + 2 * c3)
return (
(68 >> x) & 1,
(70 >> x) & 1,
(4 >> x) & 1,
(8 >> x) & 1
)
Traits: AlwaysSpeculatableImplTrait
, Commutative
, Elementwise
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
coefficients | ::mlir::DenseI32ArrayAttr | i32 dense array attribute |
lookup_tables | ::mlir::DenseI32ArrayAttr | i32 dense array attribute |
Operands:
Operand | Description |
---|---|
inputs | variadic of A type for LWE ciphertexts |
Results:
Result | Description |
---|---|
outputs | variadic of A type for LWE ciphertexts |
cggi.nand
(heir::cggi::NandOp)
Logical NAND of two ciphertexts.
Syntax:
operation ::= `cggi.nand` operands attr-dict `:` qualified(type($output))
Traits: AlwaysSpeculatableImplTrait
, Commutative
, Elementwise
, SameOperandsAndResultType
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
lhs | ciphertext-like |
rhs | ciphertext-like |
Results:
Result | Description |
---|---|
output | ciphertext-like |
cggi.nor
(heir::cggi::NorOp)
Logical NOR of two ciphertexts.
Syntax:
operation ::= `cggi.nor` operands attr-dict `:` qualified(type($output))
Traits: AlwaysSpeculatableImplTrait
, Commutative
, Elementwise
, SameOperandsAndResultType
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
lhs | ciphertext-like |
rhs | ciphertext-like |
Results:
Result | Description |
---|---|
output | ciphertext-like |
cggi.not
(heir::cggi::NotOp)
Logical NOT of two ciphertexts
Syntax:
operation ::= `cggi.not` operands attr-dict `:` qualified(type($output))
Traits: AlwaysSpeculatableImplTrait
, Elementwise
, Involution
, SameOperandsAndResultType
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
input | ciphertext-like |
Results:
Result | Description |
---|---|
output | ciphertext-like |
cggi.or
(heir::cggi::OrOp)
Logical OR of two ciphertexts.
Syntax:
operation ::= `cggi.or` operands attr-dict `:` qualified(type($output))
Traits: AlwaysSpeculatableImplTrait
, Commutative
, Elementwise
, SameOperandsAndResultType
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
lhs | ciphertext-like |
rhs | ciphertext-like |
Results:
Result | Description |
---|---|
output | ciphertext-like |
cggi.packed_lut3
(heir::cggi::PackedLut3Op)
Syntax:
operation ::= `cggi.packed_lut3` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
, SameOperandsAndResultType
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, LUTOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
lookup_tables | ::mlir::ArrayAttr | Array of integers |
Operands:
Operand | Description |
---|---|
a | ciphertext-like |
b | ciphertext-like |
c | ciphertext-like |
Results:
Result | Description |
---|---|
output | ciphertext-like |
cggi.packed_gates
(heir::cggi::PackedOp)
Syntax:
operation ::= `cggi.packed_gates` operands attr-dict `:` functional-type(operands, results)
Operation to where different Boolean gates are executed pairwise between elements of two ciphertext arrays.
For example,
%0 = cggi.packed_gates %a, %b {gates = #cggi.cggi_gate<"and", "xor">} : tensor<2x!lwe.lwe_ciphertext>
applies an “and” gate to the first elements of %a and %b and an xor gate to the second elements.
Mapping is defined in the BooleanGates.td file.
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
gates | ::mlir::heir::cggi::CGGIBoolGatesAttr | An attribute containing an array of strings to store bool gates |
Operands:
Operand | Description |
---|---|
lhs | ciphertext-like |
rhs | ciphertext-like |
Results:
Result | Description |
---|---|
output | ciphertext-like |
cggi.programmable_bootstrap
(heir::cggi::ProgrammableBootstrapOp)
Programmable Bootstrap with a given lookup table.
Syntax:
operation ::= `cggi.programmable_bootstrap` operands attr-dict `:` qualified(type($output))
An op representing a programmable bootstrap applied to an LWE ciphertext.
This operation evaluates a univariate function homomorphically on the ciphertext by selecting the correct value from a lookup table. The bit size of the lookup table integer attribute should be equal to the plaintext space size. For example, if there ciphertext can hold 3 plaintext message bits, then the lookup table must be represented at most by an integer with 8 bits.
Traits: AlwaysSpeculatableImplTrait
, Elementwise
, SameOperandsAndResultType
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
lookup_table | ::mlir::IntegerAttr | An Attribute containing a integer value |
Operands:
Operand | Description |
---|---|
input | ciphertext-like |
Results:
Result | Description |
---|---|
output | ciphertext-like |
cggi.xnor
(heir::cggi::XNorOp)
Logical XNOR of two ciphertexts.
Syntax:
operation ::= `cggi.xnor` operands attr-dict `:` qualified(type($output))
Traits: AlwaysSpeculatableImplTrait
, Commutative
, Elementwise
, SameOperandsAndResultType
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
lhs | ciphertext-like |
rhs | ciphertext-like |
Results:
Result | Description |
---|---|
output | ciphertext-like |
cggi.xor
(heir::cggi::XorOp)
Logical XOR of two ciphertexts.
Syntax:
operation ::= `cggi.xor` operands attr-dict `:` qualified(type($output))
Traits: AlwaysSpeculatableImplTrait
, Commutative
, Elementwise
, SameOperandsAndResultType
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
lhs | ciphertext-like |
rhs | ciphertext-like |
Results:
Result | Description |
---|---|
output | ciphertext-like |
CGGI additional definitions
AffineMapAttr
An Attribute containing an AffineMap object
Syntax:
affine-map-attribute ::= `affine_map` `<` affine-map `>`
Examples:
affine_map<(d0) -> (d0)>
affine_map<(d0, d1, d2) -> (d0, d1)>
Parameters:
Parameter | C++ type | Description |
---|---|---|
value | AffineMap |
ArrayAttr
A collection of other Attribute values
Syntax:
array-attribute ::= `[` (attribute-value (`,` attribute-value)*)? `]`
An array attribute is an attribute that represents a collection of attribute values.
Examples:
[]
[10, i32]
[affine_map<(d0, d1, d2) -> (d0, d1)>, i32, "string attribute"]
Parameters:
Parameter | C++ type | Description |
---|---|---|
value | ::llvm::ArrayRef<Attribute> |
DenseArrayAttr
A dense array of integer or floating point elements.
A dense array attribute is an attribute that represents a dense array of
primitive element types. Contrary to DenseIntOrFPElementsAttr this is a
flat unidimensional array which does not have a storage optimization for
splat. This allows to expose the raw array through a C++ API as
ArrayRef<T>
for compatible types. The element type must be bool or an
integer or float whose bitwidth is a multiple of 8. Bool elements are stored
as bytes.
This is the base class attribute. Access to C++ types is intended to be
managed through the subclasses DenseI8ArrayAttr
, DenseI16ArrayAttr
,
DenseI32ArrayAttr
, DenseI64ArrayAttr
, DenseF32ArrayAttr
,
and DenseF64ArrayAttr
.
Syntax:
dense-array-attribute ::= `array` `<` (integer-type | float-type)
(`:` tensor-literal)? `>`
Examples:
array<i8>
array<i32: 10, 42>
array<f64: 42., 12.>
When a specific subclass is used as argument of an operation, the declarative assembly will omit the type and print directly:
[1, 2, 3]
Parameters:
Parameter | C++ type | Description |
---|---|---|
elementType | Type | |
size | int64_t | |
rawData | ::llvm::ArrayRef<char> | 64-bit aligned storage for dense array elements |
DenseIntOrFPElementsAttr
An Attribute containing a dense multi-dimensional array of integer or floating-point values
Syntax:
tensor-literal ::= integer-literal | float-literal | bool-literal | [] | [tensor-literal (, tensor-literal)* ]
dense-intorfloat-elements-attribute ::= `dense` `<` tensor-literal `>` `:`
( tensor-type | vector-type )
A dense int-or-float elements attribute is an elements attribute containing
a densely packed vector or tensor of integer or floating-point values. The
element type of this attribute is required to be either an IntegerType
or
a FloatType
.
Examples:
// A splat tensor of integer values.
dense<10> : tensor<2xi32>
// A tensor of 2 float32 elements.
dense<[10.0, 11.0]> : tensor<2xf32>
Parameters:
Parameter | C++ type | Description |
---|---|---|
type | ShapedType | |
rawData | ArrayRef<char> |
DenseResourceElementsAttr
An Attribute containing a dense multi-dimensional array backed by a resource
Syntax:
dense-resource-elements-attribute ::=
`dense_resource` `<` resource-handle `>` `:` shaped-type
A dense resource elements attribute is an elements attribute backed by a
handle to a builtin dialect resource containing a densely packed array of
values. This class provides the low-level attribute, which should only be
interacted with in very generic terms, actual access to the underlying
resource data is intended to be managed through one of the subclasses, such
as; DenseBoolResourceElementsAttr
, DenseUI64ResourceElementsAttr
,
DenseI32ResourceElementsAttr
, DenseF32ResourceElementsAttr
,
DenseF64ResourceElementsAttr
, etc.
Examples:
"example.user_op"() {attr = dense_resource<blob1> : tensor<3xi64> } : () -> ()
{-#
dialect_resources: {
builtin: {
blob1: "0x08000000010000000000000002000000000000000300000000000000"
}
}
#-}
Parameters:
Parameter | C++ type | Description |
---|---|---|
type | ShapedType | |
rawHandle | DenseResourceElementsHandle |
DenseStringElementsAttr
An Attribute containing a dense multi-dimensional array of strings
Syntax:
dense-string-elements-attribute ::= `dense` `<` attribute-value `>` `:`
( tensor-type | vector-type )
A dense string elements attribute is an elements attribute containing a densely packed vector or tensor of string values. There are no restrictions placed on the element type of this attribute, enabling the use of dialect specific string types.
Examples:
// A splat tensor of strings.
dense<"example"> : tensor<2x!foo.string>
// A tensor of 2 string elements.
dense<["example1", "example2"]> : tensor<2x!foo.string>
Parameters:
Parameter | C++ type | Description |
---|---|---|
type | ShapedType | |
value | ArrayRef<StringRef> |
DictionaryAttr
An dictionary of named Attribute values
Syntax:
dictionary-attribute ::= `{` (attribute-entry (`,` attribute-entry)*)? `}`
A dictionary attribute is an attribute that represents a sorted collection of named attribute values. The elements are sorted by name, and each name must be unique within the collection.
Examples:
{}
{attr_name = "string attribute"}
{int_attr = 10, "string attr name" = "string attribute"}
Parameters:
Parameter | C++ type | Description |
---|---|---|
value | ::llvm::ArrayRef<NamedAttribute> |
FloatAttr
An Attribute containing a floating-point value
Syntax:
float-attribute ::= (float-literal (`:` float-type)?)
| (hexadecimal-literal `:` float-type)
A float attribute is a literal attribute that represents a floating point value of the specified float type. It can be represented in the hexadecimal form where the hexadecimal value is interpreted as bits of the underlying binary representation. This form is useful for representing infinity and NaN floating point values. To avoid confusion with integer attributes, hexadecimal literals must be followed by a float type to define a float attribute.
Examples:
42.0 // float attribute defaults to f64 type
42.0 : f32 // float attribute of f32 type
0x7C00 : f16 // positive infinity
0x7CFF : f16 // NaN (one of possible values)
42 : f32 // Error: expected integer type
Parameters:
Parameter | C++ type | Description |
---|---|---|
type | ::mlir::Type | |
value | ::llvm::APFloat |
IntegerAttr
An Attribute containing a integer value
Syntax:
integer-attribute ::= (integer-literal ( `:` (index-type | integer-type) )?)
| `true` | `false`
An integer attribute is a literal attribute that represents an integral
value of the specified integer or index type. i1
integer attributes are
treated as boolean
attributes, and use a unique assembly format of either
true
or false
depending on the value. The default type for non-boolean
integer attributes, if a type is not specified, is signless 64-bit integer.
Examples:
10 : i32
10 // : i64 is implied here.
true // A bool, i.e. i1, value.
false // A bool, i.e. i1, value.
Parameters:
Parameter | C++ type | Description |
---|---|---|
type | ::mlir::Type | |
value | APInt |
IntegerSetAttr
An Attribute containing an IntegerSet object
Syntax:
integer-set-attribute ::= `affine_set` `<` integer-set `>`
Examples:
affine_set<(d0) : (d0 - 2 >= 0)>
Parameters:
Parameter | C++ type | Description |
---|---|---|
value | IntegerSet |
OpaqueAttr
An opaque representation of another Attribute
Syntax:
opaque-attribute ::= dialect-namespace `<` attr-data `>`
Opaque attributes represent attributes of non-registered dialects. These are attribute represented in their raw string form, and can only usefully be tested for attribute equality.
Examples:
#dialect<"opaque attribute data">
Parameters:
Parameter | C++ type | Description |
---|---|---|
dialectNamespace | StringAttr | |
attrData | ::llvm::StringRef | |
type | ::mlir::Type |
SparseElementsAttr
An opaque representation of a multi-dimensional array
Syntax:
sparse-elements-attribute ::= `sparse` `<` attribute-value `,`
attribute-value `>` `:`
( tensor-type | vector-type )
A sparse elements attribute is an elements attribute that represents a sparse vector or tensor object. This is where very few of the elements are non-zero.
The attribute uses COO (coordinate list) encoding to represent the sparse elements of the elements attribute. The indices are stored via a 2-D tensor of 64-bit integer elements with shape [N, ndims], which specifies the indices of the elements in the sparse tensor that contains non-zero values. The element values are stored via a 1-D tensor with shape [N], that supplies the corresponding values for the indices.
Example:
sparse<[[0, 0], [1, 2]], [1, 5]> : tensor<3x4xi32>
// This represents the following tensor:
/// [[1, 0, 0, 0],
/// [0, 0, 5, 0],
/// [0, 0, 0, 0]]
Parameters:
Parameter | C++ type | Description |
---|---|---|
type | ShapedType | |
indices | DenseIntElementsAttr | |
values | DenseElementsAttr |
StringAttr
An Attribute containing a string
Syntax:
string-attribute ::= string-literal (`:` type)?
A string attribute is an attribute that represents a string literal value.
Examples:
"An important string"
"string with a type" : !dialect.string
Parameters:
Parameter | C++ type | Description |
---|---|---|
value | ::llvm::StringRef | |
type | ::mlir::Type |
SymbolRefAttr
An Attribute containing a symbolic reference to an Operation
Syntax:
symbol-ref-attribute ::= symbol-ref-id (`::` symbol-ref-id)*
A symbol reference attribute is a literal attribute that represents a named
reference to an operation that is nested within an operation with the
OpTrait::SymbolTable
trait. As such, this reference is given meaning by
the nearest parent operation containing the OpTrait::SymbolTable
trait. It
may optionally contain a set of nested references that further resolve to a
symbol nested within a different symbol table.
Rationale: Identifying accesses to global data is critical to enabling efficient multi-threaded compilation. Restricting global data access to occur through symbols and limiting the places that can legally hold a symbol reference simplifies reasoning about these data accesses.
See Symbols And SymbolTables
for more
information.
Examples:
@flat_reference
@parent_reference::@nested_reference
Parameters:
Parameter | C++ type | Description |
---|---|---|
rootReference | StringAttr | |
nestedReferences | ::llvm::ArrayRef<FlatSymbolRefAttr> |
TypeAttr
An Attribute containing a Type
Syntax:
type-attribute ::= type
A type attribute is an attribute that represents a type object.
Examples:
i32
!dialect.type
Parameters:
Parameter | C++ type | Description |
---|---|---|
value | Type |
UnitAttr
An Attribute value of unit
type
Syntax:
unit-attribute ::= `unit`
A unit attribute is an attribute that represents a value of unit
type. The
unit
type allows only one value forming a singleton set. This attribute
value is used to represent attributes that only have meaning from their
existence.
One example of such an attribute could be the swift.self
attribute. This
attribute indicates that a function parameter is the self/context parameter.
It could be represented as a boolean attribute(true or
false), but a value of false doesn’t really bring any value. The parameter
either is the self/context or it isn’t.
Examples:
// A unit attribute defined with the `unit` value specifier.
func.func @verbose_form() attributes {dialectName.unitAttr = unit}
// A unit attribute in an attribute dictionary can also be defined without
// the value specifier.
func.func @simple_form() attributes {dialectName.unitAttr}
StridedLayoutAttr
An Attribute representing a strided layout of a shaped type
Syntax:
strided-layout-attribute ::= `strided` `<` `[` stride-list `]`
(`,` `offset` `:` dimension)? `>`
stride-list ::= /*empty*/
| dimension (`,` dimension)*
dimension ::= decimal-literal | `?`
A strided layout attribute captures layout information of the memref type in
the canonical form. Specifically, it contains a list of strides, one for
each dimension. A stride is the number of elements in the linear storage
one must step over to reflect an increment in the given dimension. For
example, a MxN
row-major contiguous shaped type would have the strides
[N, 1]
. The layout attribute also contains the offset from the base
pointer of the shaped type to the first effectively accessed element,
expressed in terms of the number of contiguously stored elements.
Strides must be positive and the offset must be non-negative. Both the
strides and the offset may be dynamic, i.e. their value may not be known
at compile time. This is expressed as a ?
in the assembly syntax and as
ShapedType::kDynamic
in the code. Stride and offset values
must satisfy the constraints above at runtime, the behavior is undefined
otherwise.
See [Dialects/Builtin.md#memreftype](MemRef type) for more information.
Parameters:
Parameter | C++ type | Description |
---|---|---|
offset | int64_t | |
strides | ::llvm::ArrayRef<int64_t> | array of strides (64-bit integer) |
7.3 - CKKS
‘ckks’ Dialect
The CKKS dialect defines the types and operations of the CKKS cryptosystem.
CKKS ops
ckks.add
(heir::ckks::AddOp)
Addition operation between ciphertexts.
Syntax:
operation ::= `ckks.add` operands attr-dict `:` qualified(type($output))
Traits: AlwaysSpeculatableImplTrait
, Commutative
, SameOperandsAndResultType
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
lhs | A ciphertext type |
rhs | A ciphertext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
ckks.add_plain
(heir::ckks::AddPlainOp)
Addition operation between ciphertext-plaintext.
Syntax:
operation ::= `ckks.add_plain` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
ciphertextInput | A ciphertext type |
plaintextInput | A plaintext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
ckks.extract
(heir::ckks::ExtractOp)
Extract the i-th element of a ciphertext.
Syntax:
operation ::= `ckks.extract` operands attr-dict `:` functional-type(operands, results)
While this operation is costly to compute in FHE, we represent it so we can implement efficient lowerings and folders.
This op can be implemented as a plaintext multiplication with a one-hot vector and a rotate into the zero-th index.
An extraction op’s input ciphertext type is asserted to have an underlying_type
corresponding to a ranked tensor type, and this op’s return type is
inferred to have the underlying_type
corresponding to the element type of
that tensor type.
Traits: SameOperandsAndResultRings
Operands:
Operand | Description |
---|---|
input | A ciphertext type |
offset | signless integer or index |
Results:
Result | Description |
---|---|
output | A ciphertext type |
ckks.mul
(heir::ckks::MulOp)
Multiplication operation between ciphertexts.
Syntax:
operation ::= `ckks.mul` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
, Commutative
, InferTypeOpAdaptor
, SameOperandsAndResultRings
, SameTypeOperands
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
lhs | A ciphertext type |
rhs | A ciphertext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
ckks.mul_plain
(heir::ckks::MulPlainOp)
Multiplication operation between ciphertext-plaintext.
Syntax:
operation ::= `ckks.mul_plain` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
ciphertextInput | A ciphertext type |
plaintextInput | A plaintext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
ckks.negate
(heir::ckks::NegateOp)
Negate the coefficients of the ciphertext.
Syntax:
operation ::= `ckks.negate` operands attr-dict `:` qualified(type($output))
Traits: AlwaysSpeculatableImplTrait
, Involution
, SameOperandsAndResultType
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
input | A ciphertext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
ckks.relinearize
(heir::ckks::RelinearizeOp)
Relinearize the ciphertext.
Syntax:
operation ::= `ckks.relinearize` operands attr-dict `:` qualified(type($input)) `->` qualified(type($output))
This op takes integer array attributes from_basis
and to_basis
that are
used to indicate the key basis from which and to which the ciphertext is
encrypted against. A ciphertext is canonically encrypted against key basis
(1, s)
. After a multiplication, its size will increase and the basis will be
(1, s, s^2)
. The array that represents the key basis is constructed by
listing the powers of s
at each position of the array. For example, (1, s, s^2)
corresponds to [0, 1, 2]
, while (1, s^2)
corresponds to [0, 2]
.
Traits: AlwaysSpeculatableImplTrait
, InferTypeOpAdaptor
, SameOperandsAndResultRings
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
from_basis | ::mlir::DenseI32ArrayAttr | i32 dense array attribute |
to_basis | ::mlir::DenseI32ArrayAttr | i32 dense array attribute |
Operands:
Operand | Description |
---|---|
input | A ciphertext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
ckks.rescale
(heir::ckks::RescaleOp)
Rescales the ciphertext, which is the CKKS version of modulus switching in BGV/BFV.
Syntax:
operation ::= `ckks.rescale` operands attr-dict `:` qualified(type($input)) `->` qualified(type($output))
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
to_ring | ::mlir::heir::polynomial::RingAttr | an attribute specifying a polynomial ring |
Operands:
Operand | Description |
---|---|
input | A ciphertext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
ckks.rotate
(heir::ckks::RotateOp)
Rotate the coefficients of the ciphertext using a Galois automorphism.
Syntax:
operation ::= `ckks.rotate` operands attr-dict `:` qualified(type($input))
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
offset | ::mlir::IntegerAttr | An Attribute containing a integer value |
Operands:
Operand | Description |
---|---|
input | A ciphertext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
ckks.sub
(heir::ckks::SubOp)
Subtraction operation between ciphertexts.
Syntax:
operation ::= `ckks.sub` operands attr-dict `:` qualified(type($output))
Traits: SameOperandsAndResultType
Interfaces: InferTypeOpInterface
Operands:
Operand | Description |
---|---|
lhs | A ciphertext type |
rhs | A ciphertext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
ckks.sub_plain
(heir::ckks::SubPlainOp)
Subtraction operation between ciphertext-plaintext.
Syntax:
operation ::= `ckks.sub_plain` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
ciphertextInput | A ciphertext type |
plaintextInput | A plaintext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
7.4 - Comb
‘comb’ Dialect
Types and operations for comb dialect
This dialect defines the comb
dialect, which is intended to be a generic
representation of combinational logic outside of a particular use-case.
Operations
comb.add
(heir::comb::AddOp)
Syntax:
operation ::= `comb.add` (`bin` $twoState^)? $inputs attr-dict `:` qualified(type($result))
Traits: AlwaysSpeculatableImplTrait
, Commutative
, SameOperandsAndResultType
, SameTypeOperands
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
twoState | ::mlir::UnitAttr | unit attribute |
Operands:
Operand | Description |
---|---|
inputs | variadic of signless integer |
Results:
Result | Description |
---|---|
result | signless integer |
comb.and
(heir::comb::AndOp)
Syntax:
operation ::= `comb.and` (`bin` $twoState^)? $inputs attr-dict `:` qualified(type($result))
Traits: AlwaysSpeculatableImplTrait
, Commutative
, SameOperandsAndResultType
, SameTypeOperands
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
twoState | ::mlir::UnitAttr | unit attribute |
Operands:
Operand | Description |
---|---|
inputs | variadic of signless integer |
Results:
Result | Description |
---|---|
result | signless integer |
comb.concat
(heir::comb::ConcatOp)
Concatenate a variadic list of operands together.
Syntax:
operation ::= `comb.concat` $inputs attr-dict `:` qualified(type($inputs))
See the comb rationale document for details on operand ordering.
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
inputs | variadic of signless integer |
Results:
Result | Description |
---|---|
result | signless integer |
comb.extract
(heir::comb::ExtractOp)
Extract a range of bits into a smaller value, lowBit specifies the lowest bit included.
Syntax:
operation ::= `comb.extract` $input `from` $lowBit attr-dict `:` functional-type($input, $result)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
lowBit | ::mlir::IntegerAttr | 32-bit signless integer attribute |
Operands:
Operand | Description |
---|---|
input | signless integer |
Results:
Result | Description |
---|---|
result | signless integer |
comb.icmp
(heir::comb::ICmpOp)
Compare two integer values
Syntax:
operation ::= `comb.icmp` (`bin` $twoState^)? $predicate $lhs `,` $rhs attr-dict `:` qualified(type($lhs))
This operation compares two integers using a predicate. If the predicate is true, returns 1, otherwise returns 0. This operation always returns a one bit wide result.
%r = comb.icmp eq %a, %b : i4
Traits: AlwaysSpeculatableImplTrait
, SameTypeOperands
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
predicate | ::mlir::heir::comb::ICmpPredicateAttr | hw.icmp comparison predicate |
twoState | ::mlir::UnitAttr | unit attribute |
Operands:
Operand | Description |
---|---|
lhs | signless integer |
rhs | signless integer |
Results:
Result | Description |
---|---|
result | 1-bit signless integer |
comb.inv
(heir::comb::InvOp)
Syntax:
operation ::= `comb.inv` (`bin` $twoState^)? $input attr-dict `:` qualified(type($input))
Traits: AlwaysSpeculatableImplTrait
, SameOperandsAndResultType
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
twoState | ::mlir::UnitAttr | unit attribute |
Operands:
Operand | Description |
---|---|
input | signless integer |
Results:
Result | Description |
---|---|
result | signless integer |
comb.mul
(heir::comb::MulOp)
Syntax:
operation ::= `comb.mul` (`bin` $twoState^)? $inputs attr-dict `:` qualified(type($result))
Traits: AlwaysSpeculatableImplTrait
, Commutative
, SameOperandsAndResultType
, SameTypeOperands
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
twoState | ::mlir::UnitAttr | unit attribute |
Operands:
Operand | Description |
---|---|
inputs | variadic of signless integer |
Results:
Result | Description |
---|---|
result | signless integer |
comb.mux
(heir::comb::MuxOp)
Return one or the other operand depending on a selector bit
Syntax:
operation ::= `comb.mux` (`bin` $twoState^)? $cond `,` $trueValue `,` $falseValue attr-dict `:` qualified(type($result))
%0 = mux %pred, %tvalue, %fvalue : i4
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
twoState | ::mlir::UnitAttr | unit attribute |
Operands:
Operand | Description |
---|---|
cond | 1-bit signless integer |
trueValue | any type |
falseValue | any type |
Results:
Result | Description |
---|---|
result | any type |
comb.nand
(heir::comb::NandOp)
Syntax:
operation ::= `comb.nand` (`bin` $twoState^)? $inputs attr-dict `:` qualified(type($result))
Traits: AlwaysSpeculatableImplTrait
, SameOperandsAndResultType
, SameTypeOperands
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
twoState | ::mlir::UnitAttr | unit attribute |
Operands:
Operand | Description |
---|---|
inputs | variadic of signless integer |
Results:
Result | Description |
---|---|
result | signless integer |
comb.nor
(heir::comb::NorOp)
Syntax:
operation ::= `comb.nor` (`bin` $twoState^)? $inputs attr-dict `:` qualified(type($result))
Traits: AlwaysSpeculatableImplTrait
, SameOperandsAndResultType
, SameTypeOperands
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
twoState | ::mlir::UnitAttr | unit attribute |
Operands:
Operand | Description |
---|---|
inputs | variadic of signless integer |
Results:
Result | Description |
---|---|
result | signless integer |
comb.or
(heir::comb::OrOp)
Syntax:
operation ::= `comb.or` (`bin` $twoState^)? $inputs attr-dict `:` qualified(type($result))
Traits: AlwaysSpeculatableImplTrait
, Commutative
, SameOperandsAndResultType
, SameTypeOperands
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
twoState | ::mlir::UnitAttr | unit attribute |
Operands:
Operand | Description |
---|---|
inputs | variadic of signless integer |
Results:
Result | Description |
---|---|
result | signless integer |
comb.parity
(heir::comb::ParityOp)
Syntax:
operation ::= `comb.parity` (`bin` $twoState^)? $input attr-dict `:` qualified(type($input))
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
twoState | ::mlir::UnitAttr | unit attribute |
Operands:
Operand | Description |
---|---|
input | signless integer |
Results:
Result | Description |
---|---|
result | 1-bit signless integer |
comb.replicate
(heir::comb::ReplicateOp)
Concatenate the operand a constant number of times
Syntax:
operation ::= `comb.replicate` $input attr-dict `:` functional-type($input, $result)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
input | signless integer |
Results:
Result | Description |
---|---|
result | signless integer |
comb.truth_table
(heir::comb::TruthTableOp)
Return a true/false based on a lookup table
Syntax:
operation ::= `comb.truth_table` $inputs `->` $lookupTable attr-dict
%a = ... : i1
%b = ... : i1
%0 = comb.truth_table %a, %b -> 6 : ui4
This operation assumes that the lookup table is described as an integer of
2^n bits to fully specify the table. Inputs are sorted MSB -> LSB from left
to right and the offset into lookupTable
is computed from them. The
integer containing the truth table value’s LSB is the output for the input
“all false”, and the MSB is the output for the input “all true”.
No difference from array_get into an array of constants except for xprop behavior. If one of the inputs is unknown, but said input doesn’t make a difference in the output (based on the lookup table) the result should not be ‘x’ – it should be the well-known result.
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, LUTOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
lookupTable | ::mlir::IntegerAttr | An Attribute containing a integer value |
Operands:
Operand | Description |
---|---|
inputs | variadic of 1-bit signless integer |
Results:
Result | Description |
---|---|
result | 1-bit signless integer |
comb.xnor
(heir::comb::XNorOp)
Syntax:
operation ::= `comb.xnor` (`bin` $twoState^)? $inputs attr-dict `:` qualified(type($result))
Traits: AlwaysSpeculatableImplTrait
, SameOperandsAndResultType
, SameTypeOperands
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
twoState | ::mlir::UnitAttr | unit attribute |
Operands:
Operand | Description |
---|---|
inputs | variadic of signless integer |
Results:
Result | Description |
---|---|
result | signless integer |
comb.xor
(heir::comb::XorOp)
Syntax:
operation ::= `comb.xor` (`bin` $twoState^)? $inputs attr-dict `:` qualified(type($result))
Traits: AlwaysSpeculatableImplTrait
, Commutative
, SameOperandsAndResultType
, SameTypeOperands
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
twoState | ::mlir::UnitAttr | unit attribute |
Operands:
Operand | Description |
---|---|
inputs | variadic of signless integer |
Results:
Result | Description |
---|---|
result | signless integer |
Enums
ICmpPredicate
hw.icmp comparison predicate
Cases:
Symbol | Value | String |
---|---|---|
eq | 0 | eq |
ne | 1 | ne |
slt | 2 | slt |
sle | 3 | sle |
sgt | 4 | sgt |
sge | 5 | sge |
ult | 6 | ult |
ule | 7 | ule |
ugt | 8 | ugt |
uge | 9 | uge |
ceq | 10 | ceq |
cne | 11 | cne |
weq | 12 | weq |
wne | 13 | wne |
Comb types
Comb ops
comb.add
(heir::comb::AddOp)
Syntax:
operation ::= `comb.add` (`bin` $twoState^)? $inputs attr-dict `:` qualified(type($result))
Traits: AlwaysSpeculatableImplTrait
, Commutative
, SameOperandsAndResultType
, SameTypeOperands
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
twoState | ::mlir::UnitAttr | unit attribute |
Operands:
Operand | Description |
---|---|
inputs | variadic of signless integer |
Results:
Result | Description |
---|---|
result | signless integer |
comb.and
(heir::comb::AndOp)
Syntax:
operation ::= `comb.and` (`bin` $twoState^)? $inputs attr-dict `:` qualified(type($result))
Traits: AlwaysSpeculatableImplTrait
, Commutative
, SameOperandsAndResultType
, SameTypeOperands
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
twoState | ::mlir::UnitAttr | unit attribute |
Operands:
Operand | Description |
---|---|
inputs | variadic of signless integer |
Results:
Result | Description |
---|---|
result | signless integer |
comb.concat
(heir::comb::ConcatOp)
Concatenate a variadic list of operands together.
Syntax:
operation ::= `comb.concat` $inputs attr-dict `:` qualified(type($inputs))
See the comb rationale document for details on operand ordering.
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
inputs | variadic of signless integer |
Results:
Result | Description |
---|---|
result | signless integer |
comb.extract
(heir::comb::ExtractOp)
Extract a range of bits into a smaller value, lowBit specifies the lowest bit included.
Syntax:
operation ::= `comb.extract` $input `from` $lowBit attr-dict `:` functional-type($input, $result)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
lowBit | ::mlir::IntegerAttr | 32-bit signless integer attribute |
Operands:
Operand | Description |
---|---|
input | signless integer |
Results:
Result | Description |
---|---|
result | signless integer |
comb.icmp
(heir::comb::ICmpOp)
Compare two integer values
Syntax:
operation ::= `comb.icmp` (`bin` $twoState^)? $predicate $lhs `,` $rhs attr-dict `:` qualified(type($lhs))
This operation compares two integers using a predicate. If the predicate is true, returns 1, otherwise returns 0. This operation always returns a one bit wide result.
%r = comb.icmp eq %a, %b : i4
Traits: AlwaysSpeculatableImplTrait
, SameTypeOperands
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
predicate | ::mlir::heir::comb::ICmpPredicateAttr | hw.icmp comparison predicate |
twoState | ::mlir::UnitAttr | unit attribute |
Operands:
Operand | Description |
---|---|
lhs | signless integer |
rhs | signless integer |
Results:
Result | Description |
---|---|
result | 1-bit signless integer |
comb.inv
(heir::comb::InvOp)
Syntax:
operation ::= `comb.inv` (`bin` $twoState^)? $input attr-dict `:` qualified(type($input))
Traits: AlwaysSpeculatableImplTrait
, SameOperandsAndResultType
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
twoState | ::mlir::UnitAttr | unit attribute |
Operands:
Operand | Description |
---|---|
input | signless integer |
Results:
Result | Description |
---|---|
result | signless integer |
comb.mul
(heir::comb::MulOp)
Syntax:
operation ::= `comb.mul` (`bin` $twoState^)? $inputs attr-dict `:` qualified(type($result))
Traits: AlwaysSpeculatableImplTrait
, Commutative
, SameOperandsAndResultType
, SameTypeOperands
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
twoState | ::mlir::UnitAttr | unit attribute |
Operands:
Operand | Description |
---|---|
inputs | variadic of signless integer |
Results:
Result | Description |
---|---|
result | signless integer |
comb.mux
(heir::comb::MuxOp)
Return one or the other operand depending on a selector bit
Syntax:
operation ::= `comb.mux` (`bin` $twoState^)? $cond `,` $trueValue `,` $falseValue attr-dict `:` qualified(type($result))
%0 = mux %pred, %tvalue, %fvalue : i4
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
twoState | ::mlir::UnitAttr | unit attribute |
Operands:
Operand | Description |
---|---|
cond | 1-bit signless integer |
trueValue | any type |
falseValue | any type |
Results:
Result | Description |
---|---|
result | any type |
comb.nand
(heir::comb::NandOp)
Syntax:
operation ::= `comb.nand` (`bin` $twoState^)? $inputs attr-dict `:` qualified(type($result))
Traits: AlwaysSpeculatableImplTrait
, SameOperandsAndResultType
, SameTypeOperands
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
twoState | ::mlir::UnitAttr | unit attribute |
Operands:
Operand | Description |
---|---|
inputs | variadic of signless integer |
Results:
Result | Description |
---|---|
result | signless integer |
comb.nor
(heir::comb::NorOp)
Syntax:
operation ::= `comb.nor` (`bin` $twoState^)? $inputs attr-dict `:` qualified(type($result))
Traits: AlwaysSpeculatableImplTrait
, SameOperandsAndResultType
, SameTypeOperands
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
twoState | ::mlir::UnitAttr | unit attribute |
Operands:
Operand | Description |
---|---|
inputs | variadic of signless integer |
Results:
Result | Description |
---|---|
result | signless integer |
comb.or
(heir::comb::OrOp)
Syntax:
operation ::= `comb.or` (`bin` $twoState^)? $inputs attr-dict `:` qualified(type($result))
Traits: AlwaysSpeculatableImplTrait
, Commutative
, SameOperandsAndResultType
, SameTypeOperands
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
twoState | ::mlir::UnitAttr | unit attribute |
Operands:
Operand | Description |
---|---|
inputs | variadic of signless integer |
Results:
Result | Description |
---|---|
result | signless integer |
comb.parity
(heir::comb::ParityOp)
Syntax:
operation ::= `comb.parity` (`bin` $twoState^)? $input attr-dict `:` qualified(type($input))
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
twoState | ::mlir::UnitAttr | unit attribute |
Operands:
Operand | Description |
---|---|
input | signless integer |
Results:
Result | Description |
---|---|
result | 1-bit signless integer |
comb.replicate
(heir::comb::ReplicateOp)
Concatenate the operand a constant number of times
Syntax:
operation ::= `comb.replicate` $input attr-dict `:` functional-type($input, $result)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
input | signless integer |
Results:
Result | Description |
---|---|
result | signless integer |
comb.truth_table
(heir::comb::TruthTableOp)
Return a true/false based on a lookup table
Syntax:
operation ::= `comb.truth_table` $inputs `->` $lookupTable attr-dict
%a = ... : i1
%b = ... : i1
%0 = comb.truth_table %a, %b -> 6 : ui4
This operation assumes that the lookup table is described as an integer of
2^n bits to fully specify the table. Inputs are sorted MSB -> LSB from left
to right and the offset into lookupTable
is computed from them. The
integer containing the truth table value’s LSB is the output for the input
“all false”, and the MSB is the output for the input “all true”.
No difference from array_get into an array of constants except for xprop behavior. If one of the inputs is unknown, but said input doesn’t make a difference in the output (based on the lookup table) the result should not be ‘x’ – it should be the well-known result.
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, LUTOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
lookupTable | ::mlir::IntegerAttr | An Attribute containing a integer value |
Operands:
Operand | Description |
---|---|
inputs | variadic of 1-bit signless integer |
Results:
Result | Description |
---|---|
result | 1-bit signless integer |
comb.xnor
(heir::comb::XNorOp)
Syntax:
operation ::= `comb.xnor` (`bin` $twoState^)? $inputs attr-dict `:` qualified(type($result))
Traits: AlwaysSpeculatableImplTrait
, SameOperandsAndResultType
, SameTypeOperands
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
twoState | ::mlir::UnitAttr | unit attribute |
Operands:
Operand | Description |
---|---|
inputs | variadic of signless integer |
Results:
Result | Description |
---|---|
result | signless integer |
comb.xor
(heir::comb::XorOp)
Syntax:
operation ::= `comb.xor` (`bin` $twoState^)? $inputs attr-dict `:` qualified(type($result))
Traits: AlwaysSpeculatableImplTrait
, Commutative
, SameOperandsAndResultType
, SameTypeOperands
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
twoState | ::mlir::UnitAttr | unit attribute |
Operands:
Operand | Description |
---|---|
inputs | variadic of signless integer |
Results:
Result | Description |
---|---|
result | signless integer |
7.5 - Jaxite
‘jaxite’ Dialect
The jaxite
dialect is an exit dialect for generating py code against the jaxite library API,
using the jaxite parameters and encoding scheme.
See https://github.com/google/jaxite
Jaxite types
ParamsType
The jaxite security params required to perform homomorphic operations.
Syntax: !jaxite.params
PmapLut3TupleType
A tuple of pmap_lut3 args.
Syntax: !jaxite.pmap_lut3_tuple
ServerKeySetType
The jaxite server key set required to perform homomorphic operations.
Syntax: !jaxite.server_key_set
Jaxite ops
jaxite.constant
(heir::jaxite::ConstantOp)
Syntax:
operation ::= `jaxite.constant` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
value | 1-bit signless integer |
params | The jaxite security params required to perform homomorphic operations. |
Results:
Result | Description |
---|---|
output | A type for LWE ciphertexts |
jaxite.lut3_args
(heir::jaxite::Lut3ArgsOp)
Syntax:
operation ::= `jaxite.lut3_args` operands attr-dict `:` functional-type(operands, results)
The operation computed by this function is generating tuples for pmap_lut3.
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
a | A type for LWE ciphertexts |
b | A type for LWE ciphertexts |
c | A type for LWE ciphertexts |
truth_table | 8-bit signless integer |
Results:
Result | Description |
---|---|
output | A tuple of pmap_lut3 args. |
jaxite.lut3
(heir::jaxite::Lut3Op)
Syntax:
operation ::= `jaxite.lut3` operands attr-dict `:` functional-type(operands, results)
The operation computed by this function can be interpreted as
truth_table » {c, b, a}
where {c, b, a} is the unsigned 3-bit integer with bits c, b, a from most significant bit to least-significant bit.
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
a | A type for LWE ciphertexts |
b | A type for LWE ciphertexts |
c | A type for LWE ciphertexts |
truth_table | 8-bit signless integer |
serverKeySet | The jaxite server key set required to perform homomorphic operations. |
params | The jaxite security params required to perform homomorphic operations. |
Results:
Result | Description |
---|---|
output | A type for LWE ciphertexts |
jaxite.pmap_lut3
(heir::jaxite::PmapLut3Op)
Syntax:
operation ::= `jaxite.pmap_lut3` operands attr-dict `:` functional-type(operands, results)
The operation computed by this function is a bacthed Lut3Op.
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
lut3_args | pmap_lut3_tuples |
serverKeySet | The jaxite server key set required to perform homomorphic operations. |
params | The jaxite security params required to perform homomorphic operations. |
Results:
Result | Description |
---|---|
output | ciphertext-like |
7.6 - Lattigo
Lattigo attributes
BGVParametersLiteralAttr
Literal parameters for Lattigo BGV
Syntax:
#lattigo.bgv.parameters_literal<
int, # logN
DenseI64ArrayAttr, # Q
DenseI64ArrayAttr, # P
DenseI32ArrayAttr, # logQ
DenseI32ArrayAttr, # logP
int64_t # plaintextModulus
>
This attribute represents the literal parameters for Lattigo BGV.
This is in accordance with https://pkg.go.dev/github.com/tuneinsight/lattigo/v6@v6.1.0/schemes/bgv#ParametersLiteral where some field are not present in the current implementation.
Users must set the polynomial degree (LogN) and the coefficient modulus, by either setting the Q and P fields to the desired moduli chain, or by setting the LogQ and LogP fields to the desired moduli sizes.
Note that for Lattigo, Q/P requires []uint64, where this attribute only provides int64. We assume user should not select moduli so large to consider the signedness issue.
Users must also specify the coefficient modulus in plaintext-space (T). This modulus must be an NTT-friendly prime in the plaintext space: it must be equal to 1 modulo 2n where n is the plaintext ring degree (i.e., the plaintext space has n slots).
Parameters:
Parameter | C++ type | Description |
---|---|---|
logN | int | |
Q | DenseI64ArrayAttr | |
P | DenseI64ArrayAttr | |
logQ | DenseI32ArrayAttr | |
logP | DenseI32ArrayAttr | |
plaintextModulus | int64_t |
Lattigo types
BGVEncoderType
Syntax: !lattigo.bgv.encoder
This type represents the encoder for the BGV encryption scheme.
BGVEvaluatorType
Syntax: !lattigo.bgv.evaluator
This type represents the evaluator for the BGV encryption scheme.
BGVParameterType
Syntax: !lattigo.bgv.parameter
This type represents the parameters for the BGV encryption scheme.
RLWECiphertextType
Syntax: !lattigo.rlwe.ciphertext
This type represents the ciphertext for the RLWE encryption scheme.
RLWEDecryptorType
Syntax: !lattigo.rlwe.decryptor
This type represents the decryptor for the RLWE encryption scheme.
RLWEEncryptorType
Syntax: !lattigo.rlwe.encryptor
This type represents the encryptor for the RLWE encryption scheme.
RLWEEvaluationKeySetType
Syntax: !lattigo.rlwe.evaluation_key_set
This type represents the evaluation key set for the RLWE encryption scheme.
RLWEGaloisKeyType
Syntax:
!lattigo.rlwe.galois_key<
::mlir::IntegerAttr # galoisElement
>
This type represents the Galois key for the RLWE encryption scheme.
galoisElement: Enabling the automorphism X -> X^{galoisElement}.
Parameters:
Parameter | C++ type | Description |
---|---|---|
galoisElement | ::mlir::IntegerAttr | An Attribute containing a integer value |
RLWEKeyGeneratorType
Syntax: !lattigo.rlwe.key_generator
This type represents the key generator for the RLWE encryption scheme.
RLWEPlaintextType
Syntax: !lattigo.rlwe.plaintext
This type represents the plaintext for the RLWE encryption scheme.
RLWEPublicKeyType
Syntax: !lattigo.rlwe.public_key
This type represents the public key for the RLWE encryption scheme.
RLWERelinearizationKeyType
Syntax: !lattigo.rlwe.relinearization_key
This type represents the relinearization key for the RLWE encryption scheme.
RLWESecretKeyType
Syntax: !lattigo.rlwe.secret_key
This type represents the secret key for the RLWE encryption scheme.
Lattigo ops
lattigo.bgv.add
(heir::lattigo::BGVAddOp)
Add two ciphertexts in the Lattigo BGV dialect
Syntax:
operation ::= `lattigo.bgv.add` operands attr-dict `:` functional-type(operands, results)
This operation adds two ciphertext values in the Lattigo BGV dialect.
Operands:
Operand | Description |
---|---|
evaluator | |
lhs | |
rhs |
Results:
Result | Description |
---|---|
output |
lattigo.bgv.decode
(heir::lattigo::BGVDecodeOp)
Decode a plaintext value in the Lattigo BGV dialect
Syntax:
operation ::= `lattigo.bgv.decode` operands attr-dict `:` functional-type(operands, results)
This operation decodes a plaintext value using the specified encoder in the Lattigo BGV dialect.
Operands:
Operand | Description |
---|---|
encoder | |
plaintext | |
value | any type |
Results:
Result | Description |
---|---|
decoded | any type |
lattigo.bgv.encode
(heir::lattigo::BGVEncodeOp)
Encode a plaintext value in the Lattigo BGV dialect
Syntax:
operation ::= `lattigo.bgv.encode` operands attr-dict `:` functional-type(operands, results)
This operation encodes a plaintext value using the specified encoder in the Lattigo BGV dialect.
Operands:
Operand | Description |
---|---|
encoder | |
value | any type |
plaintext |
Results:
Result | Description |
---|---|
encoded |
lattigo.bgv.mul
(heir::lattigo::BGVMulOp)
Multiply two ciphertexts in the Lattigo BGV dialect
Syntax:
operation ::= `lattigo.bgv.mul` operands attr-dict `:` functional-type(operands, results)
This operation multiplies two ciphertext values in the Lattigo BGV dialect.
Operands:
Operand | Description |
---|---|
evaluator | |
lhs | |
rhs |
Results:
Result | Description |
---|---|
output |
lattigo.bgv.new_encoder
(heir::lattigo::BGVNewEncoderOp)
Create a new encoder in the Lattigo BGV dialect
Syntax:
operation ::= `lattigo.bgv.new_encoder` operands attr-dict `:` functional-type(operands, results)
This operation creates a new encoder for encoding plaintext values in the Lattigo BGV dialect.
Operands:
Operand | Description |
---|---|
params |
Results:
Result | Description |
---|---|
encoder |
lattigo.bgv.new_evaluator
(heir::lattigo::BGVNewEvaluatorOp)
Create a new evaluator in the Lattigo BGV dialect
Syntax:
operation ::= `lattigo.bgv.new_evaluator` operands attr-dict `:` functional-type(operands, results)
This operation creates a new evaluator for performing operations on ciphertexts in the Lattigo BGV dialect.
By default, the evaluator is created with the provided parameters and could execute operations which does not relying on evaluation keys.
To support operations that require evaluation keys, the optional evaluation key set should be provided.
Operands:
Operand | Description |
---|---|
params | |
evaluationKeySet |
Results:
Result | Description |
---|---|
evaluator |
lattigo.bgv.new_parameters_from_literal
(heir::lattigo::BGVNewParametersFromLiteralOp)
Create new BGV parameters from a literal in the Lattigo BGV dialect
Syntax:
operation ::= `lattigo.bgv.new_parameters_from_literal` operands attr-dict `:` functional-type(operands, results)
This operation creates new BGV parameters from a given literal value in the Lattigo BGV dialect.
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
paramsLiteral | ::mlir::heir::lattigo::BGVParametersLiteralAttr | Literal parameters for Lattigo BGV |
Results:
Result | Description |
---|---|
params |
lattigo.bgv.new_plaintext
(heir::lattigo::BGVNewPlaintextOp)
Create a new plaintext in the Lattigo BGV dialect
Syntax:
operation ::= `lattigo.bgv.new_plaintext` operands attr-dict `:` functional-type(operands, results)
This operation creates a new plaintext value in the Lattigo BGV dialect.
Operands:
Operand | Description |
---|---|
params |
Results:
Result | Description |
---|---|
plaintext |
lattigo.bgv.relinearize
(heir::lattigo::BGVRelinearizeOp)
Relinearize a ciphertext in the Lattigo BGV dialect
Syntax:
operation ::= `lattigo.bgv.relinearize` operands attr-dict `:` functional-type(operands, results)
This operation relinearizes a ciphertext value in the Lattigo BGV dialect.
Operands:
Operand | Description |
---|---|
evaluator | |
input |
Results:
Result | Description |
---|---|
output |
lattigo.bgv.rescale
(heir::lattigo::BGVRescaleOp)
Rescale a ciphertext in the Lattigo BGV dialect
Syntax:
operation ::= `lattigo.bgv.rescale` operands attr-dict `:` functional-type(operands, results)
This operation rescales a ciphertext value in the Lattigo BGV dialect.
Operands:
Operand | Description |
---|---|
evaluator | |
input |
Results:
Result | Description |
---|---|
output |
lattigo.bgv.rotate_columns
(heir::lattigo::BGVRotateColumnsOp)
Rotate columns of a ciphertext in the Lattigo BGV dialect
Syntax:
operation ::= `lattigo.bgv.rotate_columns` operands attr-dict `:` functional-type(operands, results)
This operation rotates the columns of a ciphertext value in the Lattigo BGV dialect.
Lattigo exposes the SIMD slot of BGV as a N/2 x 2 matrix, where N/2 is the column.
offset is valid in (-N/2, N/2).
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
offset | ::mlir::IntegerAttr | An Attribute containing a integer value |
Operands:
Operand | Description |
---|---|
evaluator | |
input |
Results:
Result | Description |
---|---|
output |
lattigo.bgv.rotate_rows
(heir::lattigo::BGVRotateRowsOp)
Rotate rows of a ciphertext in the Lattigo BGV dialect
Syntax:
operation ::= `lattigo.bgv.rotate_rows` operands attr-dict `:` functional-type(operands, results)
This operation swap the rows of a ciphertext value in the Lattigo BGV dialect.
Lattigo exposes the SIMD slot of BGV as a N/2 x 2 matrix, where 2 is the row.
Operands:
Operand | Description |
---|---|
evaluator | |
input |
Results:
Result | Description |
---|---|
output |
lattigo.bgv.sub
(heir::lattigo::BGVSubOp)
Subtract two ciphertexts in the Lattigo BGV dialect
Syntax:
operation ::= `lattigo.bgv.sub` operands attr-dict `:` functional-type(operands, results)
This operation subtracts one ciphertext value from another in the Lattigo BGV dialect.
Operands:
Operand | Description |
---|---|
evaluator | |
lhs | |
rhs |
Results:
Result | Description |
---|---|
output |
lattigo.rlwe.decrypt
(heir::lattigo::RLWEDecryptOp)
Decrypts a ciphertext using RLWE
Syntax:
operation ::= `lattigo.rlwe.decrypt` operands attr-dict `:` functional-type(operands, results)
This operation decrypts a ciphertext using RLWE
Operands:
Operand | Description |
---|---|
decryptor | |
ciphertext |
Results:
Result | Description |
---|---|
plaintext |
lattigo.rlwe.encrypt
(heir::lattigo::RLWEEncryptOp)
Encrypts a plaintext using RLWE
Syntax:
operation ::= `lattigo.rlwe.encrypt` operands attr-dict `:` functional-type(operands, results)
This operation encrypts a plaintext using RLWE
Operands:
Operand | Description |
---|---|
encryptor | |
plaintext |
Results:
Result | Description |
---|---|
ciphertext |
lattigo.rlwe.gen_galois_key
(heir::lattigo::RLWEGenGaloisKeyOp)
Generates a new RLWE Galois key
Syntax:
operation ::= `lattigo.rlwe.gen_galois_key` operands attr-dict `:` functional-type(operands, results)
This operation generates a new RLWE Galois key
galoisElement: Enabling the automorphism X -> X^{galoisElement}.
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
galoisElement | ::mlir::IntegerAttr | An Attribute containing a integer value |
Operands:
Operand | Description |
---|---|
keyGenerator | |
secretKey |
Results:
Result | Description |
---|---|
galoisKey |
lattigo.rlwe.gen_key_pair
(heir::lattigo::RLWEGenKeyPairOp)
Generates a new RLWE key pair
Syntax:
operation ::= `lattigo.rlwe.gen_key_pair` operands attr-dict `:` functional-type(operands, results)
This operation generates a new RLWE key pair
Operands:
Operand | Description |
---|---|
keyGenerator |
Results:
Result | Description |
---|---|
secretKey | |
publicKey |
lattigo.rlwe.gen_relinearization_key
(heir::lattigo::RLWEGenRelinearizationKeyOp)
Generates a new RLWE relinearization key
Syntax:
operation ::= `lattigo.rlwe.gen_relinearization_key` operands attr-dict `:` functional-type(operands, results)
This operation generates a new RLWE relinearization key
Operands:
Operand | Description |
---|---|
keyGenerator | |
secretKey |
Results:
Result | Description |
---|---|
relinearizationKey |
lattigo.rlwe.new_decryptor
(heir::lattigo::RLWENewDecryptorOp)
Creates a new RLWE decryptor
Syntax:
operation ::= `lattigo.rlwe.new_decryptor` operands attr-dict `:` functional-type(operands, results)
This operation creates a new RLWE decryptor
Operands:
Operand | Description |
---|---|
params | |
secretKey |
Results:
Result | Description |
---|---|
decryptor |
lattigo.rlwe.new_encryptor
(heir::lattigo::RLWENewEncryptorOp)
Creates a new RLWE encryptor
Syntax:
operation ::= `lattigo.rlwe.new_encryptor` operands attr-dict `:` functional-type(operands, results)
This operation creates a new RLWE encryptor
Operands:
Operand | Description |
---|---|
params | |
publicKey |
Results:
Result | Description |
---|---|
encryptor |
lattigo.rlwe.new_evaluation_key_set
(heir::lattigo::RLWENewEvaluationKeySetOp)
Generates a new RLWE evaluation key set
Syntax:
operation ::= `lattigo.rlwe.new_evaluation_key_set` operands attr-dict `:` functional-type(operands, results)
This operation generates a new RLWE evaluation key set
Operands:
Operand | Description |
---|---|
relinearizationKey | |
galoisKeys | variadic of |
Results:
Result | Description |
---|---|
evaluationKeySet |
lattigo.rlwe.new_key_generator
(heir::lattigo::RLWENewKeyGeneratorOp)
Generates a new RLWE key generator
Syntax:
operation ::= `lattigo.rlwe.new_key_generator` operands attr-dict `:` functional-type(operands, results)
This operation generates a new RLWE key generator
Operands:
Operand | Description |
---|---|
params |
Results:
Result | Description |
---|---|
keyGenerator |
7.7 - LWE
LWE attributes
ApplicationDataAttr
Syntax:
#lwe.application_data<
mlir::Type, # message_type
Attribute # overflow
>
An attribute describing the semantics of the underlying application data.
The messageType
parameter is used to describe the type and bits of the
original application data, e.g. i1, i32, f32. This type is later mapped
into the plaintext space of an FHE scheme by embedding, scaling, or other
techniques.
This attribute also contains information about the overflow semantics of the
data in the application. By default, we assume that the application program
was written so that the overflow is not expected and the overflow attribute
can can be no_overflow
. For LWE-based CGGI ciphertexts, the overflow
attribute will usually be preserve_overflow
, since messages will overflow
into padding bits.
Parameters:
Parameter | C++ type | Description |
---|---|---|
message_type | mlir::Type | |
overflow | Attribute |
BitFieldEncodingAttr
An attribute describing encoded LWE plaintexts using bit fields.
Syntax:
#lwe.bit_field_encoding<
unsigned, # cleartext_start
unsigned # cleartext_bitwidth
>
A bit field encoding of an integer describes which contiguous region of bits a small integer occupies within a larger integer.
The data describing the encoding consists of the starting bit positions of
the cleartext bit field and its width, where the LSB is bit 0 and the MSB
is bit bit_width-1
. So the above example would have starting bit 30
and
width 3
. The bits not specified for the message have semantics defined
by the scheme or lowering.
Note that this encoding does not specify the underlying bit width of the plaintext space. This is left for lowerings to decide.
The presence of this attribute as the encoding
attribute of a tensor
indicates that the tensor is an LWE ciphertext.
Example (CGGI):
#encoding = #lwe.bit_field_encoding<cleartext_start=30, cleartext_bitwidth=3>
!plaintext = !lwe.lwe_plaintext<encoding = #encoding>
%0 = arith.constant 4 : i3
%1 = lwe.encode %0 { encoding = #encoding }: i3 to !plaintext
The above represents an LWE plaintext encoding the 3-bit cleartext 4 as an
LWE ciphertext in a 32-bit integer, with a single bit of padding at the MSB.
This corresponds to the following, where 0 denotes a 0 bit, b
denotes a
bit of the cleartext, n
denotes a bit reserved for noise, and |
is a
visual aid to show where the bit fields begin and end.
0|bbb|nn...n
MSB^ ^LSB
Example (BGV):
Note: BGV uses the RLWE encodings, but they have the same bit-field encoding attributes as here. So this example serves mainly to show how this attribute can be used to specify storing bits in the LSB of a plaintext.
#encoding = #lwe.bit_field_encoding<cleartext_start=4, cleartext_bitwidth=4>
!plaintext = !lwe.lwe_plaintext<encoding = #encoding>
%0 = arith.constant 9 : i4
%1 = lwe.encode %0 { encoding = #encoding }: i4 to !plaintext
The above represents an LWE plaintext encoding a 4-bit cleartext as an LWE ciphertext in the least-significant bits of a larger integer. This corresponds to the following.
nn...n|bbbb
MSB^ ^LSB
Parameters:
Parameter | C++ type | Description |
---|---|---|
cleartext_start | unsigned | |
cleartext_bitwidth | unsigned |
CiphertextSpaceAttr
Syntax:
#lwe.ciphertext_space<
::mlir::heir::polynomial::RingAttr, # ring
::mlir::heir::lwe::LweEncryptionType, # encryption_type
unsigned # size
>
An attribute describing the ciphertext space and the transformation from plaintext space to ciphertext space of an FHE scheme.
The ciphertext space information includes the ring attribute, describing the space that the ciphertext elements belong to. The ring attribute contains a coefficient type attribute that describes the semantics of the coefficient. For example, a ring modulo $1 + x^1024$ with coefficients modulo $q = 298374$ will be described as
!ideal = !polynomial.int_polynomial<1 + x**1024>
!cmod = !mod_arith.mod_arith<modulus=298374 : i64>
#ring = #polynomial.ring<coefficientType = !cmod, modulus = !ideal>
#ciphertext_space = #lwe.ciphertext_space<ring = #ring, encryption_type = #encryption_type>
Ciphertexts using an RNS representation for $q$ will use an RNS type in their ring’s coefficient type attribute.
// TODO(#1085): Validate syntax of polynomial ring after coefficientType changes.
!ideal = !polynomial.int_polynomial<1 + x**1024>
!limb1 = !mod_arith.mod_arith<modulus=2251799814045697 : i64>
!limb2 = !mod_arith.mod_arith<modulus=65537 : i64>
#rns_mod = !rns.rns<!limb1, !limb2>
#ring = #polynomial.ring<coefficientType = #rns_mod, modulus = #ideal>
#ciphertext_space = #lwe.ciphertext_space<ring = #ring, encryption_type = #encryption_type>
Scalar LWE ciphertexts (like those used in CGGI) use an ideal polynomial of degree 1, $x$. CGGI ciphertexts will typically use a power of two modulus and may use a native integer type for its coefficient modulus.
!ideal = !polynomial.int_polynomial<1 + x**1024>
#ring = #polynomial.ring<coefficientType = i32, modulus = #ideal>
#ciphertext_space = #lwe.ciphertext_space<ring = #ring, encryption_type = #encryption_type>
The ciphertext encoding info is used to describe the way the plaintext data is encoded into the ciphertext (in the MSB, LSB, or mixed).
The size
parameter is used to describe the number of polynomials
comprising the ciphertext. This is typically 2 for RLWE ciphertexts that
are made up of an $(a, b)$ pair and greater than 2 for LWE instances. For
example, after an RLWE multiplication of two size 2 ciphertexts,
the ciphertext’s size will be 3.
Parameters:
Parameter | C++ type | Description |
---|---|---|
ring | ::mlir::heir::polynomial::RingAttr | |
encryption_type | ::mlir::heir::lwe::LweEncryptionType | |
size | unsigned |
CoefficientEncodingAttr
An encoding of cleartexts directly as coefficients.
Syntax:
#lwe.coefficient_encoding<
unsigned # scaling_factor
>
A coefficient encoding of a list of integers asserts that the coefficients
of the polynomials contain the integers, with the same semantics as
constant_coefficient_encoding
for per-coefficient encodings.
A scaling_factor
is optionally applied on the scalar when converting from
a rounded floating point to an integer.
Example:
#coeff_encoding = #lwe.coefficient_encoding<scaling_factor=10000>
Parameters:
Parameter | C++ type | Description |
---|---|---|
scaling_factor | unsigned |
ConstantCoefficientEncodingAttr
An encoding of a scalar in the constant coefficient
Syntax:
#lwe.constant_coefficient_encoding<
unsigned # scaling_factor
>
An encoding of a single scalar into the constant coefficient of the plaintext.
All other coefficients of the plaintext are set to be zero. This encoding is
used to encode scalar LWE ciphertexts where the plaintext space is viewed
as a polynomial ring modulo x
.
The scalar is first multiplied by the scaling_factor
and then rounded to
the nearest integer before encoding into the plaintext coefficient.
Example:
#coeff_encoding = #lwe.constant_coefficient_encoding<scaling_factor=10000>
Parameters:
Parameter | C++ type | Description |
---|---|---|
scaling_factor | unsigned |
FullCRTPackingEncodingAttr
An encoding of cleartexts via CRT slots.
Syntax:
#lwe.full_crt_packing_encoding<
unsigned # scaling_factor
>
This encoding maps a list of integers via the Chinese Remainder Theorem (CRT) into the plaintext space.
Given a ring with irreducible ideal polynomial f(x)
and coefficient
modulus q
, f(x)
can be decomposed modulo q
into a direct product of
lower-degree polynomials. This allows full SIMD-style homomorphic operations
across the slots formed from each factor.
This attribute can only be used in the context of on full CRT packing, where
the polynomial f(x)
splits completely (into linear factors) and the number
of slots equals the degree of f(x)
. This happens when q
is prime and q = 1 mod n
.
A scaling_factor
is optionally applied on the scalar when converting from
a rounded floating point to an integer.
Example:
#coeff_encoding = #lwe.full_crt_packing_encoding<scaling_factor=10000>
Parameters:
Parameter | C++ type | Description |
---|---|---|
scaling_factor | unsigned |
InverseCanonicalEncodingAttr
An encoding of cleartexts via the inverse canonical embedding.
Syntax:
#lwe.inverse_canonical_encoding<
unsigned # scaling_factor
>
Let $n$ be the degree of the polynomials in the plaintext space. An “inverse_canonical_encoding” of a list of real or complex values $v_1, \dots, v_{n/2}$ is (almost) the inverse of the following decoding map.
Define a map $\tau_N$ that maps a polynomial $p \in \mathbb{Z}[x] / (x^N + 1) \to \mathbb{C}^{N/2}$ by evaluating it at the following $N/2$ points, where $\omega = e^{2 \pi i / 2N}$ is the primitive $2N$th root of unity:
[ \omega, \omega^3, \omega^5, \dots, \omega^{N-1} ]
Then the complete decoding operation is $\textup{Decode}(p) = (1/\Delta)\tau_N(p)$, where $\Delta$ is a scaling parameter and $\tau_N$ is the truncated canonical embedding above. The encoding operation is the inverse of the decoding operation, with some caveats explained below.
The map $\tau_N$ is derived from the so-called canonical embedding $\tau$, though in the standard canonical embedding, we evaluate at all odd powers of the root of unity, $\omega, \omega^3, \dots, \omega^{2N-1}$. For polynomials in the slightly larger space $\mathbb{R}[x] / (x^N + 1)$, the image of the canonical embedding is the subspace $H \subset \mathbb{C}^N$ defined by tuples $(z_1, \dots, z_N)$ such that $\overline{z_i} = \overline{z_{N-i+1}}$. Note that this property holds because polynomial evaluation commutes with complex conjugates, and the second half of the roots of unity evaluate are complex conjugates of the first half. The converse, that any such tuple with complex conjugate symmetry has an inverse under $\tau$ with all real coefficients, makes $\tau$ is a bijection onto $H$. $\tau$ and its inverse are explicitly computable as discrete Fourier Transforms.
Because of the symmetry in canonical embedding for real polynomials, inputs to this encoding can be represented as a list of $N/2$ complex points, with the extra symmetric structure left implicit. $\tau_N$ and its inverse can also be explicitly computed without need to expand the vectors to length $N$.
The rounding step is required to invert the decoding because, while cleartexts must be (implicitly) in the subspace $H$, they need not be the output of $\tau_N$ for an integer polynomial. The rounding step ensures we can use integer polynomial plaintexts for the FHE operations. There are multiple rounding mechanisms, and this attribute does not specify which is used, because in theory two ciphertexts that have used different roundings are still compatible, though they may have different noise growth patterns.
The scaling parameter $\Delta$ is specified by the scaling_factor
, which
are applied coefficient-wise using the same semantics as the
constant_coefficient_encoding
.
A typical flow for the CKKS scheme using this encoding would be to apply an inverse FFT operation to invert the canonical embedding to be a polynomial with real coefficients, then encrypt scale the resulting polynomial’s coefficients according to the scaling parameters, then round to get integer coefficients.
Example:
#canonical_encoding = #lwe.inverse_canonical_encoding<scaling_factor=10000>
Parameters:
Parameter | C++ type | Description |
---|---|---|
scaling_factor | unsigned |
KeyAttr
Syntax:
#lwe.key<
int # slot_index
>
An attribute describing the key with which the message is currently encrypted.
The key attribute describes the key with which the message is currently encrypted and decryption can be performed. For example, if the decryption of a ciphertext $c = (c_0(x), c_1(x))$ is performed by computing the inner product $(c_0(x), c_1(x)) \cdot (1, s(x))$ then the key is $(1, s(x))$.
The slot_index
describes the key after using a Galois automorphism to
rotate the plaintext slots by slot_index
. This will correspond to an
action $\phi_k: x \rightarrow x^k$ for some k
that depends on the
structure of the Galois group for the chosen scheme parameters. The
corresponding key will have a new basis $(1, s(x^(k)))$.
Parameters:
Parameter | C++ type | Description |
---|---|---|
slot_index | int |
LWEParamsAttr
Syntax:
#lwe.lwe_params<
IntegerAttr, # cmod
unsigned # dimension
>
Parameters:
Parameter | C++ type | Description |
---|---|---|
cmod | IntegerAttr | |
dimension | unsigned |
ModulusChainAttr
Syntax:
#lwe.modulus_chain<
::llvm::ArrayRef<mlir::IntegerAttr>, # elements
int # current
>
An attribute describing the elements of the modulus chain of an RLWE scheme.
Parameters:
Parameter | C++ type | Description |
---|---|---|
elements | ::llvm::ArrayRef<mlir::IntegerAttr> | |
current | int |
NoOverflowAttr
An attribute informing that application data never overflows.
Syntax: #lwe.no_overflow
This attribute informs lowerings that a program is written so that the message data will never overflow beyond the message type.
// FIXME: Have a separate WraparoundOverflow, which lowers the same as NoOverflow?
PlaintextSpaceAttr
Syntax:
#lwe.plaintext_space<
::mlir::heir::polynomial::RingAttr, # ring
Attribute # encoding
>
An attribute describing the plaintext space and the transformation from application data to plaintext space of an FHE scheme.
The plaintext space information is the ring structure, which contains the plaintext modulus $t$, which may be a power of two in the case of CGGI ciphertexts, or a prime power for RLWE. LWE ciphertexts use the ideal polynomial of degree 1 $x$. The plaintext modulus used in LWE-based CGGI plaintexts describes the full message space $\mathbb{Z}_p$ including the padding bits. The application data info attribute describes the space $\mathbb{Z}_p’$ where $p’ < p$ that the underlying message belongs to.
For RLWE schemes, this will include the type of encoding of application data
integers to a plaintext space Z_p[X]/X^N + 1
. This may be a constant
coefficient encoding, CRT-based packing for SIMD semantics, or other slot
packing. When using full CRT packing, the ring must split into linear
factors. The CKKS scheme will also include attributes describing the complex
encoding, including the scaling factor, which will change after
multiplication and rescaling.
Parameters:
Parameter | C++ type | Description |
---|---|---|
ring | ::mlir::heir::polynomial::RingAttr | |
encoding | Attribute | An encoding of a scalar in the constant coefficient or An encoding of cleartexts directly as coefficients. or An encoding of cleartexts via the inverse canonical embedding. or An encoding of cleartexts via CRT slots. |
PreserveOverflowAttr
An attribute informing that application data overflows in the message type.
Syntax: #lwe.preserve_overflow
This attribute informs lowerings that a program is written so that the message data may overflow beyond the message type.
RLWEParamsAttr
Syntax:
#lwe.rlwe_params<
unsigned, # dimension
::mlir::heir::polynomial::RingAttr # ring
>
An attribute describing classical RLWE parameters:
dimension
: the number of polynomials used in an RLWE sample, analogous to LWEParams.dimension.ring
: the polynomial ring to use.
Parameters:
Parameter | C++ type | Description |
---|---|---|
dimension | unsigned | |
ring | ::mlir::heir::polynomial::RingAttr |
UnspecifiedBitFieldEncodingAttr
An attribute describing unspecified bit field encodings.
Syntax:
#lwe.unspecified_bit_field_encoding<
unsigned # cleartext_bitwidth
>
See LWE_BitFieldEncoding for a description of bit field encodings.
This attribute describes an unspecified bit field encoding; this is where the starting bit position of the cleartext bit field is unspecified, but its width is fixed. A noise growth analysis should be performed to determine the optimal amount of bits needed for noise and padding to specify the bit field encodings starting bit position.
Example:
#lwe_encoding = #lwe.unspecified_bit_field_encoding<cleartext_bitwidth=3>
%lwe_ciphertext = arith.constant <[1,2,3,4]> : tensor<4xi32, #lwe_encoding>
Parameters:
Parameter | C++ type | Description |
---|---|---|
cleartext_bitwidth | unsigned |
LWE types
LWECiphertextType
A type for LWE ciphertexts
Syntax:
!lwe.lwe_ciphertext<
::mlir::Attribute, # encoding
LWEParamsAttr # lwe_params
>
A type for LWE ciphertexts.
This type keeps track of the plaintext integer encoding for the LWE Ciphertext to ensure proper decoding after decryption. It also keeps track of the ring where the LWE ciphertext is defined, which provides information on the ciphertext shape and the ring operations used in LWE operations.
Parameters:
Parameter | C++ type | Description |
---|---|---|
encoding | ::mlir::Attribute | |
lwe_params | LWEParamsAttr |
LWEPlaintextType
A type for LWE plaintexts
Syntax:
!lwe.lwe_plaintext<
::mlir::Attribute # encoding
>
A type for LWE plaintexts.
This type keeps track of the plaintext integer encoding for the LWE plaintext before it is encrypted.
Parameters:
Parameter | C++ type | Description |
---|---|---|
encoding | ::mlir::Attribute |
NewLWECiphertextType
A ciphertext type
Syntax:
!lwe.new_lwe_ciphertext<
ApplicationDataAttr, # application_data
PlaintextSpaceAttr, # plaintext_space
CiphertextSpaceAttr, # ciphertext_space
KeyAttr, # key
ModulusChainAttr # modulus_chain
>
An LWE ciphertext will always contain the application data, plaintext space, ciphertext space, and key information.
A modulus chain is optionally specified for parameter choices in RLWE schemes that use more than one of modulus. When no modulus chain is specified, the ciphertext modulus is always the ciphertext ring’s coefficient modulus.
Parameters:
Parameter | C++ type | Description |
---|---|---|
application_data | ApplicationDataAttr | |
plaintext_space | PlaintextSpaceAttr | |
ciphertext_space | CiphertextSpaceAttr | |
key | KeyAttr | |
modulus_chain | ModulusChainAttr |
NewLWEPlaintextType
A plaintext type
Syntax:
!lwe.new_lwe_plaintext<
ApplicationDataAttr, # application_data
PlaintextSpaceAttr # plaintext_space
>
Parameters:
Parameter | C++ type | Description |
---|---|---|
application_data | ApplicationDataAttr | |
plaintext_space | PlaintextSpaceAttr |
NewLWEPublicKeyType
A public key for LWE
Syntax:
!lwe.new_lwe_public_key<
KeyAttr, # key
::mlir::heir::polynomial::RingAttr # ring
>
Parameters:
Parameter | C++ type | Description |
---|---|---|
key | KeyAttr | |
ring | ::mlir::heir::polynomial::RingAttr |
NewLWESecretKeyType
A secret key for LWE
Syntax:
!lwe.new_lwe_secret_key<
KeyAttr, # key
::mlir::heir::polynomial::RingAttr # ring
>
Parameters:
Parameter | C++ type | Description |
---|---|---|
key | KeyAttr | |
ring | ::mlir::heir::polynomial::RingAttr |
RLWECiphertextType
A type for RLWE ciphertexts
Syntax:
!lwe.rlwe_ciphertext<
::mlir::Attribute, # encoding
RLWEParamsAttr, # rlwe_params
Type # underlying_type
>
Parameters:
Parameter | C++ type | Description |
---|---|---|
encoding | ::mlir::Attribute | |
rlwe_params | RLWEParamsAttr | |
underlying_type | Type |
RLWEPlaintextType
A type for RLWE plaintexts
Syntax:
!lwe.rlwe_plaintext<
::mlir::Attribute, # encoding
::mlir::heir::polynomial::RingAttr, # ring
Type # underlying_type
>
Parameters:
Parameter | C++ type | Description |
---|---|---|
encoding | ::mlir::Attribute | |
ring | ::mlir::heir::polynomial::RingAttr | |
underlying_type | Type |
RLWEPublicKeyType
A public key for RLWE
Syntax:
!lwe.rlwe_public_key<
RLWEParamsAttr # rlwe_params
>
Parameters:
Parameter | C++ type | Description |
---|---|---|
rlwe_params | RLWEParamsAttr |
RLWESecretKeyType
A secret key for RLWE
Syntax:
!lwe.rlwe_secret_key<
RLWEParamsAttr # rlwe_params
>
Parameters:
Parameter | C++ type | Description |
---|---|---|
rlwe_params | RLWEParamsAttr |
LWE ops
lwe.add
(heir::lwe::AddOp)
Add two LWE ciphertexts
Syntax:
operation ::= `lwe.add` operands attr-dict `:` type($output)
Traits: AlwaysSpeculatableImplTrait
, Commutative
, Elementwise
, SameOperandsAndResultType
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
lhs | A type for LWE ciphertexts |
rhs | A type for LWE ciphertexts |
Results:
Result | Description |
---|---|
output | A type for LWE ciphertexts |
lwe.encode
(heir::lwe::EncodeOp)
Encode an integer to yield an LWE plaintext
Syntax:
operation ::= `lwe.encode` $input attr-dict `:` qualified(type($input)) `to` qualified(type($output))
Encode an integer to yield an LWE plaintext.
This op uses a an encoding attribute to encode the bits of the integer into an LWE plaintext value that can then be encrypted.
Examples:
%Y = lwe.encode %value {encoding = #enc}: i1 to !lwe.lwe_plaintext<encoding = #enc>
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
encoding | ::mlir::Attribute | An attribute describing encoded LWE plaintexts using bit fields. or An attribute describing unspecified bit field encodings. |
Operands:
Operand | Description |
---|---|
input | signless-integer-like or floating-point-like |
Results:
Result | Description |
---|---|
output | A type for LWE plaintexts |
lwe.mul_scalar
(heir::lwe::MulScalarOp)
Multiply an LWE ciphertext by a scalar
Syntax:
operation ::= `lwe.mul_scalar` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
, Elementwise
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
ciphertext | ciphertext-like |
scalar | integer |
Results:
Result | Description |
---|---|
output | ciphertext-like |
lwe.radd
(heir::lwe::RAddOp)
Add two RLWE ciphertexts
Syntax:
operation ::= `lwe.radd` operands attr-dict `:` type($output)
Traits: AlwaysSpeculatableImplTrait
, Commutative
, Elementwise
, SameOperandsAndResultType
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
lhs | A ciphertext type |
rhs | A ciphertext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
lwe.rlwe_decode
(heir::lwe::RLWEDecodeOp)
Decode an RLWE plaintext to an underlying type
Syntax:
operation ::= `lwe.rlwe_decode` $input attr-dict `:` qualified(type($input)) `->` qualified(type($output))
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
encoding | ::mlir::Attribute | An encoding of a scalar in the constant coefficient or An encoding of cleartexts directly as coefficients. or An encoding of cleartexts via the inverse canonical embedding. or An encoding of cleartexts via CRT slots. |
ring | ::mlir::heir::polynomial::RingAttr | an attribute specifying a polynomial ring |
Operands:
Operand | Description |
---|---|
input | A plaintext type |
Results:
Result | Description |
---|---|
output | signless-integer-like or floating-point-like |
lwe.rlwe_decrypt
(heir::lwe::RLWEDecryptOp)
Decrypt an RLWE ciphertext to a RLWE plaintext
Syntax:
operation ::= `lwe.rlwe_decrypt` operands attr-dict `:` functional-type(operands, results)
Decrypt an RLWE ciphertext to yield a RLWE plaintext
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
input | A ciphertext type |
secret_key | A secret key for LWE |
Results:
Result | Description |
---|---|
output | A plaintext type |
lwe.rlwe_encode
(heir::lwe::RLWEEncodeOp)
Encode an integer to yield an RLWE plaintext
Syntax:
operation ::= `lwe.rlwe_encode` $input attr-dict `:` qualified(type($input)) `->` qualified(type($output))
Encode an integer to yield an RLWE plaintext.
This op uses a an encoding attribute to encode the bits of the integer into an RLWE plaintext value that can then be encrypted. CKKS cleartext inputs may be floating points, and a scaling factor described by the encoding will be applied.
Examples:
%Y = lwe.rlwe_encode %value {encoding = #enc, ring = #ring}: i1 to !lwe.rlwe_plaintext<encoding = #enc, ring = #ring>
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
encoding | ::mlir::Attribute | An encoding of a scalar in the constant coefficient or An encoding of cleartexts directly as coefficients. or An encoding of cleartexts via the inverse canonical embedding. or An encoding of cleartexts via CRT slots. |
ring | ::mlir::heir::polynomial::RingAttr | an attribute specifying a polynomial ring |
Operands:
Operand | Description |
---|---|
input | signless-integer-like or floating-point-like |
Results:
Result | Description |
---|---|
output | A plaintext type |
lwe.rlwe_encrypt
(heir::lwe::RLWEEncryptOp)
Encrypt an RLWE plaintext to a RLWE ciphertext
Syntax:
operation ::= `lwe.rlwe_encrypt` operands attr-dict `:` functional-type(operands, results)
Encrypt an RLWE plaintext to yield a RLWE ciphertext.
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
input | A plaintext type |
key | A secret key for LWE or A public key for LWE |
Results:
Result | Description |
---|---|
output | A ciphertext type |
lwe.rmul
(heir::lwe::RMulOp)
Multiplies two RLWE ciphertexts
Syntax:
operation ::= `lwe.rmul` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
, Commutative
, Elementwise
, InferTypeOpAdaptor
, SameTypeOperands
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
lhs | A ciphertext type |
rhs | A ciphertext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
lwe.rnegate
(heir::lwe::RNegateOp)
Negate a RLWE ciphertexts
Syntax:
operation ::= `lwe.rnegate` operands attr-dict `:` type($output)
Traits: AlwaysSpeculatableImplTrait
, Elementwise
, SameOperandsAndResultType
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
input | new-lwe-ciphertext-like |
Results:
Result | Description |
---|---|
output | new-lwe-ciphertext-like |
lwe.rsub
(heir::lwe::RSubOp)
Subtract two RLWE ciphertexts
Syntax:
operation ::= `lwe.rsub` operands attr-dict `:` type($output)
Traits: AlwaysSpeculatableImplTrait
, Elementwise
, SameOperandsAndResultType
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
lhs | A ciphertext type |
rhs | A ciphertext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
lwe.trivial_encrypt
(heir::lwe::TrivialEncryptOp)
Create a trivial encryption of a plaintext.
Syntax:
operation ::= `lwe.trivial_encrypt` operands attr-dict `:` qualified(type(operands)) `to` qualified(type(results))
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
params | ::mlir::heir::lwe::LWEParamsAttr |
Operands:
Operand | Description |
---|---|
input | A type for LWE plaintexts |
Results:
Result | Description |
---|---|
output | A type for LWE ciphertexts |
lwe.reinterpret_underlying_type
(heir::lwe::ReinterpretUnderlyingTypeOp)
A placeholder cast from one ciphertext type to another
Syntax:
operation ::= `lwe.reinterpret_underlying_type` $input attr-dict `:` qualified(type($input)) `to` qualified(type($output))
The cast
op is thus used to translate underlying_type
between
ciphertexts in particular situations , such as when lowering to an API that
does not keep track of types for you.
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
input | A ciphertext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
7.8 - Mgmt
Mgmt attributes
MgmtAttr
Container attribute for all mgmt parameter
Syntax:
#mgmt.mgmt<
int, # level
int # dimension
>
This attribute is used to store all mgmt parameters.
The attribute is a struct with the following fields:
level
: the level of the ciphertext, from L to 0dimension
: the dimension of the ciphertext, defaults to 2
Internally, this attribute is used by secret-to-
It should be populated by –secret-with-mgmt-
Example:
#mgmt = #mgmt.mgmt<level = 1> // dimension defaults to 2
#mgmt1 = #mgmt.mgmt<level = 1, dimension = 3>
%0 = secret.generic ins(%arg0, %arg1 : !secret.secret<i16>) attrs = {mgmt.mgmt = #mgmt} {
...
} -> !secret.secret<i16>
Parameters:
Parameter | C++ type | Description |
---|---|---|
level | int | |
dimension | int |
Mgmt ops
mgmt.modreduce
(heir::mgmt::ModReduceOp)
Modulus switch the input ciphertext down by one limb (RNS assumed)
Syntax:
operation ::= `mgmt.modreduce` operands attr-dict `:` type($output)
This is scheme-agonistic operation that implies modulus switching/rescaling by one limb.
Input ciphertext is assumed to be in RNS form when further lowered.
When further lowered, it could be lowered to bgv.modulus_switch or ckks.rescale depending on the scheme.
Traits: AlwaysSpeculatableImplTrait
, Elementwise
, SameOperandsAndResultType
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
input | any type |
Results:
Result | Description |
---|---|
output | any type |
mgmt.relinearize
(heir::mgmt::RelinearizeOp)
Relinearize the input ciphertext to be linear
Syntax:
operation ::= `mgmt.relinearize` operands attr-dict `:` type($output)
This is scheme-agonistic operation that implies relinearization of the input ciphertext to be linear (i.e. returns to dimension 2).
This is used solely by multiplication. For rotation, currently HEIR assumes relinearization is done internally and does not have a separate scheme-specific operation for it.
This accepts a ciphertext with dimension > 2 and returns a ciphertext with dimension 2. Note that the semantic includes the relinearization of higher dimension input like input with dimension 4 or higher, which when materialized should require multiple relinearization keys.
When further lowered, it could be lowered to bgv.relinearize or ckks.relinearize depending on the scheme.
Traits: AlwaysSpeculatableImplTrait
, Elementwise
, SameOperandsAndResultType
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
input | any type |
Results:
Result | Description |
---|---|
output | any type |
7.9 - ModArith
ModArith attributes
ModArithAttr
a typed mod_arith attribute
Syntax:
#mod_arith.int<
::mlir::heir::mod_arith::ModArithType, # type
mlir::IntegerAttr # value
>
Example:
#attr = 123:i32
#attr_verbose = #mod_arith.int<123:i32>
Parameters:
Parameter | C++ type | Description |
---|---|---|
type | ::mlir::heir::mod_arith::ModArithType | |
value | mlir::IntegerAttr |
ModArith types
ModArithType
Integer type with modular arithmetic
Syntax:
!mod_arith.int<
::mlir::IntegerAttr # modulus
>
mod_arith.int<p>
represents an element of the ring of integers modulo $p$.
The modulus
attribute is the ring modulus, and mod_arith
operations lower to
arith
operations that produce results in the range [0, modulus)
, often called
the canonical representative.
modulus
is specified with an integer type suffix, for example,
mod_arith.int<65537 : i32>
. This corresponds to the storage type for the
modulus, and is i64
by default.
It is required that the underlying integer type should be larger than
twice the modulus (have one extra bit of storage space) to avoid signedness
issues. For example, when modulus == 2 ** 32 - 1
, the underlying type
for the modulus should be at least i33
, though i64
is a natural choice.
Passes may allow intermediate values that do not always produce a
canonical representative in [0, modulus)
. For example, if the machine storage
type is i64
, but the modulus
fits within an i32
, a lowering could
allow intermediate arithmetic values to grow to as large as an i64
before
reducing them. However, all passes must ensure that values used outside
the local scope (e.g., function return values or arguments to calls to linked
functions) are appropriately reduced to the canonical representative.
modulus
is the modulus the arithmetic working with.
Examples:
!Zp1 = !mod_arith.int<7> // implicitly being i64
!Zp2 = !mod_arith.int<65537 : i32>
!Zp3 = !mod_arith.int<536903681 : i64>
Parameters:
Parameter | C++ type | Description |
---|---|---|
modulus | ::mlir::IntegerAttr |
ModArith ops
mod_arith.add
(heir::mod_arith::AddOp)
Modular addition operation
Syntax:
operation ::= `mod_arith.add` operands attr-dict `:` type($output)
Computes modular addition.
Unless otherwise specified, the operation assumes both inputs are canonical representatives and guarantees the output being canonical representative.
Traits: AlwaysSpeculatableImplTrait
, Commutative
, Elementwise
, SameOperandsAndResultType
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
lhs | mod_arith-like |
rhs | mod_arith-like |
Results:
Result | Description |
---|---|
output | mod_arith-like |
mod_arith.barrett_reduce
(heir::mod_arith::BarrettReduceOp)
Compute the first step of the Barrett reduction.
Syntax:
operation ::= `mod_arith.barrett_reduce` operands attr-dict `:` qualified(type($input))
Let $q$ denote a statically known modulus and $b = 4^{w}$, where $w$ is the
smallest bit-width that contains the range $[0, q)$. The Barrett reduce
operation computes barret_reduce x = x - floor(x * floor(b / q) / b) * q
.
Given $0 <= x < q^2$, then this will compute $(x \mod q)$ or $(x \mod q) + q$.
Traits: SameOperandsAndResultType
Interfaces: InferTypeOpInterface
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
modulus | ::mlir::IntegerAttr | arbitrary integer attribute |
Operands:
Operand | Description |
---|---|
input | signless-integer-like |
Results:
Result | Description |
---|---|
output | signless-integer-like |
mod_arith.constant
(heir::mod_arith::ConstantOp)
Define a constant value via an attribute.
Example:
%0 = mod_arith.constant 123 : !mod_arith.int<65537:i32>
Traits: AlwaysSpeculatableImplTrait
, InferTypeOpAdaptor
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
value | ::mlir::heir::mod_arith::ModArithAttr | a typed mod_arith attribute |
Results:
Result | Description |
---|---|
output | Integer type with modular arithmetic |
mod_arith.encapsulate
(heir::mod_arith::EncapsulateOp)
Encapsulate an integer into a mod_arith type
Syntax:
operation ::= `mod_arith.encapsulate` operands attr-dict `:` type($input) `->` type($output)
mod_arith.encapsulate
converts the integer to be of mod_arith type.
Examples:
mod_arith.encapsulate %c0 : i32 -> mod_arith.int<65537 : i32>
mod_arith.encapsulate %c1 : i64 -> mod_arith.int<65537>
Traits: AlwaysSpeculatableImplTrait
, Elementwise
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
input | signless-integer-like |
Results:
Result | Description |
---|---|
output | mod_arith-like |
mod_arith.extract
(heir::mod_arith::ExtractOp)
Extract the integer stored inside mod_arith type
Syntax:
operation ::= `mod_arith.extract` operands attr-dict `:` type($input) `->` type($output)
mod_arith.extract
extracts the integer inside the mod_arith type.
It is required that the bitwidth of the output integer type is the same as that of the storage type of the input mod_arith type.
Examples:
%m0 = mod_arith.encapsulate %c0 : i32 -> mod_arith.int<65537 : i32>
%m1 = mod_arith.encapsulate %c1 : i64 -> mod_arith.int<65537>
%c2 = mod_arith.extract %m0 : mod_arith.int<65537 : i32> -> i32
%c3 = mod_arith.extract %m1 : mod_arith.int<65537> -> i64
Traits: AlwaysSpeculatableImplTrait
, Elementwise
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
input | mod_arith-like |
Results:
Result | Description |
---|---|
output | signless-integer-like |
mod_arith.mac
(heir::mod_arith::MacOp)
Modular multiplication-and-accumulation operation
Syntax:
operation ::= `mod_arith.mac` operands attr-dict `:` type($output)
mod_arith.mac x, y, z
computes $(x * y) + z$
Unless otherwise specified, the operation assumes all inputs are canonical representatives and guarantees the output being canonical representative.
Traits: AlwaysSpeculatableImplTrait
, Elementwise
, SameOperandsAndResultType
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
lhs | mod_arith-like |
rhs | mod_arith-like |
acc | mod_arith-like |
Results:
Result | Description |
---|---|
output | mod_arith-like |
mod_arith.mod_switch
(heir::mod_arith::ModSwitchOp)
Change the modulus of a mod_arith
Syntax:
operation ::= `mod_arith.mod_switch` $input attr-dict `:` type($input) `to` type($output)
“mod_switch” operation to change the modulus of a mod_arith type to a bigger space.
Examples:
`mod_arith.mod_switch %c0 : mod_arith.int<65537 : i32> to mod_arith.int<65539 : i32>`
Traits: AlwaysSpeculatableImplTrait
, SameOperandsAndResultShape
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
input | Integer type with modular arithmetic |
Results:
Result | Description |
---|---|
output | Integer type with modular arithmetic |
mod_arith.mul
(heir::mod_arith::MulOp)
Modular multiplication operation
Syntax:
operation ::= `mod_arith.mul` operands attr-dict `:` type($output)
Computes modular multiplication.
Unless otherwise specified, the operation assumes both inputs are canonical representatives and guarantees the output being canonical representative.
Traits: AlwaysSpeculatableImplTrait
, Commutative
, Elementwise
, SameOperandsAndResultType
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
lhs | mod_arith-like |
rhs | mod_arith-like |
Results:
Result | Description |
---|---|
output | mod_arith-like |
mod_arith.reduce
(heir::mod_arith::ReduceOp)
Reduce the mod arith type to its canonical representative
Syntax:
operation ::= `mod_arith.reduce` operands attr-dict `:` type($output)
mod_arith.reduce x
produces $y$, the canonical representative in $[0, q)$
such that $x \equiv y \mod q$.
Examples:
%c0 = arith.constant 65538 : i32
%m0 = mod_arith.encapsulate %c0 : i32 -> mod_arith.int<65537 : i32>
// mod_arith.extract %m0 produces 65538
%m1 = mod_arith.reduce %m0 : mod_arith.int<65537: i32>
// mod_arith.extract %m1 produces 1
Traits: AlwaysSpeculatableImplTrait
, Elementwise
, SameOperandsAndResultType
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
input | mod_arith-like |
Results:
Result | Description |
---|---|
output | mod_arith-like |
mod_arith.subifge
(heir::mod_arith::SubIfGEOp)
Compute (x >= y) ? x - y : x.
Syntax:
operation ::= `mod_arith.subifge` operands attr-dict `:` qualified(type($output))
Traits: SameOperandsAndResultType
Interfaces: InferTypeOpInterface
Operands:
Operand | Description |
---|---|
lhs | signless-integer-like |
rhs | signless-integer-like |
Results:
Result | Description |
---|---|
output | signless-integer-like |
mod_arith.sub
(heir::mod_arith::SubOp)
Modular subtraction operation
Syntax:
operation ::= `mod_arith.sub` operands attr-dict `:` type($output)
Computes modular subtraction.
Unless otherwise specified, the operation assumes both inputs are canonical representatives and guarantees the output being canonical representative.
Traits: AlwaysSpeculatableImplTrait
, Elementwise
, SameOperandsAndResultType
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
lhs | mod_arith-like |
rhs | mod_arith-like |
Results:
Result | Description |
---|---|
output | mod_arith-like |
7.10 - Openfhe
‘openfhe’ Dialect
The openfhe
dialect is an exit dialect for generating c++ code against the OpenFHE library API.
See https://github.com/openfheorg/openfhe-development
Openfhe types
CCParamsType
The CCParams required to create CryptoContext.
Syntax: !openfhe.cc_params
CryptoContextType
The CryptoContext required to perform homomorphic operations in OpenFHE.
Syntax: !openfhe.crypto_context
EvalKeyType
The evaluation key required to keyswitch/relinearize/rotate/automorphism operation in OpenFHE.
Syntax: !openfhe.eval_key
PrivateKeyType
The private key required to decrypt a ciphertext in OpenFHE.
Syntax: !openfhe.private_key
PublicKeyType
The public key required to encrypt plaintext in OpenFHE.
Syntax: !openfhe.public_key
Openfhe ops
openfhe.add
(heir::openfhe::AddOp)
OpenFHE add operation of two ciphertexts.
Syntax:
operation ::= `openfhe.add` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
cryptoContext | The CryptoContext required to perform homomorphic operations in OpenFHE. |
lhs | A ciphertext type |
rhs | A ciphertext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
openfhe.add_plain
(heir::openfhe::AddPlainOp)
OpenFHE add operation of a ciphertext and a plaintext.
Syntax:
operation ::= `openfhe.add_plain` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
cryptoContext | The CryptoContext required to perform homomorphic operations in OpenFHE. |
ciphertext | A ciphertext type |
plaintext | A plaintext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
openfhe.automorph
(heir::openfhe::AutomorphOp)
Syntax:
operation ::= `openfhe.automorph` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
cryptoContext | The CryptoContext required to perform homomorphic operations in OpenFHE. |
ciphertext | A ciphertext type |
evalKey | The evaluation key required to keyswitch/relinearize/rotate/automorphism operation in OpenFHE. |
Results:
Result | Description |
---|---|
output | A ciphertext type |
openfhe.bootstrap
(heir::openfhe::BootstrapOp)
OpenFHE bootstrap operation of a ciphertext. (For CKKS)
Syntax:
operation ::= `openfhe.bootstrap` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
cryptoContext | The CryptoContext required to perform homomorphic operations in OpenFHE. |
ciphertext | A ciphertext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
openfhe.decrypt
(heir::openfhe::DecryptOp)
Syntax:
operation ::= `openfhe.decrypt` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
cryptoContext | The CryptoContext required to perform homomorphic operations in OpenFHE. |
ciphertext | A ciphertext type |
privateKey | The private key required to decrypt a ciphertext in OpenFHE. |
Results:
Result | Description |
---|---|
plaintext | A plaintext type |
openfhe.encrypt
(heir::openfhe::EncryptOp)
Syntax:
operation ::= `openfhe.encrypt` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
cryptoContext | The CryptoContext required to perform homomorphic operations in OpenFHE. |
plaintext | A plaintext type |
publicKey | The public key required to encrypt plaintext in OpenFHE. |
Results:
Result | Description |
---|---|
ciphertext | A ciphertext type |
openfhe.gen_bootstrapkey
(heir::openfhe::GenBootstrapKeyOp)
Syntax:
operation ::= `openfhe.gen_bootstrapkey` operands attr-dict `:` functional-type(operands, results)
Operands:
Operand | Description |
---|---|
cryptoContext | The CryptoContext required to perform homomorphic operations in OpenFHE. |
privateKey | The private key required to decrypt a ciphertext in OpenFHE. |
openfhe.gen_context
(heir::openfhe::GenContextOp)
Syntax:
operation ::= `openfhe.gen_context` operands attr-dict `:` functional-type(operands, results)
Interfaces: InferTypeOpInterface
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
supportFHE | ::mlir::BoolAttr | bool attribute |
Operands:
Operand | Description |
---|---|
params | The CCParams required to create CryptoContext. |
Results:
Result | Description |
---|---|
context | The CryptoContext required to perform homomorphic operations in OpenFHE. |
openfhe.gen_mulkey
(heir::openfhe::GenMulKeyOp)
Syntax:
operation ::= `openfhe.gen_mulkey` operands attr-dict `:` functional-type(operands, results)
Operands:
Operand | Description |
---|---|
cryptoContext | The CryptoContext required to perform homomorphic operations in OpenFHE. |
privateKey | The private key required to decrypt a ciphertext in OpenFHE. |
openfhe.gen_params
(heir::openfhe::GenParamsOp)
Syntax:
operation ::= `openfhe.gen_params` operands attr-dict `:` functional-type(operands, results)
Interfaces: InferTypeOpInterface
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
mulDepth | ::mlir::IntegerAttr | 64-bit signless integer attribute |
plainMod | ::mlir::IntegerAttr | 64-bit signless integer attribute |
insecure | ::mlir::BoolAttr | bool attribute |
Results:
Result | Description |
---|---|
params | The CCParams required to create CryptoContext. |
openfhe.gen_rotkey
(heir::openfhe::GenRotKeyOp)
Syntax:
operation ::= `openfhe.gen_rotkey` operands attr-dict `:` functional-type(operands, results)
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
indices | ::mlir::DenseI64ArrayAttr | i64 dense array attribute |
Operands:
Operand | Description |
---|---|
cryptoContext | The CryptoContext required to perform homomorphic operations in OpenFHE. |
privateKey | The private key required to decrypt a ciphertext in OpenFHE. |
openfhe.key_switch
(heir::openfhe::KeySwitchOp)
Syntax:
operation ::= `openfhe.key_switch` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
cryptoContext | The CryptoContext required to perform homomorphic operations in OpenFHE. |
ciphertext | A ciphertext type |
evalKey | The evaluation key required to keyswitch/relinearize/rotate/automorphism operation in OpenFHE. |
Results:
Result | Description |
---|---|
output | A ciphertext type |
openfhe.level_reduce
(heir::openfhe::LevelReduceOp)
OpenFHE level_reduce operation of a ciphertext.
Syntax:
operation ::= `openfhe.level_reduce` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
cryptoContext | The CryptoContext required to perform homomorphic operations in OpenFHE. |
ciphertext | A ciphertext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
openfhe.make_ckks_packed_plaintext
(heir::openfhe::MakeCKKSPackedPlaintextOp)
Syntax:
operation ::= `openfhe.make_ckks_packed_plaintext` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
cryptoContext | The CryptoContext required to perform homomorphic operations in OpenFHE. |
value | ranked tensor of floating-point or integer values |
Results:
Result | Description |
---|---|
plaintext | A plaintext type |
openfhe.make_packed_plaintext
(heir::openfhe::MakePackedPlaintextOp)
Syntax:
operation ::= `openfhe.make_packed_plaintext` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
cryptoContext | The CryptoContext required to perform homomorphic operations in OpenFHE. |
value | ranked tensor of integer values |
Results:
Result | Description |
---|---|
plaintext | A plaintext type |
openfhe.mod_reduce
(heir::openfhe::ModReduceOp)
OpenFHE mod_reduce operation of a ciphertext. (used only for BGV/CKKS)
Syntax:
operation ::= `openfhe.mod_reduce` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
cryptoContext | The CryptoContext required to perform homomorphic operations in OpenFHE. |
ciphertext | A ciphertext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
openfhe.mul_const
(heir::openfhe::MulConstOp)
OpenFHE mul operation of a ciphertext and a constant.
Syntax:
operation ::= `openfhe.mul_const` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
cryptoContext | The CryptoContext required to perform homomorphic operations in OpenFHE. |
ciphertext | A ciphertext type |
constant | 64-bit signless integer |
Results:
Result | Description |
---|---|
output | A ciphertext type |
openfhe.mul_no_relin
(heir::openfhe::MulNoRelinOp)
OpenFHE mul operation of two ciphertexts without relinearization.
Syntax:
operation ::= `openfhe.mul_no_relin` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
cryptoContext | The CryptoContext required to perform homomorphic operations in OpenFHE. |
lhs | A ciphertext type |
rhs | A ciphertext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
openfhe.mul
(heir::openfhe::MulOp)
OpenFHE mul operation of two ciphertexts with relinearization.
Syntax:
operation ::= `openfhe.mul` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
cryptoContext | The CryptoContext required to perform homomorphic operations in OpenFHE. |
lhs | A ciphertext type |
rhs | A ciphertext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
openfhe.mul_plain
(heir::openfhe::MulPlainOp)
OpenFHE mul operation of a ciphertext and a plaintext.
Syntax:
operation ::= `openfhe.mul_plain` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
cryptoContext | The CryptoContext required to perform homomorphic operations in OpenFHE. |
ciphertext | A ciphertext type |
plaintext | A plaintext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
openfhe.negate
(heir::openfhe::NegateOp)
OpenFHE negate operation of a ciphertext.
Syntax:
operation ::= `openfhe.negate` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
cryptoContext | The CryptoContext required to perform homomorphic operations in OpenFHE. |
ciphertext | A ciphertext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
openfhe.relin
(heir::openfhe::RelinOp)
OpenFHE relinearize operation of a ciphertext.
Syntax:
operation ::= `openfhe.relin` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
cryptoContext | The CryptoContext required to perform homomorphic operations in OpenFHE. |
ciphertext | A ciphertext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
openfhe.rot
(heir::openfhe::RotOp)
Syntax:
operation ::= `openfhe.rot` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
index | ::mlir::IntegerAttr | An Attribute containing a integer value |
Operands:
Operand | Description |
---|---|
cryptoContext | The CryptoContext required to perform homomorphic operations in OpenFHE. |
ciphertext | A ciphertext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
openfhe.setup_bootstrap
(heir::openfhe::SetupBootstrapOp)
Syntax:
operation ::= `openfhe.setup_bootstrap` operands attr-dict `:` functional-type(operands, results)
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
levelBudgetEncode | ::mlir::IntegerAttr | An Attribute containing a integer value |
levelBudgetDecode | ::mlir::IntegerAttr | An Attribute containing a integer value |
Operands:
Operand | Description |
---|---|
cryptoContext | The CryptoContext required to perform homomorphic operations in OpenFHE. |
openfhe.square
(heir::openfhe::SquareOp)
OpenFHE square operation of a ciphertext.
Syntax:
operation ::= `openfhe.square` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
cryptoContext | The CryptoContext required to perform homomorphic operations in OpenFHE. |
ciphertext | A ciphertext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
openfhe.sub
(heir::openfhe::SubOp)
OpenFHE sub operation of two ciphertexts.
Syntax:
operation ::= `openfhe.sub` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
cryptoContext | The CryptoContext required to perform homomorphic operations in OpenFHE. |
lhs | A ciphertext type |
rhs | A ciphertext type |
Results:
Result | Description |
---|---|
output | A ciphertext type |
7.11 - Polynomial
Polynomial attributes
FloatPolynomialAttr
an attribute containing a single-variable polynomial with double precision floating point coefficients
A polynomial attribute represents a single-variable polynomial with double precision floating point coefficients.
The polynomial must be expressed as a list of monomial terms, with addition or subtraction between them. The choice of variable name is arbitrary, but must be consistent across all the monomials used to define a single attribute. The order of monomial terms is arbitrary, each monomial degree must occur at most once.
Example:
#poly = #polynomial.float_polynomial<0.5 x**7 + 1.5>
Parameters:
Parameter | C++ type | Description |
---|---|---|
polynomial | FloatPolynomial |
IntPolynomialAttr
an attribute containing a single-variable polynomial with integer coefficients
A polynomial attribute represents a single-variable polynomial with integer
coefficients, which is used to define the modulus of a RingAttr
, as well
as to define constants and perform constant folding for polynomial
ops.
The polynomial must be expressed as a list of monomial terms, with addition or subtraction between them. The choice of variable name is arbitrary, but must be consistent across all the monomials used to define a single attribute. The order of monomial terms is arbitrary, each monomial degree must occur at most once.
Example:
#poly = #polynomial.int_polynomial<x**1024 + 1>
Parameters:
Parameter | C++ type | Description |
---|---|---|
polynomial | ::mlir::heir::polynomial::IntPolynomial |
PrimitiveRootAttr
an attribute containing an integer and its degree as a root of unity
Syntax:
#polynomial.primitive_root<
::mlir::IntegerAttr, # value
::mlir::IntegerAttr # degree
>
A primitive root attribute stores an integer root value
and an integer
degree
, corresponding to a primitive root of unity of the given degree in
an unspecified ring.
This is used as an attribute on polynomial.ntt
and polynomial.intt
ops
to specify the root of unity used in lowering the transform.
Example:
#poly = #polynomial.primitive_root<value=123 : i32, degree : 7 index>
Parameters:
Parameter | C++ type | Description |
---|---|---|
value | ::mlir::IntegerAttr | |
degree | ::mlir::IntegerAttr |
RingAttr
an attribute specifying a polynomial ring
Syntax:
#polynomial.ring<
Type, # coefficientType
::mlir::heir::polynomial::IntPolynomialAttr # polynomialModulus
>
A ring describes the domain in which polynomial arithmetic occurs. The ring
attribute in polynomial
represents the more specific case of polynomials
with a single indeterminate; whose coefficients can be represented by
another MLIR type (coefficientType
).
All semantics pertaining to arithmetic in the ring must be owned by the
coefficient type. For example, if the polynomials are with integer
coefficients taken modulo a prime $p$, then coefficientType
must be a
type that represents integers modulo $p$, such as mod_arith<p>
.
Additionally, a polynomial ring may specify a polynomialModulus
, which
converts polynomial arithmetic to the analogue of modular integer
arithmetic, where each polynomial is represented as its remainder when
dividing by the modulus. For single-variable polynomials, a polynomial
modulus is always specified via a single polynomial.
An expressive example is polynomials with i32 coefficients, whose
coefficients are taken modulo 2**32 - 5
, with a polynomial modulus of
x**1024 - 1
.
#poly_mod = #polynomial.int_polynomial<-1 + x**1024>
#ring = #polynomial.ring<coefficientType=i32, polynomialModulus=#poly_mod>
%0 = ... : polynomial.polynomial<#ring>
In this case, the value of a polynomial is always “converted” to a
canonical form by applying repeated reductions by setting x**1024 = 1
and simplifying.
Parameters:
Parameter | C++ type | Description |
---|---|---|
coefficientType | Type | |
polynomialModulus | ::mlir::heir::polynomial::IntPolynomialAttr |
TypedFloatPolynomialAttr
a typed float_polynomial
Syntax:
#polynomial.typed_float_polynomial<
::mlir::Type, # type
::mlir::heir::polynomial::FloatPolynomialAttr # value
>
Example:
!poly_ty = !polynomial.polynomial<ring=<coefficientType=f32>>
#poly = float<1.4 x**7 + 4.5> : !poly_ty
#poly_verbose = #polynomial.typed_float_polynomial<1.4 x**7 + 4.5> : !poly_ty
Parameters:
Parameter | C++ type | Description |
---|---|---|
type | ::mlir::Type | |
value | ::mlir::heir::polynomial::FloatPolynomialAttr |
TypedIntPolynomialAttr
a typed int_polynomial
Syntax:
#polynomial.typed_int_polynomial<
::mlir::Type, # type
::mlir::heir::polynomial::IntPolynomialAttr # value
>
Example:
!poly_ty = !polynomial.polynomial<ring=<coefficientType=i32>>
#poly = int<1 x**7 + 4> : !poly_ty
#poly_verbose = #polynomial.typed_int_polynomial<1 x**7 + 4> : !poly_ty
Parameters:
Parameter | C++ type | Description |
---|---|---|
type | ::mlir::Type | |
value | ::mlir::heir::polynomial::IntPolynomialAttr |
Polynomial types
PolynomialType
An element of a polynomial ring.
Syntax:
!polynomial.polynomial<
::mlir::heir::polynomial::RingAttr # ring
>
A type for polynomials in a polynomial quotient ring.
Parameters:
Parameter | C++ type | Description |
---|---|---|
ring | ::mlir::heir::polynomial::RingAttr | an attribute specifying a polynomial ring |
Polynomial ops
polynomial.add
(heir::polynomial::AddOp)
Addition operation between polynomials.
Syntax:
operation ::= `polynomial.add` operands attr-dict `:` type($result)
Performs polynomial addition on the operands. The operands may be single polynomials or containers of identically-typed polynomials, i.e., polynomials from the same underlying ring with the same coefficient types.
This op is defined to occur in the ring defined by the ring attribute of the two operands, meaning the arithmetic is taken modulo the polynomialModulus of the ring as well as modulo any semantics defined by the coefficient type.
Example:
// add two polynomials modulo x^1024 - 1
#poly = #polynomial.int_polynomial<x**1024 - 1>
#ring = #polynomial.ring<coefficientType=i32, polynomialModulus=#poly>
%0 = polynomial.constant int<1 + x**2> : !polynomial.polynomial<#ring>
%1 = polynomial.constant int<x**5 - x + 1> : !polynomial.polynomial<#ring>
%2 = polynomial.add %0, %1 : !polynomial.polynomial<#ring>
Traits: AlwaysSpeculatableImplTrait
, Commutative
, Elementwise
, SameOperandsAndResultType
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
lhs | polynomial-like |
rhs | polynomial-like |
Results:
Result | Description |
---|---|
result | polynomial-like |
polynomial.constant
(heir::polynomial::ConstantOp)
Define a constant polynomial via an attribute.
Example:
!int_poly_ty = !polynomial.polynomial<ring=<coefficientType=i32>>
%0 = polynomial.constant int<1 + x**2> : !int_poly_ty
!float_poly_ty = !polynomial.polynomial<ring=<coefficientType=f32>>
%1 = polynomial.constant float<0.5 + 1.3e06 x**2> : !float_poly_ty
Traits: AlwaysSpeculatableImplTrait
, InferTypeOpAdaptor
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
value | ::mlir::Attribute | a typed float_polynomial or a typed int_polynomial |
Results:
Result | Description |
---|---|
output | An element of a polynomial ring. |
polynomial.from_tensor
(heir::polynomial::FromTensorOp)
Creates a polynomial from integer coefficients stored in a tensor.
Syntax:
operation ::= `polynomial.from_tensor` $input attr-dict `:` type($input) `->` type($output)
polynomial.from_tensor
creates a polynomial value from a tensor of coefficients.
The input tensor must list the coefficients in degree-increasing order.
The input one-dimensional tensor may have size at most the degree of the ring’s polynomialModulus generator polynomial, with smaller dimension implying that all higher-degree terms have coefficient zero.
Example:
#poly = #polynomial.int_polynomial<x**1024 - 1>
#ring = #polynomial.ring<coefficientType=i32, polynomialModulus=#poly>
%two = arith.constant 2 : i32
%five = arith.constant 5 : i32
%coeffs = tensor.from_elements %two, %two, %five : tensor<3xi32>
%poly = polynomial.from_tensor %coeffs : tensor<3xi32> -> !polynomial.polynomial<#ring>
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
input | ranked tensor of any type values |
Results:
Result | Description |
---|---|
output | An element of a polynomial ring. |
polynomial.intt
(heir::polynomial::INTTOp)
Computes the reverse integer Number Theoretic Transform (NTT).
Syntax:
operation ::= `polynomial.intt` $input attr-dict `:` qualified(type($input)) `->` type($output)
polynomial.intt
computes the reverse integer Number Theoretic Transform
(INTT) on the input tensor. This is the inverse operation of the
polynomial.ntt
operation.
The input tensor is interpreted as a point-value representation of the
output polynomial at powers of a primitive n
-th root of unity (see
polynomial.ntt
). The ring of the polynomial is taken from the required
encoding attribute of the tensor.
The choice of primitive root may be optionally specified.
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
root | ::mlir::heir::polynomial::PrimitiveRootAttr | an attribute containing an integer and its degree as a root of unity |
Operands:
Operand | Description |
---|---|
input | ranked tensor of Integer type with modular arithmetic values |
Results:
Result | Description |
---|---|
output | An element of a polynomial ring. |
polynomial.leading_term
(heir::polynomial::LeadingTermOp)
Compute the leading term of the polynomial.
Syntax:
operation ::= `polynomial.leading_term` operands attr-dict `:` type($input) `->` `(` type($degree) `,` type($coefficient) `)`
The degree of a polynomial is the largest $k$ for which the coefficient
a_k
of x^k
is nonzero. The leading term is the term a_k * x^k
, which
this op represents as a pair of results. The first is the degree k
as an
index, and the second is the coefficient, whose type matches the
coefficient type of the polynomial’s ring attribute.
Example:
#poly = #polynomial.int_polynomial<x**1024 - 1>
#ring = #polynomial.ring<coefficientType=i32, polynomialModulus=#poly>
%0 = polynomial.constant int<1 + x**2> : !polynomial.polynomial<#ring>
%1, %2 = polynomial.leading_term %0 : !polynomial.polynomial<#ring> -> (index, i32)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
input | An element of a polynomial ring. |
Results:
Result | Description |
---|---|
degree | index |
coefficient | any type |
polynomial.monic_monomial_mul
(heir::polynomial::MonicMonomialMulOp)
Multiply a polynomial by a monic monomial.
Syntax:
operation ::= `polynomial.monic_monomial_mul` operands attr-dict `:` functional-type(operands, results)
Multiply a polynomial by a monic monomial, meaning a polynomial of the form
1 * x^k
for an index operand k
.
In some special rings of polynomials, such as a ring of polynomials
modulo x^n - 1
, monomial_mul
can be interpreted as a cyclic shift of
the coefficients of the polynomial. For some rings, this results in
optimized lowerings that involve rotations and rescaling of the
coefficients of the input.
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
input | polynomial-like |
monomialDegree | index |
Results:
Result | Description |
---|---|
output | polynomial-like |
polynomial.monomial
(heir::polynomial::MonomialOp)
Create a polynomial that consists of a single monomial.
Syntax:
operation ::= `polynomial.monomial` operands attr-dict `:` functional-type(operands, results)
Construct a polynomial that consists of a single monomial term, from its degree and coefficient as dynamic inputs.
The coefficient type of the output polynomial’s ring attribute must match
the coefficient
input type.
Example:
#poly = #polynomial.int_polynomial<x**1024 - 1>
#ring = #polynomial.ring<coefficientType=i32, polynomialModulus=#poly>
%deg = arith.constant 1023 : index
%five = arith.constant 5 : i32
%0 = polynomial.monomial %five, %deg : (i32, index) -> !polynomial.polynomial<#ring>
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
coefficient | any type |
degree | index |
Results:
Result | Description |
---|---|
output | An element of a polynomial ring. |
polynomial.mul
(heir::polynomial::MulOp)
Multiplication operation between polynomials.
Syntax:
operation ::= `polynomial.mul` operands attr-dict `:` type($result)
Performs polynomial multiplication on the operands. The operands may be single polynomials or containers of identically-typed polynomials, i.e., polynomials from the same underlying ring with the same coefficient types.
This op is defined to occur in the ring defined by the ring attribute of the two operands, meaning the arithmetic is taken modulo the polynomialModulus of the ring as well as modulo any semantics defined by the coefficient type.
Example:
// multiply two polynomials modulo x^1024 - 1
#poly = #polynomial.int_polynomial<x**1024 - 1>
#ring = #polynomial.ring<coefficientType=i32, polynomialModulus=#poly>
%0 = polynomial.constant int<1 + x**2> : !polynomial.polynomial<#ring>
%1 = polynomial.constant int<x**5 - x + 1> : !polynomial.polynomial<#ring>
%2 = polynomial.mul %0, %1 : !polynomial.polynomial<#ring>
Traits: AlwaysSpeculatableImplTrait
, Commutative
, Elementwise
, SameOperandsAndResultType
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
lhs | polynomial-like |
rhs | polynomial-like |
Results:
Result | Description |
---|---|
result | polynomial-like |
polynomial.mul_scalar
(heir::polynomial::MulScalarOp)
Multiplication by a scalar of the field.
Syntax:
operation ::= `polynomial.mul_scalar` operands attr-dict `:` type($polynomial) `,` type($scalar)
Multiplies the polynomial operand’s coefficients by a given scalar value.
The scalar
input must have the same type as the polynomial ring’s
coefficientType.
Example:
// multiply two polynomials modulo x^1024 - 1
#poly = #polynomial.int_polynomial<x**1024 - 1>
#ring = #polynomial.ring<coefficientType=i32, polynomialModulus=#poly>
%0 = polynomial.constant int<1 + x**2> : !polynomial.polynomial<#ring>
%1 = arith.constant 3 : i32
%2 = polynomial.mul_scalar %0, %1 : !polynomial.polynomial<#ring>, i32
Traits: AlwaysSpeculatableImplTrait
, Elementwise
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
polynomial | polynomial-like |
scalar | any type |
Results:
Result | Description |
---|---|
output | polynomial-like |
polynomial.ntt
(heir::polynomial::NTTOp)
Computes point-value tensor representation of a polynomial.
Syntax:
operation ::= `polynomial.ntt` $input attr-dict `:` qualified(type($input)) `->` type($output)
polynomial.ntt
computes the forward integer Number Theoretic Transform
(NTT) on the input polynomial. It returns a tensor containing a point-value
representation of the input polynomial. The output tensor has shape equal
to the degree of the ring’s polynomialModulus
. The polynomial’s RingAttr
is embedded as the encoding attribute of the output tensor.
Given an input polynomial F(x)
over a ring whose polynomialModulus
has
degree n
, and a primitive n
-th root of unity omega_n
, the output is
the list of $n$ evaluations
f[k] = F(omega[n]^k) ; k = {0, ..., n-1}
The choice of primitive root may be optionally specified.
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
root | ::mlir::heir::polynomial::PrimitiveRootAttr | an attribute containing an integer and its degree as a root of unity |
Operands:
Operand | Description |
---|---|
input | An element of a polynomial ring. |
Results:
Result | Description |
---|---|
output | ranked tensor of Integer type with modular arithmetic values |
polynomial.sub
(heir::polynomial::SubOp)
Subtraction operation between polynomials.
Syntax:
operation ::= `polynomial.sub` operands attr-dict `:` type($result)
Performs polynomial subtraction on the operands. The operands may be single polynomials or containers of identically-typed polynomials, i.e., polynomials from the same underlying ring with the same coefficient types.
This op is defined to occur in the ring defined by the ring attribute of the two operands, meaning the arithmetic is taken modulo the polynomialModulus of the ring as well as modulo any semantics defined by the coefficient type.
Example:
// subtract two polynomials modulo x^1024 - 1
#poly = #polynomial.int_polynomial<x**1024 - 1>
#ring = #polynomial.ring<coefficientType=i32, polynomialModulus=#poly>
%0 = polynomial.constant int<1 + x**2> : !polynomial.polynomial<#ring>
%1 = polynomial.constant int<x**5 - x + 1> : !polynomial.polynomial<#ring>
%2 = polynomial.sub %0, %1 : !polynomial.polynomial<#ring>
Traits: AlwaysSpeculatableImplTrait
, Elementwise
, SameOperandsAndResultType
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
lhs | polynomial-like |
rhs | polynomial-like |
Results:
Result | Description |
---|---|
result | polynomial-like |
polynomial.to_tensor
(heir::polynomial::ToTensorOp)
Creates a tensor containing the coefficients of a polynomial.
Syntax:
operation ::= `polynomial.to_tensor` $input attr-dict `:` type($input) `->` type($output)
polynomial.to_tensor
creates a dense tensor value containing the
coefficients of the input polynomial. The output tensor contains the
coefficients in degree-increasing order.
Operations that act on the coefficients of a polynomial, such as extracting
a specific coefficient or extracting a range of coefficients, should be
implemented by composing to_tensor
with the relevant tensor
dialect
ops.
The output tensor has shape equal to the degree of the polynomial ring attribute’s polynomialModulus, including zeroes.
Example:
#poly = #polynomial.int_polynomial<x**1024 - 1>
#ring = #polynomial.ring<coefficientType=i32, polynomialModulus=#poly>
%two = arith.constant 2 : i32
%five = arith.constant 5 : i32
%coeffs = tensor.from_elements %two, %two, %five : tensor<3xi32>
%poly = polynomial.from_tensor %coeffs : tensor<3xi32> -> !polynomial.polynomial<#ring>
%tensor = polynomial.to_tensor %poly : !polynomial.polynomial<#ring> -> tensor<1024xi32>
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
input | An element of a polynomial ring. |
Results:
Result | Description |
---|---|
output | ranked tensor of any type values |
7.12 - Random
Random types
DistributionType
A random distribution type
Syntax:
!random.distribution<
::mlir::heir::random::Distribution # distribution_type
>
A generic type, representing a specific random distribution type of either uniform or gaussian as an attribute ($distribution_type).
Parameters:
Parameter | C++ type | Description |
---|---|---|
distribution_type | ::mlir::heir::random::Distribution |
PRNGType
A pseudorandom number generator type
Syntax: !random.prng
A type that provides pseudorandom number generator.
Random ops
random.discrete_gaussian_distribution
(heir::random::DiscreteGaussianDistributionOp)
Initializes the Discrete Gaussian Distribution
Syntax:
operation ::= `random.discrete_gaussian_distribution` operands attr-dict `:` functional-type(operands, results)
Initializes the Discrete Gaussian Distribution. The distribution is initialized with a mean and a standard deviation and pseudorandom generator that provides the source of the randomness.
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
mean | ::mlir::IntegerAttr | An Attribute containing a integer value |
stddev | ::mlir::IntegerAttr | An Attribute containing a integer value whose value is non-negative |
Operands:
Operand | Description |
---|---|
input | A pseudorandom number generator type |
Results:
Result | Description |
---|---|
output | A random distribution type |
random.discrete_uniform_distribution
(heir::random::DiscreteUniformDistributionOp)
Initializes the Discrete Uniform Distribution
Syntax:
operation ::= `random.discrete_uniform_distribution` $input `{` `range` `=` `[` $min `,` $max `]` `}` attr-dict `:` `(` qualified(type($input)) `)` `->` type($output)
Initializes the Discrete Uniform Distribution. The distribution is initialized with a minimum and a maximum value and pseudo random generator that provides the source of the randomness. The distribution is inclusive of the minimum and exclusive of the maximum.
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
min | ::mlir::IntegerAttr | An Attribute containing a integer value |
max | ::mlir::IntegerAttr | An Attribute containing a integer value |
Operands:
Operand | Description |
---|---|
input | A pseudorandom number generator type |
Results:
Result | Description |
---|---|
output | A random distribution type |
random.init_prng
(heir::random::InitOp)
Initializes the pseudorandom number generator with a seed.
Syntax:
operation ::= `random.init_prng` operands attr-dict `:` functional-type(operands, results)
Initializes the PRNG with a seed. The seed is dynamically provided due to protocols that agree on shared randomness. The PRNG is used to initialized the random distributions such as the discrete gaussian distribution and the discrete uniform distribution. This initialization also takes as input a number of bits that are generated for each number value sampled (num_bits). For instance, a num_bits of 32 will mean that distributions will generate a 32-bit integer value. We expect that the seed initialization is done statically and globally once per thread for all distributions; however, if multiple threads are generating randomness, then seed initialization should be done per thread; otherwise there is no guarantee of consistent behavior. Thread safety is so far not considered.
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
num_bits | ::mlir::IntegerAttr | An Attribute containing a integer value |
Operands:
Operand | Description |
---|---|
seed | signless-integer-like |
Results:
Result | Description |
---|---|
output | A pseudorandom number generator type |
random.sample
(heir::random::SampleOp)
Samples from a distribution
Syntax:
operation ::= `random.sample` operands attr-dict `:` functional-type(operands, results)
Samples from the distribution to obtain a random value or tensor of values.
Operands:
Operand | Description |
---|---|
input | A random distribution type |
Results:
Result | Description |
---|---|
output | signless-integer-like |
7.13 - RNS
RNS types
RNSType
A residue number system representation
Syntax:
!rns.rns<
::llvm::ArrayRef<mlir::Type> # basisTypes
>
Parameters:
Parameter | C++ type | Description |
---|---|---|
basisTypes | ::llvm::ArrayRef<mlir::Type> |
RNS ops
RNS additional definitions
TypeInterface definitions
RNSBasisTypeInterface (RNSBasisTypeInterface
)
This interface is required for a type to be used as a parameter
to an rns
type.
Methods:
isCompatibleWith
bool isCompatibleWith(::mlir::Type otherRnsBasisType);
Returns true if this type is compatible with another type in the same RNS basis. In particular, the set of types used for a single RNS basis are never equal as types, but instead have some common attribute that must be checked here. For example, an RNS type where the basis types are polynomials would return true if the two types are both polynomial types, even if they have different coefficient moduli.
Another example is using mod arith types as the basis types, where by the nature of chinese reminder theorem, it is required that the modulus of them must be mutually coprime.
isCompatibleWith
must be commutative, in the sense
that type1.isCompatibleWith(type2)
if and only if
type2.isCompatibleWith(type1)
.
NOTE: This method must be implemented by the user.
7.14 - Secret
‘secret’ Dialect
Secret is a dialect for computations that operate on encrypted data.
Secret is intended to serve as a scheme-agnostic front-end for the HEIR ecosystem of dialects. It is supposed to be fully interoperable with the rest of MLIR via secret.generic, while lower-level HEIR dialects would have custom types for arithmetic on secret integers of various bit widths.
Secret types
SecretType
A secret value
Syntax:
!secret.secret<
Type # valueType
>
A generic wrapper around another MLIR type, representing an encrypted value but not specifying the manner of encryption. This is useful in HEIR because the compiler may choose various details of the FHE scheme based on the properties of the input program, the backend target hardware, and cost models of the various passes.
Parameters:
Parameter | C++ type | Description |
---|---|---|
valueType | Type |
Secret ops
secret.cast
(heir::secret::CastOp)
A placeholder cast from one secret type to another
Syntax:
operation ::= `secret.cast` $input attr-dict `:` qualified(type($input)) `to` qualified(type($output))
A cast
operation represents a type cast from one secret type to another,
that is used to enable the intermixing of various equivalent secret types
before a lower-level FHE scheme has been chosen.
For example, secret.cast
can be used to convert a secret<i8>
to a
secret<tensor<8xi1>>
as a compatibility layer between boolean and
non-boolean parts of a program. The pass that later lowers the IR to
specific FHE schemes would need to replace these casts with appropriate
scheme-specific operations, and it is left to those later passes to
determine which casts are considered valid.
Example:
%result = secret.cast %0 : !secret.secret<i8> to !secret.secret<tensor<8xi1>>
%result2 = secret.cast %0 : !secret.secret<i8> to !secret.secret<tensor<2xi4>>
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
input | A secret value |
Results:
Result | Description |
---|---|
output | A secret value |
secret.conceal
(heir::secret::ConcealOp)
Convert a non-secret value into a secret
Syntax:
operation ::= `secret.conceal` $cleartext attr-dict `:` type($cleartext) `->` type($output)
Convert a value to a secret containing the same value.
This op represents a scheme-agnostic encryption operation, as well as a “trivial encryption” operation which is needed for some FHE schemes. This op is also useful for type materialization in the dialect conversion framework.
Examples:
%Y = secret.conceal %value : i32 -> !secret.secret<i32>
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
cleartext | any type |
Results:
Result | Description |
---|---|
output | A secret value |
secret.generic
(heir::secret::GenericOp)
Lift a plaintext computation to operate on secrets.
secret.generic
lifts a plaintext computation to operate on one or more
secrets. The lifted computation is represented as a region containing a
single block terminated by secret.yield
. The arguments of the secret.generic
may include one or more !secret.secret
types. The arguments of the block
in the op’s body correspond to the underlying plaintext types of the secrets.
secret.generic
is not isolated from above, so you may directly reference
values in the enclosing scope. This is required to support using
secret.generic
inside of ops with AffineScope
, while having the body
of the generic use the induction variables defined by the affine scope.
Basic examples:
Add two secret integers together
%Z = secret.generic ins(%X, %Y : !secret.secret<i32>, !secret.secret<i32>) {
^bb0(%x: i32, %y: i32):
%z = arith.addi %x, %y: i32
secret.yield %z : i32
} -> (!secret.secret<i32>)
Add a secret value with a plaintext value. I.e., not all arguments to the op need be secret.
%Z = secret.generic ins(%X, %Y : i32, !secret.secret<i32>) {
^bb0(%x: i32, %y: i32):
%z = arith.addi %x, %y: i32
secret.yield %z : i32
} -> (!secret.secret<i32>)
The same as above, but the plaintext op is not passed through the basic block.
%y = arith.constant 7: i32
%Z = secret.generic ins(%X : !secret.secret<i32>) {
^bb0(%x: i32):
%z = arith.addi %x, %y: i32
secret.yield %z : i32
} -> (!secret.secret<i32>)
Traits: SingleBlockImplicitTerminator<YieldOp>
, SingleBlock
Operands:
Operand | Description |
---|---|
inputs | variadic of any type |
Results:
Result | Description |
---|---|
results | variadic of any type |
secret.reveal
(heir::secret::RevealOp)
Convert a secret value into a non-secret
Syntax:
operation ::= `secret.reveal` $input attr-dict `:` type($input) `->` type($cleartext)
Convert a secret into a non-secret containing the same value.
This op represents a scheme-agnostic decryption operation. This op is also useful for target materialization in the dialect conversion framework.
Examples:
%Y = secret.reveal %secret_value : !secret.secret<i32> -> i32
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
input | A secret value |
Results:
Result | Description |
---|---|
cleartext | any type |
secret.separator
(heir::secret::SeparatorOp)
Convert a non-secret value into a secret
Syntax:
operation ::= `secret.separator` attr-dict ($inputs^ `:` type($inputs))?
This operation is used as a separation boundary between logical subunits of
the module. This is used in conjunction with
--secret-distribute-generic=distribute-through=secret.separator
to break a
generic around these separators and allow for optimization passses to
analyze and optimize the sub-units locally.
In order to allow bufferization of modules with this operation, we must register a (bogus) memory effect that also prevents this operation from being trivially dead during operation folding.
This operation also accepts operands, which act as boundaries between the logical units. This enforces separation of memref and affine optimizations between the subunits, preventing optimizations from removing the operand and combining the two separated regions. The operand can be thought of as an return value of the logical subunit.
Interfaces: MemoryEffectOpInterface (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{MemoryEffects::Write on ::mlir::SideEffects::DefaultResource}
Operands:
Operand | Description |
---|---|
inputs | variadic of any type |
secret.yield
(heir::secret::YieldOp)
Secret yield operation
secret.yield
is a special terminator operation for blocks inside regions
in secret
generic ops. It returns the cleartext value of the
corresponding private computation to the immediately enclosing secret
generic op.
Traits: AlwaysSpeculatableImplTrait
, HasParent<GenericOp>
, ReturnLike
, Terminator
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
, RegionBranchTerminatorOpInterface
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
values | variadic of any type |
7.15 - TensorExt
TensorExt attributes
SIMDPackingAttr
An attribute describing the SIMD packing of a tensor.
Syntax:
#tensor_ext.simd_packing<
::mlir::DenseI64ArrayAttr, # in
::mlir::DenseI64ArrayAttr, # padding
::mlir::DenseI64ArrayAttr, # out
int64_t # padding_value
>
This attribute is used as the encoding attribute on a tensor. It describes the transformations that were applied to an input tensor to pack it into the given tensor.
The in
attribute describes the shape of the original tensor.
The following transformations are applied to the input tensor.
Padding is applied first. The
padding
attribute is an array with the same size as the input tensor shape. Padding is applied at the end of the array using thepadding_value
attribute (default zero). The result after zero padding should be a power of two.The padded result is replicated or split to fill the output tensor shape.
For example,
#packing = #tensor_ext.simd_packing<
in = [7],
padding = [1],
padding_value = 0,
out = [16],
>
may be used on a tensor type like
tensor<1x16xi32, #packing>
If the original tensor had values [1, 2, 3, 4, 5, 6, 7]
then a tensor with
this attribute contains the data [1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, 0]
.
Parameters:
Parameter | C++ type | Description |
---|---|---|
in | ::mlir::DenseI64ArrayAttr | |
padding | ::mlir::DenseI64ArrayAttr | |
out | ::mlir::DenseI64ArrayAttr | |
padding_value | int64_t |
TensorExt ops
tensor_ext.rotate
(heir::tensor_ext::RotateOp)
Rotate a tensor some number of indices left.
Syntax:
operation ::= `tensor_ext.rotate` operands attr-dict `:` qualified(type($tensor)) `,` type($shift)
This op represents a left-rotation of a tensor by given number of indices. Negative shift values are interpreted as right-rotations.
This corresponds to the rotate
operation in arithmetic FHE schemes like
BGV.
This operation’s current behavior allows rotating multi-dimensional tensors by rotating along the tensor’s only non-unit dimension. This assumes the tensor is packed along the non-unit dimension.
// In the future, the op will be adjusted to support rotations of general // multi-dimensional tensors with a vector of rotation indices for each // dimension. The lowering will implement the correct operations to rotate // the tensor along the indices given its packing.
Examples:
%0 = ... : tensor<16xi32>
%c7 = arith.constant 7 : i32
%1 = tensor_ext.rotate %0, %c7 : tensor<16xi32>, i32
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
tensor | tensor of any type values |
shift | signless-integer-like |
Results:
Result | Description |
---|---|
output | tensor of any type values |
7.16 - TfheRust
’tfhe_rust’ Dialect
The thfe_rust
dialect is an exit dialect for generating rust code against the tfhe-rs library API,
using the shortint parameters and encoding scheme.
See https://github.com/zama-ai/tfhe-rs
TfheRust types
EncryptedInt8Type
An encrypted signed integer corresponding to tfhe-rs’s FHEInt8 type
Syntax: !tfhe_rust.ei8
EncryptedInt16Type
An encrypted signed integer corresponding to tfhe-rs’s FHEInt16 type
Syntax: !tfhe_rust.ei16
EncryptedInt32Type
An encrypted signed integer corresponding to tfhe-rs’s FHEInt32 type
Syntax: !tfhe_rust.ei32
EncryptedInt64Type
An encrypted signed integer corresponding to tfhe-rs’s FHEInt64 type
Syntax: !tfhe_rust.ei64
EncryptedInt128Type
An encrypted signed integer corresponding to tfhe-rs’s FHEInt128 type
Syntax: !tfhe_rust.ei128
EncryptedInt256Type
An encrypted signed integer corresponding to tfhe-rs’s FHEInt256 type
Syntax: !tfhe_rust.ei256
EncryptedUInt2Type
An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint2 type
Syntax: !tfhe_rust.eui2
EncryptedUInt3Type
An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint3 type
Syntax: !tfhe_rust.eui3
EncryptedUInt4Type
An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint4 type
Syntax: !tfhe_rust.eui4
EncryptedUInt8Type
An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint8 type
Syntax: !tfhe_rust.eui8
EncryptedUInt10Type
An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint10 type
Syntax: !tfhe_rust.eui10
EncryptedUInt12Type
An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint12 type
Syntax: !tfhe_rust.eui12
EncryptedUInt14Type
An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint14 type
Syntax: !tfhe_rust.eui14
EncryptedUInt16Type
An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint16 type
Syntax: !tfhe_rust.eui16
EncryptedUInt32Type
An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint32 type
Syntax: !tfhe_rust.eui32
EncryptedUInt64Type
An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint64 type
Syntax: !tfhe_rust.eui64
EncryptedUInt128Type
An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint128 type
Syntax: !tfhe_rust.eui128
EncryptedUInt256Type
An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint256 type
Syntax: !tfhe_rust.eui256
LookupTableType
A univariate lookup table used for programmable bootstrapping.
Syntax: !tfhe_rust.lookup_table
ServerKeyType
The short int server key required to perform homomorphic operations.
Syntax: !tfhe_rust.server_key
TfheRust ops
tfhe_rust.apply_lookup_table
(heir::tfhe_rust::ApplyLookupTableOp)
Syntax:
operation ::= `tfhe_rust.apply_lookup_table` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
serverKey | The short int server key required to perform homomorphic operations. |
input | An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint2 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint3 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint4 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint8 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint10 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint12 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint14 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint16 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint32 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint64 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint128 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint256 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt8 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt16 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt32 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt64 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt128 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt256 type |
lookupTable | A univariate lookup table used for programmable bootstrapping. |
Results:
Result | Description |
---|---|
output | An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint2 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint3 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint4 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint8 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint10 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint12 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint14 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint16 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint32 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint64 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint128 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint256 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt8 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt16 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt32 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt64 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt128 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt256 type |
tfhe_rust.create_trivial
(heir::tfhe_rust::CreateTrivialOp)
Syntax:
operation ::= `tfhe_rust.create_trivial` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
serverKey | The short int server key required to perform homomorphic operations. |
value | integer |
Results:
Result | Description |
---|---|
output | An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint2 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint3 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint4 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint8 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint10 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint12 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint14 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint16 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint32 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint64 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint128 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint256 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt8 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt16 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt32 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt64 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt128 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt256 type |
tfhe_rust.generate_lookup_table
(heir::tfhe_rust::GenerateLookupTableOp)
Syntax:
operation ::= `tfhe_rust.generate_lookup_table` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
truthTable | ::mlir::IntegerAttr | An Attribute containing a integer value |
Operands:
Operand | Description |
---|---|
serverKey | The short int server key required to perform homomorphic operations. |
Results:
Result | Description |
---|---|
lookupTable | A univariate lookup table used for programmable bootstrapping. |
tfhe_rust.scalar_left_shift
(heir::tfhe_rust::ScalarLeftShiftOp)
Syntax:
operation ::= `tfhe_rust.scalar_left_shift` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
serverKey | The short int server key required to perform homomorphic operations. |
ciphertext | An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint2 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint3 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint4 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint8 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint10 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint12 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint14 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint16 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint32 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint64 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint128 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint256 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt8 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt16 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt32 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt64 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt128 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt256 type |
shiftAmount | 8-bit integer |
Results:
Result | Description |
---|---|
output | An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint2 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint3 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint4 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint8 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint10 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint12 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint14 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint16 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint32 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint64 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint128 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint256 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt8 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt16 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt32 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt64 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt128 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt256 type |
tfhe_rust.add
(heir::tfhe_rust::AddOp)
Arithmetic add of two tfhe ciphertexts.
Syntax:
operation ::= `tfhe_rust.add` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
serverKey | The short int server key required to perform homomorphic operations. |
lhs | An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint2 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint3 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint4 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint8 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint10 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint12 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint14 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint16 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint32 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint64 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint128 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint256 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt8 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt16 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt32 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt64 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt128 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt256 type |
rhs | An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint2 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint3 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint4 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint8 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint10 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint12 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint14 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint16 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint32 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint64 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint128 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint256 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt8 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt16 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt32 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt64 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt128 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt256 type |
Results:
Result | Description |
---|---|
output | An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint2 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint3 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint4 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint8 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint10 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint12 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint14 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint16 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint32 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint64 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint128 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint256 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt8 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt16 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt32 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt64 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt128 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt256 type |
tfhe_rust.bitand
(heir::tfhe_rust::BitAndOp)
Logical AND of two tfhe ciphertexts.
Syntax:
operation ::= `tfhe_rust.bitand` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
serverKey | The short int server key required to perform homomorphic operations. |
lhs | An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint2 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint3 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint4 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint8 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint10 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint12 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint14 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint16 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint32 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint64 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint128 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint256 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt8 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt16 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt32 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt64 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt128 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt256 type |
rhs | An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint2 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint3 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint4 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint8 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint10 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint12 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint14 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint16 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint32 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint64 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint128 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint256 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt8 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt16 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt32 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt64 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt128 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt256 type |
Results:
Result | Description |
---|---|
output | An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint2 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint3 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint4 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint8 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint10 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint12 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint14 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint16 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint32 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint64 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint128 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint256 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt8 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt16 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt32 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt64 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt128 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt256 type |
tfhe_rust.sub
(heir::tfhe_rust::SubOp)
Arithmetic sub of two tfhe ciphertexts.
Syntax:
operation ::= `tfhe_rust.sub` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
serverKey | The short int server key required to perform homomorphic operations. |
lhs | An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint2 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint3 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint4 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint8 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint10 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint12 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint14 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint16 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint32 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint64 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint128 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint256 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt8 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt16 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt32 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt64 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt128 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt256 type |
rhs | An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint2 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint3 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint4 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint8 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint10 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint12 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint14 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint16 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint32 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint64 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint128 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint256 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt8 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt16 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt32 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt64 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt128 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt256 type |
Results:
Result | Description |
---|---|
output | An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint2 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint3 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint4 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint8 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint10 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint12 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint14 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint16 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint32 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint64 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint128 type or An encrypted unsigned integer corresponding to tfhe-rs’s FHEUint256 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt8 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt16 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt32 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt64 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt128 type or An encrypted signed integer corresponding to tfhe-rs’s FHEInt256 type |
7.17 - TfheRustBool
’tfhe_rust_bool’ Dialect
The tfhe_rust_bool
dialect is an exit dialect for generating rust code against the tfhe-rs library API,
using the boolean parameter set.
See https://github.com/zama-ai/tfhe-rs
TfheRustBool attributes
TfheRustBoolGatesAttr
An Attribute containing an array of strings to store bool gates
Syntax:
#tfhe_rust_bool.tfhe_rust_bool_gates<
::llvm::ArrayRef<::mlir::heir::tfhe_rust_bool::TfheRustBoolGateEnumAttr> # gates
>
This attributes stores a list of integer identifiers for Boolean gates.
Uses following mapping: AND_GATE = 0; NAND_GATE = 1; OR_GATE = 2; NOR_GATE = 3; XOR_GATE = 4; XNOR_GATE = 5; NOT_GATE = 6;
Parameters:
Parameter | C++ type | Description |
---|---|---|
gates | ::llvm::ArrayRef<::mlir::heir::tfhe_rust_bool::TfheRustBoolGateEnumAttr> |
TfheRustBool types
EncryptedBoolType
An encrypted Boolean corresponding to tfhe-rs’s FHEBool type
Syntax: !tfhe_rust_bool.eb
PackedServerKeyType
The Belfort packed API boolean server key.
Syntax: !tfhe_rust_bool.server_key_enum
ServerKeyType
The boolean server key required to perform homomorphic operations.
Syntax: !tfhe_rust_bool.server_key
TfheRustBool ops
tfhe_rust_bool.create_trivial
(heir::tfhe_rust_bool::CreateTrivialOp)
Syntax:
operation ::= `tfhe_rust_bool.create_trivial` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
serverKey | tfhe rust bool server key |
value | 1-bit signless integer |
Results:
Result | Description |
---|---|
output | An encrypted Boolean corresponding to tfhe-rs’s FHEBool type |
tfhe_rust_bool.and
(heir::tfhe_rust_bool::AndOp)
Logical AND of two TFHE-rs Bool ciphertexts.
Syntax:
operation ::= `tfhe_rust_bool.and` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
, Commutative
, Elementwise
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
serverKey | The boolean server key required to perform homomorphic operations. or The Belfort packed API boolean server key. |
lhs | eb-like |
rhs | eb-like |
Results:
Result | Description |
---|---|
output | eb-like |
tfhe_rust_bool.mux
(heir::tfhe_rust_bool::MuxOp)
Syntax:
operation ::= `tfhe_rust_bool.mux` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
serverKey | The boolean server key required to perform homomorphic operations. or The Belfort packed API boolean server key. |
cnd | An encrypted Boolean corresponding to tfhe-rs’s FHEBool type |
lhs | An encrypted Boolean corresponding to tfhe-rs’s FHEBool type |
rhs | An encrypted Boolean corresponding to tfhe-rs’s FHEBool type |
Results:
Result | Description |
---|---|
output | An encrypted Boolean corresponding to tfhe-rs’s FHEBool type |
tfhe_rust_bool.nand
(heir::tfhe_rust_bool::NandOp)
Logical NAND of two TFHE-rs Bool ciphertexts.
Syntax:
operation ::= `tfhe_rust_bool.nand` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
, Commutative
, Elementwise
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
serverKey | The boolean server key required to perform homomorphic operations. or The Belfort packed API boolean server key. |
lhs | eb-like |
rhs | eb-like |
Results:
Result | Description |
---|---|
output | eb-like |
tfhe_rust_bool.nor
(heir::tfhe_rust_bool::NorOp)
Logical NOR of two TFHE-rs Bool ciphertexts.
Syntax:
operation ::= `tfhe_rust_bool.nor` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
, Commutative
, Elementwise
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
serverKey | The boolean server key required to perform homomorphic operations. or The Belfort packed API boolean server key. |
lhs | eb-like |
rhs | eb-like |
Results:
Result | Description |
---|---|
output | eb-like |
tfhe_rust_bool.not
(heir::tfhe_rust_bool::NotOp)
Syntax:
operation ::= `tfhe_rust_bool.not` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
serverKey | The boolean server key required to perform homomorphic operations. or The Belfort packed API boolean server key. |
input | eb-like |
Results:
Result | Description |
---|---|
output | eb-like |
tfhe_rust_bool.or
(heir::tfhe_rust_bool::OrOp)
Logical OR of two TFHE-rs Bool ciphertexts.
Syntax:
operation ::= `tfhe_rust_bool.or` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
, Commutative
, Elementwise
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
serverKey | The boolean server key required to perform homomorphic operations. or The Belfort packed API boolean server key. |
lhs | eb-like |
rhs | eb-like |
Results:
Result | Description |
---|---|
output | eb-like |
tfhe_rust_bool.gates_packed
(heir::tfhe_rust_bool::PackedOp)
Syntax:
operation ::= `tfhe_rust_bool.gates_packed` operands attr-dict `:` functional-type(operands, results)
Operation to where different Boolean gates are executed pairwise between elements of two ciphertext arrays.
For example,
%0 = tfhe_rust_bool.packed_gates %a, %b {gates = #tfhe_rust_bool.tfhe_rust_bool_gates<0 : i32, 4 : i32>} :
(!tfhe_rust_bool.server_key,
tensor<2x!tfhe_rust_bool.eb>,
tensor<2x!tfhe_rust_bool.eb>) -> tensor<2x!tfhe_rust_bool.eb>
applies an “and” gate to the first elements of %a and %b and an xor gate to the second elements.
Mapping is defined in the BooleanGates.td file.
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
gates | ::mlir::heir::tfhe_rust_bool::TfheRustBoolGatesAttr | An Attribute containing an array of strings to store bool gates |
Operands:
Operand | Description |
---|---|
serverKey | The boolean server key required to perform homomorphic operations. or The Belfort packed API boolean server key. |
lhs | eb-like |
rhs | eb-like |
Results:
Result | Description |
---|---|
output | eb-like |
tfhe_rust_bool.xnor
(heir::tfhe_rust_bool::XnorOp)
Logical XNOR of two TFHE-rs Bool ciphertexts.
Syntax:
operation ::= `tfhe_rust_bool.xnor` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
, Commutative
, Elementwise
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
serverKey | The boolean server key required to perform homomorphic operations. or The Belfort packed API boolean server key. |
lhs | eb-like |
rhs | eb-like |
Results:
Result | Description |
---|---|
output | eb-like |
tfhe_rust_bool.xor
(heir::tfhe_rust_bool::XorOp)
Logical XOR of two TFHE-rs Bool ciphertexts.
Syntax:
operation ::= `tfhe_rust_bool.xor` operands attr-dict `:` functional-type(operands, results)
Traits: AlwaysSpeculatableImplTrait
, Commutative
, Elementwise
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Operands:
Operand | Description |
---|---|
serverKey | The boolean server key required to perform homomorphic operations. or The Belfort packed API boolean server key. |
lhs | eb-like |
rhs | eb-like |
Results:
Result | Description |
---|---|
output | eb-like |
TfheRustBool additional definitions
8 - Passes
-align-tensor-sizes
Resize tensors into tensors with a fixed size final dimension
This pass resizes input tensors with arbitrary sizes into
tensors with whose final dimensions has a fixed size. All input tensors are
required to be one-dimensional. The --size
option specifies the size of the
final dimension of the output tensors, and is required to be a power of two.
To align the tensors in the input IR, the pass first zero pads the input to
the nearest power of two before replicating or splitting it into the output
shape determined by size
. The resulting transformation is described in a
SIMDPackingAttr
encoding attribute on the final tensor.
For example, with size=16
,a tensor with 7 elements will be zero-padded to 8
elements, and then replicated twice to fill a tensor with size 16. The
SIMDPackingAttr
will encode the input shape, the number of elements that
were zero-padded, and the output shape.
Input:
%0 = tensor.empty() : tensor<7xi32>
Output:
%0 = tensor.empty() : tensor<16xi32, #tensor_ext.simd_packing<in = [7], padding = [1], out = [16]>>
A tensor with 30 elements will be zero padded with 2 elements and split into two tensors of size 16.
Input:
%0 = tensor.empty() : tensor<30xi32>
Output:
%0 = tensor.empty() : tensor<2x16xi32, #tensor_ext.simd_packing<in = [30], padding = [2], out = [16]>>
Note that this pass does not insert any new operations like reshape
, but
rather transforms the IR to use tensors with a fixed dimension. This pass may
be used to align the sizes of tensors that represent plaintexts and
ciphertexts in RLWE schemes that support SIMD slots and operations.
Options
-size : Power of two size of the final dimension of the output tensors.
-annotate-mgmt
Annotate MgmtAttr for secret SSA values in the IR
This pass runs the secretness/level/dimension analysis and annotates the IR with the results,
saving it into each op’s attribute dictionary as mgmt.mgmt :
-annotate-secretness
Annotate secret SSA values in the IR
This pass runs the secretness analysis and annotates the IR with the results,
saving it into each op’s attribute dictionary as secret :
-apply-folders
Apply all folding patterns from canonicalize
This pass applies all registered folding patterns greedily to the input IR. This is useful when running a full canonicalize is too slow, but applying folders before canonicalize is sufficient to simplify the IR for later passes, or even sufficient to then subsequently run a full canonicalize pass.
This is used to prepare an IR for insert-rotate
after fully unrolling
loops.
-bgv-to-lattigo
Lower bgv
to lattigo
dialect.
This pass lowers the bgv
dialect to Lattigo
dialect.
-bgv-to-lwe
Lower bgv
to lwe
dialect.
This pass lowers the bgv
dialect to lwe
dialect.
Note that some scheme specific ops (e.g., modswitch) that
have no direct analogue in the lwe
dialect are left unchanged.
TODO (#1193): support both “common” and “full” lwe lowering
-cggi-boolean-vectorize
Group different logic gates with the packed API
This pass groups independent logic gates into a single call of the packed operations. Pass is based on the straight-line-vectorizer, but is fundamentally different. This pass combines any type of boolean gates and is not restricted to combining the same type of gate operand.
Pass is intended for the FPT
tfhe-rs API, where packed_gates
function get a
the boolean gates are passed as a string vector and a left and right vector of ciphertexts.
Each boolean gates specified in gates
is then applied element wise.
let outputs_ct = fpga_key.packed_gates(&gates, &ref_to_ct_lefts, &ref_to_ct_rights);
Options
-parallelism : Parallelism factor for batching. 0 is infinite parallelism
-cggi-expand-lut
Expands LUTs into LWE operations and programmable bootstraps
This pass expands the linear combination performed in a LUT operation into the component LWE scalar operations and a programmable bootstrap operation.
For example, a LUT3 operation is composed of three LWE ciphertext inputs $c, b, a$ (in MSB to LSB ordering) which must be combined via the linear combination $4 * c + 2 * b + a$ before being fed into a programmable bootstrap defined by the lookup table.
This pass supports LUT2, LUT3, and LutLincomb operations.
-cggi-set-default-parameters
Set default parameters for CGGI ops
This pass adds default parameters to all CGGI ops as cggi_params
named
attributes, overriding any existing attribute set with that name.
This pass is primarily for testing purposes, and as a parameter provider before a proper parameter selection mechanism is added. This pass should not be used in production.
The specific parameters are hard-coded in
lib/Dialect/CGGI/Transforms/SetDefaultParameters.cpp
.
-cggi-to-jaxite
Lower cggi
to jaxite
dialect.
-cggi-to-tfhe-rust-bool
Lower cggi
to tfhe_rust_bool
dialect.
-cggi-to-tfhe-rust
Lower cggi
to tfhe_rust
dialect.
-ckks-to-lwe
Lower ckks
to lwe
dialect.
This pass lowers the ckks
dialect to lwe
dialect.
Note that some scheme specific ops (e.g., rescale) that
have no direct analogue in the lwe
dialect are left unchanged.
TODO (#1193): support both “common” and “full” lwe lowering
-collapse-insertion-chains
Collapse chains of extract/insert ops into rotate ops when possible
This pass is a cleanup pass for insert-rotate
. That pass sometimes leaves
behind a chain of insertion operations like this:
%extracted = tensor.extract %14[%c5] : tensor<16xi16>
%inserted = tensor.insert %extracted into %dest[%c0] : tensor<16xi16>
%extracted_0 = tensor.extract %14[%c6] : tensor<16xi16>
%inserted_1 = tensor.insert %extracted_0 into %inserted[%c1] : tensor<16xi16>
%extracted_2 = tensor.extract %14[%c7] : tensor<16xi16>
%inserted_3 = tensor.insert %extracted_2 into %inserted_1[%c2] : tensor<16xi16>
...
%extracted_28 = tensor.extract %14[%c4] : tensor<16xi16>
%inserted_29 = tensor.insert %extracted_28 into %inserted_27[%c15] : tensor<16xi16>
yield %inserted_29 : tensor<16xi16>
In many cases, this chain will insert into every index of the dest
tensor,
and the extracted values all come from consistently aligned indices of the same
source tensor. In this case, the chain can be collapsed into a single rotate
.
Each index used for insertion or extraction must be constant; this may
require running --canonicalize
or --sccp
before this pass to apply
folding rules (use --sccp
if you need to fold constant through control flow).
-convert-elementwise-to-affine
This pass lowers ElementwiseMappable operations to Affine loops.
This pass lowers ElementwiseMappable operations over tensors to affine loop nests that instead apply the operation to the underlying scalar values.
Usage: ‘–convert-elementwise-to-affine=convert-ops=arith.mulf ' restrict conversion to mulf op from arith dialect.
‘–convert-elementwise-to-affine=convert-ops=arith.addf,arith.divf convert-dialects=bgv’ restrict conversion to addf and divf ops from arith dialect and all of the ops in bgv dialect.
–convert-elementwise-to-affine=convert-dialects=arith restrict conversion to arith dialect so ops only from arith dialect is processed.
–convert-elementwise-to-affine=convert-ops=arith.addf,arith.mulf restrict conversion only to these two ops - addf and mulf - from arith dialect.
Options
-convert-ops : comma-separated list of ops to run this pass on
-convert-dialects : comma-separated list of dialects to run this pass on
-convert-if-to-select
Convert scf.if operations on secret conditions to arith.select operations.
Conversion for If-operations that evaluate secret condition to alternative select operations.
-convert-polynomial-mul-to-ntt
Rewrites polynomial operations to their NTT equivalents
Applies a rewrite pattern to convert polynomial multiplication to the equivalent using the number-theoretic transforms (NTT) when possible.
Polynomial multiplication can be rewritten as polynomial.NTT on each operand, followed by modulo elementwise multiplication of the point-value representation and then the inverse-NTT back to coefficient representation.
-convert-secret-extract-to-static-extract
Convert tensor.extract
operations on secret index to static extract operations.
Converts tensor.extract
operations that read value at secret index to alternative static tensor.extract
operations that extracts value at each index and conditionally selects the value extracted at the secret index.
Note: Running this pass alone does not result in a data-oblivious program; we have to run the --convert-if-to-select
pass to the resulting program to convert the secret-dependent If-operation to a Select-operation.
Example input:
mlir func.func @main(%secretTensor: !secret.secret<tensor<32xi16>>, %secretIndex: !secret.secret<index>)) -> !secret.secret<i16> { ... %0 = secret.generic ins(%secretTensor, %secretIndex : !secret.secret<tensor<32xi16>>, !secret.secret<index>) { ^bb0(%tensor: tensor<32xi16>, %index: index): // Violation: tensor.extract loads value at secret index %extractedValue = tensor.extract %tensor[%index] : tensor<16xi32> ... }
Output:
```mlir
func.func @main(%secretTensor: !secret.secret<tensor<32xi16>>, %secretIndex: !secret.secret<index>)) -> !secret.secret<i16> {
...
%0 = secret.generic ins(%secretTensor, %secretIndex : !secret.secret<tensor<32xi16>>, !secret.secret<index>) {
^bb0(%tensor: tensor<32xi16>, %index: index):
%extractedValue = affine.for %i=0 to 16 iter_args(%arg= %dummyValue) -> (i32) {
// 1. Check if %i matches %index
%cond = arith.cmpi eq, %i, %index : index
// 2. Extract value at %i
%value = tensor.extract %tensor[%i] : tensor<16xi32>
// 3. If %i matches %index, yield %value extracted in (2), else yield %dummyValue
%result = scf.if %cond -> (i32) {
scf.yield %value : i32
} else{
scf.yield %arg : i32
}
// 4. Yield result from (3)
affine.yield %result : i32
} … }
```
-convert-secret-for-to-static-for
Convert secret scf.for ops to affine.for ops with constant bounds.
Conversion for For-operation that evaluate secret bound(s) to alternative affine For-operation with constant bound(s).
It replaces data-dependent bounds with an If-operation to check the bounds, and conditionally execute and yield values from the For-operation’s body.
Note: Running this pass alone does not result in a data-oblivious program; we have to run the --convert-if-to-select
pass to the resulting program to convert the secret-dependent If-operation to a Select-operation.
Example input:
func.func @main(%secretTensor: !secret.secret<tensor<16xi32>>, %secretLower: !secret.secret<index>, %secretUpper: !secret.secret<index>) -> !secret.secret<i32> {
...
%0 = secret.generic ins(%secretTensor, %secretLower, %secretUpper : !secret.secret<tensor<16xi32>>, !secret.secret<index>, !secret.secret<index>){
^bb0(%tensor: tensor<16xi32>, %lower : index, %upper : index ):
...
%1 = scf.for %i = %lower to %upper step %step iter_args(%arg = %val) -> (i32) {
%extracted = tensor.extract %input[%i] : tensor<16xi32>
%sum = arith.addi %extracted, %arg : i32
scf.yield %sum : i32
} {lower = 0, upper = 16}
secret.yield %1 : i32
} -> !secret.secret<i32>
return %0 : !secret.secret<i32>
Output:
func.func @main(%secretTensor: !secret.secret<tensor<16xi32>>, %secretIndex: !secret.secret<index> {secret.secret}) -> !secret.secret<i32> {
...
%0 = secret.generic ins(%secretTensor, %secretLower, %secretUpper : !secret.secret<tensor<16xi32>>, !secret.secret<index>, !secret.secret<index>){
^bb0(%tensor: tensor<16xi32>, %lower : index, %upper : index ):
...
%1 = affine.for %i = 0 to 16 step %step iter_args(%arg = %val) -> (i32) {
%lowerCond = arith.cmpi sge, %i, %index : index
%upperCond = arith.cmpi slt, %i, %index : index
%cond = arith.andi %lowerCond, %upperCond : i1
%result = scf.if(%cond) -> (i32) {
%extracted = tensor.extract %input[%i] : tensor<16xi32>
%sum = arith.addi %extracted, %arg : i32
scf.yield %sum : i32
} else {
scf.yield %arg : i32
}
affine.yield %result : i32
} {lower = 0, upper = 16}
secret.yield %1 : i32
} -> !secret.secret<i32>
return %0 : !secret.secret<i32>
-convert-secret-insert-to-static-insert
Convert tensor.insert
operations on secret index to static insert operations.
Converts tensor.insert
operations that write to secret index to alternative static tensor.insert
operations that inserts the inserted value at each index and conditionally selects the newly produced tensor that contains the value at the secret index.
Note: Running this pass alone does not result in a data-oblivious program; we have to run the --convert-if-to-select
pass to the resulting program to convert the secret-dependent If-operation to a Select-operation.
Example input:
func.func @main(%secretTensor: !secret.secret<tensor<32xi16>>, %secretIndex: !secret.secret<index>)) -> !secret.secret<i16> {
...
%0 = secret.generic ins(%secretTensor, %secretIndex : !secret.secret<tensor<32xi16>>, !secret.secret<index>) {
^bb0(%tensor: tensor<32xi16>, %index: index):
// Violation: tensor.insert writes value at secret index
%inserted = tensor.insert %newValue into %tensor[%index] : tensor<16xi32>
...
}
Output:
func.func @main(%secretTensor: !secret.secret<tensor<32xi16>>, %secretIndex: !secret.secret<index>)) -> !secret.secret<i16> {
...
%0 = secret.generic ins(%secretTensor, %secretIndex : !secret.secret<tensor<32xi16>>, !secret.secret<index>) {
^bb0(%tensor: tensor<32xi16>, %index: index):
%inserted = affine.for %i=0 to 16 iter_args(%inputArg = %tensor) -> tensor<16xi32> {
// 1. Check if %i matches the %index
%cond = arith.cmpi eq, %i, %index : index
// 2. Insert %newValue and produce %newTensor
%newTensor = tensor.insert %value into %inputArg[%i] : tensor<16xi32>
// 3. If %i matches %inputIndex, yield %newTensor, else yield unchanged input tensor
%finalTensor = scf.if %cond -> (i32) {
scf.yield %newTensor : tensor<16xi32>
} else{
scf.yield %inputArg : tensor<16xi32>
}
// 4. Yield final tensor
affine.yield %finalTensor : tensor<16xi32>
}
...
}
-convert-secret-while-to-static-for
Convert secret scf.while ops to affine.for ops that have constant bounds.
Convert scf.while with a secret condition to affine.for with constant bounds. It replaces the scf.condition operation found in the scf.while loop with an scf.if operation that conditionally executes operations in the while operation’s body and yields values.
A “max_iter” attribute should be specified as part of the secret-dependent scf.while operation to successfully transform to a secret-independent affine.for operation. This attribute determines the maximum number of iterations for the new affine.for operation.
Note: Running this pass alone does not result in a data-oblivious program; we have to run the --convert-if-to-select
pass to the resulting program to convert the secret-dependent If-operation to a Select-operation.
Example input:
// C-like code
int main(int secretInput) {
while (secretInput > 100) {
secretInput = secretInput * secretInput;
}
return secretInput;
}
// MLIR
func.func @main(%secretInput: !secret.secret<i16>) -> !secret.secret<i16> {
%c100 = arith.constant 100 : i16
%0 = secret.generic ins(%secretInput : !secret.secret<i16>) {
^bb0(%input: i16):
%1 = scf.while (%arg1 = %input) : (i16) -> i16 {
%2 = arith.cmpi sgt, %arg1, %c100 : i16
scf.condition(%2) %arg1 : i16
} do {
^bb0(%arg1: i16):
%3 = arith.muli %arg1, %arg1 : i16
scf.yield %3 : i16
} attributes {max_iter = 16 : i64}
secret.yield %1 : i16
} -> !secret.secret<i16>
return %0 : !secret.secret<i16>
}
Output:
func.func @main(%secretInput: !secret.secret<i16>) -> !secret.secret<i16> {
%c100 = arith.constant 100 : i16
%0 = secret.generic ins(%secretInput : !secret.secret<i16>) {
^bb0(%input: i16):
%1 = affine.for 0 to 16 iter_args(%arg1 = %input) -> (i16) {
%2 = arith.cmpi sgt, %arg1, %c100 : i16
%3 = scf.if (%2) -> i16{
%4 = arith.muli %arg1, %arg1 : i16
scf.yield %4 : i16
} else {
scf.yield %arg1 : i16
}
affine.yield %3 : i16
} attributes {max_iter = 16 : i64}
secret.yield %1 : i16
} -> !secret.secret<i16>
return %0 : !secret.secret<i16>
}
-convert-tensor-to-scalars
Effectively ‘unrolls’ tensors of static shape to scalars.
This pass will convert a static-shaped tensor type to a TypeRange containing product(dim) copies of the element type of the tensor. This pass currently includes two patterns:
- It converts tensor.from_elements operations to the corresponding scalar inputs.
- It converts tensor.insert operations by updating the ValueRange corresponding to the converted input and updating it with the scalar to be inserted.
It also applies folders greedily to simplify, e.g., extract(from_elements).
Note: The pass is designed to be run on an IR, where the only operations
with tensor typed operands are tensor “management” operations such as insert/extract,
with all other operations (e.g., arith operations) already taking (extracted) scalar inputs.
For example, an IR where elementwise operations have been converted to scalar operations via
--convert-elementwise-to-affine
.
The pass might insert new tensor.from_elements operations or manually create the scalar ValueRange via inserting tensor.extract operations if any operations remain that operate on tensors. The pass currently applies irrespective of tensor size, i.e., might be very slow for large tensors.
TODO (#1023): Extend this pass to support more tensor operations, e.g., tensor.slice
Options
-max-size : Limits `unrolling` to tensors with at most max-size elements
-expand-copy
Expands memref.copy ops to explicit affine loads and stores
This pass removes memref copy operations by expanding them to affine loads and stores. This pass introduces affine loops over the dimensions of the MemRef, so must be run prior to any affine loop unrolling in a pipeline.
Input
module {
func.func @memref_copy() {
%alloc = memref.alloc() : memref<2x3xi32>
%alloc_0 = memref.alloc() : memref<2x3xi32>
memref.copy %alloc, %alloc_0 : memref<1x1xi32> to memref<1x1xi32>
}
}
Output
module {
func.func @memref_copy() {
%alloc = memref.alloc() : memref<2x3xi32>
%alloc_0 = memref.alloc() : memref<2x3xi32>
affine.for %arg0 = 0 to 2 {
affine.for %arg1 = 0 to 3 {
%1 = affine.load %alloc[%arg0, %arg1] : memref<2x3xi32>
affine.store %1, %alloc_0[%arg0, %arg1] : memref<2x3xi32>
}
}
}
}
When --disable-affine-loop=true
is set, then the output becomes
module {
func.func @memref_copy() {
%alloc = memref.alloc() : memref<2x3xi32>
%alloc_0 = memref.alloc() : memref<2x3xi32>
%c0 = arith.constant 0 : index
%c1 = arith.constant 1 : index
%c2 = arith.constant 2 : index
%0 = affine.load %alloc[%c0, %c0] : memref<2x3xi32>
affine.store %0, %alloc_0[%c0, %c0] : memref<2x3xi32>
%1 = affine.load %alloc[%c0, %c1] : memref<2x3xi32>
affine.store %1, %alloc_0[%c0, %c1] : memref<2x3xi32>
%2 = affine.load %alloc[%c0, %c2] : memref<2x3xi32>
affine.store %2, %alloc_0[%c0, %c2] : memref<2x3xi32>
[...]
}
}
Options
-disable-affine-loop : Use this to control to disable using affine loops
-extract-loop-body
Extracts logic of a loop bodies into functions.
This pass extracts logic in the inner body of for loops into functions.
This pass requires that tensors are lowered to memref. It expects that a loop body contains a number of affine.load statements used as inputs to the extracted function, and a single affine.store used as the extracted function’s output.
Input
module {
func.func @loop_body() {
%c-128_i8 = arith.constant -128 : i8
%c127_i8 = arith.constant 127 : i8
%alloc_7 = memref.alloc() {alignment = 64 : i64} : memref<25x20x8xi8>
affine.for %arg1 = 0 to 25 {
affine.for %arg2 = 0 to 20 {
affine.for %arg3 = 0 to 8 {
%98 = affine.load %alloc_6[%arg1, %arg2, %arg3] : memref<25x20x8xi8>
%99 = arith.cmpi slt, %arg0, %c-128_i8 : i8
%100 = arith.select %99, %c-128_i8, %arg0 : i8
%101 = arith.cmpi sgt, %arg0, %c127_i8 : i8
%102 = arith.select %101, %c127_i8, %100 : i8
affine.store %102, %alloc_7[%arg1, %arg2, %arg3] : memref<25x20x8xi8>
}
}
}
}
}
Output
module {
func.func @loop_body() {
%alloc_7 = memref.alloc() {alignment = 64 : i64} : memref<25x20x8xi8>
affine.for %arg1 = 0 to 25 {
affine.for %arg2 = 0 to 20 {
affine.for %arg3 = 0 to 8 {
%98 = affine.load %alloc_6[%arg1, %arg2, %arg3] : memref<25x20x8xi8>
%102 = func.call @__for_loop(%98) : (i8) -> i8
affine.store %102, %alloc_7[%arg1, %arg2, %arg3] : memref<25x20x8xi8>
}
}
}
}
func.func private @__for_loop(%arg0: i8) -> i8 {
%c-128_i8 = arith.constant -128 : i8
%c127_i8 = arith.constant 127 : i8
%99 = arith.cmpi slt, %arg0, %c-128_i8 : i8
%100 = arith.select %99, %c-128_i8, %arg0 : i8
%101 = arith.cmpi sgt, %arg0, %c127_i8 : i8
%102 = arith.select %101, %c127_i8, %100 : i8
return %102 : i8
}
}
Options
-min-loop-size : Use this to control the minimum loop size to apply this pass
-min-body-size : Use this to control the minimum loop body size to apply this pass
-forward-insert-to-extract
Forward inserts to extracts within a single block
This pass is similar to forward-store-to-load pass where store ops are forwarded load ops; here instead tensor.insert ops are forwarded to tensor.extract ops.
Does not support complex control flow within a block, nor ops with arbitrary subregions.
-forward-store-to-load
Forward stores to loads within a single block
This pass is a simplified version of mem2reg and similar passes. It analyzes an operation, finding all basic blocks within that op that have memrefs whose stores can be forwarded to loads.
Does not support complex control flow within a block, nor ops with arbitrary subregions.
-full-loop-unroll
Fully unroll all loops
Scan the IR for affine.for loops and unroll them all.
-insert-rotate
Vectorize arithmetic FHE operations using HECO-style heuristics
This pass implements the SIMD-vectorization passes from the HECO paper.
The pass operates by identifying arithmetic operations that can be suitably combined into a combination of cyclic rotations and vectorized operations on tensors. It further identifies a suitable “slot target” for each operation and heuristically aligns the operations to reduce unnecessary rotations.
This pass by itself does not eliminate any operations, but instead inserts
well-chosen rotations so that, for well-structured code (like unrolled affine loops),
a subsequent --cse
and --canonicalize
pass will dramatically reduce the IR.
As such, the pass is designed to be paired with the canonicalization patterns
in tensor_ext
, as well as the collapse-insertion-chains
pass, which
cleans up remaining insertion and extraction ops after the main simplifications
are applied.
Unlike HECO, this pass operates on plaintext types and tensors, along with
the HEIR-specific tensor_ext
dialect for its cyclic rotate
op. It is intended
to be run before lowering to a scheme dialect like bgv
.
-linalg-canonicalizations
This pass canonicalizes the linalg.transpose operation of a constant into a transposed constant.
This pass canonicalizes the linalg.transpose operation of a constant into a transposed constant.
-linalg-to-tensor-ext
Lower linalg.matmul
to arith and tensor_ext dialects.
This pass lowers the linalg.matmul
to a mixture of affine, tensor, and
via the Halevi-Shoup and squat matrix multiplication algorithms.
-lwe-add-client-interface
Add client interfaces to (R)LWE encrypted functions
This pass adds encrypt and decrypt functions for each compiled function in the IR. These functions maintain the same interface as the original function, while the compiled function may lose some of this information by the lowerings to ciphertext types (e.g., a scalar ciphertext, when lowered through RLWE schemes, must be encoded as a tensor).
Options
-use-public-key : If true, generate a client interface that uses a public key for encryption.
-one-value-per-helper-fn : If true, split encryption helpers into separate functions for each SSA value.
-lwe-set-default-parameters
Set default parameters for LWE ops
This pass adds default parameters to all lwe
types as the lwe_params
attribute, and for lwe
ops as the params
attribute, overriding any
existing attributes set with those names.
This pass is primarily for testing purposes, and as a parameter provider before a proper parameter selection mechanism is added. This pass should not be used in production.
The specific parameters are hard-coded in
lib/Dialect/LWE/Transforms/SetDefaultParameters.cpp
.
-lwe-to-openfhe
Lower lwe
to openfhe
dialect.
This pass lowers the lwe
dialect to Openfhe
dialect.
Currently, this also includes patterns that apply directly to ckks
and bgv
dialect operations.
TODO (#1193): investigate if the need for ckks/bgv
patterns in --lwe-to-openfhe
is permanent.
-lwe-to-polynomial
Lower lwe
to polynomial
dialect.
This pass lowers the lwe
dialect to polynomial
dialect.
-memref-global-replace
MemrefGlobalReplacePass forwards global memrefs accessors to arithmetic values
This pass forwards constant global MemRef values to referencing affine loads. This pass requires that the MemRef global values are initialized as constants and that the affine load access indices are constants (i.e. not variadic). Unroll affine loops prior to running this pass.
MemRef removal is required to remove any memory allocations from the input model (for example, TensorFlow models contain global memory holding model weights) to support FHE transpilation.
Input
module {
memref.global "private" constant @__constant_8xi16 : memref<2x4xi16> = dense<[[-10, 20, 3, 4], [5, 6, 7, 8]]>
func.func @main() -> i16 {
%c1 = arith.constant 1 : index
%c2 = arith.constant 2 : index
%0 = memref.get_global @__constant_8xi16 : memref<2x4xi16>
%1 = affine.load %0[%c1, %c1 + %c2] : memref<2x4xi16>
return %1 : i16
}
}
Output
module {
func.func @main() -> i16 {
%c1 = arith.constant 1 : index
%c2 = arith.constant 2 : index
%c8_i16 = arith.constant 8 : i16
return %c8_i16 : i16
}
}
-mod-arith-to-arith
Lower mod_arith
to standard arith
.
This pass lowers the mod_arith
dialect to their arith
equivalents.
-mod-arith-to-mac
Finds consecutive ModArith mul and add operations and converts them to a Mac operation
Walks over the programs to find Add operations, it checks if the any operands originates from a mul operation. If so, it converts the Add operation to a Mac operation and removes the mul operation.
-openfhe-configure-crypto-context
Configure the crypto context in OpenFHE
This pass generates helper functions to generate and configure the OpenFHE crypto context for the given function. Generating the crypto context sets the appropriate encryption parameters, while the configuration generates the necessary evaluation keys (relinearization and rotation keys).
For example, for an MLIR function @my_func
, the generated helpers have the following signatures
func.func @my_func__generate_crypto_context() -> !openfhe.crypto_context
func.func @my_func__configure_crypto_context(!openfhe.crypto_context, !openfhe.private_key) -> !openfhe.crypto_context
Options
-entry-function : Default entry function name of entry function.
-level-budget-encode : Level budget for CKKS bootstrap encode (s2c) phase
-level-budget-decode : Level budget for CKKS bootstrap decode (c2s) phase
-insecure : Whether to use insecure parameter for faster evaluation(should only be used in test) (defaults to false)
-operation-balancer
This pass balances addition and multiplication operations.
This pass examines a tree or graph of add and multiplication operations and balances them to minimize the depth of the tree. This exposes better parallelization and reducing the multiplication depth can decrease the parameters used in FHE, which improves performance. This pass is not necessarily optimal, as there may be intermediate computations that this pass does not optimally minimize the depth for.
The algorithm is to analyze a graph of addition operations and do a depth-first search for the operands (from the last computed values in the graph). If there are intermediate computations that are used more than once, then the pass treats that computation as its own tree to balance instead of trying to minimize the global depth of the tree.
This pass only runs on addition and multiplication operations on the arithmetic dialect that are encapsulated inside a secret.generic.
This pass was inspired by section 2.6 of ‘EVA Improved: Compiler and Extension Library for CKKS’ by Chowdhary et al.
-optimize-relinearization
Optimize placement of relinearization ops
This pass defers relinearization ops as late as possible in the IR. This is more efficient in cases where multiplication operations are followed by additions, such as in a dot product. Because relinearization also adds error, deferring it can reduce the need for bootstrapping.
In this pass, we use an integer linear program to determine the optimal
relinearization strategy. It solves an ILP for each func
op in the IR.
The assumptions of this pass include:
- All return values of functions must be linearized.
- All ciphertext arguments to an op must have the same key basis
- Rotation op inputs must have be linearized.
For an ILP model specification, see the docs at the HEIR website. The model is an adaptation of the ILP described in a blog post by Jeremy Kun.
Options
-use-loc-based-variable-names : When true, the ILP uses op source locations in variable names, which can help debug ILP model bugs.
-polynomial-to-mod-arith
Lower polynomial
to standard MLIR dialects.
This pass lowers the polynomial
dialect to standard MLIR plus mod_arith,
including possibly ops from affine, tensor, linalg, and arith.
-remove-unused-memref
Cleanup any unused memrefs
Scan the IR for unused memrefs and remove them.
This pass looks for locally allocated memrefs that are never used and deletes them. This pass can be used as a cleanup pass from other IR simplifications that forward stores to loads.
-rotate-and-reduce
Use a logarithmic number of rotations to reduce a tensor.
This pass identifies when a commutative, associative binary operation is used to reduce all of the entries of a tensor to a single value, and optimizes the operations by using a logarithmic number of reduction operations.
In particular, this pass identifies an unrolled set of operations of the form (the binary ops may come in any order):
%0 = tensor.extract %t[0] : tensor<8xi32>
%1 = tensor.extract %t[1] : tensor<8xi32>
%2 = tensor.extract %t[2] : tensor<8xi32>
%3 = tensor.extract %t[3] : tensor<8xi32>
%4 = tensor.extract %t[4] : tensor<8xi32>
%5 = tensor.extract %t[5] : tensor<8xi32>
%6 = tensor.extract %t[6] : tensor<8xi32>
%7 = tensor.extract %t[7] : tensor<8xi32>
%8 = arith.addi %0, %1 : i32
%9 = arith.addi %8, %2 : i32
%10 = arith.addi %9, %3 : i32
%11 = arith.addi %10, %4 : i32
%12 = arith.addi %11, %5 : i32
%13 = arith.addi %12, %6 : i32
%14 = arith.addi %13, %7 : i32
and replaces it with a logarithmic number of rotate
and addi
operations:
%0 = tensor_ext.rotate %t, 4 : tensor<8xi32>
%1 = arith.addi %t, %0 : tensor<8xi32>
%2 = tensor_ext.rotate %1, 2 : tensor<8xi32>
%3 = arith.addi %1, %2 : tensor<8xi32>
%4 = tensor_ext.rotate %3, 1 : tensor<8xi32>
%5 = arith.addi %3, %4 : tensor<8xi32>
-secret-capture-generic-ambient-scope
Capture the ambient scope used in a secret.generic
For each value used in the body of a secret.generic
op, which is defined
in the ambient scope outside the generic
, add it to the argument list of
the generic
.
-secret-distribute-generic
Distribute generic
ops through their bodies.
Converts generic
ops whose region contains many ops into smaller
sequences of generic ops whose regions contain a single op, dropping the
generic
part from any resulting generic
ops that have no
secret.secret
inputs. If the op has associated regions, and the operands
are not secret, then the generic is distributed recursively through the
op’s regions as well.
This pass is intended to be used as part of a front-end pipeline, where a
program that operates on a secret type annotates the input to a region as
secret
, and then wraps the contents of the region in a single large
secret.generic
, then uses this pass to simplify it.
The distribute-through
option allows one to specify a comma-separated
list of op names (e.g., distribute-thorugh="affine.for,scf.if"
), which
limits the distribution to only pass through those ops. If unset, all ops
are distributed through when possible.
Options
-distribute-through : comma-separated list of ops that should be distributed through
-secret-extract-generic-body
Extract the bodies of all generic ops into functions
This pass extracts the body of all generic ops into functions, and replaces the generic bodies with call ops. Used as a sub-operation in some passes, and extracted into its own pass for testing purposes.
This pass works best when --secret-generic-absorb-constants
is run
before it so that the extracted function contains any constants used
in the generic op’s body.
-secret-forget-secrets
Convert secret types to standard types
Drop the secret<...>
type from the IR, replacing it with the contained
type and the corresponding cleartext computation.
-secret-generic-absorb-constants
Copy constants into a secret.generic body
For each constant value used in the body of a secret.generic
op, which is
defined in the ambient scope outside the generic
, add it’s definition into
the generic
body.
-secret-generic-absorb-dealloc
Copy deallocs of internal memrefs into a secret.generic body
For each memref allocated and used only within a body of a secret.generic
op, add it’s dealloc of the memref into its generic
body.
-secret-insert-mgmt-bgv
Place BGV ciphertext management operations
This pass implements the following placement strategy:
For relinearize, after every homomorphic ciphertext-ciphertext multiplication, a mgmt.relinearize is placed after the operation. This is done to ensure that the ciphertext keeps linear.
For modulus switching, it is inserted right before a homomorphic multiplication,
including ciphertext-plaintext ones. There is an option include-first
controlling
whether to switch modulus before the first multiplication.
User can check the FLEXIBLEAUTOEXT and FLEXIBLEAUTO mode in OpenFHE as a reference. To know more technical difference about them, user can refer to the paper “Revisiting homomorphic encryption schemes for finite firelds”.
Then, for level-mismatching binary operations like addition and subtraction, additional modulus switch is placed for the operand until it reaches the same level.
This is different from crosslevel operation handling in other implementations like using modulus switching and level drop together. The reason we only use modulus switching is for simplicity for now. Further optimization on this pass could implement such a strategy.
Before yield the final result, a modulus switching is placed if it is a result of multiplication or derived value of a multiplication.
Also, it annotates the mgmt.mgmt attribute for each operation, which includes the level and dimension information of a ciphertext. This information is subsequently used by the secret-to-bgv pass to properly lower to corresponding RNS Type.
Example of multiplication+addition:
func.func @func(%arg0: !secret.secret<i16>, %arg1: !secret.secret<i16>) -> !secret.secret<i16> {
%0 = secret.generic ins(%arg0, %arg1 : !secret.secret<i16>, !secret.secret<i16>) {
^bb0(%arg2: i16, %arg3: i16):
%1 = arith.muli %arg2, %arg3 : i16
%2 = arith.addi %1, %arg3 : i16
secret.yield %2 : i16
} -> !secret.secret<i16>
return %0 : !secret.secret<i16>
}
which get transformed to:
func.func @func(%arg0: !secret.secret<i16>, %arg1: !secret.secret<i16>) -> !secret.secret<i16> {
%0 = secret.generic ins(%arg0, %arg1 : !secret.secret<i16>, !secret.secret<i16>) attrs = {arg0 = {mgmt.mgmt = #mgmt.mgmt<level = 1>}, arg1 = {mgmt.mgmt = #mgmt.mgmt<level = 1>}} {
^bb0(%arg2: i16, %arg3: i16):
%1 = arith.muli %arg2, %arg3 {mgmt.mgmt = #mgmt.mgmt<level = 1, dimension = 3>} : i16
%2 = mgmt.relinearize %1 {mgmt.mgmt = #mgmt.mgmt<level = 1>} : i16
%3 = arith.addi %2, %arg3 {mgmt.mgmt = #mgmt.mgmt<level = 1>} : i16
%4 = mgmt.modreduce %3 {mgmt.mgmt = #mgmt.mgmt<level = 0>} : i16
secret.yield %4 : i16
} -> !secret.secret<i16>
return %0 : !secret.secret<i16>
}
Options
-include-first-mul : Modulus switching right before the first multiplication (default to false)
-secret-insert-mgmt-ckks
Place CKKS ciphertext management operations
Check the description of secret-insert-mgmt-bgv. This pass implements similar strategy, where mgmt.modreduce stands for ckks.rescale here.
Options
-include-first-mul : Modulus switching right before the first multiplication (default to false)
-slot-number : Default number of slots use for ciphertext space.
-secret-merge-adjacent-generics
Merge two adjacent generics into a single generic
This pass merges two immedaitely sequential generics into a single generic. Useful as a sub-operation in some passes, and extracted into its own pass for testing purposes.
-secret-to-bgv
Lower secret
to bgv
dialect.
This pass lowers an IR with secret.generic
blocks containing arithmetic
operations to operations on ciphertexts with the BGV dialect.
The pass assumes that the secret.generic
regions have been distributed
through arithmetic operations so that only one ciphertext operation appears
per generic block. It also requires that canonicalize
was run so that
non-secret values used are removed from the secret.generic
’s block
arguments.
The pass requires that all types are tensors of a uniform shape matching the
dimension of the ciphertext space specified my poly-mod-degree
.
Options
-poly-mod-degree : Default degree of the cyclotomic polynomial modulus to use for ciphertext space.
-coefficient-mod-bits : Default number of bits of the prime coefficient modulus to use for the ciphertext space.
-secret-to-cggi
Lower secret
to cggi
dialect.
This pass lowers the secret
dialect to cggi
dialect.
-secret-to-ckks
Lower secret
to ckks
dialect.
This pass lowers an IR with secret.generic
blocks containing arithmetic
operations to operations on ciphertexts with the CKKS dialect.
The pass assumes that the secret.generic
regions have been distributed
through arithmetic operations so that only one ciphertext operation appears
per generic block. It also requires that canonicalize
was run so that
non-secret values used are removed from the secret.generic
’s block
arguments.
The pass requires that all types are tensors of a uniform shape matching the
dimension of the ciphertext space specified my poly-mod-degree
.
Options
-poly-mod-degree : Default degree of the cyclotomic polynomial modulus to use for ciphertext space.
-coefficient-mod-bits : Default number of bits of the prime coefficient modulus to use for the ciphertext space.
-secretize
Adds secret argument attributes to entry function
Helper pass that adds a secret.secret attribute argument to each function argument. By default, the pass applies to all functions in the module. This may be overridden with the option -function=func_name to apply to a single function only.
Options
-function : function to add secret annotations to
-straight-line-vectorize
A vectorizer for straight line programs.
This pass ignores control flow and only vectorizes straight-line programs within a given region.
Options
-dialect : Use this to restrict the dialect whose ops should be vectorized.
-tosa-to-secret-arith
Lower tosa.sigmoid
to secret arith dialects.
This pass lowers the tosa.sigmoid
dialect to the polynomial approximation
-0.004 * x^3 + 0.197 * x + 0.5 (composed of arith, affine, and tensor operations).
This polynomial approximation of sigmoid only works over the range [-5, 5] and is taken from the paper ‘Logisitic regression over encrypted data from fully homomorphic encryption’ by Chen et al..
-unroll-and-forward
Loop unrolls and forwards stores to loads.
This pass processes the first function in a given module, and, starting from the first loop, iteratively does the following:
- Fully unroll the loop.
- Scan for load ops. For each load op with a statically-inferrable access index:
- Backtrack to the original memref alloc
- Find all store ops at the corresponding index (possibly transitively through renames/subviews of the underlying alloc).
- Find the last store that occurs and forward it to the load.
- If the original memref is an input memref, then forward through any renames to make the target load load directly from the argument memref (instead of any subviews, say)
- Apply the same logic to any remaining loads not inside any for loop.
This pass requires that tensors are lowered to memref, and only supports affine loops with affine.load/store ops.
Memrefs that result from memref.get_global ops are excluded from forwarding, even if they are loaded with a static index, and are instead handled by memref-global-replace, which should be run after this pass.
-wrap-generic
Wraps regions using secret args in secret.generic bodies
This pass converts functions (func.func
) with {secret.secret}
annotated arguments
to use !secret.secret<...>
types and wraps the function body in a secret.generic
region.
The output type is also converted to !secret.secret<...>
.
Example input:
func.func @main(%arg0: i32 {secret.secret}) -> i32 {
%0 = arith.constant 100 : i32
%1 = arith.addi %0, %arg0 : i32
return %1 : i32
}
Output:
func.func @main(%arg0: !secret.secret<i32>) -> !secret.secret<i32> {
%0 = secret.generic ins(%arg0 : !secret.secret<i32>) {
^bb0(%arg1: i32):
%1 = arith.constant 100 : i32
%2 = arith.addi %0, %arg1 : i32
secret.yield %2 : i32
} -> !secret.secret<i32>
return %0 : !secret.secret<i32>
}
-yosys-optimizer
Invoke Yosys to perform circuit optimization.
This pass invokes Yosys to convert an arithmetic circuit to an optimized boolean circuit that uses the arith and comb dialects.
Note that booleanization changes the function signature: multi-bit integers
are transformed to a tensor of booleans, for example, an i8
is converted
to tensor<8xi1>
.
The optimizer will be applied to each secret.generic
op containing
arithmetic ops that can be optimized.
Optional parameters:
abc-fast
: Run the abc optimizer in “fast” mode, getting faster compile time at the expense of a possibly larger output circuit.unroll-factor
: Before optimizing the circuit, unroll loops by a given factor. If unset, this pass will not unroll any loops.print-stats
: Prints statistics about the optimized circuits.mode={Boolean,LUT}
: Map gates to boolean gates or lookup table gates.use-submodules
: Extract the body of a generic op into submodules. Useful for large programs with generics that can be isolated. This should not be used when distributing generics through loops to avoid index arguments in the function body.
Statistics
total circuit size : The total circuit size for all optimized circuits, after optimization is done.