Bug 1195030 - Backout of bug 890329 for breaking email reading in gmail. r=bustage

This commit is contained in:
Till Schneidereit 2015-08-15 23:58:24 +02:00
parent 20c9bb909d
commit 2abf38bbe6
19 changed files with 425 additions and 129 deletions

View File

@ -841,111 +841,3 @@ function ArrayToString() {
return callFunction(std_Object_toString, array);
return callFunction(func, array);
}
//ES 2015, 22.1.3.25 Array.prototype.splice.
function ArraySplice(start, deleteCount /*, ...items */) {
// Steps 1-2.
var O = ToObject(this);
// Steps 3-4.
// FIXME: Array operations should use ToLength (bug 924058).
var len = ToInteger(O.length);
// Steps 5-6.
var relativeStart = ToInteger(start);
// Step 7.
var actualStart = relativeStart < 0
? std_Math_max(len + relativeStart, 0)
: std_Math_min(relativeStart, len);
// Steps 8-10.
var insertCount = 0;
var actualDeleteCount = 0;
var numArgs = arguments.length;
if (numArgs === 1) {
actualDeleteCount = len - actualStart;
} else if (numArgs > 1) {
// Step 10.a.
insertCount = numArgs - 2;
// Steps 10.b-c.
var dc = ToInteger(deleteCount);
// Step 10.d.
actualDeleteCount = std_Math_min(std_Math_max(dc, 0), len - actualStart);
}
// Step 11.
if (len + insertCount - actualDeleteCount > 2 ** 53 - 1)
ThrowTypeError(JSMSG_BAD_ARRAY_LENGTH_SPLICE);
// Steps 12-13.
// FIXME: Use ArraySpeciesCreate here.
var A = NewDenseArray(actualDeleteCount);
// Steps 14-15.
for (var k = 0; k < actualDeleteCount; k++) {
// Step 15.a.
var from = actualStart + k;
// Steps 15.b-d.
if (from in O)
_DefineDataProperty(A, k, O[from]);
}
// Steps 16-17.
A.length = actualDeleteCount;
// Step 18 (implicit).
// Step 19.
var itemCount = insertCount;
// Step 20.
if (itemCount < actualDeleteCount) {
// Steps 20.a-b.
for (var k = actualStart, kMax = len - actualDeleteCount; k < kMax; k++) {
// Step 20.b.i.
var from = k + actualDeleteCount;
// Step 20.b.ii.
var to = k + itemCount;
// Steps 20.b.iii-v.
if (from in O) {
O[to] = O[from];
} else {
// Step 20.b.vi.
delete O[to];
}
}
// Steps 20.c-d.
// For packed arrays we can skip these steps: the fact that we don't
// delete the elements one by one isn't visible to content code.
if (!IsPackedArray(O)) {
for (var k = len, kMin = len - actualDeleteCount + itemCount; k > kMin; k--)
delete O[k - 1];
}
} else if (itemCount > actualDeleteCount) {
// Step 21.
// Steps 21 a-b.
for (var k = len - actualDeleteCount, kMin = actualStart; k > kMin; k--) {
// Step 21.b.i.
var from = k + actualDeleteCount - 1;
// Step 21.b.ii.
var to = k + itemCount - 1;
// Steps 21.b.iii-v.
if (from in O) {
O[to] = O[from];
} else {
// Step 21.b.vi.
delete O[to];
}
}
}
// Steps 22-23.
for (var k = actualStart, itemIndex = 2; itemIndex < numArgs; k++, itemIndex++)
O[k] = arguments[itemIndex];
// Steps 24-25.
O.length = len - actualDeleteCount + itemCount;
// Step 26.
return A;
}

View File

@ -70,8 +70,8 @@ function Number_isSafeInteger(number) {
if (integer !== number)
return false;
// Step 5.
if (std_Math_abs(integer) <= 2 ** 53 - 1)
// Step 5. If abs(integer) <= 2**53 - 1, return true.
if (std_Math_abs(integer) <= 9007199254740991)
return true;
// Step 6.

View File

@ -110,7 +110,8 @@ function ToLength(v) {
if (v <= 0)
return 0;
return std_Math_min(v, 2 ** 53 - 1);
// Math.pow(2, 53) - 1 = 0x1fffffffffffff
return std_Math_min(v, 0x1fffffffffffff);
}
/* Spec: ECMAScript Draft, 6th edition Oct 14, 2014, 7.2.4 */

View File

@ -3438,6 +3438,18 @@ CodeGenerator::visitApplyArgsGeneric(LApplyArgsGeneric* apply)
emitPopArguments(apply, extraStackSpace);
}
typedef bool (*ArraySpliceDenseFn)(JSContext*, HandleObject, uint32_t, uint32_t);
static const VMFunction ArraySpliceDenseInfo = FunctionInfo<ArraySpliceDenseFn>(ArraySpliceDense);
void
CodeGenerator::visitArraySplice(LArraySplice* lir)
{
pushArg(ToRegister(lir->getDeleteCount()));
pushArg(ToRegister(lir->getStart()));
pushArg(ToRegister(lir->getObject()));
callVM(ArraySpliceDenseInfo, lir);
}
void
CodeGenerator::visitBail(LBail* lir)
{

View File

@ -216,6 +216,7 @@ class CodeGenerator : public CodeGeneratorSpecific
void emitSetPropertyPolymorphic(LInstruction* lir, Register obj,
Register scratch, const ConstantOrRegister& value);
void visitSetPropertyPolymorphicV(LSetPropertyPolymorphicV* ins);
void visitArraySplice(LArraySplice* splice);
void visitSetPropertyPolymorphicT(LSetPropertyPolymorphicT* ins);
void visitAbsI(LAbsI* lir);
void visitAtan2D(LAtan2D* lir);

View File

@ -753,6 +753,7 @@ class IonBuilder
InliningStatus inlineArrayConcat(CallInfo& callInfo);
InliningStatus inlineArraySlice(CallInfo& callInfo);
InliningStatus inlineArrayJoin(CallInfo& callInfo);
InliningStatus inlineArraySplice(CallInfo& callInfo);
// Math natives.
InliningStatus inlineMathAbs(CallInfo& callInfo);

View File

@ -556,6 +556,16 @@ LIRGenerator::visitAssertRecoveredOnBailout(MAssertRecoveredOnBailout* assertion
MOZ_CRASH("AssertRecoveredOnBailout nodes are always recovered on bailouts.");
}
void
LIRGenerator::visitArraySplice(MArraySplice* ins)
{
LArraySplice* lir = new(alloc()) LArraySplice(useRegisterAtStart(ins->object()),
useRegisterAtStart(ins->start()),
useRegisterAtStart(ins->deleteCount()));
add(lir, ins);
assignSafepoint(lir, ins);
}
void
LIRGenerator::visitGetDynamicName(MGetDynamicName* ins)
{

View File

@ -99,6 +99,7 @@ class LIRGenerator : public LIRGeneratorSpecific
void visitLoadArrowThis(MLoadArrowThis* ins);
void visitCall(MCall* call);
void visitApplyArgs(MApplyArgs* apply);
void visitArraySplice(MArraySplice* splice);
void visitBail(MBail* bail);
void visitUnreachable(MUnreachable* unreachable);
void visitEncodeSnapshot(MEncodeSnapshot* ins);

View File

@ -90,6 +90,8 @@ IonBuilder::inlineNativeCall(CallInfo& callInfo, JSFunction* target)
return inlineArrayConcat(callInfo);
if (native == js::array_slice)
return inlineArraySlice(callInfo);
if (native == js::array_splice)
return inlineArraySplice(callInfo);
// Math natives.
if (native == js::math_abs)
@ -691,6 +693,46 @@ IonBuilder::inlineArrayPopShift(CallInfo& callInfo, MArrayPopShift::Mode mode)
return InliningStatus_Inlined;
}
IonBuilder::InliningStatus
IonBuilder::inlineArraySplice(CallInfo& callInfo)
{
if (callInfo.argc() != 2 || callInfo.constructing()) {
trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm);
return InliningStatus_NotInlined;
}
// Ensure |this|, argument and result are objects.
if (getInlineReturnType() != MIRType_Object)
return InliningStatus_NotInlined;
if (callInfo.thisArg()->type() != MIRType_Object)
return InliningStatus_NotInlined;
if (callInfo.getArg(0)->type() != MIRType_Int32)
return InliningStatus_NotInlined;
if (callInfo.getArg(1)->type() != MIRType_Int32)
return InliningStatus_NotInlined;
callInfo.setImplicitlyUsedUnchecked();
// Specialize arr.splice(start, deleteCount) with unused return value and
// avoid creating the result array in this case.
if (!BytecodeIsPopped(pc)) {
trackOptimizationOutcome(TrackedOutcome::CantInlineGeneric);
return InliningStatus_NotInlined;
}
MArraySplice* ins = MArraySplice::New(alloc(),
callInfo.thisArg(),
callInfo.getArg(0),
callInfo.getArg(1));
current->add(ins);
pushConstant(UndefinedValue());
if (!resumeAfter(ins))
return InliningStatus_Error;
return InliningStatus_Inlined;
}
IonBuilder::InliningStatus
IonBuilder::inlineArrayJoin(CallInfo& callInfo)
{

View File

@ -3813,6 +3813,42 @@ class MCallDOMNative : public MCall
virtual void computeMovable() override;
};
// arr.splice(start, deleteCount) with unused return value.
class MArraySplice
: public MTernaryInstruction,
public Mix3Policy<ObjectPolicy<0>, IntPolicy<1>, IntPolicy<2> >::Data
{
private:
MArraySplice(MDefinition* object, MDefinition* start, MDefinition* deleteCount)
: MTernaryInstruction(object, start, deleteCount)
{ }
public:
INSTRUCTION_HEADER(ArraySplice)
static MArraySplice* New(TempAllocator& alloc, MDefinition* object,
MDefinition* start, MDefinition* deleteCount)
{
return new(alloc) MArraySplice(object, start, deleteCount);
}
MDefinition* object() const {
return getOperand(0);
}
MDefinition* start() const {
return getOperand(1);
}
MDefinition* deleteCount() const {
return getOperand(2);
}
bool possiblyCalls() const override {
return true;
}
};
// fun.apply(self, arguments)
class MApplyArgs
: public MAryInstruction<3>,

View File

@ -62,6 +62,7 @@ namespace jit {
_(LoadArrowThis) \
_(Call) \
_(ApplyArgs) \
_(ArraySplice) \
_(Bail) \
_(Unreachable) \
_(EncodeSnapshot) \

View File

@ -251,6 +251,18 @@ StringsEqual(JSContext* cx, HandleString lhs, HandleString rhs, bool* res)
template bool StringsEqual<true>(JSContext* cx, HandleString lhs, HandleString rhs, bool* res);
template bool StringsEqual<false>(JSContext* cx, HandleString lhs, HandleString rhs, bool* res);
bool
ArraySpliceDense(JSContext* cx, HandleObject obj, uint32_t start, uint32_t deleteCount)
{
JS::AutoValueArray<4> argv(cx);
argv[0].setUndefined();
argv[1].setObject(*obj);
argv[2].set(Int32Value(start));
argv[3].set(Int32Value(deleteCount));
return js::array_splice_impl(cx, 2, argv.begin(), false);
}
bool
ArrayPopDense(JSContext* cx, HandleObject obj, MutableHandleValue rval)
{

View File

@ -688,6 +688,8 @@ bool InitBaselineFrameForOsr(BaselineFrame* frame, InterpreterFrame* interpFrame
JSObject* CreateDerivedTypedObj(JSContext* cx, HandleObject descr,
HandleObject owner, int32_t offset);
bool ArraySpliceDense(JSContext* cx, HandleObject obj, uint32_t start, uint32_t deleteCount);
bool Recompile(JSContext* cx);
bool ForcedRecompile(JSContext* cx);
JSString* RegExpReplace(JSContext* cx, HandleString string, HandleObject regexp,

View File

@ -1809,6 +1809,34 @@ class LApplyArgsGeneric : public LCallInstructionHelper<BOX_PIECES, BOX_PIECES +
}
};
class LArraySplice : public LCallInstructionHelper<0, 3, 0>
{
public:
LIR_HEADER(ArraySplice)
LArraySplice(const LAllocation& object, const LAllocation& start,
const LAllocation& deleteCount)
{
setOperand(0, object);
setOperand(1, start);
setOperand(2, deleteCount);
}
MArraySplice* mir() const {
return mir_->toArraySplice();
}
const LAllocation* getObject() {
return getOperand(0);
}
const LAllocation* getStart() {
return getOperand(1);
}
const LAllocation* getDeleteCount() {
return getOperand(2);
}
};
class LGetDynamicName : public LCallInstructionHelper<BOX_PIECES, 2, 3>
{
public:

View File

@ -53,6 +53,7 @@
_(NewArray) \
_(NewArrayCopyOnWrite) \
_(NewArrayDynamicLength) \
_(ArraySplice) \
_(NewObject) \
_(NewTypedObject) \
_(NewDeclEnvObject) \

View File

@ -61,7 +61,6 @@ MSG_DEF(JSMSG_SPREAD_TOO_LARGE, 0, JSEXN_RANGEERR, "array too large due t
MSG_DEF(JSMSG_BAD_WEAKMAP_KEY, 0, JSEXN_TYPEERR, "cannot use the given object as a weak map key")
MSG_DEF(JSMSG_BAD_GETTER_OR_SETTER, 1, JSEXN_TYPEERR, "invalid {0} usage")
MSG_DEF(JSMSG_BAD_ARRAY_LENGTH, 0, JSEXN_RANGEERR, "invalid array length")
MSG_DEF(JSMSG_BAD_ARRAY_LENGTH_SPLICE, 0, JSEXN_TYPEERR, "resulting array length too large")
MSG_DEF(JSMSG_REDECLARED_VAR, 2, JSEXN_TYPEERR, "redeclaration of {0} {1}")
MSG_DEF(JSMSG_UNDECLARED_VAR, 1, JSEXN_REFERENCEERR, "assignment to undeclared variable {0}")
MSG_DEF(JSMSG_GETTER_ONLY, 0, JSEXN_TYPEERR, "setting a property that has only a getter")

View File

@ -2261,6 +2261,277 @@ js::array_unshift(JSContext* cx, unsigned argc, Value* vp)
return true;
}
/*
* Returns true if this is a dense or unboxed array whose |count| properties
* starting from |startingIndex| may be accessed (get, set, delete) directly
* through its contiguous vector of elements without fear of getters, setters,
* etc. along the prototype chain, or of enumerators requiring notification of
* modifications.
*/
static inline bool
CanOptimizeForDenseStorage(HandleObject arr, uint32_t startingIndex, uint32_t count, JSContext* cx)
{
/* If the desired properties overflow dense storage, we can't optimize. */
if (UINT32_MAX - startingIndex < count)
return false;
/* There's no optimizing possible if it's not an array. */
if (!arr->is<ArrayObject>() && !arr->is<UnboxedArrayObject>())
return false;
/*
* Don't optimize if the array might be in the midst of iteration. We
* rely on this to be able to safely move dense array elements around with
* just a memmove (see NativeObject::moveDenseArrayElements), without worrying
* about updating any in-progress enumerators for properties implicitly
* deleted if a hole is moved from one location to another location not yet
* visited. See bug 690622.
*/
ObjectGroup* arrGroup = arr->getGroup(cx);
if (MOZ_UNLIKELY(!arrGroup || arrGroup->hasAllFlags(OBJECT_FLAG_ITERATED)))
return false;
/*
* Another potential wrinkle: what if the enumeration is happening on an
* object which merely has |arr| on its prototype chain?
*/
if (arr->isDelegate())
return false;
/*
* Now watch out for getters and setters along the prototype chain or in
* other indexed properties on the object. (Note that non-writable length
* is subsumed by the initializedLength comparison.)
*/
return !ObjectMayHaveExtraIndexedProperties(arr) &&
startingIndex + count <= GetAnyBoxedOrUnboxedInitializedLength(arr);
}
/* ES5 15.4.4.12. */
bool
js::array_splice(JSContext* cx, unsigned argc, Value* vp)
{
return array_splice_impl(cx, argc, vp, true);
}
bool
js::array_splice_impl(JSContext* cx, unsigned argc, Value* vp, bool returnValueIsUsed)
{
CallArgs args = CallArgsFromVp(argc, vp);
/* Step 1. */
RootedObject obj(cx, ToObject(cx, args.thisv()));
if (!obj)
return false;
/* Steps 3-4. */
uint32_t len;
if (!GetLengthProperty(cx, obj, &len))
return false;
/* Step 5. */
double relativeStart;
if (!ToInteger(cx, args.get(0), &relativeStart))
return false;
/* Step 6. */
uint32_t actualStart;
if (relativeStart < 0)
actualStart = Max(len + relativeStart, 0.0);
else
actualStart = Min(relativeStart, double(len));
/* Step 7. */
uint32_t actualDeleteCount;
if (args.length() != 1) {
double deleteCountDouble;
RootedValue cnt(cx, args.length() >= 2 ? args[1] : Int32Value(0));
if (!ToInteger(cx, cnt, &deleteCountDouble))
return false;
actualDeleteCount = Min(Max(deleteCountDouble, 0.0), double(len - actualStart));
} else {
/*
* Non-standard: if start was specified but deleteCount was omitted,
* delete to the end of the array. See bug 668024 for discussion.
*/
actualDeleteCount = len - actualStart;
}
MOZ_ASSERT(len - actualStart >= actualDeleteCount);
/* Steps 2, 8-9. */
RootedObject arr(cx);
if (CanOptimizeForDenseStorage(obj, actualStart, actualDeleteCount, cx)) {
if (returnValueIsUsed) {
arr = NewFullyAllocatedArrayTryReuseGroup(cx, obj, actualDeleteCount);
if (!arr)
return false;
DebugOnly<DenseElementResult> result =
CopyAnyBoxedOrUnboxedDenseElements(cx, arr, obj, 0, actualStart, actualDeleteCount);
MOZ_ASSERT(result.value == DenseElementResult::Success);
}
} else {
arr = NewFullyAllocatedArrayTryReuseGroup(cx, obj, actualDeleteCount);
if (!arr)
return false;
RootedValue fromValue(cx);
for (uint32_t k = 0; k < actualDeleteCount; k++) {
bool hole;
if (!CheckForInterrupt(cx) ||
!GetElement(cx, obj, actualStart + k, &hole, &fromValue) ||
(!hole && !DefineElement(cx, arr, k, fromValue)))
{
return false;
}
}
}
/* Step 11. */
uint32_t itemCount = (args.length() >= 2) ? (args.length() - 2) : 0;
if (itemCount < actualDeleteCount) {
/* Step 12: the array is being shrunk. */
uint32_t sourceIndex = actualStart + actualDeleteCount;
uint32_t targetIndex = actualStart + itemCount;
uint32_t finalLength = len - actualDeleteCount + itemCount;
if (CanOptimizeForDenseStorage(obj, 0, len, cx)) {
/* Steps 12(a)-(b). */
DenseElementResult result =
MoveAnyBoxedOrUnboxedDenseElements(cx, obj, targetIndex, sourceIndex,
len - sourceIndex);
MOZ_ASSERT(result != DenseElementResult::Incomplete);
if (result == DenseElementResult::Failure)
return false;
/* Steps 12(c)-(d). */
SetAnyBoxedOrUnboxedInitializedLength(cx, obj, finalLength);
} else {
/*
* This is all very slow if the length is very large. We don't yet
* have the ability to iterate in sorted order, so we just do the
* pessimistic thing and let CheckForInterrupt handle the
* fallout.
*/
/* Steps 12(a)-(b). */
RootedValue fromValue(cx);
for (uint32_t from = sourceIndex, to = targetIndex; from < len; from++, to++) {
if (!CheckForInterrupt(cx))
return false;
bool hole;
if (!GetElement(cx, obj, from, &hole, &fromValue))
return false;
if (hole) {
if (!DeletePropertyOrThrow(cx, obj, to))
return false;
} else {
if (!SetArrayElement(cx, obj, to, fromValue))
return false;
}
}
/* Steps 12(c)-(d). */
for (uint32_t k = len; k > finalLength; k--) {
if (!DeletePropertyOrThrow(cx, obj, k - 1))
return false;
}
}
} else if (itemCount > actualDeleteCount) {
/* Step 13. */
/*
* Optimize only if the array is already dense and we can extend it to
* its new length. It would be wrong to extend the elements here for a
* number of reasons.
*
* First, this could cause us to fall into the fast-path below. This
* would cause elements to be moved into places past the non-writable
* length. And when the dense initialized length is updated, that'll
* cause the |in| operator to think that those elements actually exist,
* even though, properly, setting them must fail.
*
* Second, extending the elements here will trigger assertions inside
* ensureDenseElements that the elements aren't being extended past the
* length of a non-writable array. This is because extending elements
* will extend capacity -- which might extend them past a non-writable
* length, violating the |capacity <= length| invariant for such
* arrays. And that would make the various JITted fast-path method
* implementations of [].push, [].unshift, and so on wrong.
*
* If the array length is non-writable, this method *will* throw. For
* simplicity, have the slow-path code do it. (Also note that the slow
* path may validly *not* throw -- if all the elements being moved are
* holes.)
*/
if (obj->is<ArrayObject>()) {
Rooted<ArrayObject*> arr(cx, &obj->as<ArrayObject>());
if (arr->lengthIsWritable()) {
DenseElementResult result =
arr->ensureDenseElements(cx, arr->length(), itemCount - actualDeleteCount);
if (result == DenseElementResult::Failure)
return false;
}
}
if (CanOptimizeForDenseStorage(obj, len, itemCount - actualDeleteCount, cx)) {
DenseElementResult result =
MoveAnyBoxedOrUnboxedDenseElements(cx, obj, actualStart + itemCount,
actualStart + actualDeleteCount,
len - (actualStart + actualDeleteCount));
MOZ_ASSERT(result != DenseElementResult::Incomplete);
if (result == DenseElementResult::Failure)
return false;
/* Steps 12(c)-(d). */
SetAnyBoxedOrUnboxedInitializedLength(cx, obj, len + itemCount - actualDeleteCount);
} else {
RootedValue fromValue(cx);
for (double k = len - actualDeleteCount; k > actualStart; k--) {
if (!CheckForInterrupt(cx))
return false;
double from = k + actualDeleteCount - 1;
double to = k + itemCount - 1;
bool hole;
if (!GetElement(cx, obj, from, &hole, &fromValue))
return false;
if (hole) {
if (!DeletePropertyOrThrow(cx, obj, to))
return false;
} else {
if (!SetArrayElement(cx, obj, to, fromValue))
return false;
}
}
}
}
/* Step 10. */
Value* items = args.array() + 2;
/* Steps 14-15. */
for (uint32_t k = actualStart, i = 0; i < itemCount; i++, k++) {
if (!SetArrayElement(cx, obj, k, HandleValue::fromMarkedLocation(&items[i])))
return false;
}
/* Step 16. */
double finalLength = double(len) - actualDeleteCount + itemCount;
if (!SetLengthProperty(cx, obj, finalLength))
return false;
/* Step 17. */
if (returnValueIsUsed)
args.rval().setObject(*arr);
return true;
}
template <JSValueType Type>
DenseElementResult
ArrayConcatDenseKernel(JSContext* cx, JSObject* obj1, JSObject* obj2, JSObject* result)
@ -2758,7 +3029,7 @@ static const JSFunctionSpec array_methods[] = {
JS_FN("pop", array_pop, 0,JSFUN_GENERIC_NATIVE),
JS_FN("shift", array_shift, 0,JSFUN_GENERIC_NATIVE),
JS_FN("unshift", array_unshift, 1,JSFUN_GENERIC_NATIVE),
JS_SELF_HOSTED_FN("splice", "ArraySplice", 2,0),
JS_FN("splice", array_splice, 2,JSFUN_GENERIC_NATIVE),
/* Pythonic sequence methods. */
JS_FN("concat", array_concat, 1,JSFUN_GENERIC_NATIVE),

View File

@ -1,14 +0,0 @@
var a = {};
a[2 ** 53 - 2] = 1;
a.length = 2 ** 53 - 1;
var exception;
try {
[].splice.call(a, 2 ** 53 - 2, 0, 2, 3, 4, 5);
} catch (e) {
exception = e;
}
reportCompare(a[2 ** 53 - 2], 1);
reportCompare(a.length, 2 ** 53 - 1);
reportCompare(exception instanceof TypeError, true, "Array#splice throws TypeError for length overflows");
reportCompare(exception.message.indexOf('array length') > -1, true, "Array#splice throws correct error for length overflows");

View File

@ -29,11 +29,11 @@ namespace js {
*
* https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode
*/
static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 300;
static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 301;
static const uint32_t XDR_BYTECODE_VERSION =
uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND);
static_assert(JSErr_Limit == 408,
static_assert(JSErr_Limit == 407,
"GREETINGS, POTENTIAL SUBTRAHEND INCREMENTER! If you added or "
"removed MSG_DEFs from js.msg, you should increment "
"XDR_BYTECODE_VERSION_SUBTRAHEND and update this assertion's "