Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions lib/Conversions/CheddarToEmitC/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
load("@heir//lib/Transforms:transforms.bzl", "add_heir_transforms")
load("@rules_cc//cc:cc_library.bzl", "cc_library")

package(
default_applicable_licenses = ["@heir//:license"],
default_visibility = ["//visibility:public"],
)

cc_library(
name = "CheddarToEmitC",
srcs = ["CheddarToEmitC.cpp"],
hdrs = ["CheddarToEmitC.h"],
deps = [
":pass_inc_gen",
"@heir//lib/Dialect/Cheddar/IR:Dialect",
"@heir//lib/Utils:ConversionUtils",
"@llvm-project//llvm:Support",
"@llvm-project//mlir:EmitCDialect",
"@llvm-project//mlir:FuncDialect",
"@llvm-project//mlir:IR",
"@llvm-project//mlir:MemRefDialect",
"@llvm-project//mlir:Pass",
"@llvm-project//mlir:Support",
"@llvm-project//mlir:TransformUtils",
],
)

add_heir_transforms(
header_filename = "CheddarToEmitC.h.inc",
pass_name = "CheddarToEmitC",
td_file = "CheddarToEmitC.td",
)
996 changes: 996 additions & 0 deletions lib/Conversions/CheddarToEmitC/CheddarToEmitC.cpp

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions lib/Conversions/CheddarToEmitC/CheddarToEmitC.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#ifndef LIB_CONVERSIONS_CHEDDARTOEMITC_CHEDDARTOEMITC_H_
#define LIB_CONVERSIONS_CHEDDARTOEMITC_CHEDDARTOEMITC_H_

#include "mlir/include/mlir/IR/DialectRegistry.h" // from @llvm-project
#include "mlir/include/mlir/Pass/Pass.h" // from @llvm-project

namespace mlir::heir {

// Attaches MemRefElementTypeInterface as an external (marker-only) model to
// emitc::OpaqueType. Needed so that the cheddar-to-emitc type converter can
// form `memref<Nx!emitc.opaque<...>>` as the converted form of
// `memref<Nx!cheddar.*>` after bufferization. Call once at tool startup.
void registerCheddarToEmitCExternalModels(DialectRegistry& registry);

#define GEN_PASS_DECL
#include "lib/Conversions/CheddarToEmitC/CheddarToEmitC.h.inc"

#define GEN_PASS_REGISTRATION
#include "lib/Conversions/CheddarToEmitC/CheddarToEmitC.h.inc"

} // namespace mlir::heir

#endif // LIB_CONVERSIONS_CHEDDARTOEMITC_CHEDDARTOEMITC_H_
22 changes: 22 additions & 0 deletions lib/Conversions/CheddarToEmitC/CheddarToEmitC.td
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#ifndef LIB_CONVERSIONS_CHEDDARTOEMITC_CHEDDARTOEMITC_TD_
#define LIB_CONVERSIONS_CHEDDARTOEMITC_CHEDDARTOEMITC_TD_

include "mlir/Pass/PassBase.td"

def CheddarToEmitC : Pass<"cheddar-to-emitc"> {
let summary = "Lower the cheddar dialect to EmitC.";

let description = [{
Translates each cheddar op into an out-parameter-style `Context`/
`UserInterface` method call expressed in the EmitC dialect.
`mlir-translate --mlir-to-cpp` renders the resulting IR as host-side
C++ against the CHEDDAR library API.
}];

let dependentDialects = [
"::mlir::emitc::EmitCDialect",
"::mlir::func::FuncDialect",
];
}

#endif // LIB_CONVERSIONS_CHEDDARTOEMITC_CHEDDARTOEMITC_TD_
8 changes: 7 additions & 1 deletion lib/Dialect/Cheddar/IR/CheddarTypes.td
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@
include "CheddarDialect.td"

include "lib/Dialect/HEIRInterfaces.td"
include "mlir/IR/BuiltinTypeInterfaces.td"
include "mlir/IR/DialectBase.td"
include "mlir/IR/AttrTypeBase.td"
include "mlir/IR/OpAsmInterface.td"

// A base class for all types in this dialect
//
// All Cheddar types implement MemRefElementTypeInterface so they can appear as
// element types in `tensor<Nx!cheddar.*>` / `memref<Nx!cheddar.*>`, which is
// what the bufferization pipeline produces when lowering looped CKKS kernels.
class Cheddar_Type<string name, string typeMnemonic, list<Trait> traits = []>
: TypeDef<Cheddar_Dialect, name, traits # [OpAsmTypeInterface]> {
: TypeDef<Cheddar_Dialect, name,
traits # [OpAsmTypeInterface, MemRefElementTypeInterface]> {
let mnemonic = typeMnemonic;
let genMnemonicAlias = 1;

Expand Down
10 changes: 10 additions & 0 deletions tests/Conversions/CheddarToEmitC/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
load("//bazel:lit.bzl", "glob_lit_tests")

package(default_applicable_licenses = ["@heir//:license"])

glob_lit_tests(
name = "all_tests",
data = ["@heir//tests:test_utilities"],
driver = "@heir//tests:run_lit.sh",
test_file_exts = ["mlir"],
)
55 changes: 55 additions & 0 deletions tests/Conversions/CheddarToEmitC/boundary_tightening.mlir
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// RUN: heir-opt --cheddar-to-emitc --split-input-file %s | FileCheck %s

// The mad_unsafe accumulator comes from a function argument; it is mutated in
// place by MadUnsafe and returned, so it must be lifted to a mutable
// `Ciphertext<word>&` (the `<"Ciphertext` prefix anchors the match away from
// the `const Ciphertext` inputs) and the result dropped -- no out-param, no
// `std::move` at the return.
// CHECK: func.func @mad_arg
// CHECK-SAME: !emitc.opaque<"Ciphertext<word>&">
// CHECK-SAME: !emitc.opaque<"const Ciphertext<word>&">
// CHECK-SAME: !emitc.opaque<"const Constant<word>&">
// CHECK: emitc.verbatim "{}->MadUnsafe({}, {}, {});"
// CHECK-NOT: std::move
func.func @mad_arg(%ctx: !cheddar.context, %acc: !cheddar.ciphertext,
%in: !cheddar.ciphertext, %c: !cheddar.constant)
-> !cheddar.ciphertext {
%r = cheddar.mad_unsafe %ctx, %acc, %in, %c
: (!cheddar.context, !cheddar.ciphertext, !cheddar.ciphertext, !cheddar.constant)
-> !cheddar.ciphertext
return %r : !cheddar.ciphertext
}

// -----

// Returning a move-only argument unchanged lifts it to an in-place
// `Ciphertext<word>&` out-param with no result and no copy.
// CHECK: func.func @identity
// CHECK-SAME: !emitc.opaque<"Ciphertext<word>&">
// CHECK-NOT: std::move
func.func @identity(%ct: !cheddar.ciphertext) -> !cheddar.ciphertext {
return %ct : !cheddar.ciphertext
}

// -----

// An EvkMap argument is move-only, so it tightens to `const EvkMap<word>&`
// rather than staying a by-value parameter.
// CHECK: func.func @boot
// CHECK-SAME: !emitc.opaque<"const EvkMap<word>&">
func.func @boot(%ctx: !cheddar.context, %ct: !cheddar.ciphertext,
%evk: !cheddar.evk_map) -> !cheddar.ciphertext {
%0 = cheddar.boot %ctx, %ct, %evk
: (!cheddar.context, !cheddar.ciphertext, !cheddar.evk_map) -> !cheddar.ciphertext
return %0 : !cheddar.ciphertext
}

// -----

// An Encoder argument (a non-assignable view) tightens to `const Encoder<word>&`.
// CHECK: func.func @encoder_arg
// CHECK-SAME: !emitc.opaque<"const Encoder<word>&">
func.func @encoder_arg(%enc: !cheddar.encoder, %ct: !cheddar.ciphertext)
-> !cheddar.ciphertext {
return %ct : !cheddar.ciphertext
}
Loading
Loading