Ignore:
Timestamp:
Jan 15, 2009, 7:20:35 PM (16 years ago)
Author:
[email protected]
Message:

2009-01-15 Gavin Barraclough <[email protected]>

Reviewed by Geoff Garen.

On x86-64 allow JSImmediate to encode 64-bit double precision values.
This patch only affects builds that set USE(ALTERNATE_JSIMMEDIATE).

Updates the implementation of JSValuePtr
and JSImmediate:: methods that operate on neumeric values to be be aware of the new representation. When this representation is in use, the class JSNumberCell is redundant and is compiled out.

The format of the new immediate representation is documented in JSImmediate.h.

  • JavaScriptCore.exp:
  • assembler/MacroAssembler.h: (JSC::MacroAssembler::subPtr):
  • assembler/X86Assembler.h: (JSC::X86Assembler::): (JSC::X86Assembler::subq_rr): (JSC::X86Assembler::movq_rr): (JSC::X86Assembler::ucomisd_rr): (JSC::X86Assembler::X86InstructionFormatter::twoByteOp64):
  • interpreter/Interpreter.cpp: (JSC::Interpreter::cti_op_stricteq): (JSC::Interpreter::cti_op_nstricteq):
  • jit/JIT.cpp: (JSC::JIT::compileOpStrictEq): (JSC::JIT::privateCompileMainPass): (JSC::JIT::privateCompileSlowCases):
  • jit/JIT.h:
  • jit/JITArithmetic.cpp: (JSC::JIT::compileFastArith_op_lshift): (JSC::JIT::compileFastArith_op_rshift): (JSC::JIT::compileFastArith_op_bitand): (JSC::JIT::compileFastArith_op_mod): (JSC::JIT::compileFastArith_op_add): (JSC::JIT::compileFastArith_op_mul): (JSC::JIT::compileFastArith_op_post_inc): (JSC::JIT::compileFastArith_op_post_dec): (JSC::JIT::compileFastArith_op_pre_inc): (JSC::JIT::compileFastArith_op_pre_dec): (JSC::JIT::putDoubleResultToJSNumberCellOrJSImmediate): (JSC::JIT::compileBinaryArithOp):
  • jit/JITInlineMethods.h: (JSC::JIT::emitJumpIfBothJSCells): (JSC::JIT::emitJumpIfEitherNumber): (JSC::JIT::emitJumpIfNotEitherNumber): (JSC::JIT::emitJumpIfImmediateIntegerNumber): (JSC::JIT::emitJumpIfNotImmediateIntegerNumber): (JSC::JIT::emitJumpIfNotImmediateIntegerNumbers): (JSC::JIT::emitJumpSlowCaseIfNotImmediateIntegerNumber): (JSC::JIT::emitJumpSlowCaseIfNotImmediateIntegerNumbers): (JSC::JIT::emitFastArithDeTagImmediate): (JSC::JIT::emitFastArithDeTagImmediateJumpIfZero): (JSC::JIT::emitFastArithReTagImmediate): (JSC::JIT::emitFastArithIntToImmNoCheck):
  • runtime/JSCell.h:
  • runtime/JSGlobalData.cpp: (JSC::JSGlobalData::JSGlobalData):
  • runtime/JSImmediate.cpp: (JSC::JSImmediate::toThisObject): (JSC::JSImmediate::toObject): (JSC::JSImmediate::toString):
  • runtime/JSImmediate.h: (JSC::wtf_reinterpret_cast): (JSC::JSImmediate::isNumber): (JSC::JSImmediate::isIntegerNumber): (JSC::JSImmediate::isDoubleNumber): (JSC::JSImmediate::isPositiveIntegerNumber): (JSC::JSImmediate::areBothImmediateIntegerNumbers): (JSC::JSImmediate::makeInt): (JSC::JSImmediate::makeDouble): (JSC::JSImmediate::doubleValue): (JSC::doubleToBoolean): (JSC::JSImmediate::toBoolean): (JSC::JSImmediate::getTruncatedUInt32): (JSC::JSImmediate::makeOutOfIntegerRange): (JSC::JSImmediate::from): (JSC::JSImmediate::getTruncatedInt32): (JSC::JSImmediate::toDouble): (JSC::JSImmediate::getUInt32): (JSC::JSValuePtr::isInt32Fast): (JSC::JSValuePtr::isUInt32Fast): (JSC::JSValuePtr::areBothInt32Fast): (JSC::JSFastMath::canDoFastBitwiseOperations): (JSC::JSFastMath::xorImmediateNumbers): (JSC::JSFastMath::canDoFastRshift): (JSC::JSFastMath::canDoFastUrshift): (JSC::JSFastMath::rightShiftImmediateNumbers): (JSC::JSFastMath::canDoFastAdditiveOperations): (JSC::JSFastMath::addImmediateNumbers): (JSC::JSFastMath::subImmediateNumbers):
  • runtime/JSNumberCell.cpp: (JSC::jsNumberCell):
  • runtime/JSNumberCell.h: (JSC::createNumberStructure): (JSC::isNumberCell): (JSC::asNumberCell): (JSC::jsNumber): (JSC::JSValuePtr::isDoubleNumber): (JSC::JSValuePtr::getDoubleNumber): (JSC::JSValuePtr::isNumber): (JSC::JSValuePtr::uncheckedGetNumber): (JSC::jsNaN): (JSC::JSValuePtr::getNumber): (JSC::JSValuePtr::numberToInt32): (JSC::JSValuePtr::numberToUInt32):
  • runtime/JSValue.h:
  • runtime/NumberConstructor.cpp: (JSC::numberConstructorNegInfinity): (JSC::numberConstructorPosInfinity): (JSC::numberConstructorMaxValue): (JSC::numberConstructorMinValue):
  • runtime/NumberObject.cpp: (JSC::constructNumber):
  • runtime/NumberObject.h:
  • runtime/Operations.h: (JSC::JSValuePtr::equal): (JSC::JSValuePtr::equalSlowCaseInline): (JSC::JSValuePtr::strictEqual): (JSC::JSValuePtr::strictEqualSlowCaseInline):
  • wtf/Platform.h:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/JavaScriptCore/runtime/JSImmediate.h

    r39879 r39958  
    2626#include <wtf/AlwaysInline.h>
    2727#include <wtf/MathExtras.h>
     28#include <wtf/StdLibExtras.h>
    2829#include "JSValue.h"
    2930#include <limits>
     
    6869    JSValuePtr jsNumber(JSGlobalData* globalData, unsigned long long i);
    6970
     71#if USE(ALTERNATE_JSIMMEDIATE)
     72    inline intptr_t reinterpretDoubleToIntptr(double value)
     73    {
     74        return WTF::bitwise_cast<intptr_t>(value);
     75    }
     76
     77    inline double reinterpretIntptrToDouble(intptr_t value)
     78    {
     79        return WTF::bitwise_cast<double>(value);
     80    }
     81#endif
     82
    7083    /*
    7184     * A JSValue* is either a pointer to a cell (a heap-allocated object) or an immediate (a type-tagged
     
    112125    /*
    113126     * On 64-bit platforms, we support an alternative encoding form for immediates, if
    114      * USE(ALTERNATE_JSIMMEDIATE) is defined.
    115      *
    116      * The top 16-bits denote the type:
     127     * USE(ALTERNATE_JSIMMEDIATE) is defined.  When this format is used, double precision
     128     * floating point values may also be encoded as JSImmediates.
     129     *
     130     * The encoding makes use of unused NaN space in the IEEE754 representation.  Any value
     131     * with the top 13 bits set represents a QNaN (with the sign bit set).  QNaN values
     132     * can encode a 51-bit payload.  Hardware produced and C-library payloads typically
     133     * have a payload of zero.  We assume that non-zero payloads are available to encode
     134     * pointer and integer values.  Since any 64-bit bit pattern where the top 15 bits are
     135     * all set represents a NaN with a non-zero payload, we can use this space in the NaN
     136     * ranges to encode other values (however there are also other ranges of NaN space that
     137     * could have been selected).  This range of NaN space is represented by 64-bit numbers
     138     * begining with the 16-bit hex patterns 0xFFFE and 0xFFFF - we rely on the fact that no
     139     * valid double-precision numbers will begin fall in these ranges.
     140     *
     141     * The scheme we have implemented encodes double precision values by adding 2^48 to the
     142     * 64-bit integer representation of the number.  After this manipulation, no encoded
     143     * double-precision value will begin with the pattern 0x0000 or 0xFFFF.
     144     *
     145     * The top 16-bits denote the type of the encoded JSImmediate:
    117146     *
    118147     * Pointer: 0000:PPPP:PPPP:PPPP
     148     *          0001:****:****:****
     149     * Double:{         ...
     150     *          FFFE:****:****:****
    119151     * Integer: FFFF:0000:IIII:IIII
    120152     *
    121      * 32-bit signed integers are marked with the 16-bit tag '0xFFFF'.  The tag '0x0000'
     153     * 32-bit signed integers are marked with the 16-bit tag 0xFFFF.  The tag 0x0000
    122154     * denotes a pointer, or another form of tagged immediate.  Boolean, null and undefined
    123155     * values are encoded in the same manner as the default format.
     
    156188
    157189#if USE(ALTERNATE_JSIMMEDIATE)
    158         static const intptr_t TagTypeInteger = 0xffff000000000000ll; // bottom bit set indicates integer, this dominates the following bit
    159 #else
    160         static const intptr_t TagTypeInteger = 0x1; // bottom bit set indicates integer, this dominates the following bit
     190        // If all bits in the mask are set, this indicates an integer number,
     191        // if any but not all are set this value is a double precision number.
     192        static const intptr_t TagTypeNumber = 0xffff000000000000ll;
     193        // This value is 2^48, used to encode doubles such that the encoded value will begin
     194        // with a 16-bit pattern within the range 0x0001..0xFFFE.
     195        static const intptr_t DoubleEncodeOffset = 0x1000000000000ll;
     196#else
     197        static const intptr_t TagTypeNumber = 0x1; // bottom bit set indicates integer, this dominates the following bit
    161198#endif
    162199        static const intptr_t TagBitTypeOther   = 0x2; // second bit set indicates immediate other than an integer
    163         static const intptr_t TagMask           = TagTypeInteger | TagBitTypeOther;
     200        static const intptr_t TagMask           = TagTypeNumber | TagBitTypeOther;
    164201
    165202        static const intptr_t ExtendedTagMask         = 0xC; // extended tag holds a further two bits
     
    190227        static ALWAYS_INLINE bool isNumber(JSValuePtr v)
    191228        {
    192             return rawValue(v) & TagTypeInteger;
    193         }
    194 
    195         static ALWAYS_INLINE bool isPositiveNumber(JSValuePtr v)
     229            return rawValue(v) & TagTypeNumber;
     230        }
     231
     232        static ALWAYS_INLINE bool isIntegerNumber(JSValuePtr v)
     233        {
     234#if USE(ALTERNATE_JSIMMEDIATE)
     235            return (rawValue(v) & TagTypeNumber) == TagTypeNumber;
     236#else
     237            return isNumber(v);
     238#endif
     239        }
     240
     241#if USE(ALTERNATE_JSIMMEDIATE)
     242        static ALWAYS_INLINE bool isDoubleNumber(JSValuePtr v)
     243        {
     244            return isNumber(v) && !isIntegerNumber(v);
     245        }
     246#endif
     247
     248        static ALWAYS_INLINE bool isPositiveIntegerNumber(JSValuePtr v)
    196249        {
    197250            // A single mask to check for the sign bit and the number tag all at once.
    198             return (rawValue(v) & (signBit | TagTypeInteger)) == TagTypeInteger;
     251            return (rawValue(v) & (signBit | TagTypeNumber)) == TagTypeNumber;
    199252        }
    200253       
     
    208261            // Undefined and null share the same value, bar the 'undefined' bit in the extended tag.
    209262            return (rawValue(v) & ~ExtendedTagBitUndefined) == FullTagTypeNull;
    210         }
    211 
    212         static bool isNegative(JSValuePtr v)
    213         {
    214             ASSERT(isNumber(v));
    215             return rawValue(v) & signBit;
    216263        }
    217264
     
    239286        }
    240287
    241         static ALWAYS_INLINE bool areBothImmediateNumbers(JSValuePtr v1, JSValuePtr v2)
    242         {
    243             return rawValue(v1) & rawValue(v2) & TagTypeInteger;
     288        static ALWAYS_INLINE bool areBothImmediateIntegerNumbers(JSValuePtr v1, JSValuePtr v2)
     289        {
     290#if USE(ALTERNATE_JSIMMEDIATE)
     291            return (rawValue(v1) & rawValue(v2) & TagTypeNumber) == TagTypeNumber;
     292#else
     293            return rawValue(v1) & rawValue(v2) & TagTypeNumber;
     294#endif
    244295        }
    245296
     
    283334        }
    284335
     336        // With USE(ALTERNATE_JSIMMEDIATE) we want the argument to be zero extended, so the
     337        // integer doesn't interfere with the tag bits in the upper word.  In the default encoding,
     338        // if intptr_t id larger then int32_t we sign extend the value through the upper word.
    285339#if USE(ALTERNATE_JSIMMEDIATE)
    286340        static ALWAYS_INLINE JSValuePtr makeInt(uint32_t value)
     
    289343#endif
    290344        {
    291             return makeValue((static_cast<intptr_t>(value) << IntegerPayloadShift) | TagTypeInteger);
    292         }
     345            return makeValue((static_cast<intptr_t>(value) << IntegerPayloadShift) | TagTypeNumber);
     346        }
     347       
     348#if USE(ALTERNATE_JSIMMEDIATE)
     349        static ALWAYS_INLINE JSValuePtr makeDouble(double value)
     350        {
     351            return makeValue(reinterpretDoubleToIntptr(value) + DoubleEncodeOffset);
     352        }
     353#endif
    293354       
    294355        static ALWAYS_INLINE JSValuePtr makeBool(bool b)
     
    306367            return makeValue(FullTagTypeNull);
    307368        }
    308        
     369
     370        template<typename T>
     371        static JSValuePtr fromNumberOutsideIntegerRange(T);
     372
     373#if USE(ALTERNATE_JSIMMEDIATE)
     374        static ALWAYS_INLINE double doubleValue(JSValuePtr v)
     375        {
     376            return reinterpretIntptrToDouble(rawValue(v) - DoubleEncodeOffset);
     377        }
     378#endif
     379
    309380        static ALWAYS_INLINE int32_t intValue(JSValuePtr v)
    310381        {
     
    340411    ALWAYS_INLINE JSValuePtr JSImmediate::impossibleValue() { return makeValue(0x4); }
    341412
     413#if USE(ALTERNATE_JSIMMEDIATE)
     414    inline bool doubleToBoolean(double value)
     415    {
     416        return value < 0.0 || value > 0.0;
     417    }
     418
    342419    ALWAYS_INLINE bool JSImmediate::toBoolean(JSValuePtr v)
    343420    {
    344421        ASSERT(isImmediate(v));
    345         intptr_t bits = rawValue(v);
    346         return (bits & TagTypeInteger)
    347             ? bits != TagTypeInteger // !0 ints
    348             : bits == (FullTagTypeBool | ExtendedPayloadBitBoolValue); // bool true
    349     }
     422        return isNumber(v) ? isIntegerNumber(v) ? v != zeroImmediate()
     423            : doubleToBoolean(doubleValue(v)) : v == trueImmediate();
     424    }
     425#else
     426    ALWAYS_INLINE bool JSImmediate::toBoolean(JSValuePtr v)
     427    {
     428        ASSERT(isImmediate(v));
     429        return isIntegerNumber(v) ? v != zeroImmediate() : v == trueImmediate();
     430    }
     431#endif
    350432
    351433    ALWAYS_INLINE uint32_t JSImmediate::getTruncatedUInt32(JSValuePtr v)
    352434    {
    353         ASSERT(isNumber(v));
     435        // FIXME: should probably be asserting isPositiveIntegerNumber here.
     436        ASSERT(isIntegerNumber(v));
    354437        return intValue(v);
    355438    }
    356439
     440#if USE(ALTERNATE_JSIMMEDIATE)
     441    template<typename T>
     442    inline JSValuePtr JSImmediate::fromNumberOutsideIntegerRange(T value)
     443    {
     444        return makeDouble(static_cast<double>(value));
     445    }
     446#else
     447    template<typename T>
     448    inline JSValuePtr JSImmediate::fromNumberOutsideIntegerRange(T)
     449    {
     450        return noValue();
     451    }
     452#endif
     453
    357454    ALWAYS_INLINE JSValuePtr JSImmediate::from(char i)
    358455    {
     
    382479    ALWAYS_INLINE JSValuePtr JSImmediate::from(int i)
    383480    {
     481#if !USE(ALTERNATE_JSIMMEDIATE)
    384482        if ((i < minImmediateInt) | (i > maxImmediateInt))
    385             return noValue();
     483            return fromNumberOutsideIntegerRange(i);
     484#endif
    386485        return makeInt(i);
    387486    }
     
    390489    {
    391490        if (i > maxImmediateUInt)
    392             return noValue();
     491            return fromNumberOutsideIntegerRange(i);
    393492        return makeInt(i);
    394493    }
     
    397496    {
    398497        if ((i < minImmediateInt) | (i > maxImmediateInt))
    399             return noValue();
     498            return fromNumberOutsideIntegerRange(i);
    400499        return makeInt(i);
    401500    }
     
    404503    {
    405504        if (i > maxImmediateUInt)
    406             return noValue();
     505            return fromNumberOutsideIntegerRange(i);
    407506        return makeInt(i);
    408507    }
     
    418517    {
    419518        if (i > maxImmediateUInt)
    420             return noValue();
     519            return fromNumberOutsideIntegerRange(i);
    421520        return makeInt(static_cast<intptr_t>(i));
    422521    }
     
    425524    {
    426525        const int intVal = static_cast<int>(d);
    427 
    428         if ((intVal < minImmediateInt) | (intVal > maxImmediateInt))
    429             return noValue();
    430526
    431527        // Check for data loss from conversion to int.
    432528        if (intVal != d || (!intVal && signbit(d)))
    433             return noValue();
    434 
    435         return makeInt(intVal);
     529            return fromNumberOutsideIntegerRange(d);
     530
     531        return from(intVal);
    436532    }
    437533
    438534    ALWAYS_INLINE int32_t JSImmediate::getTruncatedInt32(JSValuePtr v)
    439535    {
    440         ASSERT(isNumber(v));
     536        ASSERT(isIntegerNumber(v));
    441537        return intValue(v);
    442538    }
     
    445541    {
    446542        ASSERT(isImmediate(v));
    447         int i;
    448         if (isNumber(v))
    449             i = intValue(v);
    450         else if (rawValue(v) == FullTagTypeUndefined)
     543
     544        if (isIntegerNumber(v))
     545            return intValue(v);
     546
     547#if USE(ALTERNATE_JSIMMEDIATE)
     548        if (isNumber(v)) {
     549            ASSERT(isDoubleNumber(v));
     550            return doubleValue(v);
     551        }
     552#else
     553        ASSERT(!isNumber(v));
     554#endif
     555
     556        if (rawValue(v) == FullTagTypeUndefined)
    451557            return nonInlineNaN();
    452         else
    453             i = rawValue(v) >> ExtendedPayloadShift;
    454         return i;
     558
     559        ASSERT(JSImmediate::isBoolean(v) || (v == JSImmediate::nullImmediate()));
     560        return rawValue(v) >> ExtendedPayloadShift;
    455561    }
    456562
     
    458564    {
    459565        i = uintValue(v);
    460         return isPositiveNumber(v);
     566        return isPositiveIntegerNumber(v);
    461567    }
    462568
     
    464570    {
    465571        i = intValue(v);
    466         return isNumber(v);
     572        return isIntegerNumber(v);
    467573    }
    468574
     
    601707    inline bool JSValuePtr::isInt32Fast() const
    602708    {
    603         return JSImmediate::isNumber(asValue());
     709        return JSImmediate::isIntegerNumber(asValue());
    604710    }
    605711
     
    612718    inline bool JSValuePtr::isUInt32Fast() const
    613719    {
    614         return JSImmediate::isPositiveNumber(asValue());
     720        return JSImmediate::isPositiveIntegerNumber(asValue());
    615721    }
    616722
     
    628734    inline bool JSValuePtr::areBothInt32Fast(JSValuePtr v1, JSValuePtr v2)
    629735    {
    630         return JSImmediate::areBothImmediateNumbers(v1, v2);
     736        return JSImmediate::areBothImmediateIntegerNumbers(v1, v2);
    631737    }
    632738
     
    635741        static ALWAYS_INLINE bool canDoFastBitwiseOperations(JSValuePtr v1, JSValuePtr v2)
    636742        {
    637             return JSImmediate::areBothImmediateNumbers(v1, v2);
     743            return JSImmediate::areBothImmediateIntegerNumbers(v1, v2);
    638744        }
    639745
     
    659765        {
    660766            ASSERT(canDoFastBitwiseOperations(v1, v2));
    661             return JSImmediate::makeValue((JSImmediate::rawValue(v1) ^ JSImmediate::rawValue(v2)) | JSImmediate::TagTypeInteger);
     767            return JSImmediate::makeValue((JSImmediate::rawValue(v1) ^ JSImmediate::rawValue(v2)) | JSImmediate::TagTypeNumber);
    662768        }
    663769
     
    670776        static ALWAYS_INLINE bool canDoFastRshift(JSValuePtr v1, JSValuePtr v2)
    671777        {
    672             return JSImmediate::areBothImmediateNumbers(v1, v2);
     778            return JSImmediate::areBothImmediateIntegerNumbers(v1, v2);
    673779        }
    674780
    675781        static ALWAYS_INLINE bool canDoFastUrshift(JSValuePtr v1, JSValuePtr v2)
    676782        {
    677             return JSImmediate::areBothImmediateNumbers(v1, v2) && !JSImmediate::isNegative(v1);
     783            return JSImmediate::areBothImmediateIntegerNumbers(v1, v2) && !(JSImmediate::rawValue(v1) & JSImmediate::signBit);
    678784        }
    679785
     
    682788            ASSERT(canDoFastRshift(val, shift) || canDoFastUrshift(val, shift));
    683789#if USE(ALTERNATE_JSIMMEDIATE)
    684             return JSImmediate::makeValue(static_cast<intptr_t>(static_cast<uint32_t>(static_cast<int32_t>(JSImmediate::rawValue(val)) >> ((JSImmediate::rawValue(shift) >> JSImmediate::IntegerPayloadShift) & 0x1f))) | JSImmediate::TagTypeInteger);
    685 #else
    686             return JSImmediate::makeValue((JSImmediate::rawValue(val) >> ((JSImmediate::rawValue(shift) >> JSImmediate::IntegerPayloadShift) & 0x1f)) | JSImmediate::TagTypeInteger);
     790            return JSImmediate::makeValue(static_cast<intptr_t>(static_cast<uint32_t>(static_cast<int32_t>(JSImmediate::rawValue(val)) >> ((JSImmediate::rawValue(shift) >> JSImmediate::IntegerPayloadShift) & 0x1f))) | JSImmediate::TagTypeNumber);
     791#else
     792            return JSImmediate::makeValue((JSImmediate::rawValue(val) >> ((JSImmediate::rawValue(shift) >> JSImmediate::IntegerPayloadShift) & 0x1f)) | JSImmediate::TagTypeNumber);
    687793#endif
    688794        }
     
    692798            // Number is non-negative and an operation involving two of these can't overflow.
    693799            // Checking for allowed negative numbers takes more time than it's worth on SunSpider.
    694             return (JSImmediate::rawValue(v) & (JSImmediate::TagTypeInteger + (JSImmediate::signBit | (JSImmediate::signBit >> 1)))) == JSImmediate::TagTypeInteger;
     800            return (JSImmediate::rawValue(v) & (JSImmediate::TagTypeNumber + (JSImmediate::signBit | (JSImmediate::signBit >> 1)))) == JSImmediate::TagTypeNumber;
    695801        }
    696802
     
    705811        {
    706812            ASSERT(canDoFastAdditiveOperations(v1, v2));
    707             return JSImmediate::makeValue(JSImmediate::rawValue(v1) + JSImmediate::rawValue(v2) - JSImmediate::TagTypeInteger);
     813            return JSImmediate::makeValue(JSImmediate::rawValue(v1) + JSImmediate::rawValue(v2) - JSImmediate::TagTypeNumber);
    708814        }
    709815
     
    711817        {
    712818            ASSERT(canDoFastAdditiveOperations(v1, v2));
    713             return JSImmediate::makeValue(JSImmediate::rawValue(v1) - JSImmediate::rawValue(v2) + JSImmediate::TagTypeInteger);
     819            return JSImmediate::makeValue(JSImmediate::rawValue(v1) - JSImmediate::rawValue(v2) + JSImmediate::TagTypeNumber);
    714820        }
    715821
Note: See TracChangeset for help on using the changeset viewer.