Skip to content
Merged
2 changes: 0 additions & 2 deletions clang/include/clang/CIR/LowerToLLVM.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
#ifndef CLANG_CIR_LOWERTOLLVM_H
#define CLANG_CIR_LOWERTOLLVM_H

#include "mlir/Pass/Pass.h"

#include <memory>

namespace llvm {
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
)
162 changes: 157 additions & 5 deletions clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,21 @@
//
//===----------------------------------------------------------------------===//

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

Expand All @@ -22,16 +34,156 @@ 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

};

// This pass requires the CIR to be in a "flat" state. All blocks in each
Copy link
Collaborator

Choose a reason for hiding this comment

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

Would love more detail here, but I'm not knowledgable enough to review the text, so Bruno or Nathan can approve whatever they think is sufficiently descriptive.

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'm not sure what more I can say here that won't reference things that don't exist yet.

@bcardosolopes @lanza Any suggestions?

Copy link
Collaborator

Choose a reason for hiding this comment

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

It doesn't necessarily have to reference only things in this upstream, enough that the future concept is more clear (your example helped, but I'm still only sorta-familiar with it), but enough so that I don't have to ask again next time I see it always saves us time.

Copy link
Member

@bcardosolopes bcardosolopes Jan 31, 2025

Choose a reason for hiding this comment

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

I'd actually just strip this comment away, probably comes from a time when we were incrementally transitioning to eliminate all regions in favor of basic blocks prior to lowering - right now in the incubator all of LLVM lowering assumes CIR to be flat, so this comment wouldn't be adding much.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, this is a comment I added in response to Erich's question about what "flat" meant in the pass argument string. It feels like there should be some way of enforcing this constraint, assuming we might eventually be in a situation where non-clang clients are pulling CIR passes into their own pipeline. That could eventually happen, right?

Copy link
Member

Choose a reason for hiding this comment

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

Maybe move the comment closer to ConvertCIRToLLVMPass? It's a bit misleading to make it about one specific operation.

there should be some way of enforcing this constraint

In the case of GlobalOp, there are versions of it that might contain a region. Usually for other operations that always have regions (IfOp, SwitchOp, ScopeOp, etc), there's no lowering to LLVM available so you get a hard error if you'd somehow skip the flattening.

For GlobalOp, perhaps we could emit an error if ctorRegion or dtorRegion are not empty.

(Another mechanism we could use is to leverage legality: https://mlir.llvm.org/docs/DialectConversion/#conversion-target, but that doesn't help with GlobalOp, and for other ops we already get an error, so not sure it's viable)

we might eventually be in a situation where non-clang clients

I'd say a bit far-fetched given the dep on AST and other clang bits, but eventually, it's indeed possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh! This was a total fail on my part. I intended for this comment to be above ConvertCIRToLLVMPass and I hadn't even noticed that I put it in the wrong place.

// function must belong to the parent region.
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.
const bool isConst = false;
const unsigned addrSpace = 0;
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 bool isThreadLocal = false;
const uint64_t alignment = 0;
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 (auto fltAttr = mlir::dyn_cast<cir::FPAttr>(init.value())) {
// Initializer is a constant floating-point number: convert to MLIR
Copy link
Collaborator

Choose a reason for hiding this comment

The 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 (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 {
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());
});
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();
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;
}
Expand Down
42 changes: 42 additions & 0 deletions clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
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> {
mlir::DataLayout const &dataLayout;

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

#endif // CLANG_CIR_LOWERTOLLVM_H
81 changes: 81 additions & 0 deletions clang/test/CIR/Lowering/global-var-simple.cpp
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
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