Skip to content

[clang-repl] Expose RuntimeInterfaceBuilder to allow customization #83126

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 30 additions & 5 deletions clang/include/clang/Interpreter/Interpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "clang/AST/GlobalDecl.h"
#include "clang/Interpreter/PartialTranslationUnit.h"
#include "clang/Interpreter/Value.h"
#include "clang/Sema/Ownership.h"

#include "llvm/ADT/DenseMap.h"
#include "llvm/ExecutionEngine/JITSymbol.h"
Expand Down Expand Up @@ -75,17 +76,26 @@ class IncrementalCompilerBuilder {
llvm::StringRef CudaSDKPath;
};

/// Generate glue code between the Interpreter's built-in runtime and user code.
class RuntimeInterfaceBuilder {
public:
virtual ~RuntimeInterfaceBuilder() = default;

using TransformExprFunction = ExprResult(RuntimeInterfaceBuilder *Builder,
Expr *, ArrayRef<Expr *>);
virtual TransformExprFunction *getPrintValueTransformer() = 0;
};

/// Provides top-level interfaces for incremental compilation and execution.
class Interpreter {
std::unique_ptr<llvm::orc::ThreadSafeContext> TSCtx;
std::unique_ptr<IncrementalParser> IncrParser;
std::unique_ptr<IncrementalExecutor> IncrExecutor;
std::unique_ptr<RuntimeInterfaceBuilder> RuntimeIB;

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

Interpreter(std::unique_ptr<CompilerInstance> CI, llvm::Error &Err);

llvm::Error CreateExecutor();
unsigned InitPTUSize = 0;

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

// Add a call to an Expr to report its result. We query the function from
// RuntimeInterfaceBuilder once and store it as a function pointer to avoid
// frequent virtual function calls.
RuntimeInterfaceBuilder::TransformExprFunction *AddPrintValueCall = nullptr;

protected:
// Derived classes can make use an extended interface of the Interpreter.
// That's useful for testing and out-of-tree clients.
Interpreter(std::unique_ptr<CompilerInstance> CI, llvm::Error &Err);

// Lazily construct the RuntimeInterfaceBuilder. The provided instance will be
// used for the entire lifetime of the interpreter. The default implementation
// targets the in-process __clang_Interpreter runtime. Override this to use a
// custom runtime.
virtual std::unique_ptr<RuntimeInterfaceBuilder> FindRuntimeInterface();

public:
~Interpreter();
virtual ~Interpreter();

static llvm::Expected<std::unique_ptr<Interpreter>>
create(std::unique_ptr<CompilerInstance> CI);
static llvm::Expected<std::unique_ptr<Interpreter>>
Expand Down Expand Up @@ -143,8 +170,6 @@ class Interpreter {
private:
size_t getEffectivePTUSize() const;

bool FindRuntimeInterface();

llvm::DenseMap<CXXRecordDecl *, llvm::orc::ExecutorAddr> Dtors;

llvm::SmallVector<Expr *, 4> ValuePrintingInfo;
Expand Down
247 changes: 143 additions & 104 deletions clang/lib/Interpreter/Interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -507,9 +507,13 @@ static constexpr llvm::StringRef MagicRuntimeInterface[] = {
"__clang_Interpreter_SetValueWithAlloc",
"__clang_Interpreter_SetValueCopyArr", "__ci_newtag"};

bool Interpreter::FindRuntimeInterface() {
static std::unique_ptr<RuntimeInterfaceBuilder>
createInProcessRuntimeInterfaceBuilder(Interpreter &Interp, ASTContext &Ctx,
Sema &S);

std::unique_ptr<RuntimeInterfaceBuilder> Interpreter::FindRuntimeInterface() {
if (llvm::all_of(ValuePrintingInfo, [](Expr *E) { return E != nullptr; }))
return true;
return nullptr;

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

if (!LookupInterface(ValuePrintingInfo[NoAlloc],
MagicRuntimeInterface[NoAlloc]))
return false;
return nullptr;
if (!LookupInterface(ValuePrintingInfo[WithAlloc],
MagicRuntimeInterface[WithAlloc]))
return false;
return nullptr;
if (!LookupInterface(ValuePrintingInfo[CopyArray],
MagicRuntimeInterface[CopyArray]))
return false;
return nullptr;
if (!LookupInterface(ValuePrintingInfo[NewTag],
MagicRuntimeInterface[NewTag]))
return false;
return true;
return nullptr;

return createInProcessRuntimeInterfaceBuilder(*this, Ctx, S);
}

namespace {

class RuntimeInterfaceBuilder
: public TypeVisitor<RuntimeInterfaceBuilder, Interpreter::InterfaceKind> {
clang::Interpreter &Interp;
class InterfaceKindVisitor
: public TypeVisitor<InterfaceKindVisitor, Interpreter::InterfaceKind> {
friend class InProcessRuntimeInterfaceBuilder;

ASTContext &Ctx;
Sema &S;
Expr *E;
llvm::SmallVector<Expr *, 3> Args;

public:
RuntimeInterfaceBuilder(clang::Interpreter &In, ASTContext &C, Sema &SemaRef,
Expr *VE, ArrayRef<Expr *> FixedArgs)
: Interp(In), Ctx(C), S(SemaRef), E(VE) {
// The Interpreter* parameter and the out parameter `OutVal`.
for (Expr *E : FixedArgs)
Args.push_back(E);

// Get rid of ExprWithCleanups.
if (auto *EWC = llvm::dyn_cast_if_present<ExprWithCleanups>(E))
E = EWC->getSubExpr();
}

ExprResult getCall() {
QualType Ty = E->getType();
QualType DesugaredTy = Ty.getDesugaredType(Ctx);

// For lvalue struct, we treat it as a reference.
if (DesugaredTy->isRecordType() && E->isLValue()) {
DesugaredTy = Ctx.getLValueReferenceType(DesugaredTy);
Ty = Ctx.getLValueReferenceType(Ty);
}

Expr *TypeArg =
CStyleCastPtrExpr(S, Ctx.VoidPtrTy, (uintptr_t)Ty.getAsOpaquePtr());
// The QualType parameter `OpaqueType`, represented as `void*`.
Args.push_back(TypeArg);

// We push the last parameter based on the type of the Expr. Note we need
// special care for rvalue struct.
Interpreter::InterfaceKind Kind = Visit(&*DesugaredTy);
switch (Kind) {
case Interpreter::InterfaceKind::WithAlloc:
case Interpreter::InterfaceKind::CopyArray: {
// __clang_Interpreter_SetValueWithAlloc.
ExprResult AllocCall = S.ActOnCallExpr(
/*Scope=*/nullptr,
Interp.getValuePrintingInfo()[Interpreter::InterfaceKind::WithAlloc],
E->getBeginLoc(), Args, E->getEndLoc());
assert(!AllocCall.isInvalid() && "Can't create runtime interface call!");

TypeSourceInfo *TSI = Ctx.getTrivialTypeSourceInfo(Ty, SourceLocation());

// Force CodeGen to emit destructor.
if (auto *RD = Ty->getAsCXXRecordDecl()) {
auto *Dtor = S.LookupDestructor(RD);
Dtor->addAttr(UsedAttr::CreateImplicit(Ctx));
Interp.getCompilerInstance()->getASTConsumer().HandleTopLevelDecl(
DeclGroupRef(Dtor));
}

// __clang_Interpreter_SetValueCopyArr.
if (Kind == Interpreter::InterfaceKind::CopyArray) {
const auto *ConstantArrTy =
cast<ConstantArrayType>(DesugaredTy.getTypePtr());
size_t ArrSize = Ctx.getConstantArrayElementCount(ConstantArrTy);
Expr *ArrSizeExpr = IntegerLiteralExpr(Ctx, ArrSize);
Expr *Args[] = {E, AllocCall.get(), ArrSizeExpr};
return S.ActOnCallExpr(
/*Scope *=*/nullptr,
Interp
.getValuePrintingInfo()[Interpreter::InterfaceKind::CopyArray],
SourceLocation(), Args, SourceLocation());
}
Expr *Args[] = {
AllocCall.get(),
Interp.getValuePrintingInfo()[Interpreter::InterfaceKind::NewTag]};
ExprResult CXXNewCall = S.BuildCXXNew(
E->getSourceRange(),
/*UseGlobal=*/true, /*PlacementLParen=*/SourceLocation(), Args,
/*PlacementRParen=*/SourceLocation(),
/*TypeIdParens=*/SourceRange(), TSI->getType(), TSI, std::nullopt,
E->getSourceRange(), E);

assert(!CXXNewCall.isInvalid() &&
"Can't create runtime placement new call!");

return S.ActOnFinishFullExpr(CXXNewCall.get(),
/*DiscardedValue=*/false);
}
// __clang_Interpreter_SetValueNoAlloc.
case Interpreter::InterfaceKind::NoAlloc: {
return S.ActOnCallExpr(
/*Scope=*/nullptr,
Interp.getValuePrintingInfo()[Interpreter::InterfaceKind::NoAlloc],
E->getBeginLoc(), Args, E->getEndLoc());
}
default:
llvm_unreachable("Unhandled Interpreter::InterfaceKind");
}
}
InterfaceKindVisitor(ASTContext &Ctx, Sema &S, Expr *E)
: Ctx(Ctx), S(S), E(E) {}

Interpreter::InterfaceKind VisitRecordType(const RecordType *Ty) {
return Interpreter::InterfaceKind::WithAlloc;
Expand Down Expand Up @@ -713,8 +631,124 @@ class RuntimeInterfaceBuilder
Args.push_back(CastedExpr.get());
}
};

class InProcessRuntimeInterfaceBuilder : public RuntimeInterfaceBuilder {
Interpreter &Interp;
ASTContext &Ctx;
Sema &S;

public:
InProcessRuntimeInterfaceBuilder(Interpreter &Interp, ASTContext &C, Sema &S)
: Interp(Interp), Ctx(C), S(S) {}

TransformExprFunction *getPrintValueTransformer() override {
return &transformForValuePrinting;
}

private:
static ExprResult transformForValuePrinting(RuntimeInterfaceBuilder *Builder,
Expr *E,
ArrayRef<Expr *> FixedArgs) {
auto *B = static_cast<InProcessRuntimeInterfaceBuilder *>(Builder);

// Get rid of ExprWithCleanups.
if (auto *EWC = llvm::dyn_cast_if_present<ExprWithCleanups>(E))
E = EWC->getSubExpr();

InterfaceKindVisitor Visitor(B->Ctx, B->S, E);

// The Interpreter* parameter and the out parameter `OutVal`.
for (Expr *E : FixedArgs)
Visitor.Args.push_back(E);

QualType Ty = E->getType();
QualType DesugaredTy = Ty.getDesugaredType(B->Ctx);

// For lvalue struct, we treat it as a reference.
if (DesugaredTy->isRecordType() && E->isLValue()) {
DesugaredTy = B->Ctx.getLValueReferenceType(DesugaredTy);
Ty = B->Ctx.getLValueReferenceType(Ty);
}

Expr *TypeArg = CStyleCastPtrExpr(B->S, B->Ctx.VoidPtrTy,
(uintptr_t)Ty.getAsOpaquePtr());
// The QualType parameter `OpaqueType`, represented as `void*`.
Visitor.Args.push_back(TypeArg);

// We push the last parameter based on the type of the Expr. Note we need
// special care for rvalue struct.
Interpreter::InterfaceKind Kind = Visitor.Visit(&*DesugaredTy);
switch (Kind) {
case Interpreter::InterfaceKind::WithAlloc:
case Interpreter::InterfaceKind::CopyArray: {
// __clang_Interpreter_SetValueWithAlloc.
ExprResult AllocCall = B->S.ActOnCallExpr(
/*Scope=*/nullptr,
B->Interp
.getValuePrintingInfo()[Interpreter::InterfaceKind::WithAlloc],
E->getBeginLoc(), Visitor.Args, E->getEndLoc());
assert(!AllocCall.isInvalid() && "Can't create runtime interface call!");

TypeSourceInfo *TSI =
B->Ctx.getTrivialTypeSourceInfo(Ty, SourceLocation());

// Force CodeGen to emit destructor.
if (auto *RD = Ty->getAsCXXRecordDecl()) {
auto *Dtor = B->S.LookupDestructor(RD);
Dtor->addAttr(UsedAttr::CreateImplicit(B->Ctx));
B->Interp.getCompilerInstance()->getASTConsumer().HandleTopLevelDecl(
DeclGroupRef(Dtor));
}

// __clang_Interpreter_SetValueCopyArr.
if (Kind == Interpreter::InterfaceKind::CopyArray) {
const auto *ConstantArrTy =
cast<ConstantArrayType>(DesugaredTy.getTypePtr());
size_t ArrSize = B->Ctx.getConstantArrayElementCount(ConstantArrTy);
Expr *ArrSizeExpr = IntegerLiteralExpr(B->Ctx, ArrSize);
Expr *Args[] = {E, AllocCall.get(), ArrSizeExpr};
return B->S.ActOnCallExpr(
/*Scope *=*/nullptr,
B->Interp
.getValuePrintingInfo()[Interpreter::InterfaceKind::CopyArray],
SourceLocation(), Args, SourceLocation());
}
Expr *Args[] = {
AllocCall.get(),
B->Interp.getValuePrintingInfo()[Interpreter::InterfaceKind::NewTag]};
ExprResult CXXNewCall = B->S.BuildCXXNew(
E->getSourceRange(),
/*UseGlobal=*/true, /*PlacementLParen=*/SourceLocation(), Args,
/*PlacementRParen=*/SourceLocation(),
/*TypeIdParens=*/SourceRange(), TSI->getType(), TSI, std::nullopt,
E->getSourceRange(), E);

assert(!CXXNewCall.isInvalid() &&
"Can't create runtime placement new call!");

return B->S.ActOnFinishFullExpr(CXXNewCall.get(),
/*DiscardedValue=*/false);
}
// __clang_Interpreter_SetValueNoAlloc.
case Interpreter::InterfaceKind::NoAlloc: {
return B->S.ActOnCallExpr(
/*Scope=*/nullptr,
B->Interp.getValuePrintingInfo()[Interpreter::InterfaceKind::NoAlloc],
E->getBeginLoc(), Visitor.Args, E->getEndLoc());
}
default:
llvm_unreachable("Unhandled Interpreter::InterfaceKind");
}
}
};
} // namespace

static std::unique_ptr<RuntimeInterfaceBuilder>
createInProcessRuntimeInterfaceBuilder(Interpreter &Interp, ASTContext &Ctx,
Sema &S) {
return std::make_unique<InProcessRuntimeInterfaceBuilder>(Interp, Ctx, S);
}

// This synthesizes a call expression to a speciall
// function that is responsible for generating the Value.
// In general, we transform:
Expand All @@ -733,8 +767,13 @@ Expr *Interpreter::SynthesizeExpr(Expr *E) {
Sema &S = getCompilerInstance()->getSema();
ASTContext &Ctx = S.getASTContext();

if (!FindRuntimeInterface())
llvm_unreachable("We can't find the runtime iterface for pretty print!");
if (!RuntimeIB) {
RuntimeIB = FindRuntimeInterface();
AddPrintValueCall = RuntimeIB->getPrintValueTransformer();
}

assert(AddPrintValueCall &&
"We don't have a runtime interface for pretty print!");

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

// Build `__clang_Interpreter_SetValue*` call.
RuntimeInterfaceBuilder Builder(*this, Ctx, S, E, {ThisInterp, OutValue});
ExprResult Result =
AddPrintValueCall(RuntimeIB.get(), E, {ThisInterp, OutValue});

ExprResult Result = Builder.getCall();
// It could fail, like printing an array type in C. (not supported)
if (Result.isInvalid())
return E;
Expand Down
1 change: 1 addition & 0 deletions clang/unittests/Interpreter/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ add_clang_unittest(ClangReplInterpreterTests
IncrementalCompilerBuilderTest.cpp
IncrementalProcessingTest.cpp
InterpreterTest.cpp
InterpreterExtensionsTest.cpp
CodeCompletionTest.cpp
)
target_link_libraries(ClangReplInterpreterTests PUBLIC
Expand Down
Loading