[clang] [clang-repl] Fix module contamination after failed template instantiation (PR #204152)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Jun 16 06:37:59 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Anutosh Bhat (anutosh491)
<details>
<summary>Changes</summary>
Fixes https://github.com/compiler-research/xeus-cpp/issues/431
When a function template is instantiated in clang-repl and the instantiation fails (e.g. a `static_assert` in the body), the session becomes poisoned for the rest of the interaction. Any subsequent cell — even completely unrelated ones like `int x = 10;` — would fail with a JIT "Symbols not found" error.
The root cause is a timing issue between when codegen emits IR and when the error actually fires.
When you call something like `f(1.0)` where f is a non-constexpr function template, clang doesn't instantiate the body immediately. It queues the instantiation as "deferred" and the parse loop moves on. At this point, codegen has already emitted a call instruction to `f<double>` into the current LLVM module — because the call expression was processed before any error fired.
The instantiation happens later, inside `ParseTopLevelDecl` when it hits `end-of-input `and calls `ActOnEndOfTranslationUnit` → `PerformPendingInstantiations`. That's where the `static_assert` fires. By then, the `InProcessPrintingASTConsumer `guard (`if (Diags.hasErrorOccurred())` return early) kicks in and the function body never reaches CodeGen. So the module ends up with a call to a symbol that has no definition.
I dumped Modules while executing code to show the same
```
clang-repl> #include <type_traits>
clang-repl> #include <iostream>
clang-repl> template<typename T> T integral_div(T a, T b) {
static_assert(std::is_integral<T>::value, "not an integral type");
return a / b; }
clang-repl>
clang-repl> integral_div<double>(8.0, 2.0)
In file included from <<< inputs >>>:1:
input_line_3:1:78: error: static assertion failed due to requirement 'std::is_integral<double>::value':
not an integral type
1 | ...integral_div(T a, T b) { static_assert(std::is_integral<T>::value, "not a...
| ^~~~~~~~~~~~~~~~~~~~~~~~~~
input_line_4:1:1: note: in instantiation of function template specialization 'integral_div<double>'
requested here
1 | integral_div<double>(8.0, 2.0)
| ^
[clang-repl] parse error: module IR before cleanup ---
; ModuleID = 'incr_module_4'
source_filename = "incr_module_4"
target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-n32:64-S128-Fn32"
target triple = "arm64-apple-macosx26.0.0"
define internal void @<!-- -->__stmts__0() {
%1 = bitcast i32 poison to i32
%2 = call noundef double @<!-- -->_Z12integral_divIdET_S0_S0_(double noundef 8.000000e+00, double noundef 2.000000e+00)
call void (ptr, ptr, ptr, ...) @<!-- -->__clang_Interpreter_SetValueNoAlloc(ptr noundef inttoptr (i64 4547296960 to ptr), ptr noundef inttoptr (i64 4547297048 to ptr), ptr noundef inttoptr (i64 37670594624 to ptr), double noundef %2)
}
declare void @<!-- -->__clang_Interpreter_SetValueNoAlloc(ptr noundef, ptr noundef, ptr noundef, ...) #<!-- -->0
declare noundef double @<!-- -->_Z12integral_divIdET_S0_S0_(double noundef, double noundef) #<!-- -->0
attributes #<!-- -->0 = { "frame-pointer"="non-leaf-no-reserve" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="apple-m1" "target-features"="+aes,+altnzcv,+ccdp,+ccidx,+ccpp,+complxnum,+crc,+dit,+dotprod,+flagm,+fp-armv8,+fp16fml,+fptoint,+fullfp16,+jsconv,+lse,+neon,+pauth,+perfmon,+predres,+ras,+rcpc,+rdm,+sb,+sha2,+sha3,+specrestrict,+ssbs,+v8.1a,+v8.2a,+v8.3a,+v8.4a,+v8a" "tune-cpu"="apple-m5" }
!llvm.module.flags = !{!0}
!0 = !{i32 2, !"SDK Version", [2 x i32] [i32 26, i32 5]}
[clang-repl] --- end module ---
error: Parsing failed.
clang-repl>
clang-repl>
clang-repl> int x = 10;
[clang-repl] module registered for PTU 4 (incr_module_4) ---
; ModuleID = 'incr_module_4'
source_filename = "incr_module_4"
target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-n32:64-S128-Fn32"
target triple = "arm64-apple-macosx26.0.0"
@<!-- -->x = global i32 10, align 4
@<!-- -->llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @<!-- -->_GLOBAL__sub_I_incr_module_4, ptr null }]
define internal void @<!-- -->__stmts__0() {
%1 = call noundef double @<!-- -->_Z12integral_divIdET_S0_S0_(double noundef 8.000000e+00, double noundef 2.000000e+00)
call void (ptr, ptr, ptr, ...) @<!-- -->__clang_Interpreter_SetValueNoAlloc(ptr noundef inttoptr (i64 4547296960 to ptr), ptr noundef inttoptr (i64 4547297048 to ptr), ptr noundef inttoptr (i64 37670594624 to ptr), double noundef %1)
ret void
}
declare void @<!-- -->__clang_Interpreter_SetValueNoAlloc(ptr noundef, ptr noundef, ptr noundef, ...) #<!-- -->0
declare noundef double @<!-- -->_Z12integral_divIdET_S0_S0_(double noundef, double noundef) #<!-- -->0
; Function Attrs: noinline ssp uwtable(sync)
define internal void @<!-- -->_GLOBAL__sub_I_incr_module_4() #<!-- -->1 section "__TEXT,__StaticInit,regular,pure_instructions" {
call void @<!-- -->__stmts__0()
ret void
}
attributes #<!-- -->0 = { "frame-pointer"="non-leaf-no-reserve" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="apple-m1" "target-features"="+aes,+altnzcv,+ccdp,+ccidx,+ccpp,+complxnum,+crc,+dit,+dotprod,+flagm,+fp-armv8,+fp16fml,+fptoint,+fullfp16,+jsconv,+lse,+neon,+pauth,+perfmon,+predres,+ras,+rcpc,+rdm,+sb,+sha2,+sha3,+specrestrict,+ssbs,+v8.1a,+v8.2a,+v8.3a,+v8.4a,+v8a" "tune-cpu"="apple-m5" }
attributes #<!-- -->1 = { noinline ssp uwtable(sync) "frame-pointer"="non-leaf-no-reserve" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="apple-m1" "target-features"="+aes,+altnzcv,+ccdp,+ccidx,+ccpp,+complxnum,+crc,+dit,+dotprod,+flagm,+fp-armv8,+fp16fml,+fptoint,+fullfp16,+jsconv,+lse,+neon,+pauth,+perfmon,+predres,+ras,+rcpc,+rdm,+sb,+sha2,+sha3,+specrestrict,+ssbs,+v8.1a,+v8.2a,+v8.3a,+v8.4a,+v8a" "tune-cpu"="apple-m5" }
!llvm.module.flags = !{!0, !1, !2, !3}
!llvm.ident = !{!4}
!0 = !{i32 2, !"SDK Version", [2 x i32] [i32 26, i32 5]}
!1 = !{i32 8, !"PIC Level", i32 2}
!2 = !{i32 7, !"uwtable", i32 1}
!3 = !{i32 7, !"frame-pointer", i32 4}
!4 = !{!"clang version 23.0.0git (git@<!-- -->github.com:anutosh491/llvm-project.git 8abb6c1f71112016c9252ebfb19fd388a12b691e)"}
[clang-repl] --- end PTU module ---
JIT session error: Symbols not found: [ __Z12integral_divIdET_S0_S0_ ]
error: Failed to materialize symbols: { (main, { _x, $.incr_module_4.__inits.0, ____orc_init_func.incr_module_4 }) }
```
As can be seen `__stmts__0` carrying `_Z12integral_divIdET_S0_S0_` is also a part of the IR generated from the next cell which is `int x = 10`
---
Full diff: https://github.com/llvm/llvm-project/pull/204152.diff
4 Files Affected:
- (modified) clang/lib/Interpreter/IncrementalAction.cpp (+3-2)
- (modified) clang/lib/Interpreter/IncrementalAction.h (+2)
- (modified) clang/lib/Interpreter/IncrementalParser.cpp (+3-1)
- (added) clang/test/Interpreter/recover-template-instantiation.cpp (+22)
``````````diff
diff --git a/clang/lib/Interpreter/IncrementalAction.cpp b/clang/lib/Interpreter/IncrementalAction.cpp
index d22031c8fa893..d9a7de25af147 100644
--- a/clang/lib/Interpreter/IncrementalAction.cpp
+++ b/clang/lib/Interpreter/IncrementalAction.cpp
@@ -53,7 +53,7 @@ IncrementalAction::IncrementalAction(CompilerInstance &Instance,
}
return Act;
}()),
- Interp(I), CI(Instance), Consumer(std::move(Consumer)) {}
+ Interp(I), CI(Instance), LLVMCtx(LLVMCtx), Consumer(std::move(Consumer)) {}
std::unique_ptr<ASTConsumer>
IncrementalAction::CreateASTConsumer(CompilerInstance & /*CI*/,
@@ -114,7 +114,8 @@ std::unique_ptr<llvm::Module> IncrementalAction::GenModule() {
CachedInCodeGenModule->ifunc_empty())) &&
"CodeGen wrote to a readonly module");
std::unique_ptr<llvm::Module> M(CG->ReleaseModule());
- CG->StartModule("incr_module_" + std::to_string(ID++), M->getContext());
+ CG->StartModule("incr_module_" + std::to_string(ID++),
+ M ? M->getContext() : LLVMCtx);
return M;
}
return nullptr;
diff --git a/clang/lib/Interpreter/IncrementalAction.h b/clang/lib/Interpreter/IncrementalAction.h
index 725cdd0c27cf4..27daf15d8c91e 100644
--- a/clang/lib/Interpreter/IncrementalAction.h
+++ b/clang/lib/Interpreter/IncrementalAction.h
@@ -13,6 +13,7 @@
#include "clang/Frontend/MultiplexConsumer.h"
namespace llvm {
+class LLVMContext;
class Module;
}
@@ -35,6 +36,7 @@ class IncrementalAction : public WrapperFrontendAction {
bool IsTerminating = false;
Interpreter &Interp;
[[maybe_unused]] CompilerInstance &CI;
+ llvm::LLVMContext &LLVMCtx;
std::unique_ptr<ASTConsumer> Consumer;
/// When CodeGen is created the first llvm::Module gets cached in many places
diff --git a/clang/lib/Interpreter/IncrementalParser.cpp b/clang/lib/Interpreter/IncrementalParser.cpp
index f6d2779d64b2b..b3af640f3cb21 100644
--- a/clang/lib/Interpreter/IncrementalParser.cpp
+++ b/clang/lib/Interpreter/IncrementalParser.cpp
@@ -85,7 +85,9 @@ IncrementalParser::ParseOrWrapTopLevelDecl() {
DiagnosticsEngine &Diags = S.getDiagnostics();
if (Diags.hasErrorOccurred()) {
CleanUpPTU(C.getTranslationUnitDecl());
-
+ // Discard any partial CodeGen state to avoid contaminating the next cell.
+ Consumer->HandleTranslationUnit(C);
+ Act->GenModule();
Diags.Reset(/*soft=*/true);
Diags.getClient()->clear();
return llvm::make_error<llvm::StringError>("Parsing failed.",
diff --git a/clang/test/Interpreter/recover-template-instantiation.cpp b/clang/test/Interpreter/recover-template-instantiation.cpp
new file mode 100644
index 0000000000000..0cf8564cc8fe2
--- /dev/null
+++ b/clang/test/Interpreter/recover-template-instantiation.cpp
@@ -0,0 +1,22 @@
+// REQUIRES: host-supports-jit
+// RUN: cat %s | clang-repl 2>&1 | FileCheck %s
+
+// Verify that clang-repl recovers cleanly after a deferred template
+// instantiation error. The failed cell must not contaminate the CodeGen module
+// used by subsequent cells.
+
+extern "C" int printf(const char *, ...);
+
+template <typename T> T f(T a) {
+ static_assert(sizeof(T) == 0, "unsupported type");
+ return a;
+}
+
+f(1.0);
+// CHECK: error: static assertion failed
+
+int x = 10;
+auto r = printf("x = %d\n", x);
+// CHECK: x = 10
+
+%quit
``````````
</details>
https://github.com/llvm/llvm-project/pull/204152
More information about the cfe-commits
mailing list