Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -2267,6 +2267,10 @@ def CIR_FuncOp : CIR_Op<"func", [
The function linkage information is specified by `linkage`, as defined by
`GlobalLinkageKind` attribute.

The `lambda` translates to a C++ `operator()` that implements a lambda, this
allow callsites to make certain assumptions about the real function nature
when writing analysis.

The `no_proto` keyword is used to identify functions that were declared
without a prototype and, consequently, may contain calls with invalid
arguments and undefined behavior.
Expand All @@ -2289,6 +2293,7 @@ def CIR_FuncOp : CIR_Op<"func", [
let arguments = (ins SymbolNameAttr:$sym_name,
CIR_VisibilityAttr:$global_visibility,
TypeAttrOf<CIR_FuncType>:$function_type,
UnitAttr:$lambda,
UnitAttr:$no_proto,
UnitAttr:$dso_local,
DefaultValuedAttr<CIR_GlobalLinkageKind,
Expand Down
2 changes: 1 addition & 1 deletion clang/include/clang/CIR/MissingFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ struct MissingFeatures {
static bool builtinCallF128() { return false; }
static bool builtinCallMathErrno() { return false; }
static bool builtinCheckKind() { return false; }
static bool cgCapturedStmtInfo() { return false; }
static bool cgFPOptionsRAII() { return false; }
static bool cirgenABIInfo() { return false; }
static bool cleanupAfterErrorDiags() { return false; }
Expand Down Expand Up @@ -234,7 +235,6 @@ struct MissingFeatures {
static bool isMemcpyEquivalentSpecialMember() { return false; }
static bool isTrivialCtorOrDtor() { return false; }
static bool lambdaCaptures() { return false; }
static bool lambdaFieldToName() { return false; }
static bool loopInfoStack() { return false; }
static bool lowerAggregateLoadStore() { return false; }
static bool lowerModeOptLevel() { return false; }
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/CIR/CodeGen/CIRGenClass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -820,7 +820,7 @@ mlir::Value CIRGenFunction::getVTTParameter(GlobalDecl gd, bool forVirtualBase,
if (!cgm.getCXXABI().needsVTTParameter(gd))
return nullptr;

const CXXRecordDecl *rd = cast<CXXMethodDecl>(curFuncDecl)->getParent();
const CXXRecordDecl *rd = cast<CXXMethodDecl>(curCodeDecl)->getParent();
const CXXRecordDecl *base = cast<CXXMethodDecl>(gd.getDecl())->getParent();

uint64_t subVTTIndex;
Expand Down
124 changes: 121 additions & 3 deletions clang/lib/CIR/CodeGen/CIRGenExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,8 @@ LValue CIRGenFunction::emitLValueForField(LValue base, const FieldDecl *field) {

llvm::StringRef fieldName = field->getName();
unsigned fieldIndex;
assert(!cir::MissingFeatures::lambdaFieldToName());
if (cgm.lambdaFieldToName.count(field))
fieldName = cgm.lambdaFieldToName[field];

if (rec->isUnion())
fieldIndex = field->getFieldIndex();
Expand All @@ -476,8 +477,16 @@ LValue CIRGenFunction::emitLValueForField(LValue base, const FieldDecl *field) {

// If this is a reference field, load the reference right now.
if (fieldType->isReferenceType()) {
cgm.errorNYI(field->getSourceRange(), "emitLValueForField: reference type");
return LValue();
assert(!cir::MissingFeatures::opTBAA());
LValue refLVal = makeAddrLValue(addr, fieldType, fieldBaseInfo);
if (recordCVR & Qualifiers::Volatile)
refLVal.getQuals().addVolatile();
addr = emitLoadOfReference(refLVal, getLoc(field->getSourceRange()),
&fieldBaseInfo);

// Qualifiers on the struct don't apply to the referencee.
recordCVR = 0;
fieldType = fieldType->getPointeeType();
}

if (field->hasAttr<AnnotateAttr>()) {
Expand Down Expand Up @@ -619,6 +628,38 @@ static cir::FuncOp emitFunctionDeclPointer(CIRGenModule &cgm, GlobalDecl gd) {
return cgm.getAddrOfFunction(gd);
}

static LValue emitCapturedFieldLValue(CIRGenFunction &cgf, const FieldDecl *fd,
mlir::Value thisValue) {
return cgf.emitLValueForLambdaField(fd, thisValue);
}

/// Given that we are currently emitting a lambda, emit an l-value for
/// one of its members.
///
LValue CIRGenFunction::emitLValueForLambdaField(const FieldDecl *field,
mlir::Value thisValue) {
bool hasExplicitObjectParameter = false;
const auto *methD = dyn_cast_if_present<CXXMethodDecl>(curCodeDecl);
LValue lambdaLV;
if (methD) {
hasExplicitObjectParameter = methD->isExplicitObjectMemberFunction();
assert(methD->getParent()->isLambda());
assert(methD->getParent() == field->getParent());
}
if (hasExplicitObjectParameter) {
cgm.errorNYI(field->getSourceRange(), "ExplicitObjectMemberFunction");
} else {
QualType lambdaTagType =
getContext().getCanonicalTagType(field->getParent());
lambdaLV = makeNaturalAlignAddrLValue(thisValue, lambdaTagType);
}
return emitLValueForField(lambdaLV, field);
}

LValue CIRGenFunction::emitLValueForLambdaField(const FieldDecl *field) {
return emitLValueForLambdaField(field, cxxabiThisValue);
}

static LValue emitFunctionDeclLValue(CIRGenFunction &cgf, const Expr *e,
GlobalDecl gd) {
const FunctionDecl *fd = cast<FunctionDecl>(gd.getDecl());
Expand All @@ -645,13 +686,90 @@ static LValue emitFunctionDeclLValue(CIRGenFunction &cgf, const Expr *e,
AlignmentSource::Decl);
}

/// Determine whether we can emit a reference to \p vd from the current
/// context, despite not necessarily having seen an odr-use of the variable in
/// this context.
/// TODO(cir): This could be shared with classic codegen.
static bool canEmitSpuriousReferenceToVariable(CIRGenFunction &cgf,
const DeclRefExpr *e,
const VarDecl *vd) {
// For a variable declared in an enclosing scope, do not emit a spurious
// reference even if we have a capture, as that will emit an unwarranted
// reference to our capture state, and will likely generate worse code than
// emitting a local copy.
if (e->refersToEnclosingVariableOrCapture())
return false;

// For a local declaration declared in this function, we can always reference
// it even if we don't have an odr-use.
if (vd->hasLocalStorage()) {
return vd->getDeclContext() ==
dyn_cast_or_null<DeclContext>(cgf.curCodeDecl);
}

// For a global declaration, we can emit a reference to it if we know
// for sure that we are able to emit a definition of it.
vd = vd->getDefinition(cgf.getContext());
if (!vd)
return false;

// Don't emit a spurious reference if it might be to a variable that only
// exists on a different device / target.
// FIXME: This is unnecessarily broad. Check whether this would actually be a
// cross-target reference.
if (cgf.getLangOpts().OpenMP || cgf.getLangOpts().CUDA ||
cgf.getLangOpts().OpenCL) {
return false;
}

// We can emit a spurious reference only if the linkage implies that we'll
// be emitting a non-interposable symbol that will be retained until link
// time.
switch (cgf.cgm.getCIRLinkageVarDefinition(vd, /*IsConstant=*/false)) {
case cir::GlobalLinkageKind::ExternalLinkage:
case cir::GlobalLinkageKind::LinkOnceODRLinkage:
case cir::GlobalLinkageKind::WeakODRLinkage:
case cir::GlobalLinkageKind::InternalLinkage:
case cir::GlobalLinkageKind::PrivateLinkage:
return true;
default:
return false;
}
}

LValue CIRGenFunction::emitDeclRefLValue(const DeclRefExpr *e) {
const NamedDecl *nd = e->getDecl();
QualType ty = e->getType();

assert(e->isNonOdrUse() != NOUR_Unevaluated &&
"should not emit an unevaluated operand");

if (const auto *vd = dyn_cast<VarDecl>(nd)) {
// Global Named registers access via intrinsics only
if (vd->getStorageClass() == SC_Register && vd->hasAttr<AsmLabelAttr>() &&
!vd->isLocalVarDecl()) {
cgm.errorNYI(e->getSourceRange(),
"emitDeclRefLValue: Global Named registers access");
return LValue();
}

if (e->isNonOdrUse() == NOUR_Constant &&
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The incubator just asserts e->isNonOdrUse() != NOUR_Constant && "NYI", but we have a couple of test cases that reach this code because of other unimplemented handling that would otherwise intercept the decl sooner. We emit correct code for those cases, but extra qualification is needed to avoid issuing an unnecessary errorNYI diagnostic. These conditions are the conditions for which classic codegen has additional handling here.

(vd->getType()->isReferenceType() ||
!canEmitSpuriousReferenceToVariable(*this, e, vd))) {
cgm.errorNYI(e->getSourceRange(), "emitDeclRefLValue: NonOdrUse");
return LValue();
}

// Check for captured variables.
if (e->refersToEnclosingVariableOrCapture()) {
vd = vd->getCanonicalDecl();
if (FieldDecl *fd = lambdaCaptureFields.lookup(vd))
return emitCapturedFieldLValue(*this, fd, cxxabiThisValue);
assert(!cir::MissingFeatures::cgCapturedStmtInfo());
assert(!cir::MissingFeatures::openMP());
}
}

if (const auto *vd = dyn_cast<VarDecl>(nd)) {
// Checks for omitted feature handling
assert(!cir::MissingFeatures::opAllocaStaticLocal());
Expand Down
51 changes: 46 additions & 5 deletions clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ class AggExprEmitter : public StmtVisitor<AggExprEmitter> {
assert(!cir::MissingFeatures::aggValueSlotDestructedFlag());
Visit(e->getSubExpr());
}
void VisitLambdaExpr(LambdaExpr *e);

// Stubs -- These should be moved up when they are implemented.
void VisitCastExpr(CastExpr *e) {
Expand Down Expand Up @@ -239,9 +240,6 @@ class AggExprEmitter : public StmtVisitor<AggExprEmitter> {
cgf.cgm.errorNYI(e->getSourceRange(),
"AggExprEmitter: VisitCXXInheritedCtorInitExpr");
}
void VisitLambdaExpr(LambdaExpr *e) {
cgf.cgm.errorNYI(e->getSourceRange(), "AggExprEmitter: VisitLambdaExpr");
}
void VisitCXXStdInitializerListExpr(CXXStdInitializerListExpr *e) {
cgf.cgm.errorNYI(e->getSourceRange(),
"AggExprEmitter: VisitCXXStdInitializerListExpr");
Expand Down Expand Up @@ -495,8 +493,10 @@ void AggExprEmitter::emitInitializationToLValue(Expr *e, LValue lv) {
if (isa<NoInitExpr>(e))
return;

if (type->isReferenceType())
cgf.cgm.errorNYI("emitInitializationToLValue ReferenceType");
if (type->isReferenceType()) {
RValue rv = cgf.emitReferenceBindingToExpr(e);
return cgf.emitStoreThroughLValue(rv, lv);
}

switch (cgf.getEvaluationKind(type)) {
case cir::TEK_Complex:
Expand Down Expand Up @@ -550,6 +550,47 @@ void AggExprEmitter::emitNullInitializationToLValue(mlir::Location loc,
cgf.emitNullInitialization(loc, lv.getAddress(), lv.getType());
}

void AggExprEmitter::VisitLambdaExpr(LambdaExpr *e) {
CIRGenFunction::SourceLocRAIIObject loc{cgf, cgf.getLoc(e->getSourceRange())};
AggValueSlot slot = ensureSlot(cgf.getLoc(e->getSourceRange()), e->getType());
[[maybe_unused]] LValue slotLV =
cgf.makeAddrLValue(slot.getAddress(), e->getType());

// We'll need to enter cleanup scopes in case any of the element
// initializers throws an exception or contains branch out of the expressions.
assert(!cir::MissingFeatures::opScopeCleanupRegion());

for (auto [curField, capture, captureInit] : llvm::zip(
e->getLambdaClass()->fields(), e->captures(), e->capture_inits())) {
// Pick a name for the field.
llvm::StringRef fieldName = curField->getName();
if (capture.capturesVariable()) {
assert(!curField->isBitField() && "lambdas don't have bitfield members!");
ValueDecl *v = capture.getCapturedVar();
fieldName = v->getName();
cgf.cgm.lambdaFieldToName[curField] = fieldName;
} else if (capture.capturesThis()) {
cgf.cgm.lambdaFieldToName[curField] = "this";
} else {
cgf.cgm.errorNYI(e->getSourceRange(), "Unhandled capture kind");
cgf.cgm.lambdaFieldToName[curField] = "unhandled-capture-kind";
}

// Emit initialization
LValue lv =
cgf.emitLValueForFieldInitialization(slotLV, curField, fieldName);
if (curField->hasCapturedVLAType())
cgf.cgm.errorNYI(e->getSourceRange(), "lambda captured VLA type");

emitInitializationToLValue(captureInit, lv);

// Push a destructor if necessary.
if ([[maybe_unused]] QualType::DestructionKind DtorKind =
curField->getType().isDestructedType())
cgf.cgm.errorNYI(e->getSourceRange(), "lambda with destructed field");
}
}

void AggExprEmitter::VisitCallExpr(const CallExpr *e) {
if (e->getCallReturnType(cgf.getContext())->isReferenceType()) {
cgf.cgm.errorNYI(e->getSourceRange(), "reference return type");
Expand Down
32 changes: 31 additions & 1 deletion clang/lib/CIR/CodeGen/CIRGenFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@ void CIRGenFunction::startFunction(GlobalDecl gd, QualType returnType,
curFn = fn;

const Decl *d = gd.getDecl();
curCodeDecl = d;
const auto *fd = dyn_cast_or_null<FunctionDecl>(d);
curFuncDecl = d->getNonClosureContext();

Expand Down Expand Up @@ -457,7 +458,36 @@ void CIRGenFunction::startFunction(GlobalDecl gd, QualType returnType,

const auto *md = cast<CXXMethodDecl>(d);
if (md->getParent()->isLambda() && md->getOverloadedOperator() == OO_Call) {
cgm.errorNYI(loc, "lambda call operator");
// We're in a lambda.
curFn.setLambda(true);

// Figure out the captures.
md->getParent()->getCaptureFields(lambdaCaptureFields,
lambdaThisCaptureField);
if (lambdaThisCaptureField) {
// If the lambda captures the object referred to by '*this' - either by
// value or by reference, make sure CXXThisValue points to the correct
// object.

// Get the lvalue for the field (which is a copy of the enclosing object
// or contains the address of the enclosing object).
LValue thisFieldLValue =
emitLValueForLambdaField(lambdaThisCaptureField);
if (!lambdaThisCaptureField->getType()->isPointerType()) {
// If the enclosing object was captured by value, just use its
// address. Sign this pointer.
cxxThisValue = thisFieldLValue.getPointer();
} else {
// Load the lvalue pointed to by the field, since '*this' was captured
// by reference.
cxxThisValue =
emitLoadOfLValue(thisFieldLValue, SourceLocation()).getValue();
}
}
for (auto *fd : md->getParent()->fields()) {
if (fd->hasCapturedVLAType())
cgm.errorNYI(loc, "lambda captured VLA type");
}
} else {
// Not in a lambda; just use 'this' from the method.
// FIXME: Should we generate a new load for each use of 'this'? The fast
Expand Down
10 changes: 10 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ class CIRGenFunction : public CIRGenTypeCache {
/// Tracks function scope overall cleanup handling.
EHScopeStack ehStack;

llvm::DenseMap<const clang::ValueDecl *, clang::FieldDecl *>
lambdaCaptureFields;
clang::FieldDecl *lambdaThisCaptureField = nullptr;

/// CXXThisDecl - When generating code for a C++ member function,
/// this will hold the implicit 'this' declaration.
ImplicitParamDecl *cxxabiThisDecl = nullptr;
Expand All @@ -91,6 +95,8 @@ class CIRGenFunction : public CIRGenTypeCache {

// Holds the Decl for the current outermost non-closure context
const clang::Decl *curFuncDecl = nullptr;
/// This is the inner-most code context, which includes blocks.
const clang::Decl *curCodeDecl = nullptr;

/// The function for which code is currently being generated.
cir::FuncOp curFn;
Expand Down Expand Up @@ -1377,6 +1383,10 @@ class CIRGenFunction : public CIRGenTypeCache {
LValue emitLValueForBitField(LValue base, const FieldDecl *field);
LValue emitLValueForField(LValue base, const clang::FieldDecl *field);

LValue emitLValueForLambdaField(const FieldDecl *field);
LValue emitLValueForLambdaField(const FieldDecl *field,
mlir::Value thisValue);

/// Like emitLValueForField, excpet that if the Field is a reference, this
/// will return the address of the reference and not the address of the value
/// stored in the reference.
Expand Down
6 changes: 6 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ class CIRGenModule : public CIRGenTypeCache {

mlir::Operation *lastGlobalOp = nullptr;

/// Keep a map between lambda fields and names, this needs to be per module
/// since lambdas might get generated later as part of defered work, and since
/// the pointers are supposed to be uniqued, should be fine. Revisit this if
/// it ends up taking too much memory.
llvm::DenseMap<const clang::FieldDecl *, llvm::StringRef> lambdaFieldToName;

/// Tell the consumer that this variable has been instantiated.
void handleCXXStaticMemberVarInstantiation(VarDecl *vd);

Expand Down
6 changes: 6 additions & 0 deletions clang/lib/CIR/Dialect/IR/CIRDialect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1548,11 +1548,14 @@ ParseResult cir::FuncOp::parse(OpAsmParser &parser, OperationState &state) {
llvm::SMLoc loc = parser.getCurrentLocation();
mlir::Builder &builder = parser.getBuilder();

mlir::StringAttr lambdaNameAttr = getLambdaAttrName(state.name);
mlir::StringAttr noProtoNameAttr = getNoProtoAttrName(state.name);
mlir::StringAttr visNameAttr = getSymVisibilityAttrName(state.name);
mlir::StringAttr visibilityNameAttr = getGlobalVisibilityAttrName(state.name);
mlir::StringAttr dsoLocalNameAttr = getDsoLocalAttrName(state.name);

if (::mlir::succeeded(parser.parseOptionalKeyword(lambdaNameAttr.strref())))
state.addAttribute(lambdaNameAttr, parser.getBuilder().getUnitAttr());
if (parser.parseOptionalKeyword(noProtoNameAttr).succeeded())
state.addAttribute(noProtoNameAttr, parser.getBuilder().getUnitAttr());

Expand Down Expand Up @@ -1660,6 +1663,9 @@ mlir::Region *cir::FuncOp::getCallableRegion() {
}

void cir::FuncOp::print(OpAsmPrinter &p) {
if (getLambda())
p << " lambda";

if (getNoProto())
p << " no_proto";

Expand Down
Loading