Skip to content

Instantly share code, notes, and snippets.

@spinda

spinda/jsequalityimpl.md

Last active Mar 15, 2017
Embed
What would you like to do?
js equality implementation (in spidermonkey)

explanation of https://twitter.com/getify/status/780249160662605824 in terms of SpiderMonkey (Firefox) internals

edit: The tweet linked above has since been deleted, but the original code read something like:

' \t\n\r\u000c\u000b\uFEFF\u0020' == 0 // true
  1. The script is parsed and compiled to bytecode.

  2. For simplicity's sake, we assume the code runs through the interpreter rather than one of the JIT compiler backends. The interpreter sees the JSOP_EQ bytecode, representing the non-strict equality operator (==).

    Interpreter.cpp

    CASE(JSOP_EQ)
        if (!LooseEqualityOp<true>(cx, REGS))
            goto error;
    END_CASE(JSOP_EQ)
  3. The interpreter calls LooseEqualityOp

    Interpreter.cpp

    template <bool Eq>
    static MOZ_ALWAYS_INLINE bool
    LooseEqualityOp(JSContext* cx, InterpreterRegs& regs)
    {
        HandleValue rval = regs.stackHandleAt(-1);
        HandleValue lval = regs.stackHandleAt(-2);
        bool cond;
        if (!LooselyEqual(cx, lval, rval, &cond))
            return false;
        cond = (cond == Eq);
        regs.sp--;
        regs.sp[-1].setBoolean(cond);
        return true;
    }

    which in turn calls js::LooselyEqual

    Interpreter.cpp

    // ES6 draft rev32 7.2.12 Abstract Equality Comparison
    bool
    js::LooselyEqual(JSContext* cx, HandleValue lval, HandleValue rval, bool* result)
    {
  4. js::LooselyEqual performs some tests on the left- and right-hand values of the == operator until it comes to

    Interpreter.cpp

        // Step 7.
        if (lval.isString() && rval.isNumber()) {
            double num;
            if (!StringToNumber(cx, lval.toString(), &num))
                return false;

    The left-hand value is a string and the right-hand value is a number, so StringToNumber is called to convert the lval to a number.

  5. StringToNumber delegates to the function CharsToNumber, which skips the spaces in the string. \t, \n, ..., \uFEFF, \u0020 are all space characters, so the result is an empty array of characters ("").

  6. CharsToNumber in turn calls js_strtod (double d is the result number)

    jsnum.cpp

        const CharT* ep;
        double d;
        if (!js_strtod(cx, bp, end, &ep, &d)) {
            *result = GenericNaN();
            return false;
        }
  7. In this case, js_strtod performs some checks and ultimately calls js_strtod_harder (better, faster, stronger...)

    jsnum.cpp

        *d = js_strtod_harder(cx->dtoaState(), chars.begin(), &ep, &err);

    and js_strtod_harder is a thin wrapper around _strtod

    jsdtoa.cpp

    double
    js_strtod_harder(DtoaState* state, const char* s00, char** se, int* err)
    {
        double retval;
        if (err)
            *err = 0;
        retval = _strtod(state, s00, se);
        return retval;
    }
  8. _strtod ("string to double") is an insanely convoluted function, complicated by lots of platform-specific #ifdefs. It's part of dtoa.c, originally written by David M. Gay and pulled in by a bunch of projects including Firefox. In this case, it ends up seeing that the input char array is empty

    dtoa.c

    		case 0:
        		goto ret0;

    and ultimately returns zero.

  9. The zero result propagates all the way back to js::LooselyEqual; the left-hand operand of == has been converted to the number 0. Finally, this number 0 is compared with the number 0 that is the right-hand operand

    Interpreter.cpp

            *result = (num == rval.toNumber());
            return true;

    result is true, which is the value of == given back to the script. return true here indicates that no error occurred (SpiderMonkey uses return codes instead of C++ exceptions).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.