diff --git a/CMakeLists.txt b/CMakeLists.txt index 41340b3a4..c0bc31880 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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() diff --git a/include/hermes/AST/Context.h b/include/hermes/AST/Context.h index fcc7a8e8d..9474bb527 100644 --- a/include/hermes/AST/Context.h +++ b/include/hermes/AST/Context.h @@ -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 hbcBackendContext_{}; +#ifdef HERMES_RUN_WASM + std::shared_ptr 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 hbcBackendContext) { hbcBackendContext_ = std::move(hbcBackendContext); } + +#ifdef HERMES_RUN_WASM + EmitWasmIntrinsicsContext *getWasmIntrinsicsContext() { + return wasmIntrinsicsContext_.get(); + } + + void setWasmIntrinsicsContext( + std::shared_ptr wasmIntrinsicsContext) { + wasmIntrinsicsContext_ = std::move(wasmIntrinsicsContext); + } +#endif // HERMES_RUN_WASM }; } // namespace hermes diff --git a/include/hermes/Optimizer/Wasm/EmitWasmIntrinsics.h b/include/hermes/Optimizer/Wasm/EmitWasmIntrinsics.h new file mode 100644 index 000000000..cb09ed46f --- /dev/null +++ b/include/hermes/Optimizer/Wasm/EmitWasmIntrinsics.h @@ -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 diff --git a/include/hermes/Optimizer/Wasm/WasmIntrinsics.def b/include/hermes/Optimizer/Wasm/WasmIntrinsics.def new file mode 100644 index 000000000..4fa6fee0a --- /dev/null +++ b/include/hermes/Optimizer/Wasm/WasmIntrinsics.def @@ -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 diff --git a/include/hermes/Optimizer/Wasm/WasmIntrinsics.h b/include/hermes/Optimizer/Wasm/WasmIntrinsics.h new file mode 100644 index 000000000..a11fbcf06 --- /dev/null +++ b/include/hermes/Optimizer/Wasm/WasmIntrinsics.h @@ -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 + +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 diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index fa8048c4c..7967b8e70 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -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 ) diff --git a/lib/CompilerDriver/CompilerDriver.cpp b/lib/CompilerDriver/CompilerDriver.cpp index cbcc03811..5c4fd1292 100644 --- a/lib/CompilerDriver/CompilerDriver.cpp +++ b/lib/CompilerDriver/CompilerDriver.cpp @@ -553,6 +553,13 @@ static opt 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 createContext( cl::StaticBuiltins == cl::StaticBuiltinSetting::ForceOn; optimizationOpts.staticRequire = cl::StaticRequire; + optimizationOpts.useUnsafeIntrinsics = cl::UseUnsafeIntrinsics; + auto context = std::make_shared( codeGenOpts, optimizationOpts, diff --git a/lib/Optimizer/PassManager/Pipeline.cpp b/lib/Optimizer/PassManager/Pipeline.cpp index 3bf09498a..88d9f402d 100644 --- a/lib/Optimizer/PassManager/Pipeline.cpp +++ b/lib/Optimizer/PassManager/Pipeline.cpp @@ -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 diff --git a/lib/Optimizer/Wasm/EmitWasmIntrinsics.cpp b/lib/Optimizer/Wasm/EmitWasmIntrinsics.cpp new file mode 100644 index 000000000..92c0ec710 --- /dev/null +++ b/lib/Optimizer/Wasm/EmitWasmIntrinsics.cpp @@ -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(ctx.getStringTable()); + ctx.setWasmIntrinsicsContext(wasmIntrinsicsContext); + } + + return *ctx.getWasmIntrinsicsContext(); + } + + /// Look for unsafe compiler intrinsics. + hermes::OptValue 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 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 +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(inst); + auto *loadProp = llvh::dyn_cast(callInst->getCallee()); + if (!loadProp) + continue; + auto propLit = llvh::dyn_cast(loadProp->getProperty()); + if (!propLit) + continue; + auto *loadGlobalProp = + llvh::dyn_cast(loadProp->getObject()); + if (!loadGlobalProp) + continue; + if (!llvh::isa(loadGlobalProp->getObject())) + continue; + LiteralString *objLit = + llvh::dyn_cast(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 diff --git a/lib/Optimizer/Wasm/WasmIntrinsics.cpp b/lib/Optimizer/Wasm/WasmIntrinsics.cpp new file mode 100644 index 000000000..f5c7d8643 --- /dev/null +++ b/lib/Optimizer/Wasm/WasmIntrinsics.cpp @@ -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 + +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 diff --git a/test/Optimizer/wasm/unsafe-intrinsics-disable.js b/test/Optimizer/wasm/unsafe-intrinsics-disable.js new file mode 100644 index 000000000..cbd27a05e --- /dev/null +++ b/test/Optimizer/wasm/unsafe-intrinsics-disable.js @@ -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 diff --git a/test/Optimizer/wasm/unsafe-intrinsics-undefined.js b/test/Optimizer/wasm/unsafe-intrinsics-undefined.js new file mode 100644 index 000000000..7bbfc1d2d --- /dev/null +++ b/test/Optimizer/wasm/unsafe-intrinsics-undefined.js @@ -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. diff --git a/test/Optimizer/wasm/unsafe-intrinsics-wrong-num-args.js b/test/Optimizer/wasm/unsafe-intrinsics-wrong-num-args.js new file mode 100644 index 000000000..fe49fb103 --- /dev/null +++ b/test/Optimizer/wasm/unsafe-intrinsics-wrong-num-args.js @@ -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. diff --git a/test/Optimizer/wasm/unsafe-intrinsics.js b/test/Optimizer/wasm/unsafe-intrinsics.js new file mode 100644 index 000000000..bdb52310c --- /dev/null +++ b/test/Optimizer/wasm/unsafe-intrinsics.js @@ -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