Bug 1830727 - Emit JSOp::IsNullOrUndefined for null-or-undefined checks in self-hosted code. r=iain

It's fairly common in the JS spec to check whether a value is null-or-undefined.
We already have a bytecode op for this that we can use if we add an `IsNullOrUndefined`
intrinsic.

This has some minor performance benefits (no Baseline ICs instead of 3 Baseline ICs)
and no `Or` or `And` branch, but it also makes our bytecode more compact. This will
help with follow-up work to look into inlining more string builtins.

Differential Revision: https://phabricator.services.mozilla.com/D176902
This commit is contained in:
Jan de Mooij 2023-05-03 12:52:31 +00:00
Родитель 5c922d8b93
Коммит 5b6cd149a6
11 изменённых файлов: 88 добавлений и 60 удалений

Просмотреть файл

@ -136,6 +136,7 @@ module.exports = {
SetIsInlinableLargeFunction: "readonly",
ToNumeric: "readonly",
ToString: "readonly",
IsNullOrUndefined: "readonly",
// We've disabled all built-in environments, which also removed
// `undefined` from the list of globals. Put it back because it's

Просмотреть файл

@ -1024,7 +1024,7 @@ function ArrayFrom(items, mapfn = undefined, thisArg = undefined) {
// Step 5.
// Inlined: GetMethod, step 3.
if (usingIterator !== undefined && usingIterator !== null) {
if (!IsNullOrUndefined(usingIterator)) {
// Inlined: GetMethod, step 4.
if (!IsCallable(usingIterator)) {
ThrowTypeError(JSMSG_NOT_ITERABLE, DecompileArg(0, items));
@ -1135,7 +1135,7 @@ function ArrayToLocaleString(locales, options) {
// Steps 6-7.
var R;
if (firstElement === undefined || firstElement === null) {
if (IsNullOrUndefined(firstElement)) {
R = "";
} else {
#if JS_HAS_INTL_API
@ -1165,7 +1165,7 @@ function ArrayToLocaleString(locales, options) {
// Steps 9.a, 9.c-e.
R += separator;
if (!(nextElement === undefined || nextElement === null)) {
if (!IsNullOrUndefined(nextElement)) {
#if JS_HAS_INTL_API
R += ToString(
callContentFunction(

Просмотреть файл

@ -37,7 +37,7 @@ async function AsyncIteratorClose(iteratorRecord, value) {
// Step 4.
const returnMethod = iterator.return;
// Step 5.
if (returnMethod !== undefined && returnMethod !== null) {
if (!IsNullOrUndefined(returnMethod)) {
const result = await callContentFunction(returnMethod, iterator);
// Step 8.
if (!IsObject(result)) {
@ -74,7 +74,7 @@ function GetAsyncIteratorDirectWrapper(obj) {
},
async return(value) {
const returnMethod = obj.return;
if (returnMethod !== undefined && returnMethod !== null) {
if (!IsNullOrUndefined(returnMethod)) {
return callContentFunction(returnMethod, obj, value);
}
return { done: true, value };

Просмотреть файл

@ -32,7 +32,7 @@ function IteratorClose(iteratorRecord, value) {
// Step 4.
const returnMethod = iterator.return;
// Step 5.
if (returnMethod !== undefined && returnMethod !== null) {
if (!IsNullOrUndefined(returnMethod)) {
const result = callContentFunction(returnMethod, iterator);
// Step 8.
if (!IsObject(result)) {
@ -124,7 +124,7 @@ function GetIteratorDirectWrapper(obj) {
},
return(value) {
const returnMethod = obj.return;
if (returnMethod !== undefined && returnMethod !== null) {
if (!IsNullOrUndefined(returnMethod)) {
return callContentFunction(returnMethod, obj, value);
}
return { done: true, value };
@ -165,7 +165,7 @@ function IteratorFrom(O) {
let iteratorRecord;
// Step 2.
if (usingIterator !== undefined && usingIterator !== null) {
if (!IsNullOrUndefined(usingIterator)) {
// Step a.
// Inline call to GetIterator.
const iterator = callContentFunction(usingIterator, O);
@ -240,7 +240,7 @@ function WrapForValidIteratorReturn(value) {
// Inline call to IteratorClose.
const iterator = iterated.iterator;
const returnMethod = iterator.return;
if (returnMethod !== undefined && returnMethod !== null) {
if (!IsNullOrUndefined(returnMethod)) {
let innerResult = callContentFunction(returnMethod, iterator);
if (!IsObject(innerResult)) {
ThrowTypeError(JSMSG_OBJECT_REQUIRED, DecompileArg(0, innerResult));
@ -271,7 +271,7 @@ function WrapForValidIteratorThrow(value) {
// Step 4.
const throwMethod = iterator.throw;
// Step 5.
if (throwMethod === undefined || throwMethod === null) {
if (IsNullOrUndefined(throwMethod)) {
throw value;
}
// Step 6.

Просмотреть файл

@ -29,7 +29,7 @@ function ThrowIncompatibleMethod(name, thisv) {
// ES 2016 draft Mar 25, 2016 21.1.3.11.
function String_match(regexp) {
// Step 1.
if (this === undefined || this === null) {
if (IsNullOrUndefined(this)) {
ThrowIncompatibleMethod("match", this);
}
@ -37,8 +37,7 @@ function String_match(regexp) {
var isPatternString = typeof regexp === "string";
if (
!(isPatternString && StringProtoHasNoMatch()) &&
regexp !== undefined &&
regexp !== null
!IsNullOrUndefined(regexp)
) {
// Step 2.a.
var matcher = GetMethod(regexp, GetBuiltinSymbol("match"));
@ -76,19 +75,19 @@ function String_match(regexp) {
// String.prototype.matchAll ( regexp )
function String_matchAll(regexp) {
// Step 1.
if (this === undefined || this === null) {
if (IsNullOrUndefined(this)) {
ThrowIncompatibleMethod("matchAll", this);
}
// Step 2.
if (regexp !== undefined && regexp !== null) {
if (!IsNullOrUndefined(regexp)) {
// Steps 2.a-b.
if (IsRegExp(regexp)) {
// Step 2.b.i.
var flags = regexp.flags;
// Step 2.b.ii.
if (flags === undefined || flags === null) {
if (IsNullOrUndefined(flags)) {
ThrowTypeError(JSMSG_FLAGS_UNDEFINED_OR_NULL);
}
@ -127,7 +126,7 @@ function String_matchAll(regexp) {
*/
function String_pad(maxLength, fillString, padEnd) {
// Step 1.
if (this === undefined || this === null) {
if (IsNullOrUndefined(this)) {
ThrowIncompatibleMethod(padEnd ? "padEnd" : "padStart", this);
}
@ -215,15 +214,14 @@ function Substring(str, from, length) {
// ES 2016 draft Mar 25, 2016 21.1.3.14.
function String_replace(searchValue, replaceValue) {
// Step 1.
if (this === undefined || this === null) {
if (IsNullOrUndefined(this)) {
ThrowIncompatibleMethod("replace", this);
}
// Step 2.
if (
!(typeof searchValue === "string" && StringProtoHasNoReplace()) &&
searchValue !== undefined &&
searchValue !== null
!IsNullOrUndefined(searchValue)
) {
// Step 2.a.
var replacer = GetMethod(searchValue, GetBuiltinSymbol("replace"));
@ -289,19 +287,19 @@ function String_replace(searchValue, replaceValue) {
// String.prototype.replaceAll ( searchValue, replaceValue )
function String_replaceAll(searchValue, replaceValue) {
// Step 1.
if (this === undefined || this === null) {
if (IsNullOrUndefined(this)) {
ThrowIncompatibleMethod("replaceAll", this);
}
// Step 2.
if (searchValue !== undefined && searchValue !== null) {
if (!IsNullOrUndefined(searchValue)) {
// Steps 2.a-b.
if (IsRegExp(searchValue)) {
// Step 2.b.i.
var flags = searchValue.flags;
// Step 2.b.ii.
if (flags === undefined || flags === null) {
if (IsNullOrUndefined(flags)) {
ThrowTypeError(JSMSG_FLAGS_UNDEFINED_OR_NULL);
}
@ -430,7 +428,7 @@ function IsStringSearchOptimizable() {
// ES 2016 draft Mar 25, 2016 21.1.3.15.
function String_search(regexp) {
// Step 1.
if (this === undefined || this === null) {
if (IsNullOrUndefined(this)) {
ThrowIncompatibleMethod("search", this);
}
@ -438,8 +436,7 @@ function String_search(regexp) {
var isPatternString = typeof regexp === "string";
if (
!(isPatternString && StringProtoHasNoSearch()) &&
regexp !== undefined &&
regexp !== null
!IsNullOrUndefined(regexp)
) {
// Step 2.a.
var searcher = GetMethod(regexp, GetBuiltinSymbol("search"));
@ -483,7 +480,7 @@ function StringProtoHasNoSplit() {
// ES 2016 draft Mar 25, 2016 21.1.3.17.
function String_split(separator, limit) {
// Step 1.
if (this === undefined || this === null) {
if (IsNullOrUndefined(this)) {
ThrowIncompatibleMethod("split", this);
}
@ -505,8 +502,7 @@ function String_split(separator, limit) {
// Step 2.
if (
!(typeof separator === "string" && StringProtoHasNoSplit()) &&
separator !== undefined &&
separator !== null
!IsNullOrUndefined(separator)
) {
// Step 2.a.
var splitter = GetMethod(separator, GetBuiltinSymbol("split"));
@ -559,7 +555,7 @@ function String_split(separator, limit) {
// 21.1.3.22 String.prototype.substring ( start, end )
function String_substring(start, end) {
// Step 1.
if (this === undefined || this === null) {
if (IsNullOrUndefined(this)) {
ThrowIncompatibleMethod("substring", this);
}
@ -599,7 +595,7 @@ SetIsInlinableLargeFunction(String_substring);
// B.2.3.1 String.prototype.substr ( start, length )
function String_substr(start, length) {
// Steps 1.
if (this === undefined || this === null) {
if (IsNullOrUndefined(this)) {
ThrowIncompatibleMethod("substr", this);
}
@ -645,7 +641,7 @@ SetIsInlinableLargeFunction(String_substr);
// Note: String.prototype.concat.length is 1.
function String_concat(arg1) {
// Step 1.
if (this === undefined || this === null) {
if (IsNullOrUndefined(this)) {
ThrowIncompatibleMethod("concat", this);
}
@ -683,7 +679,7 @@ function String_concat(arg1) {
// 21.1.3.19 String.prototype.slice ( start, end )
function String_slice(start, end) {
// Step 1.
if (this === undefined || this === null) {
if (IsNullOrUndefined(this)) {
ThrowIncompatibleMethod("slice", this);
}
@ -724,7 +720,7 @@ SetIsInlinableLargeFunction(String_slice);
// 21.1.3.3 String.prototype.codePointAt ( pos )
function String_codePointAt(pos) {
// Step 1.
if (this === undefined || this === null) {
if (IsNullOrUndefined(this)) {
ThrowIncompatibleMethod("codePointAt", this);
}
@ -762,7 +758,7 @@ function String_codePointAt(pos) {
// 21.1.3.16 String.prototype.repeat ( count )
function String_repeat(count) {
// Step 1.
if (this === undefined || this === null) {
if (IsNullOrUndefined(this)) {
ThrowIncompatibleMethod("repeat", this);
}
@ -815,7 +811,7 @@ function String_repeat(count) {
// ES6 draft specification, section 21.1.3.27, version 2013-09-27.
function String_iterator() {
// Step 1.
if (this === undefined || this === null) {
if (IsNullOrUndefined(this)) {
ThrowTypeError(
JSMSG_INCOMPATIBLE_PROTO2,
"String",
@ -886,7 +882,7 @@ var collatorCache = new_Record();
*/
function String_localeCompare(that) {
// Step 1.
if (this === undefined || this === null) {
if (IsNullOrUndefined(this)) {
ThrowIncompatibleMethod("localeCompare", this);
}
@ -923,7 +919,7 @@ function String_localeCompare(that) {
*/
function String_toLocaleLowerCase() {
// Step 1.
if (this === undefined || this === null) {
if (IsNullOrUndefined(this)) {
ThrowIncompatibleMethod("toLocaleLowerCase", this);
}
@ -968,7 +964,7 @@ function String_toLocaleLowerCase() {
*/
function String_toLocaleUpperCase() {
// Step 1.
if (this === undefined || this === null) {
if (IsNullOrUndefined(this)) {
ThrowIncompatibleMethod("toLocaleUpperCase", this);
}
@ -1063,7 +1059,7 @@ function String_static_raw(callSite /*, ...substitutions*/) {
// String.prototype.at ( index )
function String_at(index) {
// Step 1.
if (this === undefined || this === null) {
if (IsNullOrUndefined(this)) {
ThrowIncompatibleMethod("at", this);
}
@ -1095,7 +1091,7 @@ function String_at(index) {
// ES6 draft 2014-04-27 B.2.3.3
function String_big() {
if (this === undefined || this === null) {
if (IsNullOrUndefined(this)) {
ThrowIncompatibleMethod("big", this);
}
return "<big>" + ToString(this) + "</big>";
@ -1103,7 +1099,7 @@ function String_big() {
// ES6 draft 2014-04-27 B.2.3.4
function String_blink() {
if (this === undefined || this === null) {
if (IsNullOrUndefined(this)) {
ThrowIncompatibleMethod("blink", this);
}
return "<blink>" + ToString(this) + "</blink>";
@ -1111,7 +1107,7 @@ function String_blink() {
// ES6 draft 2014-04-27 B.2.3.5
function String_bold() {
if (this === undefined || this === null) {
if (IsNullOrUndefined(this)) {
ThrowIncompatibleMethod("bold", this);
}
return "<b>" + ToString(this) + "</b>";
@ -1119,7 +1115,7 @@ function String_bold() {
// ES6 draft 2014-04-27 B.2.3.6
function String_fixed() {
if (this === undefined || this === null) {
if (IsNullOrUndefined(this)) {
ThrowIncompatibleMethod("fixed", this);
}
return "<tt>" + ToString(this) + "</tt>";
@ -1127,7 +1123,7 @@ function String_fixed() {
// ES6 draft 2014-04-27 B.2.3.9
function String_italics() {
if (this === undefined || this === null) {
if (IsNullOrUndefined(this)) {
ThrowIncompatibleMethod("italics", this);
}
return "<i>" + ToString(this) + "</i>";
@ -1135,7 +1131,7 @@ function String_italics() {
// ES6 draft 2014-04-27 B.2.3.11
function String_small() {
if (this === undefined || this === null) {
if (IsNullOrUndefined(this)) {
ThrowIncompatibleMethod("small", this);
}
return "<small>" + ToString(this) + "</small>";
@ -1143,7 +1139,7 @@ function String_small() {
// ES6 draft 2014-04-27 B.2.3.12
function String_strike() {
if (this === undefined || this === null) {
if (IsNullOrUndefined(this)) {
ThrowIncompatibleMethod("strike", this);
}
return "<strike>" + ToString(this) + "</strike>";
@ -1151,7 +1147,7 @@ function String_strike() {
// ES6 draft 2014-04-27 B.2.3.13
function String_sub() {
if (this === undefined || this === null) {
if (IsNullOrUndefined(this)) {
ThrowIncompatibleMethod("sub", this);
}
return "<sub>" + ToString(this) + "</sub>";
@ -1159,7 +1155,7 @@ function String_sub() {
// ES6 draft 2014-04-27 B.2.3.14
function String_sup() {
if (this === undefined || this === null) {
if (IsNullOrUndefined(this)) {
ThrowIncompatibleMethod("sup", this);
}
return "<sup>" + ToString(this) + "</sup>";
@ -1172,7 +1168,7 @@ function EscapeAttributeValue(v) {
// ES6 draft 2014-04-27 B.2.3.2
function String_anchor(name) {
if (this === undefined || this === null) {
if (IsNullOrUndefined(this)) {
ThrowIncompatibleMethod("anchor", this);
}
var S = ToString(this);
@ -1181,7 +1177,7 @@ function String_anchor(name) {
// ES6 draft 2014-04-27 B.2.3.7
function String_fontcolor(color) {
if (this === undefined || this === null) {
if (IsNullOrUndefined(this)) {
ThrowIncompatibleMethod("fontcolor", this);
}
var S = ToString(this);
@ -1190,7 +1186,7 @@ function String_fontcolor(color) {
// ES6 draft 2014-04-27 B.2.3.8
function String_fontsize(size) {
if (this === undefined || this === null) {
if (IsNullOrUndefined(this)) {
ThrowIncompatibleMethod("fontsize", this);
}
var S = ToString(this);
@ -1199,7 +1195,7 @@ function String_fontsize(size) {
// ES6 draft 2014-04-27 B.2.3.10
function String_link(url) {
if (this === undefined || this === null) {
if (IsNullOrUndefined(this)) {
ThrowIncompatibleMethod("link", this);
}
var S = ToString(this);

Просмотреть файл

@ -273,7 +273,7 @@ function TupleJoin(separator) {
// Steps 3-4
var sep = ",";
if (separator !== undefined && separator !== null) {
if (!IsNullOrUndefined(separator)) {
let toString = IsCallable(separator.toString)
? separator.toString
: std_Object_toString;
@ -296,7 +296,7 @@ function TupleJoin(separator) {
let element = T[k];
// Step 7c
var next = "";
if (element !== undefined && element !== null) {
if (!IsNullOrUndefined(element)) {
let toString = IsCallable(element.toString)
? element.toString
: std_Object_toString;

Просмотреть файл

@ -108,7 +108,7 @@ function TypedArraySpeciesConstructor(obj) {
var s = ctor[GetBuiltinSymbol("species")];
// Step 6.
if (s === undefined || s === null) {
if (IsNullOrUndefined(s)) {
return ConstructorForTypedArray(obj);
}

Просмотреть файл

@ -64,7 +64,7 @@ function GetMethod(V, P) {
var func = V[P];
// Step 3.
if (func === undefined || func === null) {
if (IsNullOrUndefined(func)) {
return undefined;
}
@ -108,7 +108,7 @@ function SpeciesConstructor(obj, defaultConstructor) {
var s = ctor[GetBuiltinSymbol("species")];
// Step 6.
if (s === undefined || s === null) {
if (IsNullOrUndefined(s)) {
return defaultConstructor;
}
@ -164,7 +164,7 @@ function CopyDataProperties(target, source, excludedItems) {
assert(IsObject(excludedItems), "excludedItems is an object");
// Steps 3 and 7.
if (source === undefined || source === null) {
if (IsNullOrUndefined(source)) {
return;
}
@ -205,7 +205,7 @@ function CopyDataPropertiesUnfiltered(target, source) {
// Step 2 (Not applicable).
// Steps 3 and 7.
if (source === undefined || source === null) {
if (IsNullOrUndefined(source)) {
return;
}

Просмотреть файл

@ -7403,6 +7403,32 @@ bool BytecodeEmitter::emitSelfHostedToString(CallNode* callNode) {
return emit1(JSOp::ToString);
}
bool BytecodeEmitter::emitSelfHostedIsNullOrUndefined(CallNode* callNode) {
ListNode* argsList = &callNode->right()->as<ListNode>();
MOZ_ASSERT(argsList->count() == 1);
ParseNode* argNode = argsList->head();
if (!emitTree(argNode)) {
// [stack] ARG
return false;
}
if (!emit1(JSOp::IsNullOrUndefined)) {
// [stack] ARG IS_NULL_OR_UNDEF
return false;
}
if (!emit1(JSOp::Swap)) {
// [stack] IS_NULL_OR_UNDEF ARG
return false;
}
if (!emit1(JSOp::Pop)) {
// [stack] IS_NULL_OR_UNDEF
return false;
}
return true;
}
bool BytecodeEmitter::emitSelfHostedGetBuiltinConstructorOrPrototype(
CallNode* callNode, bool isConstructor) {
ListNode* argsList = &callNode->right()->as<ListNode>();
@ -8081,6 +8107,9 @@ bool BytecodeEmitter::emitCallOrNew(CallNode* callNode, ValueUsage valueUsage) {
if (calleeName == TaggedParserAtomIndex::WellKnown::SetCanonicalName()) {
return emitSelfHostedSetCanonicalName(callNode);
}
if (calleeName == TaggedParserAtomIndex::WellKnown::IsNullOrUndefined()) {
return emitSelfHostedIsNullOrUndefined(callNode);
}
#ifdef DEBUG
if (calleeName ==
TaggedParserAtomIndex::WellKnown::UnsafeGetReservedSlot() ||

Просмотреть файл

@ -946,6 +946,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
[[nodiscard]] bool emitSelfHostedHasOwn(CallNode* callNode);
[[nodiscard]] bool emitSelfHostedToNumeric(CallNode* callNode);
[[nodiscard]] bool emitSelfHostedToString(CallNode* callNode);
[[nodiscard]] bool emitSelfHostedIsNullOrUndefined(CallNode* callNode);
[[nodiscard]] bool emitSelfHostedGetBuiltinConstructor(CallNode* callNode);
[[nodiscard]] bool emitSelfHostedGetBuiltinPrototype(CallNode* callNode);
[[nodiscard]] bool emitSelfHostedGetBuiltinSymbol(CallNode* callNode);

Просмотреть файл

@ -295,6 +295,7 @@
MACRO_(isExtensible, isExtensible, "isExtensible") \
MACRO_(isFinite, isFinite, "isFinite") \
MACRO_(isNaN, isNaN, "isNaN") \
MACRO_(IsNullOrUndefined, IsNullOrUndefined, "IsNullOrUndefined") \
MACRO_(isPrototypeOf, isPrototypeOf, "isPrototypeOf") \
MACRO_(isStepStart, isStepStart, "isStepStart") \
MACRO_(isSubsetOf, isSubsetOf, "isSubsetOf") \