diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.h b/clang/lib/AST/Interp/ByteCodeExprGen.h --- a/clang/lib/AST/Interp/ByteCodeExprGen.h +++ b/clang/lib/AST/Interp/ByteCodeExprGen.h @@ -71,9 +71,13 @@ bool VisitCastExpr(const CastExpr *E); bool VisitConstantExpr(const ConstantExpr *E); bool VisitIntegerLiteral(const IntegerLiteral *E); + bool VisitStringLiteral(const StringLiteral *E); bool VisitParenExpr(const ParenExpr *E); bool VisitBinaryOperator(const BinaryOperator *E); bool VisitUnaryMinus(const UnaryOperator *E); + bool VisitUnaryDeref(const UnaryOperator *E); + bool VisitUnaryAddrOf(const UnaryOperator *E); + bool VisitUnaryLNot(const UnaryOperator *E); bool VisitCallExpr(const CallExpr *E); // Fallback methods for nodes which are not yet implemented. @@ -150,6 +154,11 @@ bool emitFunctionCall(const FunctionDecl *Callee, llvm::Optional T, const Expr *Call); + /// Visits a short-circuit logical operator: && or ||. + bool visitShortCircuit(const BinaryOperator *E); + /// Compiles an offset calculation. + bool visitOffset(const Expr *Ptr, const Expr *Off, const Expr *E, UnaryFn Op); + /// Compiles an assignment. bool visitAssign(PrimType T, const BinaryOperator *BO); enum class DerefKind { diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.cpp b/clang/lib/AST/Interp/ByteCodeExprGen.cpp --- a/clang/lib/AST/Interp/ByteCodeExprGen.cpp +++ b/clang/lib/AST/Interp/ByteCodeExprGen.cpp @@ -229,6 +229,15 @@ return false; } +template +bool ByteCodeExprGen::VisitStringLiteral(const StringLiteral *E) { + if (InitFn) + return this->bail(E); + if (DiscardResult) + return true; + return this->emitGetPtrGlobal(P.createGlobalString(E), E); +} + template bool ByteCodeExprGen::VisitParenExpr(const ParenExpr *PE) { return this->Visit(PE->getSubExpr()); @@ -267,22 +276,22 @@ switch (BO->getOpcode()) { case BO_Add: { if (isPointer(*LT) && !isPointer(*RT)) - return this->bail(BO); - if (isPointer(*RT) && !isPointer(*LT)) - return this->bail(BO); + return visitOffset(LHS, RHS, BO, &ByteCodeExprGen::emitAddOffset); + if (!isPointer(*RT) && isPointer(*LT)) + return visitOffset(RHS, LHS, BO, &ByteCodeExprGen::emitAddOffset); break; } case BO_Sub: { if (isPointer(*LT) && isPointer(*RT)) return this->bail(BO); - if (isPointer(*LT) && isPointer(*RT)) - return this->bail(BO); + if (isPointer(*LT) && !isPointer(*RT)) + return visitOffset(LHS, RHS, BO, &ByteCodeExprGen::emitSubOffset); break; } case BO_LOr: case BO_LAnd: - return this->bail(BO); + return visitShortCircuit(BO); default: break; @@ -333,6 +342,36 @@ return this->bail(UM); } +template +bool ByteCodeExprGen::VisitUnaryDeref(const UnaryOperator *E) { + if (!this->Visit(E->getSubExpr())) + return false; + if (DiscardResult) + return true; + if (needsAdjust(E->getType())) + return this->emitNarrowPtr(E); + return true; +} + +template +bool ByteCodeExprGen::VisitUnaryAddrOf(const UnaryOperator *E) { + if (!this->Visit(E->getSubExpr())) + return false; + if (DiscardResult) + return true; + if (needsAdjust(E->getType())) + return this->emitExpandPtr(E); + return true; +} + +template +bool ByteCodeExprGen::VisitUnaryLNot(const UnaryOperator *UO) { + if (!this->Visit(UO->getSubExpr())) + return false; + PrimType T = *classify(UO->getType()); + return DiscardResult ? true : this->emitLogicalNot(T, UO); +} + template bool ByteCodeExprGen::VisitCallExpr(const CallExpr *CE) { // Emit the pointer to build the return value into. @@ -415,6 +454,45 @@ } } + +template +bool ByteCodeExprGen::visitOffset(const Expr *Ptr, const Expr *Off, + const Expr *E, UnaryFn Op) { + if (!visit(Ptr)) + return false; + if (!visit(Off)) + return false; + if (!(this->*Op)(*classify(Off->getType()), E)) + return false; + return DiscardResult ? this->emitPopPtr(E) : true; +} + +template +bool ByteCodeExprGen::visitShortCircuit(const BinaryOperator *E) { + if (!visitBool(E->getLHS())) + return false; + + LabelTy Short = this->getLabel(); + const bool IsTrue = E->getOpcode() == BO_LAnd; + if (DiscardResult) { + if (!(IsTrue ? this->jumpFalse(Short) : this->jumpTrue(Short))) + return false; + if (!this->discard(E->getRHS())) + return false; + } else { + if (!this->emitDupBool(E)) + return false; + if (!(IsTrue ? this->jumpFalse(Short) : this->jumpTrue(Short))) + return false; + if (!this->emitPopBool(E)) + return false; + if (!visitBool(E->getRHS())) + return false; + } + this->fallthrough(Short); + return true; +} + template bool ByteCodeExprGen::visitAssign(PrimType T, const BinaryOperator *BO) { diff --git a/clang/lib/AST/Interp/Interp.h b/clang/lib/AST/Interp/Interp.h --- a/clang/lib/AST/Interp/Interp.h +++ b/clang/lib/AST/Interp/Interp.h @@ -199,6 +199,11 @@ return true; } +inline bool GetPtrGlobal(InterpState &S, CodePtr OpPC, uint32_t I) { + S.Stk.push(S.P.getPtrGlobal(I)); + return true; +} + //===----------------------------------------------------------------------===// // Load, Store, Init //===----------------------------------------------------------------------===// @@ -287,6 +292,22 @@ return false; } +//===----------------------------------------------------------------------===// +// NarrowPtr, ExpandPtr +//===----------------------------------------------------------------------===// + +inline bool NarrowPtr(InterpState &S, CodePtr OpPC) { + const Pointer &Ptr = S.Stk.pop(); + S.Stk.push(Ptr.narrow()); + return true; +} + +inline bool ExpandPtr(InterpState &S, CodePtr OpPC) { + const Pointer &Ptr = S.Stk.pop(); + S.Stk.push(Ptr.expand()); + return true; +} + //===----------------------------------------------------------------------===// // Trap //===----------------------------------------------------------------------===// @@ -298,6 +319,8 @@ } #include "Opcodes/Comparison.h" +#include "Opcodes/Logical.h" +#include "Opcodes/Offset.h" } // namespace interp } // namespace clang diff --git a/clang/lib/AST/Interp/Opcodes.td b/clang/lib/AST/Interp/Opcodes.td --- a/clang/lib/AST/Interp/Opcodes.td +++ b/clang/lib/AST/Interp/Opcodes.td @@ -239,6 +239,20 @@ // Offset of parameter. let Args = [ArgUint32]; } +// [] -> [Pointer] +def GetPtrGlobal : Opcode { + // Index of global. + let Args = [ArgUint32]; +} + +//===----------------------------------------------------------------------===// +// Pointer manipulation +//===----------------------------------------------------------------------===// + +// [Pointer] -> [Pointer] +def NarrowPtr : Opcode; +// [Pointer] -> [Pointer] +def ExpandPtr : Opcode; //===----------------------------------------------------------------------===// // Direct field accessors @@ -296,6 +310,15 @@ // [Pointer, Value] -> [] def StorePop : StoreOpcode {} +//===----------------------------------------------------------------------===// +// Pointer arithmetic. +//===----------------------------------------------------------------------===// + +// [Pointer, Integral] -> [Pointer] +def AddOffset : AluOpcode; +// [Pointer, Integral] -> [Pointer] +def SubOffset : AluOpcode; + //===----------------------------------------------------------------------===// // Binary operators. //===----------------------------------------------------------------------===// @@ -316,6 +339,11 @@ let Types = [AllTypeClass]; let HasGroup = 1; } +// [Value] -> [Bool] +def LogicalNot : Opcode { + let Types = [AllTypeClass]; + let HasGroup = 1; +} //===----------------------------------------------------------------------===// // Comparison opcodes. diff --git a/clang/lib/AST/Interp/Opcodes/Logical.h b/clang/lib/AST/Interp/Opcodes/Logical.h new file mode 100644 --- /dev/null +++ b/clang/lib/AST/Interp/Opcodes/Logical.h @@ -0,0 +1,24 @@ +//===--- Logical.h - Logical instructions -----------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Implementation of logical instructions: LNot +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_AST_INTERP_OPCODES_LOGICAL_H +#define LLVM_CLANG_AST_INTERP_OPCODES_LOGICAL_H + + +template +bool LogicalNot(InterpState &S, CodePtr OpPC) { + S.Stk.push(S.Stk.pop::T>().isZero()); + return true; +} + + +#endif diff --git a/clang/lib/AST/Interp/Opcodes/Offset.h b/clang/lib/AST/Interp/Opcodes/Offset.h new file mode 100644 --- /dev/null +++ b/clang/lib/AST/Interp/Opcodes/Offset.h @@ -0,0 +1,86 @@ +//===--- PoitnerArithmetic.h - Pointer arithmetic instructions --*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Implementation of the pointer offset opcodes: AddOffset, SubOffset +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_AST_INTERP_OPCODES_OFFSET_H +#define LLVM_CLANG_AST_INTERP_OPCODES_OFFSET_H + +namespace detail { + +template +bool OffsetHelper(InterpState &S, CodePtr OpPC) { + // Fetch the pointer and the offset. + const T &Offset = S.Stk.pop(); + const Pointer &Ptr = S.Stk.pop(); + if (!S.CheckNull(OpPC, Ptr, CSK_ArrayIndex)) + return false; + if (!S.CheckRange(OpPC, Ptr, CSK_ArrayToPointer)) + return false; + + // Get a version of the index comparable to the type. + T Index = T::from(Ptr.getIndex(), Offset.bitWidth()); + // A zero offset does not change the pointer, but in the case of an array + // it has to be adjusted to point to the first element instead of the array. + if (Offset.isZero()) { + S.Stk.push(Index.isZero() ? Ptr.atIndex(0) : Ptr); + return true; + } + // Arrays of unknown bounds cannot have pointers into them. + if (!S.CheckArray(OpPC, Ptr)) + return false; + + // Compute the largest index into the array. + unsigned MaxIndex = Ptr.getNumElems(); + + // Helper to report an invalid offset, computed as APSInt. + auto InvalidOffset = [&]() { + const unsigned Bits = Offset.bitWidth(); + APSInt APOffset(Offset.toAPSInt().extend(Bits + 2), false); + APSInt APIndex(Index.toAPSInt().extend(Bits + 2), false); + APSInt NewIndex = Add ? (APIndex + APOffset) : (APIndex - APOffset); + S.CCEDiag(S.getSource(OpPC), diag::note_constexpr_array_index) + << NewIndex << /*array*/ static_cast(!Ptr.inArray()) + << static_cast(MaxIndex); + return false; + }; + + // If the new offset would be negative, bail out. + if (Add && Offset.isNegative() && (Offset.isMin() || -Offset > Index)) + return InvalidOffset(); + if (!Add && Offset.isPositive() && Index < Offset) + return InvalidOffset(); + + // If the new offset would be out of bounds, bail out. + unsigned MaxOffset = MaxIndex - Ptr.getIndex(); + if (Add && Offset.isPositive() && Offset > MaxOffset) + return InvalidOffset(); + if (!Add && Offset.isNegative() && (Offset.isMin() || -Offset > MaxOffset)) + return InvalidOffset(); + + // Offset is valid - compute it on unsigned. + int64_t WideIndex = static_cast(Index); + int64_t WideOffset = static_cast(Offset); + int64_t Result = Add ? (WideIndex + WideOffset) : (WideIndex - WideOffset); + S.Stk.push(Ptr.atIndex(static_cast(Result))); + return true; +} + +} // namespace detail + +template bool AddOffset(InterpState &S, CodePtr OpPC) { + return detail::OffsetHelper::T, true>(S, OpPC); +} + +template bool SubOffset(InterpState &S, CodePtr OpPC) { + return detail::OffsetHelper::T, false>(S, OpPC); +} + +#endif diff --git a/clang/lib/AST/Interp/Program.h b/clang/lib/AST/Interp/Program.h --- a/clang/lib/AST/Interp/Program.h +++ b/clang/lib/AST/Interp/Program.h @@ -63,6 +63,14 @@ public: Program(Context &Ctx) : Ctx(Ctx) {} + /// Emits a string literal among global data. + unsigned createGlobalString(const StringLiteral *S); + + /// Returns a pointer to a global. + Pointer getPtrGlobal(GlobalLocation Idx); + /// Returns a pointer to a global by index. + Pointer getPtrGlobal(const ValueDecl *VD); + /// Creates a new function from a code range. template Function *createFunction(const FunctionDecl *Def, Ts &&... Args) { @@ -95,7 +103,11 @@ private: friend class DeclScope; - /// Reference to the VM context. + + llvm::Optional createGlobal(const DeclTy &D, QualType Ty, + bool IsStatic, bool IsExtern); + + /// Reference to the VM context. Context &Ctx; /// Mapping from decls to cached bytecode functions. llvm::DenseMap> Funcs; @@ -108,9 +120,38 @@ /// Custom allocator for global storage. using PoolAllocTy = llvm::BumpPtrAllocatorImpl; + /// Descriptor + storage for a global object. + /// + /// Global objects never go out of scope, thus they do not track pointers. + class Global { + public: + /// Create a global descriptor for string literals. + template + Global(Tys... Args) : B(std::forward(Args)...) {} + + /// Allocates the global in the pool, reserving storate for data. + void *operator new(size_t Meta, PoolAllocTy &Alloc, size_t Data) { + return Alloc.Allocate(Meta + Data, alignof(void *)); + } + + /// Return a pointer to the data. + char *data() { return B.data(); } + /// Return a pointer to the block. + Block *block() { return &B; } + + private: + /// Required metadata - does not actually track pointers. + Block B; + }; + /// Allocator for globals. PoolAllocTy Allocator; + /// Global objects. + std::vector Globals; + /// Cached global indices. + llvm::DenseMap GlobalIndices; + /// Creates a new descriptor. template Descriptor *allocateDescriptor(Ts &&... Args) { return new (Allocator) Descriptor(std::forward(Args)...); diff --git a/clang/lib/AST/Interp/Program.cpp b/clang/lib/AST/Interp/Program.cpp --- a/clang/lib/AST/Interp/Program.cpp +++ b/clang/lib/AST/Interp/Program.cpp @@ -18,6 +18,78 @@ using namespace clang; using namespace clang::interp; +unsigned Program::createGlobalString(const StringLiteral *S) { + const size_t CharWidth = S->getCharByteWidth(); + const size_t BitWidth = CharWidth * Ctx.getCharBit(); + + PrimType CharType; + switch (CharWidth) { + case 1: + CharType = PT_Sint8; + break; + case 2: + CharType = PT_Uint16; + break; + case 4: + CharType = PT_Uint32; + break; + default: + llvm_unreachable("unsupported character width"); + } + + // Create a descriptor for the string. + Descriptor *Desc = allocateDescriptor(S, CharType, S->getLength() + 1, + /*isConst=*/true, + /*isTemporary=*/false, + /*isMutable=*/false); + + // Allocate storage for the string. + // The byte length does not include the null terminator. + unsigned I = Globals.size(); + unsigned Sz = Desc->getAllocSize(); + auto *G = new (Allocator, Sz) Global(Desc, /*isStatic=*/true, + /*isExtern=*/false); + Globals.push_back(G); + + // Construct the string in storage. + const Pointer Ptr(G->block()); + for (unsigned I = 0, N = S->getLength(); I <= N; ++I) { + Pointer Field = Ptr.atIndex(I).narrow(); + const uint32_t CodePoint = I == N ? 0 : S->getCodeUnit(I); + switch (CharType) { + case PT_Sint8: { + using T = PrimConv::T; + Field.deref() = T::from(CodePoint, BitWidth); + break; + } + case PT_Uint16: { + using T = PrimConv::T; + Field.deref() = T::from(CodePoint, BitWidth); + break; + } + case PT_Uint32: { + using T = PrimConv::T; + Field.deref() = T::from(CodePoint, BitWidth); + break; + } + default: + llvm_unreachable("unsupported character type"); + } + } + return I; +} + +Pointer Program::getPtrGlobal(GlobalLocation G) { + assert(G.Index < Globals.size()); + return Pointer(Globals[G.Index]->block()); +} + +Pointer Program::getPtrGlobal(const ValueDecl *VD) { + auto It = GlobalIndices.find(VD); + assert(It != GlobalIndices.end() && "missing global"); + return Pointer(Globals[It->second]->block()); +} + Function *Program::getFunction(const FunctionDecl *F) { F = F->getDefinition(); auto It = Funcs.find(F); @@ -36,4 +108,3 @@ // A relocation which traps if not resolved. return nullptr; } - diff --git a/clang/test/AST/Interp/string.cpp b/clang/test/AST/Interp/string.cpp new file mode 100644 --- /dev/null +++ b/clang/test/AST/Interp/string.cpp @@ -0,0 +1,27 @@ +// RUN: %clang_cc1 -std=c++17 -fsyntax-only -fexperimental-new-constant-interpreter %s -verify +// RUN: %clang_cc1 -std=c++17 -fsyntax-only %s -verify +// expected-no-diagnostics + +constexpr bool streq(const char *a, const char *b) { + while (*a && *a == *b) { + a = a + 1; + b = b + 1; + } + return *a == *b; +} + +constexpr bool is_equal() { + const char *a = "Hello"; + const char *b = "Hello"; + return streq(a, b); +} + +static_assert(is_equal()); + +constexpr bool not_equal() { + const char *a = "HA"; + const char *b = "HB"; + return streq(a, b); +} + +static_assert(!streq("HA", "HB"));