-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[CIR] Initial implementation of CIR-to-LLVM IR lowering pass #125260
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4801886
6a2570c
ce2c8b0
410665a
d0c2866
0d20709
71729bd
4558b4a
bb74dff
660788f
bd73580
db4798b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
//===---- MissingFeatures.h - Checks for unimplemented features -*- C++ -*-===// | ||
// | ||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
// See https://llvm.org/LICENSE.txt for license information. | ||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
// | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This file introduces some helper classes to guard against features that | ||
// CIR dialect supports that we do not have and also do not have great ways to | ||
// assert against. | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
#ifndef CLANG_CIR_MISSINGFEATURES_H | ||
#define CLANG_CIR_MISSINGFEATURES_H | ||
|
||
namespace cir { | ||
|
||
// As a way to track features that haven't yet been implemented this class | ||
// explicitly contains a list of static fns that will return false that you | ||
// can guard against. If and when a feature becomes implemented simply changing | ||
// this return to true will cause compilation to fail at all the points in which | ||
// we noted that we needed to address. This is a much more explicit way to | ||
// handle "TODO"s. | ||
struct MissingFeatures { | ||
// Address space related | ||
static bool addressSpace() { return false; } | ||
|
||
// Unhandled global/linkage information. | ||
static bool opGlobalDSOLocal() { return false; } | ||
static bool opGlobalThreadLocal() { return false; } | ||
static bool opGlobalConstant() { return false; } | ||
static bool opGlobalAlignment() { return false; } | ||
static bool opGlobalLinkage() { return false; } | ||
}; | ||
|
||
} // namespace cir | ||
|
||
#endif // CLANG_CIR_MISSINGFEATURES_H |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,9 +10,22 @@ | |
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
#include "clang/CIR/LowerToLLVM.h" | ||
#include "LowerToLLVM.h" | ||
|
||
#include "mlir/Conversion/LLVMCommon/TypeConverter.h" | ||
#include "mlir/Dialect/DLTI/DLTI.h" | ||
#include "mlir/Dialect/Func/IR/FuncOps.h" | ||
#include "mlir/Dialect/LLVMIR/LLVMDialect.h" | ||
#include "mlir/IR/BuiltinDialect.h" | ||
#include "mlir/IR/BuiltinOps.h" | ||
#include "mlir/Pass/Pass.h" | ||
#include "mlir/Pass/PassManager.h" | ||
#include "mlir/Target/LLVMIR/Dialect/Builtin/BuiltinToLLVMIRTranslation.h" | ||
#include "mlir/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.h" | ||
#include "mlir/Target/LLVMIR/Export.h" | ||
#include "mlir/Transforms/DialectConversion.h" | ||
#include "clang/CIR/Dialect/IR/CIRDialect.h" | ||
#include "clang/CIR/MissingFeatures.h" | ||
#include "llvm/IR/Module.h" | ||
#include "llvm/Support/TimeProfiler.h" | ||
|
||
|
@@ -22,16 +35,165 @@ using namespace llvm; | |
namespace cir { | ||
namespace direct { | ||
|
||
// This pass requires the CIR to be in a "flat" state. All blocks in each | ||
// function must belong to the parent region. Once scopes and control flow | ||
// are implemented in CIR, a pass will be run before this one to flatten | ||
// the CIR and get it into the state that this pass requires. | ||
struct ConvertCIRToLLVMPass | ||
: public mlir::PassWrapper<ConvertCIRToLLVMPass, | ||
mlir::OperationPass<mlir::ModuleOp>> { | ||
void getDependentDialects(mlir::DialectRegistry ®istry) const override { | ||
registry.insert<mlir::BuiltinDialect, mlir::DLTIDialect, | ||
mlir::LLVM::LLVMDialect, mlir::func::FuncDialect>(); | ||
} | ||
void runOnOperation() final; | ||
|
||
StringRef getDescription() const override { | ||
return "Convert the prepared CIR dialect module to LLVM dialect"; | ||
} | ||
|
||
StringRef getArgument() const override { return "cir-flat-to-llvm"; } | ||
}; | ||
|
||
mlir::LogicalResult CIRToLLVMGlobalOpLowering::matchAndRewrite( | ||
cir::GlobalOp op, OpAdaptor adaptor, | ||
mlir::ConversionPatternRewriter &rewriter) const { | ||
|
||
// Fetch required values to create LLVM op. | ||
const mlir::Type cirSymType = op.getSymType(); | ||
|
||
// This is the LLVM dialect type. | ||
const mlir::Type llvmType = getTypeConverter()->convertType(cirSymType); | ||
// FIXME: These default values are placeholders until the the equivalent | ||
// attributes are available on cir.global ops. | ||
assert(!cir::MissingFeatures::opGlobalConstant()); | ||
const bool isConst = false; | ||
assert(!cir::MissingFeatures::addressSpace()); | ||
const unsigned addrSpace = 0; | ||
assert(!cir::MissingFeatures::opGlobalDSOLocal()); | ||
const bool isDsoLocal = true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps extract alignment, addrspace, and threadlocal here too? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That sounds reasonable. I haven't dug into all the cases that will be handled here, so there might be some cases where that doesn't work, but I can at least put placeholders here for now. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be useful to introduce the |
||
assert(!cir::MissingFeatures::opGlobalThreadLocal()); | ||
const bool isThreadLocal = false; | ||
assert(!cir::MissingFeatures::opGlobalAlignment()); | ||
const uint64_t alignment = 0; | ||
assert(!cir::MissingFeatures::opGlobalLinkage()); | ||
const mlir::LLVM::Linkage linkage = mlir::LLVM::Linkage::External; | ||
const StringRef symbol = op.getSymName(); | ||
std::optional<mlir::Attribute> init = op.getInitialValue(); | ||
|
||
SmallVector<mlir::NamedAttribute> attributes; | ||
|
||
if (init.has_value()) { | ||
if (const auto fltAttr = mlir::dyn_cast<cir::FPAttr>(init.value())) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note, I THINK these need to be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is one of those things that's going to be counter-intuitive to people used to I was going to suggest that I could spell out the type here rather than using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I don't have a problem with |
||
// Initializer is a constant floating-point number: convert to MLIR | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At a certain point, a type-visitor is going to be something we want for here, but ATM this is fine. |
||
// builtin constant. | ||
init = rewriter.getFloatAttr(llvmType, fltAttr.getValue()); | ||
} else if (const auto intAttr = | ||
mlir::dyn_cast<cir::IntAttr>(init.value())) { | ||
// Initializer is a constant array: convert it to a compatible llvm init. | ||
init = rewriter.getIntegerAttr(llvmType, intAttr.getValue()); | ||
} else { | ||
op.emitError() << "unsupported initializer '" << init.value() << "'"; | ||
return mlir::failure(); | ||
} | ||
} | ||
|
||
// Rewrite op. | ||
rewriter.replaceOpWithNewOp<mlir::LLVM::GlobalOp>( | ||
op, llvmType, isConst, linkage, symbol, init.value_or(mlir::Attribute()), | ||
alignment, addrSpace, isDsoLocal, isThreadLocal, | ||
/*comdat=*/mlir::SymbolRefAttr(), attributes); | ||
|
||
return mlir::success(); | ||
} | ||
|
||
static void prepareTypeConverter(mlir::LLVMTypeConverter &converter, | ||
mlir::DataLayout &dataLayout) { | ||
converter.addConversion([&](cir::IntType type) -> mlir::Type { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Its a shame there isn't an |
||
// LLVM doesn't work with signed types, so we drop the CIR signs here. | ||
return mlir::IntegerType::get(type.getContext(), type.getWidth()); | ||
}); | ||
converter.addConversion([&](cir::SingleType type) -> mlir::Type { | ||
return mlir::Float32Type::get(type.getContext()); | ||
}); | ||
converter.addConversion([&](cir::DoubleType type) -> mlir::Type { | ||
return mlir::Float64Type::get(type.getContext()); | ||
}); | ||
converter.addConversion([&](cir::FP80Type type) -> mlir::Type { | ||
return mlir::Float80Type::get(type.getContext()); | ||
}); | ||
converter.addConversion([&](cir::FP128Type type) -> mlir::Type { | ||
return mlir::Float128Type::get(type.getContext()); | ||
}); | ||
converter.addConversion([&](cir::LongDoubleType type) -> mlir::Type { | ||
return converter.convertType(type.getUnderlying()); | ||
}); | ||
converter.addConversion([&](cir::FP16Type type) -> mlir::Type { | ||
return mlir::Float16Type::get(type.getContext()); | ||
}); | ||
converter.addConversion([&](cir::BF16Type type) -> mlir::Type { | ||
return mlir::BFloat16Type::get(type.getContext()); | ||
}); | ||
} | ||
|
||
void ConvertCIRToLLVMPass::runOnOperation() { | ||
llvm::TimeTraceScope scope("Convert CIR to LLVM Pass"); | ||
|
||
mlir::ModuleOp module = getOperation(); | ||
erichkeane marked this conversation as resolved.
Show resolved
Hide resolved
|
||
mlir::DataLayout dl(module); | ||
mlir::LLVMTypeConverter converter(&getContext()); | ||
prepareTypeConverter(converter, dl); | ||
|
||
mlir::RewritePatternSet patterns(&getContext()); | ||
|
||
patterns.add<CIRToLLVMGlobalOpLowering>(converter, patterns.getContext(), dl); | ||
|
||
mlir::ConversionTarget target(getContext()); | ||
target.addLegalOp<mlir::ModuleOp>(); | ||
target.addLegalDialect<mlir::LLVM::LLVMDialect>(); | ||
target.addIllegalDialect<mlir::BuiltinDialect, cir::CIRDialect, | ||
mlir::func::FuncDialect>(); | ||
|
||
if (failed(applyPartialConversion(module, target, std::move(patterns)))) | ||
signalPassFailure(); | ||
} | ||
|
||
static std::unique_ptr<mlir::Pass> createConvertCIRToLLVMPass() { | ||
return std::make_unique<ConvertCIRToLLVMPass>(); | ||
} | ||
|
||
static void populateCIRToLLVMPasses(mlir::OpPassManager &pm) { | ||
pm.addPass(createConvertCIRToLLVMPass()); | ||
} | ||
|
||
std::unique_ptr<llvm::Module> | ||
lowerDirectlyFromCIRToLLVMIR(mlir::ModuleOp mlirModule, LLVMContext &llvmCtx) { | ||
llvm::TimeTraceScope scope("lower from CIR to LLVM directly"); | ||
|
||
std::optional<StringRef> moduleName = mlirModule.getName(); | ||
auto llvmModule = std::make_unique<llvm::Module>( | ||
moduleName ? *moduleName : "CIRToLLVMModule", llvmCtx); | ||
mlir::MLIRContext *mlirCtx = mlirModule.getContext(); | ||
|
||
mlir::PassManager pm(mlirCtx); | ||
populateCIRToLLVMPasses(pm); | ||
|
||
if (mlir::failed(pm.run(mlirModule))) { | ||
// FIXME: Handle any errors where they occurs and return a nullptr here. | ||
report_fatal_error( | ||
"The pass manager failed to lower CIR to LLVMIR dialect!"); | ||
} | ||
|
||
mlir::registerBuiltinDialectTranslation(*mlirCtx); | ||
mlir::registerLLVMDialectTranslation(*mlirCtx); | ||
|
||
llvm::TimeTraceScope translateScope("translateModuleToLLVMIR"); | ||
|
||
StringRef moduleName = mlirModule.getName().value_or("CIRToLLVMModule"); | ||
std::unique_ptr<llvm::Module> llvmModule = | ||
mlir::translateModuleToLLVMIR(mlirModule, llvmCtx, moduleName); | ||
|
||
if (!llvmModule) | ||
if (!llvmModule) { | ||
// FIXME: Handle any errors where they occurs and return a nullptr here. | ||
report_fatal_error("Lowering from LLVMIR dialect to llvm IR failed!"); | ||
} | ||
|
||
return llvmModule; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
//====- LowerToLLVM.h- Lowering from CIR to LLVM --------------------------===// | ||
// | ||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
// See https://llvm.org/LICENSE.txt for license information. | ||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
// | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This file declares an interface for converting CIR modules to LLVM IR. | ||
// | ||
//===----------------------------------------------------------------------===// | ||
#ifndef CLANG_CIR_LOWERTOLLVM_H | ||
#define CLANG_CIR_LOWERTOLLVM_H | ||
|
||
#include "mlir/Transforms/DialectConversion.h" | ||
#include "clang/CIR/Dialect/IR/CIRDialect.h" | ||
|
||
namespace cir { | ||
|
||
namespace direct { | ||
|
||
class CIRToLLVMGlobalOpLowering | ||
: public mlir::OpConversionPattern<cir::GlobalOp> { | ||
const mlir::DataLayout &dataLayout; | ||
|
||
public: | ||
CIRToLLVMGlobalOpLowering(const mlir::TypeConverter &typeConverter, | ||
mlir::MLIRContext *context, | ||
const mlir::DataLayout &dataLayout) | ||
: OpConversionPattern(typeConverter, context), dataLayout(dataLayout) { | ||
setHasBoundedRewriteRecursion(); | ||
} | ||
|
||
mlir::LogicalResult | ||
matchAndRewrite(cir::GlobalOp op, OpAdaptor adaptor, | ||
mlir::ConversionPatternRewriter &rewriter) const override; | ||
}; | ||
|
||
} // namespace direct | ||
} // namespace cir | ||
|
||
#endif // CLANG_CIR_LOWERTOLLVM_H |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
// Global variables of intergal types | ||
// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o - | FileCheck %s | ||
|
||
// Note: Currently unsupported features include default zero-initialization | ||
// and alignment. The fact that "external" is only printed for globals | ||
// without an initializer is a quirk of the LLVM AsmWriter. | ||
|
||
char c; | ||
// CHECK: @c = external dso_local global i8 | ||
|
||
signed char sc; | ||
// CHECK: @sc = external dso_local global i8 | ||
|
||
unsigned char uc; | ||
// CHECK: @uc = external dso_local global i8 | ||
|
||
short ss; | ||
// CHECK: @ss = external dso_local global i16 | ||
|
||
unsigned short us = 100; | ||
// CHECK: @us = dso_local global i16 100 | ||
|
||
int si = 42; | ||
// CHECK: @si = dso_local global i32 42 | ||
|
||
unsigned ui; | ||
// CHECK: @ui = external dso_local global i32 | ||
|
||
long sl; | ||
// CHECK: @sl = external dso_local global i64 | ||
|
||
unsigned long ul; | ||
// CHECK: @ul = external dso_local global i64 | ||
|
||
long long sll; | ||
// CHECK: @sll = external dso_local global i64 | ||
|
||
unsigned long long ull = 123456; | ||
// CHECK: @ull = dso_local global i64 123456 | ||
|
||
__int128 s128; | ||
// CHECK: @s128 = external dso_local global i128 | ||
|
||
unsigned __int128 u128; | ||
// CHECK: @u128 = external dso_local global i128 | ||
|
||
wchar_t wc; | ||
// CHECK: @wc = external dso_local global i32 | ||
|
||
char8_t c8; | ||
// CHECK: @c8 = external dso_local global i8 | ||
|
||
char16_t c16; | ||
// CHECK: @c16 = external dso_local global i16 | ||
|
||
char32_t c32; | ||
// CHECK: @c32 = external dso_local global i32 | ||
|
||
_BitInt(20) sb20; | ||
// CHECK: @sb20 = external dso_local global i20 | ||
|
||
unsigned _BitInt(48) ub48; | ||
// CHECK: @ub48 = external dso_local global i48 | ||
|
||
_Float16 f16; | ||
// CHECK: @f16 = external dso_local global half | ||
|
||
__bf16 bf16; | ||
// CHECK: @bf16 = external dso_local global bfloat | ||
|
||
float f; | ||
// CHECK: @f = external dso_local global float | ||
|
||
double d = 1.25; | ||
// CHECK: @d = dso_local global double 1.250000e+00 | ||
|
||
long double ld; | ||
// CHECK: @ld = external dso_local global x86_fp80 | ||
|
||
__float128 f128; | ||
// CHECK: @f128 = external dso_local global fp128 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,10 @@ | ||
// Smoke test for ClangIR-to-LLVM IR code generation | ||
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o - | FileCheck %s | ||
|
||
// TODO: Add checks when proper lowering is implemented. | ||
// For now, we're just creating an empty module. | ||
// CHECK: ModuleID | ||
int a; | ||
|
||
void foo() {} | ||
erichkeane marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// CHECK: @a = external dso_local global i32 | ||
|
||
int b = 2; | ||
|
||
// CHECK: @b = dso_local global i32 2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what does 'flat' mean here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When control flow and scopes implemented in CIR, there will be nested regions describing that structure. When that happens another pass will be run before this one that flattens the CIR by inlining the nested regions to create a situation where all blocks in a function belong to the parent region.
Here's a simple example showing what that looks like: https://godbolt.org/z/Ta5dTMfPn
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Wouldn't mind a comment somewhere explaining this is for 'flat' CIR only, as it is a bit of a sub-dialect it sounds?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea, a comment might be good here since cir-flat hasn't been introduced just yet