Optimize argument handling in array and math functions by caching argument counts

This commit is contained in:
wuggy 2026-04-22 10:57:09 -07:00
commit def0f21065
4 changed files with 48 additions and 27 deletions

View file

@ -2083,6 +2083,7 @@ js::array_push(JSContext* cx, unsigned argc, Value* vp)
{
AutoSPSEntry pseudoFrame(cx->runtime(), "Array.prototype.push");
CallArgs args = CallArgsFromVp(argc, vp);
const unsigned argCount = args.length();
/* Step 1. */
RootedObject obj(cx, ToObject(cx, args.thisv()));
@ -2097,12 +2098,12 @@ js::array_push(JSContext* cx, unsigned argc, Value* vp)
if (!ObjectMayHaveExtraIndexedProperties(obj)) {
DenseElementResult result =
SetOrExtendAnyBoxedOrUnboxedDenseElements(cx, obj, length,
args.array(), args.length());
args.array(), argCount);
if (result != DenseElementResult::Incomplete) {
if (result == DenseElementResult::Failure)
return false;
uint32_t newlength = length + args.length();
uint32_t newlength = length + argCount;
args.rval().setNumber(newlength);
// SetOrExtendAnyBoxedOrUnboxedDenseElements takes care of updating the
@ -2120,11 +2121,11 @@ js::array_push(JSContext* cx, unsigned argc, Value* vp)
}
/* Steps 4-5. */
if (!InitArrayElements(cx, obj, length, args.length(), args.array()))
if (!InitArrayElements(cx, obj, length, argCount, args.array()))
return false;
/* Steps 6-7. */
double newlength = length + double(args.length());
double newlength = length + double(argCount);
args.rval().setNumber(newlength);
return SetLengthProperty(cx, obj, newlength);
}
@ -2482,6 +2483,8 @@ array_splice_impl(JSContext* cx, unsigned argc, Value* vp, bool returnValueIsUse
if (!GetLengthProperty(cx, obj, &len))
return false;
const unsigned argCount = args.length();
/* Step 3. */
double relativeStart;
if (!ToInteger(cx, args.get(0), &relativeStart))
@ -2496,10 +2499,10 @@ array_splice_impl(JSContext* cx, unsigned argc, Value* vp, bool returnValueIsUse
/* Step 5. */
uint32_t actualDeleteCount;
if (args.length() == 0) {
if (argCount == 0) {
/* Step 5.b. */
actualDeleteCount = 0;
} else if (args.length() == 1) {
} else if (argCount == 1) {
/* Step 6.b. */
actualDeleteCount = len - actualStart;
} else {
@ -2554,7 +2557,7 @@ array_splice_impl(JSContext* cx, unsigned argc, Value* vp, bool returnValueIsUse
}
/* Step 14. */
uint32_t itemCount = (args.length() >= 2) ? (args.length() - 2) : 0;
uint32_t itemCount = (argCount >= 2) ? (argCount - 2) : 0;
if (itemCount < actualDeleteCount) {
/* Step 15: the array is being shrunk. */
@ -2945,6 +2948,7 @@ js::array_slice(JSContext* cx, unsigned argc, Value* vp)
{
AutoSPSEntry pseudoFrame(cx->runtime(), "Array.prototype.slice");
CallArgs args = CallArgsFromVp(argc, vp);
const unsigned argCount = args.length();
/* Step 1. */
RootedObject obj(cx, ToObject(cx, args.thisv()));
@ -2958,7 +2962,7 @@ js::array_slice(JSContext* cx, unsigned argc, Value* vp)
uint32_t k = 0;
uint32_t final = length;
if (args.length() > 0) {
if (argCount > 0) {
double d;
/* Step 3. */
if (!ToInteger(cx, args[0], &d))
@ -3100,7 +3104,8 @@ array_isArray(JSContext* cx, unsigned argc, Value* vp)
static bool
ArrayFromCallArgs(JSContext* cx, CallArgs& args, HandleObject proto = nullptr)
{
JSObject* obj = NewCopiedArrayForCallingAllocationSite(cx, args.array(), args.length(), proto);
const unsigned argCount = args.length();
JSObject* obj = NewCopiedArrayForCallingAllocationSite(cx, args.array(), argCount, proto);
if (!obj)
return false;
@ -3112,6 +3117,7 @@ static bool
array_of(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
const unsigned argCount = args.length();
if (IsArrayConstructor(args.thisv()) || !IsConstructor(args.thisv())) {
// IsArrayConstructor(this) will usually be true in practice. This is
@ -3124,20 +3130,20 @@ array_of(JSContext* cx, unsigned argc, Value* vp)
{
FixedConstructArgs<1> cargs(cx);
cargs[0].setNumber(args.length());
cargs[0].setNumber(argCount);
if (!Construct(cx, args.thisv(), cargs, args.thisv(), &obj))
return false;
}
// Step 8.
for (unsigned k = 0; k < args.length(); k++) {
for (unsigned k = 0; k < argCount; k++) {
if (!DefineElement(cx, obj, k, args[k]))
return false;
}
// Steps 9-10.
if (!SetLengthProperty(cx, obj, args.length()))
if (!SetLengthProperty(cx, obj, argCount))
return false;
// Step 11.
@ -3249,6 +3255,8 @@ const JSPropertySpec array_static_props[] = {
static inline bool
ArrayConstructorImpl(JSContext* cx, CallArgs& args, bool isConstructor)
{
const unsigned argCount = args.length();
RootedObject proto(cx);
if (isConstructor) {
if (!GetPrototypeFromCallableConstructor(cx, args, &proto))
@ -3261,7 +3269,7 @@ ArrayConstructorImpl(JSContext* cx, CallArgs& args, bool isConstructor)
return false;
}
if (args.length() != 1 || !args[0].isNumber())
if (argCount != 1 || !args[0].isNumber())
return ArrayFromCallArgs(cx, args, proto);
uint32_t length;
@ -3301,8 +3309,9 @@ bool
js::array_construct(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
const unsigned argCount = args.length();
MOZ_ASSERT(!args.isConstructing());
MOZ_ASSERT(args.length() == 1);
MOZ_ASSERT(argCount == 1);
MOZ_ASSERT(args[0].isNumber());
return ArrayConstructorImpl(cx, args, /* isConstructor = */ false);
}
@ -3818,9 +3827,10 @@ bool
js::ArrayInfo(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
const unsigned argCount = args.length();
RootedObject obj(cx);
for (unsigned i = 0; i < args.length(); i++) {
for (unsigned i = 0; i < argCount; i++) {
HandleValue arg = args[i];
UniqueChars bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, arg, nullptr);

View file

@ -566,9 +566,10 @@ bool
js::math_max(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
const unsigned argCount = args.length();
double maxval = NegativeInfinity<double>();
for (unsigned i = 0; i < args.length(); i++) {
for (unsigned i = 0; i < argCount; i++) {
double x;
if (!ToNumber(cx, args[i], &x))
return false;
@ -591,9 +592,10 @@ bool
js::math_min(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
const unsigned argCount = args.length();
double minval = PositiveInfinity<double>();
for (unsigned i = 0; i < args.length(); i++) {
for (unsigned i = 0; i < argCount; i++) {
double x;
if (!ToNumber(cx, args[i], &x))
return false;
@ -1263,9 +1265,11 @@ js::math_hypot(JSContext* cx, unsigned argc, Value* vp)
bool
js::math_hypot_handle(JSContext* cx, HandleValueArray args, MutableHandleValue res)
{
const unsigned argCount = args.length();
// IonMonkey calls the system hypot function directly if two arguments are
// given. Do that here as well to get the same results.
if (args.length() == 2) {
if (argCount == 2) {
double x, y;
if (!ToNumber(cx, args[0], &x))
return false;
@ -1283,7 +1287,7 @@ js::math_hypot_handle(JSContext* cx, HandleValueArray args, MutableHandleValue r
double scale = 0;
double sumsq = 1;
for (unsigned i = 0; i < args.length(); i++) {
for (unsigned i = 0; i < argCount; i++) {
double x;
if (!ToNumber(cx, args[i], &x))
return false;

View file

@ -527,7 +527,8 @@ js::SetIntegrityLevel(JSContext* cx, HandleObject obj, IntegrityLevel level)
const unsigned AllowConfigureAndWritable = AllowConfigure & ~JSPROP_IGNORE_READONLY;
// 8.a/9.a. The two different loops are merged here.
for (size_t i = 0; i < keys.length(); i++) {
const size_t keyCount = keys.length();
for (size_t i = 0; i < keyCount; i++) {
id = keys[i];
if (level == IntegrityLevel::Sealed) {
@ -1062,7 +1063,8 @@ JS_CopyPropertiesFrom(JSContext* cx, HandleObject target, HandleObject obj)
if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, &props))
return false;
for (size_t i = 0; i < props.length(); ++i) {
const size_t propCount = props.length();
for (size_t i = 0; i < propCount; ++i) {
if (!JS_CopyPropertyFrom(cx, props[i], target, obj))
return false;
}
@ -1158,6 +1160,7 @@ GetScriptPlainObjectProperties(ExclusiveContext* cx, HandleObject obj,
{
if (obj->is<PlainObject>()) {
PlainObject* nobj = &obj->as<PlainObject>();
const size_t initLength = nobj->getDenseInitializedLength();
if (!properties.appendN(IdValuePair(), nobj->slotSpan()))
return false;
@ -1170,7 +1173,7 @@ GetScriptPlainObjectProperties(ExclusiveContext* cx, HandleObject obj,
properties[slot].get().value = nobj->getSlot(slot);
}
for (size_t i = 0; i < nobj->getDenseInitializedLength(); i++) {
for (size_t i = 0; i < initLength; i++) {
Value v = nobj->getDenseElement(i);
if (!v.isMagic(JS_ELEMENTS_HOLE) && !properties.append(IdValuePair(INT_TO_JSID(i), v)))
return false;
@ -1183,11 +1186,13 @@ GetScriptPlainObjectProperties(ExclusiveContext* cx, HandleObject obj,
UnboxedPlainObject* nobj = &obj->as<UnboxedPlainObject>();
const UnboxedLayout& layout = nobj->layout();
if (!properties.appendN(IdValuePair(), layout.properties().length()))
const auto& layoutProperties = layout.properties();
const size_t layoutPropertyCount = layoutProperties.length();
if (!properties.appendN(IdValuePair(), layoutPropertyCount))
return false;
for (size_t i = 0; i < layout.properties().length(); i++) {
const UnboxedLayout::Property& property = layout.properties()[i];
for (size_t i = 0; i < layoutPropertyCount; i++) {
const UnboxedLayout::Property& property = layoutProperties[i];
properties[i].get().id = NameToId(property.name);
properties[i].get().value = nobj->getValue(property);
}
@ -1244,7 +1249,8 @@ js::DeepCloneObjectLiteral(JSContext* cx, HandleObject obj, NewObjectKind newKin
if (!GetScriptPlainObjectProperties(cx, obj, &properties))
return nullptr;
for (size_t i = 0; i < properties.length(); i++) {
const size_t propertyCount = properties.length();
for (size_t i = 0; i < propertyCount; i++) {
if (!DeepCloneValue(cx, &properties[i].get().value, newKind))
return nullptr;
}

View file

@ -3352,9 +3352,10 @@ bool
js::StringConstructor(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
const unsigned argCount = args.length();
RootedString str(cx);
if (args.length() > 0) {
if (argCount > 0) {
if (!args.isConstructing() && args[0].isSymbol())
return js::SymbolDescriptiveString(cx, args[0].toSymbol(), args.rval());