Skip to content
This repository was archived by the owner on Feb 21, 2026. It is now read-only.

Commit c1e076e

Browse files
bcardosolopesTommy McMichen
andcommitted
[CIR] Add copy-to-move optimization pass
Add an optimization pass that transforms copy constructors and copy assignment operators into their move equivalents when the source object is not used after the copy. Uses points-to and live object analysis to verify safety. Includes the -fclangir-move-opt driver flag. Co-authored-by: Tommy McMichen <tommymcmichen@fb.com>
1 parent 1282ab8 commit c1e076e

File tree

14 files changed

+433
-8
lines changed

14 files changed

+433
-8
lines changed

clang/include/clang/CIR/CIRToCIRPasses.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ mlir::LogicalResult runCIRToCIRPasses(
3434
llvm::StringRef lifetimeOpts, bool enableIdiomRecognizer,
3535
llvm::StringRef idiomRecognizerOpts, bool enableLibOpt,
3636
llvm::StringRef libOptOpts, std::string &passOptParsingFailure,
37-
bool enableCIRSimplify, bool flattenCIR, bool throughMLIR,
38-
bool enableCallConvLowering, bool enableMem2reg);
37+
bool enableCIRSimplify, bool enableCIRMoveOpt, bool flattenCIR,
38+
bool throughMLIR, bool enableCallConvLowering, bool enableMem2reg);
3939

4040
} // namespace cir
4141

clang/include/clang/CIR/Dialect/Passes.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ std::unique_ptr<Pass> createLibOptPass(clang::ASTContext *astCtx);
4141
std::unique_ptr<Pass> createFlattenCFGPass();
4242
std::unique_ptr<Pass> createHoistAllocasPass();
4343
std::unique_ptr<Pass> createGotoSolverPass();
44+
std::unique_ptr<Pass> createMoveOptPass();
45+
std::unique_ptr<Pass> createMoveOptPass(clang::ASTContext *astCtx);
4446

4547
/// Create a pass to expand ABI-dependent types and operations.
4648
std::unique_ptr<Pass> createABILoweringPass();

clang/include/clang/CIR/Dialect/Passes.td

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def SCFPrepare : Pass<"cir-mlir-scf-prepare"> {
109109

110110
def HoistAllocas : Pass<"cir-hoist-allocas"> {
111111
let summary = "Hoist allocas to the entry of the function";
112-
let description = [{
112+
let description = [{
113113
This pass hoist all non-dynamic allocas to the entry of the function.
114114
This is helpful for later code generation.
115115
}];
@@ -159,7 +159,7 @@ def PointsToDiagnostic : Pass<"cir-points-to-diagnostics"> {
159159
def LiveObjectDiagnostic : Pass<"cir-live-object-diagnostics"> {
160160
let summary = "Dump live-object information";
161161
let description = [{
162-
Dump live-object information for all reads/writes in the function.
162+
Dump live-object information for all reads/writes in each function.
163163
}];
164164
let constructor = "mlir::createLiveObjectDiagnosticPass()";
165165
let dependentDialects = ["cir::CIRDialect"];
@@ -225,6 +225,27 @@ def ABILowering : Pass<"cir-abi-lowering"> {
225225
let dependentDialects = ["cir::CIRDialect"];
226226
}
227227

228+
def MoveOpt : Pass<"cir-move-opt"> {
229+
let summary = "Optimize C/C++ move semantics";
230+
let description = [{
231+
This pass optimizes C++ move semantics by using higher level information
232+
about object lifetimes and value categories. It depends on CIR points-to
233+
analysis and live-object analysis to determine when the optimization is
234+
safe to perform.
235+
This pass is not currently exception safe, it may cause an exception to be
236+
thrown, not thrown, or thrown in a different location from the original
237+
program behavior. It also assumes that custom copy/move constructors and
238+
assignment operators perform their intended purpose, i.e., that they solely
239+
copy or move the given object into this object.
240+
For these reasons, this pass should be disabled by default.
241+
This pass can be improved to support conditional moves. This pass can be
242+
further improved by extending the points-to and live-object analyses to be
243+
field-sensitive, path-sensitive, interprocedural, and context-sensitive.
244+
}];
245+
let constructor = "mlir::createMoveOptPass()";
246+
let dependentDialects = ["cir::CIRDialect"];
247+
}
248+
228249
def CallConvLowering : Pass<"cir-call-conv-lowering"> {
229250
let summary = "Handle calling conventions for CIR functions";
230251
let description = [{

clang/include/clang/Driver/Options.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3207,6 +3207,10 @@ def fclangir_mem2reg : Flag<["-"], "fclangir-mem2reg">,
32073207
Visibility<[ClangOption, CC1Option]>, Group<f_Group>,
32083208
HelpText<"Enable mem2reg on the flat ClangIR">,
32093209
MarshallingInfoFlag<FrontendOpts<"ClangIREnableMem2Reg">>;
3210+
def fclangir_move_opt : Flag<["-"], "fclangir-move-opt">,
3211+
Visibility<[ClangOption, CC1Option]>, Group<f_Group>,
3212+
HelpText<"Enable move semantic optimizations on ClangIR">,
3213+
MarshallingInfoFlag<FrontendOpts<"ClangIREnableMoveOpt">>;
32103214

32113215
def fstd_lib_stats : Flag<["-"], "fstd-lib-stats">,
32123216
Visibility<[ClangOption, CC1Option]>, Group<f_Group>,

clang/include/clang/Frontend/FrontendOptions.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,9 @@ class FrontendOptions {
440440
// Enable Clang IR mem2reg pass on the flat CIR.
441441
unsigned ClangIREnableMem2Reg : 1;
442442

443+
// Enable Clang IR std::move optimizations.
444+
unsigned ClangIREnableMoveOpt : 1;
445+
443446
// Enable Clang IR analysis only pipeline that uses traditional codegen
444447
// pipeline.
445448
unsigned ClangIRAnalysisOnly : 1;
@@ -591,7 +594,8 @@ class FrontendOptions {
591594
ClangIRVerifyDiags(false), ClangIRLifetimeCheck(false),
592595
ClangIRIdiomRecognizer(false), ClangIRLibOpt(false),
593596
ClangIRCallConvLowering(true), ClangIREnableMem2Reg(false),
594-
ClangIRAnalysisOnly(false), EmitClangIRFile(false),
597+
ClangIREnableMoveOpt(false), ClangIRAnalysisOnly(false),
598+
EmitClangIRFile(false), StdLibStats(false),
595599
TimeTraceGranularity(500), TimeTraceVerbose(false) {}
596600

597601
/// getInputKindForExtension - Return the appropriate input kind for a file

clang/lib/CIR/CodeGen/CIRPasses.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ mlir::LogicalResult runCIRToCIRPasses(
2828
llvm::StringRef lifetimeOpts, bool enableIdiomRecognizer,
2929
llvm::StringRef idiomRecognizerOpts, bool enableLibOpt,
3030
llvm::StringRef libOptOpts, std::string &passOptParsingFailure,
31-
bool enableCIRSimplify, bool flattenCIR, bool throughMLIR,
32-
bool enableCallConvLowering, bool enableMem2Reg) {
31+
bool enableCIRSimplify, bool enableCIRMoveOpt, bool flattenCIR,
32+
bool throughMLIR, bool enableCallConvLowering, bool enableMem2Reg) {
3333

3434
llvm::TimeTraceScope scope("CIR To CIR Passes");
3535

@@ -70,6 +70,9 @@ mlir::LogicalResult runCIRToCIRPasses(
7070
pm.addPass(std::move(libOpPass));
7171
}
7272

73+
if (enableCIRMoveOpt)
74+
pm.addPass(mlir::createMoveOptPass(&astContext));
75+
7376
if (enableCIRSimplify)
7477
pm.addPass(mlir::createCIRSimplifyPass());
7578

clang/lib/CIR/Dialect/Transforms/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ add_clang_library(MLIRCIRTransforms
1717
HoistAllocas.cpp
1818
PointsToAnalysis.cpp
1919
LiveObjectAnalysis.cpp
20+
MoveOpt.cpp
2021
StdLibStatistics.cpp
2122

2223
DEPENDS
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
//===- MoveOpt.cpp - optimize copies to moves -----------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "MoveOpt.h"
10+
#include "clang/CIR/Dialect/Builder/CIRBaseBuilder.h"
11+
12+
// MoveOptPass method implementations
13+
bool MoveOptPass::isCopy(cir::CallOp call) {
14+
// Get the method decl of the callee.
15+
auto calleeFunc = call.getDirectCallee(theModule);
16+
auto methodDecl = mlir::dyn_cast_if_present<cir::ASTCXXMethodDeclInterface>(
17+
calleeFunc.getAstAttr());
18+
if (!methodDecl)
19+
return false;
20+
21+
// Check if this is the copy constructor.
22+
if (auto ctorDecl =
23+
mlir::dyn_cast<cir::ASTCXXConstructorDeclInterface>(methodDecl)) {
24+
return ctorDecl.isCopyConstructor();
25+
}
26+
27+
// Check if this is the copy assignment operator.
28+
return methodDecl.isCopyAssignmentOperator();
29+
}
30+
31+
bool MoveOptPass::isLastUse(cir::CallOp call, PointsToAnalysis &pta,
32+
LiveObjectAnalysis &liveObjects) {
33+
// Get the pointer being moved.
34+
auto &ptrUse = call->getOpOperand(1);
35+
36+
// Unpack the pointer use.
37+
auto ptr = ptrUse.get();
38+
39+
// Get the set of objects that could be used.
40+
const auto &pointsTo = pta.valPointsTo[ptr];
41+
42+
if (pointsTo.contains(pta.unknown())) {
43+
call->emitRemark("move opt: copied object may be unknown");
44+
return false;
45+
}
46+
47+
// Have any of the pointees escaped out?
48+
const auto &escaped = pta.memPointsTo[pta.unknown()];
49+
for (auto obj : pointsTo) {
50+
if (escaped.contains(obj)) {
51+
call->emitRemark("move opt: copied object may have escaped");
52+
return false;
53+
}
54+
}
55+
56+
// Query the memory analysis.
57+
if (!liveObjects.isLastUse(ptrUse)) {
58+
call->emitRemark("move opt: copied object is alive after use");
59+
return false;
60+
}
61+
62+
return true;
63+
}
64+
65+
cir::ASTFunctionDeclInterface
66+
MoveOptPass::getMoveDecl(cir::ASTCXXMethodDeclInterface copyDecl) {
67+
auto recordDecl = copyDecl.getParent();
68+
if (!recordDecl)
69+
return {};
70+
71+
// Find the matching move constructor.
72+
if (isa<ASTCXXConstructorDeclInterface>(copyDecl))
73+
return recordDecl.getMoveConstructor();
74+
75+
// Find the matching move assignment operator.
76+
return recordDecl.getMoveAssignmentOperator();
77+
}
78+
79+
cir::FuncOp MoveOptPass::getMoveFuncFor(cir::CallOp call) {
80+
// Get the method callee.
81+
auto copyFunc = call.getDirectCallee(theModule);
82+
if (!copyFunc)
83+
return {};
84+
85+
auto copyDecl = mlir::dyn_cast_if_present<cir::ASTCXXMethodDeclInterface>(
86+
copyFunc.getAstAttr());
87+
if (!copyDecl)
88+
return {};
89+
90+
// Convert the copy into a move within the record..
91+
auto moveDecl = getMoveDecl(copyDecl);
92+
if (!moveDecl)
93+
return {};
94+
95+
// Get the move function from the module, if it exists.
96+
auto moveFuncName = moveDecl.getMangledName();
97+
mlir::Operation *moveFuncGlobal =
98+
mlir::SymbolTable::lookupSymbolIn(theModule, moveFuncName);
99+
100+
auto moveFunc = mlir::dyn_cast_if_present<cir::FuncOp>(moveFuncGlobal);
101+
if (!moveFunc)
102+
return {};
103+
104+
return moveFunc;
105+
}
106+
107+
bool MoveOptPass::isApplicable(cir::CallOp call, PointsToAnalysis &pta,
108+
LiveObjectAnalysis &liveObjects) {
109+
return isCopy(call) && getMoveFuncFor(call) &&
110+
isLastUse(call, pta, liveObjects);
111+
}
112+
113+
void MoveOptPass::transform(cir::CallOp call) {
114+
// Get the move function for this call.
115+
auto moveFunc = getMoveFuncFor(call);
116+
assert(moveFunc && "Missing move function");
117+
118+
// Replace the copy with a move.
119+
call.setCallee(moveFunc.getName());
120+
121+
call.emitRemark("move opt: transformed copy into move");
122+
}
123+
124+
void MoveOptPass::runOnOperation() {
125+
assert(astCtx && "Missing ASTContext, please construct with the right ctor");
126+
127+
mlir::Operation *op = getOperation();
128+
129+
// Record the current module as we go.
130+
if (mlir::isa<mlir::ModuleOp>(op))
131+
theModule = mlir::cast<mlir::ModuleOp>(op);
132+
133+
// Run the live object analysis.
134+
auto pta = getAnalysis<PointsToAnalysis>();
135+
auto liveObjects = getAnalysis<LiveObjectAnalysis>();
136+
137+
// Collect all move constructors.
138+
llvm::SmallVector<cir::CallOp> copies;
139+
op->walk([&](cir::CallOp call) {
140+
if (isApplicable(call, pta, liveObjects))
141+
copies.push_back(call);
142+
});
143+
144+
if (copies.empty())
145+
return;
146+
147+
// Transform each of the moves, if applicable.
148+
for (auto copy : copies)
149+
transform(copy);
150+
}
151+
152+
void MoveOptPass::setASTContext(clang::ASTContext *astCtx) {
153+
this->astCtx = astCtx;
154+
}
155+
156+
std::unique_ptr<Pass> mlir::createMoveOptPass() {
157+
auto pass = std::make_unique<MoveOptPass>();
158+
return std::move(pass);
159+
}
160+
161+
std::unique_ptr<Pass> mlir::createMoveOptPass(clang::ASTContext *astCtx) {
162+
auto pass = std::make_unique<MoveOptPass>();
163+
pass->setASTContext(astCtx);
164+
return std::move(pass);
165+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
//===- MoveOpt.h - optimize copies to moves -------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLVM_CLANG_LIB_CIR_DIALECT_TRANSFORMS_MOVEOPT_H
10+
#define LLVM_CLANG_LIB_CIR_DIALECT_TRANSFORMS_MOVEOPT_H
11+
12+
#include "LiveObjectAnalysis.h"
13+
#include "PassDetail.h"
14+
#include "mlir/Analysis/AliasAnalysis.h"
15+
#include "mlir/Analysis/AliasAnalysis/LocalAliasAnalysis.h"
16+
#include "mlir/Analysis/DataFlowFramework.h"
17+
#include "clang/AST/ASTContext.h"
18+
#include "clang/AST/Attr.h"
19+
#include "clang/AST/DeclCXX.h"
20+
#include "clang/CIR/Dialect/IR/CIRDialect.h"
21+
#include "clang/CIR/Dialect/Passes.h"
22+
#include "clang/CIR/Interfaces/ASTAttrInterfaces.h"
23+
#include "clang/CIR/Interfaces/CIROpInterfaces.h"
24+
#include "llvm/ADT/DenseMap.h"
25+
#include "llvm/ADT/SetVector.h"
26+
#include "llvm/ADT/SmallSet.h"
27+
28+
using namespace cir;
29+
using namespace mlir;
30+
31+
// Move optimization pass class
32+
struct MoveOptPass : public MoveOptBase<MoveOptPass> {
33+
protected:
34+
clang::ASTContext *astCtx = nullptr;
35+
mlir::ModuleOp theModule;
36+
mlir::AliasAnalysis *aliasAnalysis;
37+
38+
public:
39+
MoveOptPass() = default;
40+
41+
bool isCopy(cir::CallOp call);
42+
bool isLastUse(cir::CallOp call, PointsToAnalysis &pta,
43+
LiveObjectAnalysis &liveObjects);
44+
static cir::ASTFunctionDeclInterface
45+
getMoveDecl(cir::ASTCXXMethodDeclInterface copyDecl);
46+
cir::FuncOp getMoveFuncFor(cir::CallOp call);
47+
bool isApplicable(cir::CallOp call, PointsToAnalysis &pta,
48+
LiveObjectAnalysis &liveObjects);
49+
void transform(cir::CallOp call);
50+
void runOnOperation() override final;
51+
void setASTContext(clang::ASTContext *astCtx);
52+
};
53+
54+
// Factory function declarations
55+
std::unique_ptr<Pass> mlir::createMoveOptPass();
56+
std::unique_ptr<Pass> mlir::createMoveOptPass(clang::ASTContext *astCtx);
57+
58+
#endif // LLVM_CLANG_LIB_CIR_DIALECT_TRANSFORMS_MOVEOPT_H

clang/lib/CIR/FrontendAction/CIRGenAction.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,8 @@ class CIRGenConsumer : public clang::ASTConsumer {
240240
FeOptions.ClangIRLifetimeCheck, LifetimeOpts,
241241
FeOptions.ClangIRIdiomRecognizer, IdiomRecognizerOpts,
242242
FeOptions.ClangIRLibOpt, LibOptOpts, PassOptParsingFailure,
243-
CodeGenOpts.OptimizationLevel > 0, FlattenCir,
243+
CodeGenOpts.OptimizationLevel > 0,
244+
FeOptions.ClangIREnableMoveOpt, FlattenCir,
244245
!FeOptions.ClangIRDirectLowering, EnableCcLowering,
245246
FeOptions.ClangIREnableMem2Reg)
246247
.failed()) {

0 commit comments

Comments
 (0)