Skip to content

Commit

Permalink
[clang][Interp] Implement function calls
Browse files Browse the repository at this point in the history
Add Call() and CallVoid() ops and use them to call functions. Only
FunctionDecls are supported for now.

Differential Revision: https://reviews.llvm.org/D132286
  • Loading branch information
tbaederr committed Sep 8, 2022
1 parent 5777c05 commit 8e41e6a
Show file tree
Hide file tree
Showing 10 changed files with 191 additions and 7 deletions.
47 changes: 47 additions & 0 deletions clang/lib/AST/Interp/ByteCodeExprGen.cpp
Expand Up @@ -9,6 +9,7 @@
#include "ByteCodeExprGen.h"
#include "ByteCodeEmitter.h"
#include "ByteCodeGenError.h"
#include "ByteCodeStmtGen.h"
#include "Context.h"
#include "Function.h"
#include "PrimType.h"
Expand Down Expand Up @@ -593,6 +594,52 @@ bool ByteCodeExprGen<Emitter>::visitDecl(const VarDecl *VD) {
return this->bail(VD);
}

template <class Emitter>
bool ByteCodeExprGen<Emitter>::VisitCallExpr(const CallExpr *E) {
assert(!E->getBuiltinCallee() && "Builtin functions aren't supported yet");

const Decl *Callee = E->getCalleeDecl();
if (const auto *FuncDecl = dyn_cast_or_null<FunctionDecl>(Callee)) {
const Function *Func = P.getFunction(FuncDecl);

// Templated functions might not have been compiled yet, so do it now.
if (!Func) {
if (auto R =
ByteCodeStmtGen<ByteCodeEmitter>(Ctx, P).compileFunc(FuncDecl))
Func = *R;
}
assert(Func);

QualType ReturnType = E->getCallReturnType(Ctx.getASTContext());
Optional<PrimType> T = classify(ReturnType);

if (T || ReturnType->isVoidType()) {
// Put arguments on the stack.
for (const auto *Arg : E->arguments()) {
if (!this->visit(Arg))
return false;
}

if (T)
return this->emitCall(*T, Func, E);
return this->emitCallVoid(Func, E);
} else {
assert(false && "Can't classify function return type");
}

} else {
assert(false && "We don't support non-FunctionDecl callees right now.");
}

return false;
}

template <class Emitter>
bool ByteCodeExprGen<Emitter>::VisitCXXDefaultArgExpr(
const CXXDefaultArgExpr *E) {
return this->visit(E->getExpr());
}

template <class Emitter>
bool ByteCodeExprGen<Emitter>::VisitCXXBoolLiteralExpr(
const CXXBoolLiteralExpr *E) {
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/AST/Interp/ByteCodeExprGen.h
Expand Up @@ -69,6 +69,8 @@ class ByteCodeExprGen : public ConstStmtVisitor<ByteCodeExprGen<Emitter>, bool>,
bool VisitIntegerLiteral(const IntegerLiteral *E);
bool VisitParenExpr(const ParenExpr *E);
bool VisitBinaryOperator(const BinaryOperator *E);
bool VisitCXXDefaultArgExpr(const CXXDefaultArgExpr *E);
bool VisitCallExpr(const CallExpr *E);
bool VisitCXXBoolLiteralExpr(const CXXBoolLiteralExpr *E);
bool VisitCXXNullPtrLiteralExpr(const CXXNullPtrLiteralExpr *E);
bool VisitUnaryOperator(const UnaryOperator *E);
Expand Down
18 changes: 18 additions & 0 deletions clang/lib/AST/Interp/EvalEmitter.cpp
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
2 changes: 1 addition & 1 deletion clang/lib/AST/Interp/Function.h
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
27 changes: 27 additions & 0 deletions clang/lib/AST/Interp/Interp.cpp
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
1 change: 1 addition & 0 deletions clang/lib/AST/Interp/InterpFrame.h
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
18 changes: 17 additions & 1 deletion clang/lib/AST/Interp/Opcodes.td
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
10 changes: 5 additions & 5 deletions clang/test/AST/Interp/cxx20.cpp
Expand Up @@ -10,7 +10,7 @@ constexpr int getMinus5() {
int *p = &a;
return *p;
}
//static_assert(getMinus5() == -5, "") TODO
static_assert(getMinus5() == -5, "");

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

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


constexpr int pointerAssign() {
Expand All @@ -31,15 +31,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 +52,4 @@ constexpr int pointerAssign2() {

return v;
}
//static_assert(pointerAssign2() == 12, ""); TODO
static_assert(pointerAssign2() == 12, "");
67 changes: 67 additions & 0 deletions clang/test/AST/Interp/functions.cpp
@@ -0,0 +1,67 @@
// 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, "");
6 changes: 6 additions & 0 deletions clang/utils/TableGen/ClangOpcodesEmitter.cpp
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

0 comments on commit 8e41e6a

Please sign in to comment.