Skip to content

Commit

Permalink
IR instrumentation support for dynamic checking
Browse files Browse the repository at this point in the history
Summary:
IR instrumentation allows customizable hooks to emit code before and
after certain expressions. The list of supported expressions and
statements will expand, this is just adding initial support for the
basic ones.

In this prototype, the instrumentation calls methods on a global property,
passing the input values and the result. The property is added to the global
object whenever IR instrumentation support is compiled in.

Currently, the only application-exposed way to build instrumented bytecode is
through the `--instrument-ir` flag. Running from source or `eval` will
therefore not be instrumented.

Reviewed By: tmikov

Differential Revision: D19206572

fbshipit-source-id: abbdfbc5512d8ea6ab6700878748b455a96b1934
  • Loading branch information
willholen authored and facebook-github-bot committed Feb 20, 2020
1 parent 5cdc068 commit c3cc14c
Show file tree
Hide file tree
Showing 19 changed files with 499 additions and 14 deletions.
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ set(HERMES_STATIC_LINK OFF CACHE BOOL
set(HERMES_ENABLE_DEBUGGER OFF CACHE BOOL
"Build with debugger support")

set(HERMES_ENABLE_IR_INSTRUMENTATION OFF CACHE BOOL
"Build IR instrumentation support")

set(HERMES_FACEBOOK_BUILD OFF CACHE BOOL
"Build Facebook (rather than open-source) version of Hermes")

Expand Down Expand Up @@ -218,6 +221,10 @@ endif()
# Make the HERMES_RELEASE_VERSION accessible for version printing in C++.
add_definitions(-DHERMES_RELEASE_VERSION="${HERMES_RELEASE_VERSION}")

if(HERMES_ENABLE_IR_INSTRUMENTATION)
add_definitions(-DHERMES_ENABLE_IR_INSTRUMENTATION)
endif()

add_definitions(-DHERMESVM_GC_${HERMESVM_GCKIND})
if(HERMESVM_GC_GENERATIONAL_MARKSWEEPCOMPACT)
add_definitions(-DHERMESVM_GC_GENERATIONAL_MARKSWEEPCOMPACT)
Expand Down
2 changes: 2 additions & 0 deletions include/hermes/AST/Context.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ struct CodeGenerationSettings {
bool dumpUseList{false};
/// Dump IR after every pass.
bool dumpIRBetweenPasses{false};
/// Instrument IR for dynamic checking (if support is compiled in).
bool instrumentIR{false};
};

struct OutliningSettings {
Expand Down
2 changes: 2 additions & 0 deletions include/hermes/BCGen/HBC/BytecodeProviderFromSrc.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ struct CompileFlags {
/// Include libhermes declarations when compiling the file. This is done in
/// normal compilation, but not for eval().
bool includeLibHermes{true};
/// If set, instrument the IR for dynamic checks.
bool instrumentIR{false};
};

// The minimum code size in bytes before enabling lazy compilation.
Expand Down
3 changes: 3 additions & 0 deletions include/hermes/Support/SourceErrorManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,9 @@ class SourceErrorManager {
sourceUrls_[bufId] = url;
}

/// Find the bufferId of the specified location \p loc.
uint32_t findBufferIdForLoc(SMLoc loc) const;

/// Find the bufferId, line and column of the specified location \p loc.
/// \return true on success, false if could not be found, in which case
/// result.isValid() would also return false.
Expand Down
5 changes: 5 additions & 0 deletions include/hermes/VM/NativeFunctions.def
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,11 @@ NATIVE_FUNCTION(weakSetPrototypeAdd)
NATIVE_FUNCTION(weakSetPrototypeDelete)
NATIVE_FUNCTION(weakSetPrototypeHas)

#ifdef HERMES_ENABLE_IR_INSTRUMENTATION
NATIVE_FUNCTION(instrumentDoNothing)
NATIVE_FUNCTION(instrumentReturnNth)
#endif

#define ALL_ERROR_TYPE(name) NATIVE_FUNCTION(name##Constructor)
#include "hermes/VM/NativeErrorTypes.def"
#undef ALL_ERROR_TYPE
Expand Down
1 change: 1 addition & 0 deletions lib/BCGen/HBC/BytecodeProviderFromSrc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ BCProviderFromSrc::createBCProviderFromSrc(

CodeGenerationSettings codeGenOpts{};
codeGenOpts.unlimitedRegisters = false;
codeGenOpts.instrumentIR = compileFlags.instrumentIR;

OptimizationSettings optSettings;
// If the optional value is not set, the parser will automatically detect
Expand Down
1 change: 1 addition & 0 deletions lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ add_hermes_library(hermesFrontend
IRGen/ESTreeIRGen-stmt.cpp
IRGen/ESTreeIRGen-func.cpp
IRGen/ESTreeIRGen-except.cpp
IRGen/IRInstrument.cpp
IR/IR.cpp
IR/CFG.cpp
IR/IRBuilder.cpp
Expand Down
14 changes: 14 additions & 0 deletions lib/CompilerDriver/CompilerDriver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,12 @@ static opt<unsigned> PadFunctionBodiesPercent(
Hidden,
cat(CompilerCategory));

static opt<bool> InstrumentIR(
"instrument",
desc("Instrument code for dynamic analysis"),
init(false),
Hidden,
cat(CompilerCategory));
} // namespace cl

namespace {
Expand Down Expand Up @@ -897,6 +903,13 @@ bool validateFlags() {
if (cl::DumpTarget != DumpBytecode)
err("You can only dump bytecode for HBC bytecode file.");
}

#ifndef HERMES_ENABLE_IR_INSTRUMENTATION
if (cl::InstrumentIR) {
err("Instrumentation is requested, but support is not compiled in");
}
#endif

return !errored;
}

Expand All @@ -914,6 +927,7 @@ std::shared_ptr<Context> createContext(
if (cl::BytecodeFormat == cl::BytecodeFormatKind::HBC) {
codeGenOpts.unlimitedRegisters = false;
}
codeGenOpts.instrumentIR = cl::InstrumentIR;

OptimizationSettings optimizationOpts;
optimizationOpts.constantPropertyOptimizations = cl::EnableCPO;
Expand Down
35 changes: 22 additions & 13 deletions lib/IRGen/ESTreeIRGen-expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,13 @@ Value *ESTreeIRGen::genExpression(ESTree::Node *expr, Identifier nameHint) {
if (auto *Bin = dyn_cast<ESTree::BinaryExpressionNode>(expr)) {
Value *LHS = genExpression(Bin->_left);
Value *RHS = genExpression(Bin->_right);
auto cookie = instrumentIR_.preBinaryExpression(Bin, LHS, RHS);

auto Kind = BinaryOperatorInst::parseOperator(Bin->_operator->str());

return Builder.createBinaryOperatorInst(LHS, RHS, Kind);
BinaryOperatorInst *result =
Builder.createBinaryOperatorInst(LHS, RHS, Kind);
return instrumentIR_.postBinaryExpression(Bin, cookie, result, LHS, RHS);
}

// Handle Unary operator Expressions.
Expand Down Expand Up @@ -1316,12 +1319,14 @@ Value *ESTreeIRGen::genUnaryExpression(ESTree::UnaryExpressionNode *U) {

// Generate the unary operand:
Value *argument = genExpression(U->_argument);

if (kind == UnaryOperatorInst::OpKind::PlusKind) {
return Builder.createAsNumberInst(argument);
}

return Builder.createUnaryOperatorInst(argument, kind);
auto *cookie = instrumentIR_.preUnaryExpression(U, argument);

Value *result;
if (kind == UnaryOperatorInst::OpKind::PlusKind)
result = Builder.createAsNumberInst(argument);
else
result = Builder.createUnaryOperatorInst(argument, kind);
return instrumentIR_.postUnaryExpression(U, cookie, result, argument);
}

Value *ESTreeIRGen::genUpdateExpr(ESTree::UpdateExpressionNode *updateExpr) {
Expand Down Expand Up @@ -1368,7 +1373,6 @@ Value *ESTreeIRGen::genAssignmentExpr(ESTree::AssignmentExpressionNode *AE) {
auto AssignmentKind = BinaryOperatorInst::parseAssignmentOperator(opStr);

LReference lref = createLRef(AE->_left, false);
Value *RHS = nullptr;

Identifier nameHint{};
if (auto *var = lref.castAsVariable()) {
Expand All @@ -1377,22 +1381,27 @@ Value *ESTreeIRGen::genAssignmentExpr(ESTree::AssignmentExpressionNode *AE) {
nameHint = globProp->getName()->getValue();
}

Value *result;
if (AssignmentKind != BinaryOperatorInst::OpKind::IdentityKind) {
// Section 11.13.1 specifies that we should first load the
// LHS before materializing the RHS. Unlike in C, this
// code is well defined: "x+= x++".
// https://es5.github.io/#x11.13.1
auto V = lref.emitLoad();
RHS = genExpression(AE->_right, nameHint);
RHS = Builder.createBinaryOperatorInst(V, RHS, AssignmentKind);
auto *RHS = genExpression(AE->_right, nameHint);
auto *cookie = instrumentIR_.preAssignment(AE, V, RHS);
result = Builder.createBinaryOperatorInst(V, RHS, AssignmentKind);
result = instrumentIR_.postAssignment(AE, cookie, result, V, RHS);
} else {
RHS = genExpression(AE->_right, nameHint);
auto *RHS = genExpression(AE->_right, nameHint);
auto *cookie = instrumentIR_.preAssignment(AE, nullptr, RHS);
result = instrumentIR_.postAssignment(AE, cookie, RHS, nullptr, RHS);
}

lref.emitStore(RHS);
lref.emitStore(result);

// Return the value that we stored as the result of the expression.
return RHS;
return result;
}

Value *ESTreeIRGen::genConditionalExpr(ESTree::ConditionalExpressionNode *C) {
Expand Down
1 change: 1 addition & 0 deletions lib/IRGen/ESTreeIRGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ ESTreeIRGen::ESTreeIRGen(
const ScopeChain &scopeChain)
: Mod(M),
Builder(Mod),
instrumentIR_(M, Builder),
Root(root),
DeclarationFileList(declFileList),
lexicalScopeChain(resolveScopeIdentifiers(scopeChain)),
Expand Down
3 changes: 3 additions & 0 deletions lib/IRGen/ESTreeIRGen.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#ifndef HERMES_IRGEN_ESTREEIRGEN_H
#define HERMES_IRGEN_ESTREEIRGEN_H

#include "IRInstrument.h"
#include "hermes/ADT/ScopedHashTable.h"
#include "hermes/AST/SemValidate.h"
#include "hermes/IR/IRBuilder.h"
Expand Down Expand Up @@ -337,6 +338,8 @@ class ESTreeIRGen {
Module *Mod;
/// The IRBuilder we use to construct the module.
IRBuilder Builder;
/// Optional instrumentation
IRInstrument instrumentIR_;
/// The root of the ESTree.
ESTree::Node *Root;
/// This is a list of parsed global property declaration files.
Expand Down
128 changes: 128 additions & 0 deletions lib/IRGen/IRInstrument.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#include "IRInstrument.h"

namespace hermes {

#ifdef HERMES_ENABLE_IR_INSTRUMENTATION
IRInstrument::IRInstrument(hermes::Module *M, hermes::IRBuilder &builder)
: builder_(builder),
globalName_(builder.getLiteralString("__instrument")),
M_(M),
enabled_(M->getContext().getCodeGenerationSettings().instrumentIR) {}

IRInstrument::~IRInstrument() = default;

Value *IRInstrument::getIID(ESTree::Node *node) {
auto start = node->getStartLoc();
if (!start.isValid())
return builder_.getLiteralUndefined();

auto &sem = M_->getContext().getSourceErrorManager();
auto *buffer = sem.findBufferForLoc(start);
uint64_t bufferId = sem.findBufferIdForLoc(start);
uint64_t offset = (uint64_t)(
(uintptr_t)start.getPointer() - (uintptr_t)buffer->getBufferStart());

double id = (double)((bufferId << 32) | offset);
return builder_.getLiteralNumber(id);
}

Value *IRInstrument::invokeHook(
llvm::StringRef name,
llvm::ArrayRef<Value *> args) {
TryLoadGlobalPropertyInst *instrument =
builder_.createTryLoadGlobalPropertyInst(globalName_);
auto *hook = builder_.createLoadPropertyInst(instrument, name);
return builder_.createCallInst(hook, instrument, args);
}

Value *IRInstrument::preBinaryExpression(
ESTree::BinaryExpressionNode *node,
Value *left,
Value *right) {
if (!enabled_)
return nullptr;

auto *opStr = builder_.getLiteralString(node->_operator->str());
return invokeHook("preBinary", {getIID(node), opStr, left, right});
}

Value *IRInstrument::postBinaryExpression(
ESTree::BinaryExpressionNode *node,
Value *cookie,
Value *result,
Value *left,
Value *right) {
if (!enabled_)
return result;

auto *opStr = builder_.getLiteralString(node->_operator->str());
return invokeHook(
"postBinary",
{getIID(node), undefinedIfNull(cookie), opStr, result, left, right});
return result;
}

Value *IRInstrument::preUnaryExpression(
ESTree::UnaryExpressionNode *node,
Value *operand) {
if (!enabled_)
return nullptr;

auto *opStr = builder_.getLiteralString(node->_operator->str());
return invokeHook("preUnary", {getIID(node), opStr, operand});
}
Value *IRInstrument::postUnaryExpression(
ESTree::UnaryExpressionNode *node,
Value *cookie,
Value *result,
Value *operand) {
if (!enabled_)
return result;

auto *opStr = builder_.getLiteralString(node->_operator->str());
return invokeHook(
"postUnary",
{getIID(node), undefinedIfNull(cookie), opStr, result, operand});
}

Value *IRInstrument::preAssignment(
ESTree::AssignmentExpressionNode *node,
Value *left,
Value *right) {
if (!enabled_)
return nullptr;

auto *opStr = builder_.getLiteralString(node->_operator->str());
return invokeHook(
"preAssignment", {getIID(node), opStr, undefinedIfNull(left), right});
}

Value *IRInstrument::postAssignment(
ESTree::AssignmentExpressionNode *node,
Value *cookie,
Value *result,
Value *left,
Value *right) {
if (!enabled_)
return result;

auto *opStr = builder_.getLiteralString(node->_operator->str());
return invokeHook(
"postAssignment",
{getIID(node),
undefinedIfNull(cookie),
opStr,
result,
undefinedIfNull(left),
right});
}
#endif // HERMES_ENABLE_IR_INSTRUMENTATION

} // namespace hermes
Loading

0 comments on commit c3cc14c

Please sign in to comment.