escargot/src/runtime/FunctionObjectInlines.h
2023-08-18 18:36:46 +09:00

336 lines
16 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2019-present Samsung Electronics Co., Ltd
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
#ifndef __EscargotFunctionObjectInlines__
#define __EscargotFunctionObjectInlines__
// don't include this file except FunctionObject and descendants of FunctionObject
#include "runtime/Context.h"
#include "interpreter/ByteCode.h"
#include "interpreter/ByteCodeGenerator.h"
#include "interpreter/ByteCodeInterpreter.h"
#include "runtime/Environment.h"
#include "runtime/EnvironmentRecord.h"
#include "runtime/ArrayObject.h"
#include "runtime/GeneratorObject.h"
#include "runtime/AsyncGeneratorObject.h"
#include "runtime/ExecutionPauser.h"
#include "runtime/NativeFunctionObject.h"
namespace Escargot {
class ScriptGeneratorFunctionObject;
class ScriptAsyncFunctionObject;
// this is default version of ThisValueBinder
class FunctionObjectThisValueBinder {
public:
Value operator()(ExecutionState& callerState, ExecutionState& calleeState, FunctionObject* self, const Value& thisArgument, bool isStrict)
{
// OrdinaryCallBindThis ( F, calleeContext, thisArgument )
// Let thisMode be the value of Fs [[ThisMode]] internal slot.
// If thisMode is lexical, return NormalCompletion(undefined).
// --> thisMode is always not lexcial because this is class ctor.
// Let calleeRealm be the value of Fs [[Realm]] internal slot.
// Let localEnv be the LexicalEnvironment of calleeContext.
ASSERT(calleeState.context() == self->codeBlock()->context());
if (isStrict) {
// If thisMode is strict, let thisValue be thisArgument.
return thisArgument;
} else {
// Else
// if thisArgument is null or undefined, then
// Let thisValue be calleeRealm.[[globalThis]]
if (thisArgument.isUndefinedOrNull()) {
return calleeState.context()->globalObjectProxy();
} else {
// Else
// Let thisValue be ToObject(thisArgument).
// Assert: thisValue is not an abrupt completion.
// NOTE ToObject produces wrapper objects using calleeRealm.
return thisArgument.toObject(calleeState);
}
}
}
};
class FunctionObjectReturnValueBinder {
public:
Value operator()(ExecutionState& callerState, ExecutionState& calleeState, FunctionObject* self, const Value& interpreterReturnValue, const Value& thisArgument, FunctionEnvironmentRecord* record)
{
return interpreterReturnValue;
}
};
class FunctionObjectNewTargetBinder {
public:
void operator()(ExecutionState& callerState, ExecutionState& calleeState, FunctionObject* self, Object* newTarget, FunctionEnvironmentRecord* record)
{
}
};
class FunctionObjectProcessCallGenerator {
public:
template <typename FunctionObjectType, bool isConstructCall, bool hasNewTargetOnEnvironment, bool canBindThisValueOnEnvironment, typename ThisValueBinder, typename NewTargetBinder, typename ReturnValueBinder>
static ALWAYS_INLINE Value processCall(ExecutionState& state, FunctionObjectType* self, const Value& thisArgument, const size_t argc, Value* argv, Object* newTarget) // newTarget is null on [[call]]
{
CHECK_STACK_OVERFLOW(state);
ASSERT(self->codeBlock()->isInterpretedCodeBlock());
InterpretedCodeBlock* codeBlock = self->interpretedCodeBlock();
// prepare ByteCodeBlock if needed
if (UNLIKELY(codeBlock->byteCodeBlock() == nullptr)) {
self->generateByteCodeBlock(state);
}
ByteCodeBlock* blk = codeBlock->byteCodeBlock();
Context* ctx = codeBlock->context();
bool isStrict = codeBlock->isStrict();
const size_t registerFileSize = blk->m_requiredTotalRegisterNumber;
const size_t generalRegisterSize = blk->m_requiredOperandRegisterNumber;
// prepare env, ec
FunctionEnvironmentRecord* record;
LexicalEnvironment* lexEnv;
if (LIKELY(codeBlock->canAllocateEnvironmentOnStack())) {
// no capture, very simple case
record = new (alloca(sizeof(FunctionEnvironmentRecordOnStack<canBindThisValueOnEnvironment, hasNewTargetOnEnvironment>))) FunctionEnvironmentRecordOnStack<canBindThisValueOnEnvironment, hasNewTargetOnEnvironment>(self);
lexEnv = new (alloca(sizeof(LexicalEnvironment))) LexicalEnvironment(record, self->outerEnvironment()
#ifndef NDEBUG
,
false
#endif
);
} else {
record = createFunctionEnvironmentRecord<FunctionObjectType, hasNewTargetOnEnvironment, canBindThisValueOnEnvironment>(state, self, codeBlock);
lexEnv = new LexicalEnvironment(record, self->outerEnvironment());
}
Value* registerFile;
if (std::is_same<FunctionObjectType, ScriptGeneratorFunctionObject>::value || std::is_same<FunctionObjectType, ScriptAsyncFunctionObject>::value || std::is_same<FunctionObjectType, ScriptAsyncGeneratorFunctionObject>::value) {
registerFile = (Value*)CustomAllocator<Value>().allocate(registerFileSize);
} else {
registerFile = (Value*)alloca((registerFileSize) * sizeof(Value));
}
Value* stackStorage = registerFile + generalRegisterSize;
ThisValueBinder thisValueBinder;
if (std::is_same<FunctionObjectType, ScriptGeneratorFunctionObject>::value || std::is_same<FunctionObjectType, ScriptAsyncGeneratorFunctionObject>::value) {
Value* arguments = CustomAllocator<Value>().allocate(argc);
memcpy(arguments, argv, sizeof(Value) * argc);
ExecutionState* newState = new ExecutionState(ctx, nullptr, lexEnv, argc, arguments, isStrict, ExecutionState::ForPauser);
// prepare receiver(this variable)
// we should use newState because
// https://www.ecma-international.org/ecma-262/6.0/#sec-ordinarycallbindthis
// NOTE ToObject produces wrapper objects using calleeRealm. <<----
stackStorage[0] = thisValueBinder(state, *newState, self, thisArgument, isStrict);
// binding function name
stackStorage[1] = self;
if (isConstructCall) {
NewTargetBinder newTargetBinder;
newTargetBinder(state, *newState, self, newTarget, record);
}
// https://www.ecma-international.org/ecma-262/10.0/#sec-ordinarycreatefromconstructor
// OrdinaryCreateFromConstructor ( constructor, intrinsicDefaultProto [ , internalSlotsList ] )
// Assert: intrinsicDefaultProto is a String value that is this specification's name of an intrinsic object. The corresponding object must be an intrinsic that is intended to be used as the [[Prototype]] value of an object.
// let proto be ? GetPrototypeFromConstructor(constructor, intrinsicDefaultProto).
Object* proto = nullptr;
if (std::is_same<FunctionObjectType, ScriptGeneratorFunctionObject>::value) {
proto = Object::getPrototypeFromConstructor(state, self, [](ExecutionState& state, Context* constructorRealm) -> Object* {
return constructorRealm->globalObject()->generatorPrototype();
});
} else {
proto = Object::getPrototypeFromConstructor(state, self, [](ExecutionState& state, Context* constructorRealm) -> Object* {
return constructorRealm->globalObject()->asyncGeneratorPrototype();
});
}
RETURN_VALUE_IF_PENDING_EXCEPTION
// Return ObjectCreate(proto, internalSlotsList).
Object* generatorObject;
if (std::is_same<FunctionObjectType, ScriptGeneratorFunctionObject>::value) {
GeneratorObject* gen = new GeneratorObject(state, proto, newState, registerFile, blk);
newState->setPauseSource(gen->executionPauser());
ExecutionPauser::start(state, newState->pauseSource(), newState->pauseSource()->sourceObject(), Value(), false, false, ExecutionPauser::StartFrom::Generator);
generatorObject = gen;
} else {
AsyncGeneratorObject* gen = new AsyncGeneratorObject(state, proto, newState, registerFile, blk);
newState->setPauseSource(gen->executionPauser());
newState->pauseSource()->m_promiseCapability = PromiseObject::newPromiseCapability(*newState, newState->context()->globalObject()->promise());
ASSERT(!newState->hasPendingException());
ExecutionPauser::start(state, newState->pauseSource(), newState->pauseSource()->sourceObject(), Value(), false, false, ExecutionPauser::StartFrom::AsyncGenerator);
generatorObject = gen;
}
RETURN_VALUE_IF_PENDING_EXCEPTION
return generatorObject;
}
ExecutionState* newState;
if (std::is_same<FunctionObjectType, ScriptAsyncFunctionObject>::value) {
newState = new ExecutionState(ctx, nullptr, lexEnv, argc, argv, isStrict, ExecutionState::ForPauser);
newState->setPauseSource(new ExecutionPauser(state, self, newState, registerFile, blk));
newState->pauseSource()->m_promiseCapability = PromiseObject::newPromiseCapability(*newState, newState->context()->globalObject()->promise());
ASSERT(!newState->hasPendingException());
} else {
newState = new (alloca(sizeof(ExecutionState))) ExecutionState(ctx, &state, lexEnv, argc, argv, isStrict);
}
// prepare receiver(this variable)
// we should use newState because
// https://www.ecma-international.org/ecma-262/6.0/#sec-ordinarycallbindthis
// NOTE ToObject produces wrapper objects using calleeRealm. <<----
stackStorage[0] = thisValueBinder(state, *newState, self, thisArgument, isStrict);
// check thisValueBinder result
if (std::is_same<FunctionObjectType, ScriptClassConstructorFunctionObject>::value) {
if (UNLIKELY(newState->hasPendingException())) {
ASSERT(stackStorage[0].isException());
state.setPendingException();
return Value(Value::Exception);
}
}
if (isConstructCall) {
NewTargetBinder newTargetBinder;
newTargetBinder(state, *newState, self, newTarget, record);
}
// run function
ReturnValueBinder returnValueBinder;
const Value returnValue = returnValueBinder(state, *newState, self,
std::is_same<FunctionObjectType, ScriptAsyncFunctionObject>::value ? ExecutionPauser::start(state, newState->pauseSource(), newState->pauseSource()->sourceObject(), Value(), false, false, ExecutionPauser::StartFrom::Async)
: Interpreter::interpret(newState, blk, reinterpret_cast<const size_t>(blk->m_code.data()), registerFile),
thisArgument, record);
// check Exception
if (UNLIKELY(newState->hasPendingException())) {
ASSERT(returnValue.isException());
state.setPendingException();
}
if (UNLIKELY(blk->m_shouldClearStack)) {
clearStack<512>();
}
return returnValue;
}
private:
template <typename FunctionObjectType, bool hasNewTargetOnEnvironment, bool canBindThisValueOnEnvironment>
static NEVER_INLINE FunctionEnvironmentRecord* createFunctionEnvironmentRecord(ExecutionState& state, FunctionObjectType* self, InterpretedCodeBlock* codeBlock)
{
if (LIKELY(codeBlock->canUseIndexedVariableStorage())) {
switch (codeBlock->identifierOnHeapCount()) {
case 1:
return new FunctionEnvironmentRecordOnHeapWithInlineStorage<canBindThisValueOnEnvironment, hasNewTargetOnEnvironment, 1>(self);
case 2:
return new FunctionEnvironmentRecordOnHeapWithInlineStorage<canBindThisValueOnEnvironment, hasNewTargetOnEnvironment, 2>(self);
case 3:
return new FunctionEnvironmentRecordOnHeapWithInlineStorage<canBindThisValueOnEnvironment, hasNewTargetOnEnvironment, 3>(self);
case 4:
return new FunctionEnvironmentRecordOnHeapWithInlineStorage<canBindThisValueOnEnvironment, hasNewTargetOnEnvironment, 4>(self);
case 5:
return new FunctionEnvironmentRecordOnHeapWithInlineStorage<canBindThisValueOnEnvironment, hasNewTargetOnEnvironment, 5>(self);
default:
return new FunctionEnvironmentRecordOnHeap<canBindThisValueOnEnvironment, hasNewTargetOnEnvironment>(self);
}
} else {
if (LIKELY(!codeBlock->needsVirtualIDOperation())) {
return new FunctionEnvironmentRecordNotIndexed<canBindThisValueOnEnvironment, hasNewTargetOnEnvironment>(self);
} else {
return new FunctionEnvironmentRecordNotIndexedWithVirtualID(self);
}
}
}
};
template <bool isConstruct, bool shouldReturnsObjectOnConstructCall>
Value NativeFunctionObject::processNativeFunctionCall(ExecutionState& state, const Value& receiverSrc, const size_t argc, Value* argv, Optional<Object*> newTarget)
{
CHECK_STACK_OVERFLOW(state);
NativeCodeBlock* codeBlock = nativeCodeBlock();
Context* ctx = codeBlock->context();
bool isStrict = codeBlock->isStrict();
NativeFunctionPointer nativeFunc = codeBlock->nativeFunction();
size_t len = codeBlock->functionLength();
if (argc < len) {
Value* newArgv = (Value*)alloca(sizeof(Value) * len);
for (size_t i = 0; i < argc; i++) {
newArgv[i] = argv[i];
}
for (size_t i = argc; i < len; i++) {
newArgv[i] = Value();
}
argv = newArgv;
}
Value receiver = receiverSrc;
ExecutionState newState(ctx, &state, this, argc, argv, isStrict);
if (!isConstruct) {
// prepare receiver
if (UNLIKELY(!isStrict)) {
if (receiver.isUndefinedOrNull()) {
receiver = ctx->globalObject();
} else {
receiver = receiver.toObject(newState);
}
}
}
Value result;
if (isConstruct) {
result = nativeFunc(newState, receiver, argc, argv, newTarget);
if (shouldReturnsObjectOnConstructCall && UNLIKELY(!result.isObject()) && !newState.hasPendingException()) {
// return exception done!
ErrorObject::throwBuiltinError(newState, ErrorCode::TypeError, "Native Constructor must returns constructed new object");
result = Value(Value::Exception);
}
} else {
ASSERT(!newTarget);
result = nativeFunc(newState, receiver, argc, argv, nullptr);
}
// check Exception
if (UNLIKELY(newState.hasPendingException())) {
ASSERT(result.isException());
state.setPendingException();
}
#ifdef ESCARGOT_DEBUGGER
Debugger::updateStopState(state.context()->debugger(), &newState, ESCARGOT_DEBUGGER_ALWAYS_STOP);
#endif /* ESCARGOT_DEBUGGER */
return result;
}
} // namespace Escargot
#endif