Skip to content
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
2 changes: 1 addition & 1 deletion clang/lib/AST/ByteCode/ByteCodeEmitter.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ enum Opcode : uint32_t;
/// An emitter which links the program to bytecode for later use.
class ByteCodeEmitter {
protected:
using LabelTy = uint32_t;
using AddrTy = uintptr_t;
using Local = Scope::Local;

public:
using LabelTy = uint32_t;
/// Compiles the function into the module.
void compileFunc(const FunctionDecl *FuncDecl, Function *Func = nullptr);

Expand Down
34 changes: 24 additions & 10 deletions clang/lib/AST/ByteCode/Compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "PrimType.h"
#include "Program.h"
#include "clang/AST/Attr.h"
#include "llvm/Support/SaveAndRestore.h"

using namespace clang;
using namespace clang::interp;
Expand Down Expand Up @@ -2500,17 +2501,18 @@ bool Compiler<Emitter>::VisitAbstractConditionalOperator(
const Expr *TrueExpr = E->getTrueExpr();
const Expr *FalseExpr = E->getFalseExpr();

auto visitChildExpr = [&](const Expr *E) -> bool {
LocalScope<Emitter> S(this);
if (!this->delegate(E))
return false;
return S.destroyLocals();
};
// The TrueExpr and FalseExpr of a conditional operator do _not_ create a
// scope, which means the local variables created within them unconditionally
// always exist. However, we need to later differentiate which branch was
// taken and only destroy the varibles of the active branch. This is what the
// "enabled" flags on local variables are used for.
llvm::SaveAndRestore LAAA(this->VarScope->LocalsAlwaysEnabled,
/*NewValue=*/false);

if (std::optional<bool> BoolValue = getBoolValue(Condition)) {
if (*BoolValue)
return visitChildExpr(TrueExpr);
return visitChildExpr(FalseExpr);
return this->delegate(TrueExpr);
return this->delegate(FalseExpr);
}

bool IsBcpCall = false;
Expand Down Expand Up @@ -2542,13 +2544,15 @@ bool Compiler<Emitter>::VisitAbstractConditionalOperator(

if (!this->jumpFalse(LabelFalse))
return false;
if (!visitChildExpr(TrueExpr))
if (!this->delegate(TrueExpr))
return false;

if (!this->jump(LabelEnd))
return false;
this->emitLabel(LabelFalse);
if (!visitChildExpr(FalseExpr))
if (!this->delegate(FalseExpr))
return false;

this->fallthrough(LabelEnd);
this->emitLabel(LabelEnd);

Expand Down Expand Up @@ -2955,10 +2959,15 @@ bool Compiler<Emitter>::VisitMaterializeTemporaryExpr(
bool IsVolatile = SubExpr->getType().isVolatileQualified();
unsigned LocalIndex = allocateLocalPrimitive(
E, *SubExprT, IsConst, IsVolatile, E->getExtendingDecl());
if (!this->VarScope->LocalsAlwaysEnabled &&
!this->emitEnableLocal(LocalIndex, E))
return false;

if (!this->visit(SubExpr))
return false;
if (!this->emitSetLocal(*SubExprT, LocalIndex, E))
return false;

return this->emitGetPtrLocal(LocalIndex, E);
}

Expand All @@ -2968,6 +2977,11 @@ bool Compiler<Emitter>::VisitMaterializeTemporaryExpr(
if (UnsignedOrNone LocalIndex =
allocateLocal(E, Inner->getType(), E->getExtendingDecl())) {
InitLinkScope<Emitter> ILS(this, InitLink::Temp(*LocalIndex));

if (!this->VarScope->LocalsAlwaysEnabled &&
!this->emitEnableLocal(*LocalIndex, E))
return false;

if (!this->emitGetPtrLocal(*LocalIndex, E))
return false;
return this->visitInitializer(SubExpr) && this->emitFinishInit(E);
Expand Down
39 changes: 32 additions & 7 deletions clang/lib/AST/ByteCode/Compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -477,12 +477,14 @@ template <class Emitter> class VariableScope {
VariableScope(Compiler<Emitter> *Ctx, const ValueDecl *VD,
ScopeKind Kind = ScopeKind::Block)
: Ctx(Ctx), Parent(Ctx->VarScope), ValDecl(VD), Kind(Kind) {
if (Parent)
this->LocalsAlwaysEnabled = Parent->LocalsAlwaysEnabled;
Ctx->VarScope = this;
}

virtual ~VariableScope() { Ctx->VarScope = this->Parent; }

virtual void addLocal(const Scope::Local &Local) {
virtual void addLocal(Scope::Local Local) {
llvm_unreachable("Shouldn't be called");
}

Expand Down Expand Up @@ -519,7 +521,6 @@ template <class Emitter> class VariableScope {
if (!P)
break;
}

// Add to this scope.
this->addLocal(Local);
}
Expand All @@ -530,6 +531,11 @@ template <class Emitter> class VariableScope {
VariableScope *getParent() const { return Parent; }
ScopeKind getKind() const { return Kind; }

/// Whether locals added to this scope are enabled by default.
/// This is almost always true, except for the two branches
/// of a conditional operator.
bool LocalsAlwaysEnabled = true;

protected:
/// Compiler instance.
Compiler<Emitter> *Ctx;
Expand Down Expand Up @@ -576,29 +582,48 @@ template <class Emitter> class LocalScope : public VariableScope<Emitter> {
return Success;
}

void addLocal(const Scope::Local &Local) override {
void addLocal(Scope::Local Local) override {
if (!Idx) {
Idx = static_cast<unsigned>(this->Ctx->Descriptors.size());
this->Ctx->Descriptors.emplace_back();
this->Ctx->emitInitScope(*Idx, {});
}

Local.EnabledByDefault = this->LocalsAlwaysEnabled;
this->Ctx->Descriptors[*Idx].emplace_back(Local);
}

bool emitDestructors(const Expr *E = nullptr) override {
if (!Idx)
return true;
assert(!this->Ctx->Descriptors[*Idx].empty());

// Emit destructor calls for local variables of record
// type with a destructor.
for (Scope::Local &Local : llvm::reverse(this->Ctx->Descriptors[*Idx])) {
if (Local.Desc->hasTrivialDtor())
continue;
if (!this->Ctx->emitGetPtrLocal(Local.Offset, E))
return false;

if (!this->Ctx->emitDestructionPop(Local.Desc, Local.Desc->getLoc()))
return false;
if (!Local.EnabledByDefault) {
typename Emitter::LabelTy EndLabel = this->Ctx->getLabel();
if (!this->Ctx->emitGetLocalEnabled(Local.Offset, E))
return false;
if (!this->Ctx->jumpFalse(EndLabel))
return false;

if (!this->Ctx->emitGetPtrLocal(Local.Offset, E))
return false;

if (!this->Ctx->emitDestructionPop(Local.Desc, Local.Desc->getLoc()))
return false;

this->Ctx->emitLabel(EndLabel);
} else {
if (!this->Ctx->emitGetPtrLocal(Local.Offset, E))
return false;
if (!this->Ctx->emitDestructionPop(Local.Desc, Local.Desc->getLoc()))
return false;
}

removeIfStoredOpaqueValue(Local);
}
Expand Down
29 changes: 28 additions & 1 deletion clang/lib/AST/ByteCode/EvalEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ Scope::Local EvalEmitter::createLocal(Descriptor *D) {
InlineDescriptor &Desc = *reinterpret_cast<InlineDescriptor *>(B->rawData());
Desc.Desc = D;
Desc.Offset = sizeof(InlineDescriptor);
Desc.IsActive = true;
Desc.IsActive = false;
Desc.IsBase = false;
Desc.IsFieldMutable = false;
Desc.IsConst = false;
Expand Down Expand Up @@ -322,6 +322,33 @@ bool EvalEmitter::emitDestroy(uint32_t I, SourceInfo Info) {
return true;
}

bool EvalEmitter::emitGetLocalEnabled(uint32_t I, SourceInfo Info) {
if (!isActive())
return true;

Block *B = getLocal(I);
const InlineDescriptor &Desc =
*reinterpret_cast<InlineDescriptor *>(B->rawData());

S.Stk.push<bool>(Desc.IsActive);
return true;
}

bool EvalEmitter::emitEnableLocal(uint32_t I, SourceInfo Info) {
if (!isActive())
return true;

// FIXME: This is a little dirty, but to avoid adding a flag to
// InlineDescriptor that's only ever useful on the toplevel of local
// variables, we reuse the IsActive flag for the enabled state. We should
// probably use a different struct than InlineDescriptor for the block-level
// inline descriptor of local varaibles.
Block *B = getLocal(I);
InlineDescriptor &Desc = *reinterpret_cast<InlineDescriptor *>(B->rawData());
Desc.IsActive = true;
return true;
}

/// Global temporaries (LifetimeExtendedTemporary) carry their value
/// around as an APValue, which codegen accesses.
/// We set their value once when creating them, but we don't update it
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/AST/ByteCode/Function.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ class Scope final {
unsigned Offset;
/// Descriptor of the local.
Descriptor *Desc;
/// If the cleanup for this local should be emitted.
bool EnabledByDefault = true;
};

using LocalVectorTy = llvm::SmallVector<Local, 8>;
Expand Down
12 changes: 12 additions & 0 deletions clang/lib/AST/ByteCode/Interp.h
Original file line number Diff line number Diff line change
Expand Up @@ -2474,6 +2474,18 @@ inline bool InitScope(InterpState &S, CodePtr OpPC, uint32_t I) {
return true;
}

inline bool EnableLocal(InterpState &S, CodePtr OpPC, uint32_t I) {
assert(!S.Current->isLocalEnabled(I));
S.Current->enableLocal(I);
return true;
}

inline bool GetLocalEnabled(InterpState &S, CodePtr OpPC, uint32_t I) {
assert(S.Current);
S.Stk.push<bool>(S.Current->isLocalEnabled(I));
return true;
}

//===----------------------------------------------------------------------===//
// Cast, CastFP
//===----------------------------------------------------------------------===//
Expand Down
12 changes: 12 additions & 0 deletions clang/lib/AST/ByteCode/InterpFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,23 @@ void InterpFrame::destroyScopes() {
void InterpFrame::initScope(unsigned Idx) {
if (!Func)
return;

for (auto &Local : Func->getScope(Idx).locals()) {
localBlock(Local.Offset)->invokeCtor();
}
}

void InterpFrame::enableLocal(unsigned Idx) {
assert(Func);

// FIXME: This is a little dirty, but to avoid adding a flag to
// InlineDescriptor that's only ever useful on the toplevel of local
// variables, we reuse the IsActive flag for the enabled state. We should
// probably use a different struct than InlineDescriptor for the block-level
// inline descriptor of local varaibles.
localInlineDesc(Idx)->IsActive = true;
}

void InterpFrame::destroy(unsigned Idx) {
for (auto &Local : Func->getScope(Idx).locals_reverse()) {
S.deallocate(localBlock(Local.Offset));
Expand Down
4 changes: 4 additions & 0 deletions clang/lib/AST/ByteCode/InterpFrame.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ class InterpFrame final : public Frame {
void destroy(unsigned Idx);
void initScope(unsigned Idx);
void destroyScopes();
void enableLocal(unsigned Idx);
bool isLocalEnabled(unsigned Idx) const {
return localInlineDesc(Idx)->IsActive;
}

/// Describes the frame with arguments for diagnostic purposes.
void describe(llvm::raw_ostream &OS) const override;
Expand Down
10 changes: 10 additions & 0 deletions clang/lib/AST/ByteCode/Opcodes.td
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,16 @@ def InitScope : Opcode {
let Args = [ArgUint32];
}

def GetLocalEnabled : Opcode {
let Args = [ArgUint32];
let HasCustomEval = 1;
}

def EnableLocal : Opcode {
let Args = [ArgUint32];
let HasCustomEval = 1;
}

//===----------------------------------------------------------------------===//
// Constants
//===----------------------------------------------------------------------===//
Expand Down
23 changes: 23 additions & 0 deletions clang/test/AST/ByteCode/cxx23.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -473,3 +473,26 @@ namespace AIEWithIndex0Narrows {
}
static_assert(test());
}

#if __cplusplus >= 202302L
namespace InactiveLocalsInConditionalOp {
struct A { constexpr A(){}; ~A(); constexpr int get() { return 10; } }; // all-note 2{{declared here}}
constexpr int get(bool b) {
return b ? A().get() : 1; // all-note {{non-constexpr function '~A' cannot be used in a constant expression}}
}
static_assert(get(false) == 1, "");
static_assert(get(true) == 10, ""); // all-error {{not an integral constant expression}} \
// all-note {{in call to}}

static_assert( (false ? A().get() : 1) == 1);
static_assert( (true ? A().get() : 1) == 1); // all-error {{not an integral constant expression}} \
// all-note {{non-constexpr function '~A' cannot be used in a constant expression}}

constexpr bool test2(bool b) {
unsigned long __ms = b ? (const unsigned long &)0 : __ms;
return true;
}
static_assert(test2(true));

}
#endif