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
- 6: Pipelines
- 7: Dialects
- 7.1: BGV
- 7.2: CGGI
- 7.3: CKKS
- 7.4: Comb
- 7.5: Jaxite
- 7.6: LinAlg
- 7.7: LWE
- 7.8: ModArith
- 7.9: Openfhe
- 7.10: Polynomial
- 7.11: Random
- 7.12: RNS
- 7.13: Secret
- 7.14: TensorExt
- 7.15: TfheRust
- 7.16: TfheRustBool
- 7.17: TOSA
- 8: Passes
- 8.1: ApplyFoldersPasses
- 8.2: BGVToLWE
- 8.3: BGVToOpenfhe
- 8.4: CGGIPasses
- 8.5: CGGIToJaxite
- 8.6: CGGIToTfheRust
- 8.7: CGGIToTfheRustBool
- 8.8: CKKSToOpenfhe
- 8.9: CombToCGGI
- 8.10: ConvertIfToSelectPasses
- 8.11: ConvertSecretExtractToStaticExtractPasses
- 8.12: ConvertSecretForToStaticForPasses
- 8.13: ConvertSecretInsertToStaticInsertPasses
- 8.14: ConvertSecretWhileToStaticForPasses
- 8.15: ElementwiseToAffinePasses
- 8.16: ForwardInsertToExtractPasses
- 8.17: ForwardStoreToLoadPasses
- 8.18: FullLoopUnrollPasses
- 8.19: LinalgCanonicalizationsPasses
- 8.20: LinalgToTensorExt
- 8.21: LWEPasses
- 8.22: LWEToPolynomial
- 8.23: OpenfhePasses
- 8.24: OperationBalancerPasses
- 8.25: PolynomialPasses
- 8.26: PolynomialToStandard
- 8.27: SecretizePasses
- 8.28: SecretPasses
- 8.29: SecretToBGV
- 8.30: SecretToCKKS
- 8.31: StraightLineVectorizerPasses
- 8.32: TensorExtPasses
- 8.33: TensorToScalarsPasses
- 8.34: TosaToSecretArith
- 8.35: UnusedMemRefPasses
- 8.36: YosysOptimizerPasses
1 - Getting Started
Getting HEIR
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 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.
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>, %arg1: tensor<8xi16>) -> 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). The raw
output is rather verbose, and an abbreviated version is shown below.
!tensor_ct = !lwe.rlwe_ciphertext<..., underlying_type = tensor<8xi16>>
!scalar_ct = !lwe.rlwe_ciphertext<..., underlying_type = i16>
!mul_ct = !lwe.rlwe_ciphertext<..., underlying_type = tensor<8xi16>>
!tensor_plaintext = lwe.rlwe_plaintext<..., underlying_type = tensor<8xi16>>
module {
func.func @dot_product(%arg0: !openfhe.crypto_context, %arg1: !tensor_ct, %arg2: !tensor_ct) -> !scalar_ct {
%c1 = arith.constant 1 : index
%c2 = arith.constant 2 : index
%c4 = arith.constant 4 : index
%c7 = arith.constant 7 : index
%0 = openfhe.mul_no_relin %arg0, %arg1, %arg2 : (!openfhe.crypto_context, !tensor_ct, !tensor_ct) -> !mul_ct
%1 = openfhe.relin %arg0, %0 : (!openfhe.crypto_context, !mul_ct) -> !tensor_ct
%2 = arith.index_cast %c4 : index to i64
%3 = openfhe.rot %arg0, %1, %2 : (!openfhe.crypto_context, !tensor_ct, i64) -> !tensor_ct
%4 = openfhe.add %arg0, %1, %3 : (!openfhe.crypto_context, !tensor_ct, !tensor_ct) -> !tensor_ct
%5 = arith.index_cast %c2 : index to i64
%6 = openfhe.rot %arg0, %4, %5 : (!openfhe.crypto_context, !tensor_ct, i64) -> !tensor_ct
%7 = openfhe.add %arg0, %4, %6 : (!openfhe.crypto_context, !tensor_ct, !tensor_ct) -> !tensor_ct
%8 = arith.index_cast %c1 : index to i64
%9 = openfhe.rot %arg0, %7, %8 : (!openfhe.crypto_context, !tensor_ct, i64) -> !tensor_ct
%10 = openfhe.add %arg0, %7, %9 : (!openfhe.crypto_context, !tensor_ct, !tensor_ct) -> !tensor_ct
%cst = arith.constant dense<[0, 0, 0, 0, 0, 0, 0, 1]> : tensor<8xi16>
%11 = lwe.rlwe_encode %cst {encoding = #lwe.polynomial_evaluation_encoding<cleartext_start = 16, cleartext_bitwidth = 16>, ring = #_polynomial.ring<cmod=463187969, ideal=#_polynomial.polynomial<1 + x**8>>} : tensor<8xi16> -> !tensor_plaintext
%12 = openfhe.mul_plain %arg0, %10, %11 : (!openfhe.crypto_context, !tensor_ct, !tensor_plaintext) -> !tensor_ct
%13 = arith.index_cast %c7 : index to i64
%14 = openfhe.rot %arg0, %12, %13 : (!openfhe.crypto_context, !tensor_ct, i64) -> !tensor_ct
%15 = lwe.reinterpret_underlying_type %14 : !tensor_ct to !scalar_ct
return %15 : !scalar_ct
}
func.func @dot_product__encrypt__arg0(%arg0: !openfhe.crypto_context, %arg1: tensor<8xi16>, %arg2: !openfhe.public_key) -> !tensor_ct
...
}
func.func @dot_product__encrypt__arg1(%arg0: !openfhe.crypto_context, %arg1: tensor<8xi16>, %arg2: !openfhe.public_key) -> !tensor_ct
...
}
func.func @dot_product__decrypt__result0(%arg0: !openfhe.crypto_context, %arg1: !scalar_ct, %arg2: !openfhe.private_key) -> i16 {
...
}
}
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 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 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);
// 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) {
size_t v3 = 1;
size_t v4 = 2;
size_t v5 = 4;
size_t v6 = 7;
const auto& v7 = v0->EvalMultNoRelin(v1, v2);
const auto& v8 = v0->Relinearize(v7);
int64_t v9 = static_cast<int64_t>(v5);
const auto& v10 = v0->EvalRotate(v8, v9);
const auto& v11 = v0->EvalAdd(v8, v10);
int64_t v12 = static_cast<int64_t>(v4);
const auto& v13 = v0->EvalRotate(v11, v12);
const auto& v14 = v0->EvalAdd(v11, v13);
int64_t v15 = static_cast<int64_t>(v3);
const auto& v16 = v0->EvalRotate(v14, v15);
const auto& v17 = v0->EvalAdd(v14, v16);
std::vector<int16_t> v18 = {0, 0, 0, 0, 0, 0, 0, 1};
std::vector<int64_t> v18_cast(std::begin(v18), std::end(v18));
const auto& v19 = v0->MakePackedPlaintext(v18_cast);
const auto& v20 = v0->EvalMult(v17, v19);
int64_t v21 = static_cast<int64_t>(v6);
const auto& v22 = v0->EvalRotate(v20, v21);
const auto& v23 = v22;
return v23;
}
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) {
...
}
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[]) {
CCParams<CryptoContextBGVRNS> parameters;
// TODO(#661): replace this setup with a HEIR-generated helper function
parameters.SetMultiplicativeDepth(2);
parameters.SetPlaintextModulus(65537);
CryptoContext<DCRTPoly> cryptoContext = GenCryptoContext(parameters);
cryptoContext->Enable(PKE);
cryptoContext->Enable(KEYSWITCH);
cryptoContext->Enable(LEVELEDSHE);
KeyPair<DCRTPoly> keyPair;
keyPair = cryptoContext->KeyGen();
cryptoContext->EvalMultKeyGen(keyPair.secretKey);
cryptoContext->EvalRotateKeyGen(keyPair.secretKey, {1, 2, 4, 7});
int32_t n = cryptoContext->GetCryptoParameters()
->GetElementParams()
->GetCyclotomicOrder() /
2;
int16_t arg0Vals[8] = {1, 2, 3, 4, 5, 6, 7, 8};
int16_t arg1Vals[8] = {2, 3, 4, 5, 6, 7, 8, 9};
int64_t expected = 240;
std::vector<int16_t> arg0;
std::vector<int16_t> arg1;
arg0.reserve(n);
arg1.reserve(n);
// TODO(#645): support cyclic repetition in add-client-interface
for (int i = 0; i < n; ++i) {
arg0.push_back(arg0Vals[i % 8]);
arg1.push_back(arg1Vals[i % 8]);
}
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 -- \
--comb-to-cggi -cse \
$PWD/tests/comb_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=entry-function=box_blur --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.
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
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 type for RLWE ciphertexts |
rhs | A type for RLWE ciphertexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE ciphertexts |
plaintextInput | A type for RLWE plaintexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE ciphertexts |
offset | signless integer or index |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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::polynomial::RingAttr | an attribute specifying a polynomial ring |
Operands:
Operand | Description |
---|---|
input | A type for RLWE ciphertexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE ciphertexts |
rhs | A type for RLWE ciphertexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE ciphertexts |
plaintextInput | A type for RLWE plaintexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE ciphertexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE ciphertexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE ciphertexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE ciphertexts |
rhs | A type for RLWE ciphertexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE ciphertexts |
plaintextInput | A type for RLWE plaintexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
7.2 - CGGI
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;
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_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.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
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 type for RLWE ciphertexts |
rhs | A type for RLWE ciphertexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE ciphertexts |
plaintextInput | A type for RLWE plaintexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE ciphertexts |
offset | signless integer or index |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE ciphertexts |
rhs | A type for RLWE ciphertexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE ciphertexts |
plaintextInput | A type for RLWE plaintexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE ciphertexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE ciphertexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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::polynomial::RingAttr | an attribute specifying a polynomial ring |
Operands:
Operand | Description |
---|---|
input | A type for RLWE ciphertexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE ciphertexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE ciphertexts |
rhs | A type for RLWE ciphertexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE ciphertexts |
plaintextInput | A type for RLWE plaintexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
7.4 - Comb
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
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
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
(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 |
7.6 - LinAlg
LinAlg additional definitions
7.7 - LWE
LWE attributes
PlaintextEncodingAttr
Syntax: #lwe.plaintext_encoding
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::polynomial::RingAttr, # ring
::mlir::heir::lwe::LweEncryptionType # encryption_type
>
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 structure, which contains the ciphertext modulus $q$. Ciphertexts using an RNS representation for $q$ will represent their ciphertext components in the ring attribute. Scalar LWE ciphertexts (as opposed to RLWE) use an ideal polynomial of degree 1, $x$. CGGI ciphertexts will typically use a power of two modulus.
The ciphertext encoding info is used to describe the way the plaintext data is encoded into the ciphertext (in the MSB, LSB, or mixed).
Parameters:
Parameter | C++ type | Description |
---|---|---|
ring | ::mlir::polynomial::RingAttr | |
encryption_type | ::mlir::heir::lwe::LweEncryptionType |
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<
::mlir::StringAttr, # id
unsigned, # size
::llvm::ArrayRef<unsigned int> # basis
>
An attribute describing the key used for encrypting the ciphertext.
This attribute includes a key identifier for the original key used to encrypt the secret key.
The key_size
parameter is used to describe the number of polynomials of
the secret key. This is typically $1$ for RLWE ciphertexts and greater than
$1$ for LWE instances. A ciphertext encrypted with a key_size
of $k$ will
have size $k+1$.
The key basis describes the inner product used in the phase calculation in
decryption. This attribute is only supported for RLWE ciphertexts whose
key_size
is $1$. An RLWE 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]
.
Parameters:
Parameter | C++ type | Description |
---|---|---|
id | ::mlir::StringAttr | |
size | unsigned | |
basis | ::llvm::ArrayRef<unsigned 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::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::polynomial::RingAttr | |
encoding | Attribute |
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::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::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 |
InverseCanonicalEmbeddingEncodingAttr
An attribute describing encoded RLWE plaintexts via the rounded inverse canonical embedding.
Syntax:
#lwe.inverse_canonical_embedding_encoding<
unsigned, # cleartext_start
unsigned # cleartext_bitwidth
>
Let $n$ be the degree of the polynomials in the plaintext space. An “inverse canonical embedding 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 cleartext_start
and
cleartext_bitwidth
parameters, which are applied coefficient-wise using
the same semantics as the bit_field_encoding
.
This attribute can be used in multiple ways:
- On a
poly.poly
, it asserts that the polynomial has been transformed from a coefficient list using the canonical embedding. - On a tensor of
poly.poly
, it asserts that the tensor is an RLWE ciphertext for some RLWE scheme that supports the approximate embedding 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:
#generator = #poly.polynomial<1 + x**1024>
#ring = #poly.ring<cmod=65536, ideal=#generator>
#lwe_encoding = #lwe.polynomial_evaluation_encoding<cleartext_start=30, cleartext_bitwidth=3>
%evals = arith.constant <[1, 2, 4, 5]> : tensor<4xi16>
%poly1 = poly.intt %evals : tensor<4xi16, #ring> -> !poly.poly<#ring, #eval_encoding>
%poly2 = poly.intt %evals : tensor<4xi16, #ring> -> !poly.poly<#ring, #eval_encoding>
%rlwe_ciphertext = tensor.from_elements %poly1, %poly2 : tensor<2x!poly.poly<#ring, #eval_encoding>>
See bit_field_encoding
for the definition of the cleartext_start
and
cleartext_bitwidth
fields.
Parameters:
Parameter | C++ type | Description |
---|---|---|
cleartext_start | unsigned | |
cleartext_bitwidth | unsigned |
PolynomialCoefficientEncodingAttr
An attribute describing encoded RLWE plaintexts via coefficients.
Syntax:
#lwe.polynomial_coefficient_encoding<
unsigned, # cleartext_start
unsigned # cleartext_bitwidth
>
A coefficient encoding of a list of integers asserts that the coefficients
of the polynomials contain the cleartexts, with the same semantics as
bit_field_encoding
for per-coefficient encodings.
The presence of this attribute as the encoding
attribute of a tensor of
poly.poly
indicates that the tensor is an RLWE ciphertext for some RLWE
scheme that supports the coefficient encoding.
See bit_field_encoding
for the definition of the cleartext_start
and
cleartext_bitwidth
fields.
Example:
#generator = #poly.polynomial<1 + x**1024>
#ring = #poly.ring<cmod=65536, ideal=#generator>
#coeff_encoding = #lwe.polynomial_coefficient_encoding<cleartext_start=15, cleartext_bitwidth=4>
%poly1 = poly.from_tensor %coeffs1 : tensor<10xi16> -> !poly.poly<#ring>
%poly2 = poly.from_tensor %coeffs2 : tensor<10xi16> -> !poly.poly<#ring>
%rlwe_ciphertext = tensor.from_elements %poly1, %poly2 : tensor<2x!poly.poly<#ring>, #coeff_encoding>
Parameters:
Parameter | C++ type | Description |
---|---|---|
cleartext_start | unsigned | |
cleartext_bitwidth | unsigned |
PolynomialEvaluationEncodingAttr
An attribute describing encoded RLWE plaintexts via evaluations at fixed points.
Syntax:
#lwe.polynomial_evaluation_encoding<
unsigned, # cleartext_start
unsigned # cleartext_bitwidth
>
A “evaluation encoding” of a list of integers $(v_1, \dots, v_n)$ asserts
that $f(x_1 ) = v_1, \dots, f(x_n) = v_n$ for some implicit, but fixed and
distinct choice of inputs $x_i$. The encoded values are also scaled by a
scale factor, having the same semantics as bit_field_encoding
, but
applied entry-wise (to either the coefficient or evaluation representation).
This attribute can be used in multiple ways:
- On a
poly.poly
, it asserts that the polynomial has been transformed from an evaluation tensor. - On a tensor of
poly.poly
, it asserts that the tensor is an RLWE ciphertext for some RLWE scheme that supports the evaluation encoding.
A typical workflow for the BFV/BGV schemes using this encoding would be to apply a INTT operation to the input list of cleartexts to convert from evaluation form to coefficient form, then encrypt the resulting polynomial in coefficient form, then apply NTT back to the evaluation form for faster multiplication of ciphertexts.
The points chosen are fixed to be the powers of a primitive root of unity of the coefficient ring of the plaintext space, which allows one to use NTT/INTT to tansform quickly between the coefficient and evaluation forms.
Example:
#generator = #poly.polynomial<1 + x**1024>
// note that the cmod should be chosen so as to ensure a primitive root of
// unity exists in the multiplicative group (Z / cmod Z)^*
#ring = #poly.ring<cmod=65536, ideal=#generator>
#lwe_encoding = #lwe.polynomial_evaluation_encoding<cleartext_start=30, cleartext_bitwidth=3>
%evals = arith.constant <[1, 2, 4, 5]> : tensor<4xi16>
%poly1 = poly.intt %evals : tensor<4xi16, #ring> -> !poly.poly<#ring, #eval_encoding>
%poly2 = poly.intt %evals : tensor<4xi16, #ring> -> !poly.poly<#ring, #eval_encoding>
%rlwe_ciphertext = tensor.from_elements %poly1, %poly2 : tensor<2x!poly.poly<#ring, #eval_encoding>>
See bit_field_encoding
for the definition of the cleartext_start
and
cleartext_bitwidth
fields.
Parameters:
Parameter | C++ type | Description |
---|---|---|
cleartext_start | unsigned | |
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::polynomial::RingAttr # ring
>
Parameters:
Parameter | C++ type | Description |
---|---|---|
key | KeyAttr | |
ring | ::mlir::polynomial::RingAttr |
NewLWESecretKeyType
A secret key for LWE
Syntax:
!lwe.new_lwe_secret_key<
KeyAttr, # key
::mlir::polynomial::RingAttr # ring
>
Parameters:
Parameter | C++ type | Description |
---|---|---|
key | KeyAttr | |
ring | ::mlir::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::polynomial::RingAttr, # ring
Type # underlying_type
>
Parameters:
Parameter | C++ type | Description |
---|---|---|
encoding | ::mlir::Attribute | |
ring | ::mlir::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` $plaintext attr-dict `:` qualified(type($plaintext)) `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 |
---|---|
plaintext | signless-integer-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 type for RLWE ciphertexts |
rhs | A type for RLWE ciphertexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 attribute describing encoded RLWE plaintexts via coefficients. or An attribute describing encoded RLWE plaintexts via evaluations at fixed points. or An attribute describing encoded RLWE plaintexts via the rounded inverse canonical embedding. |
ring | ::mlir::polynomial::RingAttr | an attribute specifying a polynomial ring |
Operands:
Operand | Description |
---|---|
input | A type for RLWE plaintexts |
Results:
Result | Description |
---|---|
output | signless-integer-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 type for RLWE ciphertexts |
secret_key | A secret key for RLWE |
Results:
Result | Description |
---|---|
output | A type for RLWE plaintexts |
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 attribute describing encoded RLWE plaintexts via coefficients. or An attribute describing encoded RLWE plaintexts via evaluations at fixed points. or An attribute describing encoded RLWE plaintexts via the rounded inverse canonical embedding. |
ring | ::mlir::polynomial::RingAttr | an attribute specifying a polynomial ring |
Operands:
Operand | Description |
---|---|
input | signless-integer-like or floating-point-like |
Results:
Result | Description |
---|---|
output | A type for RLWE plaintexts |
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 type for RLWE plaintexts |
key | A secret key for RLWE or A public key for RLWE |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE ciphertexts |
rhs | A type for RLWE ciphertexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 | ciphertext-like |
Results:
Result | Description |
---|---|
output | 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 type for RLWE ciphertexts |
rhs | A type for RLWE ciphertexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE ciphertexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
7.8 - ModArith
ModArith ops
mod_arith.add
(heir::mod_arith::AddOp)
Modular addition operation
Syntax:
operation ::= `mod_arith.add` operands attr-dict `:` type($output)
Computes addition modulo a statically known modulus $q$.
Traits: AlwaysSpeculatableImplTrait
, Commutative
, Elementwise
, SameOperandsAndResultType
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
modulus | ::mlir::IntegerAttr | arbitrary integer attribute |
Operands:
Operand | Description |
---|---|
lhs | signless-integer-like |
rhs | signless-integer-like |
Results:
Result | Description |
---|---|
output | signless-integer-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) + p$.
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.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 {modulus = q}
computes $(x * y) + z \mod q$
Traits: AlwaysSpeculatableImplTrait
, Elementwise
, SameOperandsAndResultType
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
modulus | ::mlir::IntegerAttr | arbitrary integer attribute |
Operands:
Operand | Description |
---|---|
lhs | signless-integer-like |
rhs | signless-integer-like |
acc | signless-integer-like |
Results:
Result | Description |
---|---|
output | signless-integer-like |
mod_arith.mul
(heir::mod_arith::MulOp)
Modular multiplication operation
Syntax:
operation ::= `mod_arith.mul` operands attr-dict `:` type($output)
Computes multiplication modulo a statically known modulus $q$.
Traits: AlwaysSpeculatableImplTrait
, Commutative
, Elementwise
, SameOperandsAndResultType
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
modulus | ::mlir::IntegerAttr | arbitrary integer attribute |
Operands:
Operand | Description |
---|---|
lhs | signless-integer-like |
rhs | signless-integer-like |
Results:
Result | Description |
---|---|
output | signless-integer-like |
mod_arith.reduce
(heir::mod_arith::ReduceOp)
Reduce a signed integer to its congruence modulo equivalent
Syntax:
operation ::= `mod_arith.reduce` operands attr-dict `:` type($output)
mod_arith.reduce x {modulus = q}
computes $y \in [0, q)$ such that
$x \equiv y \mod n$.
Note this will interpret x
as a signed integer. It is required the bitwidth of q
is smaller
than that of x
. For an unsigned integer, equivalent functionality is: y = arith.remui x
.
Traits: AlwaysSpeculatableImplTrait
, Elementwise
, SameOperandsAndResultType
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
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.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 subtraction modulo a statically known modulus $q$.
Traits: AlwaysSpeculatableImplTrait
, Elementwise
, SameOperandsAndResultType
, Scalarizable
, Tensorizable
, Vectorizable
Interfaces: ConditionallySpeculatable
, InferTypeOpInterface
, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
Attributes:
Attribute | MLIR Type | Description |
---|---|---|
modulus | ::mlir::IntegerAttr | arbitrary integer attribute |
Operands:
Operand | Description |
---|---|
lhs | signless-integer-like |
rhs | signless-integer-like |
Results:
Result | Description |
---|---|
output | signless-integer-like |
7.9 - Openfhe
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 type for RLWE ciphertexts |
rhs | A type for RLWE ciphertexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE ciphertexts |
plaintext | A type for RLWE plaintexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE ciphertexts |
evalKey | The evaluation key required to keyswitch/relinearize/rotate/automorphism operation in OpenFHE. |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE ciphertexts |
privateKey | The private key required to decrypt a ciphertext in OpenFHE. |
Results:
Result | Description |
---|---|
plaintext | A type for RLWE plaintexts |
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 type for RLWE plaintexts |
publicKey | The public key required to encrypt plaintext in OpenFHE. |
Results:
Result | Description |
---|---|
ciphertext | A type for RLWE ciphertexts |
openfhe.gen_context
(heir::openfhe::GenContextOp)
Syntax:
operation ::= `openfhe.gen_context` operands attr-dict `:` functional-type(operands, results)
Interfaces: InferTypeOpInterface
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 |
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 type for RLWE ciphertexts |
evalKey | The evaluation key required to keyswitch/relinearize/rotate/automorphism operation in OpenFHE. |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE ciphertexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE plaintexts |
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 type for RLWE plaintexts |
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 type for RLWE ciphertexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE ciphertexts |
constant | 64-bit signless integer |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE ciphertexts |
rhs | A type for RLWE ciphertexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE ciphertexts |
rhs | A type for RLWE ciphertexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE ciphertexts |
plaintext | A type for RLWE plaintexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE ciphertexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE ciphertexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE ciphertexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE ciphertexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
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 type for RLWE ciphertexts |
rhs | A type for RLWE ciphertexts |
Results:
Result | Description |
---|---|
output | A type for RLWE ciphertexts |
7.10 - Polynomial
Polynomial additional definitions
7.11 - 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.12 - 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.
isCompatibleWith
must be both commutative and associative, in the sense
that type1.isCompatibleWith(type2)
if and only if
type2.isCompatibleWith(type1)
, and further
type2.isCompatibleWith(type3)
if and only if
type1.isCompatibleWith(type3)
.
NOTE: This method must be implemented by the user.
7.13 - Secret
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.14 - 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.15 - TfheRust
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.16 - TfheRustBool
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;
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
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 | The boolean server key required to perform homomorphic operations. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
lhs | eb-like |
rhs | eb-like |
Results:
Result | Description |
---|---|
output | eb-like |
TfheRustBool additional definitions
7.17 - TOSA
TOSA additional definitions
8 - Passes
This section contains the passes defined by HEIR.
8.1 - ApplyFoldersPasses
-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.
8.2 - BGVToLWE
-bgv-to-lwe
Lower bgv
to lwe
dialect.
This pass lowers the bgv
dialect to lwe
dialect.
8.3 - BGVToOpenfhe
-bgv-to-openfhe
Lower bgv
to openfhe
dialect.
This pass lowers the bgv
dialect to Openfhe
dialect.
8.4 - CGGIPasses
-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);
-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
.
8.5 - CGGIToJaxite
-cggi-to-jaxite
Lower cggi
to jaxite
dialect.
8.6 - CGGIToTfheRust
-cggi-to-tfhe-rust
Lower cggi
to tfhe_rust
dialect.
8.7 - CGGIToTfheRustBool
-cggi-to-tfhe-rust-bool
Lower cggi
to tfhe_rust_bool
dialect.
8.8 - CKKSToOpenfhe
-ckks-to-openfhe
Lower ckks
to openfhe
dialect.
This pass lowers the ckks
dialect to Openfhe
dialect.
8.9 - CombToCGGI
-comb-to-cggi
Lower comb
to cggi
dialect.
This pass lowers the comb
dialect to cggi
dialect.
8.10 - ConvertIfToSelectPasses
-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.
8.11 - ConvertSecretExtractToStaticExtractPasses
-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
} … }
```
8.12 - ConvertSecretForToStaticForPasses
-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>
8.13 - ConvertSecretInsertToStaticInsertPasses
-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>
}
...
}
8.14 - ConvertSecretWhileToStaticForPasses
-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>
}
8.15 - ElementwiseToAffinePasses
-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
8.16 - ForwardInsertToExtractPasses
-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.
8.17 - ForwardStoreToLoadPasses
-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.
8.18 - FullLoopUnrollPasses
-full-loop-unroll
Fully unroll all loops
Scan the IR for affine.for loops and unroll them all.
8.19 - LinalgCanonicalizationsPasses
-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.
8.20 - LinalgToTensorExt
-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.
8.21 - LWEPasses
-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
.
8.22 - LWEToPolynomial
-lwe-to-polynomial
Lower lwe
to polynomial
dialect.
This pass lowers the lwe
dialect to polynomial
dialect.
8.23 - OpenfhePasses
-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.
8.24 - OperationBalancerPasses
-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.
8.25 - PolynomialPasses
-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.
8.26 - PolynomialToStandard
-polynomial-to-standard
Lower polynomial
to standard MLIR dialects.
This pass lowers the polynomial
dialect to standard MLIR, a mixture of
affine, tensor, and arith.
8.27 - SecretizePasses
-secretize
Adds secret argument attributes to entry function
Adds a secret.secret attribute argument to each argument in the entry
function of an MLIR module. By default, the function is main
. This may be
overridden with the option -entry-function=top_level_func.
Options
-entry-function : entry function of the module
-wrap-generic
Wraps regions using secret args in secret.generic bodies
This pass wraps function regions of func.func
that use secret arguments in
secret.generic
bodies.
Secret arguments are annotated using a secret.secret
argument attribute.
This pass converts these to secret types and then inserts a secret.generic
body to hold the functions region. The output type is also converted to a
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>
}
8.28 - SecretPasses
-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-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.
8.29 - SecretToBGV
-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.
8.30 - SecretToCKKS
-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.
8.31 - StraightLineVectorizerPasses
-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.
8.32 - TensorExtPasses
-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.
-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).
-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
.
-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>
8.33 - TensorToScalarsPasses
-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
8.34 - TosaToSecretArith
-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..
8.35 - UnusedMemRefPasses
-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.
8.36 - YosysOptimizerPasses
-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.
Statistics
total circuit size : The total circuit size for all optimized circuits, after optimization is done.