Array.prototype.flat/flatMap have a minor bug in ArraySpeciesCreate
authoryusukesuzuki@slowstart.org <yusukesuzuki@slowstart.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 9 Jan 2019 02:56:15 +0000 (02:56 +0000)
committeryusukesuzuki@slowstart.org <yusukesuzuki@slowstart.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 9 Jan 2019 02:56:15 +0000 (02:56 +0000)
https://bugs.webkit.org/show_bug.cgi?id=193127

Reviewed by Saam Barati.

JSTests:

* stress/array-species-create-should-handle-masquerader.js: Added.
(shouldThrow):
* stress/is-undefined-or-null-builtin.js: Added.
(shouldBe):
(isUndefinedOrNull.vm.createBuiltin):

Source/JavaScriptCore:

`== null` is frequently used idiom to check `null` or `undefined` in JS.
However, it has a problem in terms of the builtin JS implementation: it
returns true if masquerade-as-undefined objects (e.g. document.all) come.

In this patch, we introduce a convenient builtin intrinsic @isUndefinedOrNull,
which is equivalent to C++ `JSValue::isUndefinedOrNull`. It does not consider
about masquerade-as-undefined objects, so that we can use it instead of
`value === null || value === @undefined`. We introduce is_undefined_or_null
bytecode, IsUndefinedOrNull DFG node and its DFG and FTL backends. Since
Null and Undefined have some bit patterns, we can implement this query
very efficiently.

* builtins/ArrayIteratorPrototype.js:
(next):
* builtins/ArrayPrototype.js:
(globalPrivate.arraySpeciesCreate):
* builtins/GlobalOperations.js:
(globalPrivate.speciesConstructor):
(globalPrivate.copyDataProperties):
(globalPrivate.copyDataPropertiesNoExclusions):
* builtins/MapIteratorPrototype.js:
(next):
* builtins/SetIteratorPrototype.js:
(next):
* builtins/StringIteratorPrototype.js:
(next):
* builtins/StringPrototype.js:
(match):
(repeat):
(padStart):
(padEnd):
(intrinsic.StringPrototypeReplaceIntrinsic.replace):
(search):
(split):
(concat):
(globalPrivate.createHTML):
* builtins/TypedArrayPrototype.js:
(globalPrivate.typedArraySpeciesConstructor):
(map):
(filter):
* bytecode/BytecodeIntrinsicRegistry.h:
* bytecode/BytecodeList.rb:
* bytecode/BytecodeUseDef.h:
(JSC::computeUsesForBytecodeOffset):
(JSC::computeDefsForBytecodeOffset):
* bytecompiler/BytecodeGenerator.cpp:
(JSC::BytecodeGenerator::emitIsUndefinedOrNull):
* bytecompiler/BytecodeGenerator.h:
* bytecompiler/NodesCodegen.cpp:
(JSC::BytecodeIntrinsicNode::emit_intrinsic_isUndefinedOrNull):
* dfg/DFGAbstractInterpreterInlines.h:
(JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
* dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::parseBlock):
* dfg/DFGCapabilities.cpp:
(JSC::DFG::capabilityLevel):
* dfg/DFGClobberize.h:
(JSC::DFG::clobberize):
* dfg/DFGDoesGC.cpp:
(JSC::DFG::doesGC):
* dfg/DFGFixupPhase.cpp:
(JSC::DFG::FixupPhase::fixupNode):
* dfg/DFGNodeType.h:
* dfg/DFGPredictionPropagationPhase.cpp:
* dfg/DFGSafeToExecute.h:
(JSC::DFG::safeToExecute):
* dfg/DFGSpeculativeJIT32_64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* dfg/DFGSpeculativeJIT64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* ftl/FTLCapabilities.cpp:
(JSC::FTL::canCompile):
* ftl/FTLLowerDFGToB3.cpp:
(JSC::FTL::DFG::LowerDFGToB3::compileNode):
(JSC::FTL::DFG::LowerDFGToB3::compileIsUndefinedOrNull):
* jit/JIT.cpp:
(JSC::JIT::privateCompileMainPass):
* jit/JIT.h:
* jit/JITOpcodes.cpp:
(JSC::JIT::emit_op_is_undefined_or_null):
* jit/JITOpcodes32_64.cpp:
(JSC::JIT::emit_op_is_undefined_or_null):
* llint/LowLevelInterpreter32_64.asm:
* llint/LowLevelInterpreter64.asm:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@239761 268f45cc-cd09-0410-ab3c-d52691b4dbfc

37 files changed:
JSTests/ChangeLog
JSTests/stress/array-species-create-should-handle-masquerader.js [new file with mode: 0644]
JSTests/stress/is-undefined-or-null-builtin.js [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/builtins/ArrayIteratorPrototype.js
Source/JavaScriptCore/builtins/ArrayPrototype.js
Source/JavaScriptCore/builtins/GlobalOperations.js
Source/JavaScriptCore/builtins/MapIteratorPrototype.js
Source/JavaScriptCore/builtins/SetIteratorPrototype.js
Source/JavaScriptCore/builtins/StringIteratorPrototype.js
Source/JavaScriptCore/builtins/StringPrototype.js
Source/JavaScriptCore/builtins/TypedArrayPrototype.js
Source/JavaScriptCore/bytecode/BytecodeIntrinsicRegistry.h
Source/JavaScriptCore/bytecode/BytecodeList.rb
Source/JavaScriptCore/bytecode/BytecodeUseDef.h
Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp
Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h
Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp
Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
Source/JavaScriptCore/dfg/DFGCapabilities.cpp
Source/JavaScriptCore/dfg/DFGClobberize.h
Source/JavaScriptCore/dfg/DFGDoesGC.cpp
Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
Source/JavaScriptCore/dfg/DFGNodeType.h
Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp
Source/JavaScriptCore/dfg/DFGSafeToExecute.h
Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp
Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
Source/JavaScriptCore/ftl/FTLCapabilities.cpp
Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
Source/JavaScriptCore/jit/JIT.cpp
Source/JavaScriptCore/jit/JIT.h
Source/JavaScriptCore/jit/JITOpcodes.cpp
Source/JavaScriptCore/jit/JITOpcodes32_64.cpp
Source/JavaScriptCore/llint/LowLevelInterpreter32_64.asm
Source/JavaScriptCore/llint/LowLevelInterpreter64.asm

index 4620538..e5a3171 100644 (file)
@@ -1,3 +1,16 @@
+2019-01-08  Yusuke Suzuki  <yusukesuzuki@slowstart.org>
+
+        Array.prototype.flat/flatMap have a minor bug in ArraySpeciesCreate
+        https://bugs.webkit.org/show_bug.cgi?id=193127
+
+        Reviewed by Saam Barati.
+
+        * stress/array-species-create-should-handle-masquerader.js: Added.
+        (shouldThrow):
+        * stress/is-undefined-or-null-builtin.js: Added.
+        (shouldBe):
+        (isUndefinedOrNull.vm.createBuiltin):
+
 2019-01-08  Tadeu Zagallo  <tzagallo@apple.com>
 
         LLInt put_by_id uses the wrong load instruction for loading flags from the metadata
diff --git a/JSTests/stress/array-species-create-should-handle-masquerader.js b/JSTests/stress/array-species-create-should-handle-masquerader.js
new file mode 100644 (file)
index 0000000..6caaccf
--- /dev/null
@@ -0,0 +1,21 @@
+function shouldThrow(func, errorMessage) {
+    var errorThrown = false;
+    var error = null;
+    try {
+        func();
+    } catch (e) {
+        errorThrown = true;
+        error = e;
+    }
+    if (!errorThrown)
+        throw new Error('not thrown');
+    if (String(error) !== errorMessage)
+        throw new Error(`bad error: ${String(error)}`);
+}
+noInline(shouldThrow);
+
+for (var i = 0; i < 1e5; ++i) {
+    shouldThrow(() => {
+        new (class extends Array { static get [Symbol.species]() { return makeMasquerader(); } })(1, 2, 3).flat().constructor
+    }, `TypeError: Masquerader is not a constructor`);
+}
diff --git a/JSTests/stress/is-undefined-or-null-builtin.js b/JSTests/stress/is-undefined-or-null-builtin.js
new file mode 100644 (file)
index 0000000..0f5a8c5
--- /dev/null
@@ -0,0 +1,26 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+noInline(shouldBe);
+
+var isUndefinedOrNull = $vm.createBuiltin(`(function (value) { return @isUndefinedOrNull(value); })`);
+noInline(isUndefinedOrNull);
+
+var masquerader = makeMasquerader();
+for (var i = 0; i < 1e5; ++i) {
+    shouldBe(isUndefinedOrNull(null), true);
+    shouldBe(isUndefinedOrNull(undefined), true);
+    shouldBe(isUndefinedOrNull("Hello"), false);
+    shouldBe(isUndefinedOrNull(Symbol("Hello")), false);
+    shouldBe(isUndefinedOrNull(42), false);
+    shouldBe(isUndefinedOrNull(-42), false);
+    shouldBe(isUndefinedOrNull(0), false);
+    shouldBe(isUndefinedOrNull(-0), false);
+    shouldBe(isUndefinedOrNull(42.2), false);
+    shouldBe(isUndefinedOrNull(-42.2), false);
+    shouldBe(isUndefinedOrNull({}), false);
+    shouldBe(isUndefinedOrNull([]), false);
+    shouldBe(isUndefinedOrNull(true), false);
+    shouldBe(isUndefinedOrNull(masquerader), false);
+}
index d6fce9f..33eaaad 100644 (file)
@@ -1,3 +1,95 @@
+2019-01-08  Yusuke Suzuki  <yusukesuzuki@slowstart.org>
+
+        Array.prototype.flat/flatMap have a minor bug in ArraySpeciesCreate
+        https://bugs.webkit.org/show_bug.cgi?id=193127
+
+        Reviewed by Saam Barati.
+
+        `== null` is frequently used idiom to check `null` or `undefined` in JS.
+        However, it has a problem in terms of the builtin JS implementation: it
+        returns true if masquerade-as-undefined objects (e.g. document.all) come.
+
+        In this patch, we introduce a convenient builtin intrinsic @isUndefinedOrNull,
+        which is equivalent to C++ `JSValue::isUndefinedOrNull`. It does not consider
+        about masquerade-as-undefined objects, so that we can use it instead of
+        `value === null || value === @undefined`. We introduce is_undefined_or_null
+        bytecode, IsUndefinedOrNull DFG node and its DFG and FTL backends. Since
+        Null and Undefined have some bit patterns, we can implement this query
+        very efficiently.
+
+        * builtins/ArrayIteratorPrototype.js:
+        (next):
+        * builtins/ArrayPrototype.js:
+        (globalPrivate.arraySpeciesCreate):
+        * builtins/GlobalOperations.js:
+        (globalPrivate.speciesConstructor):
+        (globalPrivate.copyDataProperties):
+        (globalPrivate.copyDataPropertiesNoExclusions):
+        * builtins/MapIteratorPrototype.js:
+        (next):
+        * builtins/SetIteratorPrototype.js:
+        (next):
+        * builtins/StringIteratorPrototype.js:
+        (next):
+        * builtins/StringPrototype.js:
+        (match):
+        (repeat):
+        (padStart):
+        (padEnd):
+        (intrinsic.StringPrototypeReplaceIntrinsic.replace):
+        (search):
+        (split):
+        (concat):
+        (globalPrivate.createHTML):
+        * builtins/TypedArrayPrototype.js:
+        (globalPrivate.typedArraySpeciesConstructor):
+        (map):
+        (filter):
+        * bytecode/BytecodeIntrinsicRegistry.h:
+        * bytecode/BytecodeList.rb:
+        * bytecode/BytecodeUseDef.h:
+        (JSC::computeUsesForBytecodeOffset):
+        (JSC::computeDefsForBytecodeOffset):
+        * bytecompiler/BytecodeGenerator.cpp:
+        (JSC::BytecodeGenerator::emitIsUndefinedOrNull):
+        * bytecompiler/BytecodeGenerator.h:
+        * bytecompiler/NodesCodegen.cpp:
+        (JSC::BytecodeIntrinsicNode::emit_intrinsic_isUndefinedOrNull):
+        * dfg/DFGAbstractInterpreterInlines.h:
+        (JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
+        * dfg/DFGByteCodeParser.cpp:
+        (JSC::DFG::ByteCodeParser::parseBlock):
+        * dfg/DFGCapabilities.cpp:
+        (JSC::DFG::capabilityLevel):
+        * dfg/DFGClobberize.h:
+        (JSC::DFG::clobberize):
+        * dfg/DFGDoesGC.cpp:
+        (JSC::DFG::doesGC):
+        * dfg/DFGFixupPhase.cpp:
+        (JSC::DFG::FixupPhase::fixupNode):
+        * dfg/DFGNodeType.h:
+        * dfg/DFGPredictionPropagationPhase.cpp:
+        * dfg/DFGSafeToExecute.h:
+        (JSC::DFG::safeToExecute):
+        * dfg/DFGSpeculativeJIT32_64.cpp:
+        (JSC::DFG::SpeculativeJIT::compile):
+        * dfg/DFGSpeculativeJIT64.cpp:
+        (JSC::DFG::SpeculativeJIT::compile):
+        * ftl/FTLCapabilities.cpp:
+        (JSC::FTL::canCompile):
+        * ftl/FTLLowerDFGToB3.cpp:
+        (JSC::FTL::DFG::LowerDFGToB3::compileNode):
+        (JSC::FTL::DFG::LowerDFGToB3::compileIsUndefinedOrNull):
+        * jit/JIT.cpp:
+        (JSC::JIT::privateCompileMainPass):
+        * jit/JIT.h:
+        * jit/JITOpcodes.cpp:
+        (JSC::JIT::emit_op_is_undefined_or_null):
+        * jit/JITOpcodes32_64.cpp:
+        (JSC::JIT::emit_op_is_undefined_or_null):
+        * llint/LowLevelInterpreter32_64.asm:
+        * llint/LowLevelInterpreter64.asm:
+
 2019-01-08  David Kilzer  <ddkilzer@apple.com>
 
         Leak of VectorBufferBase.m_buffer (16-64 bytes) under JSC::CompactVariableEnvironment in com.apple.WebKit.WebContent running layout tests
index f21a0ab..94eb5f4 100644 (file)
@@ -28,7 +28,7 @@ function next()
 {
     "use strict";
 
-    if (this == null)
+    if (@isUndefinedOrNull(this))
         @throwTypeError("%ArrayIteratorPrototype%.next requires that |this| not be null or undefined");
 
     let next = @getByIdDirectPrivate(this, "arrayIteratorNext");
index bb67a72..1b518f6 100644 (file)
@@ -761,7 +761,7 @@ function arraySpeciesCreate(array, length)
 
     if (@isObject(constructor)) {
         constructor = constructor.@speciesSymbol;
-        if (constructor == null)
+        if (@isUndefinedOrNull(constructor))
             return @newArrayWithSize(length);
     }
 
index 62c29d5..362d204 100644 (file)
@@ -77,7 +77,7 @@ function speciesConstructor(obj, defaultConstructor)
     if (!@isObject(constructor))
         @throwTypeError("|this|.constructor is not an Object or undefined");
     constructor = constructor.@speciesSymbol;
-    if (constructor == null)
+    if (@isUndefinedOrNull(constructor))
         return defaultConstructor;
     if (@isConstructor(constructor))
         return constructor;
@@ -92,7 +92,7 @@ function copyDataProperties(target, source, excludedSet)
     if (!@isObject(target))
         @throwTypeError("target needs to be an object");
 
-    if (source == null) 
+    if (@isUndefinedOrNull(source))
         return target;
 
     let from = @toObject(source);
@@ -119,7 +119,7 @@ function copyDataPropertiesNoExclusions(target, source)
     if (!@isObject(target))
         @throwTypeError("target needs to be an object");
 
-    if (source == null) 
+    if (@isUndefinedOrNull(source))
         return target;
 
     let from = @toObject(source);
index 31ca95b..41cd408 100644 (file)
@@ -48,7 +48,7 @@ function next()
 {
     "use strict";
 
-    if (this == null)
+    if (@isUndefinedOrNull(this))
         @throwTypeError("%MapIteratorPrototype%.next requires that |this| not be null or undefined");
 
     var bucket = @getByIdDirectPrivate(this, "mapBucket");
index b80b932..183ed67 100644 (file)
@@ -45,7 +45,7 @@ function next()
 {
     "use strict";
 
-    if (this == null)
+    if (@isUndefinedOrNull(this))
         @throwTypeError("%SetIteratorPrototype%.next requires that |this| not be null or undefined");
 
     var bucket = @getByIdDirectPrivate(this, "setBucket");
index fc77b52..d122de8 100644 (file)
@@ -27,7 +27,7 @@ function next()
 {
     "use strict";
 
-    if (this == null)
+    if (@isUndefinedOrNull(this))
         @throwTypeError("%StringIteratorPrototype%.next requires that |this| not be null or undefined");
 
     var position = @getByIdDirectPrivate(this, "stringIteratorNextIndex");
index 4028f80..80fc826 100644 (file)
@@ -29,7 +29,7 @@ function match(regexp)
 {
     "use strict";
 
-    if (this == null)
+    if (@isUndefinedOrNull(this))
         @throwTypeError("String.prototype.match requires that |this| not be null or undefined");
 
     if (regexp != null) {
@@ -101,7 +101,7 @@ function repeat(count)
 {
     "use strict";
 
-    if (this == null)
+    if (@isUndefinedOrNull(this))
         @throwTypeError("String.prototype.repeat requires that |this| not be null or undefined");
 
     var string = @toString(this);
@@ -120,7 +120,7 @@ function padStart(maxLength/*, fillString*/)
 {
     "use strict";
 
-    if (this == null)
+    if (@isUndefinedOrNull(this))
         @throwTypeError("String.prototype.padStart requires that |this| not be null or undefined");
 
     var string = @toString(this);
@@ -157,7 +157,7 @@ function padEnd(maxLength/*, fillString*/)
 {
     "use strict";
 
-    if (this == null)
+    if (@isUndefinedOrNull(this))
         @throwTypeError("String.prototype.padEnd requires that |this| not be null or undefined");
 
     var string = @toString(this);
@@ -221,7 +221,7 @@ function replace(search, replace)
 {
     "use strict";
 
-    if (this == null)
+    if (@isUndefinedOrNull(this))
         @throwTypeError("String.prototype.replace requires that |this| not be null or undefined");
 
     if (search != null) {
@@ -254,7 +254,7 @@ function localeCompare(that/*, locales, options */)
     // http://ecma-international.org/publications/standards/Ecma-402.htm
 
     // 1. Let O be RequireObjectCoercible(this value).
-    if (this == null)
+    if (@isUndefinedOrNull(this))
         @throwTypeError("String.prototype.localeCompare requires that |this| not be null or undefined");
 
     // 2. Let S be ToString(O).
@@ -283,7 +283,7 @@ function search(regexp)
 {
     "use strict";
 
-    if (this == null)
+    if (@isUndefinedOrNull(this))
         @throwTypeError("String.prototype.search requires that |this| not be null or undefined");
 
     if (regexp != null) {
@@ -301,7 +301,7 @@ function split(separator, limit)
 {
     "use strict";
     
-    if (this == null)
+    if (@isUndefinedOrNull(this))
         @throwTypeError("String.prototype.split requires that |this| not be null or undefined");
     
     if (separator != null) {
@@ -328,7 +328,7 @@ function concat(arg /* ... */)
 {
     "use strict";
 
-    if (this == null)
+    if (@isUndefinedOrNull(this))
         @throwTypeError("String.prototype.concat requires that |this| not be null or undefined");
 
     if (@argumentCount() === 1)
@@ -340,7 +340,7 @@ function concat(arg /* ... */)
 function createHTML(func, string, tag, attribute, value)
 {
     "use strict";
-    if (string == null)
+    if (@isUndefinedOrNull(string))
         @throwTypeError(`${func} requires that |this| not be null or undefined`);
     let S = @toString(string);
     let p1 = "<" + tag;
index 989aa60..f5faa6d 100644 (file)
@@ -43,7 +43,7 @@ function typedArraySpeciesConstructor(value)
         @throwTypeError("|this|.constructor is not an Object or undefined");
 
     constructor = constructor.@speciesSymbol;
-    if (constructor == null)
+    if (@isUndefinedOrNull(constructor))
         return @typedArrayGetOriginalConstructor(value);
     // The lack of an @isConstructor(constructor) check here is not observable because
     // the first thing we will do with the value is attempt to construct the result with it.
@@ -340,7 +340,7 @@ function map(callback /*, thisArg */)
         result = new (@typedArrayGetOriginalConstructor(this))(length);
     else {
         var speciesConstructor = constructor.@speciesSymbol;
-        if (speciesConstructor === null || speciesConstructor === @undefined)
+        if (@isUndefinedOrNull(speciesConstructor))
             result = new (@typedArrayGetOriginalConstructor(this))(length);
         else {
             result = new speciesConstructor(length);
@@ -381,7 +381,7 @@ function filter(callback /*, thisArg */)
         result = new (@typedArrayGetOriginalConstructor(this))(resultLength);
     else {
         var speciesConstructor = constructor.@speciesSymbol;
-        if (speciesConstructor === null || speciesConstructor === @undefined)
+        if (@isUndefinedOrNull(speciesConstructor))
             result = new (@typedArrayGetOriginalConstructor(this))(resultLength);
         else {
             result = new speciesConstructor(resultLength);
index eda34ec..e97bd95 100644 (file)
@@ -50,6 +50,7 @@ class Identifier;
     macro(isRegExpObject) \
     macro(isMap) \
     macro(isSet) \
+    macro(isUndefinedOrNull) \
     macro(tailCallForwardArguments) \
     macro(throwTypeError) \
     macro(throwRangeError) \
index 211532d..3c8b312 100644 (file)
@@ -286,6 +286,7 @@ op_group :UnaryOp,
         :unsigned,
         :is_empty,
         :is_undefined,
+        :is_undefined_or_null,
         :is_boolean,
         :is_number,
         :is_object,
index 2279227..bba02b1 100644 (file)
@@ -168,6 +168,7 @@ void computeUsesForBytecodeOffset(Block* codeBlock, OpcodeID opcodeID, const Ins
     USES(OpTypeof, value)
     USES(OpIsEmpty, operand)
     USES(OpIsUndefined, operand)
+    USES(OpIsUndefinedOrNull, operand)
     USES(OpIsBoolean, operand)
     USES(OpIsNumber, operand)
     USES(OpIsObject, operand)
@@ -403,6 +404,7 @@ void computeDefsForBytecodeOffset(Block* codeBlock, OpcodeID opcodeID, const Ins
     DEFS(OpIdentityWithProfile, srcDst)
     DEFS(OpIsEmpty, dst)
     DEFS(OpIsUndefined, dst)
+    USES(OpIsUndefinedOrNull, dst)
     DEFS(OpIsBoolean, dst)
     DEFS(OpIsNumber, dst)
     DEFS(OpIsObject, dst)
index 71eff15..51ee8ef 100644 (file)
@@ -4193,6 +4193,12 @@ RegisterID* BytecodeGenerator::emitIsUndefined(RegisterID* dst, RegisterID* src)
     return dst;
 }
 
+RegisterID* BytecodeGenerator::emitIsUndefinedOrNull(RegisterID* dst, RegisterID* src)
+{
+    OpIsUndefinedOrNull::emit(this, dst, src);
+    return dst;
+}
+
 RegisterID* BytecodeGenerator::emitIsEmpty(RegisterID* dst, RegisterID* src)
 {
     OpIsEmpty::emit(this, dst, src);
index 968eb09..6ad716e 100644 (file)
@@ -859,6 +859,7 @@ namespace JSC {
         RegisterID* emitIsObject(RegisterID* dst, RegisterID* src);
         RegisterID* emitIsNumber(RegisterID* dst, RegisterID* src);
         RegisterID* emitIsUndefined(RegisterID* dst, RegisterID* src);
+        RegisterID* emitIsUndefinedOrNull(RegisterID* dst, RegisterID* src);
         RegisterID* emitIsEmpty(RegisterID* dst, RegisterID* src);
         RegisterID* emitIsDerivedArray(RegisterID* dst, RegisterID* src) { return emitIsCellWithType(dst, src, DerivedArrayType); }
         void emitRequireObjectCoercible(RegisterID* value, const String& error);
index 4c09459..90b6d49 100644 (file)
@@ -1221,6 +1221,15 @@ RegisterID* BytecodeIntrinsicNode::emit_intrinsic_isSet(JSC::BytecodeGenerator&
     return generator.move(dst, generator.emitIsSet(generator.tempDestination(dst), src.get()));
 }
 
+RegisterID* BytecodeIntrinsicNode::emit_intrinsic_isUndefinedOrNull(JSC::BytecodeGenerator& generator, JSC::RegisterID* dst)
+{
+    ArgumentListNode* node = m_args->m_listNode;
+    RefPtr<RegisterID> src = generator.emitNode(node);
+    ASSERT(!node->m_next);
+
+    return generator.move(dst, generator.emitIsUndefinedOrNull(generator.tempDestination(dst), src.get()));
+}
+
 RegisterID* BytecodeIntrinsicNode::emit_intrinsic_newArrayWithSize(JSC::BytecodeGenerator& generator, JSC::RegisterID* dst)
 {
     ArgumentListNode* node = m_args->m_listNode;
index a8113f3..2c55ac3 100644 (file)
@@ -1281,6 +1281,7 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi
 
     case IsEmpty:
     case IsUndefined:
+    case IsUndefinedOrNull:
     case IsBoolean:
     case IsNumber:
     case NumberIsInteger:
@@ -1302,6 +1303,9 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi
                     ? child.value().asCell()->structure(m_vm)->masqueradesAsUndefined(m_codeBlock->globalObjectFor(node->origin.semantic))
                     : child.value().isUndefined()));
                 break;
+            case IsUndefinedOrNull:
+                setConstant(node, jsBoolean(child.value().isUndefinedOrNull()));
+                break;
             case IsBoolean:
                 setConstant(node, jsBoolean(child.value().isBoolean()));
                 break;
@@ -1390,6 +1394,19 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi
             }
             
             break;
+        case IsUndefinedOrNull:
+            if (!(child.m_type & ~SpecOther)) {
+                setConstant(node, jsBoolean(true));
+                constantWasSet = true;
+                break;
+            }
+
+            if (!(child.m_type & SpecOther)) {
+                setConstant(node, jsBoolean(false));
+                constantWasSet = true;
+                break;
+            }
+            break;
         case IsBoolean:
             if (!(child.m_type & ~SpecBoolean)) {
                 setConstant(node, jsBoolean(true));
index 93529db..a6d3128 100644 (file)
@@ -5197,6 +5197,12 @@ void ByteCodeParser::parseBlock(unsigned limit)
             set(bytecode.dst, addToGraph(IsUndefined, value));
             NEXT_OPCODE(op_is_undefined);
         }
+        case op_is_undefined_or_null: {
+            auto bytecode = currentInstruction->as<OpIsUndefinedOrNull>();
+            Node* value = get(bytecode.operand);
+            set(bytecode.dst, addToGraph(IsUndefinedOrNull, value));
+            NEXT_OPCODE(op_is_undefined_or_null);
+        }
 
         case op_is_boolean: {
             auto bytecode = currentInstruction->as<OpIsBoolean>();
index c3de14b..4053363 100644 (file)
@@ -143,6 +143,7 @@ CapabilityLevel capabilityLevel(OpcodeID opcodeID, CodeBlock* codeBlock, const I
     case op_instanceof_custom:
     case op_is_empty:
     case op_is_undefined:
+    case op_is_undefined_or_null:
     case op_is_boolean:
     case op_is_number:
     case op_is_object:
index fcb3c6b..58ec030 100644 (file)
@@ -176,6 +176,7 @@ void clobberize(Graph& graph, Node* node, const ReadFunctor& read, const WriteFu
     case SameValue:
     case IsEmpty:
     case IsUndefined:
+    case IsUndefinedOrNull:
     case IsBoolean:
     case IsNumber:
     case NumberIsInteger:
index ddbfa5d..4f79af6 100644 (file)
@@ -185,6 +185,7 @@ bool doesGC(Graph& graph, Node* node)
     case InstanceOfCustom:
     case IsEmpty:
     case IsUndefined:
+    case IsUndefinedOrNull:
     case IsBoolean:
     case IsNumber:
     case NumberIsInteger:
index d7860ea..e6bac2d 100644 (file)
@@ -2356,6 +2356,7 @@ private:
         case IsTypedArrayView:
         case IsEmpty:
         case IsUndefined:
+        case IsUndefinedOrNull:
         case IsBoolean:
         case IsNumber:
         case IsObjectOrNull:
index 03fe46b..ded0e26 100644 (file)
@@ -368,6 +368,7 @@ namespace JSC { namespace DFG {
     macro(IsCellWithType, NodeResultBoolean) \
     macro(IsEmpty, NodeResultBoolean) \
     macro(IsUndefined, NodeResultBoolean) \
+    macro(IsUndefinedOrNull, NodeResultBoolean) \
     macro(IsBoolean, NodeResultBoolean) \
     macro(IsNumber, NodeResultBoolean) \
     macro(NumberIsInteger, NodeResultBoolean) \
index 1ba4eff..a5869b3 100644 (file)
@@ -930,6 +930,7 @@ private:
         case InstanceOfCustom:
         case IsEmpty:
         case IsUndefined:
+        case IsUndefinedOrNull:
         case IsBoolean:
         case IsNumber:
         case NumberIsInteger:
index 06da94f..48444a1 100644 (file)
@@ -327,6 +327,7 @@ bool safeToExecute(AbstractStateType& state, Graph& graph, Node* node, bool igno
     case InstanceOfCustom:
     case IsEmpty:
     case IsUndefined:
+    case IsUndefinedOrNull:
     case IsBoolean:
     case IsNumber:
     case NumberIsInteger:
index 10bddf8..c299bcc 100644 (file)
@@ -3564,6 +3564,23 @@ void SpeculativeJIT::compile(Node* node)
         break;
     }
 
+    case IsUndefinedOrNull: {
+        JSValueOperand value(this, node->child1());
+        GPRTemporary result(this, Reuse, value, TagWord);
+
+        GPRReg valueTagGPR = value.tagGPR();
+        GPRReg resultGPR = result.gpr();
+
+        m_jit.move(valueTagGPR, resultGPR);
+        static_assert((JSValue::UndefinedTag + 1 == JSValue::NullTag) && (JSValue::NullTag & 0x1), "");
+        m_jit.or32(CCallHelpers::TrustedImm32(1), resultGPR);
+        m_jit.compare32(CCallHelpers::Equal, resultGPR, CCallHelpers::TrustedImm32(JSValue::NullTag), resultGPR);
+
+        booleanResult(resultGPR, node);
+        break;
+    }
+
+
     case IsBoolean: {
         JSValueOperand value(this, node->child1());
         GPRTemporary result(this, Reuse, value, TagWord);
index 5d58fda..32e08c9 100644 (file)
@@ -3873,6 +3873,21 @@ void SpeculativeJIT::compile(Node* node)
         jsValueResult(result.gpr(), node, DataFormatJSBoolean);
         break;
     }
+
+    case IsUndefinedOrNull: {
+        JSValueOperand value(this, node->child1());
+        GPRTemporary result(this, Reuse, value);
+
+        GPRReg valueGPR = value.gpr();
+        GPRReg resultGPR = result.gpr();
+
+        m_jit.move(valueGPR, resultGPR);
+        m_jit.and64(CCallHelpers::TrustedImm32(~TagBitUndefined), resultGPR);
+        m_jit.compare64(CCallHelpers::Equal, resultGPR, CCallHelpers::TrustedImm32(ValueNull), resultGPR);
+
+        unblessedBooleanResult(resultGPR, node);
+        break;
+    }
         
     case IsBoolean: {
         JSValueOperand value(this, node->child1());
index 6b7d0fc..7db5b56 100644 (file)
@@ -236,6 +236,7 @@ inline CapabilityLevel canCompile(Node* node)
     case WeakMapSet:
     case IsEmpty:
     case IsUndefined:
+    case IsUndefinedOrNull:
     case IsBoolean:
     case IsNumber:
     case NumberIsInteger:
index 9156a48..73836a8 100644 (file)
@@ -1117,6 +1117,9 @@ private:
         case IsUndefined:
             compileIsUndefined();
             break;
+        case IsUndefinedOrNull:
+            compileIsUndefinedOrNull();
+            break;
         case IsBoolean:
             compileIsBoolean();
             break;
@@ -9323,6 +9326,11 @@ private:
     {
         setBoolean(equalNullOrUndefined(m_node->child1(), AllCellsAreFalse, EqualUndefined));
     }
+
+    void compileIsUndefinedOrNull()
+    {
+        setBoolean(isOther(lowJSValue(m_node->child1()), provenType(m_node->child1())));
+    }
     
     void compileIsBoolean()
     {
index 39bfd64..5d6e077 100644 (file)
@@ -355,6 +355,7 @@ void JIT::privateCompileMainPass()
         DEFINE_OP(op_instanceof_custom)
         DEFINE_OP(op_is_empty)
         DEFINE_OP(op_is_undefined)
+        DEFINE_OP(op_is_undefined_or_null)
         DEFINE_OP(op_is_boolean)
         DEFINE_OP(op_is_number)
         DEFINE_OP(op_is_object)
index a1ca2ea..6c9d3bb 100644 (file)
@@ -545,6 +545,7 @@ namespace JSC {
         void emit_op_instanceof_custom(const Instruction*);
         void emit_op_is_empty(const Instruction*);
         void emit_op_is_undefined(const Instruction*);
+        void emit_op_is_undefined_or_null(const Instruction*);
         void emit_op_is_boolean(const Instruction*);
         void emit_op_is_number(const Instruction*);
         void emit_op_is_object(const Instruction*);
index 801a6a8..6ec0749 100644 (file)
@@ -243,6 +243,21 @@ void JIT::emit_op_is_undefined(const Instruction* currentInstruction)
     emitPutVirtualRegister(dst);
 }
 
+void JIT::emit_op_is_undefined_or_null(const Instruction* currentInstruction)
+{
+    auto bytecode = currentInstruction->as<OpIsUndefinedOrNull>();
+    int dst = bytecode.dst.offset();
+    int value = bytecode.operand.offset();
+
+    emitGetVirtualRegister(value, regT0);
+
+    and64(TrustedImm32(~TagBitUndefined), regT0);
+    compare64(Equal, regT0, TrustedImm32(ValueNull), regT0);
+
+    boxBoolean(regT0, JSValueRegs { regT0 });
+    emitPutVirtualRegister(dst);
+}
+
 void JIT::emit_op_is_boolean(const Instruction* currentInstruction)
 {
     auto bytecode = currentInstruction->as<OpIsBoolean>();
index 87c1fa0..acdc007 100644 (file)
@@ -252,6 +252,19 @@ void JIT::emit_op_is_undefined(const Instruction* currentInstruction)
     emitStoreBool(dst, regT0);
 }
 
+void JIT::emit_op_is_undefined_or_null(const Instruction* currentInstruction)
+{
+    auto bytecode = currentInstruction->as<OpIsUndefinedOrNull>();
+    int dst = bytecode.dst.offset();
+    int value = bytecode.operand.offset();
+
+    emitLoadTag(value, regT0);
+    static_assert((JSValue::UndefinedTag + 1 == JSValue::NullTag) && (JSValue::NullTag & 0x1), "");
+    or32(TrustedImm32(1), regT0);
+    compare32(Equal, regT0, TrustedImm32(JSValue::NullTag), regT0);
+    emitStoreBool(dst, regT0);
+}
+
 void JIT::emit_op_is_boolean(const Instruction* currentInstruction)
 {
     auto bytecode = currentInstruction->as<OpIsBoolean>();
index bb22057..05b3a69 100644 (file)
@@ -859,6 +859,16 @@ equalNullComparisonOp(op_neq_null, OpNeqNull,
     macro (value) xori 1, value end)
 
 
+llintOpWithReturn(op_is_undefined_or_null, OpIsUndefinedOrNull, macro (size, get, dispatch, return)
+    get(operand, t0)
+    assertNotConstant(size, t0)
+    loadi TagOffset[cfr, t0, 8], t1
+    ori 1, t1
+    cieq t1, NullTag, t1
+    return(BooleanTag, t1)
+end)
+
+
 macro strictEqOp(name, op, equalityOperation)
     llintOpWithReturn(op_%name%, op, macro (size, get, dispatch, return)
         get(rhs, t2)
index eed2a0f..412b5c6 100644 (file)
@@ -790,6 +790,16 @@ equalNullComparisonOp(op_neq_null, OpNeqNull,
     macro (value) xorq ValueTrue, value end)
 
 
+llintOpWithReturn(op_is_undefined_or_null, OpIsUndefinedOrNull, macro (size, get, dispatch, return)
+    get(operand, t0)
+    loadq [cfr, t0, 8], t0
+    andq ~TagBitUndefined, t0
+    cqeq t0, ValueNull, t0
+    orq ValueFalse, t0
+    return(t0)
+end)
+
+
 macro strictEqOp(name, op, equalityOperation)
     llintOpWithReturn(op_%name%, op, macro (size, get, dispatch, return)
         get(rhs, t0)