Skip to content

Commit 58d4109

Browse files
committed
Introduce ValueCleanup for managed destruction of JIT values
1 parent 243331f commit 58d4109

File tree

7 files changed

+369
-111
lines changed

7 files changed

+369
-111
lines changed

clang/include/clang/Interpreter/Interpreter.h

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -218,12 +218,6 @@ class Interpreter {
218218

219219
std::unique_ptr<llvm::orc::LLJITBuilder> JITBuilder;
220220

221-
/// @}
222-
/// @name Value and pretty printing support
223-
/// @{
224-
225-
std::string ValueDataToString(const Value &V) const;
226-
227221
llvm::Expected<Expr *> convertExprToValue(Expr *E, bool IsOOP = false);
228222

229223
// When we deallocate clang::Value we need to run the destructor of the type.

clang/include/clang/Interpreter/Value.h

Lines changed: 84 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,50 @@ class QualType;
9595
X(double, Double) \
9696
X(long double, LongDouble)
9797

98+
class Value;
99+
100+
/// \struct ValueCleanup
101+
/// \brief Encapsulates destructor invocation for REPL values.
102+
///
103+
/// `ValueCleanup` provides the logic to run object destructors in the JIT
104+
/// process. It captures the runtime addresses of the destructor wrapper
105+
/// functions and the object destructor itself.
106+
///
107+
/// Typical usage:
108+
/// - Constructed when a JIT'd type requires cleanup.
109+
/// - Attached to a `Value` via `setValueCleanup`.
110+
/// - Invoked through `operator()(Value&)` to run the destructor on demand.
111+
struct ValueCleanup {
112+
using DtorLookupFn =
113+
std::function<llvm::Expected<llvm::orc::ExecutorAddr>(QualType Ty)>;
114+
llvm::orc::ExecutionSession *ES;
115+
llvm::orc::ExecutorAddr DtorWrapperFn;
116+
llvm::orc::ExecutorAddr DtorFn;
117+
DtorLookupFn ObjDtor;
118+
ValueCleanup() = default;
119+
ValueCleanup(llvm::orc::ExecutionSession *ES,
120+
llvm::orc::ExecutorAddr WrapperFn,
121+
llvm::orc::ExecutorAddr DtorFn, DtorLookupFn Dtor)
122+
: ES(ES), DtorWrapperFn(WrapperFn), DtorFn(DtorFn),
123+
ObjDtor(std::move(Dtor)) {}
124+
~ValueCleanup() = default;
125+
void operator()(Value &V);
126+
};
127+
128+
/// \class Value
129+
/// \brief Represents a dynamically typed value in the REPL.
130+
///
131+
/// `Value` provides a type-erased container for runtime values that can be
132+
/// produced or consumed by the REPL. It supports multiple storage kinds:
133+
///
134+
/// - Builtin scalars (int, float, etc.)
135+
/// - Arrays of `Value`
136+
/// - Pointers (with optional pointee tracking)
137+
/// - Strings
138+
/// - An empty state (`K_None`)
139+
///
140+
/// `Value` also integrates with `ValueCleanup`, which holds runtime
141+
/// destructor logic for objects that require cleanup.
98142
class REPL_EXTERNAL_VISIBILITY Value final {
99143
public:
100144
enum BuiltinKind {
@@ -105,6 +149,7 @@ class REPL_EXTERNAL_VISIBILITY Value final {
105149
};
106150

107151
private:
152+
/// Storage for builtin scalar values.
108153
struct Builtins {
109154
private:
110155
BuiltinKind BK = K_Unspecified;
@@ -137,6 +182,7 @@ class REPL_EXTERNAL_VISIBILITY Value final {
137182
#undef X
138183
};
139184

185+
/// Represents an array of `Value` elements.
140186
struct ArrValue {
141187
std::vector<Value> Elements;
142188
uint64_t ArrSize;
@@ -147,6 +193,7 @@ class REPL_EXTERNAL_VISIBILITY Value final {
147193
}
148194
};
149195

196+
/// Represents a pointer. Holds the address and optionally a pointee `Value`.
150197
struct PtrValue {
151198
uint64_t Addr = 0;
152199
Value *Pointee; // optional for str
@@ -157,6 +204,7 @@ class REPL_EXTERNAL_VISIBILITY Value final {
157204
}
158205
};
159206

207+
/// Represents a string value (wrapper over std::string).
160208
struct StrValue {
161209
std::string StringBuf;
162210
StrValue(std::string str) : StringBuf(std::move(str)) {}
@@ -173,11 +221,16 @@ class REPL_EXTERNAL_VISIBILITY Value final {
173221
ValKind VKind = K_None;
174222
DataType Data;
175223

224+
/// Optional cleanup action (e.g. call dtor in JIT runtime).
225+
std::optional<ValueCleanup> Cleanup = std::nullopt;
226+
176227
public:
177228
Value() = default;
178229
explicit Value(QualType Ty, ValKind K) : Ty(Ty), VKind(K) {}
179230
Value(const Value &RHS);
180-
Value(Value &&RHS) : Ty(RHS.Ty), VKind(RHS.VKind), Data(RHS.Data) {
231+
Value(Value &&RHS)
232+
: Ty(RHS.Ty), VKind(RHS.VKind), Data(RHS.Data),
233+
Cleanup(std::move(RHS.Cleanup)) {
181234
RHS.VKind = K_None;
182235
}
183236

@@ -206,6 +259,7 @@ class REPL_EXTERNAL_VISIBILITY Value final {
206259
destroy();
207260
}
208261

262+
// ---- Raw buffer conversion ----
209263
template <typename T> static T as(std::vector<uint8_t> &raw) {
210264
T v{};
211265
// assert(raw.size() >= sizeof(T) && "Buffer too small for type!");
@@ -266,12 +320,14 @@ class REPL_EXTERNAL_VISIBILITY Value final {
266320
const StrValue &asStr() const { return const_cast<Value *>(this)->asStr(); }
267321

268322
public:
323+
// ---- Query helpers ----
269324
bool hasBuiltinThis(BuiltinKind K) const {
270325
if (isBuiltin())
271326
return asBuiltin().getKind() == K;
272327
return false;
273328
}
274329

330+
// ---- String accessors ----
275331
void setStrVal(const char *buf) {
276332
assert(isStr() && "Not a Str");
277333
asStr().StringBuf = buf;
@@ -287,9 +343,10 @@ class REPL_EXTERNAL_VISIBILITY Value final {
287343
return StringRef(asStr().StringBuf);
288344
}
289345

346+
// ---- Array accessors ----
290347
uint64_t getArraySize() const { return asArray().ArrSize; }
291348

292-
uint64_t getArrayInitializedElts() const { return asArray().ArrSize; }
349+
uint64_t getArrayInitializedElts() const { return asArray().Elements.size(); }
293350

294351
Value &getArrayInitializedElt(unsigned I) {
295352
assert(isArray() && "Invalid accessor");
@@ -301,6 +358,7 @@ class REPL_EXTERNAL_VISIBILITY Value final {
301358
return const_cast<Value *>(this)->getArrayInitializedElt(I);
302359
}
303360

361+
// ---- Pointer accessors ----
304362
bool HasPointee() const {
305363
assert(isPointer() && "Invalid accessor");
306364
return !(asPointer().Pointee->isAbsent());
@@ -317,6 +375,7 @@ class REPL_EXTERNAL_VISIBILITY Value final {
317375

318376
uint64_t getAddr() const { return asPointer().Addr; }
319377

378+
// ---- Builtin setters/getters ----
320379
#define X(type, name) \
321380
void set##name(type Val) { asBuiltin().set##name(Val); } \
322381
type get##name() const { return asBuiltin().get##name(); }
@@ -329,10 +388,19 @@ class REPL_EXTERNAL_VISIBILITY Value final {
329388
void print(llvm::raw_ostream &Out, ASTContext &Ctx) const;
330389
void dump(ASTContext &Ctx) const;
331390

332-
// ---- clear ----
333-
void clear() { destroy(); }
391+
// ---- Cleanup & destruction ----
392+
void setValueCleanup(ValueCleanup VC) {
393+
assert(!Cleanup.has_value());
394+
Cleanup.emplace(std::move(VC));
395+
}
396+
void clear() {
397+
if (Cleanup.has_value())
398+
(*Cleanup)(*this);
399+
destroy();
400+
}
334401

335402
private:
403+
// ---- Constructors for each kind ----
336404
void MakeBuiltIns() {
337405
assert(isAbsent() && "Bad state change");
338406
new ((void *)(char *)&Data) Builtins(BuiltinKind::K_Unspecified);
@@ -408,6 +476,13 @@ class ValueToString {
408476
std::string ArrayToString(const Value &A);
409477
};
410478

479+
/// \class ValueResultManager
480+
/// \brief Manages values returned from JIT code.
481+
///
482+
/// Each result is registered with a unique ID and its `QualType`.
483+
/// The JIT code later calls back into the runtime with that ID, and
484+
/// `deliverResult` uses it to look up the type, read the value from memory,
485+
/// and attach any destructor cleanup before making it available to the host.
411486
class ValueResultManager {
412487
public:
413488
using ValueId = uint64_t;
@@ -418,9 +493,12 @@ class ValueResultManager {
418493
static std::unique_ptr<ValueResultManager>
419494
Create(llvm::orc::LLJIT &EE, ASTContext &Ctx, bool IsOutOfProcess = false);
420495

421-
ValueId registerPendingResult(QualType QT) {
496+
ValueId registerPendingResult(QualType QT,
497+
std::optional<ValueCleanup> VC = std::nullopt) {
422498
ValueId NewID = NextID.fetch_add(1, std::memory_order_relaxed);
423499
IdToType.insert({NewID, QT});
500+
if (VC)
501+
IdToValCleanup.insert({NewID, std::move(*VC)});
424502
return NewID;
425503
}
426504

@@ -439,6 +517,7 @@ class ValueResultManager {
439517
llvm::orc::MemoryAccess &MemAcc;
440518
Value LastVal;
441519
llvm::DenseMap<ValueId, clang::QualType> IdToType;
520+
llvm::DenseMap<ValueId, ValueCleanup> IdToValCleanup;
442521
};
443522

444523
} // namespace clang

clang/lib/Interpreter/Interpreter.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,9 @@ const char *const Runtimes = R"(
341341
void __clang_Interpreter_SetValueCopyArr(const T (*Src)[N], void* Placement, unsigned long Size) {
342342
__clang_Interpreter_SetValueCopyArr(Src[0], Placement, Size);
343343
}
344+
EXTERN_C void __clang_Interpreter_destroyObj(unsigned char *ptr) {
345+
delete[] ptr;
346+
}
344347
#else
345348
#define EXTERN_C extern
346349
EXTERN_C void *memcpy(void *restrict dst, const void *restrict src, __SIZE_TYPE__ n);

clang/lib/Interpreter/InterpreterValuePrinter.cpp

Lines changed: 74 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,6 @@ static std::string FunctionToString(ASTContext &Ctx, QualType QT,
156156

157157
namespace clang {
158158

159-
std::string Interpreter::ValueDataToString(const Value &V) const { return ""; }
160-
161159
std::string ValueToString::toString(const Value *Buf) {
162160

163161
switch (Buf->getKind()) {
@@ -597,9 +595,9 @@ class InterfaceKindVisitor : public TypeVisitor<InterfaceKindVisitor, bool> {
597595
}
598596

599597
bool VisitRecordType(const RecordType *Ty) {
600-
// Args.push_back(
601-
// Converter.handleRecordTypeExpr(Ty, QualType(Ty, 0), E).get());
602-
return false;
598+
Args.push_back(
599+
Converter.handleRecordTypeExpr(Ty, QualType(Ty, 0), E).get());
600+
return true;
603601
}
604602

605603
bool VisitConstantArrayType(const ConstantArrayType *Ty) {
@@ -626,10 +624,18 @@ class InterfaceKindVisitor : public TypeVisitor<InterfaceKindVisitor, bool> {
626624
}
627625
};
628626

629-
enum RunTimeFnTag { OrcSendResult, ClangSendResult, ClangDestroyObj };
627+
enum RunTimeFnTag {
628+
OrcSendResult,
629+
ClangSendResult,
630+
ClangDestroyObj,
631+
OrcRunDtorWrapper,
632+
ClangRunDtorWrapper
633+
};
630634

631635
static constexpr llvm::StringRef RunTimeFnTagName[] = {
632-
"__orc_rt_SendResultValue", "__clang_Interpreter_SendResultValue"};
636+
"__orc_rt_SendResultValue", "__clang_Interpreter_SendResultValue",
637+
"__clang_Interpreter_destroyObj", "__orc_rt_runDtor",
638+
"__clang_Interpreter_runDtor"};
633639

634640
// This synthesizes a call expression to a speciall
635641
// function that is responsible for generating the Value.
@@ -674,6 +680,10 @@ llvm::Expected<Expr *> Interpreter::convertExprToValue(Expr *E, bool isOOP) {
674680
if (llvm::Error Err = LookupInterface(ValuePrintingInfo[ClangSendResult],
675681
RunTimeFnTagName[ClangSendResult]))
676682
return std::move(Err);
683+
684+
if (llvm::Error Err = LookupInterface(ValuePrintingInfo[ClangDestroyObj],
685+
RunTimeFnTagName[ClangDestroyObj]))
686+
return std::move(Err);
677687
}
678688

679689
llvm::SmallVector<Expr *, 4> AdjustedArgs;
@@ -696,7 +706,49 @@ llvm::Expected<Expr *> Interpreter::convertExprToValue(Expr *E, bool isOOP) {
696706
Ty = Ctx.getLValueReferenceType(Ty);
697707
}
698708

699-
auto ID = ValMgr->registerPendingResult(Ty);
709+
std::optional<ValueCleanup> CleanUp = std::nullopt;
710+
if (DesugaredTy->isRecordType() && E->isPRValue())
711+
if (auto *CXXRD = DesugaredTy->getAsCXXRecordDecl()) {
712+
auto *Dtor = S.LookupDestructor(CXXRD);
713+
Dtor->addAttr(UsedAttr::CreateImplicit(Ctx));
714+
getCompilerInstance()->getASTConsumer().HandleTopLevelDecl(
715+
DeclGroupRef(Dtor));
716+
717+
auto ObjDtor =
718+
[this](QualType Ty) -> llvm::Expected<llvm::orc::ExecutorAddr> {
719+
if (auto *CXXRD = Ty->getAsCXXRecordDecl())
720+
return this->CompileDtorCall(CXXRD);
721+
return llvm::make_error<llvm::StringError>(
722+
"destructor not found!", llvm::inconvertibleErrorCode());
723+
};
724+
725+
std::string DestroyObjFnName = RunTimeFnTagName[ClangDestroyObj].str();
726+
std::string RunDtorWrapperFnName =
727+
RunTimeFnTagName[isOOP ? OrcRunDtorWrapper : ClangRunDtorWrapper]
728+
.str();
729+
730+
// #if defined(__APPLE__)
731+
// // On macOS, runtime symbols may require a leading underscore
732+
// DestroyObjFnName.insert(0, "_");
733+
// RunDtorWrapperFnName.insert(0, "_");
734+
// #endif
735+
736+
auto RunDtorWrapperAddr = getSymbolAddress(RunDtorWrapperFnName);
737+
if (!RunDtorWrapperAddr)
738+
return RunDtorWrapperAddr.takeError();
739+
740+
auto DestroyObjAddr = getSymbolAddress(DestroyObjFnName);
741+
if (!DestroyObjAddr)
742+
return DestroyObjAddr.takeError();
743+
744+
if (auto E = getExecutionEngine()) {
745+
CleanUp = std::make_optional<ValueCleanup>(
746+
&E->getExecutionSession(), *RunDtorWrapperAddr, *DestroyObjAddr,
747+
std::move(ObjDtor));
748+
}
749+
}
750+
751+
auto ID = ValMgr->registerPendingResult(Ty, std::move(CleanUp));
700752

701753
AdjustedArgs.push_back(IntegerLiteralExpr(Ctx, ID));
702754

@@ -731,6 +783,20 @@ __clang_Interpreter_SendResultValue(void *Ctx, uint64_t Id, void *Addr) {
731783
[](llvm::Error Err) { llvm::cantFail(std::move(Err)); }, Id,
732784
llvm::orc::ExecutorAddr::fromPtr(Addr));
733785
}
786+
787+
REPL_EXTERNAL_VISIBILITY llvm::orc::shared::CWrapperFunctionResult
788+
__clang_Interpreter_runDtor(char *ArgData, size_t ArgSize) {
789+
return llvm::orc::shared::WrapperFunction<llvm::orc::shared::SPSError(
790+
llvm::orc::shared::SPSExecutorAddr, llvm::orc::shared::SPSExecutorAddr)>::
791+
handle(ArgData, ArgSize,
792+
[](llvm::orc::ExecutorAddr DtorFn,
793+
llvm::orc::ExecutorAddr This) -> llvm::Error {
794+
DtorFn.toPtr<void (*)(unsigned char *)>()(
795+
This.toPtr<unsigned char *>());
796+
return llvm::Error::success();
797+
})
798+
.release();
799+
}
734800
}
735801

736802
// A trampoline to work around the fact that operator placement new cannot

0 commit comments

Comments
 (0)