Skip to content
Merged
22 changes: 21 additions & 1 deletion clang/include/clang/CIR/LowerToLLVM.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
#ifndef CLANG_CIR_LOWERTOLLVM_H
#define CLANG_CIR_LOWERTOLLVM_H

#include "mlir/Pass/Pass.h"
#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
#include "mlir/Transforms/DialectConversion.h"
#include "clang/CIR/Dialect/IR/CIRDialect.h"

#include <memory>

Expand All @@ -31,6 +33,24 @@ namespace direct {
std::unique_ptr<llvm::Module>
lowerDirectlyFromCIRToLLVMIR(mlir::ModuleOp mlirModule,
llvm::LLVMContext &llvmCtx);

class CIRToLLVMGlobalOpLowering
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is originally in clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h. Any reason you're promoting it to the include top level dir?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that was an accident. This is a different file with the same name but different location. I think I missed that detail when I was moving this bit of code over from the incubator project.

: public mlir::OpConversionPattern<cir::GlobalOp> {
mlir::DataLayout const &dataLayout;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

curious why east const for only this type?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think I understand your question here.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typically we do const west, so const is on the left of the type. You do that everywhere else, except for this one point, where it is east const (on the right).

SO I guess I'm asking, why:
mlir::DataLayout const & instead of const mlir::DataLayout &? And only for this type (in the parameter list below as well).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. I wasn't familiar with the east const terminology, thought maybe the "east" part was a typo.

I suspect this was just a style preference of the person who introduced this parameter in the incubator implementation. I'll make it consistent.


public:
CIRToLLVMGlobalOpLowering(const mlir::TypeConverter &typeConverter,
mlir::MLIRContext *context,
mlir::DataLayout const &dataLayout)
: OpConversionPattern(typeConverter, context), dataLayout(dataLayout) {
setHasBoundedRewriteRecursion();
}

mlir::LogicalResult
matchAndRewrite(cir::GlobalOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const override;
};

} // namespace direct
} // namespace cir

Expand Down
7 changes: 7 additions & 0 deletions clang/lib/CIR/Lowering/DirectToLLVM/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ set(LLVM_LINK_COMPONENTS
Support
)

get_property(dialect_libs GLOBAL PROPERTY MLIR_DIALECT_LIBS)

add_clang_library(clangCIRLoweringDirectToLLVM
LowerToLLVM.cpp

LINK_LIBS
${dialect_libs}
MLIRBuiltinToLLVMIRTranslation
MLIRLLVMToLLVMIRTranslation
)
130 changes: 128 additions & 2 deletions clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,19 @@

#include "clang/CIR/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 "llvm/IR/Module.h"
#include "llvm/Support/TimeProfiler.h"

Expand All @@ -22,13 +34,127 @@ using namespace llvm;
namespace cir {
namespace direct {

struct ConvertCIRToLLVMPass
: public mlir::PassWrapper<ConvertCIRToLLVMPass,
mlir::OperationPass<mlir::ModuleOp>> {
void getDependentDialects(mlir::DialectRegistry &registry) 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"; }
Copy link
Collaborator

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?

Copy link
Contributor Author

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

Copy link
Collaborator

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?

Copy link
Member

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

};

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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// This is the LLVM dialect type
// This is the LLVM dialect type.

const mlir::Type llvmType = getTypeConverter()->convertType(cirSymType);
// These defaults are just here until the equivalent attributes are
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// These defaults are just here until the equivalent attributes are
// FIXME: These default values are placeholders until the the equivalent attributes are available on cir.global ops.

^^ Plus word-wrap.

// available on cir.global ops.
const bool isConst = false;
const bool isDsoLocal = true;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps extract alignment, addrspace, and threadlocal here too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be useful to introduce the MissingFeatures.h header to upstream so we can start tracking these little missing details.

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;

// Check for missing funcionalities.
if (!init.has_value()) {
rewriter.replaceOpWithNewOp<mlir::LLVM::GlobalOp>(
op, llvmType, isConst, linkage, symbol, mlir::Attribute(),
/*alignment*/ 0, /*addrSpace*/ 0, /*dsoLocal*/ isDsoLocal,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/*alignment*/ 0, /*addrSpace*/ 0, /*dsoLocal*/ isDsoLocal,
/*alignment=*/ 0, /*addrSpace=*/ 0, isDsoLocal,

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clang typically uses paramname=. Also, don't need a comment if the variable name tells you what it is.

/*threadLocal*/ false, /*comdat*/ mlir::SymbolRefAttr(), attributes);
return mlir::success();
}

// Initializer is a constant array: convert it to a compatible llvm init.
if (auto intAttr = mlir::dyn_cast<cir::IntAttr>(init.value())) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have 'float' types implemented in the Clang->CIR bit, could we perhaps add float here too if it is only a handful of lines? Would be nice to see what this is going to look like some day.

Also, I find myself wondering if this should be some sort of visitor pattern instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I have that implemented and was going to save it for the next patch along with some other types that are less straightforward, but I can bring in float types here if you like.

I'll think about whether a visitor pattern would work here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the else arm we already emit an error for not implemented things (like float in this example), IMO it should be fine as an incremental addition later on.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (auto intAttr = mlir::dyn_cast<cir::IntAttr>(init.value())) {
if (const auto *intAttr = mlir::dyn_cast<cir::IntAttr>(init.value())) {

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(), /*alignment*/ 0,
/*addrSpace*/ 0, /*dsoLocal*/ isDsoLocal, /*threadLocal*/ false,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/*addrSpace*/ 0, /*dsoLocal*/ isDsoLocal, /*threadLocal*/ false,
/*addrSpace*/ 0, isDsoLocal, /*threadLocal*/ false,

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same other suggestions on comments.

/*comdat*/ mlir::SymbolRefAttr(), attributes);

return mlir::success();
}

static void prepareTypeConverter(mlir::LLVMTypeConverter &converter,
mlir::DataLayout &dataLayout) {
converter.addConversion([&](cir::IntType type) -> mlir::Type {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its a shame there isn't an addConversions variadic version of this that we could just call all of these in 1 place. I see this comes from MLIR unfortunately, but perhaps something to suggest upstream elsewhere.

// LLVM doesn't work with signed types, so we drop the CIR signs here.
return mlir::IntegerType::get(type.getContext(), type.getWidth());
});
}

void ConvertCIRToLLVMPass::runOnOperation() {
llvm::TimeTraceScope scope("Convert CIR to LLVM Pass");

mlir::ModuleOp module = getOperation();
mlir::DataLayout dl(module);
mlir::LLVMTypeConverter converter(&getContext());
prepareTypeConverter(converter, dl); // , lowerModule.get());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
prepareTypeConverter(converter, dl); // , lowerModule.get());
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");

mlir::MLIRContext *mlirCtx = mlirModule.getContext();

mlir::PassManager pm(mlirCtx);
populateCIRToLLVMPasses(pm);

bool result = !mlir::failed(pm.run(mlirModule));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This variable is harming readability to me i think. The name isn't clear which direction it means, plus the double-not is REALLY confusing here :D I suspect just if (mlir::failed(pm.run(mlirModule)) would be most readable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expect reporting a fatal error here is also not what we want long term. I'll do something to clean it up.

if (!result)
report_fatal_error(
"The pass manager failed to lower CIR to LLVMIR dialect!");

mlir::registerBuiltinDialectTranslation(*mlirCtx);
mlir::registerLLVMDialectTranslation(*mlirCtx);

llvm::TimeTraceScope translateScope("translateModuleToLLVMIR");

std::optional<StringRef> moduleName = mlirModule.getName();
auto llvmModule = std::make_unique<llvm::Module>(
moduleName ? *moduleName : "CIRToLLVMModule", llvmCtx);
std::unique_ptr<llvm::Module> llvmModule = mlir::translateModuleToLLVMIR(
mlirModule, llvmCtx, moduleName ? *moduleName : "CIRToLLVMModule");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
mlirModule, llvmCtx, moduleName ? *moduleName : "CIRToLLVMModule");
mlirModule, llvmCtx, moduleName.value_or("CIRToLLVMModule"));

Better yet, make moduleName above a StringRef, and just do mlirModule.getName().value_or("CIRToLLVMModule"));


if (!llvmModule)
report_fatal_error("Lowering from LLVMIR dialect to llvm IR failed!");
Expand Down
63 changes: 63 additions & 0 deletions clang/test/CIR/Lowering/global-var-simple.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// 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
10 changes: 6 additions & 4 deletions clang/test/CIR/Lowering/hello.c
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() {}
// CHECK: @a = external dso_local global i32

int b = 2;

// CHECK: @b = dso_local global i32 2