зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1655465 - Part 9: Change JSOp::FunctionProto to JSOp::BuiltinObject. r=jandem
Callers to `GetBuiltinPrototype()` rely on inlining the function itself plus optimising the object access, so that the property value is directly seen as a constant in the compiler. By changing `GetBuiltinPrototype()` and `GetBuiltinConstructor()` to be directly translated into a JSOp, we can avoid heavily relying on the compiler to optimise these two functions. The new opcode replaces the existing JSOp::FunctionProto opcode. It doesn't use JSProtoKey directly in order to help jsparagus (bug 1622530), but instead uses its own set of mapping from enum values to built-in prototypes and constructors. This change also resolves bug 1377264. Differential Revision: https://phabricator.services.mozilla.com/D84991
This commit is contained in:
Родитель
17346573ef
Коммит
8aa2e27c24
|
@ -103,19 +103,6 @@ function IsPropertyKey(argument) {
|
|||
#define TO_PROPERTY_KEY(name) \
|
||||
(typeof name !== "string" && typeof name !== "number" && typeof name !== "symbol" ? ToPropertyKey(name) : name)
|
||||
|
||||
var _builtinCtorsCache = {__proto__: null};
|
||||
|
||||
function GetBuiltinConstructor(builtinName) {
|
||||
var ctor = _builtinCtorsCache[builtinName] ||
|
||||
(_builtinCtorsCache[builtinName] = GetBuiltinConstructorImpl(builtinName));
|
||||
assert(ctor, `No builtin with name "${builtinName}" found`);
|
||||
return ctor;
|
||||
}
|
||||
|
||||
function GetBuiltinPrototype(builtinName) {
|
||||
return (_builtinCtorsCache[builtinName] || GetBuiltinConstructor(builtinName)).prototype;
|
||||
}
|
||||
|
||||
// ES 2016 draft Mar 25, 2016 7.3.20.
|
||||
function SpeciesConstructor(obj, defaultConstructor) {
|
||||
// Step 1.
|
||||
|
|
|
@ -1544,7 +1544,7 @@ static bool BytecodeIsEffectful(JSOp op) {
|
|||
case JSOp::CheckClassHeritage:
|
||||
case JSOp::FunWithProto:
|
||||
case JSOp::ObjWithProto:
|
||||
case JSOp::FunctionProto:
|
||||
case JSOp::BuiltinObject:
|
||||
case JSOp::DerivedConstructor:
|
||||
case JSOp::CheckThis:
|
||||
case JSOp::CheckReturn:
|
||||
|
|
|
@ -510,6 +510,10 @@ bool BytecodeEmitter::emitCheckIsObj(CheckIsObjectKind kind) {
|
|||
return emit2(JSOp::CheckIsObj, uint8_t(kind));
|
||||
}
|
||||
|
||||
bool BytecodeEmitter::emitBuiltinObject(BuiltinObjectKind kind) {
|
||||
return emit2(JSOp::BuiltinObject, uint8_t(kind));
|
||||
}
|
||||
|
||||
/* Updates line number notes, not column notes. */
|
||||
bool BytecodeEmitter::updateLineNumberNotes(uint32_t offset) {
|
||||
if (skipLocationSrcNotes()) {
|
||||
|
@ -7304,6 +7308,54 @@ bool BytecodeEmitter::emitSelfHostedToString(BinaryNode* callNode) {
|
|||
return emit1(JSOp::ToString);
|
||||
}
|
||||
|
||||
bool BytecodeEmitter::emitSelfHostedGetBuiltinConstructorOrPrototype(
|
||||
BinaryNode* callNode, bool isConstructor) {
|
||||
ListNode* argsList = &callNode->right()->as<ListNode>();
|
||||
|
||||
if (argsList->count() != 1) {
|
||||
const char* name =
|
||||
isConstructor ? "GetBuiltinConstructor" : "GetBuiltinPrototype";
|
||||
reportNeedMoreArgsError(callNode, name, "1", "", argsList);
|
||||
return false;
|
||||
}
|
||||
|
||||
ParseNode* argNode = argsList->head();
|
||||
|
||||
if (!argNode->isKind(ParseNodeKind::StringExpr)) {
|
||||
reportError(callNode, JSMSG_UNEXPECTED_TYPE, "built-in name",
|
||||
"not a string constant");
|
||||
return false;
|
||||
}
|
||||
|
||||
JSAtom* name = argNode->as<NameNode>().atom();
|
||||
|
||||
BuiltinObjectKind kind;
|
||||
if (isConstructor) {
|
||||
kind = BuiltinConstructorForName(cx, name);
|
||||
} else {
|
||||
kind = BuiltinPrototypeForName(cx, name);
|
||||
}
|
||||
|
||||
if (kind == BuiltinObjectKind::None) {
|
||||
reportError(callNode, JSMSG_UNEXPECTED_TYPE, "built-in name",
|
||||
"not a valid built-in");
|
||||
return false;
|
||||
}
|
||||
|
||||
return emitBuiltinObject(kind);
|
||||
}
|
||||
|
||||
bool BytecodeEmitter::emitSelfHostedGetBuiltinConstructor(
|
||||
BinaryNode* callNode) {
|
||||
return emitSelfHostedGetBuiltinConstructorOrPrototype(
|
||||
callNode, /* isConstructor = */ true);
|
||||
}
|
||||
|
||||
bool BytecodeEmitter::emitSelfHostedGetBuiltinPrototype(BinaryNode* callNode) {
|
||||
return emitSelfHostedGetBuiltinConstructorOrPrototype(
|
||||
callNode, /* isConstructor = */ false);
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
bool BytecodeEmitter::checkSelfHostedUnsafeGetReservedSlot(
|
||||
BinaryNode* callNode) {
|
||||
|
@ -7825,6 +7877,12 @@ bool BytecodeEmitter::emitCallOrNew(
|
|||
if (calleeName == cx->parserNames().ToString) {
|
||||
return emitSelfHostedToString(callNode);
|
||||
}
|
||||
if (calleeName == cx->parserNames().GetBuiltinConstructor) {
|
||||
return emitSelfHostedGetBuiltinConstructor(callNode);
|
||||
}
|
||||
if (calleeName == cx->parserNames().GetBuiltinPrototype) {
|
||||
return emitSelfHostedGetBuiltinPrototype(callNode);
|
||||
}
|
||||
#ifdef DEBUG
|
||||
if (calleeName == cx->parserNames().UnsafeGetReservedSlot ||
|
||||
calleeName == cx->parserNames().UnsafeGetObjectFromReservedSlot ||
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
#include "frontend/ValueUsage.h" // ValueUsage
|
||||
#include "js/RootingAPI.h" // JS::Rooted, JS::Handle
|
||||
#include "js/TypeDecls.h" // jsbytecode
|
||||
#include "vm/BuiltinObjectKind.h" // BuiltinObjectKind
|
||||
#include "vm/BytecodeUtil.h" // JSOp
|
||||
#include "vm/CheckIsObjectKind.h" // CheckIsObjectKind
|
||||
#include "vm/FunctionPrefixKind.h" // FunctionPrefixKind
|
||||
|
@ -404,6 +405,9 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
|
|||
// Helper to emit JSOp::CheckIsObj.
|
||||
MOZ_MUST_USE bool emitCheckIsObj(CheckIsObjectKind kind);
|
||||
|
||||
// Helper to emit JSOp::BuiltinObject.
|
||||
MOZ_MUST_USE bool emitBuiltinObject(BuiltinObjectKind kind);
|
||||
|
||||
// Push whether the value atop of the stack is non-undefined and non-null.
|
||||
MOZ_MUST_USE bool emitPushNotUndefinedOrNull();
|
||||
|
||||
|
@ -763,6 +767,8 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
|
|||
MOZ_MUST_USE bool emitSelfHostedHasOwn(BinaryNode* callNode);
|
||||
MOZ_MUST_USE bool emitSelfHostedToNumeric(BinaryNode* callNode);
|
||||
MOZ_MUST_USE bool emitSelfHostedToString(BinaryNode* callNode);
|
||||
MOZ_MUST_USE bool emitSelfHostedGetBuiltinConstructor(BinaryNode* callNode);
|
||||
MOZ_MUST_USE bool emitSelfHostedGetBuiltinPrototype(BinaryNode* callNode);
|
||||
#ifdef DEBUG
|
||||
MOZ_MUST_USE bool checkSelfHostedUnsafeGetReservedSlot(BinaryNode* callNode);
|
||||
MOZ_MUST_USE bool checkSelfHostedUnsafeSetReservedSlot(BinaryNode* callNode);
|
||||
|
@ -864,6 +870,9 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
|
|||
GCThingIndex atomIndex);
|
||||
|
||||
MOZ_MUST_USE bool allowSelfHostedIter(ParseNode* parseNode);
|
||||
|
||||
MOZ_MUST_USE bool emitSelfHostedGetBuiltinConstructorOrPrototype(
|
||||
BinaryNode* callNode, bool isConstructor);
|
||||
};
|
||||
|
||||
class MOZ_RAII AutoCheckUnstableEmitterScope {
|
||||
|
|
|
@ -565,7 +565,7 @@ bool ClassEmitter::emitDerivedClass(JS::Handle<JSAtom*> name,
|
|||
// [stack]
|
||||
return false;
|
||||
}
|
||||
if (!bce_->emit1(JSOp::FunctionProto)) {
|
||||
if (!bce_->emitBuiltinObject(BuiltinObjectKind::FunctionPrototype)) {
|
||||
// [stack] PROTO
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
let getCtor = getSelfHostedValue('GetBuiltinConstructor');
|
||||
|
||||
assertEq(getCtor('Array'), Array);
|
||||
|
||||
let origArray = Array;
|
||||
Array = function(){};
|
||||
assertEq(getCtor('Array') == Array, false);
|
||||
assertEq(getCtor('Array'), origArray);
|
||||
|
||||
let origMap = Map;
|
||||
Map = function(){};
|
||||
assertEq(getCtor('Map') == Map, false);
|
||||
assertEq(getCtor('Map'), origMap);
|
|
@ -24,6 +24,7 @@
|
|||
#include "js/UniquePtr.h"
|
||||
#include "vm/AsyncFunction.h"
|
||||
#include "vm/AsyncIteration.h"
|
||||
#include "vm/BuiltinObjectKind.h"
|
||||
#include "vm/EnvironmentObject.h"
|
||||
#include "vm/FunctionFlags.h" // js::FunctionFlags
|
||||
#include "vm/Interpreter.h"
|
||||
|
@ -6386,22 +6387,25 @@ bool BaselineCodeGen<Handler>::emit_InitHomeObject() {
|
|||
}
|
||||
|
||||
template <>
|
||||
bool BaselineCompilerCodeGen::emit_FunctionProto() {
|
||||
// The function prototype is a constant for a given global.
|
||||
JSObject* funProto = FunctionProtoOperation(cx);
|
||||
if (!funProto) {
|
||||
bool BaselineCompilerCodeGen::emit_BuiltinObject() {
|
||||
// Built-in objects are constants for a given global.
|
||||
auto kind = BuiltinObjectKind(GET_UINT8(handler.pc()));
|
||||
JSObject* builtin = BuiltinObjectOperation(cx, kind);
|
||||
if (!builtin) {
|
||||
return false;
|
||||
}
|
||||
frame.push(ObjectValue(*funProto));
|
||||
frame.push(ObjectValue(*builtin));
|
||||
return true;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool BaselineInterpreterCodeGen::emit_FunctionProto() {
|
||||
bool BaselineInterpreterCodeGen::emit_BuiltinObject() {
|
||||
prepareVMCall();
|
||||
|
||||
using Fn = JSObject* (*)(JSContext*);
|
||||
if (!callVM<Fn, FunctionProtoOperation>()) {
|
||||
pushUint8BytecodeOperandArg(R0.scratchReg());
|
||||
|
||||
using Fn = JSObject* (*)(JSContext*, BuiltinObjectKind);
|
||||
if (!callVM<Fn, BuiltinObjectOperation>()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
#include "vm/ArrayBufferViewObject.h"
|
||||
#include "vm/AsyncFunction.h"
|
||||
#include "vm/AsyncIteration.h"
|
||||
#include "vm/BuiltinObjectKind.h"
|
||||
#include "vm/EqualityOperations.h" // js::SameValue
|
||||
#include "vm/FunctionFlags.h" // js::FunctionFlags
|
||||
#include "vm/MatchPairs.h"
|
||||
|
@ -14454,9 +14455,11 @@ void CodeGenerator::visitObjectStaticProto(LObjectStaticProto* lir) {
|
|||
#endif
|
||||
}
|
||||
|
||||
void CodeGenerator::visitFunctionProto(LFunctionProto* lir) {
|
||||
using Fn = JSObject* (*)(JSContext*);
|
||||
callVM<Fn, js::FunctionProtoOperation>(lir);
|
||||
void CodeGenerator::visitBuiltinObject(LBuiltinObject* lir) {
|
||||
pushArg(Imm32(static_cast<int32_t>(lir->mir()->builtinObjectKind())));
|
||||
|
||||
using Fn = JSObject* (*)(JSContext*, BuiltinObjectKind);
|
||||
callVM<Fn, js::BuiltinObjectOperation>(lir);
|
||||
}
|
||||
|
||||
void CodeGenerator::visitSuperFunction(LSuperFunction* lir) {
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include "js/ScalarType.h" // js::Scalar::Type
|
||||
#include "util/CheckedArithmetic.h"
|
||||
#include "vm/ArgumentsObject.h"
|
||||
#include "vm/BuiltinObjectKind.h"
|
||||
#include "vm/BytecodeIterator.h"
|
||||
#include "vm/BytecodeLocation.h"
|
||||
#include "vm/BytecodeUtil.h"
|
||||
|
@ -2386,8 +2387,8 @@ AbortReasonOr<Ok> IonBuilder::inspectOpcode(JSOp op, bool* restarted) {
|
|||
case JSOp::ObjWithProto:
|
||||
return jsop_objwithproto();
|
||||
|
||||
case JSOp::FunctionProto:
|
||||
return jsop_functionproto();
|
||||
case JSOp::BuiltinObject:
|
||||
return jsop_builtinobject();
|
||||
|
||||
case JSOp::CheckReturn:
|
||||
return jsop_checkreturn();
|
||||
|
@ -12700,17 +12701,17 @@ AbortReasonOr<Ok> IonBuilder::jsop_objwithproto() {
|
|||
return resumeAfter(ins);
|
||||
}
|
||||
|
||||
AbortReasonOr<Ok> IonBuilder::jsop_functionproto() {
|
||||
JSProtoKey key = JSProto_Function;
|
||||
AbortReasonOr<Ok> IonBuilder::jsop_builtinobject() {
|
||||
auto kind = BuiltinObjectKind(GET_UINT8(pc));
|
||||
|
||||
// Bake in the prototype if it exists.
|
||||
if (JSObject* proto = script()->global().maybeGetPrototype(key)) {
|
||||
pushConstant(ObjectValue(*proto));
|
||||
// Bake in the built-in if it exists.
|
||||
if (JSObject* builtin = MaybeGetBuiltinObject(&script()->global(), kind)) {
|
||||
pushConstant(ObjectValue(*builtin));
|
||||
return Ok();
|
||||
}
|
||||
|
||||
// Otherwise emit code to generate it.
|
||||
auto* ins = MFunctionProto::New(alloc());
|
||||
auto* ins = MBuiltinObject::New(alloc(), kind);
|
||||
current->add(ins);
|
||||
current->push(ins);
|
||||
return resumeAfter(ins);
|
||||
|
|
|
@ -672,7 +672,7 @@ class MOZ_STACK_CLASS IonBuilder {
|
|||
AbortReasonOr<Ok> jsop_instrumentation_scriptid();
|
||||
AbortReasonOr<Ok> jsop_coalesce();
|
||||
AbortReasonOr<Ok> jsop_objwithproto();
|
||||
AbortReasonOr<Ok> jsop_functionproto();
|
||||
AbortReasonOr<Ok> jsop_builtinobject();
|
||||
AbortReasonOr<Ok> jsop_checkreturn();
|
||||
AbortReasonOr<Ok> jsop_checkthis();
|
||||
AbortReasonOr<Ok> jsop_checkthisreinit();
|
||||
|
|
|
@ -5196,10 +5196,10 @@ void LIRGenerator::visitObjectStaticProto(MObjectStaticProto* ins) {
|
|||
define(lir, ins);
|
||||
};
|
||||
|
||||
void LIRGenerator::visitFunctionProto(MFunctionProto* ins) {
|
||||
void LIRGenerator::visitBuiltinObject(MBuiltinObject* ins) {
|
||||
MOZ_ASSERT(ins->type() == MIRType::Object);
|
||||
|
||||
auto* lir = new (alloc()) LFunctionProto();
|
||||
auto* lir = new (alloc()) LBuiltinObject();
|
||||
defineReturn(lir, ins);
|
||||
assignSafepoint(lir, ins);
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include "js/HeapAPI.h"
|
||||
#include "js/ScalarType.h" // js::Scalar::Type
|
||||
#include "vm/ArrayObject.h"
|
||||
#include "vm/BuiltinObjectKind.h"
|
||||
#include "vm/EnvironmentObject.h"
|
||||
#include "vm/FunctionFlags.h" // js::FunctionFlags
|
||||
#include "vm/RegExpObject.h"
|
||||
|
@ -11624,15 +11625,20 @@ class MObjectStaticProto : public MUnaryInstruction,
|
|||
}
|
||||
};
|
||||
|
||||
class MFunctionProto : public MNullaryInstruction {
|
||||
explicit MFunctionProto() : MNullaryInstruction(classOpcode) {
|
||||
class MBuiltinObject : public MNullaryInstruction {
|
||||
BuiltinObjectKind builtinObjectKind_;
|
||||
|
||||
explicit MBuiltinObject(BuiltinObjectKind kind)
|
||||
: MNullaryInstruction(classOpcode), builtinObjectKind_(kind) {
|
||||
setResultType(MIRType::Object);
|
||||
}
|
||||
|
||||
public:
|
||||
INSTRUCTION_HEADER(FunctionProto)
|
||||
INSTRUCTION_HEADER(BuiltinObject)
|
||||
TRIVIAL_NEW_WRAPPERS
|
||||
|
||||
BuiltinObjectKind builtinObjectKind() const { return builtinObjectKind_; }
|
||||
|
||||
bool possiblyCalls() const override { return true; }
|
||||
};
|
||||
|
||||
|
|
|
@ -74,6 +74,7 @@ namespace jit {
|
|||
_(BindVarOperation, js::BindVarOperation) \
|
||||
_(BoxBoxableValue, js::wasm::BoxBoxableValue) \
|
||||
_(BoxNonStrictThis, js::BoxNonStrictThis) \
|
||||
_(BuiltinObjectOperation, js::BuiltinObjectOperation) \
|
||||
_(CallNativeGetter, js::jit::CallNativeGetter) \
|
||||
_(CallNativeGetterByValue, js::jit::CallNativeGetterByValue) \
|
||||
_(CallNativeSetter, js::jit::CallNativeSetter) \
|
||||
|
@ -126,7 +127,6 @@ namespace jit {
|
|||
_(FinishBoundFunctionInit, JSFunction::finishBoundFunctionInit) \
|
||||
_(FreshenLexicalEnv, js::jit::FreshenLexicalEnv) \
|
||||
_(FunWithProtoOperation, js::FunWithProtoOperation) \
|
||||
_(FunctionProtoOperation, js::FunctionProtoOperation) \
|
||||
_(GeneratorThrowOrReturn, js::jit::GeneratorThrowOrReturn) \
|
||||
_(GetAndClearException, js::GetAndClearException) \
|
||||
_(GetElementOperation, js::GetElementOperation) \
|
||||
|
|
|
@ -2213,14 +2213,15 @@ bool WarpBuilder::build_SuperFun(BytecodeLocation) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool WarpBuilder::build_FunctionProto(BytecodeLocation loc) {
|
||||
if (auto* snapshot = getOpSnapshot<WarpFunctionProto>(loc)) {
|
||||
JSObject* proto = snapshot->proto();
|
||||
pushConstant(ObjectValue(*proto));
|
||||
bool WarpBuilder::build_BuiltinObject(BytecodeLocation loc) {
|
||||
if (auto* snapshot = getOpSnapshot<WarpBuiltinObject>(loc)) {
|
||||
JSObject* builtin = snapshot->builtin();
|
||||
pushConstant(ObjectValue(*builtin));
|
||||
return true;
|
||||
}
|
||||
|
||||
auto* ins = MFunctionProto::New(alloc());
|
||||
auto kind = loc.getBuiltinObjectKind();
|
||||
auto* ins = MBuiltinObject::New(alloc(), kind);
|
||||
current->add(ins);
|
||||
current->push(ins);
|
||||
return resumeAfter(ins, loc);
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "jit/MIRGenerator.h"
|
||||
#include "jit/WarpBuilder.h"
|
||||
#include "jit/WarpCacheIRTranspiler.h"
|
||||
#include "vm/BuiltinObjectKind.h"
|
||||
#include "vm/BytecodeIterator.h"
|
||||
#include "vm/BytecodeLocation.h"
|
||||
#include "vm/EnvironmentObject.h"
|
||||
|
@ -331,11 +332,11 @@ AbortReasonOr<WarpScriptSnapshot*> WarpScriptOracle::createScriptSnapshot() {
|
|||
}
|
||||
break;
|
||||
|
||||
case JSOp::FunctionProto: {
|
||||
// If we already resolved this proto we can bake it in.
|
||||
if (JSObject* proto =
|
||||
cx_->global()->maybeGetPrototype(JSProto_Function)) {
|
||||
if (!AddOpSnapshot<WarpFunctionProto>(alloc_, opSnapshots, offset,
|
||||
case JSOp::BuiltinObject: {
|
||||
// If we already resolved this built-in we can bake it in.
|
||||
auto kind = loc.getBuiltinObjectKind();
|
||||
if (JSObject* proto = MaybeGetBuiltinObject(cx_->global(), kind)) {
|
||||
if (!AddOpSnapshot<WarpBuiltinObject>(alloc_, opSnapshots, offset,
|
||||
proto)) {
|
||||
return abort(AbortReason::Alloc);
|
||||
}
|
||||
|
|
|
@ -133,8 +133,8 @@ void WarpRegExp::dumpData(GenericPrinter& out) const {
|
|||
out.printf(" hasShared: %u\n", hasShared());
|
||||
}
|
||||
|
||||
void WarpFunctionProto::dumpData(GenericPrinter& out) const {
|
||||
out.printf(" proto: 0x%p\n", proto());
|
||||
void WarpBuiltinObject::dumpData(GenericPrinter& out) const {
|
||||
out.printf(" builtin: 0x%p\n", builtin());
|
||||
}
|
||||
|
||||
void WarpGetIntrinsic::dumpData(GenericPrinter& out) const {
|
||||
|
@ -267,8 +267,8 @@ void WarpRegExp::traceData(JSTracer* trc) {
|
|||
// No GC pointers.
|
||||
}
|
||||
|
||||
void WarpFunctionProto::traceData(JSTracer* trc) {
|
||||
TraceWarpGCPtr(trc, proto_, "warp-function-proto");
|
||||
void WarpBuiltinObject::traceData(JSTracer* trc) {
|
||||
TraceWarpGCPtr(trc, builtin_, "warp-builtin-object");
|
||||
}
|
||||
|
||||
void WarpGetIntrinsic::traceData(JSTracer* trc) {
|
||||
|
|
|
@ -29,7 +29,7 @@ class WarpScriptSnapshot;
|
|||
#define WARP_OP_SNAPSHOT_LIST(_) \
|
||||
_(WarpArguments) \
|
||||
_(WarpRegExp) \
|
||||
_(WarpFunctionProto) \
|
||||
_(WarpBuiltinObject) \
|
||||
_(WarpGetIntrinsic) \
|
||||
_(WarpGetImport) \
|
||||
_(WarpLambda) \
|
||||
|
@ -147,18 +147,18 @@ class WarpRegExp : public WarpOpSnapshot {
|
|||
#endif
|
||||
};
|
||||
|
||||
// The proto for JSOp::FunctionProto if it exists at compile-time.
|
||||
class WarpFunctionProto : public WarpOpSnapshot {
|
||||
WarpGCPtr<JSObject*> proto_;
|
||||
// The object for JSOp::BuiltinObject if it exists at compile-time.
|
||||
class WarpBuiltinObject : public WarpOpSnapshot {
|
||||
WarpGCPtr<JSObject*> builtin_;
|
||||
|
||||
public:
|
||||
static constexpr Kind ThisKind = Kind::WarpFunctionProto;
|
||||
static constexpr Kind ThisKind = Kind::WarpBuiltinObject;
|
||||
|
||||
WarpFunctionProto(uint32_t offset, JSObject* proto)
|
||||
: WarpOpSnapshot(ThisKind, offset), proto_(proto) {
|
||||
MOZ_ASSERT(proto);
|
||||
WarpBuiltinObject(uint32_t offset, JSObject* builtin)
|
||||
: WarpOpSnapshot(ThisKind, offset), builtin_(builtin) {
|
||||
MOZ_ASSERT(builtin);
|
||||
}
|
||||
JSObject* proto() const { return proto_; }
|
||||
JSObject* builtin() const { return builtin_; }
|
||||
|
||||
void traceData(JSTracer* trc);
|
||||
|
||||
|
|
|
@ -7716,13 +7716,13 @@ class LObjectStaticProto : public LInstructionHelper<1, 1, 0> {
|
|||
}
|
||||
};
|
||||
|
||||
class LFunctionProto : public LCallInstructionHelper<1, 0, 0> {
|
||||
class LBuiltinObject : public LCallInstructionHelper<1, 0, 0> {
|
||||
public:
|
||||
LIR_HEADER(FunctionProto)
|
||||
LIR_HEADER(BuiltinObject)
|
||||
|
||||
LFunctionProto() : LCallInstructionHelper(classOpcode) {}
|
||||
LBuiltinObject() : LCallInstructionHelper(classOpcode) {}
|
||||
|
||||
MFunctionProto* mir() const { return mir_->toFunctionProto(); }
|
||||
MBuiltinObject* mir() const { return mir_->toBuiltinObject(); }
|
||||
};
|
||||
|
||||
class LSuperFunction : public LInstructionHelper<BOX_PIECES, 1, 1> {
|
||||
|
|
|
@ -320,6 +320,7 @@ UNIFIED_SOURCES += [
|
|||
'vm/AsyncIteration.cpp',
|
||||
'vm/BigIntType.cpp',
|
||||
'vm/BuildId.cpp',
|
||||
'vm/BuiltinObjectKind.cpp',
|
||||
'vm/BytecodeLocation.cpp',
|
||||
'vm/BytecodeUtil.cpp',
|
||||
'vm/Caches.cpp',
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "vm/BuiltinObjectKind.h"
|
||||
|
||||
#include "jspubtd.h"
|
||||
|
||||
#include "vm/GlobalObject.h"
|
||||
#include "vm/JSContext.h"
|
||||
|
||||
using namespace js;
|
||||
|
||||
static JSProtoKey ToProtoKey(BuiltinObjectKind kind) {
|
||||
switch (kind) {
|
||||
case BuiltinObjectKind::Array:
|
||||
return JSProto_Array;
|
||||
case BuiltinObjectKind::ArrayBuffer:
|
||||
return JSProto_ArrayBuffer;
|
||||
case BuiltinObjectKind::Iterator:
|
||||
return JSProto_Iterator;
|
||||
case BuiltinObjectKind::Promise:
|
||||
return JSProto_Promise;
|
||||
case BuiltinObjectKind::RegExp:
|
||||
return JSProto_RegExp;
|
||||
case BuiltinObjectKind::SharedArrayBuffer:
|
||||
return JSProto_SharedArrayBuffer;
|
||||
|
||||
case BuiltinObjectKind::FunctionPrototype:
|
||||
return JSProto_Function;
|
||||
case BuiltinObjectKind::ObjectPrototype:
|
||||
return JSProto_Object;
|
||||
case BuiltinObjectKind::RegExpPrototype:
|
||||
return JSProto_RegExp;
|
||||
case BuiltinObjectKind::StringPrototype:
|
||||
return JSProto_String;
|
||||
|
||||
case BuiltinObjectKind::DateTimeFormat:
|
||||
return JSProto_DateTimeFormat;
|
||||
case BuiltinObjectKind::NumberFormat:
|
||||
return JSProto_NumberFormat;
|
||||
|
||||
case BuiltinObjectKind::None:
|
||||
break;
|
||||
}
|
||||
MOZ_CRASH("Unexpected builtin object kind");
|
||||
}
|
||||
|
||||
static bool IsPrototype(BuiltinObjectKind kind) {
|
||||
switch (kind) {
|
||||
case BuiltinObjectKind::Array:
|
||||
case BuiltinObjectKind::ArrayBuffer:
|
||||
case BuiltinObjectKind::Iterator:
|
||||
case BuiltinObjectKind::Promise:
|
||||
case BuiltinObjectKind::RegExp:
|
||||
case BuiltinObjectKind::SharedArrayBuffer:
|
||||
return false;
|
||||
|
||||
case BuiltinObjectKind::FunctionPrototype:
|
||||
case BuiltinObjectKind::ObjectPrototype:
|
||||
case BuiltinObjectKind::RegExpPrototype:
|
||||
case BuiltinObjectKind::StringPrototype:
|
||||
return true;
|
||||
|
||||
case BuiltinObjectKind::DateTimeFormat:
|
||||
case BuiltinObjectKind::NumberFormat:
|
||||
return false;
|
||||
|
||||
case BuiltinObjectKind::None:
|
||||
break;
|
||||
}
|
||||
MOZ_CRASH("Unexpected builtin object kind");
|
||||
}
|
||||
|
||||
using BuiltinName = js::ImmutablePropertyNamePtr JSAtomState::*;
|
||||
|
||||
struct BuiltinObjectMap {
|
||||
BuiltinName name;
|
||||
BuiltinObjectKind kind;
|
||||
};
|
||||
|
||||
BuiltinObjectKind js::BuiltinConstructorForName(JSContext* cx, JSAtom* name) {
|
||||
static constexpr BuiltinObjectMap constructors[] = {
|
||||
{&JSAtomState::Array, BuiltinObjectKind::Array},
|
||||
{&JSAtomState::ArrayBuffer, BuiltinObjectKind::ArrayBuffer},
|
||||
{&JSAtomState::Iterator, BuiltinObjectKind::Iterator},
|
||||
{&JSAtomState::Promise, BuiltinObjectKind::Promise},
|
||||
{&JSAtomState::RegExp, BuiltinObjectKind::RegExp},
|
||||
{&JSAtomState::SharedArrayBuffer, BuiltinObjectKind::SharedArrayBuffer},
|
||||
{&JSAtomState::DateTimeFormat, BuiltinObjectKind::DateTimeFormat},
|
||||
{&JSAtomState::NumberFormat, BuiltinObjectKind::NumberFormat},
|
||||
};
|
||||
|
||||
for (auto& builtin : constructors) {
|
||||
if (name == cx->names().*(builtin.name)) {
|
||||
return builtin.kind;
|
||||
}
|
||||
}
|
||||
return BuiltinObjectKind::None;
|
||||
}
|
||||
|
||||
BuiltinObjectKind js::BuiltinPrototypeForName(JSContext* cx, JSAtom* name) {
|
||||
static constexpr BuiltinObjectMap prototypes[] = {
|
||||
{&JSAtomState::Function, BuiltinObjectKind::FunctionPrototype},
|
||||
{&JSAtomState::Object, BuiltinObjectKind::ObjectPrototype},
|
||||
{&JSAtomState::RegExp, BuiltinObjectKind::RegExpPrototype},
|
||||
{&JSAtomState::String, BuiltinObjectKind::StringPrototype},
|
||||
};
|
||||
|
||||
for (auto& builtin : prototypes) {
|
||||
if (name == cx->names().*(builtin.name)) {
|
||||
return builtin.kind;
|
||||
}
|
||||
}
|
||||
return BuiltinObjectKind::None;
|
||||
}
|
||||
|
||||
JSObject* js::MaybeGetBuiltinObject(GlobalObject* global,
|
||||
BuiltinObjectKind kind) {
|
||||
JSProtoKey key = ToProtoKey(kind);
|
||||
if (IsPrototype(kind)) {
|
||||
return global->maybeGetPrototype(key);
|
||||
}
|
||||
return global->maybeGetConstructor(key);
|
||||
}
|
||||
|
||||
JSObject* js::GetOrCreateBuiltinObject(JSContext* cx, BuiltinObjectKind kind) {
|
||||
JSProtoKey key = ToProtoKey(kind);
|
||||
if (IsPrototype(kind)) {
|
||||
return GlobalObject::getOrCreatePrototype(cx, key);
|
||||
}
|
||||
return GlobalObject::getOrCreateConstructor(cx, key);
|
||||
}
|
||||
|
||||
const char* js::BuiltinObjectName(BuiltinObjectKind kind) {
|
||||
switch (kind) {
|
||||
case BuiltinObjectKind::Array:
|
||||
return "Array";
|
||||
case BuiltinObjectKind::ArrayBuffer:
|
||||
return "ArrayBuffer";
|
||||
case BuiltinObjectKind::Iterator:
|
||||
return "Iterator";
|
||||
case BuiltinObjectKind::Promise:
|
||||
return "Promise";
|
||||
case BuiltinObjectKind::RegExp:
|
||||
return "RegExp";
|
||||
case BuiltinObjectKind::SharedArrayBuffer:
|
||||
return "SharedArrayBuffer";
|
||||
|
||||
case BuiltinObjectKind::FunctionPrototype:
|
||||
return "Function.prototype";
|
||||
case BuiltinObjectKind::ObjectPrototype:
|
||||
return "Object.prototype";
|
||||
case BuiltinObjectKind::RegExpPrototype:
|
||||
return "RegExp.prototype";
|
||||
case BuiltinObjectKind::StringPrototype:
|
||||
return "String.prototype";
|
||||
|
||||
case BuiltinObjectKind::DateTimeFormat:
|
||||
return "DateTimeFormat";
|
||||
case BuiltinObjectKind::NumberFormat:
|
||||
return "NumberFormat";
|
||||
|
||||
case BuiltinObjectKind::None:
|
||||
break;
|
||||
}
|
||||
MOZ_CRASH("Unexpected builtin object kind");
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef vm_BuiltinObjectKind_h
|
||||
#define vm_BuiltinObjectKind_h
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "jstypes.h"
|
||||
|
||||
class JS_PUBLIC_API JSAtom;
|
||||
struct JS_PUBLIC_API JSContext;
|
||||
class JS_PUBLIC_API JSObject;
|
||||
|
||||
namespace js {
|
||||
|
||||
class GlobalObject;
|
||||
|
||||
/**
|
||||
* Built-in objects used by the GetBuiltinConstructor and GetBuiltinPrototype
|
||||
* self-hosted intrinsics.
|
||||
*/
|
||||
enum class BuiltinObjectKind : uint8_t {
|
||||
// Built-in constructors.
|
||||
Array,
|
||||
ArrayBuffer,
|
||||
Iterator,
|
||||
Promise,
|
||||
RegExp,
|
||||
SharedArrayBuffer,
|
||||
|
||||
// Built-in prototypes.
|
||||
FunctionPrototype,
|
||||
ObjectPrototype,
|
||||
RegExpPrototype,
|
||||
StringPrototype,
|
||||
|
||||
// Built-in Intl constructors.
|
||||
DateTimeFormat,
|
||||
NumberFormat,
|
||||
|
||||
// Invalid placeholder.
|
||||
None,
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the BuiltinObjectKind for the given constructor name. Return
|
||||
* BuiltinObjectKind::None if no matching constructor was found.
|
||||
*/
|
||||
BuiltinObjectKind BuiltinConstructorForName(JSContext* cx, JSAtom* name);
|
||||
|
||||
/**
|
||||
* Return the BuiltinObjectKind for the given prototype name. Return
|
||||
* BuiltinObjectKind::None if no matching prototype was found.
|
||||
*/
|
||||
BuiltinObjectKind BuiltinPrototypeForName(JSContext* cx, JSAtom* name);
|
||||
|
||||
/**
|
||||
* Return the built-in object if already created for the given global. Otherwise
|
||||
* return nullptr.
|
||||
*/
|
||||
JSObject* MaybeGetBuiltinObject(GlobalObject* global, BuiltinObjectKind kind);
|
||||
|
||||
/**
|
||||
* Return the built-in object for the given global.
|
||||
*/
|
||||
JSObject* GetOrCreateBuiltinObject(JSContext* cx, BuiltinObjectKind kind);
|
||||
|
||||
/**
|
||||
* Return the display name for a built-in object.
|
||||
*/
|
||||
const char* BuiltinObjectName(BuiltinObjectKind kind);
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif /* vm_BuiltinObjectKind_h */
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include "frontend/NameAnalysisTypes.h"
|
||||
#include "js/TypeDecls.h"
|
||||
#include "vm/BuiltinObjectKind.h"
|
||||
#include "vm/BytecodeUtil.h"
|
||||
#include "vm/CheckIsObjectKind.h" // CheckIsObjectKind
|
||||
#include "vm/FunctionPrefixKind.h" // FunctionPrefixKind
|
||||
|
@ -279,6 +280,11 @@ class BytecodeLocation {
|
|||
return CheckIsObjectKind(GET_UINT8(rawBytecode_));
|
||||
}
|
||||
|
||||
BuiltinObjectKind getBuiltinObjectKind() const {
|
||||
MOZ_ASSERT(is(JSOp::BuiltinObject));
|
||||
return BuiltinObjectKind(GET_UINT8(rawBytecode_));
|
||||
}
|
||||
|
||||
uint32_t getNewArrayLength() const {
|
||||
MOZ_ASSERT(is(JSOp::NewArray));
|
||||
return GET_UINT32(rawBytecode_);
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
#include "util/Memory.h"
|
||||
#include "util/StringBuffer.h"
|
||||
#include "util/Text.h"
|
||||
#include "vm/BuiltinObjectKind.h"
|
||||
#include "vm/BytecodeLocation.h"
|
||||
#include "vm/CodeCoverage.h"
|
||||
#include "vm/EnvironmentObject.h"
|
||||
|
@ -2004,6 +2005,11 @@ bool ExpressionDecompiler::decompilePC(jsbytecode* pc, uint8_t defIndex) {
|
|||
return write("[bigint]");
|
||||
#endif
|
||||
|
||||
case JSOp::BuiltinObject: {
|
||||
auto kind = BuiltinObjectKind(GET_UINT8(pc));
|
||||
return write(BuiltinObjectName(kind));
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -183,6 +183,8 @@
|
|||
MACRO(GeneratorThrow, GeneratorThrow, "GeneratorThrow") \
|
||||
MACRO(get, get, "get") \
|
||||
MACRO(GetAggregateError, GetAggregateError, "GetAggregateError") \
|
||||
MACRO(GetBuiltinConstructor, GetBuiltinConstructor, "GetBuiltinConstructor") \
|
||||
MACRO(GetBuiltinPrototype, GetBuiltinPrototype, "GetBuiltinPrototype") \
|
||||
MACRO(GetInternalError, GetInternalError, "GetInternalError") \
|
||||
MACRO(getBigInt64, getBigInt64, "getBigInt64") \
|
||||
MACRO(getBigUint64, getBigUint64, "getBigUint64") \
|
||||
|
|
|
@ -201,6 +201,13 @@ class GlobalObject : public NativeObject {
|
|||
return &global->getPrototype(key).toObject();
|
||||
}
|
||||
|
||||
JSObject* maybeGetConstructor(JSProtoKey protoKey) const {
|
||||
MOZ_ASSERT(JSProto_Null < protoKey);
|
||||
MOZ_ASSERT(protoKey < JSProto_LIMIT);
|
||||
const Value& v = getConstructor(protoKey);
|
||||
return v.isObject() ? &v.toObject() : nullptr;
|
||||
}
|
||||
|
||||
JSObject* maybeGetPrototype(JSProtoKey protoKey) const {
|
||||
MOZ_ASSERT(JSProto_Null < protoKey);
|
||||
MOZ_ASSERT(protoKey < JSProto_LIMIT);
|
||||
|
|
|
@ -4378,14 +4378,15 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_CALLER bool Interpret(JSContext* cx,
|
|||
}
|
||||
END_CASE(CheckClassHeritage)
|
||||
|
||||
CASE(FunctionProto) {
|
||||
JSObject* builtin = FunctionProtoOperation(cx);
|
||||
CASE(BuiltinObject) {
|
||||
auto kind = BuiltinObjectKind(GET_UINT8(REGS.pc));
|
||||
JSObject* builtin = BuiltinObjectOperation(cx, kind);
|
||||
if (!builtin) {
|
||||
goto error;
|
||||
}
|
||||
PUSH_OBJECT(*builtin);
|
||||
}
|
||||
END_CASE(FunctionProto)
|
||||
END_CASE(BuiltinObject)
|
||||
|
||||
CASE(FunWithProto) {
|
||||
ReservedRooted<JSObject*> proto(&rootObject1, ®S.sp[-1].toObject());
|
||||
|
@ -4961,8 +4962,8 @@ JSObject* js::ImportMetaOperation(JSContext* cx, HandleScript script) {
|
|||
return GetOrCreateModuleMetaObject(cx, module);
|
||||
}
|
||||
|
||||
JSObject* js::FunctionProtoOperation(JSContext* cx) {
|
||||
return GlobalObject::getOrCreatePrototype(cx, JSProto_Function);
|
||||
JSObject* js::BuiltinObjectOperation(JSContext* cx, BuiltinObjectKind kind) {
|
||||
return GetOrCreateBuiltinObject(cx, kind);
|
||||
}
|
||||
|
||||
bool js::ThrowMsgOperation(JSContext* cx, const unsigned throwMsgKind) {
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
#include "jspubtd.h"
|
||||
|
||||
#include "vm/BuiltinObjectKind.h"
|
||||
#include "vm/CheckIsObjectKind.h" // CheckIsObjectKind
|
||||
#include "vm/Iteration.h"
|
||||
#include "vm/Stack.h"
|
||||
|
@ -600,7 +601,7 @@ JSObject* SingletonObjectLiteralOperation(JSContext* cx, HandleScript script,
|
|||
|
||||
JSObject* ImportMetaOperation(JSContext* cx, HandleScript script);
|
||||
|
||||
JSObject* FunctionProtoOperation(JSContext* cx);
|
||||
JSObject* BuiltinObjectOperation(JSContext* cx, BuiltinObjectKind kind);
|
||||
|
||||
bool ThrowMsgOperation(JSContext* cx, const unsigned throwMsgKind);
|
||||
|
||||
|
|
|
@ -225,6 +225,7 @@
|
|||
* SetPrototype
|
||||
* Array literals
|
||||
* RegExp literals
|
||||
* Built-in objects
|
||||
* [Functions]
|
||||
* Creating functions
|
||||
* Creating constructors
|
||||
|
@ -1700,17 +1701,17 @@
|
|||
*/ \
|
||||
MACRO(DerivedConstructor, derived_constructor, NULL, 13, 1, 1, JOF_CLASS_CTOR) \
|
||||
/*
|
||||
* Pushes the current global's FunctionPrototype.
|
||||
* Pushes the current global's %BuiltinObject%.
|
||||
*
|
||||
* `kind` must be in range for `JSProtoKey` (and must not be
|
||||
* `JSProto_LIMIT`).
|
||||
* `kind` must be a valid `BuiltinObjectKind` (and must not be
|
||||
* `BuiltinObjectKind::None`).
|
||||
*
|
||||
* Category: Functions
|
||||
* Type: Creating constructors
|
||||
* Operands:
|
||||
* Stack: => %FunctionPrototype%
|
||||
* Category: Objects
|
||||
* Type: Built-in objects
|
||||
* Operands: uint8_t kind
|
||||
* Stack: => %BuiltinObject%
|
||||
*/ \
|
||||
MACRO(FunctionProto, function_proto, NULL, 1, 0, 1, JOF_BYTE) \
|
||||
MACRO(BuiltinObject, builtin_object, NULL, 2, 0, 1, JOF_UINT8) \
|
||||
/*
|
||||
* Invoke `callee` with `this` and `args`, and push the return value. Throw
|
||||
* a TypeError if `callee` isn't a function.
|
||||
|
|
|
@ -293,42 +293,6 @@ static bool intrinsic_IsPossiblyWrappedInstanceOfBuiltin(JSContext* cx,
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Self-hosting intrinsic returning the original constructor for a builtin
|
||||
* the name of which is the first and only argument.
|
||||
*
|
||||
* The return value is guaranteed to be the original constructor even if
|
||||
* content code changed the named binding on the global object.
|
||||
*
|
||||
* This intrinsic shouldn't be called directly. Instead, the
|
||||
* `GetBuiltinConstructor` and `GetBuiltinPrototype` helper functions in
|
||||
* Utilities.js should be used, as they cache results, improving performance.
|
||||
*/
|
||||
static bool intrinsic_GetBuiltinConstructor(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
MOZ_ASSERT(args.length() == 1);
|
||||
RootedString str(cx, args[0].toString());
|
||||
JSAtom* atom;
|
||||
if (str->isAtom()) {
|
||||
atom = &str->asAtom();
|
||||
} else {
|
||||
atom = AtomizeString(cx, str);
|
||||
if (!atom) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
RootedId id(cx, AtomToId(atom));
|
||||
JSProtoKey key = JS_IdToProtoKey(cx, id);
|
||||
MOZ_ASSERT(key != JSProto_Null);
|
||||
JSObject* ctor = GlobalObject::getOrCreateConstructor(cx, key);
|
||||
if (!ctor) {
|
||||
return false;
|
||||
}
|
||||
args.rval().setObject(*ctor);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool intrinsic_SubstringKernel(JSContext* cx, unsigned argc, Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
MOZ_ASSERT(args[0].isString());
|
||||
|
@ -2244,7 +2208,6 @@ static const JSFunctionSpec intrinsic_functions[] = {
|
|||
IntrinsicIsCallable),
|
||||
JS_INLINABLE_FN("IsConstructor", intrinsic_IsConstructor, 1, 0,
|
||||
IntrinsicIsConstructor),
|
||||
JS_FN("GetBuiltinConstructorImpl", intrinsic_GetBuiltinConstructor, 1, 0),
|
||||
JS_FN("MakeConstructible", intrinsic_MakeConstructible, 2, 0),
|
||||
JS_FN("_ConstructFunction", intrinsic_ConstructFunction, 2, 0),
|
||||
JS_FN("ThrowRangeError", intrinsic_ThrowRangeError, 4, 0),
|
||||
|
|
Загрузка…
Ссылка в новой задаче