Skip to content

Commit

Permalink
The QIR codegen is one of the oldest pieces of the project and really
Browse files Browse the repository at this point in the history
shows its age at this point. It was written before much of the rest
of the core was implemented or even designed.

These changes replace the existing QIR codegen for the C++ compiler.
Unfortunately, there are a number of semantics violations from Python
that need to be fixed before it can be switched over. Therefore, this
PR leaves Python intact and using the old codegen passes.

The purpose of these changes is two-fold.

 1. Instead of converting all of the IR to the LLVM-IR dialect at
    the same time as converting to QIR, these two steps are bifurcated.
    This allows us to convert the quake dialect to other higher level
    operations first and before LLVM-IR dialect is introduced. This
    will be beneficial in short order...
 2. Instead of piecemealing different flavors of QIR in completely ad
    hoc spaghetti plate passes, the flavor of QIR is specified as a
    mixin modifier class for a singular set of steps to convert to any
    flavor of QIR. This does mean that one will no longer be able to
    convert to the LLVM-IR dialect with QIR calls and then change their
    mind from chocolate QIR to strawberry QIR much later.

Notes:
 - Remove the option to disable the qir profile preparation pass.
   This pass is not optional. The IR will be translated to an
   invalid state if the required function declarations are not
   created at all.
 - Make it clear that AggressiveEarlyInlining is a pipeline. Refactor
   the registration functions so that we're not block-copying things
   between the core and python (which was dropping things on the floor
   already).
 - Add a new pass, convert to QIR API.
   This pass will replace the cascade of passes to convert to full QIR and
   then convert some more to base profile or adaptive profile.
 - Refactor QIRFunctionNames.h.
 - Add a raft of declarations to the intrinsics table. This will
   dramatically reduce the amount of code in codegen and make
   maintenance much easier.
 - Add the analysis and prep pass.
 - Improve pipeline locality and performance.
 - Use the new code in the default code gen path for C++.
 - Workarounds for issue NVIDIA#2541 and issue NVIDIA#2539. Keep the old codegen for
   python. Too many bugs.
 - Update tests. Fix bugs in mock servers. Have python kernel builder add the
   cudaq-kernel attribute. (See issue 2541.)

Signed-off-by: Eric Schweitz <[email protected]>
  • Loading branch information
schweitzpgi committed Feb 3, 2025
1 parent 3b0f04c commit ca736ae
Show file tree
Hide file tree
Showing 118 changed files with 3,705 additions and 1,126 deletions.
2 changes: 2 additions & 0 deletions include/cudaq/Optimizer/Builder/Factory.h
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ std::optional<double> maybeValueOfFloatConstant(mlir::Value v);
/// \em{not} control dependent (other than on function entry).
mlir::Value createLLVMTemporary(mlir::Location loc, mlir::OpBuilder &builder,
mlir::Type type, std::size_t size = 1);
mlir::Value createTemporary(mlir::Location loc, mlir::OpBuilder &builder,
mlir::Type type, std::size_t size = 1);

//===----------------------------------------------------------------------===//

Expand Down
5 changes: 5 additions & 0 deletions include/cudaq/Optimizer/Builder/Intrinsics.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,11 @@ class IRBuilder : public mlir::OpBuilder {
mlir::LogicalResult loadIntrinsic(mlir::ModuleOp module,
llvm::StringRef name);

llvm::StringRef getIntrinsicText(llvm::StringRef name);
mlir::LogicalResult loadIntrinsicWithAliases(mlir::ModuleOp module,
llvm::StringRef name,
llvm::StringRef prefix);

std::string hashStringByContent(llvm::StringRef sref);

/// Generates code that yields the size of any type that can be reified in
Expand Down
18 changes: 18 additions & 0 deletions include/cudaq/Optimizer/CodeGen/CodeGenOps.td
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,22 @@ def cgq_RAIIOp : CGQOp<"qmem_raii", [MemoryEffects<[MemAlloc, MemWrite]>]> {
}];
}

def cgq_MaterializeConstantArrayOp :
CGQOp<"materialize_constant_array", [Pure]> {
let summary = "Macro to materialize a cc.const_array into memory.";
let description = [{
This operation is the equivalent of creating some space in memory (such as
on the stack) and storing a cc.const_array value to that memory address.
This operation is needed for local rewrites, but is likely to be eliminated
if the constant array can be completely folded away.
}];

let arguments = (ins cc_ArrayType:$constArray);
let results = (outs cc_PointerType);

let assemblyFormat = [{
$constArray `:` functional-type(operands, results) attr-dict
}];
}

#endif
23 changes: 17 additions & 6 deletions include/cudaq/Optimizer/CodeGen/Passes.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,17 @@ namespace cudaq::opt {
/// @param pm Pass Manager to add QIR passes to
/// @param convertTo Expected to be `qir-base` or `qir-adaptive` (comes from the
/// cudaq-translate command line `--convert-to` parameter)
/// @param performPrep Whether or not to perform the initial prep pass (normally
/// true, but false for the WireSet QIR path)
void addQIRProfilePipeline(mlir::OpPassManager &pm, llvm::StringRef convertTo,
bool performPrep = true);
/// \deprecated Replaced by the convert to QIR API pipeline.
void addQIRProfilePipeline(mlir::OpPassManager &pm, llvm::StringRef convertTo);

void addQIRProfileVerify(mlir::OpPassManager &pm, llvm::StringRef convertTo);

void addLowerToCCPipeline(mlir::OpPassManager &pm);
void addWiresetToProfileQIRPipeline(mlir::OpPassManager &pm,
llvm::StringRef profile);

/// @brief Verify that all `CallOp` targets are QIR- or NVQIR-defined functions
/// or in the provided allowed list.
/// Verify that all `CallOp` targets are QIR- or NVQIR-defined functions or in
/// the provided allowed list.
std::unique_ptr<mlir::Pass>
createVerifyNVQIRCallOpsPass(const std::vector<llvm::StringRef> &allowedFuncs);

Expand All @@ -61,7 +61,18 @@ void registerCodeGenDialect(mlir::DialectRegistry &registry);

mlir::LLVM::LLVMStructType lambdaAsPairOfPointers(mlir::MLIRContext *context);

/// The pipeline for lowering Quake code to the QIR API. There will be three
/// distinct flavors of QIR that can be generated with this pipeline. These
/// are `"qir"`, `"qir-base"`, and `"qir-adaptive"`. This pipeline should be run
/// before conversion to the LLVM-IR dialect.
void registerToQIRAPIPipeline();
void addConvertToQIRAPIPipeline(mlir::OpPassManager &pm, mlir::StringRef api,
bool opaquePtr = false);

/// The pipeline for lowering Quake code to the execution manager API. This
/// pipeline should be run before conversion to the LLVM-IR dialect.
void registerToExecutionManagerCCPipeline();

void registerWireSetToProfileQIRPipeline();
void populateCCTypeConversions(mlir::LLVMTypeConverter *converter);

Expand Down
62 changes: 61 additions & 1 deletion include/cudaq/Optimizer/CodeGen/Passes.td
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ def ConvertToQIR : Pass<"quake-to-qir", "mlir::ModuleOp"> {
def LowerToCG : Pass<"lower-to-cg", "mlir::ModuleOp"> {
let summary = "Lower Quake to CG dialect.";
let description = [{
For testing purposes only.
Lower the Quake IR to the codegen dialect. The codegen dialect is used to
fuse small DAGs of IR into larger macro operations just prior to the final
codegen. This allows conversions to take place on the macro operations and
avoid some of the limitations of an MLIR conversion pass.
}];
let dependentDialects = [ "cudaq::codegen::CodeGenDialect" ];
}
Expand Down Expand Up @@ -212,4 +215,61 @@ def WireSetToProfileQIRPrep :
let dependentDialects = ["cudaq::cc::CCDialect", "mlir::func::FuncDialect"];
}

def QuakeToQIRAPI : Pass<"quake-to-qir-api"> {
let summary = "Convert the Quake dialect to the QIR API.";
let description = [{
This pass converts Quake operations to the QIR API as expressed in terms
of function calls to QIR functions.

Which QIR functions are to be used is parameterized on the `api` option.

This pass can lower to either use the obsolete opaque structure types (per
the QIR spec) or to use LLVM's currently supported opaque pointers. In the
latter case, type information is fully understood from the function names
themselves.
}];

let dependentDialects = ["cudaq::cc::CCDialect", "mlir::arith::ArithDialect",
"mlir::cf::ControlFlowDialect", "mlir::func::FuncDialect",
"mlir::LLVM::LLVMDialect"];

let options = [
Option<"api", "api", "std::string", /*default=*/"\"full\"",
"Select the QIR API to use.">,
Option<"opaquePtr", "opaque-pointer", "bool", /*default=*/"false",
"Use opaque pointers.">
];
}

def QuakeToQIRAPIFinal : Pass<"quake-to-qir-api-final", "mlir::ModuleOp"> {
let summary = "Convert the Quake dialect to the QIR API finalization.";
let description = [{
}];

let dependentDialects = ["cudaq::cc::CCDialect", "mlir::arith::ArithDialect",
"mlir::cf::ControlFlowDialect", "mlir::func::FuncDialect",
"mlir::LLVM::LLVMDialect"];

let options = [
Option<"api", "api", "std::string", /*default=*/"\"full\"",
"Select the QIR API to use.">
];
}

def QuakeToQIRAPIPrep : Pass<"quake-to-qir-api-prep", "mlir::ModuleOp"> {
let summary = "Convert the Quake dialect to the QIR API preparation.";
let description = [{
}];
let dependentDialects = ["cudaq::cc::CCDialect", "mlir::arith::ArithDialect",
"mlir::cf::ControlFlowDialect", "mlir::func::FuncDialect",
"mlir::LLVM::LLVMDialect", "cudaq::codegen::CodeGenDialect"];
let options = [
Option<"api", "api", "std::string", /*default=*/"\"full\"",
"Select the QIR API to use.">,
Option<"opaquePtr", "opaque-pointer", "bool", /*default=*/"false",
"Use opaque pointers.">
];
}


#endif // CUDAQ_OPT_OPTIMIZER_CODEGEN_PASSES
1 change: 1 addition & 0 deletions include/cudaq/Optimizer/CodeGen/Peephole.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#pragma once

#include "cudaq/Optimizer/CodeGen/QIRFunctionNames.h"
#include "cudaq/Optimizer/CodeGen/QIROpaqueStructTypes.h"
#include "cudaq/Optimizer/Dialect/Quake/QuakeOps.h"
#include "llvm/ADT/StringRef.h"
#include "mlir/IR/ValueRange.h"
Expand Down
32 changes: 24 additions & 8 deletions include/cudaq/Optimizer/CodeGen/Pipelines.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,41 @@

namespace cudaq::opt {

/// The common pipeline.
/// Adds the common pipeline (with or without a profile specifier) but without
/// the final QIR profile lowering passes.
void commonPipelineConvertToQIR(
/// Adds the common pipeline. \p codeGenFor specifies which variant of QIR is to
/// be generated: full, base-profile, adaptive-profile, etc. \p passConfigAs
/// specifies which variant of QIR to use with \e other passes, and not the
/// final `codegen`, in the pipeline. Typically, \p codeGenFor and \p
/// passConfigAs will have identical values.
void commonPipelineConvertToQIR(mlir::PassManager &pm,
mlir::StringRef codeGenFor = "qir",
mlir::StringRef passConfigAs = "qir");

/// \deprecated{Only for Python, since it can't use the new QIR codegen.}
void commonPipelineConvertToQIR_PythonWorkaround(
mlir::PassManager &pm, const std::optional<mlir::StringRef> &convertTo);

/// \brief Pipeline builder to convert Quake to QIR.
/// Does not specify a particular QIR profile.
inline void addPipelineConvertToQIR(mlir::PassManager &pm) {
commonPipelineConvertToQIR(pm, std::nullopt);
commonPipelineConvertToQIR(pm);
}

/// \deprecated{Only for Python, since it can't use the new QIR codegen.}
inline void addPipelineConvertToQIR_PythonWorkaround(mlir::PassManager &pm) {
commonPipelineConvertToQIR_PythonWorkaround(pm, std::nullopt);
}

/// \brief Pipeline builder to convert Quake to QIR.
/// Specifies a particular QIR profile in \p convertTo.
/// \p pm Pass manager to append passes to
/// \p convertTo name of QIR profile (e.g., `qir-base`, `qir-adaptive`, ...)
inline void addPipelineConvertToQIR(mlir::PassManager &pm,
mlir::StringRef convertTo) {
commonPipelineConvertToQIR(pm, convertTo);
void addPipelineConvertToQIR(mlir::PassManager &pm, mlir::StringRef convertTo);

/// \deprecated{Only for Python, since it can't use the new QIR codegen.}
inline void
addPipelineConvertToQIR_PythonWorkaround(mlir::PassManager &pm,
mlir::StringRef convertTo) {
commonPipelineConvertToQIR_PythonWorkaround(pm, convertTo);
addQIRProfilePipeline(pm, convertTo);
}

Expand Down
2 changes: 2 additions & 0 deletions include/cudaq/Optimizer/CodeGen/QIRAttributeNames.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,7 @@ static constexpr const char QIRRequiredResultsAttrName[] = "requiredResults";
static constexpr const char QIRIrreversibleFlagName[] = "irreversible";

static constexpr const char StartingOffsetAttrName[] = "StartingOffset";
static constexpr const char ResultIndexAttrName[] = "ResultIndex";
static constexpr const char MzAssignedNameAttrName[] = "MzAssignedName";

} // namespace cudaq::opt
39 changes: 9 additions & 30 deletions include/cudaq/Optimizer/CodeGen/QIRFunctionNames.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@
/// This file provides some common QIR function names for use throughout our
/// MLIR lowering infrastructure.

#include "mlir/Conversion/LLVMCommon/TypeConverter.h"

namespace cudaq::opt {

/// QIS Function name strings
Expand All @@ -21,14 +19,19 @@ static constexpr const char QIRMeasureBody[] = "__quantum__qis__mz__body";
static constexpr const char QIRMeasure[] = "__quantum__qis__mz";
static constexpr const char QIRMeasureToRegister[] =
"__quantum__qis__mz__to__register";
static constexpr const char QIRResetBody[] = "__quantum__qis__reset__body";
static constexpr const char QIRReset[] = "__quantum__qis__reset";

static constexpr const char QIRCnot[] = "__quantum__qis__cnot";
static constexpr const char QIRCnot[] = "__quantum__qis__cnot__body";
static constexpr const char QIRCphase[] = "__quantum__qis__cphase";
static constexpr const char QIRCZ[] = "__quantum__qis__cz";
static constexpr const char QIRCZ[] = "__quantum__qis__cz__body";
static constexpr const char QIRReadResultBody[] =
"__quantum__qis__read_result__body";

static constexpr const char QIRCustomOp[] = "__quantum__qis__custom_unitary";
static constexpr const char QIRCustomAdjOp[] =
"__quantum__qis__custom_unitary__adj";
static constexpr const char QIRExpPauli[] = "__quantum__qis__exp_pauli";

static constexpr const char NVQIRInvokeWithControlBits[] =
"invokeWithControlQubits";
Expand All @@ -38,6 +41,8 @@ static constexpr const char NVQIRInvokeU3RotationWithControlBits[] =
"invokeU3RotationWithControlQubits";
static constexpr const char NVQIRInvokeWithControlRegisterOrBits[] =
"invokeWithControlRegisterOrQubits";
static constexpr const char NVQIRGeneralizedInvokeAny[] =
"generalizedInvokeWithRotationsControlsTargets";
static constexpr const char NVQIRPackSingleQubitInArray[] =
"packSingleQubitInArray";
static constexpr const char NVQIRReleasePackedQubitArray[] =
Expand Down Expand Up @@ -89,30 +94,4 @@ static constexpr const char QIRRecordOutput[] =
static constexpr const char QIRClearResultMaps[] =
"__quantum__rt__clear_result_maps";

inline mlir::Type getQuantumTypeByName(mlir::StringRef type,
mlir::MLIRContext *context) {
return mlir::LLVM::LLVMStructType::getOpaque(type, context);
}

inline mlir::Type getQubitType(mlir::MLIRContext *context) {
return mlir::LLVM::LLVMPointerType::get(
getQuantumTypeByName("Qubit", context));
}

inline mlir::Type getArrayType(mlir::MLIRContext *context) {
return mlir::LLVM::LLVMPointerType::get(
getQuantumTypeByName("Array", context));
}

inline mlir::Type getResultType(mlir::MLIRContext *context) {
return mlir::LLVM::LLVMPointerType::get(
getQuantumTypeByName("Result", context));
}

inline mlir::Type getCharPointerType(mlir::MLIRContext *context) {
return mlir::LLVM::LLVMPointerType::get(mlir::IntegerType::get(context, 8));
}

void initializeTypeConversions(mlir::LLVMTypeConverter &typeConverter);

} // namespace cudaq::opt
94 changes: 94 additions & 0 deletions include/cudaq/Optimizer/CodeGen/QIROpaqueStructTypes.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/****************************************************************-*- C++ -*-****
* Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. *
* All rights reserved. *
* *
* This source code and the accompanying materials are made available under *
* the terms of the Apache License 2.0 which accompanies this distribution. *
******************************************************************************/

#pragma once

/// This file provides the opaque struct types to be used with the obsolete LLVM
/// typed pointer type.

#include "mlir/Conversion/LLVMCommon/TypeConverter.h"
#include "mlir/Dialect/LLVMIR/LLVMTypes.h"

namespace cudaq {
inline mlir::Type getQuantumTypeByName(mlir::StringRef type,
mlir::MLIRContext *context) {
return mlir::LLVM::LLVMStructType::getOpaque(type, context);
}

namespace opt {

// The following type creators are deprecated and should only be used in the
// older codegen passes. Use the creators in the cg namespace immediately below
// instead.
inline mlir::Type getOpaquePointerType(mlir::MLIRContext *context) {
return mlir::LLVM::LLVMPointerType::get(context);
}

inline mlir::Type getQubitType(mlir::MLIRContext *context) {
return mlir::LLVM::LLVMPointerType::get(
getQuantumTypeByName("Qubit", context));
}

inline mlir::Type getArrayType(mlir::MLIRContext *context) {
return mlir::LLVM::LLVMPointerType::get(
getQuantumTypeByName("Array", context));
}

inline mlir::Type getResultType(mlir::MLIRContext *context) {
return mlir::LLVM::LLVMPointerType::get(
getQuantumTypeByName("Result", context));
}

inline mlir::Type getCharPointerType(mlir::MLIRContext *context) {
return mlir::LLVM::LLVMPointerType::get(mlir::IntegerType::get(context, 8));
}

void initializeTypeConversions(mlir::LLVMTypeConverter &typeConverter);

} // namespace opt

namespace cg {

// The following type creators replace the ones above. They are configurable on
// the fly to either use opaque structs or opaque pointers. The default is to
// use pointers to opaque structs, which is no longer supported in modern LLVM.

inline mlir::Type getOpaquePointerType(mlir::MLIRContext *context) {
return cc::PointerType::get(mlir::NoneType::get(context));
}

inline mlir::Type getQubitType(mlir::MLIRContext *context,
bool useOpaquePtr = false) {
if (useOpaquePtr)
return getOpaquePointerType(context);
return cc::PointerType::get(getQuantumTypeByName("Qubit", context));
}

inline mlir::Type getArrayType(mlir::MLIRContext *context,
bool useOpaquePtr = false) {
if (useOpaquePtr)
return getOpaquePointerType(context);
return cc::PointerType::get(getQuantumTypeByName("Array", context));
}

inline mlir::Type getResultType(mlir::MLIRContext *context,
bool useOpaquePtr = false) {
if (useOpaquePtr)
return getOpaquePointerType(context);
return cc::PointerType::get(getQuantumTypeByName("Result", context));
}

inline mlir::Type getCharPointerType(mlir::MLIRContext *context,
bool useOpaquePtr = false) {
if (useOpaquePtr)
return getOpaquePointerType(context);
return cc::PointerType::get(mlir::IntegerType::get(context, 8));
}

} // namespace cg
} // namespace cudaq
File renamed without changes.
Loading

0 comments on commit ca736ae

Please sign in to comment.