Author: Aaron Jomy Date: 2026-10-23T08:22:53+03:00 New Revision: aa6e40c073e669c0a79250ca4333e48b9d274831
URL: https://github.com/llvm/llvm-project/commit/aa6e40c073e669c0a79250ca4333e48b9d274831 DIFF: https://github.com/llvm/llvm-project/commit/aa6e40c073e669c0a79250ca4333e48b9d274831.diff LOG: [clang-repl][CodeGen] Add setOptLevel, #pragma clang repl optimize(N) (#214793) clang-repl fixes its CodeGenOptions when the interpreter is created, so the optimization level is the same for every input. This adds a way to change it, similar to Cling: - Add a new `CodeGenerator::StartModule` overload that takes a CodeGenOptions, so the incremental path can start a module with different codegen options than the ones fixed at construction. - `Interpreter::setOptLevel` uses it to re-stage the pending module at the new level. - `#pragma clang repl optimize(N)` exposes this in source, the level applies to the input the pragma appears in and stays in effect until changed again. assisted by claude-opus-5-5 Added: clang/test/Interpreter/pragma-optimize-diagnostics.cpp clang/test/Interpreter/pragma-optimize.cpp Modified: clang/include/clang/Interpreter/Interpreter.h clang/lib/Interpreter/Interpreter.cpp clang/unittests/Interpreter/InterpreterTest.cpp Removed: ################################################################################ diff --git a/clang/include/clang/Interpreter/Interpreter.h b/clang/include/clang/Interpreter/Interpreter.h index 64dee1cf80db8d..5228b7fe80bd32 100644 --- a/clang/include/clang/Interpreter/Interpreter.h +++ b/clang/include/clang/Interpreter/Interpreter.h @@ -170,6 +170,11 @@ class Interpreter { llvm::Error Execute(PartialTranslationUnit &T); llvm::Error ParseAndExecute(llvm::StringRef Code, Value *V = nullptr); + /// Set the optimization level and size used to emit subsequently parsed + /// input. \p OptSize mirrors \c CodeGenOptions::OptimizeSize (1 for -Os, + /// 2 for -Oz). + void setOptLevel(unsigned OptLevel, unsigned OptSize = 0); + /// Undo N previous incremental inputs. llvm::Error Undo(unsigned N = 1); diff --git a/clang/lib/Interpreter/Interpreter.cpp b/clang/lib/Interpreter/Interpreter.cpp index a195835f4fe8e2..1fa0456f6b4ee8 100644 --- a/clang/lib/Interpreter/Interpreter.cpp +++ b/clang/lib/Interpreter/Interpreter.cpp @@ -20,6 +20,7 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/Mangle.h" #include "clang/AST/TypeVisitor.h" +#include "clang/Basic/DiagnosticParse.h" #include "clang/Basic/DiagnosticSema.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/TargetInfo.h" @@ -38,6 +39,8 @@ #include "clang/Interpreter/IncrementalExecutor.h" #include "clang/Interpreter/Interpreter.h" #include "clang/Interpreter/Value.h" +#include "clang/Lex/Pragma.h" +#include "clang/Lex/Preprocessor.h" #include "clang/Lex/PreprocessorOptions.h" #include "clang/Options/OptionUtils.h" #include "clang/Options/Options.h" @@ -46,6 +49,7 @@ #include "clang/Serialization/ModuleCache.h" #include "clang/Serialization/ObjectFilePCHContainerReader.h" #include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringSwitch.h" #include "llvm/ExecutionEngine/JITSymbol.h" #include "llvm/ExecutionEngine/Orc/EPCDynamicLibrarySearchGenerator.h" #include "llvm/ExecutionEngine/Orc/LLJIT.h" @@ -231,6 +235,70 @@ static llvm::Error ExecuteIncrementalAction(CompilerInstance &CI, return llvm::Error::success(); } +// '#pragma clang repl optimize(<opt>)' sets the optimization level and size +// used to emit modules for input parsed after the pragma. <opt> is an -O flag +// spelled without the leading dash: O0, O1, O2, O3, Os or Oz. +struct PragmaReplHandler : public PragmaHandler { + Interpreter &Interp; + + PragmaReplHandler(Interpreter &Interp) + : PragmaHandler("repl"), Interp(Interp) {} + + void HandlePragma(Preprocessor &PP, PragmaIntroducer Introducer, + Token &FirstToken) override { + Token Tok; + PP.LexUnexpandedToken(Tok); + if (Tok.isNot(tok::identifier) || + !Tok.getIdentifierInfo()->isStr("optimize")) { + PP.Diag(Tok.getLocation(), diag::warn_pragma_expected_identifier) + << "clang repl"; + return; + } + + PP.LexUnexpandedToken(Tok); + if (Tok.isNot(tok::l_paren)) { + PP.Diag(Tok.getLocation(), diag::warn_pragma_expected_lparen) + << "clang repl optimize"; + return; + } + + PP.LexUnexpandedToken(Tok); + std::string Opt = PP.getSpelling(Tok); + // The OptimizationLevel and OptimizeSize that each -O flag sets. + std::optional<std::pair<unsigned, unsigned>> Level = + llvm::StringSwitch<std::optional<std::pair<unsigned, unsigned>>>(Opt) + .Case("O0", std::pair(0U, 0U)) + .Case("O1", std::pair(1U, 0U)) + .Case("O2", std::pair(2U, 0U)) + .Case("O3", std::pair(3U, 0U)) + .Case("Os", std::pair(2U, 1U)) + .Case("Oz", std::pair(2U, 2U)) + .Default(std::nullopt); + if (!Level) { + PP.Diag(Tok.getLocation(), diag::warn_pragma_invalid_argument) + << Opt << "clang repl optimize" << /*Expected=*/true + << "'O0', 'O1', 'O2', 'O3', 'Os' or 'Oz'"; + return; + } + + PP.LexUnexpandedToken(Tok); + if (Tok.isNot(tok::r_paren)) { + PP.Diag(Tok.getLocation(), diag::warn_pragma_expected_rparen) + << "clang repl optimize"; + return; + } + + PP.LexUnexpandedToken(Tok); + if (Tok.isNot(tok::eod)) { + PP.Diag(Tok.getLocation(), diag::warn_pragma_extra_tokens_at_eol) + << "clang repl optimize"; + return; + } + + Interp.setOptLevel(Level->first, Level->second); + } +}; + } // anonymous namespace namespace clang { @@ -397,6 +465,8 @@ Interpreter::Interpreter(std::unique_ptr<CompilerInstance> Instance, if (ErrOut) return; + CI->getPreprocessor().AddPragmaHandler("clang", new PragmaReplHandler(*this)); + if (Act->getCodeGen()) { Act->CacheCodeGenModule(); // The initial PTU is filled by `-include`/`-include-pch` or by CUDA @@ -575,6 +645,14 @@ llvm::Expected<IncrementalExecutor &> Interpreter::getExecutionEngine() { return *IncrExecutor.get(); } +void Interpreter::setOptLevel(unsigned OptLevel, unsigned OptSize) { + CodeGenOptions &CGO = getCompilerInstance()->getCodeGenOpts(); + CGO.OptimizationLevel = OptLevel; + CGO.OptimizeSize = OptSize; + CGO.setInlining(OptLevel ? CodeGenOptions::NormalInlining + : CodeGenOptions::OnlyAlwaysInlining); +} + ASTContext &Interpreter::getASTContext() { return getCompilerInstance()->getASTContext(); } diff --git a/clang/test/Interpreter/pragma-optimize-diagnostics.cpp b/clang/test/Interpreter/pragma-optimize-diagnostics.cpp new file mode 100644 index 00000000000000..c091dd737e04a4 --- /dev/null +++ b/clang/test/Interpreter/pragma-optimize-diagnostics.cpp @@ -0,0 +1,20 @@ +// REQUIRES: host-supports-jit +// +// RUN: cat %s | clang-repl -Xcc -Xclang -Xcc -verify + +#pragma clang repl optimize(O0) +#pragma clang repl optimize(O1) +#pragma clang repl optimize(O2) +#pragma clang repl optimize(O3) +#pragma clang repl optimize(Os) +#pragma clang repl optimize(Oz) + +#pragma clang repl optimise // expected-warning {{expected identifier in '#pragma clang repl' - ignored}} +#pragma clang repl optimize // expected-warning {{missing '(' after '#pragma clang repl optimize' - ignoring}} +#pragma clang repl optimize(2) // expected-warning {{unexpected argument '2' to '#pragma clang repl optimize'; expected 'O0', 'O1', 'O2', 'O3', 'Os' or 'Oz'}} +#pragma clang repl optimize(foo) // expected-warning {{unexpected argument 'foo' to '#pragma clang repl optimize'}} +#pragma clang repl optimize(O4) // expected-warning {{unexpected argument 'O4' to '#pragma clang repl optimize'}} +#pragma clang repl optimize(Og) // expected-warning {{unexpected argument 'Og' to '#pragma clang repl optimize'}} +#pragma clang repl optimize(Ofast) // expected-warning {{unexpected argument 'Ofast' to '#pragma clang repl optimize'}} +#pragma clang repl optimize(O2 // expected-warning {{missing ')' after '#pragma clang repl optimize' - ignoring}} +#pragma clang repl optimize(O2) extra // expected-warning {{extra tokens at end of '#pragma clang repl optimize' - ignored}} diff --git a/clang/test/Interpreter/pragma-optimize.cpp b/clang/test/Interpreter/pragma-optimize.cpp new file mode 100644 index 00000000000000..1d43c54e8edb42 --- /dev/null +++ b/clang/test/Interpreter/pragma-optimize.cpp @@ -0,0 +1,50 @@ +// REQUIRES: host-supports-jit +// +// '#pragma clang repl optimize(<opt>)' sets the optimization level and size for +// input parsed after it; <opt> is an -O flag spelled without the leading dash. +// +// RUN: cat %s | clang-repl -Xcc -Xclang -Xcc -emit-llvm | FileCheck %s + +extern "C" int f_o0(int x) { return x; } +// CHECK: ; Function Attrs: {{.*}}noinline {{.*}}optnone +// CHECK-NEXT: define {{.*}}i32 @f_o0( +// CHECK-NEXT: entry: +// CHECK-NEXT: %x.addr = alloca i32 + +#pragma clang repl optimize(O2) +extern "C" int f_o2(int x) { return x; } +// CHECK: ; Function Attrs: +// CHECK-NOT: {{minsize|noinline|optnone|optsize}} +// CHECK-SAME: {{$}} +// CHECK-NEXT: define {{.*}}i32 @f_o2( +// CHECK-NEXT: entry: +// CHECK-NEXT: ret i32 %x + +#pragma clang repl optimize(Os) +extern "C" int f_os(int x) { return x; } +// CHECK: ; Function Attrs: +// CHECK-NOT: {{minsize|noinline|optnone}} +// CHECK-SAME: optsize +// CHECK-NEXT: define {{.*}}i32 @f_os( +// CHECK-NEXT: entry: +// CHECK-NEXT: ret i32 %x + +#pragma clang repl optimize(Oz) +extern "C" int f_oz(int x) { return x; } +// CHECK: ; Function Attrs: {{.*}}minsize +// CHECK-NOT: {{noinline|optnone}} +// CHECK-SAME: optsize +// CHECK-NEXT: define {{.*}}i32 @f_oz( +// CHECK-NEXT: entry: +// CHECK-NEXT: ret i32 %x + +#pragma clang repl optimize(O0) +extern "C" int f_back(int x) { return x; } +// CHECK: ; Function Attrs: +// CHECK-NOT: minsize +// CHECK-SAME: noinline {{.*}}optnone +// CHECK-NOT: optsize +// CHECK-SAME: {{$}} +// CHECK-NEXT: define {{.*}}i32 @f_back( +// CHECK-NEXT: entry: +// CHECK-NEXT: %x.addr = alloca i32 diff --git a/clang/unittests/Interpreter/InterpreterTest.cpp b/clang/unittests/Interpreter/InterpreterTest.cpp index 17cc6fc9b6ca8e..41c1facc8f2f08 100644 --- a/clang/unittests/Interpreter/InterpreterTest.cpp +++ b/clang/unittests/Interpreter/InterpreterTest.cpp @@ -22,6 +22,8 @@ #include "clang/Sema/Lookup.h" #include "clang/Sema/Sema.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/Module.h" #include "llvm/TargetParser/Host.h" #include "gmock/gmock.h" @@ -574,6 +576,81 @@ TEST_F(InterpreterTest, ValueMoveSemantics) { Interp.reset(); } +TEST_F(InterpreterTest, SetOptLevel) { + std::unique_ptr<Interpreter> Interp = createInterpreter(); + + // -O0 emits functions with the 'optnone' attribute. + auto &PTU0 = cantFail(Interp->Parse("extern \"C\" int f0() { return 0; }")); + llvm::Function *F0 = PTU0.TheModule->getFunction("f0"); + ASSERT_NE(F0, nullptr); + EXPECT_TRUE(F0->hasFnAttribute(llvm::Attribute::OptimizeNone)); + + // A change only takes effect on the next parsed module. + Interp->setOptLevel(2); + + auto &PTU1 = cantFail(Interp->Parse("extern \"C\" int f1() { return 1; }")); + llvm::Function *F1 = PTU1.TheModule->getFunction("f1"); + ASSERT_NE(F1, nullptr); + EXPECT_FALSE(F1->hasFnAttribute(llvm::Attribute::OptimizeNone)); + EXPECT_FALSE(F1->hasFnAttribute(llvm::Attribute::NoInline)); + + // -Oz is level 2, size 2. + Interp->setOptLevel(2, 2); + + auto &PTU2 = cantFail(Interp->Parse("extern \"C\" int f2() { return 2; }")); + llvm::Function *F2 = PTU2.TheModule->getFunction("f2"); + ASSERT_NE(F2, nullptr); + EXPECT_TRUE(F2->hasFnAttribute(llvm::Attribute::OptimizeForSize)); + EXPECT_TRUE(F2->hasFnAttribute(llvm::Attribute::MinSize)); +} + +TEST_F(InterpreterTest, PragmaOptimizeLevel) { + std::unique_ptr<Interpreter> Interp = createInterpreter(); + + auto HasAttr = [](PartialTranslationUnit &PTU, const char *Name, + llvm::Attribute::AttrKind Kind) { + llvm::Function *F = PTU.TheModule->getFunction(Name); + EXPECT_NE(F, nullptr); + return F && F->hasFnAttribute(Kind); + }; + + // Default -O0: 'optnone' present. + auto &PTU0 = cantFail(Interp->Parse("extern \"C\" int f0() { return 0; }")); + EXPECT_TRUE(HasAttr(PTU0, "f0", llvm::Attribute::OptimizeNone)); + + // The pragma applies to the code that follows it; f1 is emitted at -O2. + auto &PTU1 = cantFail(Interp->Parse("#pragma clang repl optimize(O2)\n" + "extern \"C\" int f1() { return 1; }")); + EXPECT_FALSE(HasAttr(PTU1, "f1", llvm::Attribute::OptimizeNone)); + EXPECT_FALSE(HasAttr(PTU1, "f1", llvm::Attribute::NoInline)); + + // The level is sticky across inputs. + auto &PTU2 = cantFail(Interp->Parse("extern \"C\" int f2() { return 2; }")); + EXPECT_FALSE(HasAttr(PTU2, "f2", llvm::Attribute::OptimizeNone)); + + // -Os sets 'optsize' only; -Oz additionally sets 'minsize'. + auto &PTU3 = cantFail(Interp->Parse("#pragma clang repl optimize(Os)\n" + "extern \"C\" int f3() { return 3; }")); + EXPECT_TRUE(HasAttr(PTU3, "f3", llvm::Attribute::OptimizeForSize)); + EXPECT_FALSE(HasAttr(PTU3, "f3", llvm::Attribute::MinSize)); + + auto &PTU4 = cantFail(Interp->Parse("#pragma clang repl optimize(Oz)\n" + "extern \"C\" int f4() { return 4; }")); + EXPECT_TRUE(HasAttr(PTU4, "f4", llvm::Attribute::MinSize)); + + // A lower level can be selected again, not just a higher one. + auto &PTU5 = cantFail(Interp->Parse("#pragma clang repl optimize(O0)\n" + "extern \"C\" int f5() { return 5; }")); + EXPECT_TRUE(HasAttr(PTU5, "f5", llvm::Attribute::OptimizeNone)); + EXPECT_FALSE(HasAttr(PTU5, "f5", llvm::Attribute::OptimizeForSize)); + + // Code parsed ahead of the pragma stays in the module of its input. + auto &PTU6 = cantFail(Interp->Parse("extern \"C\" int f6() { return 6; }\n" + "extern \"C\" int f7() { return 7; }\n" + "#pragma clang repl optimize(O2)\n")); + EXPECT_TRUE(HasAttr(PTU6, "f6", llvm::Attribute::OptimizeNone)); +} + TEST_F(InterpreterTest, TranslationUnit_CanonicalDecl) { std::vector<const char *> Args; std::unique_ptr<Interpreter> Interp = createInterpreter(Args); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
