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
[CIR][CIRGen] Add complex type and its CIRGen support #513
base: main
Are you sure you want to change the base?
Conversation
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.
This is nice, thanks for working on it!
Comments inline but overall I'm just a bit confused by the fact that LLVM skeleton isn't being much followed here (and some comments on CIRGen decisions).
let cppNamespace = "::mlir::cir"; | ||
} | ||
|
||
def ComplexExtractOp : CIR_Op<"complex.extract", [Pure]> { |
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.
I suggest we split this into two operations: complex.real
and complex.imag
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.
Updated.
clang/lib/CIR/CodeGen/CIRGenCXX.cpp
Outdated
@@ -200,8 +200,6 @@ static void buildDeclInit(CIRGenFunction &CGF, const VarDecl *D, | |||
case TEK_Scalar: | |||
CGF.buildScalarInit(Init, CGF.getLoc(D->getLocation()), lv, false); | |||
return; | |||
case TEK_Complex: | |||
llvm_unreachable("complext evaluation NYI"); |
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.
Looking at traditional LLVM codegen:
case TEK_Complex:
CGF.EmitComplexExprIntoLValue(Init, lv, /*isInit*/ true);
return;
Any specific reason why you are not following the skeleton?
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.
Updated.
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.
Why this isn't calling buildComplexExprIntoLValue
?
@@ -171,6 +171,25 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> { | |||
CGF.getLoc(E->getExprLoc()), Ty, | |||
Builder.getAttr<mlir::cir::FPAttr>(Ty, E->getValue())); | |||
} | |||
mlir::Value VisitImaginaryLiteral(const ImaginaryLiteral *E) { |
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.
We should also create a CIRGenExprComplex.cpp
and place relevant methods there. Looks like you should create a ComplexExprEmitter
visitor?
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.
Updated.
mlir::TypedAttr real; | ||
mlir::TypedAttr imag; | ||
|
||
if (elementTy.isa<mlir::cir::IntType>()) { |
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.
Perhaps move getZeroInitAttr
to CIRBaseBuilder
and call it 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.
Updated.
llvm::function_ref<mlir::InFlightDiagnostic()> emitError, | ||
mlir::Type elementTy) { | ||
if (!elementTy.isa<mlir::cir::IntType, mlir::cir::CIRFPTypeInterface>()) { | ||
emitError() << "element type of !cir.complex must be either a " |
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.
I wish there was a way to enforce this in tablegen like there is for operations.
clang/test/CIR/CodeGen/complex.c
Outdated
// RUN: FileCheck --input-file=%t.cir %s | ||
|
||
double _Complex float_complex_basic(void) { | ||
double _Complex x = 2.0 + 3.0i; |
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.
I'm a bit confused with the approach here, seems a bit less ideal than what LLVM generates: https://godbolt.org/z/3Go78MMh1, looking at the CIR output:
%2 = cir.const(#cir.fp<2.000000e+00> : !cir.double) : !cir.double loc(#loc5)
%3 = cir.const(#cir.complex<#cir.fp<0.000000e+00> : !cir.double, #cir.fp<3.000000e+00> : !cir.double> : !cir.complex<!cir.double>) : !cir.complex<!cir.double> loc(#loc4)
%4 = cir.cast(float_to_complex, %2 : !cir.double), !cir.complex<!cir.double> loc(#loc5)
%5 = cir.binop(add, %4, %3) : !cir.complex<!cir.double> loc(#loc81)
Why doesn't %3
already builds the complex? Seems off that CIRGen first creates %2
, followed by bitcast + addition.
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.
Question still remains, sounds like we could do a better initialization job here. It's also fine if you plan to do this as incremental improvement, but I'm mostly looking to hear more about the plan here.
Example: | ||
|
||
```mlir | ||
!fcmp = !cir.complex<!cir.float> |
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.
!fcmp
might sounds like "float comparison", perhaps use !complex
to make the docs more readable.
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.
Updated.
@bcardosolopes Hi Bruno, thanks for your review! Let me explain in more details about why I'm not following the skeleton here. The original clang CodeGen divide expressions into three categories according to their types: 1) scalar, 2) complex, and 3) aggregate. CodeGen handles these three categories of expressions differently:
So the CodeGen of scalar expressions and complex expressions are actually very similar. I believe the reason why CodeGen distinguishes them is that CodeGen of scalar expressions yields OK, that's enough of background story. In CIR, what makes a difference is that instead of using something like
If we follow the skeleton, the code in I hope this explanation is clear enough for you! If you are still confused please let me know. |
Thanks for the detailed explanation (very clarifying) and for thinking about code duplication!
I believe duplication will lead us to converge faster and have less issues. How about you add a |
ed3955a
to
8b7417c
Compare
OK I believe this is also acceptable and it allows us to evaluate this divergence more deeply before we can make further decisions. I'll move the complex CIRGen code to a new |
Rebased onto the latest |
@Lancern sorry, I was out on vacation. Let me know when you address all reviews and update the PR. |
@bcardosolopes Hi Bruno, I have updated the implementation so that it now follows the upstream skeleton. |
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.
Very nice. This is in the right direction, thanks the updates. More comments inline!
``` | ||
}]; | ||
|
||
let results = (outs CIR_AnyType:$result); |
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.
Should the constraint be CIR_AnyIntOrFloat
?
``` | ||
}]; | ||
|
||
let results = (outs CIR_AnyType:$result); |
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.
Should the constraint be CIR_AnyIntOrFloat
?
|
||
%0 = cir.const(#cir.int<1> : !u32i) : !u32i | ||
%1 = cir.const(#cir.int<2> : !u32i) : !u32i | ||
%2 = cir.complex.create(%0 : !u32i, %1) : !complex |
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.
This example is probably best if done in term of building a complex number out of non-constant pieces, because for this one specific we could be using the complex attribute to initialize a constant complex.
}]; | ||
|
||
let results = (outs CIR_ComplexType:$result); | ||
let arguments = (ins CIR_AnyType:$real, CIR_AnyType:$imag); |
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.
Should the operands be constrained in terms of CIR_AnyIntOrFloat
?
clang/lib/CIR/CodeGen/CIRGenCXX.cpp
Outdated
@@ -200,8 +200,6 @@ static void buildDeclInit(CIRGenFunction &CGF, const VarDecl *D, | |||
case TEK_Scalar: | |||
CGF.buildScalarInit(Init, CGF.getLoc(D->getLocation()), lv, false); | |||
return; | |||
case TEK_Complex: | |||
llvm_unreachable("complext evaluation NYI"); |
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.
Why this isn't calling buildComplexExprIntoLValue
?
} | ||
|
||
mlir::Value ComplexExprEmitter::VisitInitListExpr(InitListExpr *E) { | ||
if (E->getNumInits() == 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.
Thanks for introducing the skeleton and for all the added testcases. However, this new file is quite big and introduces lots of CIRGen that I don't see tested (example, this one). Few options here:
- Add more testcases and exercise each of the visitors.
- Delete non-tested visitors and add
llvm_unreacheable
to them. Later on, do incremental patches to add more visitors and their correspondent tests.
clang/lib/CIR/CodeGen/CIRGenDecl.cpp
Outdated
@@ -684,6 +684,15 @@ void CIRGenFunction::buildScalarInit(const Expr *init, mlir::Location loc, | |||
return; | |||
} | |||
|
|||
void CIRGenFunction::buildComplexInit(const Expr *init, mlir::Location loc, | |||
LValue lvalue) { | |||
// TODO: this is where a lot of ObjC lifetime stuff would be done. |
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.
This needs an assert+unimplemented.
number value of the specified complex type. | ||
|
||
The `real` parameter gives the real part of the complex number, and the | ||
`imag` parameter gives the imaginary part of the complex number. |
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.
Add an example of it's use with cir.const
.
def ComplexCreateOp : CIR_Op<"complex.create", [Pure, SameTypeOperands]> { | ||
let summary = "Create a new complex value from its real and imaginary parts"; | ||
let description = [{ | ||
The `cir.complex.create` operation takes two operands of the same type and |
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.
I don't see any use of this operation in the tests, please make sure it shows up.
clang/test/CIR/CodeGen/complex.c
Outdated
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir-enable -emit-cir %s -o %t.cir | ||
// RUN: FileCheck --input-file=%t.cir %s | ||
// RUN: %clang_cc1 -x c++ -triple x86_64-unknown-linux-gnu -fclangir-enable -emit-cir %s -o %t.cir | ||
// RUN: FileCheck --input-file=%t.cir %s |
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.
it would be nice to have a test/CIR/IR/complex.cir
test for printing and parsing as well.
Let me know once this is ready for review again! |
@lanza rebased over the weekend, sorry for the churn. Can you rebase this PR again? |
Rebased onto the latest |
This patch adds an initial support for the C complex type, i.e. `_Complex`. It introduces the following new types, attributes, and operations: - `!cir.complex`, which represents the C complex number type; - `#cir.imag`, which represents an imaginary number literal in C; - `cir.complex.create`, which creates a complex number from its real and imaginary parts; - `cir.complex.get_real`, which derives a pointer to the real part of a complex number given a pointer to the complex number; - `cir.complex.get_imag`, which derives a pointer to the imaginary part of a complex number given a pointer to the complex number. CIRGen for some basic complex number operations is also included in this patch.
@bcardosolopes Hi Bruno, I believe this PR is now ready for another round of review. I decide to keep this PR simpler (but the size of the PR is still quite big). In the latest version I made the following adjustments:
All supported operations are tested in |
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.
I decide to keep this PR simpler (but the size of the PR is still quite big).
Thanks, I appreciate that.
I replaced the #cir.complex attribute with the #cir.imag attribute. The former represents a whole complex number, while the latter represents a complex number that is purely imaginary (i.e. its real part is 0), which can be written in C as a number literal followed by the i suffix (e.g. 3.0i will become #cir.imag<#cir.fp<3.0>>).
After looking the PR, I'm not sure we need #cir.imag
, it should just be an int or fp attribute directly.
// CHECK: %[[#REAL:]] = cir.const #cir.fp<1.000000e+00> : !cir.double
// CHECK-NEXT: %[[#IMAG:]] = cir.const #cir.fp<2.000000e+00> : !cir.double
// CHECK-NEXT: %{{.+}} = cir.complex.create %[[#REAL]], %[[#IMAG]] : !cir.double -> !cir.complex<!cir.double>
This composes better with what cir.complex.create
ends up doing (building imaginary out of regulaer fp/int directly).
I replaced the cir.complex.real and cir.complex.imag with cir.complex.get_real and cir.complex.get_imag, respectively. The main difference here is the get_* version yields pointers to the corresponding complex number component just like what cir.get_member is doing. This change is to support the real and imag operator which yield lvalues.
I'll address this in another comment.
I removed a lot of code in CGExprComplex.cpp and left most visitors as unimplemented for now. Only a few fundamental complex number operations are supported in this PR so code review can be easier, including
Awesome!
All supported operations are tested in clang/test/CIR/CodeGen/complex.c . Support for more complex number operations can be added in future PRs.
Can you please add LLVM IR support and test it altogether in complex.c
?
#ifdef __cplusplus__ | ||
#define VOID | ||
#else | ||
#define VOID void |
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.
nit: please remove the VOID
thing and just use empty paren.
@@ -0,0 +1,130 @@ | |||
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir -o - %s | FileCheck %s --check-prefixes=C,CHECK |
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.
Please write files to disk, it makes it convenient to run locally and look at the output
// CHECK: } | ||
|
||
void get_real(VOID) { | ||
double *r1 = &__real__ c; |
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.
Please add another example where instead of c
being global, its another local complex variable.
|
||
void imag_literal(VOID) { | ||
c = 3.0i; | ||
ci = 3i; |
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.
Here in imag_literal()
%0 = cir.const #cir.imag<#cir.fp<3.000000e+00> : !cir.double> : !cir.complex<!cir.double>
%1 = cir.get_global @c : !cir.ptr<!cir.complex<!cir.double>>
cir.store %0, %1 : !cir.complex<!cir.double>, !cir.ptr<!cir.complex<!cir.double>>
How do you plan to distinguish if the cir.store
is storing to real or imag part when lowering this? Relying on the imag attribute sounds brittle. Another reason to add LLVM lowering as part of the work, it'd have popped up this kind of question / design issue. This should be using the same mechanism as elsewhere:
%0 = cir.const #cir.imag<#cir.fp<3.000000e+00> : !cir.double> : !cir.complex<!cir.double>
%1 = cir.get_global @c : !cir.ptr<!cir.complex<!cir.double>>
%3 = cir.complex.get_img %1 : !cir.ptr<!cir.complex<!cir.double>> -> !cir.ptr<!cir.double>
cir.store %0, %3 : !cir.complex<!cir.double>, !cir.ptr<!cir.complex<!cir.double>>
Please rename cir.complex.get_img
to cir.complex.imag_ptr
, cir.complex.get_real
to cir.complex.real_ptr
.
|
||
void list_init(VOID) { | ||
double _Complex c1 = {1.0, 2.0}; | ||
int _Complex c2 = {1, 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.
Add another testcase that the list initialization happens with two params passed to the function.
// CPP: cir.func @_Z9list_initv() | ||
// CHECK: %[[#REAL:]] = cir.const #cir.fp<1.000000e+00> : !cir.double | ||
// CHECK-NEXT: %[[#IMAG:]] = cir.const #cir.fp<2.000000e+00> : !cir.double | ||
// CHECK-NEXT: %{{.+}} = cir.complex.create %[[#REAL]], %[[#IMAG]] : !cir.double -> !cir.complex<!cir.double> |
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.
%0 = cir.const #cir.fp<1.000000e+00> : !cir.double
%1 = cir.const #cir.fp<2.000000e+00> : !cir.double
%2 = cir.complex.create %0, %1 : !cir.complex<!cir.double>
cir.store %2, ... : !cir.ptr<!cir.complex<!cir.double>>
I prefer keeping the #cir.complex
to be used directly with cir.const
, it composes well with the rest of CIR consts. By applying that change, cir.complex.create
becomes only useful for "merging" two non-constants fp/ints in order to build a !cir.complex
. But this brings the question if this is really useful, two possible approaches:
- We remove it in favor of "get_real/get_imag + store to each", which reuses existing ops. A single store would still be used for when
cir.const
creates the complex type, which also brings some inconsistency. - We keep it. Useful to "join" two non-constants into a complex type so that we only have one final store instead of two, theoretically easier to track on analysis. The downside is having an extra op to build and later lower.
Perhaps we should do (2) because it's easier to translate to MLIR's complex dialect?
This PR adds
!cir.complex
type to model the_Complex
type in C. It also contains support for its CIRGen.In detail, this patch adds the following CIR types, ops, and attributes:
!cir.complex
type is added to model the_Complex
type in C. This type is parameterized with the type of the components of the complex number, which must be either an integer type or a floating-point type.The#cir.complex
attribute is added to represent a literal value of_Complex
type. It is a struct-like attribute that provides the real and imaginary part of the literal_Complex
value.#cir.imag
attribute is added to represent a purely imaginary number.cir.complex.create
op is added to create a complex value from its real and imaginary parts.Thecir.complex.real
andcir.complex.imag
op is added to extract the real and imaginary part of a value of!cir.complex
type, respectively.cir.complex.get_real
andcir.complex.get_imag
op is added to derive a pointer to the real and imaginary part of a value of!cir.complex
type, respectively.CIRGen support for some of the fundamental complex number operations is also included.
Note the implementation diverges from the original clang CodeGen, where expressions of complex types are handled differently from scalars and aggregates. Instead, this patch treats expressions of complex types as scalars, as such expressions can be simply lowered to a CIR value of!cir.complex
type.This PR addresses #445 .