tools/emhermesc/emhermesc.cpp (151 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* This is a driver of the Hermes Compiler intended to be compiled to
* WebAssembly with Emscripten and invoked from JavaScript.
*
* When configuring CMake, don't specify CMAKE_EXE_LINKER_FLAGS, because the
* correct flags are already set for this target.
*
* HermesCompiler.js is a module exposing the compiler interface to JS.
*/
#include "hermes/AST/SemValidate.h"
#include "hermes/BCGen/HBC/HBC.h"
#include "hermes/SourceMap/SourceMapParser.h"
#include "hermes/Support/Algorithms.h"
#include "hermes/Support/SimpleDiagHandler.h"
#include "llvh/Support/SHA1.h"
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#else
#define EMSCRIPTEN_KEEPALIVE
#endif
using namespace hermes;
// Forward declaration.
class CompileResult;
extern "C" {
/// Compile the supplied source and return a CompileResult, which is an opaque
/// structure containing the generated bytecode or an error message. The
/// result must be freed with \c hermesCompileResult_free().
///
/// \param source utf-8 encoded input string. It must be zero terminated.
/// \param sourceSize the length of \c source in bytes, including the
/// terminating zero.
/// \param sourceURL optional string containing the source URL.
/// \param sourceMapData optional string containing a source map.
/// \param sourceMapSize the length if \c sourceMapData, including nul
/// \return a new instance of CompileResult.
CompileResult *hermesCompileToBytecode(
const char *source,
size_t sourceSize,
const char *sourceURL,
const char *sourceMapData,
size_t sourceMapSize);
/// Free the CompileResult allocated by \c hermesCompileToBytecode().
void hermesCompileResult_free(CompileResult *res);
/// \return nullptr if compilation was successfull, the error string otherwise.
const char *hermesCompileResult_getError(const CompileResult *res);
/// \return a pointer to the generated bytecode, or nullptr if there was an
/// error.
const char *hermesCompileResult_getBytecodeAddr(const CompileResult *res);
/// \return the size of the generated bytecode, or 0 if there was an error.
size_t hermesCompileResult_getBytecodeSize(const CompileResult *res);
/// \return a JSON string encoding constant Hermes properties.
EMSCRIPTEN_KEEPALIVE
extern "C" const char *hermesGetProperties();
} // extern "C"
/// An opaque object containing the result of a compilation.
class CompileResult {
public:
std::string error_;
llvh::SmallVector<char, 0> bytecode_;
};
EMSCRIPTEN_KEEPALIVE
extern "C" void hermesCompileResult_free(CompileResult *res) {
delete res;
}
EMSCRIPTEN_KEEPALIVE
extern "C" const char *hermesCompileResult_getError(const CompileResult *res) {
if (!res || res->error_.empty())
return nullptr;
return res->error_.c_str();
}
EMSCRIPTEN_KEEPALIVE
extern "C" const char *hermesCompileResult_getBytecodeAddr(
const CompileResult *res) {
if (!res || res->bytecode_.empty())
return nullptr;
return res->bytecode_.data();
}
EMSCRIPTEN_KEEPALIVE
extern "C" size_t hermesCompileResult_getBytecodeSize(
const CompileResult *res) {
if (!res || res->bytecode_.empty())
return 0;
return res->bytecode_.size();
}
EMSCRIPTEN_KEEPALIVE
extern "C" CompileResult *hermesCompileToBytecode(
const char *source,
size_t sourceSize,
const char *sourceURL,
const char *sourceMapData,
size_t sourceMapSize) {
auto compileRes = std::make_unique<CompileResult>();
std::unique_ptr<SourceMap> sourceMap;
if (source[sourceSize - 1] != 0) {
compileRes->error_ = "Input source must be zero-terminated";
return compileRes.release();
}
if (sourceMapData != nullptr && sourceMapData[0] != '\0') {
if (sourceMapData[sourceMapSize - 1] != 0) {
compileRes->error_ = "Input sourcemap must be zero-terminated";
return compileRes.release();
}
SourceErrorManager sm;
SimpleDiagHandlerRAII diagHandler(sm);
sourceMap = SourceMapParser::parse({sourceMapData, sourceMapSize - 1}, sm);
if (!sourceMap) {
compileRes->error_ =
"Failed to parse source map:" + diagHandler.getErrorString();
return compileRes.release();
}
}
hbc::CompileFlags flags{};
flags.debug = true;
// Note that we are relying the zero termination provided by str.data(),
// because the parser requires it.
auto res = hbc::BCProviderFromSrc::createBCProviderFromSrc(
std::make_unique<hermes::Buffer>((const uint8_t *)source, sourceSize - 1),
sourceURL ? sourceURL : "",
std::move(sourceMap),
flags);
if (!res.first) {
if (!res.second.empty())
compileRes->error_ = res.second;
else
compileRes->error_ = "Unknown compilation error";
return compileRes.release();
}
llvh::raw_svector_ostream bcstream{compileRes->bytecode_};
BytecodeGenerationOptions opts(::hermes::EmitBundle);
opts.optimizationEnabled = false;
hbc::BytecodeSerializer BS{bcstream, opts};
BS.serialize(
*res.first->getBytecodeModule(),
llvh::SHA1::hash(llvh::makeArrayRef(
reinterpret_cast<const uint8_t *>(source), sourceSize - 1)));
return compileRes.release();
}
static std::string getPropertiesHelper() {
std::string json{};
llvh::raw_string_ostream OS{json};
OS << "{ \"BYTECODE_ALIGNMENT\":" << hbc::BYTECODE_ALIGNMENT
<< ", \"HEADER_SIZE\":" << sizeof(hbc::BytecodeFileHeader)
<< ", \"VERSION\":" << hbc::BYTECODE_VERSION << ", \"MAGIC\": ["
<< (uint32_t)hbc::MAGIC << ", " << (uint32_t)(hbc::MAGIC >> 32) << "]"
<< ", \"LENGTH_OFFSET\":" << offsetof(hbc::BytecodeFileHeader, fileLength)
<< "}";
return OS.str();
}
EMSCRIPTEN_KEEPALIVE
extern "C" const char *hermesGetProperties() {
static std::string props = getPropertiesHelper();
return props.c_str();
}
// This is just a dummy main routine to exercise the code. It won't actually
// be called by JS.
int main() {
const char map[] = R"(
{
"version": 3,
"file": "x.js",
"sourceRoot": "",
"sources": [
"test.js"
],
"names": [],
"mappings": "AAKA,SAAS,OAAO,CAAC,MAAc;IAC3B,OAAO,SAAS,GAAG,MAAM,CAAC,SAAS,GAAG,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC;AAChE,CAAC;AAED,IAAI,IAAI,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;AACnD,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC"
}
)";
static const char src1[] = "var x = 1; print(x);";
auto *res1 =
hermesCompileToBytecode(src1, sizeof(src1), "x.js", map, sizeof(map));
assert(!hermesCompileResult_getError(res1) && "success expected");
llvh::outs() << "Generated " << hermesCompileResult_getBytecodeSize(res1)
<< " bytecode bytes\n";
hermesCompileResult_free(res1);
static const char src2[] = "var x = 1 + ;";
auto *res2 =
hermesCompileToBytecode(src2, sizeof(src2), "x.js", map, sizeof(map));
assert(hermesCompileResult_getError(res2) && "error expected");
llvh::outs() << "Error " << hermesCompileResult_getError(res2) << "\n";
hermesCompileResult_free(res2);
return 0;
}