Skip to content

Commit

Permalink
Speed up interpreter number checks for binary operations
Browse files Browse the repository at this point in the history
Summary:
It can sometimes be more efficient to use a NaN check to test whether a
`HermesValue` is a number than `isNumber`. Since we use NaN boxing, the
former can be used as a conservative check for whether a given value is
a number (at the expense of excluding actual NaNs).

In particular, for binary operations, we can test both operands for NaN
with a single comparison, since x86 and ARM have condition codes that
will be set if either operand to a comparison is NaN.

Reviewed By: tmikov

Differential Revision: D65582078

fbshipit-source-id: c06e9686bcba8b675be91f1aa3f28109acc9a44d
  • Loading branch information
neildhar authored and facebook-github-bot committed Nov 23, 2024
1 parent 0308342 commit 18aa2ff
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 45 deletions.
22 changes: 22 additions & 0 deletions include/hermes/VM/sh_legacy_value.h
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,28 @@ static inline bool _sh_ljs_tryfast_truncate_to_int32(
}
}

/// Test whether the given HermesValues are both non-NaN number values.
/// Since we use NaN-boxing, this just checks if either parameter is NaN, which
/// can have some performance advantages over _sh_ljs_is_double():
/// 1. This can typically be done with a single comparison if the
/// architecture provides a condition code that is set if either
/// operand to a comparison is NaN (e.g. VS on ARM).
/// 2. The operation is done in a floating point register, which may avoid
/// some moves if other users of the value are floating point operations.
static inline bool _sh_ljs_are_both_non_nan_numbers(
SHLegacyValue a,
SHLegacyValue b) {
// We do not use isunordered() here because it may produce a call on some
// compilers (e.g. MSVC). Instead, we use a builtin when it is available, or
// fall back to checking each operand for NaN if it is not.
#ifdef __has_builtin
#if __has_builtin(__builtin_isunordered)
return !__builtin_isunordered(a.f64, b.f64);
#endif
#endif
return _sh_ljs_is_non_nan_number(a) && _sh_ljs_is_non_nan_number(b);
}

/// Flags associated with an object.
typedef union {
struct {
Expand Down
91 changes: 46 additions & 45 deletions lib/VM/Interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1238,26 +1238,27 @@ CallResult<HermesValue> Interpreter::interpretFunction(
/// operands are numbers.
/// \param name the name of the instruction. The fast path case will have a
/// "n" appended to the name.
#define BINOP(name) \
CASE(name) { \
if (LLVM_LIKELY(O2REG(name).isNumber() && O3REG(name).isNumber())) { \
/* Fast-path. */ \
CASE(name##N) { \
O1REG(name) = HermesValue::encodeTrustedNumberValue( \
do##name(O2REG(name).getNumber(), O3REG(name).getNumber())); \
ip = NEXTINST(name); \
DISPATCH; \
} \
} \
CAPTURE_IP( \
res = doOperSlowPath_RJS<do##name>( \
runtime, Handle<>(&O2REG(name)), Handle<>(&O3REG(name)))); \
if (res == ExecutionStatus::EXCEPTION) \
goto exception; \
O1REG(name) = *res; \
gcScope.flushToSmallCount(KEEP_HANDLES); \
ip = NEXTINST(name); \
DISPATCH; \
#define BINOP(name) \
CASE(name) { \
if (LLVM_LIKELY( \
_sh_ljs_are_both_non_nan_numbers(O2REG(name), O3REG(name)))) { \
/* Fast-path. */ \
CASE(name##N) { \
O1REG(name) = HermesValue::encodeTrustedNumberValue( \
do##name(O2REG(name).getNumber(), O3REG(name).getNumber())); \
ip = NEXTINST(name); \
DISPATCH; \
} \
} \
CAPTURE_IP( \
res = doOperSlowPath_RJS<do##name>( \
runtime, Handle<>(&O2REG(name)), Handle<>(&O3REG(name)))); \
if (res == ExecutionStatus::EXCEPTION) \
goto exception; \
O1REG(name) = *res; \
gcScope.flushToSmallCount(KEEP_HANDLES); \
ip = NEXTINST(name); \
DISPATCH; \
}

#define INCDECOP(name) \
Expand Down Expand Up @@ -1340,24 +1341,25 @@ CallResult<HermesValue> Interpreter::interpretFunction(
/// \param oper the C++ operator to use to actually perform the fast arithmetic
/// comparison.
/// \param operFuncName function to call for the slow-path comparison.
#define CONDOP(name, oper, operFuncName) \
CASE(name) { \
if (LLVM_LIKELY(O2REG(name).isNumber() && O3REG(name).isNumber())) { \
/* Fast-path. */ \
O1REG(name) = HermesValue::encodeBoolValue( \
O2REG(name).getNumber() oper O3REG(name).getNumber()); \
ip = NEXTINST(name); \
DISPATCH; \
} \
CAPTURE_IP( \
boolRes = operFuncName( \
runtime, Handle<>(&O2REG(name)), Handle<>(&O3REG(name)))); \
if (boolRes == ExecutionStatus::EXCEPTION) \
goto exception; \
gcScope.flushToSmallCount(KEEP_HANDLES); \
O1REG(name) = HermesValue::encodeBoolValue(boolRes.getValue()); \
ip = NEXTINST(name); \
DISPATCH; \
#define CONDOP(name, oper, operFuncName) \
CASE(name) { \
if (LLVM_LIKELY( \
_sh_ljs_are_both_non_nan_numbers(O2REG(name), O3REG(name)))) { \
/* Fast-path. */ \
O1REG(name) = HermesValue::encodeBoolValue( \
O2REG(name).getNumber() oper O3REG(name).getNumber()); \
ip = NEXTINST(name); \
DISPATCH; \
} \
CAPTURE_IP( \
boolRes = operFuncName( \
runtime, Handle<>(&O2REG(name)), Handle<>(&O3REG(name)))); \
if (boolRes == ExecutionStatus::EXCEPTION) \
goto exception; \
gcScope.flushToSmallCount(KEEP_HANDLES); \
O1REG(name) = HermesValue::encodeBoolValue(boolRes.getValue()); \
ip = NEXTINST(name); \
DISPATCH; \
}

/// Implement a comparison conditional jump with a fast path where both
Expand All @@ -1374,9 +1376,8 @@ CallResult<HermesValue> Interpreter::interpretFunction(
#define JCOND_IMPL( \
name, suffix, oper, operFuncName, trueDest, falseDest, NUMBER_CASE) \
CASE(name##suffix) { \
if (LLVM_LIKELY( \
O2REG(name##suffix).isNumber() && \
O3REG(name##suffix).isNumber())) { \
if (LLVM_LIKELY(_sh_ljs_are_both_non_nan_numbers( \
O2REG(name##suffix), O3REG(name##suffix)))) { \
/* Fast-path. */ \
NUMBER_CASE(name##N##suffix) { \
if (O2REG(name##suffix) \
Expand Down Expand Up @@ -2872,9 +2873,8 @@ CallResult<HermesValue> Interpreter::interpretFunction(
DISPATCH;
}
CASE(Add) {
if (LLVM_LIKELY(
O2REG(Add).isNumber() &&
O3REG(Add).isNumber())) { /* Fast-path. */
if (LLVM_LIKELY(_sh_ljs_are_both_non_nan_numbers(
O2REG(Add), O3REG(Add)))) { /* Fast-path. */
O1REG(Add) = HermesValue::encodeTrustedNumberValue(
O2REG(Add).getNumber() + O3REG(Add).getNumber());
ip = NEXTINST(Add);
Expand Down Expand Up @@ -3280,7 +3280,8 @@ CallResult<HermesValue> Interpreter::interpretFunction(
DISPATCH;
}
CASE(Mod) {
if (LLVM_LIKELY(O2REG(Mod).isNumber() && O3REG(Mod).isNumber())) {
if (LLVM_LIKELY(
_sh_ljs_are_both_non_nan_numbers(O2REG(Mod), O3REG(Mod)))) {
/* Fast-path. */
O1REG(Mod) = HermesValue::encodeTrustedNumberValue(
doMod(O2REG(Mod).getNumber(), O3REG(Mod).getNumber()));
Expand Down

0 comments on commit 18aa2ff

Please sign in to comment.