Recognize unsafe Wasm intrinsics

Summary:
This Diff we provide a way to define and recognize unsafe compiler
intrinsics used in Asm.js/Wasm. For now during the pass it only checks
if an intrinsic is defined and is called with correct number of arguments.

Reviewed By: tmikov

Differential Revision: D29637395

fbshipit-source-id: 663bb77ee3beff277b66fb459c5b57be4d735c60
This commit is contained in:
Lin Cheng 2021-07-24 08:21:01 -07:00 коммит произвёл Facebook GitHub Bot
Родитель 92e16809eb
Коммит 32d4b941e8
14 изменённых файлов: 457 добавлений и 0 удалений

Просмотреть файл

@ -227,6 +227,9 @@ set(HERMESVM_EXCEPTION_ON_OOM OFF CACHE BOOL
set(HERMESVM_PLATFORM_LOGGING OFF CACHE BOOL
"hermesLog(...) is enabled, using the platform's logging mechanism")
set(HERMES_RUN_WASM ON CACHE BOOL
"Emit Asm.js/Wasm unsafe compiler intrinsics")
set(HERMES_USE_FLOWPARSER OFF CACHE BOOL
"Use libflowparser for parsing es6")
@ -403,6 +406,9 @@ endif()
if(HERMESVM_PLATFORM_LOGGING)
add_definitions(-DHERMESVM_PLATFORM_LOGGING)
endif()
if(HERMES_RUN_WASM)
add_definitions(-DHERMES_RUN_WASM)
endif()
if (NOT (ANDROID_LINUX_PERF_PATH STREQUAL ""))
add_definitions(-DANDROID_LINUX_PERF_PATH="${ANDROID_LINUX_PERF_PATH}")
endif()

Просмотреть файл

@ -19,6 +19,10 @@ namespace hbc {
class BackendContext;
}
#ifdef HERMES_RUN_WASM
class EmitWasmIntrinsicsContext;
#endif // HERMES_RUN_WASM
struct CodeGenerationSettings {
/// Whether we should emit TDZ checks.
bool enableTDZ{true};
@ -60,6 +64,9 @@ struct OptimizationSettings {
/// Attempt to resolve CommonJS require() calls at compile time.
bool staticRequire{false};
/// Recognize and emit Asm.js/Wasm unsafe compiler intrinsics.
bool useUnsafeIntrinsics{false};
};
enum class DebugInfoSetting {
@ -213,6 +220,10 @@ class Context {
/// on its destructor.
std::shared_ptr<hbc::BackendContext> hbcBackendContext_{};
#ifdef HERMES_RUN_WASM
std::shared_ptr<EmitWasmIntrinsicsContext> wasmIntrinsicsContext_{};
#endif // HERMES_RUN_WASM
public:
explicit Context(
SourceErrorManager &sm,
@ -392,6 +403,10 @@ class Context {
return optimizationSettings_.staticBuiltins;
}
bool getUseUnsafeIntrinsics() const {
return optimizationSettings_.useUnsafeIntrinsics;
}
const CodeGenerationSettings &getCodeGenerationSettings() const {
return codeGenerationSettings_;
}
@ -418,6 +433,17 @@ class Context {
std::shared_ptr<hbc::BackendContext> hbcBackendContext) {
hbcBackendContext_ = std::move(hbcBackendContext);
}
#ifdef HERMES_RUN_WASM
EmitWasmIntrinsicsContext *getWasmIntrinsicsContext() {
return wasmIntrinsicsContext_.get();
}
void setWasmIntrinsicsContext(
std::shared_ptr<EmitWasmIntrinsicsContext> wasmIntrinsicsContext) {
wasmIntrinsicsContext_ = std::move(wasmIntrinsicsContext);
}
#endif // HERMES_RUN_WASM
};
} // namespace hermes

Просмотреть файл

@ -0,0 +1,28 @@
/*
* 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.
*/
#ifdef HERMES_RUN_WASM
#ifndef HERMES_BCGEN_HBC_PASSES_WASMINTRINSICSPASS_H
#define HERMES_BCGEN_HBC_PASSES_WASMINTRINSICSPASS_H
#include "hermes/Optimizer/PassManager/Pass.h"
namespace hermes {
/// Detect calls to unsafe intrinsics like `__uasm.add32()` and replace them
/// with CallIntrinsicsInst.
class EmitWasmIntrinsics : public FunctionPass {
public:
explicit EmitWasmIntrinsics() : FunctionPass("EmitWasmIntrinsics") {}
bool runOnFunction(Function *F) override;
};
} // namespace hermes
#endif // HERMES_BCGEN_HBC_PASSES_WASMINTRINSICSPASS_H
#endif // HERMES_RUN_WASM

Просмотреть файл

@ -0,0 +1,45 @@
/*
* 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.
*/
#ifndef WASM_INTRINSICS
#define WASM_INTRINSICS(name, numArgs)
#endif
// Arithmetic
WASM_INTRINSICS(add32, 2)
WASM_INTRINSICS(sub32, 2)
WASM_INTRINSICS(mul32, 2)
WASM_INTRINSICS(divi32, 2)
WASM_INTRINSICS(divu32, 2)
WASM_INTRINSICS(modi32, 2)
WASM_INTRINSICS(modu32, 2)
WASM_INTRINSICS(shl32, 2)
WASM_INTRINSICS(shri32, 2)
WASM_INTRINSICS(shru32, 2)
// Comparison
WASM_INTRINSICS(eq32, 2)
WASM_INTRINSICS(ne32, 2)
WASM_INTRINSICS(lti32, 2)
WASM_INTRINSICS(ltu32, 2)
// Memory
WASM_INTRINSICS(loadi8, 2)
WASM_INTRINSICS(loadu8, 2)
WASM_INTRINSICS(loadi16, 2)
WASM_INTRINSICS(loadu16, 2)
WASM_INTRINSICS(load32, 2)
WASM_INTRINSICS(loadf32, 2)
WASM_INTRINSICS(loadf64, 2)
WASM_INTRINSICS(store8, 3)
WASM_INTRINSICS(store16, 3)
WASM_INTRINSICS(store32, 3)
WASM_INTRINSICS(storef32, 3)
WASM_INTRINSICS(storef64, 3)
#undef WASM_INTRINSICS

Просмотреть файл

@ -0,0 +1,31 @@
/*
* 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.
*/
#ifdef HERMES_RUN_WASM
#ifndef HERMES_INST_WASM_INTRINSICS_H
#define HERMES_INST_WASM_INTRINSICS_H
#include <cassert>
namespace hermes {
namespace WasmIntrinsics {
enum Enum : unsigned char {
#define WASM_INTRINSICS(name, numArgs) __uasm##_##name,
#include "WasmIntrinsics.def"
_count,
};
} // namespace WasmIntrinsics
/// Return a string representation of the Wasm intrinsics.
const char *getWasmIntrinsicsName(unsigned intrinsics);
} // namespace hermes
#endif // HERMES_INST_WASM_INTRINSICS_H
#endif // HERMES_RUN_WASM

Просмотреть файл

@ -26,6 +26,8 @@ add_hermes_library(hermesOptimizer
Optimizer/Scalar/HoistStartGenerator.cpp
Optimizer/Scalar/InstructionEscapeAnalysis.cpp
Optimizer/Scalar/TDZDedup.cpp
Optimizer/Wasm/WasmIntrinsics.cpp
Optimizer/Wasm/EmitWasmIntrinsics.cpp
IR/Analysis.cpp
IR/IREval.cpp
)

Просмотреть файл

@ -553,6 +553,13 @@ static opt<bool> InstrumentIR(
init(false),
Hidden,
cat(CompilerCategory));
static CLFlag UseUnsafeIntrinsics(
'f',
"unsafe-intrinsics",
false,
"Recognize and lower Asm.js/Wasm unsafe compiler intrinsics.",
CompilerCategory);
} // namespace cl
namespace {
@ -1054,6 +1061,8 @@ std::shared_ptr<Context> createContext(
cl::StaticBuiltins == cl::StaticBuiltinSetting::ForceOn;
optimizationOpts.staticRequire = cl::StaticRequire;
optimizationOpts.useUnsafeIntrinsics = cl::UseUnsafeIntrinsics;
auto context = std::make_shared<Context>(
codeGenOpts,
optimizationOpts,

Просмотреть файл

@ -13,6 +13,7 @@
#include "hermes/Optimizer/Scalar/SimplifyCFG.h"
#include "hermes/Optimizer/Scalar/StackPromotion.h"
#include "hermes/Optimizer/Scalar/TypeInference.h"
#include "hermes/Optimizer/Wasm/EmitWasmIntrinsics.h"
#include "llvh/Support/Debug.h"
#include "llvh/Support/raw_ostream.h"
@ -81,6 +82,13 @@ void hermes::runFullOptimizationPasses(Module &M) {
// Move StartGenerator instructions to the start of functions.
PM.addHoistStartGenerator();
#ifdef HERMES_RUN_WASM
// Emit Asm.js/Wasm unsafe compiler intrinsics, if enabled.
if (M.getContext().getUseUnsafeIntrinsics()) {
PM.addPass(new EmitWasmIntrinsics());
}
#endif // HERMES_RUN_WASM
// Run the optimizations.
PM.run(&M);
}
@ -95,12 +103,32 @@ void hermes::runDebugOptimizationPasses(Module &M) {
// Move StartGenerator instructions to the start of functions.
PM.addHoistStartGenerator();
#ifdef HERMES_RUN_WASM
// Emit Asm.js/Wasm unsafe compiler intrinsics, if enabled.
if (M.getContext().getUseUnsafeIntrinsics()) {
PM.addPass(new EmitWasmIntrinsics());
}
#endif // HERMES_RUN_WASM
// Run the optimizations.
PM.run(&M);
}
#ifdef HERMES_RUN_WASM
void hermes::runNoOptimizationPasses(Module &M) {
LLVM_DEBUG(dbgs() << "Running -O0 optimizations...\n");
// Emit Asm.js/Wasm unsafe compiler intrinsics, if enabled.
if (M.getContext().getUseUnsafeIntrinsics()) {
PassManager PM;
PM.addPass(new EmitWasmIntrinsics());
PM.run(&M);
}
}
#else
void hermes::runNoOptimizationPasses(Module &) {
LLVM_DEBUG(dbgs() << "Running -O0 optimizations...\n");
}
#endif // HERMES_RUN_WASM
#undef DEBUG_TYPE

Просмотреть файл

@ -0,0 +1,162 @@
/*
* 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.
*/
#ifdef HERMES_RUN_WASM
#define DEBUG_TYPE "EmitWasmIntrinsics"
#include "hermes/Optimizer/Wasm/EmitWasmIntrinsics.h"
#include "hermes/IR/IRBuilder.h"
#include "hermes/Optimizer/Wasm/WasmIntrinsics.h"
#include "hermes/Support/Statistic.h"
#include "hermes/Support/StringTable.h"
#include "llvh/ADT/DenseMap.h"
#include "llvh/Support/Debug.h"
STATISTIC(NumWasmIntrinsics, "Number of Wasm intrinsics recognized");
namespace hermes {
class EmitWasmIntrinsicsContext {
public:
explicit EmitWasmIntrinsicsContext(StringTable &strTab);
static EmitWasmIntrinsicsContext &get(Context &ctx) {
if (!ctx.getWasmIntrinsicsContext()) {
auto wasmIntrinsicsContext =
std::make_shared<EmitWasmIntrinsicsContext>(ctx.getStringTable());
ctx.setWasmIntrinsicsContext(wasmIntrinsicsContext);
}
return *ctx.getWasmIntrinsicsContext();
}
/// Look for unsafe compiler intrinsics.
hermes::OptValue<WasmIntrinsics::Enum> findWasmIntrinsics(
Identifier intrinsicsName);
/// Lookup the expected number of arguments for a specific intrinsic.
unsigned getWasmIntrinsicsNumArgs(WasmIntrinsics::Enum index) {
return intrinsicsNumArgs_[index];
}
private:
/// Map from an intrinisc name to an integer "intrinsic index".
llvh::DenseMap<Identifier, WasmIntrinsics::Enum> intrinsics_;
/// Map from an integer "intrinsic index" to its expected number of
/// arguments.
unsigned intrinsicsNumArgs_[WasmIntrinsics::_count];
};
EmitWasmIntrinsicsContext::EmitWasmIntrinsicsContext(StringTable &strTab) {
// Insert all intrinsics.
int intrinsicsIndex = 0;
#define WASM_INTRINSICS(name, numArgs) \
intrinsics_[strTab.getIdentifier(#name)] = \
(WasmIntrinsics::Enum)(intrinsicsIndex++);
#include "hermes/Optimizer/Wasm/WasmIntrinsics.def"
// Insert number of arguments.
intrinsicsIndex = 0;
#define WASM_INTRINSICS(name, numArgs) \
intrinsicsNumArgs_[intrinsicsIndex++] = numArgs;
#include "hermes/Optimizer/Wasm/WasmIntrinsics.def"
}
hermes::OptValue<WasmIntrinsics::Enum>
EmitWasmIntrinsicsContext::findWasmIntrinsics(Identifier intrinsicsName) {
auto intrinsicsIt = intrinsics_.find(intrinsicsName);
if (intrinsicsIt == intrinsics_.end()) {
return llvh::None;
}
return intrinsicsIt->second;
}
bool EmitWasmIntrinsics::runOnFunction(Function *F) {
NumWasmIntrinsics = 0;
IRBuilder builder{F};
bool changed = false;
auto &wasmIntrinsics = EmitWasmIntrinsicsContext::get(F->getContext());
for (auto &BB : *F) {
for (auto it = BB.begin(), e = BB.end(); it != e;) {
auto *inst = &*it++;
// Look for an instruction sequence of the kind:
// (Call
// (LoadProperty
// (LoadProperty globalObject, "__uasm")
// Prop)
// ...)
if (inst->getKind() != ValueKind::CallInstKind)
continue;
auto *callInst = cast<CallInst>(inst);
auto *loadProp = llvh::dyn_cast<LoadPropertyInst>(callInst->getCallee());
if (!loadProp)
continue;
auto propLit = llvh::dyn_cast<LiteralString>(loadProp->getProperty());
if (!propLit)
continue;
auto *loadGlobalProp =
llvh::dyn_cast<LoadPropertyInst>(loadProp->getObject());
if (!loadGlobalProp)
continue;
if (!llvh::isa<GlobalObject>(loadGlobalProp->getObject()))
continue;
LiteralString *objLit =
llvh::dyn_cast<LiteralString>(loadGlobalProp->getProperty());
if (!objLit || (objLit->getValue().str() != "__uasm"))
continue;
// Check if the intrinsic is defined.
auto wasmIntrinsicsIndex =
wasmIntrinsics.findWasmIntrinsics(propLit->getValue());
if (!wasmIntrinsicsIndex) {
// Undefined intrinsics in __uasm namesapce.
F->getContext().getSourceErrorManager().error(
F->getSourceRange(),
Twine("the intrinsic \"") + propLit->getValue().str() +
"\" is undefined.");
continue;
}
LLVM_DEBUG(
llvh::dbgs() << "__uasm call: " << objLit->getValue() << "."
<< propLit->getValue() << "\n");
LLVM_DEBUG(
llvh::dbgs() << "Wasm intriniscs found [" << (int)*wasmIntrinsicsIndex
<< "] " << getWasmIntrinsicsName(*wasmIntrinsicsIndex)
<< "()\n");
// Check if the intrinsic call has the correct number of arguments.
unsigned numArgsExcludingThis = callInst->getNumArguments() - 1;
unsigned numExpectedArgs =
wasmIntrinsics.getWasmIntrinsicsNumArgs(*wasmIntrinsicsIndex);
if (numArgsExcludingThis != numExpectedArgs) {
F->getContext().getSourceErrorManager().error(
F->getSourceRange(),
Twine("the intrinsic \"") + propLit->getValue().str() +
"\" is called with incorrect number of arguments. Expecting " +
llvh::Twine(numExpectedArgs) + " but got " +
llvh::Twine(numArgsExcludingThis) + ".");
continue;
}
NumWasmIntrinsics++;
}
}
return changed;
}
} // namespace hermes
#undef DEBUG_TYPE
#endif // HERMES_RUN_WASM

Просмотреть файл

@ -0,0 +1,27 @@
/*
* 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.
*/
#ifdef HERMES_RUN_WASM
#include "hermes/Optimizer/Wasm/WasmIntrinsics.h"
#include <cassert>
namespace hermes {
static const char *wasmIntrinsicsName[] = {
#define WASM_INTRINSICS(name, numArgs) "__uasm." #name "_" #numArgs,
#include "hermes/Optimizer/Wasm/WasmIntrinsics.def"
};
const char *getWasmIntrinsicsName(unsigned intrinsics) {
assert(
intrinsics < WasmIntrinsics::_count && "invalid Wasm intrinsics index");
return wasmIntrinsicsName[intrinsics];
}
} // namespace hermes
#endif // HERMES_RUN_WASM

Просмотреть файл

@ -0,0 +1,24 @@
/**
* 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.
*/
// RUN: %hermesc -dump-lir %s | %FileCheck --match-full-lines %s
// LowerIntrinsics pass is only enabled if -funsafe-intrinsics is set.
function undefinedIntrinsic(func) {
return __uasm.foo(42, 3);
}
//CHECK-LABEL:function undefinedIntrinsic(func)
//CHECK-NEXT:frame = []
//CHECK-NEXT:%BB0:
//CHECK-NEXT: %0 = HBCGetGlobalObjectInst
//CHECK-NEXT: %1 = TryLoadGlobalPropertyInst %0 : object, "__uasm" : string
//CHECK-NEXT: %2 = LoadPropertyInst %1, "foo" : string
//CHECK-NEXT: %3 = HBCLoadConstInst 42 : number
//CHECK-NEXT: %4 = HBCLoadConstInst 3 : number
//CHECK-NEXT: %5 = HBCCallNInst %2, %1, %3 : number, %4 : number
//CHECK-NEXT: %6 = ReturnInst %5
//CHECK-NEXT:function_end

Просмотреть файл

@ -0,0 +1,16 @@
/**
* 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.
*/
// RUN: ( ! %hermesc -funsafe-intrinsics -dump-lir -Werror %s 2>&1 ) | %FileCheck %s --match-full-lines
function undefinedIntrinsic(func) {
return __uasm.foo(42, 3);
}
//CHECK: {{.*}}unsafe-intrinsics-undefined.js:10:1: error: the intrinsic "foo" is undefined.
//CHECK-NEXT: function undefinedIntrinsic(func) {
//CHECK-NEXT: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//CHECK-NEXT: Emitted 1 errors. exiting.

Просмотреть файл

@ -0,0 +1,16 @@
/**
* 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.
*/
// RUN: ( ! %hermesc -funsafe-intrinsics -dump-lir -Werror %s 2>&1 ) | %FileCheck %s --match-full-lines
function wrongNumArgs(func) {
return __uasm.add32(1, 2, 3);
}
//CHECK: {{.*}}unsafe-intrinsics-wrong-num-args.js:10:1: error: the intrinsic "add32" is called with incorrect number of arguments. Expecting 2 but got 3.
//CHECK-NEXT: function wrongNumArgs(func) {
//CHECK-NEXT: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//CHECK-NEXT: Emitted 1 errors. exiting.

Просмотреть файл

@ -0,0 +1,37 @@
/**
* 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.
*/
// RUN: %hermesc -funsafe-intrinsics -dump-lir %s | %FileCheck --match-full-lines %s
// Instrinsics that are defined should not cause any error. But
// we also do not do anything, yet.
function unsafeIntrinsics(func) {
t0 = __uasm.add32(1, 2);
t1 = __uasm.modu32(42, 7);
return t0 + t1;
}
//CHECK-LABEL:function unsafeIntrinsics(func) : string|number
//CHECK-NEXT:frame = []
//CHECK-NEXT:%BB0:
//CHECK-NEXT: %0 = HBCGetGlobalObjectInst
//CHECK-NEXT: %1 = TryLoadGlobalPropertyInst %0 : object, "__uasm" : string
//CHECK-NEXT: %2 = LoadPropertyInst %1, "add32" : string
//CHECK-NEXT: %3 = HBCLoadConstInst 1 : number
//CHECK-NEXT: %4 = HBCLoadConstInst 2 : number
//CHECK-NEXT: %5 = HBCCallNInst %2, %1, %3 : number, %4 : number
//CHECK-NEXT: %6 = StorePropertyInst %5, %0 : object, "t0" : string
//CHECK-NEXT: %7 = TryLoadGlobalPropertyInst %0 : object, "__uasm" : string
//CHECK-NEXT: %8 = LoadPropertyInst %7, "modu32" : string
//CHECK-NEXT: %9 = HBCLoadConstInst 42 : number
//CHECK-NEXT: %10 = HBCLoadConstInst 7 : number
//CHECK-NEXT: %11 = HBCCallNInst %8, %7, %9 : number, %10 : number
//CHECK-NEXT: %12 = StorePropertyInst %11, %0 : object, "t1" : string
//CHECK-NEXT: %13 = TryLoadGlobalPropertyInst %0 : object, "t0" : string
//CHECK-NEXT: %14 = TryLoadGlobalPropertyInst %0 : object, "t1" : string
//CHECK-NEXT: %15 = BinaryOperatorInst '+', %13, %14
//CHECK-NEXT: %16 = ReturnInst %15 : string|number
//CHECK-NEXT:function_end