https://github.com/aaronj0 updated https://github.com/llvm/llvm-project/pull/214793
>From 42c04c07ca43570a2f57bacd43acff5c97ef18fb Mon Sep 17 00:00:00 2001 From: Aaron Jomy <[email protected]> Date: Thu, 22 Oct 2026 23:55:57 +0200 Subject: [PATCH 1/2] [clang-repl] Add Interpreter::setOptLevel clang-repl fixes the optimization level when the interpreter is created and uses it for every PTU. Add Interpreter::setOptLevel. It sets OptimizationLevel, OptimizeSize and the inlining mode in the CompilerInvocation's CodeGenOptions, which CodeGen and the backend hold by reference. Code emitted after the call gets the new level. The unittest switches the level between inputs and checks the attributes of the emitted functions. --- clang/include/clang/Interpreter/Interpreter.h | 5 ++++ clang/lib/Interpreter/Interpreter.cpp | 8 +++++ .../unittests/Interpreter/InterpreterTest.cpp | 30 +++++++++++++++++++ 3 files changed, 43 insertions(+) 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..c9159ffeda32e4 100644 --- a/clang/lib/Interpreter/Interpreter.cpp +++ b/clang/lib/Interpreter/Interpreter.cpp @@ -575,6 +575,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/unittests/Interpreter/InterpreterTest.cpp b/clang/unittests/Interpreter/InterpreterTest.cpp index 17cc6fc9b6ca8e..17e00409558bef 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,34 @@ 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, TranslationUnit_CanonicalDecl) { std::vector<const char *> Args; std::unique_ptr<Interpreter> Interp = createInterpreter(Args); >From 3531dbc63909ea67fcef779fce5432ea9b3cae18 Mon Sep 17 00:00:00 2001 From: Aaron Jomy <[email protected]> Date: Thu, 22 Oct 2026 23:55:58 +0200 Subject: [PATCH 2/2] [clang-repl] Add #pragma clang repl optimize(<opt>) Cling can change the optimization level per input with '#pragma cling optimize'. The clang-repl pragma calls Interpreter::setOptLevel for O0, O1, O2, O3, Os and Oz and stays in effect until the next one. The Preprocessor owns the handler. Add a unittest for the pragma, a clang-repl test that switches from O0 to O2, Os, Oz and back and checks the attributes and the emitted bodies, and a test for the pragma's diagnostics. --- clang/lib/Interpreter/Interpreter.cpp | 70 +++++++++++++++++++ .../pragma-optimize-diagnostics.cpp | 20 ++++++ clang/test/Interpreter/pragma-optimize.cpp | 50 +++++++++++++ .../unittests/Interpreter/InterpreterTest.cpp | 47 +++++++++++++ 4 files changed, 187 insertions(+) create mode 100644 clang/test/Interpreter/pragma-optimize-diagnostics.cpp create mode 100644 clang/test/Interpreter/pragma-optimize.cpp diff --git a/clang/lib/Interpreter/Interpreter.cpp b/clang/lib/Interpreter/Interpreter.cpp index c9159ffeda32e4..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 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 17e00409558bef..41c1facc8f2f08 100644 --- a/clang/unittests/Interpreter/InterpreterTest.cpp +++ b/clang/unittests/Interpreter/InterpreterTest.cpp @@ -604,6 +604,53 @@ TEST_F(InterpreterTest, SetOptLevel) { 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
