Skip to content

Commit ec2875c

Browse files
[clang-repl] Expose RuntimeInterfaceBuilder to allow customization (#83126)
RuntimeInterfaceBuilder wires up JITed expressions with the hardcoded Interpreter runtime. It's used only for value printing right now, but it is not limited to that. The default implementation focuses on an evaluation process where the Interpreter has direct access to the memory of JITed expressions (in-process execution or shared memory). We need a different approach to support out-of-process evaluation or variations of the runtime. It seems reasonable to expose a minimal interface for it. The new RuntimeInterfaceBuilder is an abstract base class in the public header. For that, the TypeVisitor had to become a component (instead of inheriting from it). FindRuntimeInterface() was adjusted to return an instance of the RuntimeInterfaceBuilder and it can be overridden from derived classes.
1 parent 9277a32 commit ec2875c

File tree

4 files changed

+253
-109
lines changed

4 files changed

+253
-109
lines changed

clang/include/clang/Interpreter/Interpreter.h

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "clang/AST/GlobalDecl.h"
1919
#include "clang/Interpreter/PartialTranslationUnit.h"
2020
#include "clang/Interpreter/Value.h"
21+
#include "clang/Sema/Ownership.h"
2122

2223
#include "llvm/ADT/DenseMap.h"
2324
#include "llvm/ExecutionEngine/JITSymbol.h"
@@ -75,17 +76,26 @@ class IncrementalCompilerBuilder {
7576
llvm::StringRef CudaSDKPath;
7677
};
7778

79+
/// Generate glue code between the Interpreter's built-in runtime and user code.
80+
class RuntimeInterfaceBuilder {
81+
public:
82+
virtual ~RuntimeInterfaceBuilder() = default;
83+
84+
using TransformExprFunction = ExprResult(RuntimeInterfaceBuilder *Builder,
85+
Expr *, ArrayRef<Expr *>);
86+
virtual TransformExprFunction *getPrintValueTransformer() = 0;
87+
};
88+
7889
/// Provides top-level interfaces for incremental compilation and execution.
7990
class Interpreter {
8091
std::unique_ptr<llvm::orc::ThreadSafeContext> TSCtx;
8192
std::unique_ptr<IncrementalParser> IncrParser;
8293
std::unique_ptr<IncrementalExecutor> IncrExecutor;
94+
std::unique_ptr<RuntimeInterfaceBuilder> RuntimeIB;
8395

8496
// An optional parser for CUDA offloading
8597
std::unique_ptr<IncrementalParser> DeviceParser;
8698

87-
Interpreter(std::unique_ptr<CompilerInstance> CI, llvm::Error &Err);
88-
8999
llvm::Error CreateExecutor();
90100
unsigned InitPTUSize = 0;
91101

@@ -94,8 +104,25 @@ class Interpreter {
94104
// printing happens, it's in an invalid state.
95105
Value LastValue;
96106

107+
// Add a call to an Expr to report its result. We query the function from
108+
// RuntimeInterfaceBuilder once and store it as a function pointer to avoid
109+
// frequent virtual function calls.
110+
RuntimeInterfaceBuilder::TransformExprFunction *AddPrintValueCall = nullptr;
111+
112+
protected:
113+
// Derived classes can make use an extended interface of the Interpreter.
114+
// That's useful for testing and out-of-tree clients.
115+
Interpreter(std::unique_ptr<CompilerInstance> CI, llvm::Error &Err);
116+
117+
// Lazily construct the RuntimeInterfaceBuilder. The provided instance will be
118+
// used for the entire lifetime of the interpreter. The default implementation
119+
// targets the in-process __clang_Interpreter runtime. Override this to use a
120+
// custom runtime.
121+
virtual std::unique_ptr<RuntimeInterfaceBuilder> FindRuntimeInterface();
122+
97123
public:
98-
~Interpreter();
124+
virtual ~Interpreter();
125+
99126
static llvm::Expected<std::unique_ptr<Interpreter>>
100127
create(std::unique_ptr<CompilerInstance> CI);
101128
static llvm::Expected<std::unique_ptr<Interpreter>>
@@ -143,8 +170,6 @@ class Interpreter {
143170
private:
144171
size_t getEffectivePTUSize() const;
145172

146-
bool FindRuntimeInterface();
147-
148173
llvm::DenseMap<CXXRecordDecl *, llvm::orc::ExecutorAddr> Dtors;
149174

150175
llvm::SmallVector<Expr *, 4> ValuePrintingInfo;

clang/lib/Interpreter/Interpreter.cpp

Lines changed: 143 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -507,9 +507,13 @@ static constexpr llvm::StringRef MagicRuntimeInterface[] = {
507507
"__clang_Interpreter_SetValueWithAlloc",
508508
"__clang_Interpreter_SetValueCopyArr", "__ci_newtag"};
509509

510-
bool Interpreter::FindRuntimeInterface() {
510+
static std::unique_ptr<RuntimeInterfaceBuilder>
511+
createInProcessRuntimeInterfaceBuilder(Interpreter &Interp, ASTContext &Ctx,
512+
Sema &S);
513+
514+
std::unique_ptr<RuntimeInterfaceBuilder> Interpreter::FindRuntimeInterface() {
511515
if (llvm::all_of(ValuePrintingInfo, [](Expr *E) { return E != nullptr; }))
512-
return true;
516+
return nullptr;
513517

514518
Sema &S = getCompilerInstance()->getSema();
515519
ASTContext &Ctx = S.getASTContext();
@@ -528,120 +532,34 @@ bool Interpreter::FindRuntimeInterface() {
528532

529533
if (!LookupInterface(ValuePrintingInfo[NoAlloc],
530534
MagicRuntimeInterface[NoAlloc]))
531-
return false;
535+
return nullptr;
532536
if (!LookupInterface(ValuePrintingInfo[WithAlloc],
533537
MagicRuntimeInterface[WithAlloc]))
534-
return false;
538+
return nullptr;
535539
if (!LookupInterface(ValuePrintingInfo[CopyArray],
536540
MagicRuntimeInterface[CopyArray]))
537-
return false;
541+
return nullptr;
538542
if (!LookupInterface(ValuePrintingInfo[NewTag],
539543
MagicRuntimeInterface[NewTag]))
540-
return false;
541-
return true;
544+
return nullptr;
545+
546+
return createInProcessRuntimeInterfaceBuilder(*this, Ctx, S);
542547
}
543548

544549
namespace {
545550

546-
class RuntimeInterfaceBuilder
547-
: public TypeVisitor<RuntimeInterfaceBuilder, Interpreter::InterfaceKind> {
548-
clang::Interpreter &Interp;
551+
class InterfaceKindVisitor
552+
: public TypeVisitor<InterfaceKindVisitor, Interpreter::InterfaceKind> {
553+
friend class InProcessRuntimeInterfaceBuilder;
554+
549555
ASTContext &Ctx;
550556
Sema &S;
551557
Expr *E;
552558
llvm::SmallVector<Expr *, 3> Args;
553559

554560
public:
555-
RuntimeInterfaceBuilder(clang::Interpreter &In, ASTContext &C, Sema &SemaRef,
556-
Expr *VE, ArrayRef<Expr *> FixedArgs)
557-
: Interp(In), Ctx(C), S(SemaRef), E(VE) {
558-
// The Interpreter* parameter and the out parameter `OutVal`.
559-
for (Expr *E : FixedArgs)
560-
Args.push_back(E);
561-
562-
// Get rid of ExprWithCleanups.
563-
if (auto *EWC = llvm::dyn_cast_if_present<ExprWithCleanups>(E))
564-
E = EWC->getSubExpr();
565-
}
566-
567-
ExprResult getCall() {
568-
QualType Ty = E->getType();
569-
QualType DesugaredTy = Ty.getDesugaredType(Ctx);
570-
571-
// For lvalue struct, we treat it as a reference.
572-
if (DesugaredTy->isRecordType() && E->isLValue()) {
573-
DesugaredTy = Ctx.getLValueReferenceType(DesugaredTy);
574-
Ty = Ctx.getLValueReferenceType(Ty);
575-
}
576-
577-
Expr *TypeArg =
578-
CStyleCastPtrExpr(S, Ctx.VoidPtrTy, (uintptr_t)Ty.getAsOpaquePtr());
579-
// The QualType parameter `OpaqueType`, represented as `void*`.
580-
Args.push_back(TypeArg);
581-
582-
// We push the last parameter based on the type of the Expr. Note we need
583-
// special care for rvalue struct.
584-
Interpreter::InterfaceKind Kind = Visit(&*DesugaredTy);
585-
switch (Kind) {
586-
case Interpreter::InterfaceKind::WithAlloc:
587-
case Interpreter::InterfaceKind::CopyArray: {
588-
// __clang_Interpreter_SetValueWithAlloc.
589-
ExprResult AllocCall = S.ActOnCallExpr(
590-
/*Scope=*/nullptr,
591-
Interp.getValuePrintingInfo()[Interpreter::InterfaceKind::WithAlloc],
592-
E->getBeginLoc(), Args, E->getEndLoc());
593-
assert(!AllocCall.isInvalid() && "Can't create runtime interface call!");
594-
595-
TypeSourceInfo *TSI = Ctx.getTrivialTypeSourceInfo(Ty, SourceLocation());
596-
597-
// Force CodeGen to emit destructor.
598-
if (auto *RD = Ty->getAsCXXRecordDecl()) {
599-
auto *Dtor = S.LookupDestructor(RD);
600-
Dtor->addAttr(UsedAttr::CreateImplicit(Ctx));
601-
Interp.getCompilerInstance()->getASTConsumer().HandleTopLevelDecl(
602-
DeclGroupRef(Dtor));
603-
}
604-
605-
// __clang_Interpreter_SetValueCopyArr.
606-
if (Kind == Interpreter::InterfaceKind::CopyArray) {
607-
const auto *ConstantArrTy =
608-
cast<ConstantArrayType>(DesugaredTy.getTypePtr());
609-
size_t ArrSize = Ctx.getConstantArrayElementCount(ConstantArrTy);
610-
Expr *ArrSizeExpr = IntegerLiteralExpr(Ctx, ArrSize);
611-
Expr *Args[] = {E, AllocCall.get(), ArrSizeExpr};
612-
return S.ActOnCallExpr(
613-
/*Scope *=*/nullptr,
614-
Interp
615-
.getValuePrintingInfo()[Interpreter::InterfaceKind::CopyArray],
616-
SourceLocation(), Args, SourceLocation());
617-
}
618-
Expr *Args[] = {
619-
AllocCall.get(),
620-
Interp.getValuePrintingInfo()[Interpreter::InterfaceKind::NewTag]};
621-
ExprResult CXXNewCall = S.BuildCXXNew(
622-
E->getSourceRange(),
623-
/*UseGlobal=*/true, /*PlacementLParen=*/SourceLocation(), Args,
624-
/*PlacementRParen=*/SourceLocation(),
625-
/*TypeIdParens=*/SourceRange(), TSI->getType(), TSI, std::nullopt,
626-
E->getSourceRange(), E);
627-
628-
assert(!CXXNewCall.isInvalid() &&
629-
"Can't create runtime placement new call!");
630-
631-
return S.ActOnFinishFullExpr(CXXNewCall.get(),
632-
/*DiscardedValue=*/false);
633-
}
634-
// __clang_Interpreter_SetValueNoAlloc.
635-
case Interpreter::InterfaceKind::NoAlloc: {
636-
return S.ActOnCallExpr(
637-
/*Scope=*/nullptr,
638-
Interp.getValuePrintingInfo()[Interpreter::InterfaceKind::NoAlloc],
639-
E->getBeginLoc(), Args, E->getEndLoc());
640-
}
641-
default:
642-
llvm_unreachable("Unhandled Interpreter::InterfaceKind");
643-
}
644-
}
561+
InterfaceKindVisitor(ASTContext &Ctx, Sema &S, Expr *E)
562+
: Ctx(Ctx), S(S), E(E) {}
645563

646564
Interpreter::InterfaceKind VisitRecordType(const RecordType *Ty) {
647565
return Interpreter::InterfaceKind::WithAlloc;
@@ -713,8 +631,124 @@ class RuntimeInterfaceBuilder
713631
Args.push_back(CastedExpr.get());
714632
}
715633
};
634+
635+
class InProcessRuntimeInterfaceBuilder : public RuntimeInterfaceBuilder {
636+
Interpreter &Interp;
637+
ASTContext &Ctx;
638+
Sema &S;
639+
640+
public:
641+
InProcessRuntimeInterfaceBuilder(Interpreter &Interp, ASTContext &C, Sema &S)
642+
: Interp(Interp), Ctx(C), S(S) {}
643+
644+
TransformExprFunction *getPrintValueTransformer() override {
645+
return &transformForValuePrinting;
646+
}
647+
648+
private:
649+
static ExprResult transformForValuePrinting(RuntimeInterfaceBuilder *Builder,
650+
Expr *E,
651+
ArrayRef<Expr *> FixedArgs) {
652+
auto *B = static_cast<InProcessRuntimeInterfaceBuilder *>(Builder);
653+
654+
// Get rid of ExprWithCleanups.
655+
if (auto *EWC = llvm::dyn_cast_if_present<ExprWithCleanups>(E))
656+
E = EWC->getSubExpr();
657+
658+
InterfaceKindVisitor Visitor(B->Ctx, B->S, E);
659+
660+
// The Interpreter* parameter and the out parameter `OutVal`.
661+
for (Expr *E : FixedArgs)
662+
Visitor.Args.push_back(E);
663+
664+
QualType Ty = E->getType();
665+
QualType DesugaredTy = Ty.getDesugaredType(B->Ctx);
666+
667+
// For lvalue struct, we treat it as a reference.
668+
if (DesugaredTy->isRecordType() && E->isLValue()) {
669+
DesugaredTy = B->Ctx.getLValueReferenceType(DesugaredTy);
670+
Ty = B->Ctx.getLValueReferenceType(Ty);
671+
}
672+
673+
Expr *TypeArg = CStyleCastPtrExpr(B->S, B->Ctx.VoidPtrTy,
674+
(uintptr_t)Ty.getAsOpaquePtr());
675+
// The QualType parameter `OpaqueType`, represented as `void*`.
676+
Visitor.Args.push_back(TypeArg);
677+
678+
// We push the last parameter based on the type of the Expr. Note we need
679+
// special care for rvalue struct.
680+
Interpreter::InterfaceKind Kind = Visitor.Visit(&*DesugaredTy);
681+
switch (Kind) {
682+
case Interpreter::InterfaceKind::WithAlloc:
683+
case Interpreter::InterfaceKind::CopyArray: {
684+
// __clang_Interpreter_SetValueWithAlloc.
685+
ExprResult AllocCall = B->S.ActOnCallExpr(
686+
/*Scope=*/nullptr,
687+
B->Interp
688+
.getValuePrintingInfo()[Interpreter::InterfaceKind::WithAlloc],
689+
E->getBeginLoc(), Visitor.Args, E->getEndLoc());
690+
assert(!AllocCall.isInvalid() && "Can't create runtime interface call!");
691+
692+
TypeSourceInfo *TSI =
693+
B->Ctx.getTrivialTypeSourceInfo(Ty, SourceLocation());
694+
695+
// Force CodeGen to emit destructor.
696+
if (auto *RD = Ty->getAsCXXRecordDecl()) {
697+
auto *Dtor = B->S.LookupDestructor(RD);
698+
Dtor->addAttr(UsedAttr::CreateImplicit(B->Ctx));
699+
B->Interp.getCompilerInstance()->getASTConsumer().HandleTopLevelDecl(
700+
DeclGroupRef(Dtor));
701+
}
702+
703+
// __clang_Interpreter_SetValueCopyArr.
704+
if (Kind == Interpreter::InterfaceKind::CopyArray) {
705+
const auto *ConstantArrTy =
706+
cast<ConstantArrayType>(DesugaredTy.getTypePtr());
707+
size_t ArrSize = B->Ctx.getConstantArrayElementCount(ConstantArrTy);
708+
Expr *ArrSizeExpr = IntegerLiteralExpr(B->Ctx, ArrSize);
709+
Expr *Args[] = {E, AllocCall.get(), ArrSizeExpr};
710+
return B->S.ActOnCallExpr(
711+
/*Scope *=*/nullptr,
712+
B->Interp
713+
.getValuePrintingInfo()[Interpreter::InterfaceKind::CopyArray],
714+
SourceLocation(), Args, SourceLocation());
715+
}
716+
Expr *Args[] = {
717+
AllocCall.get(),
718+
B->Interp.getValuePrintingInfo()[Interpreter::InterfaceKind::NewTag]};
719+
ExprResult CXXNewCall = B->S.BuildCXXNew(
720+
E->getSourceRange(),
721+
/*UseGlobal=*/true, /*PlacementLParen=*/SourceLocation(), Args,
722+
/*PlacementRParen=*/SourceLocation(),
723+
/*TypeIdParens=*/SourceRange(), TSI->getType(), TSI, std::nullopt,
724+
E->getSourceRange(), E);
725+
726+
assert(!CXXNewCall.isInvalid() &&
727+
"Can't create runtime placement new call!");
728+
729+
return B->S.ActOnFinishFullExpr(CXXNewCall.get(),
730+
/*DiscardedValue=*/false);
731+
}
732+
// __clang_Interpreter_SetValueNoAlloc.
733+
case Interpreter::InterfaceKind::NoAlloc: {
734+
return B->S.ActOnCallExpr(
735+
/*Scope=*/nullptr,
736+
B->Interp.getValuePrintingInfo()[Interpreter::InterfaceKind::NoAlloc],
737+
E->getBeginLoc(), Visitor.Args, E->getEndLoc());
738+
}
739+
default:
740+
llvm_unreachable("Unhandled Interpreter::InterfaceKind");
741+
}
742+
}
743+
};
716744
} // namespace
717745

746+
static std::unique_ptr<RuntimeInterfaceBuilder>
747+
createInProcessRuntimeInterfaceBuilder(Interpreter &Interp, ASTContext &Ctx,
748+
Sema &S) {
749+
return std::make_unique<InProcessRuntimeInterfaceBuilder>(Interp, Ctx, S);
750+
}
751+
718752
// This synthesizes a call expression to a speciall
719753
// function that is responsible for generating the Value.
720754
// In general, we transform:
@@ -733,8 +767,13 @@ Expr *Interpreter::SynthesizeExpr(Expr *E) {
733767
Sema &S = getCompilerInstance()->getSema();
734768
ASTContext &Ctx = S.getASTContext();
735769

736-
if (!FindRuntimeInterface())
737-
llvm_unreachable("We can't find the runtime iterface for pretty print!");
770+
if (!RuntimeIB) {
771+
RuntimeIB = FindRuntimeInterface();
772+
AddPrintValueCall = RuntimeIB->getPrintValueTransformer();
773+
}
774+
775+
assert(AddPrintValueCall &&
776+
"We don't have a runtime interface for pretty print!");
738777

739778
// Create parameter `ThisInterp`.
740779
auto *ThisInterp = CStyleCastPtrExpr(S, Ctx.VoidPtrTy, (uintptr_t)this);
@@ -743,9 +782,9 @@ Expr *Interpreter::SynthesizeExpr(Expr *E) {
743782
auto *OutValue = CStyleCastPtrExpr(S, Ctx.VoidPtrTy, (uintptr_t)&LastValue);
744783

745784
// Build `__clang_Interpreter_SetValue*` call.
746-
RuntimeInterfaceBuilder Builder(*this, Ctx, S, E, {ThisInterp, OutValue});
785+
ExprResult Result =
786+
AddPrintValueCall(RuntimeIB.get(), E, {ThisInterp, OutValue});
747787

748-
ExprResult Result = Builder.getCall();
749788
// It could fail, like printing an array type in C. (not supported)
750789
if (Result.isInvalid())
751790
return E;

clang/unittests/Interpreter/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ add_clang_unittest(ClangReplInterpreterTests
1010
IncrementalCompilerBuilderTest.cpp
1111
IncrementalProcessingTest.cpp
1212
InterpreterTest.cpp
13+
InterpreterExtensionsTest.cpp
1314
CodeCompletionTest.cpp
1415
)
1516
target_link_libraries(ClangReplInterpreterTests PUBLIC

0 commit comments

Comments
 (0)