34 changes: 18 additions & 16 deletions clang/lib/AST/Interp/ByteCodeStmtGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,7 @@ bool ByteCodeStmtGen<Emitter>::visitReturnStmt(const ReturnStmt *RS) {
return this->emitRet(*ReturnType, RS);
} else {
// RVO - construct the value in the return location.
auto ReturnLocation = [this, RE] { return this->emitGetParamPtr(0, RE); };
if (!this->visitInitializer(RE, ReturnLocation))
if (!this->visitInitializer(RE))
return false;
this->emitCleanup();
return this->emitRetVoid(RS);
Expand Down Expand Up @@ -232,32 +231,35 @@ bool ByteCodeStmtGen<Emitter>::visitIfStmt(const IfStmt *IS) {

template <class Emitter>
bool ByteCodeStmtGen<Emitter>::visitVarDecl(const VarDecl *VD) {
auto DT = VD->getType();

if (!VD->hasLocalStorage()) {
// No code generation required.
return true;
}

// Integers, pointers, primitives.
if (Optional<PrimType> T = this->classify(DT)) {
auto Off = this->allocateLocalPrimitive(VD, *T, DT.isConstQualified());
// Compile the initialiser in its own scope.
if (Optional<PrimType> T = this->classify(VD->getType())) {
const Expr *Init = VD->getInit();

if (!Init)
return false;

unsigned Offset =
this->allocateLocalPrimitive(VD, *T, VD->getType().isConstQualified());
// Compile the initializer in its own scope.
{
ExprScope<Emitter> Scope(this);
if (!this->visit(VD->getInit()))
if (!this->visit(Init))
return false;
}
// Set the value.
return this->emitSetLocal(*T, Off, VD);
} else {
// Composite types - allocate storage and initialize it.
if (auto Off = this->allocateLocal(VD)) {
return this->visitLocalInitializer(VD->getInit(), *Off);
} else {
return this->bail(VD);
}
return this->emitSetLocal(*T, Offset, VD);
}

// Composite types - allocate storage and initialize it.
if (Optional<unsigned> Offset = this->allocateLocal(VD))
return this->visitLocalInitializer(VD->getInit(), *Offset);

return this->bail(VD);
}

namespace clang {
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/AST/Interp/Context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ bool Context::evaluateAsInitializer(State &Parent, const VarDecl *VD,

const LangOptions &Context::getLangOpts() const { return Ctx.getLangOpts(); }

llvm::Optional<PrimType> Context::classify(QualType T) {
llvm::Optional<PrimType> Context::classify(QualType T) const {
if (T->isReferenceType() || T->isPointerType()) {
return PT_Ptr;
}
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/AST/Interp/Context.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class Context {
unsigned getCharBit() const;

/// Classifies an expression.
llvm::Optional<PrimType> classify(QualType T);
llvm::Optional<PrimType> classify(QualType T) const;

private:
/// Runs a function.
Expand Down
18 changes: 18 additions & 0 deletions clang/lib/AST/Interp/EvalEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,24 @@ template <PrimType OpType> bool EvalEmitter::emitRet(const SourceInfo &Info) {
return ReturnValue<T>(S.Stk.pop<T>(), Result);
}

template <PrimType OpType>
bool EvalEmitter::emitCall(const Function *Func, const SourceInfo &Info) {

S.Current =
new InterpFrame(S, const_cast<Function *>(Func), S.Current, {}, {});
// Result of call will be on the stack and needs to be handled by the caller.
return Interpret(S, Result);
}

bool EvalEmitter::emitCallVoid(const Function *Func, const SourceInfo &Info) {
APValue VoidResult;
S.Current =
new InterpFrame(S, const_cast<Function *>(Func), S.Current, {}, {});
bool Success = Interpret(S, VoidResult);
assert(VoidResult.isAbsent());
return Success;
}

bool EvalEmitter::emitRetVoid(const SourceInfo &Info) { return true; }

bool EvalEmitter::emitRetValue(const SourceInfo &Info) {
Expand Down
10 changes: 9 additions & 1 deletion clang/lib/AST/Interp/Function.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class Function {

/// Returns the size of the function's local stack.
unsigned getFrameSize() const { return FrameSize; }
/// Returns the size of the argument stackx
/// Returns the size of the argument stack.
unsigned getArgSize() const { return ArgSize; }

/// Returns a pointer to the start of the code.
Expand Down Expand Up @@ -112,6 +112,9 @@ class Function {
/// Checks if the function is a constructor.
bool isConstructor() const { return isa<CXXConstructorDecl>(F); }

/// Checks if the function is fully done compiling.
bool isFullyCompiled() const { return IsFullyCompiled; }

private:
/// Construct a function representing an actual function.
Function(Program &P, const FunctionDecl *F, unsigned ArgSize,
Expand All @@ -128,6 +131,8 @@ class Function {
IsValid = true;
}

void setIsFullyCompiled(bool FC) { IsFullyCompiled = FC; }

private:
friend class Program;
friend class ByteCodeEmitter;
Expand All @@ -154,6 +159,9 @@ class Function {
llvm::DenseMap<unsigned, ParamDescriptor> Params;
/// Flag to indicate if the function is valid.
bool IsValid = false;
/// Flag to indicate if the function is done being
/// compiled to bytecode.
bool IsFullyCompiled = false;

public:
/// Dumps the disassembled bytecode to \c llvm::errs().
Expand Down
4 changes: 2 additions & 2 deletions clang/lib/AST/Interp/Integral.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ template <unsigned Bits, bool Signed> class Integral {
return APSInt(APInt(Bits, static_cast<uint64_t>(V), Signed), !Signed);
}
APSInt toAPSInt(unsigned NumBits) const {
if (Signed)
if constexpr (Signed)
return APSInt(toAPSInt().sextOrTrunc(NumBits), !Signed);
else
return APSInt(toAPSInt().zextOrTrunc(NumBits), !Signed);
Expand Down Expand Up @@ -171,7 +171,7 @@ template <unsigned Bits, bool Signed> class Integral {
}

template <bool SrcSign> static Integral from(Integral<0, SrcSign> Value) {
if (SrcSign)
if constexpr (SrcSign)
return Integral(Value.V.getSExtValue());
else
return Integral(Value.V.getZExtValue());
Expand Down
27 changes: 27 additions & 0 deletions clang/lib/AST/Interp/Interp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,27 @@ static bool Ret(InterpState &S, CodePtr &PC, APValue &Result) {
return true;
}

template <PrimType Name, class T = typename PrimConv<Name>::T>
static bool Call(InterpState &S, CodePtr &PC, const Function *Func) {
S.Current =
new InterpFrame(S, const_cast<Function *>(Func), S.Current, PC, {});
APValue CallResult;
// Note that we cannot assert(CallResult.hasValue()) here since
// Ret() above only sets the APValue if the curent frame doesn't
// have a caller set.
return Interpret(S, CallResult);
}

static bool CallVoid(InterpState &S, CodePtr &PC, const Function *Func) {
APValue VoidResult;
S.Current =
new InterpFrame(S, const_cast<Function *>(Func), S.Current, PC, {});
bool Success = Interpret(S, VoidResult);
assert(VoidResult.isAbsent());

return Success;
}

static bool RetVoid(InterpState &S, CodePtr &PC, APValue &Result) {
S.CallStackDepth--;

Expand Down Expand Up @@ -398,7 +419,13 @@ bool CheckPure(InterpState &S, CodePtr OpPC, const CXXMethodDecl *MD) {
S.Note(MD->getLocation(), diag::note_declared_at);
return false;
}

bool Interpret(InterpState &S, APValue &Result) {
// The current stack frame when we started Interpret().
// This is being used by the ops to determine wheter
// to return from this function and thus terminate
// interpretation.
const InterpFrame *StartFrame = S.Current;
assert(!S.Current->isRoot());
CodePtr PC = S.Current->getPC();

Expand Down
4 changes: 4 additions & 0 deletions clang/lib/AST/Interp/Interp.h
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,9 @@ bool InitPop(InterpState &S, CodePtr OpPC) {
return true;
}

/// 1) Pops the value from the stack
/// 2) Peeks a pointer and gets its index \Idx
/// 3) Sets the value on the pointer, leaving the pointer on the stack.
template <PrimType Name, class T = typename PrimConv<Name>::T>
bool InitElem(InterpState &S, CodePtr OpPC, uint32_t Idx) {
const T &Value = S.Stk.pop<T>();
Expand All @@ -732,6 +735,7 @@ bool InitElem(InterpState &S, CodePtr OpPC, uint32_t Idx) {
return true;
}

/// The same as InitElem, but pops the pointer as well.
template <PrimType Name, class T = typename PrimConv<Name>::T>
bool InitElemPop(InterpState &S, CodePtr OpPC, uint32_t Idx) {
const T &Value = S.Stk.pop<T>();
Expand Down
1 change: 1 addition & 0 deletions clang/lib/AST/Interp/InterpFrame.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ class InterpFrame final : public Frame {
private:
/// Returns an original argument from the stack.
template <typename T> const T &stackRef(unsigned Offset) {
assert(Args);
return *reinterpret_cast<const T *>(Args - ArgSize + Offset);
}

Expand Down
29 changes: 25 additions & 4 deletions clang/lib/AST/Interp/Opcodes.td
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def ArgSint64 : ArgType { let Name = "int64_t"; }
def ArgUint64 : ArgType { let Name = "uint64_t"; }
def ArgBool : ArgType { let Name = "bool"; }

def ArgFunction : ArgType { let Name = "Function *"; }
def ArgFunction : ArgType { let Name = "const Function *"; }
def ArgRecord : ArgType { let Name = "Record *"; }

def ArgSema : ArgType { let Name = "const fltSemantics *"; }
Expand Down Expand Up @@ -153,6 +153,22 @@ def RetValue : Opcode {
// [] -> EXIT
def NoRet : Opcode {}


def Call : Opcode {
let Args = [ArgFunction];
let Types = [AllTypeClass];
let ChangesPC = 1;
let HasCustomEval = 1;
let HasGroup = 1;
}

def CallVoid : Opcode {
let Args = [ArgFunction];
let Types = [];
let ChangesPC = 1;
let HasCustomEval = 1;
}

//===----------------------------------------------------------------------===//
// Frame management
//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -187,6 +203,7 @@ def ConstBool : ConstOpcode<Bool, ArgBool>;
// [] -> [Integer]
def Zero : Opcode {
let Types = [AluTypeClass];
let HasGroup = 1;
}

// [] -> [Pointer]
Expand Down Expand Up @@ -409,12 +426,16 @@ def Neg: Opcode {
//===----------------------------------------------------------------------===//
// TODO: Expand this to handle casts between more types.

def Sint32TypeClass : TypeClass {
let Types = [Sint32];
def FromCastTypeClass : TypeClass {
let Types = [Uint32, Sint32, Bool];
}

def ToCastTypeClass : TypeClass {
let Types = [Uint32, Sint32, Bool];
}

def Cast: Opcode {
let Types = [BoolTypeClass, Sint32TypeClass];
let Types = [FromCastTypeClass, ToCastTypeClass];
let HasGroup = 1;
}

Expand Down
5 changes: 3 additions & 2 deletions clang/lib/AST/Interp/Pointer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ APValue Pointer::toAPValue() const {

// Build the path into the object.
Pointer Ptr = *this;
while (Ptr.isField()) {
while (Ptr.isField() || Ptr.isArrayElement()) {
if (Ptr.isArrayElement()) {
Path.push_back(APValue::LValuePathEntry::ArrayIndex(Ptr.getIndex()));
Ptr = Ptr.getArray();
Expand Down Expand Up @@ -154,7 +154,8 @@ bool Pointer::isInitialized() const {
void Pointer::initialize() const {
assert(Pointee && "Cannot initialize null pointer");
Descriptor *Desc = getFieldDesc();
if (Desc->isPrimitiveArray()) {

if (Desc->isArray()) {
if (!Pointee->IsStatic) {
// Primitive array initializer.
InitMap *&Map = getInitMap();
Expand Down
9 changes: 5 additions & 4 deletions clang/lib/AST/Interp/Program.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -334,14 +334,15 @@ Descriptor *Program::createDescriptor(const DeclTy &D, const Type *Ty,
} else {
// Arrays of composites. In this case, the array is a list of pointers,
// followed by the actual elements.
Descriptor *Desc =
Descriptor *ElemDesc =
createDescriptor(D, ElemTy.getTypePtr(), IsConst, IsTemporary);
if (!Desc)
if (!ElemDesc)
return nullptr;
InterpSize ElemSize = Desc->getAllocSize() + sizeof(InlineDescriptor);
InterpSize ElemSize =
ElemDesc->getAllocSize() + sizeof(InlineDescriptor);
if (std::numeric_limits<unsigned>::max() / ElemSize <= NumElems)
return {};
return allocateDescriptor(D, Desc, NumElems, IsConst, IsTemporary,
return allocateDescriptor(D, ElemDesc, NumElems, IsConst, IsTemporary,
IsMutable);
}
}
Expand Down
93 changes: 93 additions & 0 deletions clang/test/AST/Interp/arrays.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// RUN: %clang_cc1 -fexperimental-new-constant-interpreter -verify %s
// RUN: %clang_cc1 -verify=ref %s

constexpr int m = 3;
constexpr const int *foo[][5] = {
{nullptr, &m, nullptr, nullptr, nullptr},
{nullptr, nullptr, &m, nullptr, nullptr},
{nullptr, nullptr, nullptr, &m, nullptr},
};

static_assert(foo[0][0] == nullptr, "");
static_assert(foo[0][1] == &m, "");
static_assert(foo[0][2] == nullptr, "");
static_assert(foo[0][3] == nullptr, "");
static_assert(foo[0][4] == nullptr, "");
static_assert(foo[1][0] == nullptr, "");
static_assert(foo[1][1] == nullptr, "");
static_assert(foo[1][2] == &m, "");
static_assert(foo[1][3] == nullptr, "");
static_assert(foo[1][4] == nullptr, "");
static_assert(foo[2][0] == nullptr, "");
static_assert(foo[2][1] == nullptr, "");
static_assert(foo[2][2] == nullptr, "");
static_assert(foo[2][3] == &m, "");
static_assert(foo[2][4] == nullptr, "");


/// A init list for a primitive value.
constexpr int f{5};
static_assert(f == 5, "");


constexpr int getElement(int i) {
int values[] = {1, 4, 9, 16, 25, 36};
return values[i];
}
static_assert(getElement(1) == 4, "");
static_assert(getElement(5) == 36, "");


template<typename T>
constexpr T getElementOf(T* array, int i) {
return array[i];
}
static_assert(getElementOf(foo[0], 1) == &m, "");


constexpr int data[] = {5, 4, 3, 2, 1};
static_assert(data[0] == 4, ""); // expected-error{{failed}} \
// expected-note{{5 == 4}} \
// ref-error{{failed}} \
// ref-note{{5 == 4}}


constexpr int dynamic[] = {
f, 3, 2 + 5, data[3], *getElementOf(foo[2], 3)
};
static_assert(dynamic[0] == f, "");
static_assert(dynamic[3] == 2, "");


constexpr int dependent[4] = {
0, 1, dependent[0], dependent[1]
};
static_assert(dependent[2] == dependent[0], "");
static_assert(dependent[3] == dependent[1], "");

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wc99-extensions"
#pragma clang diagnostic ignored "-Winitializer-overrides"
constexpr int DI[] = {
[0] = 10,
[1] = 20,
30,
40,
[1] = 50
};
static_assert(DI[0] == 10, "");
static_assert(DI[1] == 50, "");
static_assert(DI[2] == 30, "");
static_assert(DI[3] == 40, "");

/// FIXME: The example below tests ImplicitValueInitExprs, but we can't
/// currently evaluate other parts of it.
#if 0
struct fred {
char s [6];
int n;
};

struct fred y [] = { [0] = { .s[0] = 'q' } };
#endif
#pragma clang diagnostic pop
21 changes: 14 additions & 7 deletions clang/test/AST/Interp/cxx20.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@
// RUN: %clang_cc1 -std=c++20 -verify=ref %s


// expected-no-diagnostics
// ref-no-diagnostics
constexpr int getMinus5() {
int a = 10;
a = -5;
int *p = &a;
return *p;
}
//static_assert(getMinus5() == -5, "") TODO
static_assert(getMinus5() == -5, "");

constexpr int assign() {
int m = 10;
Expand All @@ -20,7 +18,7 @@ constexpr int assign() {

return m;
}
//static_assert(assign() == 20, ""); TODO
static_assert(assign() == 20, "");


constexpr int pointerAssign() {
Expand All @@ -31,15 +29,15 @@ constexpr int pointerAssign() {

return m;
}
//static_assert(pointerAssign() == 12, ""); TODO
static_assert(pointerAssign() == 12, "");

constexpr int pointerDeref() {
int m = 12;
int *p = &m;

return *p;
}
//static_assert(pointerDeref() == 12, ""); TODO
static_assert(pointerDeref() == 12, "");

constexpr int pointerAssign2() {
int m = 10;
Expand All @@ -52,4 +50,13 @@ constexpr int pointerAssign2() {

return v;
}
//static_assert(pointerAssign2() == 12, ""); TODO
static_assert(pointerAssign2() == 12, "");


constexpr int unInitLocal() {
int a;
return a; // ref-note{{read of uninitialized object}}
}
static_assert(unInitLocal() == 0, ""); // expected-error {{not an integral constant expression}} \
// ref-error {{not an integral constant expression}} \
// ref-note {{in call to 'unInitLocal()'}}
75 changes: 75 additions & 0 deletions clang/test/AST/Interp/functions.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// RUN: %clang_cc1 -fexperimental-new-constant-interpreter -verify %s
// RUN: %clang_cc1 -verify=ref %s

// expected-no-diagnostics
// ref-no-diagnostics

constexpr void doNothing() {}
constexpr int gimme5() {
doNothing();
return 5;
}
static_assert(gimme5() == 5, "");


template<typename T> constexpr T identity(T t) { return t; }
static_assert(identity(true), "");
static_assert(identity(true), ""); /// Compiled bytecode should be cached
static_assert(!identity(false), "");

constexpr auto add(int a, int b) -> int {
return identity(a) + identity(b);
}

constexpr int sub(int a, int b) {
return a - b;
}
static_assert(sub(5, 2) == 3, "");
static_assert(sub(0, 5) == -5, "");

constexpr int norm(int n) {
if (n >= 0) {
return identity(n);
}
return -identity(n);
}
static_assert(norm(5) == norm(-5), "");

constexpr int square(int n) {
return norm(n) * norm(n);
}
static_assert(square(2) == 4, "");

constexpr int add_second(int a, int b, bool doAdd = true) {
if (doAdd)
return a + b;
return a;
}
static_assert(add_second(10, 3, true) == 13, "");
static_assert(add_second(10, 3) == 13, "");
static_assert(add_second(300, -20, false) == 300, "");


constexpr int sub(int a, int b, int c) {
return a - b - c;
}
static_assert(sub(10, 8, 2) == 0, "");


constexpr int recursion(int i) {
doNothing();
i = i - 1;
if (i == 0)
return identity(0);

return recursion(i);
}
static_assert(recursion(10) == 0, "");

template<int N = 5>
constexpr decltype(N) getNum() {
return N;
}
static_assert(getNum<-2>() == -2, "");
static_assert(getNum<10>() == 10, "");
static_assert(getNum() == 5, "");
32 changes: 32 additions & 0 deletions clang/test/AST/Interp/literals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,40 @@ constexpr int number = 10;
static_assert(number == 10, "");
static_assert(number != 10, ""); // expected-error{{failed}} \
// ref-error{{failed}} \
// expected-note{{evaluates to}} \
// ref-note{{evaluates to}}

constexpr bool b = number;
static_assert(b, "");
constexpr int one = true;
static_assert(one == 1, "");

namespace IntegralCasts {
constexpr int i = 12;
constexpr unsigned int ui = i;
static_assert(ui == 12, "");
constexpr unsigned int ub = !false;
static_assert(ub == 1, "");

constexpr int si = ui;
static_assert(si == 12, "");
constexpr int sb = true;
static_assert(sb == 1, "");

constexpr int zero = 0;
constexpr unsigned int uzero = 0;
constexpr bool bs = i;
static_assert(bs, "");
constexpr bool bu = ui;
static_assert(bu, "");
constexpr bool ns = zero;
static_assert(!ns, "");
constexpr bool nu = uzero;
static_assert(!nu, "");
};



constexpr bool getTrue() { return true; }
constexpr bool getFalse() { return false; }
constexpr void* getNull() { return nullptr; }
Expand Down
47 changes: 47 additions & 0 deletions clang/unittests/AST/EvaluateAsRValueTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,50 @@ TEST(EvaluateAsRValue, FailsGracefullyForUnknownTypes) {
Args));
}
}

class CheckLValueToRValueConversionVisitor
: public clang::RecursiveASTVisitor<CheckLValueToRValueConversionVisitor> {
public:
bool VisitDeclRefExpr(const clang::DeclRefExpr *E) {
clang::Expr::EvalResult Result;
E->EvaluateAsRValue(Result, E->getDecl()->getASTContext(), true);

EXPECT_TRUE(Result.Val.hasValue());
// Since EvaluateAsRValue does an implicit lvalue-to-rvalue conversion,
// the result cannot be a LValue.
EXPECT_FALSE(Result.Val.isLValue());

return true;
}
};

class CheckConversionAction : public clang::ASTFrontendAction {
public:
std::unique_ptr<clang::ASTConsumer>
CreateASTConsumer(clang::CompilerInstance &Compiler,
llvm::StringRef FilePath) override {
return std::make_unique<Consumer>();
}

private:
class Consumer : public clang::ASTConsumer {
public:
~Consumer() override {}

void HandleTranslationUnit(clang::ASTContext &Ctx) override {
CheckLValueToRValueConversionVisitor Evaluator;
Evaluator.TraverseDecl(Ctx.getTranslationUnitDecl());
}
};
};

TEST(EvaluateAsRValue, LValueToRValueConversionWorks) {
std::string ModesToTest[] = {"", "-fexperimental-new-constant-interpreter"};
for (std::string const &Mode : ModesToTest) {
std::vector<std::string> Args(1, Mode);
ASSERT_TRUE(runToolOnCodeWithArgs(std::make_unique<CheckConversionAction>(),
"constexpr int a = 20;\n"
"static_assert(a == 20, \"\");\n",
Args));
}
}
6 changes: 6 additions & 0 deletions clang/utils/TableGen/ClangOpcodesEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ void ClangOpcodesEmitter::EmitInterp(raw_ostream &OS, StringRef N, Record *R) {

OS << "case OP_" << ID << ": {\n";

if (CanReturn)
OS << " bool DoReturn = (S.Current == StartFrame);\n";

// Emit calls to read arguments.
for (size_t I = 0, N = Args.size(); I < N; ++I) {
OS << " auto V" << I;
Expand All @@ -146,6 +149,9 @@ void ClangOpcodesEmitter::EmitInterp(raw_ostream &OS, StringRef N, Record *R) {
if (CanReturn) {
OS << " if (!S.Current || S.Current->isRoot())\n";
OS << " return true;\n";

OS << " if (DoReturn)\n";
OS << " return true;\n";
}

OS << " continue;\n";
Expand Down