Arquivos
hhvm/hphp/tools/bootstrap/gen-ext-hhvm.cpp
T
Edwin Smith 9b6bf426b9 Move runtime/base/array/* up one level.
Also rename array_inline.h to array_data-inl.h
2013-07-15 17:34:47 -07:00

706 linhas
20 KiB
C++

/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/
#include <algorithm>
#include <cxxabi.h>
#include <fstream>
#include <sstream>
#include <iostream>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include "folly/FBString.h"
#include "folly/Format.h"
#include "folly/ScopeGuard.h"
#include "hphp/tools/bootstrap/idl.h"
using folly::fbstring;
using namespace HPHP::IDL;
using namespace HPHP;
std::unordered_map<fbstring, const PhpFunc*> g_mangleMap;
std::unordered_map<fbstring, const PhpClass*> g_classMap;
// Functions with return types that don't fit in registers are handled
// differently on ARM. Instead of using a pointer-to-return-value-space hidden
// first parameter like on x64, the pointer is passed in a register reserved for
// this purpose, not part of the normal argument sequence.
bool g_armMode = false;
constexpr char* g_allIncludes = R"(
#include "hphp/runtime/ext_hhvm/ext_hhvm.h"
#include "hphp/runtime/base/builtin_functions.h"
#include "hphp/runtime/base/array_init.h"
#include "hphp/runtime/ext/ext.h"
#include "hphp/runtime/vm/class.h"
#include "hphp/runtime/vm/runtime.h"
#include <exception>
)";
///////////////////////////////////////////////////////////////////////////////
// Code emission helpers -- leaf functions
void emitCtorHelper(const fbstring& className, std::ostream& out) {
out << folly::format(
R"(
ObjectData* new_{0:s}_Instance(HPHP::Class* cls) {{
size_t nProps = cls->numDeclProperties();
size_t builtinPropSize = sizeof(c_{0:s}) - sizeof(ObjectData);
size_t size = ObjectData::sizeForNProps(nProps) + builtinPropSize;
ObjectData *inst = (ObjectData*)ALLOCOBJSZ(size);
new ((void *)inst) c_{0:s}(cls);
return inst;
}})",
className) << "\n\n";
}
/*
* Emits a declaration that corresponds to the f_* functions, but with types and
* signatures adjusted to reflect the underlying C++ ABI.
*/
void emitRemappedFuncDecl(const PhpFunc& func,
const fbstring& mangled,
const fbstring& prefix,
std::ostream& out) {
int returnKindOf = func.returnKindOf();
bool indirectReturn = func.isIndirectReturn();
if (indirectReturn) {
if (returnKindOf == KindOfAny) {
out << "TypedValue* ";
} else {
out << "Value* ";
}
} else {
out << func.returnCppType() << ' ';
}
out << prefix << func.getUniqueName() << '(';
bool isFirstParam = true;
if (!g_armMode && indirectReturn) {
if (func.returnKindOf() == KindOfAny) {
out << "TypedValue* _rv";
} else {
out << "Value* _rv";
}
isFirstParam = false;
}
if (func.usesThis()) {
if (!isFirstParam) {
out << ", ";
}
out << "ObjectData* this_";
isFirstParam = false;
}
if (func.isVarArgs()) {
if (!isFirstParam) {
out << ", ";
}
out << "int64_t _argc";
isFirstParam = false;
}
for (auto const& param : func.params()) {
if (!isFirstParam) {
out << ", ";
}
auto kindof = param.kindOf();
if (param.isIndirectPass()) {
if (kindof == KindOfAny || kindof == KindOfRef) {
out << "TypedValue*";
} else {
out << "Value*";
}
} else {
out << param.getCppType();
}
out << ' ' << param.name();
isFirstParam = false;
}
if (func.isVarArgs()) {
assert(!isFirstParam);
out << ", Value* _argv";
}
out << ") asm(\""
<< mangled << "\");\n\n";
}
void emitCast(const PhpParam& param, int32_t index, std::ostream& out,
const char* ind, bool doCheck) {
if (doCheck) {
out << ind << "if (";
if (param.kindOf() == KindOfString) {
out << "!IS_STRING_TYPE((args-" << index << ")->m_type)";
} else {
out << "(args-" << index << ")->m_type != KindOf"
<< kindOfString(param.kindOf());
}
out << ") {\n";
ind -= 2;
}
if (param.getParamMode() == ParamMode::Zend) {
out << ind << "if (!"
<< "tvCoerceParamTo" << kindOfString(param.kindOf())
<< "InPlace(args-" << index << ")) {\n"
<< ind << " raise_param_type_warning(__func__, " << index << " + 1, "
<< "KindOf" << kindOfString(param.kindOf()) << ", "
<< "(args-" << index << ")->m_type);\n"
<< ind << " rv->m_type = KindOfUninit;\n"
<< ind << " return;\n"
<< ind << "}\n";
} else if (param.kindOf() != KindOfAny) {
out << ind << "tvCastTo" << kindOfString(param.kindOf())
<< "InPlace(args-" << index << ");\n";
}
if (doCheck) {
ind += 2;
out << ind << "}\n";
}
}
/*
* Emits an expression which will check the types of arguments on the VM stack.
*/
void emitTypechecks(const PhpFunc& func, std::ostream& out, const char* ind) {
bool isFirstParam = true;
for (int k = func.numParams() - 1; k >= 0; --k) {
auto const& param = func.param(k);
auto kindof = param.kindOf();
if (kindof == KindOfAny || kindof == KindOfRef) {
continue;
}
if (!isFirstParam) {
out << " &&\n" << ind << " ";
}
isFirstParam = false;
bool isOptional = (k >= func.minNumParams());
if (isOptional) {
out << "(count <= " << k << " || ";
}
if (kindof == KindOfString) {
out << "IS_STRING_TYPE((args - " << k << ")->m_type)";
} else {
out << "(args - " << k << ")->m_type == KindOf" << kindOfString(kindof);
}
if (isOptional) {
out << ")";
}
}
}
/*
* Marshals varargs into an array.
*/
void emitBuildExtraArgs(const PhpFunc& func, std::ostream& out,
const char* ind) {
out << folly::format(
R"(
{0}Array extraArgs;
{0}{{
{0} ArrayInit ai(count-{1});
{0} for (int32_t i = {1}; i < count; ++i) {{
{0} TypedValue* extraArg = ar->getExtraArg(i-{1});
{0} if (tvIsStronglyBound(extraArg)) {{
{0} ai.setRef(i-{1}, tvAsVariant(extraArg));
{0} }} else {{
{0} ai.set(i-{1}, tvAsVariant(extraArg));
{0} }}
{0} }}
{0} extraArgs = ai.create();
{0}}}
)",
ind,
func.numParams()
);
}
void emitCallExpression(const PhpFunc& func, const fbstring& prefix,
std::ostream& out) {
out << prefix << func.getUniqueName() << '(';
bool isFirstParam = true;
if (!g_armMode && func.isIndirectReturn()) {
isFirstParam = false;
if (func.returnKindOf() == KindOfAny) {
out << "rv";
} else {
out << "&(rv->m_data)";
}
}
if (func.usesThis()) {
if (!isFirstParam) {
out << ", ";
}
isFirstParam = false;
out << "(this_)";
}
if (func.isVarArgs()) {
if (!isFirstParam) {
out << ", ";
}
isFirstParam = false;
out << "count";
}
for (auto k = 0; k < func.numParams(); ++k) {
auto const& param = func.param(k);
if (!isFirstParam) {
out << ", ";
}
isFirstParam = false;
if (param.hasDefault()) {
out << "(count > " << k << ") ? ";
}
if (param.isIndirectPass()) {
auto kindof = param.kindOf();
if (kindof == KindOfAny || kindof == KindOfRef) {
out << "(args-" << k << ')';
} else {
out << "&args[-" << k << "].m_data";
}
} else {
if (param.kindOf() == KindOfDouble) {
out << "(args[-" << k << "].m_data.dbl)";
} else {
out << '(' << param.getCppType() << ")(args[-" << k << "].m_data.num)";
}
}
if (param.hasDefault()) {
out << " : ";
auto kindof = param.kindOf();
if (param.defValueNeedsVariable()) {
if (kindof == KindOfAny || kindof == KindOfRef) {
out << "(TypedValue*)(&defVal" << k << ')';
} else {
out << "(Value*)(&defVal" << k << ')';
}
} else if (param.isIndirectPass()) {
if (kindof == KindOfAny || kindof == KindOfRef) {
out << "(TypedValue*)(&" << param.getDefault() << ')';
} else {
out << "(Value*)(&" << param.getDefault() << ')';
}
} else {
out << '(' << param.getCppType() << ")(" << param.getDefault() << ')';
}
}
}
if (func.isVarArgs()) {
assert(!isFirstParam);
out << ", (Value*)(&extraArgs)";
}
out << ')';
}
///////////////////////////////////////////////////////////////////////////////
// Code emission helpers -- non-leaf functions
/*
* Marshals varargs into an array if necessary, emits variables for default
* values if necessary, and emits the call itself.
*/
void emitExtCall(const PhpFunc& func, std::ostream& out, const char* ind) {
fbstring call_prefix;
fbstring call_suffix;
// Set up the type of the return value, and emit post-call code to normalize
// return types and values
auto returnKindOf = func.returnKindOf();
if (returnKindOf == KindOfBoolean) {
out << ind << "rv->m_type = KindOfBoolean;\n";
call_prefix = "rv->m_data.num = (";
call_suffix = ") ? 1LL : 0LL;\n";
} else if (returnKindOf == KindOfInt64) {
out << ind << "rv->m_type = KindOfInt64;\n";
call_prefix = "rv->m_data.num = (int64_t)";
call_suffix = ";\n";
} else if (returnKindOf == KindOfDouble) {
out << ind << "rv->m_type = KindOfDouble;\n";
call_prefix = "rv->m_data.dbl = ";
call_suffix = ";\n";
} else if (returnKindOf == KindOfInvalid || returnKindOf == KindOfNull) {
out << ind << "rv->m_type = KindOfNull;\n";
call_suffix = ";\n";
} else if (returnKindOf == KindOfString) {
out << ind << "rv->m_type = KindOfString;\n";
call_suffix = (fbstring(";\n") + ind +
"if (rv->m_data.num == 0LL) rv->m_type = KindOfNull;\n");
} else if (returnKindOf == KindOfArray) {
out << ind << "rv->m_type = KindOfArray;\n";
call_suffix = (fbstring(";\n") + ind +
"if (rv->m_data.num == 0LL) rv->m_type = KindOfNull;\n");
} else if (returnKindOf == KindOfObject) {
out << ind << "rv->m_type = KindOfObject;\n";
call_suffix = (fbstring(";\n") + ind +
"if (rv->m_data.num == 0LL) rv->m_type = KindOfNull;\n");
} else {
call_suffix = (fbstring(";\n") + ind +
"if (rv->m_type == KindOfUninit) "
"rv->m_type = KindOfNull;\n");
}
if (func.isVarArgs()) {
emitBuildExtraArgs(func, out, ind);
}
// If any default values need variables (because they have nontrivial values),
// declare and initialize those
for (auto k = 0; k < func.numParams(); ++k) {
auto const& param = func.param(k);
if (param.defValueNeedsVariable()) {
DataType kindof = param.kindOf();
out << ind << param.getStrippedCppType() << " defVal" << k;
fbstring defVal = param.getDefault();
if (kindof != KindOfAny ||
(defVal != "null" && defVal != "null_variant")) {
out << " = ";
std::string nullToType =
kindof == KindOfArray ? ".toArray()" :
kindof == KindOfString ? ".toString()" :
kindof == KindOfObject ? ".toObject()" :
kindof == KindOfRef ? "" :
"icantconvertthisfromnull";
if (defVal == "null_variant") defVal += nullToType;
out << (defVal == "null" ? "uninit_null()" + nullToType : defVal);
}
out << ";\n";
}
}
// Put the return-value-space pointer into x8
if (g_armMode) {
out << ind << "asm volatile (\"mov x8, %0\\n\" : : \"r\"(";
if (func.returnKindOf() == KindOfAny) {
out << "rv";
} else {
out << "&(rv->m_data)";
}
out << ") : \"x8\");\n";
}
out << ind << call_prefix;
emitCallExpression(func, func.isMethod() ? "th_" : "fh_", out);
out << call_suffix;
}
void emitCasts(const PhpFunc& func, std::ostream& out, const char* ind) {
assert(func.numTypeChecks() > 0);
if (func.numTypeChecks() == 1) {
for (auto i = func.numParams() - 1; i >= 0; --i) {
if (func.param(i).isCheckedType()) {
emitCast(func.param(i), i, out, ind, false);
return;
}
}
assert(false); // not reached
}
if (func.minNumParams() != func.numParams()) {
out << ind << "switch (count) {\n";
for (auto i = func.numParams() - 1; i >= func.minNumParams(); --i) {
auto const& param = func.param(i);
if (i == func.numParams() - 1) {
out << ind << "default: // count >= " << func.numParams() << '\n';
} else {
out << ind << "case " << (i + 1) << ":\n";
}
if (param.isCheckedType()) {
emitCast(param, i, out, ind - 2, true);
}
}
out << ind << "case " << func.minNumParams() << ":\n";
out << ind << " break;\n";
out << ind << "}\n";
}
for (auto i = func.minNumParams() - 1; i >= 0; --i) {
auto const& param = func.param(i);
if (param.isCheckedType()) {
emitCast(param, i, out, ind, true);
}
}
}
/*
* Emits the fg1_ helper, which assumes that the arg count is acceptable, but at
* least one typecheck has failed. It will cast arguments to the appropriate
* types and then call the fh_ alias.
*/
void emitSlowPathHelper(const PhpFunc& func, const fbstring& prefix,
std::ostream& out) {
out << "void " << prefix << func.getUniqueName()
<< "(TypedValue* rv, ActRec* ar, int32_t count";
if (func.usesThis()) {
out << ", ObjectData* this_";
}
out << ") __attribute__((noinline,cold));\n";
out << "void " << prefix << func.getUniqueName()
<< "(TypedValue* rv, ActRec* ar, int32_t count";
if (func.usesThis()) {
out << ", ObjectData* this_";
}
out << ") {\n";
const char* eightSpaces = " ";
const char* ind = eightSpaces + 6;
out << ind << "TypedValue* args UNUSED = ((TypedValue*)ar) - 1;\n";
emitCasts(func, out, ind);
emitExtCall(func, out, ind);
out << "}\n\n";
}
///////////////////////////////////////////////////////////////////////////////
// Top level
/*
* Called for each line on stdin. Looks up the symbol's characteristics in the
* IDL, and emits up to three things:
*
* - An fh_* declaration, which is the f_* signature with ABI exposed.
* - [Maybe] an fg1_* stub which does type casting of arguments, then calls fh_.
* - An fg_ stub which checks arg counts and types, then calls fh_ or fg1_.
*/
void processSymbol(const fbstring& symbol, std::ostream& header,
std::ostream& cpp) {
int status;
const char *mangledSymbol = symbol.c_str();
#ifdef __APPLE__
mangledSymbol++;
#endif
auto demangled = abi::__cxa_demangle(mangledSymbol, nullptr, 0, &status);
SCOPE_EXIT { free(demangled); };
if (status != 0) {
return;
}
auto idlIt = g_mangleMap.find(demangled);
if (idlIt == g_mangleMap.end()) {
// A symbol that doesn't correspond to anything in the IDL.
return;
}
auto& func = *idlIt->second;
bool isMethod = func.isMethod();
auto classIt = g_classMap.find(func.className());
if (func.isCtor() && classIt != g_classMap.end()) {
auto& klass = *classIt->second;
if (!(klass.flags() & IsCppAbstract)) {
emitCtorHelper(klass.name(), cpp);
}
if (klass.flags() & NoDefaultSweep) {
cpp << "IMPLEMENT_CLASS_NO_DEFAULT_SWEEP(" << klass.name() << ");\n";
} else {
cpp << "IMPLEMENT_CLASS(" << klass.name() << ");\n";
}
}
fbstring declPrefix = (isMethod ? "th_" : "fh_");
fbstring slowPathPrefix = (isMethod ? "tg1_" : "fg1_");
fbstring stubPrefix = (isMethod ? "tg_" : "fg_");
std::ostringstream decl;
emitRemappedFuncDecl(func, symbol, declPrefix, decl);
if (!isMethod) {
header << decl.str();
}
cpp << decl.str();
if (func.numTypeChecks() > 0) {
emitSlowPathHelper(func, slowPathPrefix, cpp);
}
// This is how we change the indentation level. So clever.
const char* eightSpaces = " ";
const char* in = eightSpaces + 6;
cpp << "TypedValue* " << stubPrefix << func.getUniqueName()
<< "(ActRec* ar) {\n";
cpp << in << "TypedValue rvSpace;\n";
cpp << in << "TypedValue* rv = &rvSpace;\n";
cpp << in << "int32_t count = ar->numArgs();\n";
cpp << in << "TypedValue* args UNUSED = ((TypedValue*)ar) - 1;\n";
if (func.usesThis()) {
cpp << in
<< "ObjectData* this_ = (ar->hasThis() ? ar->getThis() : nullptr);\n";
cpp << in << "if (this_) {\n";
in -= 2;
}
// Check the arg count
bool needArgMiscountClause = false;
if (func.isVarArgs()) {
if (func.minNumParams() > 0) {
cpp << in << "if (count >= " << func.minNumParams() << ") {\n";
needArgMiscountClause = true;
in -= 2;
}
} else {
if (func.minNumParams() == func.numParams()) {
cpp << in << "if (count == " << func.minNumParams() << ") {\n";
} else if (func.minNumParams() == 0) {
cpp << in << "if (count <= " << func.numParams() << ") {\n";
} else {
cpp << in << "if (count >= " << func.minNumParams()
<< " && count <= "<< func.numParams() << ") {\n";
}
needArgMiscountClause = true;
in -= 2;
}
// Count is OK. Check arg types
if (func.numTypeChecks() > 0) {
cpp << in << "if (";
emitTypechecks(func, cpp, in);
cpp << ") {\n";
in -= 2;
}
// Call the f_ function via the fh_ alias
emitExtCall(func, cpp, in);
// Deal with type mismatches: punt to fg1_
if (func.numTypeChecks() > 0) {
cpp << in + 2 << "} else {\n";
cpp << in << slowPathPrefix << func.getUniqueName() << "(rv, ar, count";
if (func.usesThis()) {
cpp << ", this_";
}
cpp << ");\n";
in += 2;
cpp << in << "}\n";
}
if (needArgMiscountClause) {
cpp << in + 2 << "} else {\n";
if (func.isVarArgs()) {
cpp << in << "throw_missing_arguments_nr(\"" << func.getPrettyName()
<< "\", " << func.minNumParams() << ", count, 1);\n";
} else {
if (func.minNumParams() == 0) {
cpp << in << "throw_toomany_arguments_nr(\"" << func.getPrettyName()
<< "\", " << func.numParams() << ", 1);\n";
} else {
cpp << in << "throw_wrong_arguments_nr(\"" << func.getPrettyName()
<< "\", count, " << func.minNumParams() << ", "
<< func.numParams() << ", 1);\n";
}
}
cpp << in << "rv->m_data.num = 0LL;\n";
cpp << in << "rv->m_type = KindOfNull;\n";
in += 2;
cpp << in << "}\n";
}
if (func.isMethod() && !func.isStatic()) {
cpp << in + 2 << "} else {\n";
cpp << in << "throw_instance_method_fatal(\"" << func.className()
<< "::" << func.name() << "\");\n";
in += 2;
cpp << in << "}\n";
}
auto numLocals = func.numParams();
auto frameFree =
func.usesThis() ? "frame_free_locals_inl" : "frame_free_locals_no_this_inl";
cpp << in << frameFree << "(ar, " << numLocals << ");\n";
cpp << in << "memcpy(&ar->m_r, rv, sizeof(TypedValue));\n";
cpp << in << "return &ar->m_r;\n";
cpp << "}\n\n";
}
int main(int argc, const char* argv[]) {
if (argc < 5) {
std::cout << "Usage: " << argv[0]
<< " <x64|arm> <output .h> <output .cpp> <*.idl.json>...\n"
<< "Pipe mangled C++ symbols to stdin.\n";
return 0;
}
g_armMode = (strcmp(argv[1], "arm") == 0);
std::ofstream header(argv[2]);
std::ofstream cpp(argv[3]);
fbvector<PhpFunc> funcs;
fbvector<PhpClass> classes;
for (auto i = 4; i < argc; ++i) {
try {
parseIDL(argv[i], funcs, classes);
} catch (const std::exception& exc) {
std::cerr << argv[i] << ": " << exc.what() << "\n";
return 1;
}
}
for (auto const& func : funcs) {
g_mangleMap[func.getCppSig()] = &func;
}
for (auto const& klass : classes) {
g_classMap[klass.name()] = &klass;
for (auto const& func : klass.methods()) {
g_mangleMap[func.getCppSig()] = &func;
}
}
header << "namespace HPHP {\n\n";
cpp << g_allIncludes << "\n";
cpp << "namespace HPHP {\n\n";
std::string line;
while (std::getline(std::cin, line)) {
processSymbol(line, header, cpp);
}
header << "} // namespace HPHP\n";
cpp << "} // namespace HPHP\n";
return 0;
}