diff options
Diffstat (limited to 'src/v4')
77 files changed, 28260 insertions, 0 deletions
diff --git a/src/v4/debugging.cpp b/src/v4/debugging.cpp new file mode 100644 index 0000000000..61ae0e62d0 --- /dev/null +++ b/src/v4/debugging.cpp @@ -0,0 +1,214 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "debugging.h" +#include "qv4object.h" +#include "qv4functionobject.h" +#include <iostream> + +#define LOW_LEVEL_DEBUGGING_HELPERS + +using namespace QQmlJS; +using namespace QQmlJS::Debugging; + +FunctionState::FunctionState(VM::ExecutionContext *context) + : _context(context) +{ + if (debugger()) + debugger()->enterFunction(this); +} + +FunctionState::~FunctionState() +{ + if (debugger()) + debugger()->leaveFunction(this); +} + +VM::Value *FunctionState::argument(unsigned idx) +{ + if (idx < _context->argumentCount) + return _context->arguments + idx; + else + return 0; +} + +VM::Value *FunctionState::local(unsigned idx) +{ + if (idx < _context->variableCount()) + return _context->locals + idx; + return 0; +} + +#ifdef LOW_LEVEL_DEBUGGING_HELPERS +Debugger *globalInstance = 0; + +void printStackTrace() +{ + if (globalInstance) + globalInstance->printStackTrace(); + else + std::cerr << "No debugger." << std::endl; +} +#endif // DO_TRACE_INSTR + +Debugger::Debugger(VM::ExecutionEngine *engine) + : _engine(engine) +{ +#ifdef LOW_LEVEL_DEBUGGING_HELPERS + globalInstance = this; +#endif // DO_TRACE_INSTR +} + +Debugger::~Debugger() +{ +#ifdef LOW_LEVEL_DEBUGGING_HELPERS + globalInstance = 0; +#endif // DO_TRACE_INSTR + + qDeleteAll(_functionInfo.values()); +} + +void Debugger::addFunction(IR::Function *function) +{ + _functionInfo.insert(function, new FunctionDebugInfo(function)); +} + +void Debugger::setSourceLocation(IR::Function *function, unsigned line, unsigned column) +{ + _functionInfo[function]->setSourceLocation(line, column); +} + +void Debugger::mapFunction(VM::Function *vmf, IR::Function *irf) +{ + _vmToIr.insert(vmf, irf); +} + +FunctionDebugInfo *Debugger::debugInfo(VM::FunctionObject *function) const +{ + if (!function) + return 0; + + if (VM::ScriptFunction *sf = function->asScriptFunction()) + return _functionInfo[irFunction(sf->function)]; + else + return 0; +} + +QString Debugger::name(VM::FunctionObject *function) const +{ + if (FunctionDebugInfo *i = debugInfo(function)) + return i->name; + + return QString(); +} + +void Debugger::aboutToCall(VM::FunctionObject *function, VM::ExecutionContext *context) +{ + _callStack.append(CallInfo(context, function)); +} + +void Debugger::justLeft(VM::ExecutionContext *context) +{ + int idx = callIndex(context); + if (idx < 0) + qDebug() << "Oops, leaving a function that was not registered...?"; + else + _callStack.resize(idx); +} + +void Debugger::enterFunction(FunctionState *state) +{ + _callStack[callIndex(state->context())].state = state; + +#ifdef DO_TRACE_INSTR + QString n = name(_callStack[callIndex(state->context())].function); + std::cerr << "*** Entering \"" << qPrintable(n) << "\" with " << state->context()->argumentCount << " args" << std::endl; +// for (unsigned i = 0; i < state->context()->variableEnvironment->argumentCount; ++i) +// std::cerr << " " << i << ": " << currentArg(i) << std::endl; +#endif // DO_TRACE_INSTR +} + +void Debugger::leaveFunction(FunctionState *state) +{ + _callStack[callIndex(state->context())].state = 0; +} + +void Debugger::aboutToThrow(VM::Value *value) +{ + qDebug() << "*** We are about to throw...:" << value->toString(currentState()->context())->toQString(); +} + +FunctionState *Debugger::currentState() const +{ + if (_callStack.isEmpty()) + return 0; + else + return _callStack.last().state; +} + +const char *Debugger::currentArg(unsigned idx) const +{ + FunctionState *state = currentState(); + return qPrintable(state->argument(idx)->toString(state->context())->toQString()); +} + +const char *Debugger::currentLocal(unsigned idx) const +{ + FunctionState *state = currentState(); + return qPrintable(state->local(idx)->toString(state->context())->toQString()); +} + +const char *Debugger::currentTemp(unsigned idx) const +{ + FunctionState *state = currentState(); + return qPrintable(state->temp(idx)->toString(state->context())->toQString()); +} + +void Debugger::printStackTrace() const +{ + for (int i = _callStack.size() - 1; i >=0; --i) { + QString n = name(_callStack[i].function); + std::cerr << "\tframe #" << i << ": " << qPrintable(n) << std::endl; + } +} + +int Debugger::callIndex(VM::ExecutionContext *context) +{ + for (int idx = _callStack.size() - 1; idx >= 0; --idx) { + if (_callStack[idx].context == context) + return idx; + } + + return -1; +} + +IR::Function *Debugger::irFunction(VM::Function *vmf) const +{ + return _vmToIr[vmf]; +} diff --git a/src/v4/debugging.h b/src/v4/debugging.h new file mode 100644 index 0000000000..00c96717a5 --- /dev/null +++ b/src/v4/debugging.h @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef DEBUGGING_H +#define DEBUGGING_H + +#include "qv4global.h" +#include "qmljs_engine.h" +#include "qmljs_environment.h" + +#include <QHash> + +namespace QQmlJS { + +namespace IR { +struct BasicBlock; +struct Function; +} // namespace IR + +namespace Debugging { + +class Debugger; + +struct FunctionDebugInfo { // TODO: use opaque d-pointers here + QString name; + unsigned startLine, startColumn; + + FunctionDebugInfo(IR::Function *function): + startLine(0), startColumn(0) + { + if (function->name) + name = *function->name; + } + + void setSourceLocation(unsigned line, unsigned column) + { startLine = line; startColumn = column; } +}; + +class FunctionState +{ +public: + FunctionState(VM::ExecutionContext *context); + virtual ~FunctionState(); + + virtual VM::Value *argument(unsigned idx); + virtual VM::Value *local(unsigned idx); + virtual VM::Value *temp(unsigned idx) = 0; + + VM::ExecutionContext *context() const + { return _context; } + + Debugger *debugger() const + { return _context->engine->debugger; } + +private: + VM::ExecutionContext *_context; +}; + +struct CallInfo +{ + VM::ExecutionContext *context; + VM::FunctionObject *function; + FunctionState *state; + + CallInfo(VM::ExecutionContext *context = 0, VM::FunctionObject *function = 0, FunctionState *state = 0) + : context(context) + , function(function) + , state(state) + {} +}; + +class Q_V4_EXPORT Debugger +{ +public: + Debugger(VM::ExecutionEngine *_engine); + ~Debugger(); + +public: // compile-time interface + void addFunction(IR::Function *function); + void setSourceLocation(IR::Function *function, unsigned line, unsigned column); + void mapFunction(VM::Function *vmf, IR::Function *irf); + +public: // run-time querying interface + FunctionDebugInfo *debugInfo(VM::FunctionObject *function) const; + QString name(VM::FunctionObject *function) const; + +public: // execution hooks + void aboutToCall(VM::FunctionObject *function, VM::ExecutionContext *context); + void justLeft(VM::ExecutionContext *context); + void enterFunction(FunctionState *state); + void leaveFunction(FunctionState *state); + void aboutToThrow(VM::Value *value); + +public: // debugging hooks + FunctionState *currentState() const; + const char *currentArg(unsigned idx) const; + const char *currentLocal(unsigned idx) const; + const char *currentTemp(unsigned idx) const; + void printStackTrace() const; + +private: + int callIndex(VM::ExecutionContext *context); + IR::Function *irFunction(VM::Function *vmf) const; + +private: // TODO: use opaque d-pointers here + VM::ExecutionEngine *_engine; + QHash<IR::Function *, FunctionDebugInfo *> _functionInfo; + QHash<VM::Function *, IR::Function *> _vmToIr; + QVector<CallInfo> _callStack; +}; + +} // namespace Debugging +} // namespace QQmlJS + +#endif // DEBUGGING_H diff --git a/src/v4/llvm_runtime.cpp b/src/v4/llvm_runtime.cpp new file mode 100644 index 0000000000..212c8d17d5 --- /dev/null +++ b/src/v4/llvm_runtime.cpp @@ -0,0 +1,523 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmljs_runtime.h" +#include "qmljs_environment.h" +#include <stdio.h> +#include <setjmp.h> + +using namespace QQmlJS::VM; + +extern "C" { + +Value __qmljs_llvm_return(ExecutionContext */*ctx*/, Value *result) +{ + return *result; +} + +Value *__qmljs_llvm_get_argument(ExecutionContext *ctx, int index) +{ + return &ctx->arguments[index]; +} + +void __qmljs_llvm_init_undefined(Value *result) +{ + *result = Value::undefinedValue(); +} + +void __qmljs_llvm_init_null(Value *result) +{ + *result = Value::nullValue(); +} + +void __qmljs_llvm_init_boolean(Value *result, bool value) +{ + *result = Value::fromBoolean(value); +} + +void __qmljs_llvm_init_number(Value *result, double value) +{ + *result = Value::fromDouble(value); +} + +void __qmljs_llvm_init_string(ExecutionContext *ctx, Value *result, const char *str) +{ + *result = Value::fromString(__qmljs_string_from_utf8(ctx, str)); +} + +void __qmljs_llvm_init_closure(ExecutionContext *ctx, Value *result, + String *name, bool hasDirectEval, + bool usesArgumentsObject, bool isStrict, + bool hasNestedFunctions, + String **formals, unsigned formalCount, + String **locals, unsigned localCount) +{ + Function *clos = __qmljs_register_function(ctx, name, hasDirectEval, + usesArgumentsObject, isStrict, + hasNestedFunctions, + formals, formalCount, + locals, localCount); + *result = __qmljs_init_closure(clos, ctx); +} + +bool __qmljs_llvm_to_boolean(ExecutionContext *ctx, const Value *value) +{ + return __qmljs_to_boolean(*value, ctx); +} + +void __qmljs_llvm_bit_and(ExecutionContext *ctx, Value *result, Value *left, Value *right) +{ + *result = __qmljs_bit_and(*left, *right, ctx); +} + +void __qmljs_llvm_bit_or(ExecutionContext *ctx, Value *result, Value *left, Value *right) +{ + *result = __qmljs_bit_or(*left, *right, ctx); +} + +void __qmljs_llvm_bit_xor(ExecutionContext *ctx, Value *result, Value *left, Value *right) +{ + *result = __qmljs_bit_xor(*left, *right, ctx); +} + +void __qmljs_llvm_add(ExecutionContext *ctx, Value *result, Value *left, Value *right) +{ + *result = __qmljs_add(*left, *right, ctx); +} + +void __qmljs_llvm_sub(ExecutionContext *ctx, Value *result, Value *left, Value *right) +{ + *result = __qmljs_sub(*left, *right, ctx); +} + +void __qmljs_llvm_mul(ExecutionContext *ctx, Value *result, Value *left, Value *right) +{ + *result = __qmljs_mul(*left, *right, ctx); +} + +void __qmljs_llvm_div(ExecutionContext *ctx, Value *result, Value *left, Value *right) +{ + *result = __qmljs_div(*left, *right, ctx); +} + +void __qmljs_llvm_mod(ExecutionContext *ctx, Value *result, Value *left, Value *right) +{ + *result = __qmljs_mod(*left, *right, ctx); +} + +void __qmljs_llvm_shl(ExecutionContext *ctx, Value *result, Value *left, Value *right) +{ + *result = __qmljs_shl(*left, *right, ctx); +} + +void __qmljs_llvm_shr(ExecutionContext *ctx, Value *result, Value *left, Value *right) +{ + *result = __qmljs_shr(*left, *right, ctx); +} + +void __qmljs_llvm_ushr(ExecutionContext *ctx, Value *result, Value *left, Value *right) +{ + *result = __qmljs_ushr(*left, *right, ctx); +} + +void __qmljs_llvm_gt(ExecutionContext *ctx, Value *result, Value *left, Value *right) +{ + *result = __qmljs_gt(*left, *right, ctx); +} + +void __qmljs_llvm_lt(ExecutionContext *ctx, Value *result, Value *left, Value *right) +{ + *result = __qmljs_lt(*left, *right, ctx); +} + +void __qmljs_llvm_ge(ExecutionContext *ctx, Value *result, Value *left, Value *right) +{ + *result = __qmljs_ge(*left, *right, ctx); +} + +void __qmljs_llvm_le(ExecutionContext *ctx, Value *result, Value *left, Value *right) +{ + *result = __qmljs_le(*left, *right, ctx); +} + +void __qmljs_llvm_eq(ExecutionContext *ctx, Value *result, Value *left, Value *right) +{ + *result = __qmljs_eq(*left, *right, ctx); +} + +void __qmljs_llvm_ne(ExecutionContext *ctx, Value *result, Value *left, Value *right) +{ + *result = __qmljs_ne(*left, *right, ctx); +} + +void __qmljs_llvm_se(ExecutionContext *ctx, Value *result, Value *left, Value *right) +{ + *result = __qmljs_se(*left, *right, ctx); +} + +void __qmljs_llvm_sne(ExecutionContext *ctx, Value *result, Value *left, Value *right) +{ + *result = __qmljs_sne(*left, *right, ctx); +} + +void __qmljs_llvm_instanceof(ExecutionContext *ctx, Value *result, Value *left, Value *right) +{ + *result = __qmljs_instanceof(*left, *right, ctx); +} + +void __qmljs_llvm_in(ExecutionContext *ctx, Value *result, Value *left, Value *right) +{ + *result = __qmljs_in(*left, *right, ctx); +} + +void __qmljs_llvm_uplus(ExecutionContext *ctx, Value *result, const Value *value) +{ + *result = __qmljs_uplus(*value, ctx); +} + +void __qmljs_llvm_uminus(ExecutionContext *ctx, Value *result, const Value *value) +{ + *result = __qmljs_uminus(*value, ctx); +} + +void __qmljs_llvm_compl(ExecutionContext *ctx, Value *result, const Value *value) +{ + *result = __qmljs_compl(*value, ctx); +} + +void __qmljs_llvm_not(ExecutionContext *ctx, Value *result, const Value *value) +{ + *result = __qmljs_not(*value, ctx); +} + +void __qmljs_llvm_inplace_bit_and_name(ExecutionContext *ctx, String *dest, Value *src) +{ + __qmljs_inplace_bit_and_name(*src, dest, ctx); +} + +void __qmljs_llvm_inplace_bit_or_name(ExecutionContext *ctx, String *dest, Value *src) +{ + __qmljs_inplace_bit_or_name(*src, dest, ctx); +} + +void __qmljs_llvm_inplace_bit_xor_name(ExecutionContext *ctx, String *dest, Value *src) +{ + __qmljs_inplace_bit_xor_name(*src, dest, ctx); +} + +void __qmljs_llvm_inplace_add_name(ExecutionContext *ctx, String *dest, Value *src) +{ + __qmljs_inplace_add_name(*src, dest, ctx); +} + +void __qmljs_llvm_inplace_sub_name(ExecutionContext *ctx, String *dest, Value *src) +{ + __qmljs_inplace_sub_name(*src, dest, ctx); +} + +void __qmljs_llvm_inplace_mul_name(ExecutionContext *ctx, String *dest, Value *src) +{ + __qmljs_inplace_mul_name(*src, dest, ctx); +} + +void __qmljs_llvm_inplace_div_name(ExecutionContext *ctx, String *dest, Value *src) +{ + __qmljs_inplace_div_name(*src, dest, ctx); +} + +void __qmljs_llvm_inplace_mod_name(ExecutionContext *ctx, String *dest, Value *src) +{ + __qmljs_inplace_mod_name(*src, dest, ctx); +} + +void __qmljs_llvm_inplace_shl_name(ExecutionContext *ctx, String *dest, Value *src) +{ + __qmljs_inplace_shl_name(*src, dest, ctx); +} + +void __qmljs_llvm_inplace_shr_name(ExecutionContext *ctx, String *dest, Value *src) +{ + __qmljs_inplace_shr_name(*src, dest, ctx); +} + +void __qmljs_llvm_inplace_ushr_name(ExecutionContext *ctx, String *dest, Value *src) +{ + __qmljs_inplace_ushr_name(*src, dest, ctx); +} + +void __qmljs_llvm_inplace_bit_and_element(ExecutionContext *ctx, Value *base, Value *index, Value *value) +{ + __qmljs_inplace_bit_and_element(*base, *index, *value, ctx); +} + +void __qmljs_llvm_inplace_bit_or_element(ExecutionContext *ctx, Value *base, Value *index, Value *value) +{ + __qmljs_inplace_bit_or_element(*base, *index, *value, ctx); +} + +void __qmljs_llvm_inplace_bit_xor_element(ExecutionContext *ctx, Value *base, Value *index, Value *value) +{ + __qmljs_inplace_bit_xor_element(*base, *index, *value, ctx); +} + +void __qmljs_llvm_inplace_add_element(ExecutionContext *ctx, Value *base, Value *index, Value *value) +{ + __qmljs_inplace_add_element(*base, *index, *value, ctx); +} + +void __qmljs_llvm_inplace_sub_element(ExecutionContext *ctx, Value *base, Value *index, Value *value) +{ + __qmljs_inplace_sub_element(*base, *index, *value, ctx); +} + +void __qmljs_llvm_inplace_mul_element(ExecutionContext *ctx, Value *base, Value *index, Value *value) +{ + __qmljs_inplace_mul_element(*base, *index, *value, ctx); +} + +void __qmljs_llvm_inplace_div_element(ExecutionContext *ctx, Value *base, Value *index, Value *value) +{ + __qmljs_inplace_div_element(*base, *index, *value, ctx); +} + +void __qmljs_llvm_inplace_mod_element(ExecutionContext *ctx, Value *base, Value *index, Value *value) +{ + __qmljs_inplace_mod_element(*base, *index, *value, ctx); +} + +void __qmljs_llvm_inplace_shl_element(ExecutionContext *ctx, Value *base, Value *index, Value *value) +{ + __qmljs_inplace_shl_element(*base, *index, *value, ctx); +} + +void __qmljs_llvm_inplace_shr_element(ExecutionContext *ctx, Value *base, Value *index, Value *value) +{ + __qmljs_inplace_shr_element(*base, *index, *value, ctx); +} + +void __qmljs_llvm_inplace_ushr_element(ExecutionContext *ctx, Value *base, Value *index, Value *value) +{ + __qmljs_inplace_ushr_element(*base, *index, *value, ctx); +} + +void __qmljs_llvm_inplace_bit_and_member(ExecutionContext *ctx, Value *value, Value *base, String *member) +{ + __qmljs_inplace_bit_and_member(*value, *base, member, ctx); +} + +void __qmljs_llvm_inplace_bit_or_member(ExecutionContext *ctx, Value *value, Value *base, String *member) +{ + __qmljs_inplace_bit_or_member(*value, *base, member, ctx); +} + +void __qmljs_llvm_inplace_bit_xor_member(ExecutionContext *ctx, Value *value, Value *base, String *member) +{ + __qmljs_inplace_bit_xor_member(*value, *base, member, ctx); +} + +void __qmljs_llvm_inplace_add_member(ExecutionContext *ctx, Value *value, Value *base, String *member) +{ + __qmljs_inplace_add_member(*value, *base, member, ctx); +} + +void __qmljs_llvm_inplace_sub_member(ExecutionContext *ctx, Value *value, Value *base, String *member) +{ + __qmljs_inplace_sub_member(*value, *base, member, ctx); +} + +void __qmljs_llvm_inplace_mul_member(ExecutionContext *ctx, Value *value, Value *base, String *member) +{ + __qmljs_inplace_mul_member(*value, *base, member, ctx); +} + +void __qmljs_llvm_inplace_div_member(ExecutionContext *ctx, Value *value, Value *base, String *member) +{ + __qmljs_inplace_div_member(*value, *base, member, ctx); +} + +void __qmljs_llvm_inplace_mod_member(ExecutionContext *ctx, Value *value, Value *base, String *member) +{ + __qmljs_inplace_mod_member(*value, *base, member, ctx); +} + +void __qmljs_llvm_inplace_shl_member(ExecutionContext *ctx, Value *value, Value *base, String *member) +{ + __qmljs_inplace_shl_member(*value, *base, member, ctx); +} + +void __qmljs_llvm_inplace_shr_member(ExecutionContext *ctx, Value *value, Value *base, String *member) +{ + __qmljs_inplace_shr_member(*value, *base, member, ctx); +} + +void __qmljs_llvm_inplace_ushr_member(ExecutionContext *ctx, Value *value, Value *base, String *member) +{ + __qmljs_inplace_ushr_member(*value, *base, member, ctx); +} + +String *__qmljs_llvm_identifier_from_utf8(ExecutionContext *ctx, const char *str) +{ + return __qmljs_identifier_from_utf8(ctx, str); // ### make it unique +} + +void __qmljs_llvm_call_activation_property(ExecutionContext *context, Value *result, String *name, Value *args, int argc) +{ + *result = __qmljs_call_activation_property(context, name, args, argc); +} + +void __qmljs_llvm_call_value(ExecutionContext *context, Value *result, const Value *thisObject, const Value *func, Value *args, int argc) +{ + Value that = thisObject ? *thisObject : Value::undefinedValue(); + *result = __qmljs_call_value(context, that, *func, args, argc); +} + +void __qmljs_llvm_construct_activation_property(ExecutionContext *context, Value *result, String *name, Value *args, int argc) +{ + *result = __qmljs_construct_activation_property(context, name, args, argc); +} + +void __qmljs_llvm_construct_value(ExecutionContext *context, Value *result, const Value *func, Value *args, int argc) +{ + *result = __qmljs_construct_value(context, *func, args, argc); +} + +void __qmljs_llvm_get_activation_property(ExecutionContext *ctx, Value *result, String *name) +{ + *result = __qmljs_get_activation_property(ctx, name); +} + +void __qmljs_llvm_set_activation_property(ExecutionContext *ctx, String *name, Value *value) +{ + __qmljs_set_activation_property(ctx, name, *value); +} + +void __qmljs_llvm_get_property(ExecutionContext *ctx, Value *result, Value *object, String *name) +{ + *result = __qmljs_get_property(ctx, *object, name); +} + +void __qmljs_llvm_call_property(ExecutionContext *context, Value *result, const Value *base, String *name, Value *args, int argc) +{ + *result = __qmljs_call_property(context, *base, name, args, argc); +} + +void __qmljs_llvm_construct_property(ExecutionContext *context, Value *result, const Value *base, String *name, Value *args, int argc) +{ + *result = __qmljs_construct_property(context, *base, name, args, argc); +} + +void __qmljs_llvm_get_element(ExecutionContext *ctx, Value *result, Value *object, Value *index) +{ + *result = __qmljs_get_element(ctx, *object, *index); +} + +void __qmljs_llvm_set_element(ExecutionContext *ctx, Value *object, Value *index, Value *value) +{ + __qmljs_set_element(ctx, *object, *index, *value); +} + +void __qmljs_llvm_set_property(ExecutionContext *ctx, Value *object, String *name, Value *value) +{ + __qmljs_set_property(ctx, *object, name, *value); +} + +void __qmljs_llvm_builtin_declare_var(ExecutionContext *ctx, bool deletable, String *name) +{ + __qmljs_builtin_declare_var(ctx, deletable, name); +} + +void __qmljs_llvm_typeof(ExecutionContext *ctx, Value *result, const Value *value) +{ + *result = __qmljs_builtin_typeof(*value, ctx); +} + +void __qmljs_llvm_throw(ExecutionContext *context, Value *value) +{ + __qmljs_throw(*value, context); +} + +void __qmljs_llvm_create_exception_handler(ExecutionContext *context, Value *result) +{ + void *buf = __qmljs_create_exception_handler(context); + *result = Value::fromInt32(setjmp(* static_cast<jmp_buf *>(buf))); +} + +void __qmljs_llvm_delete_exception_handler(ExecutionContext *context) +{ + __qmljs_delete_exception_handler(context); +} + +void __qmljs_llvm_get_exception(ExecutionContext *context, Value *result) +{ + *result = __qmljs_get_exception(context); +} + +void __qmljs_llvm_foreach_iterator_object(ExecutionContext *context, Value *result, Value *in) +{ + *result = __qmljs_foreach_iterator_object(*in, context); +} + +void __qmljs_llvm_foreach_next_property_name(Value *result, Value *it) +{ + *result = __qmljs_foreach_next_property_name(*it); +} + +void __qmljs_llvm_get_this_object(ExecutionContext *ctx, Value *result) +{ + *result = __qmljs_get_thisObject(ctx); +} + +void __qmljs_llvm_delete_subscript(ExecutionContext *ctx, Value *result, Value *base, Value *index) +{ + *result = __qmljs_delete_subscript(ctx, *base, *index); +} + +void __qmljs_llvm_delete_member(ExecutionContext *ctx, Value *result, Value *base, String *name) +{ + *result = __qmljs_delete_member(ctx, *base, name); +} + +void __qmljs_llvm_delete_name(ExecutionContext *ctx, Value *result, String *name) +{ + *result = __qmljs_delete_name(ctx, name); +} + +} // extern "C" diff --git a/src/v4/moth/moth.pri b/src/v4/moth/moth.pri new file mode 100644 index 0000000000..73bd893286 --- /dev/null +++ b/src/v4/moth/moth.pri @@ -0,0 +1,13 @@ +INCLUDEPATH += $$PWD + +HEADERS += \ + $$PWD/qv4isel_moth_p.h \ + $$PWD/qv4instr_moth_p.h \ + $$PWD/qv4vme_moth_p.h + +SOURCES += \ + $$PWD/qv4isel_moth.cpp \ + $$PWD/qv4instr_moth.cpp \ + $$PWD/qv4vme_moth.cpp + +#DEFINES += DO_TRACE_INSTR diff --git a/src/v4/moth/qv4instr_moth.cpp b/src/v4/moth/qv4instr_moth.cpp new file mode 100644 index 0000000000..a2bad39e00 --- /dev/null +++ b/src/v4/moth/qv4instr_moth.cpp @@ -0,0 +1,15 @@ +#include "qv4instr_moth_p.h" + +using namespace QQmlJS; +using namespace QQmlJS::Moth; + +int Instr::size(Type type) +{ +#define MOTH_RETURN_INSTR_SIZE(I, FMT) case I: return InstrMeta<(int)I>::Size; + switch (type) { + FOR_EACH_MOTH_INSTR(MOTH_RETURN_INSTR_SIZE) + default: return 0; + } +#undef MOTH_RETURN_INSTR_SIZE +} + diff --git a/src/v4/moth/qv4instr_moth_p.h b/src/v4/moth/qv4instr_moth_p.h new file mode 100644 index 0000000000..741ddba79d --- /dev/null +++ b/src/v4/moth/qv4instr_moth_p.h @@ -0,0 +1,514 @@ +#ifndef QV4INSTR_MOTH_P_H +#define QV4INSTR_MOTH_P_H + +#include <QtCore/qglobal.h> +#include "qv4object.h" + +#define FOR_EACH_MOTH_INSTR(F) \ + F(Ret, ret) \ + F(LoadValue, loadValue) \ + F(LoadClosure, loadClosure) \ + F(MoveTemp, moveTemp) \ + F(LoadName, loadName) \ + F(StoreName, storeName) \ + F(LoadElement, loadElement) \ + F(StoreElement, storeElement) \ + F(LoadProperty, loadProperty) \ + F(StoreProperty, storeProperty) \ + F(Push, push) \ + F(CallValue, callValue) \ + F(CallProperty, callProperty) \ + F(CallElement, callElement) \ + F(CallActivationProperty, callActivationProperty) \ + F(CallBuiltinThrow, callBuiltinThrow) \ + F(CallBuiltinCreateExceptionHandler, callBuiltinCreateExceptionHandler) \ + F(CallBuiltinDeleteExceptionHandler, callBuiltinDeleteExceptionHandler) \ + F(CallBuiltinGetException, callBuiltinGetException) \ + F(CallBuiltinPushScope, callBuiltinPushScope) \ + F(CallBuiltinPopScope, callBuiltinPopScope) \ + F(CallBuiltinForeachIteratorObject, callBuiltinForeachIteratorObject) \ + F(CallBuiltinForeachNextPropertyName, callBuiltinForeachNextPropertyName) \ + F(CallBuiltinDeleteMember, callBuiltinDeleteMember) \ + F(CallBuiltinDeleteSubscript, callBuiltinDeleteSubscript) \ + F(CallBuiltinDeleteName, callBuiltinDeleteName) \ + F(CallBuiltinTypeofMember, callBuiltinTypeofMember) \ + F(CallBuiltinTypeofSubscript, callBuiltinTypeofSubscript) \ + F(CallBuiltinTypeofName, callBuiltinTypeofName) \ + F(CallBuiltinTypeofValue, callBuiltinTypeofValue) \ + F(CallBuiltinPostIncMember, callBuiltinPostIncMember) \ + F(CallBuiltinPostIncSubscript, callBuiltinPostIncSubscript) \ + F(CallBuiltinPostIncName, callBuiltinPostIncName) \ + F(CallBuiltinPostIncValue, callBuiltinPostIncValue) \ + F(CallBuiltinPostDecMember, callBuiltinPostDecMember) \ + F(CallBuiltinPostDecSubscript, callBuiltinPostDecSubscript) \ + F(CallBuiltinPostDecName, callBuiltinPostDecName) \ + F(CallBuiltinPostDecValue, callBuiltinPostDecValue) \ + F(CallBuiltinDeclareVar, callBuiltinDeclareVar) \ + F(CallBuiltinDefineGetterSetter, callBuiltinDefineGetterSetter) \ + F(CallBuiltinDefineProperty, callBuiltinDefineProperty) \ + F(CallBuiltinDefineArrayProperty, callBuiltinDefineArrayProperty) \ + F(CreateValue, createValue) \ + F(CreateProperty, createProperty) \ + F(CreateActivationProperty, createActivationProperty) \ + F(Jump, jump) \ + F(CJump, cjump) \ + F(Unop, unop) \ + F(Binop, binop) \ + F(LoadThis, loadThis) \ + F(InplaceElementOp, inplaceElementOp) \ + F(InplaceMemberOp, inplaceMemberOp) \ + F(InplaceNameOp, inplaceNameOp) + +#if defined(Q_CC_GNU) && (!defined(Q_CC_INTEL) || __INTEL_COMPILER >= 1200) +# define MOTH_THREADED_INTERPRETER +#endif + +#define MOTH_INSTR_ALIGN_MASK (Q_ALIGNOF(QQmlJS::Moth::Instr) - 1) + +#ifdef MOTH_THREADED_INTERPRETER +# define MOTH_INSTR_HEADER void *code; +#else +# define MOTH_INSTR_HEADER quint8 instructionType; +#endif + +#define MOTH_INSTR_ENUM(I, FMT) I, +#define MOTH_INSTR_SIZE(I, FMT) ((sizeof(QQmlJS::Moth::Instr::instr_##FMT) + MOTH_INSTR_ALIGN_MASK) & ~MOTH_INSTR_ALIGN_MASK) + + +namespace QQmlJS { +namespace Moth { + +union Instr +{ + struct Param { + enum { + ValueType = 0, + ArgumentType = 1, + LocalType = 2, + TempType = 3 + }; + VM::Value value; + unsigned type : 2; + unsigned index : 30; + + bool isValue() const { return type == ValueType; } + bool isArgument() const { return type == ArgumentType; } + bool isLocal() const { return type == LocalType; } + bool isTemp() const { return type == TempType; } + + static Param createValue(const VM::Value &v) + { + Param p; + p.type = ValueType; + p.value = v; + return p; + } + + static Param createArgument(unsigned idx) + { + Param p; + p.type = ArgumentType; + p.index = idx; + return p; + } + + static Param createLocal(unsigned idx) + { + Param p; + p.type = LocalType; + p.index = idx; + return p; + } + + static Param createTemp(unsigned idx) + { + Param p; + p.type = TempType; + p.index = idx; + return p; + } + }; + + enum Type { + FOR_EACH_MOTH_INSTR(MOTH_INSTR_ENUM) + }; + + struct instr_common { + MOTH_INSTR_HEADER + }; + struct instr_ret { + MOTH_INSTR_HEADER + Param result; + }; + struct instr_loadValue { + MOTH_INSTR_HEADER + Param value; + Param result; + }; + struct instr_moveTemp { + MOTH_INSTR_HEADER + Param source; + Param result; + }; + struct instr_loadClosure { + MOTH_INSTR_HEADER + VM::Function *value; + Param result; + }; + struct instr_loadName { + MOTH_INSTR_HEADER + VM::String *name; + Param result; + }; + struct instr_storeName { + MOTH_INSTR_HEADER + VM::String *name; + Param source; + }; + struct instr_loadProperty { + MOTH_INSTR_HEADER + VM::String *name; + Param base; + Param result; + }; + struct instr_storeProperty { + MOTH_INSTR_HEADER + VM::String *name; + Param base; + Param source; + }; + struct instr_loadElement { + MOTH_INSTR_HEADER + Param base; + Param index; + Param result; + }; + struct instr_storeElement { + MOTH_INSTR_HEADER + Param base; + Param index; + Param source; + }; + struct instr_push { + MOTH_INSTR_HEADER + quint32 value; + }; + struct instr_callValue { + MOTH_INSTR_HEADER + quint32 argc; + quint32 args; + Param dest; + Param result; + }; + struct instr_callProperty { + MOTH_INSTR_HEADER + VM::String *name; + quint32 argc; + quint32 args; + Param base; + Param result; + }; + struct instr_callElement { + MOTH_INSTR_HEADER + Param base; + Param index; + quint32 argc; + quint32 args; + Param result; + }; + struct instr_callActivationProperty { + MOTH_INSTR_HEADER + VM::String *name; + quint32 argc; + quint32 args; + Param result; + }; + struct instr_callBuiltinThrow { + MOTH_INSTR_HEADER + Param arg; + }; + struct instr_callBuiltinCreateExceptionHandler { + MOTH_INSTR_HEADER + Param result; + }; + struct instr_callBuiltinDeleteExceptionHandler { + MOTH_INSTR_HEADER + }; + struct instr_callBuiltinGetException { + MOTH_INSTR_HEADER + Param result; + }; + struct instr_callBuiltinPushScope { + MOTH_INSTR_HEADER + Param arg; + }; + struct instr_callBuiltinPopScope { + MOTH_INSTR_HEADER + }; + struct instr_callBuiltinForeachIteratorObject { + MOTH_INSTR_HEADER + Param arg; + Param result; + }; + struct instr_callBuiltinForeachNextPropertyName { + MOTH_INSTR_HEADER + Param arg; + Param result; + }; + struct instr_callBuiltinDeleteMember { + MOTH_INSTR_HEADER + VM::String *member; + Param base; + Param result; + }; + struct instr_callBuiltinDeleteSubscript { + MOTH_INSTR_HEADER + Param base; + Param index; + Param result; + }; + struct instr_callBuiltinDeleteName { + MOTH_INSTR_HEADER + VM::String *name; + Param result; + }; + struct instr_callBuiltinTypeofMember { + MOTH_INSTR_HEADER + VM::String *member; + Param base; + Param result; + }; + struct instr_callBuiltinTypeofSubscript { + MOTH_INSTR_HEADER + Param base; + Param index; + Param result; + }; + struct instr_callBuiltinTypeofName { + MOTH_INSTR_HEADER + VM::String *name; + Param result; + }; + struct instr_callBuiltinTypeofValue { + MOTH_INSTR_HEADER + Param value; + Param result; + }; + struct instr_callBuiltinPostIncMember { + MOTH_INSTR_HEADER + Param base; + VM::String *member; + Param result; + }; + struct instr_callBuiltinPostIncSubscript { + MOTH_INSTR_HEADER + Param base; + Param index; + Param result; + }; + struct instr_callBuiltinPostIncName { + MOTH_INSTR_HEADER + VM::String *name; + Param result; + }; + struct instr_callBuiltinPostIncValue { + MOTH_INSTR_HEADER + Param value; + Param result; + }; + struct instr_callBuiltinPostDecMember { + MOTH_INSTR_HEADER + Param base; + VM::String *member; + Param result; + }; + struct instr_callBuiltinPostDecSubscript { + MOTH_INSTR_HEADER + Param base; + Param index; + Param result; + }; + struct instr_callBuiltinPostDecName { + MOTH_INSTR_HEADER + VM::String *name; + Param result; + }; + struct instr_callBuiltinPostDecValue { + MOTH_INSTR_HEADER + Param value; + Param result; + }; + struct instr_callBuiltinDeclareVar { + MOTH_INSTR_HEADER + VM::String *varName; + bool isDeletable; + }; + struct instr_callBuiltinDefineGetterSetter { + MOTH_INSTR_HEADER + VM::String *name; + Param object; + Param getter; + Param setter; + }; + struct instr_callBuiltinDefineProperty { + MOTH_INSTR_HEADER + VM::String *name; + Param object; + Param value; + }; + struct instr_callBuiltinDefineArrayProperty { + MOTH_INSTR_HEADER + Param object; + Param value; + int index; + }; + struct instr_createValue { + MOTH_INSTR_HEADER + quint32 argc; + quint32 args; + Param func; + Param result; + }; + struct instr_createProperty { + MOTH_INSTR_HEADER + VM::String *name; + quint32 argc; + quint32 args; + Param base; + Param result; + }; + struct instr_createActivationProperty { + MOTH_INSTR_HEADER + VM::String *name; + quint32 argc; + quint32 args; + Param result; + }; + struct instr_jump { + MOTH_INSTR_HEADER + ptrdiff_t offset; + }; + struct instr_cjump { + MOTH_INSTR_HEADER + ptrdiff_t offset; + Param condition; + }; + struct instr_unop { + MOTH_INSTR_HEADER + VM::Value (*alu)(const VM::Value value, VM::ExecutionContext *ctx); + Param source; + Param result; + }; + struct instr_binop { + MOTH_INSTR_HEADER + VM::Value (*alu)(const VM::Value , const VM::Value, VM::ExecutionContext *); + Param lhs; + Param rhs; + Param result; + }; + struct instr_loadThis { + MOTH_INSTR_HEADER + Param result; + }; + struct instr_inplaceElementOp { + MOTH_INSTR_HEADER + void (*alu)(VM::Value, VM::Value, VM::Value, VM::ExecutionContext *); + Param base; + Param index; + Param source; + }; + struct instr_inplaceMemberOp { + MOTH_INSTR_HEADER + void (*alu)(VM::Value, VM::Value, VM::String *, VM::ExecutionContext *); + VM::String *member; + Param base; + Param source; + }; + struct instr_inplaceNameOp { + MOTH_INSTR_HEADER + void (*alu)(VM::Value, VM::String *, VM::ExecutionContext *); + VM::String *name; + Param source; + }; + + instr_common common; + instr_ret ret; + instr_loadValue loadValue; + instr_moveTemp moveTemp; + instr_loadClosure loadClosure; + instr_loadName loadName; + instr_storeName storeName; + instr_loadElement loadElement; + instr_storeElement storeElement; + instr_loadProperty loadProperty; + instr_storeProperty storeProperty; + instr_push push; + instr_callValue callValue; + instr_callProperty callProperty; + instr_callElement callElement; + instr_callActivationProperty callActivationProperty; + instr_callBuiltinThrow callBuiltinThrow; + instr_callBuiltinCreateExceptionHandler callBuiltinCreateExceptionHandler; + instr_callBuiltinDeleteExceptionHandler callBuiltinDeleteExceptionHandler; + instr_callBuiltinGetException callBuiltinGetException; + instr_callBuiltinPushScope callBuiltinPushScope; + instr_callBuiltinPopScope callBuiltinPopScope; + instr_callBuiltinForeachIteratorObject callBuiltinForeachIteratorObject; + instr_callBuiltinForeachNextPropertyName callBuiltinForeachNextPropertyName; + instr_callBuiltinDeleteMember callBuiltinDeleteMember; + instr_callBuiltinDeleteSubscript callBuiltinDeleteSubscript; + instr_callBuiltinDeleteName callBuiltinDeleteName; + instr_callBuiltinTypeofMember callBuiltinTypeofMember; + instr_callBuiltinTypeofSubscript callBuiltinTypeofSubscript; + instr_callBuiltinTypeofName callBuiltinTypeofName; + instr_callBuiltinTypeofValue callBuiltinTypeofValue; + instr_callBuiltinPostIncMember callBuiltinPostIncMember; + instr_callBuiltinPostIncSubscript callBuiltinPostIncSubscript; + instr_callBuiltinPostIncName callBuiltinPostIncName; + instr_callBuiltinPostIncValue callBuiltinPostIncValue; + instr_callBuiltinPostDecMember callBuiltinPostDecMember; + instr_callBuiltinPostDecSubscript callBuiltinPostDecSubscript; + instr_callBuiltinPostDecName callBuiltinPostDecName; + instr_callBuiltinPostDecValue callBuiltinPostDecValue; + instr_callBuiltinDeclareVar callBuiltinDeclareVar; + instr_callBuiltinDefineGetterSetter callBuiltinDefineGetterSetter; + instr_callBuiltinDefineProperty callBuiltinDefineProperty; + instr_callBuiltinDefineArrayProperty callBuiltinDefineArrayProperty; + instr_createValue createValue; + instr_createProperty createProperty; + instr_createActivationProperty createActivationProperty; + instr_jump jump; + instr_cjump cjump; + instr_unop unop; + instr_binop binop; + instr_loadThis loadThis; + instr_inplaceElementOp inplaceElementOp; + instr_inplaceMemberOp inplaceMemberOp; + instr_inplaceNameOp inplaceNameOp; + + static int size(Type type); +}; + +template<int N> +struct InstrMeta { +}; + +#define MOTH_INSTR_META_TEMPLATE(I, FMT) \ + template<> struct InstrMeta<(int)Instr::I> { \ + enum { Size = MOTH_INSTR_SIZE(I, FMT) }; \ + typedef Instr::instr_##FMT DataType; \ + static const DataType &data(const Instr &instr) { return instr.FMT; } \ + static void setData(Instr &instr, const DataType &v) { instr.FMT = v; } \ + }; +FOR_EACH_MOTH_INSTR(MOTH_INSTR_META_TEMPLATE); +#undef MOTH_INSTR_META_TEMPLATE + +template<int InstrType> +class InstrData : public InstrMeta<InstrType>::DataType +{ +}; + +} // namespace Moth +} // namespace QQmlJS + +#endif // QV4INSTR_MOTH_P_H diff --git a/src/v4/moth/qv4isel_moth.cpp b/src/v4/moth/qv4isel_moth.cpp new file mode 100644 index 0000000000..5522be4c32 --- /dev/null +++ b/src/v4/moth/qv4isel_moth.cpp @@ -0,0 +1,975 @@ +#include "qv4isel_util_p.h" +#include "qv4isel_moth_p.h" +#include "qv4vme_moth_p.h" +#include "qv4functionobject.h" +#include "qv4regexpobject.h" +#include "debugging.h" + +using namespace QQmlJS; +using namespace QQmlJS::Moth; + +namespace { + +QTextStream qout(stderr, QIODevice::WriteOnly); + +//#define DEBUG_TEMP_COMPRESSION +#ifdef DEBUG_TEMP_COMPRESSION +# define DBTC(x) x +#else // !DEBUG_TEMP_COMPRESSION +# define DBTC(x) +#endif // DEBUG_TEMP_COMPRESSION +class CompressTemps: public IR::StmtVisitor, IR::ExprVisitor +{ +public: + void run(IR::Function *function) + { + DBTC(qDebug() << "starting on function" << (*function->name) << "with" << function->tempCount << "temps.";) + + _seenTemps.clear(); + _nextFree = 0; + _active.reserve(function->tempCount); + _localCount = function->locals.size(); + + QVector<int> pinned; + foreach (IR::BasicBlock *block, function->basicBlocks) { + if (IR::Stmt *last = block->terminator()) { + const QBitArray &liveOut = last->d->liveOut; + for (int i = 0, ei = liveOut.size(); i < ei; ++i) { + if (liveOut.at(i) && !pinned.contains(i)) { + pinned.append(i); + add(i - _localCount, _nextFree); + } + } + } + } + _pinnedCount = _nextFree; + + int maxUsed = _nextFree; + + foreach (IR::BasicBlock *block, function->basicBlocks) { + DBTC(qDebug("L%d:", block->index)); + + for (int i = 0, ei = block->statements.size(); i < ei; ++i ) { + _currentStatement = block->statements[i]; + if (i == 0) + expireOld(); + + DBTC(_currentStatement->dump(qout);qout<<endl<<flush;) + + if (_currentStatement->d) + _currentStatement->accept(this); + } + maxUsed = std::max(maxUsed, _nextFree); + } + DBTC(qDebug() << "function" << (*function->name) << "uses" << maxUsed << "temps.";) + function->tempCount = maxUsed + _localCount; + } + +private: + virtual void visitConst(IR::Const *) {} + virtual void visitString(IR::String *) {} + virtual void visitRegExp(IR::RegExp *) {} + virtual void visitName(IR::Name *) {} + virtual void visitClosure(IR::Closure *) {} + virtual void visitUnop(IR::Unop *e) { e->expr->accept(this); } + virtual void visitBinop(IR::Binop *e) { e->left->accept(this); e->right->accept(this); } + virtual void visitSubscript(IR::Subscript *e) { e->base->accept(this); e->index->accept(this); } + virtual void visitMember(IR::Member *e) { e->base->accept(this); } + virtual void visitExp(IR::Exp *s) { s->expr->accept(this); } + virtual void visitEnter(IR::Enter *) {} + virtual void visitLeave(IR::Leave *) {} + virtual void visitJump(IR::Jump *) {} + virtual void visitCJump(IR::CJump *s) { s->cond->accept(this); } + virtual void visitRet(IR::Ret *s) { s->expr->accept(this); } + + virtual void visitTemp(IR::Temp *e) { + if (_seenTemps.contains(e)) + return; + _seenTemps.insert(e); + + if (e->index < 0) + return; + if (e->index < _localCount) // don't optimise locals yet. + return; + + e->index = remap(e->index - _localCount) + _localCount; + } + + virtual void visitCall(IR::Call *e) { + e->base->accept(this); + for (IR::ExprList *it = e->args; it; it = it->next) + it->expr->accept(this); + } + + virtual void visitNew(IR::New *e) { + e->base->accept(this); + for (IR::ExprList *it = e->args; it; it = it->next) + it->expr->accept(this); + } + + virtual void visitMove(IR::Move *s) { + s->target->accept(this); + s->source->accept(this); + } + + int remap(int tempIndex) { + for (ActiveTemps::const_iterator i = _active.begin(), ei = _active.end(); i < ei; ++i) { + if (i->first == tempIndex) { + DBTC(qDebug() << " lookup" << (tempIndex + _localCount) << "->" << (i->second + _localCount);) + return i->second; + } + } + + int firstFree = expireOld(); + add(tempIndex, firstFree); + return firstFree; + } + + void add(int tempIndex, int firstFree) { + if (_nextFree <= firstFree) + _nextFree = firstFree + 1; + _active.prepend(qMakePair(tempIndex, firstFree)); + DBTC(qDebug() << " add" << (tempIndex + _localCount) << "->" << (firstFree+ _localCount);) + } + + int expireOld() { + Q_ASSERT(_currentStatement->d); + + const QBitArray &liveIn = _currentStatement->d->liveIn; + QBitArray inUse(_nextFree); + int i = 0; + while (i < _active.size()) { + const QPair<int, int> &p = _active[i]; + + if (p.second < _pinnedCount) { + inUse.setBit(p.second); + ++i; + continue; + } + + if (liveIn[p.first + _localCount]) { + inUse[p.second] = true; + ++i; + } else { + DBTC(qDebug() << " remove" << (p.first + _localCount) << "->" << (p.second + _localCount);) + _active.remove(i); + } + } + for (int i = 0, ei = inUse.size(); i < ei; ++i) + if (!inUse[i]) + return i; + return _nextFree; + } + +private: + typedef QVector<QPair<int, int> > ActiveTemps; + ActiveTemps _active; + QSet<IR::Temp *> _seenTemps; + IR::Stmt *_currentStatement; + int _localCount; + int _nextFree; + int _pinnedCount; +}; +#undef DBTC + +typedef VM::Value (*ALUFunction)(const VM::Value, const VM::Value, VM::ExecutionContext*); +inline ALUFunction aluOpFunction(IR::AluOp op) +{ + switch (op) { + case IR::OpInvalid: + return 0; + case IR::OpIfTrue: + return 0; + case IR::OpNot: + return 0; + case IR::OpUMinus: + return 0; + case IR::OpUPlus: + return 0; + case IR::OpCompl: + return 0; + case IR::OpBitAnd: + return VM::__qmljs_bit_and; + case IR::OpBitOr: + return VM::__qmljs_bit_or; + case IR::OpBitXor: + return VM::__qmljs_bit_xor; + case IR::OpAdd: + return VM::__qmljs_add; + case IR::OpSub: + return VM::__qmljs_sub; + case IR::OpMul: + return VM::__qmljs_mul; + case IR::OpDiv: + return VM::__qmljs_div; + case IR::OpMod: + return VM::__qmljs_mod; + case IR::OpLShift: + return VM::__qmljs_shl; + case IR::OpRShift: + return VM::__qmljs_shr; + case IR::OpURShift: + return VM::__qmljs_ushr; + case IR::OpGt: + return VM::__qmljs_gt; + case IR::OpLt: + return VM::__qmljs_lt; + case IR::OpGe: + return VM::__qmljs_ge; + case IR::OpLe: + return VM::__qmljs_le; + case IR::OpEqual: + return VM::__qmljs_eq; + case IR::OpNotEqual: + return VM::__qmljs_ne; + case IR::OpStrictEqual: + return VM::__qmljs_se; + case IR::OpStrictNotEqual: + return VM::__qmljs_sne; + case IR::OpInstanceof: + return VM::__qmljs_instanceof; + case IR::OpIn: + return VM::__qmljs_in; + case IR::OpAnd: + return 0; + case IR::OpOr: + return 0; + default: + assert(!"Unknown AluOp"); + return 0; + } +}; + +} // anonymous namespace + +InstructionSelection::InstructionSelection(VM::ExecutionEngine *engine, IR::Module *module) + : EvalInstructionSelection(engine, module) + , _function(0) + , _vmFunction(0) + , _block(0) + , _codeStart(0) + , _codeNext(0) + , _codeEnd(0) +{ +} + +InstructionSelection::~InstructionSelection() +{ +} + +void InstructionSelection::run(VM::Function *vmFunction, IR::Function *function) +{ + IR::BasicBlock *block; + + QHash<IR::BasicBlock *, QVector<ptrdiff_t> > patches; + QHash<IR::BasicBlock *, ptrdiff_t> addrs; + + int codeSize = 4096; + uchar *codeStart = new uchar[codeSize]; + uchar *codeNext = codeStart; + uchar *codeEnd = codeStart + codeSize; + + qSwap(_function, function); + qSwap(_vmFunction, vmFunction); + qSwap(block, _block); + qSwap(patches, _patches); + qSwap(addrs, _addrs); + qSwap(codeStart, _codeStart); + qSwap(codeNext, _codeNext); + qSwap(codeEnd, _codeEnd); + + // TODO: FIXME: fix the temp compression with the new temp index layout. +// CompressTemps().run(_function); + + int locals = frameSize(); + assert(locals >= 0); + + Instruction::Push push; + push.value = quint32(locals); + addInstruction(push); + + foreach (_block, _function->basicBlocks) { + _addrs.insert(_block, _codeNext - _codeStart); + + foreach (IR::Stmt *s, _block->statements) + s->accept(this); + } + + patchJumpAddresses(); + + _vmFunction->code = VME::exec; + _vmFunction->codeData = squeezeCode(); + + qSwap(_function, function); + qSwap(_vmFunction, vmFunction); + qSwap(block, _block); + qSwap(patches, _patches); + qSwap(addrs, _addrs); + qSwap(codeStart, _codeStart); + qSwap(codeNext, _codeNext); + qSwap(codeEnd, _codeEnd); + + delete[] codeStart; +} + +void InstructionSelection::callValue(IR::Temp *value, IR::ExprList *args, IR::Temp *result) +{ + Instruction::CallValue call; + prepareCallArgs(args, call.argc, call.args); + call.dest = getParam(value); + call.result = getResultParam(result); + addInstruction(call); +} + +void InstructionSelection::callProperty(IR::Temp *base, const QString &name, IR::ExprList *args, IR::Temp *result) +{ + // call the property on the loaded base + Instruction::CallProperty call; + call.base = getParam(base); + call.name = identifier(name); + prepareCallArgs(args, call.argc, call.args); + call.result = getResultParam(result); + addInstruction(call); +} + +void InstructionSelection::callSubscript(IR::Temp *base, IR::Temp *index, IR::ExprList *args, IR::Temp *result) +{ + // call the property on the loaded base + Instruction::CallElement call; + call.base = getParam(base); + call.index = getParam(index); + prepareCallArgs(args, call.argc, call.args); + call.result = getResultParam(result); + addInstruction(call); +} + +void InstructionSelection::constructActivationProperty(IR::Name *func, + IR::ExprList *args, + IR::Temp *result) +{ + Instruction::CreateActivationProperty create; + create.name = identifier(*func->id); + prepareCallArgs(args, create.argc, create.args); + create.result = getResultParam(result); + addInstruction(create); +} + +void InstructionSelection::constructProperty(IR::Temp *base, const QString &name, IR::ExprList *args, IR::Temp *result) +{ + Instruction::CreateProperty create; + create.base = getParam(base); + create.name = identifier(name); + prepareCallArgs(args, create.argc, create.args); + create.result = getResultParam(result); + addInstruction(create); +} + +void InstructionSelection::constructValue(IR::Temp *value, IR::ExprList *args, IR::Temp *result) +{ + Instruction::CreateValue create; + create.func = getParam(value); + prepareCallArgs(args, create.argc, create.args); + create.result = getResultParam(result); + addInstruction(create); +} + +void InstructionSelection::loadThisObject(IR::Temp *temp) +{ + Instruction::LoadThis load; + load.result = getResultParam(temp); + addInstruction(load); +} + +void InstructionSelection::loadConst(IR::Const *sourceConst, IR::Temp *targetTemp) +{ + assert(sourceConst); + + Instruction::LoadValue load; + load.value = getParam(sourceConst); + load.result = getResultParam(targetTemp); + addInstruction(load); +} + +void InstructionSelection::loadString(const QString &str, IR::Temp *targetTemp) +{ + Instruction::LoadValue load; + load.value = Instr::Param::createValue(VM::Value::fromString(identifier(str))); + load.result = getResultParam(targetTemp); + addInstruction(load); +} + +void InstructionSelection::loadRegexp(IR::RegExp *sourceRegexp, IR::Temp *targetTemp) +{ + Instruction::LoadValue load; + load.value = Instr::Param::createValue( + VM::Value::fromObject(engine()->newRegExpObject( + *sourceRegexp->value, + sourceRegexp->flags))); + load.result = getResultParam(targetTemp); + addInstruction(load); +} + +void InstructionSelection::getActivationProperty(const QString &name, IR::Temp *temp) +{ + Instruction::LoadName load; + load.name = identifier(name); + load.result = getResultParam(temp); + addInstruction(load); +} + +void InstructionSelection::setActivationProperty(IR::Expr *source, const QString &targetName) +{ + Instruction::StoreName store; + store.source = getParam(source); + store.name = identifier(targetName); + addInstruction(store); +} + +void InstructionSelection::initClosure(IR::Closure *closure, IR::Temp *target) +{ + VM::Function *vmFunc = vmFunction(closure->value); + assert(vmFunc); + Instruction::LoadClosure load; + load.value = vmFunc; + load.result = getResultParam(target); + addInstruction(load); +} + +void InstructionSelection::getProperty(IR::Temp *base, const QString &name, IR::Temp *target) +{ + Instruction::LoadProperty load; + load.base = getParam(base); + load.name = identifier(name); + load.result = getResultParam(target); + addInstruction(load); +} + +void InstructionSelection::setProperty(IR::Expr *source, IR::Temp *targetBase, const QString &targetName) +{ + Instruction::StoreProperty store; + store.base = getParam(targetBase); + store.name = identifier(targetName); + store.source = getParam(source); + addInstruction(store); +} + +void InstructionSelection::getElement(IR::Temp *base, IR::Temp *index, IR::Temp *target) +{ + Instruction::LoadElement load; + load.base = getParam(base); + load.index = getParam(index); + load.result = getResultParam(target); + addInstruction(load); +} + +void InstructionSelection::setElement(IR::Expr *source, IR::Temp *targetBase, IR::Temp *targetIndex) +{ + Instruction::StoreElement store; + store.base = getParam(targetBase); + store.index = getParam(targetIndex); + store.source = getParam(source); + addInstruction(store); +} + +void InstructionSelection::copyValue(IR::Temp *sourceTemp, IR::Temp *targetTemp) +{ + Instruction::MoveTemp move; + move.source = getParam(sourceTemp); + move.result = getResultParam(targetTemp); + addInstruction(move); +} + +void InstructionSelection::unop(IR::AluOp oper, IR::Temp *sourceTemp, IR::Temp *targetTemp) +{ + VM::Value (*op)(const VM::Value value, VM::ExecutionContext *ctx) = 0; + switch (oper) { + case IR::OpIfTrue: assert(!"unreachable"); break; + case IR::OpNot: op = VM::__qmljs_not; break; + case IR::OpUMinus: op = VM::__qmljs_uminus; break; + case IR::OpUPlus: op = VM::__qmljs_uplus; break; + case IR::OpCompl: op = VM::__qmljs_compl; break; + case IR::OpIncrement: op = VM::__qmljs_increment; break; + case IR::OpDecrement: op = VM::__qmljs_decrement; break; + default: assert(!"unreachable"); break; + } // switch + + if (op) { + Instruction::Unop unop; + unop.alu = op; + unop.source = getParam(sourceTemp); + unop.result = getResultParam(targetTemp); + addInstruction(unop); + } else { + qWarning(" UNOP1"); + } +} + +void InstructionSelection::binop(IR::AluOp oper, IR::Expr *leftSource, IR::Expr *rightSource, IR::Temp *target) +{ + Instruction::Binop binop; + binop.alu = aluOpFunction(oper); + binop.lhs = getParam(leftSource); + binop.rhs = getParam(rightSource); + binop.result = getResultParam(target); + addInstruction(binop); +} + +void InstructionSelection::inplaceNameOp(IR::AluOp oper, IR::Expr *sourceExpr, const QString &targetName) +{ + void (*op)(VM::Value value, VM::String *name, VM::ExecutionContext *ctx) = 0; + switch (oper) { + case IR::OpBitAnd: op = VM::__qmljs_inplace_bit_and_name; break; + case IR::OpBitOr: op = VM::__qmljs_inplace_bit_or_name; break; + case IR::OpBitXor: op = VM::__qmljs_inplace_bit_xor_name; break; + case IR::OpAdd: op = VM::__qmljs_inplace_add_name; break; + case IR::OpSub: op = VM::__qmljs_inplace_sub_name; break; + case IR::OpMul: op = VM::__qmljs_inplace_mul_name; break; + case IR::OpDiv: op = VM::__qmljs_inplace_div_name; break; + case IR::OpMod: op = VM::__qmljs_inplace_mod_name; break; + case IR::OpLShift: op = VM::__qmljs_inplace_shl_name; break; + case IR::OpRShift: op = VM::__qmljs_inplace_shr_name; break; + case IR::OpURShift: op = VM::__qmljs_inplace_ushr_name; break; + default: break; + } + + if (op) { + Instruction::InplaceNameOp ieo; + ieo.alu = op; + ieo.name = identifier(targetName); + ieo.source = getParam(sourceExpr); + addInstruction(ieo); + } +} + +void InstructionSelection::inplaceElementOp(IR::AluOp oper, IR::Expr *sourceExpr, IR::Temp *targetBaseTemp, IR::Temp *targetIndexTemp) +{ + void (*op)(VM::Value base, VM::Value index, VM::Value value, VM::ExecutionContext *ctx) = 0; + switch (oper) { + case IR::OpBitAnd: op = VM::__qmljs_inplace_bit_and_element; break; + case IR::OpBitOr: op = VM::__qmljs_inplace_bit_or_element; break; + case IR::OpBitXor: op = VM::__qmljs_inplace_bit_xor_element; break; + case IR::OpAdd: op = VM::__qmljs_inplace_add_element; break; + case IR::OpSub: op = VM::__qmljs_inplace_sub_element; break; + case IR::OpMul: op = VM::__qmljs_inplace_mul_element; break; + case IR::OpDiv: op = VM::__qmljs_inplace_div_element; break; + case IR::OpMod: op = VM::__qmljs_inplace_mod_element; break; + case IR::OpLShift: op = VM::__qmljs_inplace_shl_element; break; + case IR::OpRShift: op = VM::__qmljs_inplace_shr_element; break; + case IR::OpURShift: op = VM::__qmljs_inplace_ushr_element; break; + default: break; + } + + Instruction::InplaceElementOp ieo; + ieo.alu = op; + ieo.base = getParam(targetBaseTemp); + ieo.index = getParam(targetIndexTemp); + ieo.source = getParam(sourceExpr); + addInstruction(ieo); +} + +void InstructionSelection::inplaceMemberOp(IR::AluOp oper, IR::Expr *source, IR::Temp *targetBase, const QString &targetName) +{ + void (*op)(VM::Value value, VM::Value base, VM::String *name, VM::ExecutionContext *ctx) = 0; + switch (oper) { + case IR::OpBitAnd: op = VM::__qmljs_inplace_bit_and_member; break; + case IR::OpBitOr: op = VM::__qmljs_inplace_bit_or_member; break; + case IR::OpBitXor: op = VM::__qmljs_inplace_bit_xor_member; break; + case IR::OpAdd: op = VM::__qmljs_inplace_add_member; break; + case IR::OpSub: op = VM::__qmljs_inplace_sub_member; break; + case IR::OpMul: op = VM::__qmljs_inplace_mul_member; break; + case IR::OpDiv: op = VM::__qmljs_inplace_div_member; break; + case IR::OpMod: op = VM::__qmljs_inplace_mod_member; break; + case IR::OpLShift: op = VM::__qmljs_inplace_shl_member; break; + case IR::OpRShift: op = VM::__qmljs_inplace_shr_member; break; + case IR::OpURShift: op = VM::__qmljs_inplace_ushr_member; break; + default: break; + } + + Instruction::InplaceMemberOp imo; + imo.alu = op; + imo.base = getParam(targetBase); + imo.member = identifier(targetName); + imo.source = getParam(source); + addInstruction(imo); +} + +void InstructionSelection::prepareCallArgs(IR::ExprList *e, quint32 &argc, quint32 &args) +{ + bool singleArgIsTemp = false; + if (e && e->next == 0) { + // ok, only 1 argument in the call... + const int idx = e->expr->asTemp()->index; + // We can only pass a reference into the stack, which holds temps that + // are not arguments (idx >= 0) nor locals (idx >= localCound). + singleArgIsTemp = idx >= _function->locals.size(); + } + + if (singleArgIsTemp) { + // We pass single arguments as references to the stack, but only if it's not a local or an argument. + argc = 1; + args = e->expr->asTemp()->index - _function->locals.size(); + } else if (e) { + // We need to move all the temps into the function arg array + int argLocation = outgoingArgumentTempStart(); + assert(argLocation >= 0); + argc = 0; + args = argLocation; + while (e) { + Instruction::MoveTemp move; + move.source = getParam(e->expr); + move.result = Instr::Param::createTemp(argLocation); + addInstruction(move); + ++argLocation; + ++argc; + e = e->next; + } + } else { + argc = 0; + args = 0; + } +} + +void InstructionSelection::visitJump(IR::Jump *s) +{ + Instruction::Jump jump; + jump.offset = 0; + ptrdiff_t loc = addInstruction(jump) + (((const char *)&jump.offset) - ((const char *)&jump)); + + _patches[s->target].append(loc); +} + +void InstructionSelection::visitCJump(IR::CJump *s) +{ + Instr::Param condition; + if (IR::Temp *t = s->cond->asTemp()) { + condition = getResultParam(t); + } else if (IR::Binop *b = s->cond->asBinop()) { + condition = getResultParam(0); + Instruction::Binop binop; + binop.alu = aluOpFunction(b->op); + binop.lhs = getParam(b->left); + binop.rhs = getParam(b->right); + binop.result = condition; + addInstruction(binop); + } else { + Q_UNIMPLEMENTED(); + } + + Instruction::CJump jump; + jump.offset = 0; + jump.condition = condition; + ptrdiff_t trueLoc = addInstruction(jump) + (((const char *)&jump.offset) - ((const char *)&jump)); + _patches[s->iftrue].append(trueLoc); + + if (_block->index + 1 != s->iffalse->index) { + Instruction::Jump jump; + jump.offset = 0; + ptrdiff_t falseLoc = addInstruction(jump) + (((const char *)&jump.offset) - ((const char *)&jump)); + _patches[s->iffalse].append(falseLoc); + } +} + +void InstructionSelection::visitRet(IR::Ret *s) +{ + Instruction::Ret ret; + ret.result = getParam(s->expr); + addInstruction(ret); +} + +void InstructionSelection::callBuiltinInvalid(IR::Name *func, IR::ExprList *args, IR::Temp *result) +{ + Instruction::CallActivationProperty call; + call.name = identifier(*func->id); + prepareCallArgs(args, call.argc, call.args); + call.result = getResultParam(result); + addInstruction(call); +} + +void InstructionSelection::callBuiltinTypeofMember(IR::Temp *base, const QString &name, IR::Temp *result) +{ + Instruction::CallBuiltinTypeofMember call; + call.base = getParam(base); + call.member = identifier(name); + call.result = getResultParam(result); + addInstruction(call); +} + +void InstructionSelection::callBuiltinTypeofSubscript(IR::Temp *base, IR::Temp *index, IR::Temp *result) +{ + Instruction::CallBuiltinTypeofSubscript call; + call.base = getParam(base); + call.index = getParam(index); + call.result = getResultParam(result); + addInstruction(call); +} + +void InstructionSelection::callBuiltinTypeofName(const QString &name, IR::Temp *result) +{ + Instruction::CallBuiltinTypeofName call; + call.name = identifier(name); + call.result = getResultParam(result); + addInstruction(call); +} + +void InstructionSelection::callBuiltinTypeofValue(IR::Temp *value, IR::Temp *result) +{ + Instruction::CallBuiltinTypeofValue call; + call.value = getParam(value); + call.result = getResultParam(result); + addInstruction(call); +} + +void InstructionSelection::callBuiltinDeleteMember(IR::Temp *base, const QString &name, IR::Temp *result) +{ + Instruction::CallBuiltinDeleteMember call; + call.base = getParam(base); + call.member = identifier(name); + call.result = getResultParam(result); + addInstruction(call); +} + +void InstructionSelection::callBuiltinDeleteSubscript(IR::Temp *base, IR::Temp *index, IR::Temp *result) +{ + Instruction::CallBuiltinDeleteSubscript call; + call.base = getParam(base); + call.index = getParam(index); + call.result = getResultParam(result); + addInstruction(call); +} + +void InstructionSelection::callBuiltinDeleteName(const QString &name, IR::Temp *result) +{ + Instruction::CallBuiltinDeleteName call; + call.name = identifier(name); + call.result = getResultParam(result); + addInstruction(call); +} + +void InstructionSelection::callBuiltinDeleteValue(IR::Temp *result) +{ + Instruction::LoadValue load; + load.value = Instr::Param::createValue(VM::Value::fromBoolean(false)); + load.result = getResultParam(result); + addInstruction(load); +} + +void InstructionSelection::callBuiltinPostDecrementMember(IR::Temp *base, const QString &name, IR::Temp *result) +{ + Instruction::CallBuiltinPostDecMember call; + call.base = getParam(base); + call.member = identifier(name); + call.result = getResultParam(result); + addInstruction(call); +} + +void InstructionSelection::callBuiltinPostDecrementSubscript(IR::Temp *base, IR::Temp *index, IR::Temp *result) +{ + Instruction::CallBuiltinPostDecSubscript call; + call.base = getParam(base); + call.index = getParam(index); + call.result = getResultParam(result); + addInstruction(call); +} + +void InstructionSelection::callBuiltinPostDecrementName(const QString &name, IR::Temp *result) +{ + Instruction::CallBuiltinPostDecName call; + call.name = identifier(name); + call.result = getResultParam(result); + addInstruction(call); +} + +void InstructionSelection::callBuiltinPostDecrementValue(IR::Temp *value, IR::Temp *result) +{ + Instruction::CallBuiltinPostDecValue call; + call.value = getParam(value); + call.result = getResultParam(result); + addInstruction(call); +} + +void InstructionSelection::callBuiltinPostIncrementMember(IR::Temp *base, const QString &name, IR::Temp *result) +{ + Instruction::CallBuiltinPostIncMember call; + call.base = getParam(base); + call.member = identifier(name); + call.result = getResultParam(result); + addInstruction(call); +} + +void InstructionSelection::callBuiltinPostIncrementSubscript(IR::Temp *base, IR::Temp *index, IR::Temp *result) +{ + Instruction::CallBuiltinPostIncSubscript call; + call.base = getParam(base); + call.index = getParam(index); + call.result = getResultParam(result); + addInstruction(call); +} + +void InstructionSelection::callBuiltinPostIncrementName(const QString &name, IR::Temp *result) +{ + Instruction::CallBuiltinPostIncName call; + call.name = identifier(name); + call.result = getResultParam(result); + addInstruction(call); +} + +void InstructionSelection::callBuiltinPostIncrementValue(IR::Temp *value, IR::Temp *result) +{ + Instruction::CallBuiltinPostIncValue call; + call.value = getParam(value); + call.result = getResultParam(result); + addInstruction(call); +} + +void InstructionSelection::callBuiltinThrow(IR::Temp *arg) +{ + Instruction::CallBuiltinThrow call; + call.arg = getParam(arg); + addInstruction(call); +} + +void InstructionSelection::callBuiltinCreateExceptionHandler(IR::Temp *result) +{ + Instruction::CallBuiltinCreateExceptionHandler call; + call.result = getResultParam(result); + addInstruction(call); +} + +void InstructionSelection::callBuiltinDeleteExceptionHandler() +{ + Instruction::CallBuiltinDeleteExceptionHandler call; + addInstruction(call); +} + +void InstructionSelection::callBuiltinGetException(IR::Temp *result) +{ + Instruction::CallBuiltinGetException call; + call.result = getResultParam(result); + addInstruction(call); +} + +void InstructionSelection::callBuiltinForeachIteratorObject(IR::Temp *arg, IR::Temp *result) +{ + Instruction::CallBuiltinForeachIteratorObject call; + call.arg = getParam(arg); + call.result = getResultParam(result); + addInstruction(call); +} + +void InstructionSelection::callBuiltinForeachNextPropertyname(IR::Temp *arg, IR::Temp *result) +{ + Instruction::CallBuiltinForeachNextPropertyName call; + call.arg = getParam(arg); + call.result = getResultParam(result); + addInstruction(call); +} + +void InstructionSelection::callBuiltinPushWithScope(IR::Temp *arg) +{ + Instruction::CallBuiltinPushScope call; + call.arg = getParam(arg); + addInstruction(call); +} + +void InstructionSelection::callBuiltinPopScope() +{ + Instruction::CallBuiltinPopScope call; + addInstruction(call); +} + +void InstructionSelection::callBuiltinDeclareVar(bool deletable, const QString &name) +{ + Instruction::CallBuiltinDeclareVar call; + call.isDeletable = deletable; + call.varName = identifier(name); + addInstruction(call); +} + +void InstructionSelection::callBuiltinDefineGetterSetter(IR::Temp *object, const QString &name, IR::Temp *getter, IR::Temp *setter) +{ + Instruction::CallBuiltinDefineGetterSetter call; + call.object = getParam(object); + call.name = identifier(name); + call.getter = getParam(getter); + call.setter = getParam(setter); + addInstruction(call); +} + +void InstructionSelection::callBuiltinDefineProperty(IR::Temp *object, const QString &name, IR::Temp *value) +{ + Instruction::CallBuiltinDefineProperty call; + call.object = getParam(object); + call.name = identifier(name); + call.value = getParam(value); + addInstruction(call); +} + +void InstructionSelection::callBuiltinDefineArrayProperty(IR::Temp *object, int index, IR::Temp *value) +{ + Instruction::CallBuiltinDefineArrayProperty call; + call.object = getParam(object); + call.index = index; + call.value = getParam(value); + addInstruction(call); +} + +ptrdiff_t InstructionSelection::addInstructionHelper(Instr::Type type, Instr &instr) +{ +#ifdef MOTH_THREADED_INTERPRETER + instr.common.code = VME::instructionJumpTable()[static_cast<int>(type)]; +#else + instr.common.instructionType = type; +#endif + + int instructionSize = Instr::size(type); + if (_codeEnd - _codeNext < instructionSize) { + int currSize = _codeEnd - _codeStart; + uchar *newCode = new uchar[currSize * 2]; + ::memset(newCode + currSize, 0, currSize); + ::memcpy(newCode, _codeStart, currSize); + _codeNext = _codeNext - _codeStart + newCode; + delete[] _codeStart; + _codeStart = newCode; + _codeEnd = _codeStart + currSize * 2; + } + + ::memcpy(_codeNext, reinterpret_cast<const char *>(&instr), instructionSize); + ptrdiff_t ptrOffset = _codeNext - _codeStart; + _codeNext += instructionSize; + + return ptrOffset; +} + +void InstructionSelection::patchJumpAddresses() +{ + typedef QHash<IR::BasicBlock *, QVector<ptrdiff_t> >::ConstIterator PatchIt; + for (PatchIt i = _patches.begin(), ei = _patches.end(); i != ei; ++i) { + Q_ASSERT(_addrs.contains(i.key())); + ptrdiff_t target = _addrs.value(i.key()); + + const QVector<ptrdiff_t> &patchList = i.value(); + for (int ii = 0, eii = patchList.count(); ii < eii; ++ii) { + ptrdiff_t patch = patchList.at(ii); + + *((ptrdiff_t *)(_codeStart + patch)) = target - patch; + } + } + + _patches.clear(); + _addrs.clear(); +} + +uchar *InstructionSelection::squeezeCode() const +{ + int codeSize = _codeNext - _codeStart; + uchar *squeezed = new uchar[codeSize]; + ::memcpy(squeezed, _codeStart, codeSize); + return squeezed; +} + +VM::String *InstructionSelection::identifier(const QString &s) +{ + VM::String *str = engine()->identifier(s); + _vmFunction->identifiers.append(str); + return str; +} diff --git a/src/v4/moth/qv4isel_moth_p.h b/src/v4/moth/qv4isel_moth_p.h new file mode 100644 index 0000000000..0b93ea853f --- /dev/null +++ b/src/v4/moth/qv4isel_moth_p.h @@ -0,0 +1,168 @@ +#ifndef QV4ISEL_MOTH_P_H +#define QV4ISEL_MOTH_P_H + +#include "qv4global.h" +#include "qv4isel_p.h" +#include "qv4ir_p.h" +#include "qv4object.h" +#include "qv4instr_moth_p.h" + +namespace QQmlJS { +namespace Moth { + +class Q_V4_EXPORT InstructionSelection: + public IR::InstructionSelection, + public EvalInstructionSelection +{ +public: + InstructionSelection(VM::ExecutionEngine *engine, IR::Module *module); + ~InstructionSelection(); + + virtual void run(VM::Function *vmFunction, IR::Function *function); + +protected: + virtual void visitJump(IR::Jump *); + virtual void visitCJump(IR::CJump *); + virtual void visitRet(IR::Ret *); + + virtual void callBuiltinInvalid(IR::Name *func, IR::ExprList *args, IR::Temp *result); + virtual void callBuiltinTypeofMember(IR::Temp *base, const QString &name, IR::Temp *result); + virtual void callBuiltinTypeofSubscript(IR::Temp *base, IR::Temp *index, IR::Temp *result); + virtual void callBuiltinTypeofName(const QString &name, IR::Temp *result); + virtual void callBuiltinTypeofValue(IR::Temp *value, IR::Temp *result); + virtual void callBuiltinDeleteMember(IR::Temp *base, const QString &name, IR::Temp *result); + virtual void callBuiltinDeleteSubscript(IR::Temp *base, IR::Temp *index, IR::Temp *result); + virtual void callBuiltinDeleteName(const QString &name, IR::Temp *result); + virtual void callBuiltinDeleteValue(IR::Temp *result); + virtual void callBuiltinPostDecrementMember(IR::Temp *base, const QString &name, IR::Temp *result); + virtual void callBuiltinPostDecrementSubscript(IR::Temp *base, IR::Temp *index, IR::Temp *result); + virtual void callBuiltinPostDecrementName(const QString &name, IR::Temp *result); + virtual void callBuiltinPostDecrementValue(IR::Temp *value, IR::Temp *result); + virtual void callBuiltinPostIncrementMember(IR::Temp *base, const QString &name, IR::Temp *result); + virtual void callBuiltinPostIncrementSubscript(IR::Temp *base, IR::Temp *index, IR::Temp *result); + virtual void callBuiltinPostIncrementName(const QString &name, IR::Temp *result); + virtual void callBuiltinPostIncrementValue(IR::Temp *value, IR::Temp *result); + virtual void callBuiltinThrow(IR::Temp *arg); + virtual void callBuiltinCreateExceptionHandler(IR::Temp *result); + virtual void callBuiltinDeleteExceptionHandler(); + virtual void callBuiltinGetException(IR::Temp *result); + virtual void callBuiltinForeachIteratorObject(IR::Temp *arg, IR::Temp *result); + virtual void callBuiltinForeachNextPropertyname(IR::Temp *arg, IR::Temp *result); + virtual void callBuiltinPushWithScope(IR::Temp *arg); + virtual void callBuiltinPopScope(); + virtual void callBuiltinDeclareVar(bool deletable, const QString &name); + virtual void callBuiltinDefineGetterSetter(IR::Temp *object, const QString &name, IR::Temp *getter, IR::Temp *setter); + virtual void callBuiltinDefineProperty(IR::Temp *object, const QString &name, IR::Temp *value); + virtual void callBuiltinDefineArrayProperty(IR::Temp *object, int index, IR::Temp *value); + virtual void callValue(IR::Temp *value, IR::ExprList *args, IR::Temp *result); + virtual void callProperty(IR::Temp *base, const QString &name, IR::ExprList *args, IR::Temp *result); + virtual void callSubscript(IR::Temp *base, IR::Temp *index, IR::ExprList *args, IR::Temp *result); + virtual void constructActivationProperty(IR::Name *func, IR::ExprList *args, IR::Temp *result); + virtual void constructProperty(IR::Temp *base, const QString &name, IR::ExprList *args, IR::Temp *result); + virtual void constructValue(IR::Temp *value, IR::ExprList *args, IR::Temp *result); + virtual void loadThisObject(IR::Temp *temp); + virtual void loadConst(IR::Const *sourceConst, IR::Temp *targetTemp); + virtual void loadString(const QString &str, IR::Temp *targetTemp); + virtual void loadRegexp(IR::RegExp *sourceRegexp, IR::Temp *targetTemp); + virtual void getActivationProperty(const QString &name, IR::Temp *temp); + virtual void setActivationProperty(IR::Expr *source, const QString &targetName); + virtual void initClosure(IR::Closure *closure, IR::Temp *target); + virtual void getProperty(IR::Temp *base, const QString &name, IR::Temp *target); + virtual void setProperty(IR::Expr *source, IR::Temp *targetBase, const QString &targetName); + virtual void getElement(IR::Temp *base, IR::Temp *index, IR::Temp *target); + virtual void setElement(IR::Expr *source, IR::Temp *targetBase, IR::Temp *targetIndex); + virtual void copyValue(IR::Temp *sourceTemp, IR::Temp *targetTemp); + virtual void unop(IR::AluOp oper, IR::Temp *sourceTemp, IR::Temp *targetTemp); + virtual void binop(IR::AluOp oper, IR::Expr *leftSource, IR::Expr *rightSource, IR::Temp *target); + virtual void inplaceNameOp(IR::AluOp oper, IR::Expr *sourceExpr, const QString &targetName); + virtual void inplaceElementOp(IR::AluOp oper, IR::Expr *sourceExpr, IR::Temp *targetBaseTemp, IR::Temp *targetIndexTemp); + virtual void inplaceMemberOp(IR::AluOp oper, IR::Expr *source, IR::Temp *targetBase, const QString &targetName); + +private: + struct Instruction { +#define MOTH_INSTR_DATA_TYPEDEF(I, FMT) typedef InstrData<Instr::I> I; + FOR_EACH_MOTH_INSTR(MOTH_INSTR_DATA_TYPEDEF) +#undef MOTH_INSTR_DATA_TYPEDEF + private: + Instruction(); + }; + + Instr::Param getParam(IR::Expr *e) + { + Q_ASSERT(e); + + typedef Instr::Param Param; + if (IR::Const *c = e->asConst()) { + return Param::createValue(convertToValue(c)); + } else if (IR::Temp *t = e->asTemp()) { + const int index = t->index; + if (index < 0) { + return Param::createArgument(-index - 1); + } else { + const int localCount = _function->locals.size(); + if (index < localCount) + return Param::createLocal(index); + else + return Param::createTemp(index - localCount); + } + } else { + Q_UNIMPLEMENTED(); + return Param(); + } + } + + Instr::Param getResultParam(IR::Temp *result) + { + if (result) + return getParam(result); + else + return Instr::Param::createTemp(scratchTempIndex()); + } + + void simpleMove(IR::Move *); + void prepareCallArgs(IR::ExprList *, quint32 &, quint32 &); + + int outgoingArgumentTempStart() const { return _function->tempCount - _function->locals.size(); } + int scratchTempIndex() const { return outgoingArgumentTempStart() + _function->maxNumberOfArguments; } + int frameSize() const { return scratchTempIndex() + 1; } + + template <int Instr> + inline ptrdiff_t addInstruction(const InstrData<Instr> &data); + ptrdiff_t addInstructionHelper(Instr::Type type, Instr &instr); + void patchJumpAddresses(); + uchar *squeezeCode() const; + + VM::String *identifier(const QString &s); + + IR::Function *_function; + VM::Function *_vmFunction; + IR::BasicBlock *_block; + + QHash<IR::BasicBlock *, QVector<ptrdiff_t> > _patches; + QHash<IR::BasicBlock *, ptrdiff_t> _addrs; + + uchar *_codeStart; + uchar *_codeNext; + uchar *_codeEnd; +}; + +class Q_V4_EXPORT ISelFactory: public EvalISelFactory +{ +public: + virtual ~ISelFactory() {} + virtual EvalInstructionSelection *create(VM::ExecutionEngine *engine, IR::Module *module) + { return new InstructionSelection(engine, module); } +}; + +template<int InstrT> +ptrdiff_t InstructionSelection::addInstruction(const InstrData<InstrT> &data) +{ + Instr genericInstr; + InstrMeta<InstrT>::setData(genericInstr, data); + return addInstructionHelper(static_cast<Instr::Type>(InstrT), genericInstr); +} + +} // namespace Moth +} // namespace QQmlJS + +#endif // QV4ISEL_MOTH_P_H diff --git a/src/v4/moth/qv4vme_moth.cpp b/src/v4/moth/qv4vme_moth.cpp new file mode 100644 index 0000000000..a3ab1de46a --- /dev/null +++ b/src/v4/moth/qv4vme_moth.cpp @@ -0,0 +1,483 @@ +#include "qv4vme_moth_p.h" +#include "qv4instr_moth_p.h" +#include "qmljs_value.h" +#include "debugging.h" + +#include <iostream> + +#include <alloca.h> + +#ifdef DO_TRACE_INSTR +# define TRACE_INSTR(I) fprintf(stderr, "executing a %s\n", #I); +# define TRACE(n, str, ...) { char buf[4096]; snprintf(buf, 4096, str, __VA_ARGS__); fprintf(stderr, " %s : %s\n", #n, buf); } +#else +# define TRACE_INSTR(I) +# define TRACE(n, str, ...) +#endif // DO_TRACE_INSTR + +using namespace QQmlJS; +using namespace QQmlJS::Moth; + +class FunctionState: public Debugging::FunctionState +{ +public: + FunctionState(QQmlJS::VM::ExecutionContext *context, const uchar **code) + : Debugging::FunctionState(context) + , stack(0) + , stackSize(0) + , code(code) + {} + + virtual VM::Value *temp(unsigned idx) { return stack + idx; } + + void setStack(VM::Value *stack, unsigned stackSize) + { this->stack = stack; this->stackSize = stackSize; } + +private: + VM::Value *stack; + unsigned stackSize; + const uchar **code; +}; + +#define MOTH_BEGIN_INSTR_COMMON(I) { \ + const InstrMeta<(int)Instr::I>::DataType &instr = InstrMeta<(int)Instr::I>::data(*genericInstr); \ + code += InstrMeta<(int)Instr::I>::Size; \ + Q_UNUSED(instr); \ + TRACE_INSTR(I) + +#ifdef MOTH_THREADED_INTERPRETER + +# define MOTH_BEGIN_INSTR(I) op_##I: \ + MOTH_BEGIN_INSTR_COMMON(I) + +# define MOTH_NEXT_INSTR(I) { \ + genericInstr = reinterpret_cast<const Instr *>(code); \ + goto *genericInstr->common.code; \ + } + +# define MOTH_END_INSTR(I) } \ + genericInstr = reinterpret_cast<const Instr *>(code); \ + goto *genericInstr->common.code; \ + +#else + +# define MOTH_BEGIN_INSTR(I) \ + case Instr::I: \ + MOTH_BEGIN_INSTR_COMMON(I) + +# define MOTH_NEXT_INSTR(I) { \ + break; \ + } + +# define MOTH_END_INSTR(I) } \ + break; + +#endif + +static inline VM::Value *getValueRef(QQmlJS::VM::ExecutionContext *context, + VM::Value* stack, + const Instr::Param ¶m +#if !defined(QT_NO_DEBUG) + , unsigned stackSize +#endif + ) +{ +#ifdef DO_TRACE_INSTR + if (param.isValue()) { + fprintf(stderr, " value\n"); + } else if (param.isArgument()) { + fprintf(stderr, " argument %d\n", param.index); + } else if (param.isLocal()) { + fprintf(stderr, " local %d\n", param.index); + } else if (param.isTemp()) { + fprintf(stderr, " temp %d\n", param.index); + } else { + Q_ASSERT(!"INVALID"); + } +#endif // DO_TRACE_INSTR + + if (param.isValue()) { + return const_cast<VM::Value *>(¶m.value); + } else if (param.isArgument()) { + const unsigned arg = param.index; + Q_ASSERT(arg >= 0); + Q_ASSERT((unsigned) arg < context->argumentCount); + Q_ASSERT(context->arguments); + return context->arguments + arg; + } else if (param.isLocal()) { + const unsigned index = param.index; + Q_ASSERT(index >= 0); + Q_ASSERT(index < context->variableCount()); + Q_ASSERT(context->locals); + return context->locals + index; + } else if (param.isTemp()) { + Q_ASSERT(param.index < stackSize); + return stack + param.index; + } else { + Q_UNIMPLEMENTED(); + return 0; + } +} + +#if defined(QT_NO_DEBUG) +# define VALUE(param) *getValueRef(context, stack, param) +# define VALUEPTR(param) getValueRef(context, stack, param) +#else +# define VALUE(param) *getValueRef(context, stack, param, stackSize) +# define VALUEPTR(param) getValueRef(context, stack, param, stackSize) +#endif + +VM::Value VME::operator()(QQmlJS::VM::ExecutionContext *context, const uchar *code +#ifdef MOTH_THREADED_INTERPRETER + , void ***storeJumpTable +#endif + ) +{ +#ifdef DO_TRACE_INSTR + qDebug("Starting VME with context=%p and code=%p", context, code); +#endif // DO_TRACE_INSTR + +#ifdef MOTH_THREADED_INTERPRETER + if (storeJumpTable) { +#define MOTH_INSTR_ADDR(I, FMT) &&op_##I, + static void *jumpTable[] = { + FOR_EACH_MOTH_INSTR(MOTH_INSTR_ADDR) + }; +#undef MOTH_INSTR_ADDR + *storeJumpTable = jumpTable; + return VM::Value::undefinedValue(); + } +#endif + + VM::Value *stack = 0; + unsigned stackSize = 0; + FunctionState state(context, &code); + +#ifdef MOTH_THREADED_INTERPRETER + const Instr *genericInstr = reinterpret_cast<const Instr *>(code); + goto *genericInstr->common.code; +#else + for (;;) { + const Instr *genericInstr = reinterpret_cast<const Instr *>(code); + switch (genericInstr->common.instructionType) { +#endif + + MOTH_BEGIN_INSTR(MoveTemp) + VALUE(instr.result) = VALUE(instr.source); + MOTH_END_INSTR(MoveTemp) + + MOTH_BEGIN_INSTR(LoadValue) +// TRACE(value, "%s", instr.value.toString(context)->toQString().toUtf8().constData()); + VALUE(instr.result) = VALUE(instr.value); + MOTH_END_INSTR(LoadValue) + + MOTH_BEGIN_INSTR(LoadClosure) + VALUE(instr.result) = __qmljs_init_closure(instr.value, context); + MOTH_END_INSTR(LoadClosure) + + MOTH_BEGIN_INSTR(LoadName) + TRACE(inline, "property name = %s", instr.name->toQString().toUtf8().constData()); + VALUE(instr.result) = __qmljs_get_activation_property(context, instr.name); + MOTH_END_INSTR(LoadName) + + MOTH_BEGIN_INSTR(StoreName) + TRACE(inline, "property name = %s", instr.name->toQString().toUtf8().constData()); + __qmljs_set_activation_property(context, instr.name, VALUE(instr.source)); + MOTH_END_INSTR(StoreName) + + MOTH_BEGIN_INSTR(LoadElement) + VALUE(instr.result) = __qmljs_get_element(context, VALUE(instr.base), VALUE(instr.index)); + MOTH_END_INSTR(LoadElement) + + MOTH_BEGIN_INSTR(StoreElement) + __qmljs_set_element(context, VALUE(instr.base), VALUE(instr.index), VALUE(instr.source)); + MOTH_END_INSTR(StoreElement) + + MOTH_BEGIN_INSTR(LoadProperty) + VALUE(instr.result) = __qmljs_get_property(context, VALUE(instr.base), instr.name); + MOTH_END_INSTR(LoadProperty) + + MOTH_BEGIN_INSTR(StoreProperty) + __qmljs_set_property(context, VALUE(instr.base), instr.name, VALUE(instr.source)); + MOTH_END_INSTR(StoreProperty) + + MOTH_BEGIN_INSTR(Push) + TRACE(inline, "stack size: %u", instr.value); + stackSize = instr.value; + stack = static_cast<VM::Value *>(alloca(stackSize * sizeof(VM::Value))); + state.setStack(stack, stackSize); + MOTH_END_INSTR(Push) + + MOTH_BEGIN_INSTR(CallValue) +#ifdef DO_TRACE_INSTR + if (Debugging::Debugger *debugger = context->engine->debugger) { + if (VM::FunctionObject *o = (VALUE(instr.dest)).asFunctionObject()) { + if (Debugging::FunctionDebugInfo *info = debugger->debugInfo(o)) { + QString n = debugger->name(o); + std::cerr << "*** Call to \"" << (n.isNull() ? "<no name>" : qPrintable(n)) << "\" defined @" << info->startLine << ":" << info->startColumn << std::endl; + } + } + } +#endif // DO_TRACE_INSTR + Q_ASSERT(instr.args + instr.argc < stackSize); + VM::Value *args = stack + instr.args; + VALUE(instr.result) = __qmljs_call_value(context, VM::Value::undefinedValue(), VALUE(instr.dest), args, instr.argc); + MOTH_END_INSTR(CallValue) + + MOTH_BEGIN_INSTR(CallProperty) + TRACE(property name, "%s", qPrintable(instr.name->toQString())); + Q_ASSERT(instr.args + instr.argc < stackSize); + VM::Value *args = stack + instr.args; + VALUE(instr.result) = __qmljs_call_property(context, VALUE(instr.base), instr.name, args, instr.argc); + MOTH_END_INSTR(CallProperty) + + MOTH_BEGIN_INSTR(CallElement) + Q_ASSERT(instr.args + instr.argc < stackSize); + VM::Value *args = stack + instr.args; + VALUE(instr.result) = __qmljs_call_element(context, VALUE(instr.base), VALUE(instr.index), args, instr.argc); + MOTH_END_INSTR(CallProperty) + + MOTH_BEGIN_INSTR(CallActivationProperty) + Q_ASSERT(instr.args + instr.argc < stackSize); + VM::Value *args = stack + instr.args; + VALUE(instr.result) = __qmljs_call_activation_property(context, instr.name, args, instr.argc); + MOTH_END_INSTR(CallActivationProperty) + + MOTH_BEGIN_INSTR(CallBuiltinThrow) + __qmljs_builtin_throw(VALUE(instr.arg), context); + MOTH_END_INSTR(CallBuiltinThrow) + + MOTH_BEGIN_INSTR(CallBuiltinCreateExceptionHandler) + void *buf = __qmljs_create_exception_handler(context); + // The result is the only value we need from the instr to + // continue execution when an exception is caught. + VM::Value *result = getValueRef(context, stack, instr.result +#if !defined(QT_NO_DEBUG) + , stackSize +#endif + ); + int didThrow = setjmp(* static_cast<jmp_buf *>(buf)); + // Two ways to come here: after a create, or after a throw. + if (didThrow) + // At this point, the interpreter state can be anything but + // valid, so first restore the state. + restoreState(context, result, code); + else + // Save the state and any variables we need when catching an + // exception, so we can restore the state at that point. + saveState(context, result, code); + *result = VM::Value::fromInt32(didThrow); + MOTH_END_INSTR(CallBuiltinCreateExceptionHandler) + + MOTH_BEGIN_INSTR(CallBuiltinDeleteExceptionHandler) + __qmljs_delete_exception_handler(context); + MOTH_END_INSTR(CallBuiltinDeleteExceptionHandler) + + MOTH_BEGIN_INSTR(CallBuiltinGetException) + VALUE(instr.result) = __qmljs_get_exception(context); + MOTH_END_INSTR(CallBuiltinGetException) + + MOTH_BEGIN_INSTR(CallBuiltinPushScope) + context = __qmljs_builtin_push_with_scope(VALUE(instr.arg), context); + MOTH_END_INSTR(CallBuiltinPushScope) + + MOTH_BEGIN_INSTR(CallBuiltinPopScope) + context = __qmljs_builtin_pop_scope(context); + MOTH_END_INSTR(CallBuiltinPopScope) + + MOTH_BEGIN_INSTR(CallBuiltinForeachIteratorObject) + VALUE(instr.result) = __qmljs_foreach_iterator_object(VALUE(instr.arg), context); + MOTH_END_INSTR(CallBuiltinForeachIteratorObject) + + MOTH_BEGIN_INSTR(CallBuiltinForeachNextPropertyName) + VALUE(instr.result) = __qmljs_foreach_next_property_name(VALUE(instr.arg)); + MOTH_END_INSTR(CallBuiltinForeachNextPropertyName) + + MOTH_BEGIN_INSTR(CallBuiltinDeleteMember) + VALUE(instr.result) = __qmljs_delete_member(context, VALUE(instr.base), instr.member); + MOTH_END_INSTR(CallBuiltinDeleteMember) + + MOTH_BEGIN_INSTR(CallBuiltinDeleteSubscript) + VALUE(instr.result) = __qmljs_delete_subscript(context, VALUE(instr.base), VALUE(instr.index)); + MOTH_END_INSTR(CallBuiltinDeleteSubscript) + + MOTH_BEGIN_INSTR(CallBuiltinDeleteName) + VALUE(instr.result) = __qmljs_delete_name(context, instr.name); + MOTH_END_INSTR(CallBuiltinDeleteName) + + MOTH_BEGIN_INSTR(CallBuiltinTypeofMember) + VALUE(instr.result) = __qmljs_builtin_typeof_member(VALUE(instr.base), instr.member, context); + MOTH_END_INSTR(CallBuiltinTypeofMember) + + MOTH_BEGIN_INSTR(CallBuiltinTypeofSubscript) + VALUE(instr.result) = __qmljs_builtin_typeof_element(VALUE(instr.base), VALUE(instr.index), context); + MOTH_END_INSTR(CallBuiltinTypeofSubscript) + + MOTH_BEGIN_INSTR(CallBuiltinTypeofName) + VALUE(instr.result) = __qmljs_builtin_typeof_name(instr.name, context); + MOTH_END_INSTR(CallBuiltinTypeofName) + + MOTH_BEGIN_INSTR(CallBuiltinTypeofValue) + VALUE(instr.result) = __qmljs_builtin_typeof(VALUE(instr.value), context); + MOTH_END_INSTR(CallBuiltinTypeofValue) + + MOTH_BEGIN_INSTR(CallBuiltinPostIncMember) + VALUE(instr.result) = __qmljs_builtin_post_increment_member(VALUE(instr.base), instr.member, context); + MOTH_END_INSTR(CallBuiltinTypeofMember) + + MOTH_BEGIN_INSTR(CallBuiltinPostIncSubscript) + VALUE(instr.result) = __qmljs_builtin_post_increment_element(VALUE(instr.base), VALUE(instr.index), context); + MOTH_END_INSTR(CallBuiltinTypeofSubscript) + + MOTH_BEGIN_INSTR(CallBuiltinPostIncName) + VALUE(instr.result) = __qmljs_builtin_post_increment_name(instr.name, context); + MOTH_END_INSTR(CallBuiltinTypeofName) + + MOTH_BEGIN_INSTR(CallBuiltinPostIncValue) + VALUE(instr.result) = __qmljs_builtin_post_increment(VALUEPTR(instr.value), context); + MOTH_END_INSTR(CallBuiltinTypeofValue) + + MOTH_BEGIN_INSTR(CallBuiltinPostDecMember) + VALUE(instr.result) = __qmljs_builtin_post_decrement_member(VALUE(instr.base), instr.member, context); + MOTH_END_INSTR(CallBuiltinTypeofMember) + + MOTH_BEGIN_INSTR(CallBuiltinPostDecSubscript) + VALUE(instr.result) = __qmljs_builtin_post_decrement_element(VALUE(instr.base), VALUE(instr.index), context); + MOTH_END_INSTR(CallBuiltinTypeofSubscript) + + MOTH_BEGIN_INSTR(CallBuiltinPostDecName) + VALUE(instr.result) = __qmljs_builtin_post_decrement_name(instr.name, context); + MOTH_END_INSTR(CallBuiltinTypeofName) + + MOTH_BEGIN_INSTR(CallBuiltinPostDecValue) + VALUE(instr.result) = __qmljs_builtin_post_decrement(VALUEPTR(instr.value), context); + MOTH_END_INSTR(CallBuiltinTypeofValue) + + MOTH_BEGIN_INSTR(CallBuiltinDeclareVar) + __qmljs_builtin_declare_var(context, instr.isDeletable, instr.varName); + MOTH_END_INSTR(CallBuiltinDeclareVar) + + MOTH_BEGIN_INSTR(CallBuiltinDefineGetterSetter) + __qmljs_builtin_define_getter_setter(VALUE(instr.object), instr.name, VALUE(instr.getter), VALUE(instr.setter), context); + MOTH_END_INSTR(CallBuiltinDefineGetterSetter) + + MOTH_BEGIN_INSTR(CallBuiltinDefineProperty) + __qmljs_builtin_define_property(VALUE(instr.object), instr.name, VALUE(instr.value), context); + MOTH_END_INSTR(CallBuiltinDefineProperty) + + MOTH_BEGIN_INSTR(CallBuiltinDefineArrayProperty) + __qmljs_builtin_define_array_property(VALUE(instr.object), instr.index, VALUE(instr.value), context); + MOTH_END_INSTR(CallBuiltinDefineArrayProperty) + + MOTH_BEGIN_INSTR(CreateValue) + Q_ASSERT(instr.args + instr.argc < stackSize); + VM::Value *args = stack + instr.args; + VALUE(instr.result) = __qmljs_construct_value(context, VALUE(instr.func), args, instr.argc); + MOTH_END_INSTR(CreateValue) + + MOTH_BEGIN_INSTR(CreateProperty) + Q_ASSERT(instr.args + instr.argc < stackSize); + VM::Value *args = stack + instr.args; + VALUE(instr.result) = __qmljs_construct_property(context, VALUE(instr.base), instr.name, args, instr.argc); + MOTH_END_INSTR(CreateProperty) + + MOTH_BEGIN_INSTR(CreateActivationProperty) + TRACE(inline, "property name = %s, args = %d, argc = %d", instr.name->toQString().toUtf8().constData(), instr.args, instr.argc); + Q_ASSERT(instr.args + instr.argc < stackSize); + VM::Value *args = stack + instr.args; + VALUE(instr.result) = __qmljs_construct_activation_property(context, instr.name, args, instr.argc); + MOTH_END_INSTR(CreateActivationProperty) + + MOTH_BEGIN_INSTR(Jump) + code = ((uchar *)&instr.offset) + instr.offset; + MOTH_END_INSTR(Jump) + + MOTH_BEGIN_INSTR(CJump) + uint cond = __qmljs_to_boolean(VALUE(instr.condition), context); + TRACE(condition, "%s", cond ? "TRUE" : "FALSE"); + if (cond) + code = ((uchar *)&instr.offset) + instr.offset; + MOTH_END_INSTR(CJump) + + MOTH_BEGIN_INSTR(Unop) + VALUE(instr.result) = instr.alu(VALUE(instr.source), context); + MOTH_END_INSTR(Unop) + + MOTH_BEGIN_INSTR(Binop) + VALUE(instr.result) = instr.alu(VALUE(instr.lhs), VALUE(instr.rhs), context); + MOTH_END_INSTR(Binop) + + MOTH_BEGIN_INSTR(Ret) + VM::Value &result = VALUE(instr.result); +// TRACE(Ret, "returning value %s", result.toString(context)->toQString().toUtf8().constData()); + return result; + MOTH_END_INSTR(Ret) + + MOTH_BEGIN_INSTR(LoadThis) + VALUE(instr.result) = __qmljs_get_thisObject(context); + MOTH_END_INSTR(LoadThis) + + MOTH_BEGIN_INSTR(InplaceElementOp) + instr.alu(VALUE(instr.base), + VALUE(instr.index), + VALUE(instr.source), + context); + MOTH_END_INSTR(InplaceElementOp) + + MOTH_BEGIN_INSTR(InplaceMemberOp) + instr.alu(VALUE(instr.source), + VALUE(instr.base), + instr.member, + context); + MOTH_END_INSTR(InplaceMemberOp) + + MOTH_BEGIN_INSTR(InplaceNameOp) + TRACE(name, "%s", instr.name->toQString().toUtf8().constData()); + instr.alu(VALUE(instr.source), + instr.name, + context); + MOTH_END_INSTR(InplaceNameOp) + +#ifdef MOTH_THREADED_INTERPRETER + // nothing to do +#else + default: + qFatal("QQmlJS::Moth::VME: Internal error - unknown instruction %d", genericInstr->common.instructionType); + break; + } + } +#endif + +} + +#ifdef MOTH_THREADED_INTERPRETER +void **VME::instructionJumpTable() +{ + static void **jumpTable = 0; + if (!jumpTable) { + VME dummy; + dummy(0, 0, &jumpTable); + } + return jumpTable; +} +#endif + +VM::Value VME::exec(VM::ExecutionContext *ctxt, const uchar *code) +{ + VME vme; + return vme(ctxt, code); +} + +void VME::restoreState(VM::ExecutionContext *context, VM::Value *&target, const uchar *&code) +{ + VM::ExecutionEngine::ExceptionHandler &handler = context->engine->unwindStack.last(); + target = handler.target; + code = handler.code; +} + +void VME::saveState(VM::ExecutionContext *context, VM::Value *target, const uchar *code) +{ + VM::ExecutionEngine::ExceptionHandler &handler = context->engine->unwindStack.last(); + handler.target = target; + handler.code = code; +} diff --git a/src/v4/moth/qv4vme_moth_p.h b/src/v4/moth/qv4vme_moth_p.h new file mode 100644 index 0000000000..2fd877f7b9 --- /dev/null +++ b/src/v4/moth/qv4vme_moth_p.h @@ -0,0 +1,37 @@ +#ifndef QV4VME_MOTH_P_H +#define QV4VME_MOTH_P_H + +#include "qmljs_runtime.h" +#include "qv4instr_moth_p.h" + +namespace QQmlJS { +namespace VM { + struct Value; +} + +namespace Moth { + +class VME +{ +public: + static VM::Value exec(VM::ExecutionContext *, const uchar *); + + VM::Value operator()(QQmlJS::VM::ExecutionContext *, const uchar *code +#ifdef MOTH_THREADED_INTERPRETER + , void ***storeJumpTable = 0 +#endif + ); + +#ifdef MOTH_THREADED_INTERPRETER + static void **instructionJumpTable(); +#endif + +private: + static void restoreState(VM::ExecutionContext *context, VM::Value *&target, const uchar *&code); + static void saveState(VM::ExecutionContext *context, VM::Value *target, const uchar *code); +}; + +} // namespace Moth +} // namespace QQmlJS + +#endif // QV4VME_MOTH_P_H diff --git a/src/v4/qmljs_engine.cpp b/src/v4/qmljs_engine.cpp new file mode 100644 index 0000000000..0b1b14b13e --- /dev/null +++ b/src/v4/qmljs_engine.cpp @@ -0,0 +1,493 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <qmljs_engine.h> +#include <qmljs_value.h> +#include <qv4object.h> +#include <qv4objectproto.h> +#include <qv4arrayobject.h> +#include <qv4booleanobject.h> +#include <qv4globalobject.h> +#include <qv4errorobject.h> +#include <qv4functionobject.h> +#include <qv4mathobject.h> +#include <qv4numberobject.h> +#include <qv4regexpobject.h> +#include <qmljs_runtime.h> +#include "qv4mm.h" +#include <qv4argumentsobject.h> +#include <qv4dateobject.h> +#include <qv4jsonobject.h> +#include <qv4stringobject.h> + +namespace QQmlJS { +namespace VM { + +ExecutionEngine::ExecutionEngine(EvalISelFactory *factory) + : memoryManager(new QQmlJS::VM::MemoryManager) + , iselFactory(factory) + , debugger(0) + , globalObject(Value::nullValue()) + , globalCode(0) + , exception(Value::nullValue()) +{ + MemoryManager::GCBlocker gcBlocker(memoryManager); + + memoryManager->setExecutionEngine(this); + + rootContext = newContext(); + rootContext->init(this); + current = rootContext; + + id_length = identifier(QStringLiteral("length")); + id_prototype = identifier(QStringLiteral("prototype")); + id_constructor = identifier(QStringLiteral("constructor")); + id_arguments = identifier(QStringLiteral("arguments")); + id_caller = identifier(QStringLiteral("caller")); + id_this = identifier(QStringLiteral("this")); + id___proto__ = identifier(QStringLiteral("__proto__")); + id_enumerable = identifier(QStringLiteral("enumerable")); + id_configurable = identifier(QStringLiteral("configurable")); + id_writable = identifier(QStringLiteral("writable")); + id_value = identifier(QStringLiteral("value")); + id_get = identifier(QStringLiteral("get")); + id_set = identifier(QStringLiteral("set")); + id_eval = identifier(QStringLiteral("eval")); + + objectPrototype = new (memoryManager) ObjectPrototype(); + stringPrototype = new (memoryManager) StringPrototype(rootContext); + numberPrototype = new (memoryManager) NumberPrototype(); + booleanPrototype = new (memoryManager) BooleanPrototype(); + arrayPrototype = new (memoryManager) ArrayPrototype(rootContext); + datePrototype = new (memoryManager) DatePrototype(); + functionPrototype = new (memoryManager) FunctionPrototype(rootContext); + regExpPrototype = new (memoryManager) RegExpPrototype(this); + errorPrototype = new (memoryManager) ErrorPrototype(this); + evalErrorPrototype = new (memoryManager) EvalErrorPrototype(rootContext); + rangeErrorPrototype = new (memoryManager) RangeErrorPrototype(rootContext); + referenceErrorPrototype = new (memoryManager) ReferenceErrorPrototype(rootContext); + syntaxErrorPrototype = new (memoryManager) SyntaxErrorPrototype(rootContext); + typeErrorPrototype = new (memoryManager) TypeErrorPrototype(rootContext); + uRIErrorPrototype = new (memoryManager) URIErrorPrototype(rootContext); + + stringPrototype->prototype = objectPrototype; + numberPrototype->prototype = objectPrototype; + booleanPrototype->prototype = objectPrototype; + arrayPrototype->prototype = objectPrototype; + datePrototype->prototype = objectPrototype; + functionPrototype->prototype = objectPrototype; + regExpPrototype->prototype = objectPrototype; + errorPrototype->prototype = objectPrototype; + evalErrorPrototype->prototype = objectPrototype; + rangeErrorPrototype->prototype = objectPrototype; + referenceErrorPrototype->prototype = objectPrototype; + syntaxErrorPrototype->prototype = objectPrototype; + typeErrorPrototype->prototype = objectPrototype; + uRIErrorPrototype->prototype = objectPrototype; + + objectCtor = Value::fromObject(new (memoryManager) ObjectCtor(rootContext)); + stringCtor = Value::fromObject(new (memoryManager) StringCtor(rootContext)); + numberCtor = Value::fromObject(new (memoryManager) NumberCtor(rootContext)); + booleanCtor = Value::fromObject(new (memoryManager) BooleanCtor(rootContext)); + arrayCtor = Value::fromObject(new (memoryManager) ArrayCtor(rootContext)); + functionCtor = Value::fromObject(new (memoryManager) FunctionCtor(rootContext)); + dateCtor = Value::fromObject(new (memoryManager) DateCtor(rootContext)); + regExpCtor = Value::fromObject(new (memoryManager) RegExpCtor(rootContext)); + errorCtor = Value::fromObject(new (memoryManager) ErrorCtor(rootContext)); + evalErrorCtor = Value::fromObject(new (memoryManager) EvalErrorCtor(rootContext)); + rangeErrorCtor = Value::fromObject(new (memoryManager) RangeErrorCtor(rootContext)); + referenceErrorCtor = Value::fromObject(new (memoryManager) ReferenceErrorCtor(rootContext)); + syntaxErrorCtor = Value::fromObject(new (memoryManager) SyntaxErrorCtor(rootContext)); + typeErrorCtor = Value::fromObject(new (memoryManager) TypeErrorCtor(rootContext)); + uRIErrorCtor = Value::fromObject(new (memoryManager) URIErrorCtor(rootContext)); + + objectCtor.objectValue()->prototype = functionPrototype; + stringCtor.objectValue()->prototype = functionPrototype; + numberCtor.objectValue()->prototype = functionPrototype; + booleanCtor.objectValue()->prototype = functionPrototype; + arrayCtor.objectValue()->prototype = functionPrototype; + functionCtor.objectValue()->prototype = functionPrototype; + dateCtor.objectValue()->prototype = functionPrototype; + regExpCtor.objectValue()->prototype = functionPrototype; + errorCtor.objectValue()->prototype = functionPrototype; + evalErrorCtor.objectValue()->prototype = functionPrototype; + rangeErrorCtor.objectValue()->prototype = functionPrototype; + referenceErrorCtor.objectValue()->prototype = functionPrototype; + syntaxErrorCtor.objectValue()->prototype = functionPrototype; + typeErrorCtor.objectValue()->prototype = functionPrototype; + uRIErrorCtor.objectValue()->prototype = functionPrototype; + + objectPrototype->init(rootContext, objectCtor); + stringPrototype->init(rootContext, stringCtor); + numberPrototype->init(rootContext, numberCtor); + booleanPrototype->init(rootContext, booleanCtor); + arrayPrototype->init(rootContext, arrayCtor); + datePrototype->init(rootContext, dateCtor); + functionPrototype->init(rootContext, functionCtor); + regExpPrototype->init(rootContext, regExpCtor); + errorPrototype->init(rootContext, errorCtor); + evalErrorPrototype->init(rootContext, evalErrorCtor); + rangeErrorPrototype->init(rootContext, rangeErrorCtor); + referenceErrorPrototype->init(rootContext, referenceErrorCtor); + syntaxErrorPrototype->init(rootContext, syntaxErrorCtor); + typeErrorPrototype->init(rootContext, typeErrorCtor); + uRIErrorPrototype->init(rootContext, uRIErrorCtor); + + // + // set up the global object + // + VM::Object *glo = newObject(/*rootContext*/); + globalObject = Value::fromObject(glo); + rootContext->activation = glo; + rootContext->thisObject = Value::fromObject(glo); + + glo->defineDefaultProperty(rootContext, QStringLiteral("Object"), objectCtor); + glo->defineDefaultProperty(rootContext, QStringLiteral("String"), stringCtor); + glo->defineDefaultProperty(rootContext, QStringLiteral("Number"), numberCtor); + glo->defineDefaultProperty(rootContext, QStringLiteral("Boolean"), booleanCtor); + glo->defineDefaultProperty(rootContext, QStringLiteral("Array"), arrayCtor); + glo->defineDefaultProperty(rootContext, QStringLiteral("Function"), functionCtor); + glo->defineDefaultProperty(rootContext, QStringLiteral("Date"), dateCtor); + glo->defineDefaultProperty(rootContext, QStringLiteral("RegExp"), regExpCtor); + glo->defineDefaultProperty(rootContext, QStringLiteral("Error"), errorCtor); + glo->defineDefaultProperty(rootContext, QStringLiteral("EvalError"), evalErrorCtor); + glo->defineDefaultProperty(rootContext, QStringLiteral("RangeError"), rangeErrorCtor); + glo->defineDefaultProperty(rootContext, QStringLiteral("ReferenceError"), referenceErrorCtor); + glo->defineDefaultProperty(rootContext, QStringLiteral("SyntaxError"), syntaxErrorCtor); + glo->defineDefaultProperty(rootContext, QStringLiteral("TypeError"), typeErrorCtor); + glo->defineDefaultProperty(rootContext, QStringLiteral("URIError"), uRIErrorCtor); + glo->defineDefaultProperty(rootContext, QStringLiteral("Math"), Value::fromObject(newMathObject(rootContext))); + glo->defineDefaultProperty(rootContext, QStringLiteral("JSON"), Value::fromObject(new (memoryManager) JsonObject(rootContext))); + + glo->defineReadonlyProperty(this, QStringLiteral("undefined"), Value::undefinedValue()); + glo->defineReadonlyProperty(this, QStringLiteral("NaN"), Value::fromDouble(nan(""))); + glo->defineReadonlyProperty(this, QStringLiteral("Infinity"), Value::fromDouble(INFINITY)); + + evalFunction = new (memoryManager) EvalFunction(rootContext); + glo->defineDefaultProperty(rootContext, QStringLiteral("eval"), Value::fromObject(evalFunction)); + + glo->defineDefaultProperty(rootContext, QStringLiteral("parseInt"), GlobalFunctions::method_parseInt, 2); + glo->defineDefaultProperty(rootContext, QStringLiteral("parseFloat"), GlobalFunctions::method_parseFloat, 1); + glo->defineDefaultProperty(rootContext, QStringLiteral("isNaN"), GlobalFunctions::method_isNaN, 1); + glo->defineDefaultProperty(rootContext, QStringLiteral("isFinite"), GlobalFunctions::method_isFinite, 1); + glo->defineDefaultProperty(rootContext, QStringLiteral("decodeURI"), GlobalFunctions::method_decodeURI, 1); + glo->defineDefaultProperty(rootContext, QStringLiteral("decodeURIComponent"), GlobalFunctions::method_decodeURIComponent, 1); + glo->defineDefaultProperty(rootContext, QStringLiteral("encodeURI"), GlobalFunctions::method_encodeURI, 1); + glo->defineDefaultProperty(rootContext, QStringLiteral("encodeURIComponent"), GlobalFunctions::method_encodeURIComponent, 1); + glo->defineDefaultProperty(rootContext, QStringLiteral("escape"), GlobalFunctions::method_escape, 1); + glo->defineDefaultProperty(rootContext, QStringLiteral("unescape"), GlobalFunctions::method_unescape, 1); +} + +ExecutionEngine::~ExecutionEngine() +{ + delete globalObject.asObject(); + rootContext->destroy(); + delete rootContext; + qDeleteAll(functions); + delete memoryManager; +} + +ExecutionContext *ExecutionEngine::newContext() +{ + return new ExecutionContext(); +} + +String *ExecutionEngine::identifier(const QString &s) +{ + return new (memoryManager) String(s); +} + +Function *ExecutionEngine::newFunction(const QString &name) +{ + VM::Function *f = new VM::Function(identifier(name)); + functions.append(f); + return f; +} + +FunctionObject *ExecutionEngine::newBuiltinFunction(ExecutionContext *scope, String *name, Value (*code)(ExecutionContext *)) +{ + BuiltinFunctionOld *f = new (memoryManager) BuiltinFunctionOld(scope, name, code); + return f; +} + +FunctionObject *ExecutionEngine::newBuiltinFunction(ExecutionContext *scope, String *name, Value (*code)(ExecutionContext *, Value, Value *, int)) +{ + BuiltinFunction *f = new (memoryManager) BuiltinFunction(scope, name, code); + return f; +} + +FunctionObject *ExecutionEngine::newScriptFunction(ExecutionContext *scope, VM::Function *function) +{ + assert(function); + + ScriptFunction *f = new (memoryManager) ScriptFunction(scope, function); + return f; +} + +BoundFunction *ExecutionEngine::newBoundFunction(ExecutionContext *scope, FunctionObject *target, Value boundThis, const QVector<Value> &boundArgs) +{ + assert(target); + + BoundFunction *f = new (memoryManager) BoundFunction(scope, target, boundThis, boundArgs); + return f; +} + + +Object *ExecutionEngine::newObject() +{ + Object *object = new (memoryManager) Object(); + object->prototype = objectPrototype; + return object; +} + +FunctionObject *ExecutionEngine::newObjectCtor(ExecutionContext *ctx) +{ + return new (memoryManager) ObjectCtor(ctx); +} + +String *ExecutionEngine::newString(const QString &s) +{ + return new (memoryManager) String(s); +} + +Object *ExecutionEngine::newStringObject(ExecutionContext *ctx, const Value &value) +{ + StringObject *object = new (memoryManager) StringObject(ctx, value); + object->prototype = stringPrototype; + return object; +} + +FunctionObject *ExecutionEngine::newStringCtor(ExecutionContext *ctx) +{ + return new (memoryManager) StringCtor(ctx); +} + +Object *ExecutionEngine::newNumberObject(const Value &value) +{ + NumberObject *object = new (memoryManager) NumberObject(value); + object->prototype = numberPrototype; + return object; +} + +FunctionObject *ExecutionEngine::newNumberCtor(ExecutionContext *ctx) +{ + return new (memoryManager) NumberCtor(ctx); +} + +Object *ExecutionEngine::newBooleanObject(const Value &value) +{ + Object *object = new (memoryManager) BooleanObject(value); + object->prototype = booleanPrototype; + return object; +} + +FunctionObject *ExecutionEngine::newBooleanCtor(ExecutionContext *ctx) +{ + return new (memoryManager) BooleanCtor(ctx); +} + +Object *ExecutionEngine::newFunctionObject(ExecutionContext *ctx) +{ + Object *object = new (memoryManager) FunctionObject(ctx); + object->prototype = functionPrototype; + return object; +} + +ArrayObject *ExecutionEngine::newArrayObject(ExecutionContext *ctx) +{ + ArrayObject *object = new (memoryManager) ArrayObject(ctx); + object->prototype = arrayPrototype; + return object; +} + +ArrayObject *ExecutionEngine::newArrayObject(ExecutionContext *ctx, const Array &value) +{ + ArrayObject *object = new (memoryManager) ArrayObject(ctx, value); + object->prototype = arrayPrototype; + return object; +} + +FunctionObject *ExecutionEngine::newArrayCtor(ExecutionContext *ctx) +{ + return new (memoryManager) ArrayCtor(ctx); +} + +Object *ExecutionEngine::newDateObject(const Value &value) +{ + Object *object = new (memoryManager) DateObject(value); + object->prototype = datePrototype; + return object; +} + +FunctionObject *ExecutionEngine::newDateCtor(ExecutionContext *ctx) +{ + return new (memoryManager) DateCtor(ctx); +} + +RegExpObject *ExecutionEngine::newRegExpObject(const QString &pattern, int flags) +{ + bool global = (flags & IR::RegExp::RegExp_Global); + bool ignoreCase = false; + bool multiline = false; + if (flags & IR::RegExp::RegExp_IgnoreCase) + ignoreCase = true; + if (flags & IR::RegExp::RegExp_Multiline) + multiline = true; + + return newRegExpObject(RegExp::create(this, pattern, ignoreCase, multiline), global); +} + +RegExpObject *ExecutionEngine::newRegExpObject(PassRefPtr<RegExp> re, bool global) +{ + RegExpObject *object = new (memoryManager) RegExpObject(this, re, global); + object->prototype = regExpPrototype; + return object; +} + +FunctionObject *ExecutionEngine::newRegExpCtor(ExecutionContext *ctx) +{ + return new (memoryManager) RegExpCtor(ctx); +} + +Object *ExecutionEngine::newErrorObject(const Value &value) +{ + ErrorObject *object = new (memoryManager) ErrorObject(this, value); + object->prototype = errorPrototype; + return object; +} + +Object *ExecutionEngine::newSyntaxErrorObject(ExecutionContext *ctx, DiagnosticMessage *message) +{ + return new (memoryManager) SyntaxErrorObject(ctx, message); +} + +Object *ExecutionEngine::newReferenceErrorObject(ExecutionContext *ctx, const QString &message) +{ + return new (memoryManager) ReferenceErrorObject(ctx, message); +} + +Object *ExecutionEngine::newTypeErrorObject(ExecutionContext *ctx, const QString &message) +{ + return new (memoryManager) TypeErrorObject(ctx, message); +} + +Object *ExecutionEngine::newRangeErrorObject(ExecutionContext *ctx, const QString &message) +{ + return new (memoryManager) RangeErrorObject(ctx, message); +} + +Object *ExecutionEngine::newURIErrorObject(ExecutionContext *ctx, Value message) +{ + return new (memoryManager) URIErrorObject(ctx, message); +} + +Object *ExecutionEngine::newMathObject(ExecutionContext *ctx) +{ + MathObject *object = new (memoryManager) MathObject(ctx); + object->prototype = objectPrototype; + return object; +} + +Object *ExecutionEngine::newActivationObject() +{ + return new (memoryManager) Object(); +} + +Object *ExecutionEngine::newForEachIteratorObject(ExecutionContext *ctx, Object *o) +{ + return new (memoryManager) ForEachIteratorObject(ctx, o); +} + +void ExecutionEngine::requireArgumentsAccessors(int n) +{ + if (n <= argumentsAccessors.size()) + return; + + uint oldSize = argumentsAccessors.size(); + argumentsAccessors.resize(n); + for (int i = oldSize; i < n; ++i) { + FunctionObject *get = new (memoryManager) ArgumentsGetterFunction(rootContext, i); + get->prototype = functionPrototype; + FunctionObject *set = new (memoryManager) ArgumentsSetterFunction(rootContext, i); + set->prototype = functionPrototype; + PropertyDescriptor pd = PropertyDescriptor::fromAccessor(get, set); + pd.configurable = PropertyDescriptor::Enabled; + pd.enumberable = PropertyDescriptor::Enabled; + argumentsAccessors[i] = pd; + } +} + +void ExecutionEngine::markObjects() +{ + globalObject.mark(); + + if (globalCode) + globalCode->mark(); + + exception.mark(); + + for (int i = 0; i < argumentsAccessors.size(); ++i) { + const PropertyDescriptor &pd = argumentsAccessors.at(i); + pd.get->mark(); + pd.set->mark(); + } + + + for (int i = 0; i < functions.size(); ++i) + functions.at(i)->mark(); + + id_length->mark(); + id_prototype->mark(); + id_constructor->mark(); + id_arguments->mark(); + id_caller->mark(); + id_this->mark(); + id___proto__->mark(); + id_enumerable->mark(); + id_configurable->mark(); + id_writable->mark(); + id_value->mark(); + id_get->mark(); + id_set->mark(); + id_eval->mark(); +} + +} // namespace VM +} // namespace QQmlJS diff --git a/src/v4/qmljs_engine.h b/src/v4/qmljs_engine.h new file mode 100644 index 0000000000..f7416880a2 --- /dev/null +++ b/src/v4/qmljs_engine.h @@ -0,0 +1,235 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QMLJS_ENGINE_H +#define QMLJS_ENGINE_H + +#include "qv4global.h" +#include "qv4isel_p.h" +#include "qv4object.h" +#include "qmljs_environment.h" +#include <setjmp.h> + +#include <wtf/PassRefPtr.h> +#include <wtf/BumpPointerAllocator.h> + +namespace QQmlJS { + +namespace Debugging { +class Debugger; +} // namespace Debugging + +namespace VM { + +struct Value; +class Array; +struct Function; +struct Object; +struct BooleanObject; +struct NumberObject; +struct StringObject; +struct ArrayObject; +struct DateObject; +struct FunctionObject; +struct BoundFunction; +struct RegExpObject; +struct ErrorObject; +struct ArgumentsObject; +struct ExecutionContext; +struct ExecutionEngine; +class MemoryManager; + +struct ObjectPrototype; +struct StringPrototype; +struct NumberPrototype; +struct BooleanPrototype; +struct ArrayPrototype; +struct FunctionPrototype; +struct DatePrototype; +struct RegExpPrototype; +struct ErrorPrototype; +struct EvalErrorPrototype; +struct RangeErrorPrototype; +struct ReferenceErrorPrototype; +struct SyntaxErrorPrototype; +struct TypeErrorPrototype; +struct URIErrorPrototype; +struct EvalFunction; + +class RegExp; + +struct Q_V4_EXPORT ExecutionEngine +{ + MemoryManager *memoryManager; + EvalISelFactory *iselFactory; + ExecutionContext *current; + ExecutionContext *rootContext; + WTF::BumpPointerAllocator bumperPointerAllocator; // Used by Yarr Regex engine. + + Debugging::Debugger *debugger; + + Value globalObject; + + VM::Function *globalCode; + + Value objectCtor; + Value stringCtor; + Value numberCtor; + Value booleanCtor; + Value arrayCtor; + Value functionCtor; + Value dateCtor; + Value regExpCtor; + Value errorCtor; + Value evalErrorCtor; + Value rangeErrorCtor; + Value referenceErrorCtor; + Value syntaxErrorCtor; + Value typeErrorCtor; + Value uRIErrorCtor; + + ObjectPrototype *objectPrototype; + StringPrototype *stringPrototype; + NumberPrototype *numberPrototype; + BooleanPrototype *booleanPrototype; + ArrayPrototype *arrayPrototype; + FunctionPrototype *functionPrototype; + DatePrototype *datePrototype; + RegExpPrototype *regExpPrototype; + ErrorPrototype *errorPrototype; + EvalErrorPrototype *evalErrorPrototype; + RangeErrorPrototype *rangeErrorPrototype; + ReferenceErrorPrototype *referenceErrorPrototype; + SyntaxErrorPrototype *syntaxErrorPrototype; + TypeErrorPrototype *typeErrorPrototype; + URIErrorPrototype *uRIErrorPrototype; + + EvalFunction *evalFunction; + + QVector<PropertyDescriptor> argumentsAccessors; + + String *id_length; + String *id_prototype; + String *id_constructor; + String *id_arguments; + String *id_caller; + String *id_this; + String *id___proto__; + String *id_enumerable; + String *id_configurable; + String *id_writable; + String *id_value; + String *id_get; + String *id_set; + String *id_eval; + + struct ExceptionHandler { + ExecutionContext *context; + const uchar *code; // Interpreter state + Value *target; // Interpreter state + jmp_buf stackFrame; + }; + + QVector<ExceptionHandler> unwindStack; + Value exception; + + QVector<Function *> functions; + + ExecutionEngine(EvalISelFactory *iselFactory); + ~ExecutionEngine(); + + ExecutionContext *newContext(); + + String *identifier(const QString &s); + + VM::Function *newFunction(const QString &name); + + FunctionObject *newBuiltinFunction(ExecutionContext *scope, String *name, Value (*code)(ExecutionContext *)); + FunctionObject *newBuiltinFunction(ExecutionContext *scope, String *name, Value (*code)(ExecutionContext *, Value, Value *, int)); + FunctionObject *newScriptFunction(ExecutionContext *scope, VM::Function *function); + BoundFunction *newBoundFunction(ExecutionContext *scope, FunctionObject *target, Value boundThis, const QVector<Value> &boundArgs); + + Object *newObject(); + FunctionObject *newObjectCtor(ExecutionContext *ctx); + + String *newString(const QString &s); + Object *newStringObject(ExecutionContext *ctx, const Value &value); + FunctionObject *newStringCtor(ExecutionContext *ctx); + + Object *newNumberObject(const Value &value); + FunctionObject *newNumberCtor(ExecutionContext *ctx); + + Object *newBooleanObject(const Value &value); + FunctionObject *newBooleanCtor(ExecutionContext *ctx); + + Object *newFunctionObject(ExecutionContext *ctx); + + ArrayObject *newArrayObject(ExecutionContext *ctx); + ArrayObject *newArrayObject(ExecutionContext *ctx, const Array &value); + FunctionObject *newArrayCtor(ExecutionContext *ctx); + + Object *newDateObject(const Value &value); + FunctionObject *newDateCtor(ExecutionContext *ctx); + + RegExpObject *newRegExpObject(const QString &pattern, int flags); + RegExpObject *newRegExpObject(PassRefPtr<RegExp> re, bool global); + FunctionObject *newRegExpCtor(ExecutionContext *ctx); + + Object *newErrorObject(const Value &value); + Object *newSyntaxErrorObject(ExecutionContext *ctx, DiagnosticMessage *message); + Object *newReferenceErrorObject(ExecutionContext *ctx, const QString &message); + Object *newTypeErrorObject(ExecutionContext *ctx, const QString &message); + Object *newRangeErrorObject(ExecutionContext *ctx, const QString &message); + Object *newURIErrorObject(ExecutionContext *ctx, Value message); + + Object *newMathObject(ExecutionContext *ctx); + Object *newActivationObject(); + + Object *newForEachIteratorObject(ExecutionContext *ctx, Object *o); + + void requireArgumentsAccessors(int n); + + void markObjects(); +}; + +} // namespace VM +} // namespace QQmlJS + +#endif diff --git a/src/v4/qmljs_environment.cpp b/src/v4/qmljs_environment.cpp new file mode 100644 index 0000000000..9042a435a0 --- /dev/null +++ b/src/v4/qmljs_environment.cpp @@ -0,0 +1,564 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QString> +#include "debugging.h" +#include <qmljs_environment.h> +#include <qv4object.h> +#include <qv4objectproto.h> +#include "qv4mm.h" +#include <qv4argumentsobject.h> + +namespace QQmlJS { +namespace VM { + +DiagnosticMessage::DiagnosticMessage() + : offset(0) + , length(0) + , startLine(0) + , startColumn(0) + , type(0) + , next(0) +{} + +DiagnosticMessage::~DiagnosticMessage() +{ + delete next; +} + +String *DiagnosticMessage::buildFullMessage(ExecutionContext *ctx) const +{ + QString msg; + if (!fileName.isEmpty()) + msg = fileName + QLatin1Char(':'); + msg += QString::number(startLine) + QLatin1Char(':') + QString::number(startColumn) + QLatin1String(": "); + if (type == QQmlJS::VM::DiagnosticMessage::Error) + msg += QLatin1String("error"); + else + msg += QLatin1String("warning"); + msg += ": " + message; + + return ctx->engine->newString(msg); +} + +bool ExecutionContext::hasBinding(String *name) const +{ + if (!function) + return false; + + for (unsigned int i = 0; i < function->varCount; ++i) { + if (function->varList[i]->isEqualTo(name)) + return true; + } + for (unsigned int i = 0; i < function->formalParameterCount; ++i) { + if (function->formalParameterList[i]->isEqualTo(name)) + return true; + } + if (activation) + return activation->__hasProperty__(this, name); + return false; +} + +void ExecutionContext::createMutableBinding(String *name, bool deletable) +{ + if (!activation) + activation = engine->newActivationObject(); + + if (activation->__hasProperty__(this, name)) + return; + PropertyDescriptor desc; + desc.value = Value::undefinedValue(); + desc.type = PropertyDescriptor::Data; + desc.configurable = deletable ? PropertyDescriptor::Enabled : PropertyDescriptor::Disabled; + desc.writable = PropertyDescriptor::Enabled; + desc.enumberable = PropertyDescriptor::Enabled; + activation->__defineOwnProperty__(this, name, &desc); +} + +bool ExecutionContext::setMutableBinding(ExecutionContext *scope, String *name, Value value) +{ + // ### throw if scope->strict is true, and it would change an immutable binding + if (function) { + for (unsigned int i = 0; i < function->varCount; ++i) + if (function->varList[i]->isEqualTo(name)) { + locals[i] = value; + return true; + } + for (int i = (int)function->formalParameterCount - 1; i >= 0; --i) + if (function->formalParameterList[i]->isEqualTo(name)) { + arguments[i] = value; + return true; + } + } + + if (activation && activation->__hasProperty__(scope, name)) { + activation->__put__(scope, name, value); + return true; + } + + return false; +} + +Value ExecutionContext::getBindingValue(ExecutionContext *scope, String *name, bool strict) const +{ + Q_UNUSED(strict); + assert(function); + + if (function) { + for (unsigned int i = 0; i < function->varCount; ++i) + if (function->varList[i]->isEqualTo(name)) + return locals[i]; + for (int i = (int)function->formalParameterCount - 1; i >= 0; --i) + if (function->formalParameterList[i]->isEqualTo(name)) + return arguments[i]; + } + + if (activation) { + bool hasProperty = false; + Value v = activation->__get__(scope, name, &hasProperty); + if (hasProperty) + return v; + } + assert(false); +} + +bool ExecutionContext::deleteBinding(ExecutionContext *scope, String *name) +{ + if (activation) + activation->__delete__(scope, name); + + if (scope->strictMode) + __qmljs_throw_type_error(scope); + return false; +} + +ExecutionContext *ExecutionContext::createWithScope(Object *with) +{ + ExecutionContext *withCtx = engine->newContext(); + withCtx->init(this, with); + engine->current = withCtx; + return withCtx; +} + +ExecutionContext *ExecutionContext::popScope() +{ + assert(engine->current == this); + assert(withObject != 0); + + engine->current = parent; + parent = 0; + return engine->current; +} + +String * const *ExecutionContext::formals() const +{ + return function ? function->formalParameterList : 0; +} + +unsigned int ExecutionContext::formalCount() const +{ + return function ? function->formalParameterCount : 0; +} + +String * const *ExecutionContext::variables() const +{ + return function ? function->varList : 0; +} + +unsigned int ExecutionContext::variableCount() const +{ + return function ? function->varCount : 0; +} + + +void ExecutionContext::init(ExecutionEngine *eng) +{ + engine = eng; + parent = 0; + outer = 0; + thisObject = eng->globalObject; + + function = 0; + arguments = 0; + argumentCount = 0; + locals = 0; + strictMode = false; + activation = 0; + withObject = 0; + + eng->exception = Value::undefinedValue(); +} + +void ExecutionContext::init(ExecutionContext *p, Object *with) +{ + engine = p->engine; + parent = p; + outer = p; + thisObject = p->thisObject; + + function = 0; + arguments = 0; + argumentCount = 0; + locals = 0; + strictMode = false; + activation = 0; + withObject = with; +} + +void ExecutionContext::destroy() +{ + delete[] arguments; + delete[] locals; +} + +bool ExecutionContext::deleteProperty(String *name) +{ + bool hasWith = false; + for (ExecutionContext *ctx = this; ctx; ctx = ctx->outer) { + if (ctx->withObject) { + hasWith = true; + if (ctx->withObject->__hasProperty__(this, name)) + return ctx->withObject->__delete__(this, name); + } else { + if (ctx->activation && ctx->activation->__hasProperty__(this, name)) + return ctx->activation->__delete__(this, name); + } + if (FunctionObject *f = ctx->function) { + if (f->needsActivation || hasWith) { + for (unsigned int i = 0; i < f->varCount; ++i) + if (f->varList[i]->isEqualTo(name)) + return false; + for (int i = (int)f->formalParameterCount - 1; i >= 0; --i) + if (f->formalParameterList[i]->isEqualTo(name)) + return false; + } + } + } + if (strictMode) + throwSyntaxError(0); + return true; +} + +void ExecutionContext::mark() +{ + thisObject.mark(); + if (function) + function->mark(); + for (unsigned arg = 0, lastArg = formalCount(); arg < lastArg; ++arg) + arguments[arg].mark(); + for (unsigned local = 0, lastLocal = variableCount(); local < lastLocal; ++local) + locals[local].mark(); + if (activation) + activation->mark(); + if (withObject) + withObject->mark(); +} + +void ExecutionContext::setProperty(String *name, Value value) +{ +// qDebug() << "=== SetProperty" << value.toString(this)->toQString(); + for (ExecutionContext *ctx = this; ctx; ctx = ctx->outer) { + if (Object *w = ctx->withObject) { +// qDebug() << ctx << "hasWith"; + if (w->__hasProperty__(ctx, name)) { +// qDebug() << " withHasProp"; + w->__put__(ctx, name, value); + return; + } + } else { +// qDebug() << ctx << "setting mutable binding"; + if (ctx->setMutableBinding(this, name, value)) + return; + } + } + if (strictMode || name->isEqualTo(engine->id_this)) + throwReferenceError(Value::fromString(name)); + engine->globalObject.objectValue()->__put__(this, name, value); +} + +Value ExecutionContext::getProperty(String *name) +{ + if (name->isEqualTo(engine->id_this)) + return thisObject; + + bool hasWith = false; +// qDebug() << "=== getProperty" << name->toQString(); + for (ExecutionContext *ctx = this; ctx; ctx = ctx->outer) { + if (Object *w = ctx->withObject) { + hasWith = true; +// qDebug() << ctx << "hasWith"; + bool hasProperty = false; + Value v = w->__get__(ctx, name, &hasProperty); + if (hasProperty) { +// qDebug() << " withHasProp"; + return v; + } + continue; + } + + if (FunctionObject *f = ctx->function) { + if (f->needsActivation || hasWith) { + for (unsigned int i = 0; i < f->varCount; ++i) + if (f->varList[i]->isEqualTo(name)) + return ctx->locals[i]; + for (int i = (int)f->formalParameterCount - 1; i >= 0; --i) + if (f->formalParameterList[i]->isEqualTo(name)) + return ctx->arguments[i]; + } + } + if (ctx->activation) { + bool hasProperty = false; + Value v = ctx->activation->__get__(ctx, name, &hasProperty); + if (hasProperty) + return v; + } + } + throwReferenceError(Value::fromString(name)); + return Value::undefinedValue(); +} + +Value ExecutionContext::getPropertyNoThrow(String *name) +{ + if (name->isEqualTo(engine->id_this)) + return thisObject; + + bool hasWith = false; + for (ExecutionContext *ctx = this; ctx; ctx = ctx->outer) { + if (Object *w = ctx->withObject) { + hasWith = true; + bool hasProperty = false; + Value v = w->__get__(ctx, name, &hasProperty); + if (hasProperty) + return v; + continue; + } + + if (FunctionObject *f = ctx->function) { + if (f->needsActivation || hasWith) { + for (unsigned int i = 0; i < f->varCount; ++i) + if (f->varList[i]->isEqualTo(name)) + return ctx->locals[i]; + for (int i = (int)f->formalParameterCount - 1; i >= 0; --i) + if (f->formalParameterList[i]->isEqualTo(name)) + return ctx->arguments[i]; + } + } + if (ctx->activation) { + bool hasProperty = false; + Value v = ctx->activation->__get__(ctx, name, &hasProperty); + if (hasProperty) + return v; + } + } + return Value::undefinedValue(); +} + +Value ExecutionContext::getPropertyAndBase(String *name, Object **base) +{ + *base = 0; + + if (name->isEqualTo(engine->id_this)) + return thisObject; + + bool hasWith = false; + for (ExecutionContext *ctx = this; ctx; ctx = ctx->outer) { + if (Object *w = ctx->withObject) { + hasWith = true; + bool hasProperty = false; + Value v = w->__get__(ctx, name, &hasProperty); + if (hasProperty) { + *base = w; + return v; + } + continue; + } + + if (FunctionObject *f = ctx->function) { + if (f->needsActivation || hasWith) { + for (unsigned int i = 0; i < f->varCount; ++i) + if (f->varList[i]->isEqualTo(name)) + return ctx->locals[i]; + for (int i = (int)f->formalParameterCount - 1; i >= 0; --i) + if (f->formalParameterList[i]->isEqualTo(name)) + return ctx->arguments[i]; + } + } + if (ctx->activation) { + bool hasProperty = false; + Value v = ctx->activation->__get__(ctx, name, &hasProperty); + if (hasProperty) + return v; + } + } + throwReferenceError(Value::fromString(name)); + return Value::undefinedValue(); +} + + + +void ExecutionContext::inplaceBitOp(Value value, String *name, BinOp op) +{ + Value lhs = getProperty(name); + value = op(lhs, value, this); + setProperty(name, value); +} + +void ExecutionContext::throwError(Value value) +{ + __qmljs_builtin_throw(value, this); +} + +void ExecutionContext::throwError(const QString &message) +{ + Value v = Value::fromString(this, message); + throwError(Value::fromObject(engine->newErrorObject(v))); +} + +void ExecutionContext::throwSyntaxError(DiagnosticMessage *message) +{ + throwError(Value::fromObject(engine->newSyntaxErrorObject(this, message))); +} + +void ExecutionContext::throwTypeError() +{ + throwError(Value::fromObject(engine->newTypeErrorObject(this, QStringLiteral("Type error")))); +} + +void ExecutionContext::throwUnimplemented(const QString &message) +{ + Value v = Value::fromString(this, QStringLiteral("Unimplemented ") + message); + throwError(Value::fromObject(engine->newErrorObject(v))); +} + +void ExecutionContext::throwReferenceError(Value value) +{ + String *s = value.toString(this); + QString msg = s->toQString() + QStringLiteral(" is not defined"); + throwError(Value::fromObject(engine->newReferenceErrorObject(this, msg))); +} + +void ExecutionContext::throwRangeError(Value value) +{ + String *s = value.toString(this); + QString msg = s->toQString() + QStringLiteral(" out of range"); + throwError(Value::fromObject(engine->newRangeErrorObject(this, msg))); +} + +void ExecutionContext::throwURIError(Value msg) +{ + throwError(Value::fromObject(engine->newURIErrorObject(this, msg))); +} + +void ExecutionContext::initCallContext(ExecutionContext *parent, const Value that, FunctionObject *f, Value *args, unsigned argc) +{ + MemoryManager::GCBlocker blockGC(parent->engine->memoryManager); + + engine = parent->engine; + this->parent = parent; + outer = f->scope; + engine->current = this; + + function = f; + strictMode = f->strictMode; + + thisObject = that; + if (!strictMode && !thisObject.isObject()) { + if (thisObject.isUndefined() || thisObject.isNull()) + thisObject = engine->globalObject; + else + thisObject = thisObject.toObject(this); + } + + locals = function->varCount ? reinterpret_cast<Value *>(this + 1) : 0; + if (locals) + std::fill(locals, locals + function->varCount, Value::undefinedValue()); + + arguments = args; + argumentCount = argc; + if (function->needsActivation || argc < function->formalParameterCount){ + argumentCount = qMax(argc, function->formalParameterCount); + arguments = reinterpret_cast<Value *>(this + 1) + function->varCount; + if (argc) + std::copy(args, args + argc, arguments); + if (argc < function->formalParameterCount) + std::fill(arguments + argc, arguments + function->formalParameterCount, Value::undefinedValue()); + } + + + activation = 0; + withObject = 0; + + if (function->usesArgumentsObject) { + ArgumentsObject *args = new (engine->memoryManager) ArgumentsObject(this, function->formalParameterCount, argc); + args->prototype = engine->objectPrototype; + Value arguments = Value::fromObject(args); + createMutableBinding(engine->id_arguments, false); + setMutableBinding(this, engine->id_arguments, arguments); + } + + if (engine->debugger) + engine->debugger->aboutToCall(f, this); +} + +void ExecutionContext::leaveCallContext() +{ + if (!function->needsActivation) + locals = 0; + engine->current = parent; + parent = 0; + + if (engine->debugger) + engine->debugger->justLeft(this); +} + +void ExecutionContext::wireUpPrototype() +{ + assert(thisObject.isObject()); + + Value proto = function->__get__(this, engine->id_prototype); + if (proto.isObject()) + thisObject.objectValue()->prototype = proto.objectValue(); + else + thisObject.objectValue()->prototype = engine->objectPrototype; +} + +} // namespace VM +} // namespace QQmlJS diff --git a/src/v4/qmljs_environment.h b/src/v4/qmljs_environment.h new file mode 100644 index 0000000000..111c6ae239 --- /dev/null +++ b/src/v4/qmljs_environment.h @@ -0,0 +1,150 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QMLJS_ENVIRONMENT_H +#define QMLJS_ENVIRONMENT_H + +#include "qv4global.h" +#include <qmljs_runtime.h> + +namespace QQmlJS { +namespace VM { + +struct Value; +struct Object; +struct ExecutionEngine; +struct ExecutionContext; +struct DeclarativeEnvironment; + +struct Q_V4_EXPORT DiagnosticMessage +{ + enum { Error, Warning }; + + QString fileName; + quint32 offset; + quint32 length; + quint32 startLine; + unsigned startColumn: 31; + unsigned type: 1; + QString message; + DiagnosticMessage *next; + + DiagnosticMessage(); + ~DiagnosticMessage(); + String *buildFullMessage(ExecutionContext *ctx) const; +}; + +struct ExecutionContext +{ + ExecutionEngine *engine; + ExecutionContext *parent; + ExecutionContext *outer; + Value thisObject; + + FunctionObject *function; + + Value *arguments; + unsigned int argumentCount; + Value *locals; + + String * const *formals() const; + unsigned int formalCount() const; + String * const *variables() const; + unsigned int variableCount() const; + + bool strictMode; + + Object *activation; + Object *withObject; + + void init(ExecutionEngine *e); + void init(ExecutionContext *p, Object *with); + void destroy(); + + bool hasBinding(String *name) const; + void createMutableBinding(String *name, bool deletable); + bool setMutableBinding(ExecutionContext *scope, String *name, Value value); + Value getBindingValue(ExecutionContext *scope, String *name, bool strict) const; + bool deleteBinding(ExecutionContext *ctx, String *name); + + ExecutionContext *createWithScope(Object *with); + ExecutionContext *popScope(); + + void initCallContext(ExecutionContext *parent, const Value that, FunctionObject *f, Value *args, unsigned argc); + void leaveCallContext(); + + void wireUpPrototype(); + + void throwError(Value value); + void throwError(const QString &message); + void throwSyntaxError(DiagnosticMessage *message); + void throwTypeError(); + void throwReferenceError(Value value); + void throwRangeError(Value value); + void throwURIError(Value msg); + void throwUnimplemented(const QString &message); + + void setProperty(String *name, Value value); + Value getProperty(String *name); + Value getPropertyNoThrow(String *name); + Value getPropertyAndBase(String *name, Object **base); + void inplaceBitOp(Value value, String *name, BinOp op); + bool deleteProperty(String *name); + + inline Value argument(unsigned int index = 0) + { + if (index < argumentCount) + return arguments[index]; + return Value::undefinedValue(); + } + + void mark(); + +}; + +/* Function *f, int argc */ +#define requiredMemoryForExecutionContect(f, argc) \ + sizeof(ExecutionContext) + sizeof(Value) * (f->varCount + qMax((uint)argc, f->formalParameterCount)) + + +} // namespace VM +} // namespace QQmlJS + +#endif diff --git a/src/v4/qmljs_math.h b/src/v4/qmljs_math.h new file mode 100644 index 0000000000..4645c5a324 --- /dev/null +++ b/src/v4/qmljs_math.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QMLJS_MATH_H +#define QMLJS_MATH_H + +#ifndef QMLJS_LLVM_RUNTIME +# include <QtCore/qnumeric.h> +#endif // QMLJS_LLVM_RUNTIME +#include <cmath> + +#if !defined(QMLJS_LLVM_RUNTIME) && 1 //CPU(X86_64) + +namespace QQmlJS { +namespace VM { + +static inline Value add_int32(int a, int b) +{ + quint8 overflow = 0; + int aa = a; + + asm ("addl %2, %1\n" + "seto %0" + : "=q" (overflow), "=r" (aa) + : "r" (b), "1" (aa) + : "cc" + ); + if (!overflow) + return Value::fromInt32(aa); + return Value::fromDouble((double)a + (double)b); +} + +static inline Value sub_int32(int a, int b) +{ + quint8 overflow = 0; + int aa = a; + + asm ("subl %2, %1\n" + "seto %0" + : "=q" (overflow), "=r" (aa) + : "r" (b), "1" (aa) + : "cc" + ); + if (!overflow) + return Value::fromInt32(aa); + return Value::fromDouble((double)a - (double)b); +} + +static inline Value mul_int32(int a, int b) +{ + quint8 overflow = 0; + int aa = a; + + asm ("imul %2, %1\n" + "setc %0" + : "=q" (overflow), "=r" (aa) + : "r" (b), "1" (aa) + : "cc" + ); + if (!overflow) + return Value::fromInt32(aa); + return Value::fromDouble((double)a * (double)b); +} + +} // namespace VM +} // namespace QQmlJS + +#endif // !defined(QMLJS_LLVM_RUNTIME) && 1 //CPU(X86_64) +#endif // QMLJS_MATH_H diff --git a/src/v4/qmljs_runtime.cpp b/src/v4/qmljs_runtime.cpp new file mode 100644 index 0000000000..ce1d1abbaa --- /dev/null +++ b/src/v4/qmljs_runtime.cpp @@ -0,0 +1,1161 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4global.h" +#include "debugging.h" +#include "qmljs_runtime.h" +#include "qv4object.h" +#include "qv4ir_p.h" +#include "qv4objectproto.h" +#include "qv4globalobject.h" +#include "qv4stringobject.h" +#include "private/qlocale_tools_p.h" + +#include <QtCore/qmath.h> +#include <QtCore/qnumeric.h> +#include <QtCore/QDebug> +#include <cstdio> +#include <cassert> +#include <typeinfo> +#include <stdlib.h> + +#include "../3rdparty/double-conversion/double-conversion.h" + +namespace QQmlJS { +namespace VM { + +QString numberToString(double num, int radix = 10) +{ + if (std::isnan(num)) { + return QStringLiteral("NaN"); + } else if (qIsInf(num)) { + return QLatin1String(num < 0 ? "-Infinity" : "Infinity"); + } + + if (radix == 10) { + char str[100]; + double_conversion::StringBuilder builder(str, sizeof(str)); + double_conversion::DoubleToStringConverter::EcmaScriptConverter().ToShortest(num, &builder); + return QString::fromLatin1(builder.Finalize()); + } + + QString str; + bool negative = false; + + if (num < 0) { + negative = true; + num = -num; + } + + double frac = num - ::floor(num); + num = Value::toInteger(num); + + do { + char c = (char)::fmod(num, radix); + c = (c < 10) ? (c + '0') : (c - 10 + 'a'); + str.prepend(QLatin1Char(c)); + num = ::floor(num / radix); + } while (num != 0); + + if (frac != 0) { + str.append(QLatin1Char('.')); + do { + frac = frac * radix; + char c = (char)::floor(frac); + c = (c < 10) ? (c + '0') : (c - 10 + 'a'); + str.append(QLatin1Char(c)); + frac = frac - ::floor(frac); + } while (frac != 0); + } + + if (negative) + str.prepend(QLatin1Char('-')); + + return str; +} + +extern "C" { + +Value __qmljs_init_closure(VM::Function *clos, ExecutionContext *ctx) +{ + assert(clos); + return Value::fromObject(ctx->engine->newScriptFunction(ctx, clos)); +} + +Function *__qmljs_register_function(ExecutionContext *ctx, String *name, + bool hasDirectEval, + bool usesArgumentsObject, bool isStrict, + bool hasNestedFunctions, + String **formals, unsigned formalCount, + String **locals, unsigned localCount) +{ + Function *f = ctx->engine->newFunction(name ? name->toQString() : QString()); + + f->hasDirectEval = hasDirectEval; + f->usesArgumentsObject = usesArgumentsObject; + f->isStrict = isStrict; + f->hasNestedFunctions = hasNestedFunctions; + + for (unsigned i = 0; i < formalCount; ++i) + if (formals[i]) + f->formals.append(formals[i]); + for (unsigned i = 0; i < localCount; ++i) + if (locals[i]) + f->locals.append(locals[i]); + + return f; +} + +Value __qmljs_string_literal_undefined(ExecutionContext *ctx) +{ + return Value::fromString(ctx->engine->identifier(QStringLiteral("undefined"))); +} + +Value __qmljs_string_literal_null(ExecutionContext *ctx) +{ + return Value::fromString(ctx->engine->identifier(QStringLiteral("null"))); +} + +Value __qmljs_string_literal_true(ExecutionContext *ctx) +{ + return Value::fromString(ctx->engine->identifier(QStringLiteral("true"))); +} + +Value __qmljs_string_literal_false(ExecutionContext *ctx) +{ + return Value::fromString(ctx->engine->identifier(QStringLiteral("false"))); +} + +Value __qmljs_string_literal_object(ExecutionContext *ctx) +{ + return Value::fromString(ctx->engine->identifier(QStringLiteral("object"))); +} + +Value __qmljs_string_literal_boolean(ExecutionContext *ctx) +{ + return Value::fromString(ctx->engine->identifier(QStringLiteral("boolean"))); +} + +Value __qmljs_string_literal_number(ExecutionContext *ctx) +{ + return Value::fromString(ctx->engine->identifier(QStringLiteral("number"))); +} + +Value __qmljs_string_literal_string(ExecutionContext *ctx) +{ + return Value::fromString(ctx->engine->identifier(QStringLiteral("string"))); +} + +Value __qmljs_string_literal_function(ExecutionContext *ctx) +{ + return Value::fromString(ctx->engine->identifier(QStringLiteral("function"))); +} + +Value __qmljs_delete_subscript(ExecutionContext *ctx, Value base, Value index) +{ + if (Object *o = base.asObject()) { + uint n = UINT_MAX; + if (index.isInteger()) + n = index.integerValue(); + else if (index.isDouble()) + n = index.doubleValue(); + if (n < UINT_MAX) + return Value::fromBoolean(o->__delete__(ctx, n)); + } + + String *name = index.toString(ctx); + return __qmljs_delete_member(ctx, base, name); +} + +Value __qmljs_delete_member(ExecutionContext *ctx, Value base, String *name) +{ + Value obj = base.toObject(ctx); + return Value::fromBoolean(obj.objectValue()->__delete__(ctx, name)); +} + +Value __qmljs_delete_name(ExecutionContext *ctx, String *name) +{ + return Value::fromBoolean(ctx->deleteProperty(name)); +} + +Value __qmljs_add_helper(Value left, Value right, ExecutionContext *ctx) +{ + Value pleft = __qmljs_to_primitive(left, ctx, PREFERREDTYPE_HINT); + Value pright = __qmljs_to_primitive(right, ctx, PREFERREDTYPE_HINT); + if (pleft.isString() || pright.isString()) { + if (!pleft.isString()) + pleft = __qmljs_to_string(pleft, ctx); + if (!pright.isString()) + pright = __qmljs_to_string(pright, ctx); + String *string = __qmljs_string_concat(ctx, pleft.stringValue(), pright.stringValue()); + return Value::fromString(string); + } + double x = __qmljs_to_number(pleft, ctx); + double y = __qmljs_to_number(pright, ctx); + return Value::fromDouble(x + y); +} + +Value __qmljs_instanceof(Value left, Value right, ExecutionContext *ctx) +{ + if (FunctionObject *function = right.asFunctionObject()) { + bool r = function->hasInstance(ctx, left); + return Value::fromBoolean(r); + } + + return __qmljs_throw_type_error(ctx); +} + +Value __qmljs_in(Value left, Value right, ExecutionContext *ctx) +{ + if (right.isObject()) { + String *s = left.toString(ctx); + bool r = right.objectValue()->__hasProperty__(ctx, s); + return Value::fromBoolean(r); + } else { + return __qmljs_throw_type_error(ctx); + } +} + +void __qmljs_inplace_bit_and_name(Value value, String *name, ExecutionContext *ctx) +{ + ctx->inplaceBitOp(value, name, __qmljs_bit_and); +} + +void __qmljs_inplace_bit_or_name(Value value, String *name, ExecutionContext *ctx) +{ + ctx->inplaceBitOp(value, name, __qmljs_bit_or); +} + +void __qmljs_inplace_bit_xor_name(Value value, String *name, ExecutionContext *ctx) +{ + ctx->inplaceBitOp(value, name, __qmljs_bit_xor); +} + +void __qmljs_inplace_add_name(Value value, String *name, ExecutionContext *ctx) +{ + ctx->inplaceBitOp(value, name, __qmljs_add); +} + +void __qmljs_inplace_sub_name(Value value, String *name, ExecutionContext *ctx) +{ + ctx->inplaceBitOp(value, name, __qmljs_sub); +} + +void __qmljs_inplace_mul_name(Value value, String *name, ExecutionContext *ctx) +{ + ctx->inplaceBitOp(value, name, __qmljs_mul); +} + +void __qmljs_inplace_div_name(Value value, String *name, ExecutionContext *ctx) +{ + ctx->inplaceBitOp(value, name, __qmljs_div); +} + +void __qmljs_inplace_mod_name(Value value, String *name, ExecutionContext *ctx) +{ + ctx->inplaceBitOp(value, name, __qmljs_mod); +} + +void __qmljs_inplace_shl_name(Value value, String *name, ExecutionContext *ctx) +{ + ctx->inplaceBitOp(value, name, __qmljs_shl); +} + +void __qmljs_inplace_shr_name(Value value, String *name, ExecutionContext *ctx) +{ + ctx->inplaceBitOp(value, name, __qmljs_shr); +} + +void __qmljs_inplace_ushr_name(Value value, String *name, ExecutionContext *ctx) +{ + ctx->inplaceBitOp(value, name, __qmljs_ushr); +} + +void __qmljs_inplace_bit_and_element(Value base, Value index, Value value, ExecutionContext *ctx) +{ + Object *obj = base.toObject(ctx).objectValue(); + obj->inplaceBinOp(value, index, __qmljs_bit_and, ctx); +} + +void __qmljs_inplace_bit_or_element(Value base, Value index, Value value, ExecutionContext *ctx) +{ + Object *obj = base.toObject(ctx).objectValue(); + obj->inplaceBinOp(value, index, __qmljs_bit_or, ctx); +} + +void __qmljs_inplace_bit_xor_element(Value base, Value index, Value value, ExecutionContext *ctx) +{ + Object *obj = base.toObject(ctx).objectValue(); + obj->inplaceBinOp(value, index, __qmljs_bit_xor, ctx); +} + +void __qmljs_inplace_add_element(Value base, Value index, Value value, ExecutionContext *ctx) +{ + Object *obj = base.toObject(ctx).objectValue(); + obj->inplaceBinOp(value, index, __qmljs_add, ctx); +} + +void __qmljs_inplace_sub_element(Value base, Value index, Value value, ExecutionContext *ctx) +{ + Object *obj = base.toObject(ctx).objectValue(); + obj->inplaceBinOp(value, index, __qmljs_sub, ctx); +} + +void __qmljs_inplace_mul_element(Value base, Value index, Value value, ExecutionContext *ctx) +{ + Object *obj = base.toObject(ctx).objectValue(); + obj->inplaceBinOp(value, index, __qmljs_mul, ctx); +} + +void __qmljs_inplace_div_element(Value base, Value index, Value value, ExecutionContext *ctx) +{ + Object *obj = base.toObject(ctx).objectValue(); + obj->inplaceBinOp(value, index, __qmljs_div, ctx); +} + +void __qmljs_inplace_mod_element(Value base, Value index, Value value, ExecutionContext *ctx) +{ + Object *obj = base.toObject(ctx).objectValue(); + obj->inplaceBinOp(value, index, __qmljs_mod, ctx); +} + +void __qmljs_inplace_shl_element(Value base, Value index, Value value, ExecutionContext *ctx) +{ + Object *obj = base.toObject(ctx).objectValue(); + obj->inplaceBinOp(value, index, __qmljs_shl, ctx); +} + +void __qmljs_inplace_shr_element(Value base, Value index, Value value, ExecutionContext *ctx) +{ + Object *obj = base.toObject(ctx).objectValue(); + obj->inplaceBinOp(value, index, __qmljs_shr, ctx); +} + +void __qmljs_inplace_ushr_element(Value base, Value index, Value value, ExecutionContext *ctx) +{ + Object *obj = base.toObject(ctx).objectValue(); + obj->inplaceBinOp(value, index, __qmljs_ushr, ctx); +} + +void __qmljs_inplace_bit_and_member(Value value, Value base, String *name, ExecutionContext *ctx) +{ + Object *o = base.toObject(ctx).objectValue(); + o->inplaceBinOp(value, name, __qmljs_bit_and, ctx); +} + +void __qmljs_inplace_bit_or_member(Value value, Value base, String *name, ExecutionContext *ctx) +{ + Object *o = base.toObject(ctx).objectValue(); + o->inplaceBinOp(value, name, __qmljs_bit_or, ctx); +} + +void __qmljs_inplace_bit_xor_member(Value value, Value base, String *name, ExecutionContext *ctx) +{ + Object *o = base.toObject(ctx).objectValue(); + o->inplaceBinOp(value, name, __qmljs_bit_xor, ctx); +} + +void __qmljs_inplace_add_member(Value value, Value base, String *name, ExecutionContext *ctx) +{ + Object *o = base.toObject(ctx).objectValue(); + o->inplaceBinOp(value, name, __qmljs_add, ctx); +} + +void __qmljs_inplace_sub_member(Value value, Value base, String *name, ExecutionContext *ctx) +{ + Object *o = base.toObject(ctx).objectValue(); + o->inplaceBinOp(value, name, __qmljs_sub, ctx); +} + +void __qmljs_inplace_mul_member(Value value, Value base, String *name, ExecutionContext *ctx) +{ + Object *o = base.toObject(ctx).objectValue(); + o->inplaceBinOp(value, name, __qmljs_mul, ctx); +} + +void __qmljs_inplace_div_member(Value value, Value base, String *name, ExecutionContext *ctx) +{ + Object *o = base.toObject(ctx).objectValue(); + o->inplaceBinOp(value, name, __qmljs_div, ctx); +} + +void __qmljs_inplace_mod_member(Value value, Value base, String *name, ExecutionContext *ctx) +{ + Object *o = base.toObject(ctx).objectValue(); + o->inplaceBinOp(value, name, __qmljs_mod, ctx); +} + +void __qmljs_inplace_shl_member(Value value, Value base, String *name, ExecutionContext *ctx) +{ + Object *o = base.toObject(ctx).objectValue(); + o->inplaceBinOp(value, name, __qmljs_shl, ctx); +} + +void __qmljs_inplace_shr_member(Value value, Value base, String *name, ExecutionContext *ctx) +{ + Object *o = base.toObject(ctx).objectValue(); + o->inplaceBinOp(value, name, __qmljs_shr, ctx); +} + +void __qmljs_inplace_ushr_member(Value value, Value base, String *name, ExecutionContext *ctx) +{ + Object *o = base.toObject(ctx).objectValue(); + o->inplaceBinOp(value, name, __qmljs_ushr, ctx); +} + +String *__qmljs_string_from_utf8(ExecutionContext *ctx, const char *s) +{ + return ctx->engine->newString(QString::fromUtf8(s)); +} + +String *__qmljs_identifier_from_utf8(ExecutionContext *ctx, const char *s) +{ + return ctx->engine->identifier(QString::fromUtf8(s)); +} + +int __qmljs_string_length(ExecutionContext *, String *string) +{ + return string->toQString().length(); +} + +double __qmljs_string_to_number(const String *string) +{ + QString s = string->toQString(); + s = s.trimmed(); + if (s.startsWith(QLatin1String("0x")) || s.startsWith(QLatin1String("0X"))) + return s.toLong(0, 16); + bool ok; + QByteArray ba = s.toLatin1(); + const char *begin = ba.constData(); + const char *end = 0; + double d = qstrtod(begin, &end, &ok); + if (end - begin != ba.size()) { + if (ba == "Infinity" || ba == "+Infinity") + d = INFINITY; + else if (ba == "-Infinity") + d = -INFINITY; + else + d = nan(""); + } + return d; +} + +Value __qmljs_string_from_number(ExecutionContext *ctx, double number) +{ + String *string = ctx->engine->newString(numberToString(number, 10)); + return Value::fromString(string); +} + +Bool __qmljs_string_compare(ExecutionContext *, String *left, String *right) +{ + return left->toQString() < right->toQString(); +} + +Bool __qmljs_string_equal(String *left, String *right) +{ + return left->isEqualTo(right); +} + +String *__qmljs_string_concat(ExecutionContext *ctx, String *first, String *second) +{ + return ctx->engine->newString(first->toQString() + second->toQString()); +} + +Bool __qmljs_is_function(Value value) +{ + return value.objectValue()->asFunctionObject() != 0; +} + +Value __qmljs_object_default_value(ExecutionContext *ctx, Value object, int typeHint) +{ + if (typeHint == PREFERREDTYPE_HINT) { + if (object.asDateObject()) + typeHint = STRING_HINT; + else + typeHint = NUMBER_HINT; + } + + String *meth1 = ctx->engine->identifier("toString"); + String *meth2 = ctx->engine->identifier("valueOf"); + + if (typeHint == NUMBER_HINT) + qSwap(meth1, meth2); + + assert(object.isObject()); + Object *oo = object.objectValue(); + + Value conv = oo->__get__(ctx, meth1); + if (FunctionObject *o = conv.asFunctionObject()) { + Value r = o->call(ctx, object, 0, 0); + if (r.isPrimitive()) + return r; + } + + conv = oo->__get__(ctx, meth2); + if (FunctionObject *o = conv.asFunctionObject()) { + Value r = o->call(ctx, object, 0, 0); + if (r.isPrimitive()) + return r; + } + + ctx->throwTypeError(); + return Value::undefinedValue(); +} + +Value __qmljs_throw_type_error(ExecutionContext *ctx) +{ + ctx->throwTypeError(); + return Value::undefinedValue(); +} + +Value __qmljs_new_object(ExecutionContext *ctx) +{ + return Value::fromObject(ctx->engine->newObject()); +} + +Value __qmljs_new_boolean_object(ExecutionContext *ctx, bool boolean) +{ + Value value = Value::fromBoolean(boolean); + return Value::fromObject(ctx->engine->newBooleanObject(value)); +} + +Value __qmljs_new_number_object(ExecutionContext *ctx, double number) +{ + Value value = Value::fromDouble(number); + return Value::fromObject(ctx->engine->newNumberObject(value)); +} + +Value __qmljs_new_string_object(ExecutionContext *ctx, String *string) +{ + Value value = Value::fromString(string); + return Value::fromObject(ctx->engine->newStringObject(ctx, value)); +} + +void __qmljs_set_property(ExecutionContext *ctx, Value object, String *name, Value value) +{ + if (! object.isObject()) + object = __qmljs_to_object(object, ctx); + object.objectValue()->__put__(ctx, name, value); +} + +Value __qmljs_get_element(ExecutionContext *ctx, Value object, Value index) +{ + uint type = object.type(); + uint idx = index.asArrayIndex(); + + if (type != Value::Object_Type) { + if (type == Value::String_Type && idx < UINT_MAX) { + String *str = object.stringValue(); + if (idx >= (uint)str->toQString().length()) + return Value::undefinedValue(); + const QString s = str->toQString().mid(idx, 1); + return Value::fromString(ctx, s); + } + + object = __qmljs_to_object(object, ctx); + } + + Object *o = object.objectValue(); + + if (idx < UINT_MAX) { + const PropertyDescriptor *p = o->array.nonSparseAt(idx); + if (p && p->type == PropertyDescriptor::Data) + return p->value; + + return o->__get__(ctx, idx); + } + + String *name = index.toString(ctx); + return o->__get__(ctx, name); +} + +void __qmljs_set_element(ExecutionContext *ctx, Value object, Value index, Value value) +{ + if (!object.isObject()) + object = __qmljs_to_object(object, ctx); + + Object *o = object.objectValue(); + + uint idx = index.asArrayIndex(); + if (idx < UINT_MAX) { + PropertyDescriptor *p = o->array.nonSparseAtRef(idx); + if (p && p->type == PropertyDescriptor::Data && p->isWritable()) { + p->value = value; + return; + } + o->__put__(ctx, idx, value); + return; + } + + String *name = index.toString(ctx); + o->__put__(ctx, name, value); +} + +Value __qmljs_foreach_iterator_object(Value in, ExecutionContext *ctx) +{ + if (!in.isNull() && !in.isUndefined()) + in = __qmljs_to_object(in, ctx); + Object *it = ctx->engine->newForEachIteratorObject(ctx, in.asObject()); + return Value::fromObject(it); +} + +Value __qmljs_foreach_next_property_name(Value foreach_iterator) +{ + assert(foreach_iterator.isObject()); + + ForEachIteratorObject *it = static_cast<ForEachIteratorObject *>(foreach_iterator.objectValue()); + assert(it->asForeachIteratorObject()); + + return it->nextPropertyName(); +} + + +void __qmljs_set_activation_property(ExecutionContext *ctx, String *name, Value value) +{ + ctx->setProperty(name, value); +} + +Value __qmljs_get_property(ExecutionContext *ctx, Value object, String *name) +{ + if (object.isObject()) { + return object.objectValue()->__get__(ctx, name); + } else if (object.isString() && name->isEqualTo(ctx->engine->id_length)) { + return Value::fromInt32(object.stringValue()->toQString().length()); + } else { + object = __qmljs_to_object(object, ctx); + + if (object.isObject()) { + return object.objectValue()->__get__(ctx, name); + } else { + ctx->throwTypeError(); + return Value::undefinedValue(); + } + } +} + +Value __qmljs_get_activation_property(ExecutionContext *ctx, String *name) +{ + return ctx->getProperty(name); +} + +Value __qmljs_get_thisObject(ExecutionContext *ctx) +{ + return ctx->thisObject; +} + +uint __qmljs_equal(Value x, Value y, ExecutionContext *ctx) +{ + if (x.type() == y.type()) { + switch (x.type()) { + case Value::Undefined_Type: + return true; + case Value::Null_Type: + return true; + case Value::Boolean_Type: + return x.booleanValue() == y.booleanValue(); + break; + case Value::Integer_Type: + return x.integerValue() == y.integerValue(); + case Value::String_Type: + return x.stringValue()->isEqualTo(y.stringValue()); + case Value::Object_Type: + return x.objectValue() == y.objectValue(); + default: // double + return x.doubleValue() == y.doubleValue(); + } + // unreachable + } else { + if (x.isNumber() && y.isNumber()) + return x.asDouble() == y.asDouble(); + if (x.isNull() && y.isUndefined()) { + return true; + } else if (x.isUndefined() && y.isNull()) { + return true; + } else if (x.isNumber() && y.isString()) { + Value ny = Value::fromDouble(__qmljs_to_number(y, ctx)); + return __qmljs_equal(x, ny, ctx); + } else if (x.isString() && y.isNumber()) { + Value nx = Value::fromDouble(__qmljs_to_number(x, ctx)); + return __qmljs_equal(nx, y, ctx); + } else if (x.isBoolean()) { + Value nx = Value::fromDouble((double) x.booleanValue()); + return __qmljs_equal(nx, y, ctx); + } else if (y.isBoolean()) { + Value ny = Value::fromDouble((double) y.booleanValue()); + return __qmljs_equal(x, ny, ctx); + } else if ((x.isNumber() || x.isString()) && y.isObject()) { + Value py = __qmljs_to_primitive(y, ctx, PREFERREDTYPE_HINT); + return __qmljs_equal(x, py, ctx); + } else if (x.isObject() && (y.isNumber() || y.isString())) { + Value px = __qmljs_to_primitive(x, ctx, PREFERREDTYPE_HINT); + return __qmljs_equal(px, y, ctx); + } + } + + return false; +} + +Value __qmljs_call_activation_property(ExecutionContext *context, String *name, Value *args, int argc) +{ + Object *base; + Value func = context->getPropertyAndBase(name, &base); + FunctionObject *o = func.asFunctionObject(); + if (!o) + context->throwTypeError(); + + Value thisObject = base ? Value::fromObject(base) : Value::undefinedValue(); + + if (o == context->engine->evalFunction && name->isEqualTo(context->engine->id_eval)) + return static_cast<EvalFunction *>(o)->evalCall(context, thisObject, args, argc, true); + + return o->call(context, thisObject, args, argc); +} + +Value __qmljs_call_property(ExecutionContext *context, Value thisObject, String *name, Value *args, int argc) +{ + Object *baseObject; + if (thisObject.isString()) { + baseObject = context->engine->stringPrototype; + } else { + if (!thisObject.isObject()) + thisObject = __qmljs_to_object(thisObject, context); + + assert(thisObject.isObject()); + baseObject = thisObject.objectValue(); + } + + Value func = baseObject->__get__(context, name); + FunctionObject *o = func.asFunctionObject(); + if (!o) + context->throwTypeError(); + + return o->call(context, thisObject, args, argc); +} + +Value __qmljs_call_element(ExecutionContext *context, Value that, Value index, Value *args, int argc) +{ + Value thisObject = that; + if (!thisObject.isObject()) + thisObject = __qmljs_to_object(thisObject, context); + + assert(thisObject.isObject()); + Object *baseObject = thisObject.objectValue(); + + Value func = baseObject->__get__(context, index.toString(context)); + FunctionObject *o = func.asFunctionObject(); + if (!o) + context->throwTypeError(); + + return o->call(context, thisObject, args, argc); +} + +Value __qmljs_call_value(ExecutionContext *context, Value thisObject, Value func, Value *args, int argc) +{ + FunctionObject *o = func.asFunctionObject(); + if (!o) + context->throwTypeError(); + return o->call(context, thisObject, args, argc); +} + +Value __qmljs_construct_activation_property(ExecutionContext *context, String *name, Value *args, int argc) +{ + Value func = context->getProperty(name); + return __qmljs_construct_value(context, func, args, argc); +} + +Value __qmljs_construct_value(ExecutionContext *context, Value func, Value *args, int argc) +{ + if (FunctionObject *f = func.asFunctionObject()) + return f->construct(context, args, argc); + + context->throwTypeError(); + return Value::undefinedValue(); +} + +Value __qmljs_construct_property(ExecutionContext *context, Value base, String *name, Value *args, int argc) +{ + Value thisObject = base; + if (!thisObject.isObject()) + thisObject = __qmljs_to_object(base, context); + + Value func = thisObject.objectValue()->__get__(context, name); + if (FunctionObject *f = func.asFunctionObject()) + return f->construct(context, args, argc); + + context->throwTypeError(); + return Value::undefinedValue(); +} + +void __qmljs_throw(Value value, ExecutionContext *context) +{ + assert(!context->engine->unwindStack.isEmpty()); + + if (context->engine->debugger) + context->engine->debugger->aboutToThrow(&value); + + ExecutionEngine::ExceptionHandler &handler = context->engine->unwindStack.last(); + + // clean up call contexts + while (context != handler.context) { + ExecutionContext *parent = context->parent; + if (!context->withObject) + context->leaveCallContext(); + context = parent; + } + + context->engine->exception = value; + + longjmp(handler.stackFrame, 1); +} + +Q_V4_EXPORT void * __qmljs_create_exception_handler(ExecutionContext *context) +{ + context->engine->exception = Value::undefinedValue(); + context->engine->unwindStack.append(ExecutionEngine::ExceptionHandler()); + ExecutionEngine::ExceptionHandler &handler = context->engine->unwindStack.last(); + handler.context = context; + return handler.stackFrame; +} + +void __qmljs_delete_exception_handler(ExecutionContext *context) +{ + assert(!context->engine->unwindStack.isEmpty()); + + context->engine->unwindStack.pop_back(); +} + +Value __qmljs_get_exception(ExecutionContext *context) +{ + return context->engine->exception; +} + +Value __qmljs_builtin_typeof(Value value, ExecutionContext *ctx) +{ + switch (value.type()) { + case Value::Undefined_Type: + return __qmljs_string_literal_undefined(ctx); + break; + case Value::Null_Type: + return __qmljs_string_literal_object(ctx); + break; + case Value::Boolean_Type: + return __qmljs_string_literal_boolean(ctx); + break; + case Value::String_Type: + return __qmljs_string_literal_string(ctx); + break; + case Value::Object_Type: + if (__qmljs_is_callable(value, ctx)) + return __qmljs_string_literal_function(ctx); + else + return __qmljs_string_literal_object(ctx); // ### implementation-defined + break; + default: + return __qmljs_string_literal_number(ctx); + break; + } +} + +Value __qmljs_builtin_typeof_name(String *name, ExecutionContext *context) +{ + return __qmljs_builtin_typeof(context->getPropertyNoThrow(name), context); +} + +Value __qmljs_builtin_typeof_member(Value base, String *name, ExecutionContext *context) +{ + Value obj = base.toObject(context); + return __qmljs_builtin_typeof(obj.objectValue()->__get__(context, name), context); +} + +Value __qmljs_builtin_typeof_element(Value base, Value index, ExecutionContext *context) +{ + String *name = index.toString(context); + Value obj = base.toObject(context); + return __qmljs_builtin_typeof(obj.objectValue()->__get__(context, name), context); +} + +Value __qmljs_builtin_post_increment(Value *val, ExecutionContext *ctx) +{ + if (val->isInteger() && val->integerValue() < INT_MAX) { + Value retval = *val; + val->int_32 += 1; + return retval; + } + + double d = __qmljs_to_number(*val, ctx); + *val = Value::fromDouble(d + 1); + return Value::fromDouble(d); +} + +Value __qmljs_builtin_post_increment_name(String *name, ExecutionContext *context) +{ + Value v = context->getProperty(name); + Value retval; + + if (v.isInteger() && v.integerValue() < INT_MAX) { + retval = v; + v.int_32 += 1; + } else { + double d = __qmljs_to_number(v, context); + retval = Value::fromDouble(d); + v = Value::fromDouble(d + 1); + } + + context->setProperty(name, v); + return retval; +} + +Value __qmljs_builtin_post_increment_member(Value base, String *name, ExecutionContext *context) +{ + Object *o = __qmljs_to_object(base, context).objectValue(); + + Value v = o->__get__(context, name); + Value retval; + + if (v.isInteger() && v.integerValue() < INT_MAX) { + retval = v; + v.int_32 += 1; + } else { + double d = __qmljs_to_number(v, context); + retval = Value::fromDouble(d); + v = Value::fromDouble(d + 1); + } + + o->__put__(context, name, v); + return retval; +} + +Value __qmljs_builtin_post_increment_element(Value base, Value index, ExecutionContext *context) +{ + Object *o = __qmljs_to_object(base, context).objectValue(); + + uint idx = index.asArrayIndex(); + + if (idx == UINT_MAX) { + String *s = index.toString(context); + return __qmljs_builtin_post_increment_member(base, s, context); + } + + Value v = o->__get__(context, idx); + Value retval; + + if (v.isInteger() && v.integerValue() < INT_MAX) { + retval = v; + v.int_32 += 1; + } else { + double d = __qmljs_to_number(v, context); + retval = Value::fromDouble(d); + v = Value::fromDouble(d + 1); + } + + o->__put__(context, idx, v); + return retval; +} + +Value __qmljs_builtin_post_decrement(Value *val, ExecutionContext *ctx) +{ + if (val->isInteger() && val->integerValue() > INT_MIN) { + Value retval = *val; + val->int_32 -= 1; + return retval; + } + + double d = __qmljs_to_number(*val, ctx); + *val = Value::fromDouble(d - 1); + return Value::fromDouble(d); +} + +Value __qmljs_builtin_post_decrement_name(String *name, ExecutionContext *context) +{ + Value v = context->getProperty(name); + Value retval; + + if (v.isInteger() && v.integerValue() > INT_MIN) { + retval = v; + v.int_32 -= 1; + } else { + double d = __qmljs_to_number(v, context); + retval = Value::fromDouble(d); + v = Value::fromDouble(d - 1); + } + + context->setProperty(name, v); + return retval; +} + +Value __qmljs_builtin_post_decrement_member(Value base, String *name, ExecutionContext *context) +{ + Object *o = __qmljs_to_object(base, context).objectValue(); + + Value v = o->__get__(context, name); + Value retval; + + if (v.isInteger() && v.integerValue() > INT_MIN) { + retval = v; + v.int_32 -= 1; + } else { + double d = __qmljs_to_number(v, context); + retval = Value::fromDouble(d); + v = Value::fromDouble(d - 1); + } + + o->__put__(context, name, v); + return retval; +} + +Value __qmljs_builtin_post_decrement_element(Value base, Value index, ExecutionContext *context) +{ + Object *o = __qmljs_to_object(base, context).objectValue(); + + uint idx = index.asArrayIndex(); + + if (idx == UINT_MAX) { + String *s = index.toString(context); + return __qmljs_builtin_post_decrement_member(base, s, context); + } + + Value v = o->__get__(context, idx); + Value retval; + + if (v.isInteger() && v.integerValue() > INT_MIN) { + retval = v; + v.int_32 -= 1; + } else { + double d = __qmljs_to_number(v, context); + retval = Value::fromDouble(d); + v = Value::fromDouble(d - 1); + } + + o->__put__(context, idx, v); + return retval; +} + +void __qmljs_builtin_throw(Value val, ExecutionContext *context) +{ + __qmljs_throw(val, context); +} + +ExecutionContext *__qmljs_builtin_push_with_scope(Value o, ExecutionContext *ctx) +{ + Object *obj = __qmljs_to_object(o, ctx).asObject(); + return ctx->createWithScope(obj); +} + +ExecutionContext *__qmljs_builtin_pop_scope(ExecutionContext *ctx) +{ + return ctx->popScope(); +} + +void __qmljs_builtin_declare_var(ExecutionContext *ctx, bool deletable, String *name) +{ + ctx->createMutableBinding(name, deletable); +} + +void __qmljs_builtin_define_property(Value object, String *name, Value val, ExecutionContext *ctx) +{ + Object *o = object.asObject(); + assert(o); + + PropertyDescriptor pd; + pd.value = val; + pd.type = PropertyDescriptor::Data; + pd.writable = PropertyDescriptor::Enabled; + pd.enumberable = PropertyDescriptor::Enabled; + pd.configurable = PropertyDescriptor::Enabled; + o->__defineOwnProperty__(ctx, name, &pd); +} + +void __qmljs_builtin_define_array_property(Value object, int index, Value val, ExecutionContext *ctx) +{ + Object *o = object.asObject(); + assert(o); + + PropertyDescriptor pd; + pd.value = val; + pd.type = PropertyDescriptor::Data; + pd.writable = PropertyDescriptor::Enabled; + pd.enumberable = PropertyDescriptor::Enabled; + pd.configurable = PropertyDescriptor::Enabled; + o->__defineOwnProperty__(ctx, index, &pd); +} + +void __qmljs_builtin_define_getter_setter(Value object, String *name, Value getter, Value setter, ExecutionContext *ctx) +{ + Object *o = object.asObject(); + assert(o); + + PropertyDescriptor pd; + pd.get = getter.asFunctionObject(); + pd.set = setter.asFunctionObject(); + pd.type = PropertyDescriptor::Accessor; + pd.writable = PropertyDescriptor::Undefined; + pd.enumberable = PropertyDescriptor::Enabled; + pd.configurable = PropertyDescriptor::Enabled; + o->__defineOwnProperty__(ctx, name, &pd); +} + +Value __qmljs_increment(Value value, ExecutionContext *ctx) +{ + TRACE1(value); + + if (value.isInteger()) + return Value::fromInt32(value.integerValue() + 1); + + double d = __qmljs_to_number(value, ctx); + return Value::fromDouble(d + 1); +} + +Value __qmljs_decrement(Value value, ExecutionContext *ctx) +{ + TRACE1(value); + + if (value.isInteger()) + return Value::fromInt32(value.integerValue() - 1); + + double d = __qmljs_to_number(value, ctx); + return Value::fromDouble(d - 1); +} + +} // extern "C" + + +} // namespace VM +} // namespace QQmlJS diff --git a/src/v4/qmljs_runtime.h b/src/v4/qmljs_runtime.h new file mode 100644 index 0000000000..2ea612642c --- /dev/null +++ b/src/v4/qmljs_runtime.h @@ -0,0 +1,862 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QMLJS_RUNTIME_H +#define QMLJS_RUNTIME_H + +#include "qmljs_value.h" +#include "qmljs_math.h" + + +#include <QtCore/QString> +#include <QtCore/qnumeric.h> +#include <QtCore/QDebug> + +#include <cmath> +#include <cassert> + +#ifdef DO_TRACE_INSTR +# define TRACE1(x) fprintf(stderr, " %s\n", __FUNCTION__); +# define TRACE2(x, y) fprintf(stderr, " %s\n", __FUNCTION__); +#else +# define TRACE1(x) +# define TRACE2(x, y) +#endif // TRACE1 + +namespace QQmlJS { +namespace VM { + +enum TypeHint { + PREFERREDTYPE_HINT, + NUMBER_HINT, + STRING_HINT +}; + +struct Function; +struct Object; +struct String; +struct PropertyDescriptor; +struct ExecutionContext; +struct FunctionObject; +struct BooleanObject; +struct NumberObject; +struct StringObject; +struct DateObject; +struct RegExpObject; +struct ArrayObject; +struct ErrorObject; +struct ExecutionEngine; + +extern "C" { + +// context +Value __qmljs_call_activation_property(ExecutionContext *, String *name, Value *args, int argc); +Value __qmljs_call_property(ExecutionContext *context, Value that, String *name, Value *args, int argc); +Value __qmljs_call_element(ExecutionContext *context, Value that, Value index, Value *args, int argc); +Value __qmljs_call_value(ExecutionContext *context, Value thisObject, Value func, Value *args, int argc); + +Value __qmljs_construct_activation_property(ExecutionContext *, String *name, Value *args, int argc); +Value __qmljs_construct_property(ExecutionContext *context, Value base, String *name, Value *args, int argc); +Value __qmljs_construct_value(ExecutionContext *context, Value func, Value *args, int argc); + +Value __qmljs_builtin_typeof(Value val, ExecutionContext *ctx); +Value __qmljs_builtin_typeof_name(String *name, ExecutionContext *context); +Value __qmljs_builtin_typeof_member(Value base, String *name, ExecutionContext *context); +Value __qmljs_builtin_typeof_element(Value base, Value index, ExecutionContext *context); + +Value __qmljs_builtin_post_increment(Value *val, ExecutionContext *ctx); +Value __qmljs_builtin_post_increment_name(String *name, ExecutionContext *context); +Value __qmljs_builtin_post_increment_member(Value base, String *name, ExecutionContext *context); +Value __qmljs_builtin_post_increment_element(Value base, Value index, ExecutionContext *context); + +Value __qmljs_builtin_post_decrement(Value *val, ExecutionContext *ctx); +Value __qmljs_builtin_post_decrement_name(String *name, ExecutionContext *context); +Value __qmljs_builtin_post_decrement_member(Value base, String *name, ExecutionContext *context); +Value __qmljs_builtin_post_decrement_element(Value base, Value index, ExecutionContext *context); + +void __qmljs_builtin_throw(Value val, ExecutionContext *context); +void __qmljs_builtin_rethrow(ExecutionContext *context); +ExecutionContext *__qmljs_builtin_push_with_scope(Value o, ExecutionContext *ctx); +ExecutionContext *__qmljs_builtin_pop_scope(ExecutionContext *ctx); +void __qmljs_builtin_declare_var(ExecutionContext *ctx, bool deletable, String *name); +void __qmljs_builtin_define_property(Value object, String *name, Value val, ExecutionContext *ctx); +void __qmljs_builtin_define_array_property(Value object, int index, Value val, ExecutionContext *ctx); +void __qmljs_builtin_define_getter_setter(Value object, String *name, Value getter, Value setter, ExecutionContext *ctx); + +// constructors +Value __qmljs_init_closure(VM::Function *clos, ExecutionContext *ctx); +VM::Function *__qmljs_register_function(ExecutionContext *ctx, String *name, + bool hasDirectEval, + bool usesArgumentsObject, bool isStrict, + bool hasNestedFunctions, + String **formals, unsigned formalCount, + String **locals, unsigned localCount); + +Bool __qmljs_is_function(Value value); + +// string literals +Value __qmljs_string_literal_undefined(ExecutionContext *ctx); +Value __qmljs_string_literal_null(ExecutionContext *ctx); +Value __qmljs_string_literal_true(ExecutionContext *ctx); +Value __qmljs_string_literal_false(ExecutionContext *ctx); +Value __qmljs_string_literal_object(ExecutionContext *ctx); +Value __qmljs_string_literal_boolean(ExecutionContext *ctx); +Value __qmljs_string_literal_number(ExecutionContext *ctx); +Value __qmljs_string_literal_string(ExecutionContext *ctx); +Value __qmljs_string_literal_function(ExecutionContext *ctx); + +// strings +String *__qmljs_string_from_utf8(ExecutionContext *ctx, const char *s); +int __qmljs_string_length(ExecutionContext *ctx, String *string); +double __qmljs_string_to_number(const String *string); +Value __qmljs_string_from_number(ExecutionContext *ctx, double number); +Bool __qmljs_string_compare(ExecutionContext *ctx, String *left, String *right); +Bool __qmljs_string_equal(String *left, String *right); +String *__qmljs_string_concat(ExecutionContext *ctx, String *first, String *second); +String *__qmljs_identifier_from_utf8(ExecutionContext *ctx, const char *s); + +// objects +Value __qmljs_object_default_value(ExecutionContext *ctx, Value object, int typeHint); +Value __qmljs_throw_type_error(ExecutionContext *ctx); +Value __qmljs_new_object(ExecutionContext *ctx); +Value __qmljs_new_boolean_object(ExecutionContext *ctx, bool boolean); +Value __qmljs_new_number_object(ExecutionContext *ctx, double n); +Value __qmljs_new_string_object(ExecutionContext *ctx, String *string); +void __qmljs_set_activation_property(ExecutionContext *ctx, String *name, Value value); +void __qmljs_set_property(ExecutionContext *ctx, Value object, String *name, Value value); +Value __qmljs_get_property(ExecutionContext *ctx, Value object, String *name); +Value __qmljs_get_activation_property(ExecutionContext *ctx, String *name); + +Value __qmljs_get_element(ExecutionContext *ctx, Value object, Value index); +void __qmljs_set_element(ExecutionContext *ctx, Value object, Value index, Value value); + +// For each +Value __qmljs_foreach_iterator_object(Value in, ExecutionContext *ctx); +Value __qmljs_foreach_next_property_name(Value foreach_iterator); + +// context +Value __qmljs_get_thisObject(ExecutionContext *ctx); + +// type conversion and testing +Value __qmljs_to_primitive(Value value, ExecutionContext *ctx, int typeHint); +Bool __qmljs_to_boolean(Value value, ExecutionContext *ctx); +double __qmljs_to_number(Value value, ExecutionContext *ctx); +double __qmljs_to_integer(Value value, ExecutionContext *ctx); +int __qmljs_to_int32(Value value, ExecutionContext *ctx); +unsigned short __qmljs_to_uint16(Value value, ExecutionContext *ctx); +Value __qmljs_to_string(Value value, ExecutionContext *ctx); +Value __qmljs_to_object(Value value, ExecutionContext *ctx); +//uint __qmljs_check_object_coercible(Context *ctx, Value *result, Value *value); +Bool __qmljs_is_callable(Value value, ExecutionContext *ctx); +Value __qmljs_default_value(Value value, ExecutionContext *ctx, int typeHint); + +Bool __qmljs_equal(Value x, Value y, ExecutionContext *ctx); +Bool __qmljs_strict_equal(Value x, Value y); + +// unary operators +Value __qmljs_uplus(Value value, ExecutionContext *ctx); +Value __qmljs_uminus(Value value, ExecutionContext *ctx); +Value __qmljs_compl(Value value, ExecutionContext *ctx); +Value __qmljs_not(Value value, ExecutionContext *ctx); +Value __qmljs_increment(Value value, ExecutionContext *ctx); +Value __qmljs_decrement(Value value, ExecutionContext *ctx); + +Value __qmljs_delete_subscript(ExecutionContext *ctx, Value base, Value index); +Value __qmljs_delete_member(ExecutionContext *ctx, Value base, String *name); +Value __qmljs_delete_name(ExecutionContext *ctx, String *name); + +void __qmljs_throw(Value value, ExecutionContext *context); +// actually returns a jmp_buf * +void *__qmljs_create_exception_handler(ExecutionContext *context); +void __qmljs_delete_exception_handler(ExecutionContext *context); +Value __qmljs_get_exception(ExecutionContext *context); + +// binary operators +typedef Value (*BinOp)(Value left, Value right, ExecutionContext *ctx); + +Value __qmljs_instanceof(Value left, Value right, ExecutionContext *ctx); +Value __qmljs_in(Value left, Value right, ExecutionContext *ctx); +Value __qmljs_bit_or(Value left, Value right, ExecutionContext *ctx); +Value __qmljs_bit_xor(Value left, Value right, ExecutionContext *ctx); +Value __qmljs_bit_and(Value left, Value right, ExecutionContext *ctx); +Value __qmljs_add(Value left, Value right, ExecutionContext *ctx); +Value __qmljs_sub(Value left, Value right, ExecutionContext *ctx); +Value __qmljs_mul(Value left, Value right, ExecutionContext *ctx); +Value __qmljs_div(Value left, Value right, ExecutionContext *ctx); +Value __qmljs_mod(Value left, Value right, ExecutionContext *ctx); +Value __qmljs_shl(Value left, Value right, ExecutionContext *ctx); +Value __qmljs_shr(Value left, Value right, ExecutionContext *ctx); +Value __qmljs_ushr(Value left, Value right, ExecutionContext *ctx); +Value __qmljs_gt(Value left, Value right, ExecutionContext *ctx); +Value __qmljs_lt(Value left, Value right, ExecutionContext *ctx); +Value __qmljs_ge(Value left, Value right, ExecutionContext *ctx); +Value __qmljs_le(Value left, Value right, ExecutionContext *ctx); +Value __qmljs_eq(Value left, Value right, ExecutionContext *ctx); +Value __qmljs_ne(Value left, Value right, ExecutionContext *ctx); +Value __qmljs_se(Value left, Value right, ExecutionContext *ctx); +Value __qmljs_sne(Value left, Value right, ExecutionContext *ctx); + +Value __qmljs_add_helper(Value left, Value right, ExecutionContext *ctx); + +void __qmljs_inplace_bit_and_name(Value value, String *name, ExecutionContext *ctx); +void __qmljs_inplace_bit_or_name(Value value, String *name, ExecutionContext *ctx); +void __qmljs_inplace_bit_xor_name(Value value, String *name, ExecutionContext *ctx); +void __qmljs_inplace_add_name(Value value, String *name, ExecutionContext *ctx); +void __qmljs_inplace_sub_name(Value value, String *name, ExecutionContext *ctx); +void __qmljs_inplace_mul_name(Value value, String *name, ExecutionContext *ctx); +void __qmljs_inplace_div_name(Value value, String *name, ExecutionContext *ctx); +void __qmljs_inplace_mod_name(Value value, String *name, ExecutionContext *ctx); +void __qmljs_inplace_shl_name(Value value, String *name, ExecutionContext *ctx); +void __qmljs_inplace_shr_name(Value value, String *name, ExecutionContext *ctx); +void __qmljs_inplace_ushr_name(Value value, String *name, ExecutionContext *ctx); + +void __qmljs_inplace_bit_and_element(Value base, Value index, Value value, ExecutionContext *ctx); +void __qmljs_inplace_bit_or_element(Value base, Value index, Value value, ExecutionContext *ctx); +void __qmljs_inplace_bit_xor_element(Value base, Value index, Value value, ExecutionContext *ctx); +void __qmljs_inplace_add_element(Value base, Value index, Value value, ExecutionContext *ctx); +void __qmljs_inplace_sub_element(Value base, Value index, Value value, ExecutionContext *ctx); +void __qmljs_inplace_mul_element(Value base, Value index, Value value, ExecutionContext *ctx); +void __qmljs_inplace_div_element(Value base, Value index, Value value, ExecutionContext *ctx); +void __qmljs_inplace_mod_element(Value base, Value index, Value value, ExecutionContext *ctx); +void __qmljs_inplace_shl_element(Value base, Value index, Value value, ExecutionContext *ctx); +void __qmljs_inplace_shr_element(Value base, Value index, Value value, ExecutionContext *ctx); +void __qmljs_inplace_ushr_element(Value base, Value index, Value value, ExecutionContext *ctx); + +void __qmljs_inplace_bit_and_member(Value value, Value base, String *name, ExecutionContext *ctx); +void __qmljs_inplace_bit_or_member(Value value, Value base, String *name, ExecutionContext *ctx); +void __qmljs_inplace_bit_xor_member(Value value, Value base, String *name, ExecutionContext *ctx); +void __qmljs_inplace_add_member(Value value, Value base, String *name, ExecutionContext *ctx); +void __qmljs_inplace_sub_member(Value value, Value base, String *name, ExecutionContext *ctx); +void __qmljs_inplace_mul_member(Value value, Value base, String *name, ExecutionContext *ctx); +void __qmljs_inplace_div_member(Value value, Value base, String *name, ExecutionContext *ctx); +void __qmljs_inplace_mod_member(Value value, Value base, String *name, ExecutionContext *ctx); +void __qmljs_inplace_shl_member(Value value, Value base, String *name, ExecutionContext *ctx); +void __qmljs_inplace_shr_member(Value value, Value base, String *name, ExecutionContext *ctx); +void __qmljs_inplace_ushr_member(Value value, Value base, String *name, ExecutionContext *ctx); + +Bool __qmljs_cmp_gt(Value left, Value right, ExecutionContext *ctx); +Bool __qmljs_cmp_lt(Value left, Value right, ExecutionContext *ctx); +Bool __qmljs_cmp_ge(Value left, Value right, ExecutionContext *ctx); +Bool __qmljs_cmp_le(Value left, Value right, ExecutionContext *ctx); +Bool __qmljs_cmp_eq(Value left, Value right, ExecutionContext *ctx); +Bool __qmljs_cmp_ne(Value left, Value right, ExecutionContext *ctx); +Bool __qmljs_cmp_se(Value left, Value right, ExecutionContext *ctx); +Bool __qmljs_cmp_sne(Value left, Value right, ExecutionContext *ctx); +Bool __qmljs_cmp_instanceof(Value left, Value right, ExecutionContext *ctx); +Bool __qmljs_cmp_in(Value left, Value right, ExecutionContext *ctx); + +// type conversion and testing +inline Value __qmljs_to_primitive(Value value, ExecutionContext *ctx, int typeHint) +{ + if (!value.isObject()) + return value; + return __qmljs_default_value(value, ctx, typeHint); +} + +inline Bool __qmljs_to_boolean(Value value, ExecutionContext *ctx) +{ + switch (value.type()) { + case Value::Undefined_Type: + case Value::Null_Type: + return false; + case Value::Boolean_Type: + case Value::Integer_Type: + return (bool)value.int_32; + case Value::String_Type: + return __qmljs_string_length(ctx, value.stringValue()) > 0; + case Value::Object_Type: + return true; + default: // double + if (! value.doubleValue() || std::isnan(value.doubleValue())) + return false; + return true; + } +} + +inline double __qmljs_to_number(Value value, ExecutionContext *ctx) +{ + switch (value.type()) { + case Value::Undefined_Type: + return nan(""); + case Value::Null_Type: + return 0; + case Value::Boolean_Type: + return (value.booleanValue() ? 1. : 0.); + case Value::Integer_Type: + return value.int_32; + case Value::String_Type: + return __qmljs_string_to_number(value.stringValue()); + case Value::Object_Type: { + Value prim = __qmljs_to_primitive(value, ctx, NUMBER_HINT); + return __qmljs_to_number(prim, ctx); + } + default: // double + return value.doubleValue(); + } +} + +inline double __qmljs_to_integer(Value value, ExecutionContext *ctx) +{ + if (value.isConvertibleToInt()) + return value.int_32; + + return Value::toInteger(__qmljs_to_number(value, ctx)); +} + +inline int __qmljs_to_int32(Value value, ExecutionContext *ctx) +{ + if (value.isConvertibleToInt()) + return value.int_32; + + return Value::toInt32(__qmljs_to_number(value, ctx)); +} + +inline unsigned short __qmljs_to_uint16(Value value, ExecutionContext *ctx) +{ + if (value.isConvertibleToInt()) + return (ushort)(uint)value.integerValue(); + + double number = __qmljs_to_number(value, ctx); + + double D16 = 65536.0; + if ((number >= 0 && number < D16)) + return static_cast<ushort>(number); + + if (!std::isfinite(number)) + return +0; + + double d = ::floor(::fabs(number)); + if (std::signbit(number)) + d = -d; + + number = ::fmod(d , D16); + + if (number < 0) + number += D16; + + return (unsigned short)number; +} + +inline Value __qmljs_to_string(Value value, ExecutionContext *ctx) +{ + switch (value.type()) { + case Value::Undefined_Type: + return __qmljs_string_literal_undefined(ctx); + break; + case Value::Null_Type: + return __qmljs_string_literal_null(ctx); + break; + case Value::Boolean_Type: + if (value.booleanValue()) + return __qmljs_string_literal_true(ctx); + else + return __qmljs_string_literal_false(ctx); + break; + case Value::String_Type: + return value; + break; + case Value::Object_Type: { + Value prim = __qmljs_to_primitive(value, ctx, STRING_HINT); + if (prim.isPrimitive()) + return __qmljs_to_string(prim, ctx); + else + return __qmljs_throw_type_error(ctx); + break; + } + case Value::Integer_Type: + return __qmljs_string_from_number(ctx, value.int_32); + break; + default: // double + return __qmljs_string_from_number(ctx, value.doubleValue()); + break; + + } // switch +} + +inline Value __qmljs_to_object(Value value, ExecutionContext *ctx) +{ + switch (value.type()) { + case Value::Undefined_Type: + case Value::Null_Type: + return __qmljs_throw_type_error(ctx); + break; + case Value::Boolean_Type: + return __qmljs_new_boolean_object(ctx, value.booleanValue()); + break; + case Value::String_Type: + return __qmljs_new_string_object(ctx, value.stringValue()); + break; + case Value::Object_Type: + return value; + break; + case Value::Integer_Type: + return __qmljs_new_number_object(ctx, value.int_32); + break; + default: // double + return __qmljs_new_number_object(ctx, value.doubleValue()); + break; + } +} + +/* +inline uint __qmljs_check_object_coercible(Context *ctx, Value *result, Value *value) +{ + switch (value->type()) { + case Value::Undefined_Type: + case Value::Null_Type: + *result = __qmljs_throw_type_error(ctx); + return false; + default: + return true; + } +} +*/ + +inline Bool __qmljs_is_callable(Value value, ExecutionContext * /*ctx*/) +{ + if (value.isObject()) + return __qmljs_is_function(value); + else + return false; +} + +inline Value __qmljs_default_value(Value value, ExecutionContext *ctx, int typeHint) +{ + if (value.isObject()) + return __qmljs_object_default_value(ctx, value, typeHint); + return Value::undefinedValue(); +} + + +inline Value __qmljs_uplus(Value value, ExecutionContext *ctx) +{ + TRACE1(value); + + if (value.tryIntegerConversion()) + return value; + + double n = __qmljs_to_number(value, ctx); + return Value::fromDouble(n); +} + +inline Value __qmljs_uminus(Value value, ExecutionContext *ctx) +{ + TRACE1(value); + + // +0 != -0, so we need to convert to double when negating 0 + if (value.isInteger() && value.integerValue()) + return Value::fromInt32(-value.integerValue()); + double n = __qmljs_to_number(value, ctx); + return Value::fromDouble(-n); +} + +inline Value __qmljs_compl(Value value, ExecutionContext *ctx) +{ + TRACE1(value); + + int n; + if (value.isConvertibleToInt()) + n = value.int_32; + else + n = Value::toInt32(__qmljs_to_number(value, ctx)); + + return Value::fromInt32(~n); +} + +inline Value __qmljs_not(Value value, ExecutionContext *ctx) +{ + TRACE1(value); + + bool b = __qmljs_to_boolean(value, ctx); + return Value::fromBoolean(!b); +} + +// binary operators +inline Value __qmljs_bit_or(Value left, Value right, ExecutionContext *ctx) +{ + TRACE2(left, right); + + if (Value::integerCompatible(left, right)) + return Value::fromInt32(left.integerValue() | right.integerValue()); + + int lval = Value::toInt32(__qmljs_to_number(left, ctx)); + int rval = Value::toInt32(__qmljs_to_number(right, ctx)); + return Value::fromInt32(lval | rval); +} + +inline Value __qmljs_bit_xor(Value left, Value right, ExecutionContext *ctx) +{ + TRACE2(left, right); + + if (Value::integerCompatible(left, right)) + return Value::fromInt32(left.integerValue() ^ right.integerValue()); + + int lval = Value::toInt32(__qmljs_to_number(left, ctx)); + int rval = Value::toInt32(__qmljs_to_number(right, ctx)); + return Value::fromInt32(lval ^ rval); +} + +inline Value __qmljs_bit_and(Value left, Value right, ExecutionContext *ctx) +{ + TRACE2(left, right); + + if (Value::integerCompatible(left, right)) + return Value::fromInt32(left.integerValue() & right.integerValue()); + + int lval = Value::toInt32(__qmljs_to_number(left, ctx)); + int rval = Value::toInt32(__qmljs_to_number(right, ctx)); + return Value::fromInt32(lval & rval); +} + +inline Value __qmljs_add(Value left, Value right, ExecutionContext *ctx) +{ + TRACE2(left, right); + +#ifndef QMLJS_LLVM_RUNTIME + if (Value::integerCompatible(left, right)) + return add_int32(left.integerValue(), right.integerValue()); +#endif // QMLJS_LLVM_RUNTIME + + if (Value::bothDouble(left, right)) + return Value::fromDouble(left.doubleValue() + right.doubleValue()); + + return __qmljs_add_helper(left, right, ctx); +} + +inline Value __qmljs_sub(Value left, Value right, ExecutionContext *ctx) +{ + TRACE2(left, right); + +#ifndef QMLJS_LLVM_RUNTIME + if (Value::integerCompatible(left, right)) + return sub_int32(left.integerValue(), right.integerValue()); +#endif // QMLJS_LLVM_RUNTIME + + double lval = __qmljs_to_number(left, ctx); + double rval = __qmljs_to_number(right, ctx); + return Value::fromDouble(lval - rval); +} + +inline Value __qmljs_mul(Value left, Value right, ExecutionContext *ctx) +{ + TRACE2(left, right); + +#ifndef QMLJS_LLVM_RUNTIME + if (Value::integerCompatible(left, right)) + return mul_int32(left.integerValue(), right.integerValue()); +#endif // QMLJS_LLVM_RUNTIME + + double lval = __qmljs_to_number(left, ctx); + double rval = __qmljs_to_number(right, ctx); + return Value::fromDouble(lval * rval); +} + +inline Value __qmljs_div(Value left, Value right, ExecutionContext *ctx) +{ + TRACE2(left, right); + + double lval = __qmljs_to_number(left, ctx); + double rval = __qmljs_to_number(right, ctx); + return Value::fromDouble(lval / rval); +} + +inline Value __qmljs_mod(Value left, Value right, ExecutionContext *ctx) +{ + TRACE2(left, right); + + if (Value::integerCompatible(left, right) && right.integerValue() != 0) + return Value::fromInt32(left.integerValue() % right.integerValue()); + + double lval = __qmljs_to_number(left, ctx); + double rval = __qmljs_to_number(right, ctx); + return Value::fromDouble(fmod(lval, rval)); +} + +inline Value __qmljs_shl(Value left, Value right, ExecutionContext *ctx) +{ + TRACE2(left, right); + + if (Value::integerCompatible(left, right)) + return Value::fromInt32(left.integerValue() << ((uint(right.integerValue()) & 0x1f))); + + int lval = Value::toInt32(__qmljs_to_number(left, ctx)); + unsigned rval = Value::toUInt32(__qmljs_to_number(right, ctx)) & 0x1f; + return Value::fromInt32(lval << rval); +} + +inline Value __qmljs_shr(Value left, Value right, ExecutionContext *ctx) +{ + TRACE2(left, right); + + if (Value::integerCompatible(left, right)) + return Value::fromInt32(left.integerValue() >> ((uint(right.integerValue()) & 0x1f))); + + int lval = Value::toInt32(__qmljs_to_number(left, ctx)); + unsigned rval = Value::toUInt32(__qmljs_to_number(right, ctx)) & 0x1f; + return Value::fromInt32(lval >> rval); +} + +inline Value __qmljs_ushr(Value left, Value right, ExecutionContext *ctx) +{ + TRACE2(left, right); + + uint result; + if (Value::integerCompatible(left, right)) { + result = uint(left.integerValue()) >> (uint(right.integerValue()) & 0x1f); + } else { + unsigned lval = Value::toUInt32(__qmljs_to_number(left, ctx)); + unsigned rval = Value::toUInt32(__qmljs_to_number(right, ctx)) & 0x1f; + result = lval >> rval; + } + + if (result > INT_MAX) + return Value::fromDouble(result); + return Value::fromInt32(result); +} + +inline Value __qmljs_gt(Value left, Value right, ExecutionContext *ctx) +{ + TRACE2(left, right); + + return Value::fromBoolean(__qmljs_cmp_gt(left, right, ctx)); +} + +inline Value __qmljs_lt(Value left, Value right, ExecutionContext *ctx) +{ + TRACE2(left, right); + + return Value::fromBoolean(__qmljs_cmp_lt(left, right, ctx)); +} + +inline Value __qmljs_ge(Value left, Value right, ExecutionContext *ctx) +{ + TRACE2(left, right); + + return Value::fromBoolean(__qmljs_cmp_ge(left, right, ctx)); +} + +inline Value __qmljs_le(Value left, Value right, ExecutionContext *ctx) +{ + TRACE2(left, right); + + return Value::fromBoolean(__qmljs_cmp_le(left, right, ctx)); +} + +inline Value __qmljs_eq(Value left, Value right, ExecutionContext *ctx) +{ + TRACE2(left, right); + + return Value::fromBoolean(__qmljs_cmp_eq(left, right, ctx)); +} + +inline Value __qmljs_ne(Value left, Value right, ExecutionContext *ctx) +{ + TRACE2(left, right); + + return Value::fromBoolean(!__qmljs_cmp_eq(left, right, ctx)); +} + +inline Value __qmljs_se(Value left, Value right, ExecutionContext *) +{ + TRACE2(left, right); + + bool r = __qmljs_strict_equal(left, right); + return Value::fromBoolean(r); +} + +inline Value __qmljs_sne(Value left, Value right, ExecutionContext *) +{ + TRACE2(left, right); + + bool r = ! __qmljs_strict_equal(left, right); + return Value::fromBoolean(r); +} + +inline Bool __qmljs_cmp_gt(Value left, Value right, ExecutionContext *ctx) +{ + TRACE2(left, right); + + left = __qmljs_to_primitive(left, ctx, NUMBER_HINT); + right = __qmljs_to_primitive(right, ctx, NUMBER_HINT); + + if (Value::integerCompatible(left, right)) + return left.integerValue() > right.integerValue(); + if (Value::bothDouble(left, right)) { + return left.doubleValue() > right.doubleValue(); + } else if (left.isString() && right.isString()) { + return __qmljs_string_compare(ctx, right.stringValue(), left.stringValue()); + } else { + double l = __qmljs_to_number(left, ctx); + double r = __qmljs_to_number(right, ctx); + return l > r; + } +} + +inline Bool __qmljs_cmp_lt(Value left, Value right, ExecutionContext *ctx) +{ + TRACE2(left, right); + + left = __qmljs_to_primitive(left, ctx, NUMBER_HINT); + right = __qmljs_to_primitive(right, ctx, NUMBER_HINT); + + if (Value::integerCompatible(left, right)) + return left.integerValue() < right.integerValue(); + if (Value::bothDouble(left, right)) { + return left.doubleValue() < right.doubleValue(); + } else if (left.isString() && right.isString()) { + return __qmljs_string_compare(ctx, left.stringValue(), right.stringValue()); + } else { + double l = __qmljs_to_number(left, ctx); + double r = __qmljs_to_number(right, ctx); + return l < r; + } +} + +inline Bool __qmljs_cmp_ge(Value left, Value right, ExecutionContext *ctx) +{ + TRACE2(left, right); + + left = __qmljs_to_primitive(left, ctx, NUMBER_HINT); + right = __qmljs_to_primitive(right, ctx, NUMBER_HINT); + + if (Value::integerCompatible(left, right)) + return left.integerValue() >= right.integerValue(); + if (Value::bothDouble(left, right)) { + return left.doubleValue() >= right.doubleValue(); + } else if (left.isString() && right.isString()) { + return !__qmljs_string_compare(ctx, left.stringValue(), right.stringValue()); + } else { + double l = __qmljs_to_number(left, ctx); + double r = __qmljs_to_number(right, ctx); + return l >= r; + } +} + +inline Bool __qmljs_cmp_le(Value left, Value right, ExecutionContext *ctx) +{ + TRACE2(left, right); + + left = __qmljs_to_primitive(left, ctx, NUMBER_HINT); + right = __qmljs_to_primitive(right, ctx, NUMBER_HINT); + + if (Value::integerCompatible(left, right)) + return left.integerValue() <= right.integerValue(); + if (Value::bothDouble(left, right)) { + return left.doubleValue() <= right.doubleValue(); + } else if (left.isString() && right.isString()) { + return !__qmljs_string_compare(ctx, right.stringValue(), left.stringValue()); + } else { + double l = __qmljs_to_number(left, ctx); + double r = __qmljs_to_number(right, ctx); + return l <= r; + } +} + +inline Bool __qmljs_cmp_eq(Value left, Value right, ExecutionContext *ctx) +{ + TRACE2(left, right); + + // need to test for doubles first as NaN != NaN + if (Value::bothDouble(left, right)) + return left.doubleValue() == right.doubleValue(); + if (left.val == right.val) + return true; + if (left.isString() && right.isString()) + return __qmljs_string_equal(left.stringValue(), right.stringValue()); + + return __qmljs_equal(left, right, ctx); +} + +inline Bool __qmljs_cmp_ne(Value left, Value right, ExecutionContext *ctx) +{ + TRACE2(left, right); + + return !__qmljs_cmp_eq(left, right, ctx); +} + +inline Bool __qmljs_cmp_se(Value left, Value right, ExecutionContext *) +{ + TRACE2(left, right); + + return __qmljs_strict_equal(left, right); +} + +inline Bool __qmljs_cmp_sne(Value left, Value right, ExecutionContext *) +{ + TRACE2(left, right); + + return ! __qmljs_strict_equal(left, right); +} + +inline Bool __qmljs_cmp_instanceof(Value left, Value right, ExecutionContext *ctx) +{ + TRACE2(left, right); + + Value v = __qmljs_instanceof(left, right, ctx); + return v.booleanValue(); +} + +inline uint __qmljs_cmp_in(Value left, Value right, ExecutionContext *ctx) +{ + TRACE2(left, right); + + Value v = __qmljs_in(left, right, ctx); + return v.booleanValue(); +} + +inline Bool __qmljs_strict_equal(Value x, Value y) +{ + TRACE2(x, y); + + if (x.isDouble() || y.isDouble()) + return x.asDouble() == y.asDouble(); + if (x.rawValue() == y.rawValue()) + return true; + if (x.isString() && y.isString()) + return __qmljs_string_equal(x.stringValue(), y.stringValue()); + return false; +} + +} // extern "C" + +} // namespace VM +} // namespace QQmlJS + +#endif // QMLJS_RUNTIME_H diff --git a/src/v4/qmljs_value.cpp b/src/v4/qmljs_value.cpp new file mode 100644 index 0000000000..8dba13a9c9 --- /dev/null +++ b/src/v4/qmljs_value.cpp @@ -0,0 +1,163 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <qmljs_engine.h> +#include <qv4object.h> +#include <qv4objectproto.h> + +namespace QQmlJS { +namespace VM { + + +int Value::toUInt16(ExecutionContext *ctx) const +{ + return __qmljs_to_uint16(*this, ctx); +} + +Bool Value::toBoolean(ExecutionContext *ctx) const +{ + return __qmljs_to_boolean(*this, ctx); +} + +double Value::toInteger(ExecutionContext *ctx) const +{ + return __qmljs_to_integer(*this, ctx); +} + +double Value::toNumber(ExecutionContext *ctx) const +{ + return __qmljs_to_number(*this, ctx); +} + +String *Value::toString(ExecutionContext *ctx) const +{ + Value v = __qmljs_to_string(*this, ctx); + assert(v.isString()); + return v.stringValue(); +} + +Value Value::toObject(ExecutionContext *ctx) const +{ + return __qmljs_to_object(*this, ctx); +} + +bool Value::sameValue(Value other) const { + if (val == other.val) + return true; + if (isString() && other.isString()) + return stringValue()->isEqualTo(other.stringValue()); + if (isInteger() && int_32 == 0 && other.dbl == 0) + return true; + if (dbl == 0 && other.isInteger() && other.int_32 == 0) + return true; + return false; +} + +Value Value::fromString(ExecutionContext *ctx, const QString &s) +{ + return fromString(ctx->engine->newString(s)); +} + +int Value::toInt32(double number) +{ + const double D32 = 4294967296.0; + const double D31 = D32 / 2.0; + + if ((number >= -D31 && number < D31)) + return static_cast<int>(number); + + + if (!std::isfinite(number)) + return 0; + + double d = ::floor(::fabs(number)); + if (std::signbit(number)) + d = -d; + + number = ::fmod(d , D32); + + if (number < -D31) + number += D32; + else if (number >= D31) + number -= D32; + + return int(number); +} + +unsigned int Value::toUInt32(double number) +{ + const double D32 = 4294967296.0; + if ((number >= 0 && number < D32)) + return static_cast<uint>(number); + + if (!std::isfinite(number)) + return +0; + + double d = ::floor(::fabs(number)); + if (std::signbit(number)) + d = -d; + + number = ::fmod(d , D32); + + if (number < 0) + number += D32; + + return unsigned(number); +} + +double Value::toInteger(double number) +{ + if (std::isnan(number)) + return +0; + else if (! number || std::isinf(number)) + return number; + const double v = floor(fabs(number)); + return std::signbit(number) ? -v : v; +} + +Value Value::property(ExecutionContext *ctx, String *name) const +{ + return isObject() ? objectValue()->__get__(ctx, name) : undefinedValue(); +} + + + +} // namespace VM +} // namespace QQmlJS diff --git a/src/v4/qmljs_value.h b/src/v4/qmljs_value.h new file mode 100644 index 0000000000..e11b08f50c --- /dev/null +++ b/src/v4/qmljs_value.h @@ -0,0 +1,480 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QMLJS_VALUE_H +#define QMLJS_VALUE_H + +#include <QtCore/QString> +#include <QtCore/qnumeric.h> +#include "qv4global.h" +#include "qv4string.h" +#include <QtCore/QDebug> +#include "qv4managed.h" + +namespace QQmlJS { +namespace VM { + +struct String; +struct ExecutionContext; +struct ExecutionEngine; +struct Value; + +extern "C" { +double __qmljs_to_number(Value value, ExecutionContext *ctx); +} + +typedef uint Bool; + + +struct Q_V4_EXPORT Value +{ + union { + quint64 val; + double dbl; + struct { +#if Q_BYTE_ORDER != Q_LITTLE_ENDIAN + uint tag; +#endif + union { + uint uint_32; + int int_32; +#if CPU(X86_64) +#else + Managed *m; + Object *o; + String *s; +#endif + }; +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN + uint tag; +#endif + }; + }; + + enum Masks { + NotDouble_Mask = 0xfffc0000, + Type_Mask = 0xffff8000, + Immediate_Mask = NotDouble_Mask | 0x00008000, + Tag_Shift = 32 + }; + enum ValueType { + Undefined_Type = Immediate_Mask | 0x00000, + Null_Type = Immediate_Mask | 0x10000, + Boolean_Type = Immediate_Mask | 0x20000, + Integer_Type = Immediate_Mask | 0x30000, + Object_Type = NotDouble_Mask | 0x00000, + String_Type = NotDouble_Mask | 0x10000 + }; + + enum ImmediateFlags { + ConvertibleToInt = Immediate_Mask | 0x1 + }; + + enum ValueTypeInternal { + _Undefined_Type = Undefined_Type, + _Null_Type = Null_Type | ConvertibleToInt, + _Boolean_Type = Boolean_Type | ConvertibleToInt, + _Integer_Type = Integer_Type | ConvertibleToInt, + _Object_Type = Object_Type, + _String_Type = String_Type + + }; + + inline unsigned type() const { + return tag & Type_Mask; + } + + inline bool isUndefined() const { return tag == _Undefined_Type; } + inline bool isNull() const { return tag == _Null_Type; } + inline bool isBoolean() const { return tag == _Boolean_Type; } + inline bool isInteger() const { return tag == _Integer_Type; } + inline bool isDouble() const { return (tag & NotDouble_Mask) != NotDouble_Mask; } + inline bool isNumber() const { return tag == _Integer_Type || (tag & NotDouble_Mask) != NotDouble_Mask; } +#if CPU(X86_64) + inline bool isString() const { return (tag & Type_Mask) == String_Type; } + inline bool isObject() const { return (tag & Type_Mask) == Object_Type; } +#else + inline bool isString() const { return tag == String_Type; } + inline bool isObject() const { return tag == Object_Type; } +#endif + inline bool isConvertibleToInt() const { return (tag & ConvertibleToInt) == ConvertibleToInt; } + + Bool booleanValue() const { + return int_32; + } + double doubleValue() const { + return dbl; + } + void setDouble(double d) { + dbl = d; + } + double asDouble() const { + if (tag == _Integer_Type) + return int_32; + return dbl; + } + int integerValue() const { + return int_32; + } + +#if CPU(X86_64) + String *stringValue() const { + return (String *)(val & ~(quint64(Type_Mask) << Tag_Shift)); + } + Object *objectValue() const { + return (Object *)(val & ~(quint64(Type_Mask) << Tag_Shift)); + } + Managed *managed() const { + return (Managed *)(val & ~(quint64(Type_Mask) << Tag_Shift)); + } +#else + String *stringValue() const { + return s; + } + Object *objectValue() const { + return o; + } + Managed *managed() const { + return m; + } +#endif + + quint64 rawValue() const { + return val; + } + + static Value undefinedValue(); + static Value nullValue(); + static Value fromBoolean(Bool b); + static Value fromDouble(double d); + static Value fromInt32(int i); + static Value fromUInt32(uint i); + static Value fromString(String *s); + static Value fromObject(Object *o); + +#ifndef QMLJS_LLVM_RUNTIME + static Value fromString(ExecutionContext *ctx, const QString &fromString); +#endif + + static double toInteger(double fromNumber); + static int toInt32(double value); + static unsigned int toUInt32(double value); + + int toUInt16(ExecutionContext *ctx) const; + int toInt32(ExecutionContext *ctx) const; + unsigned int toUInt32(ExecutionContext *ctx) const; + + Bool toBoolean(ExecutionContext *ctx) const; + double toInteger(ExecutionContext *ctx) const; + double toNumber(ExecutionContext *ctx) const; + String *toString(ExecutionContext *ctx) const; + Value toObject(ExecutionContext *ctx) const; + + inline bool isPrimitive() const { return !isObject(); } +#if CPU(X86_64) + inline bool integerCompatible() const { + const quint64 mask = quint64(ConvertibleToInt) << 32; + return (val & mask) == mask; + } + static inline bool integerCompatible(Value a, Value b) { + const quint64 mask = quint64(ConvertibleToInt) << 32; + return ((a.val & b.val) & mask) == mask; + } + static inline bool bothDouble(Value a, Value b) { + const quint64 mask = quint64(NotDouble_Mask) << 32; + return ((a.val | b.val) & mask) != mask; + } +#else + inline bool integerCompatible() const { + return (tag & ConvertibleToInt) == ConvertibleToInt; + } + static inline bool integerCompatible(Value a, Value b) { + return ((a.tag & b.tag) & ConvertibleToInt) == ConvertibleToInt; + } + static inline bool bothDouble(Value a, Value b) { + return ((a.tag | b.tag) & NotDouble_Mask) != NotDouble_Mask; + } +#endif + inline bool tryIntegerConversion() { + bool b = isConvertibleToInt(); + if (b) + tag = _Integer_Type; + return b; + } + + Managed *asManaged() const; + Object *asObject() const; + FunctionObject *asFunctionObject() const; + BooleanObject *asBooleanObject() const; + NumberObject *asNumberObject() const; + StringObject *asStringObject() const; + DateObject *asDateObject() const; + RegExpObject *asRegExpObject() const; + ArrayObject *asArrayObject() const; + ErrorObject *asErrorObject() const; + uint asArrayIndex() const; + uint asArrayLength(ExecutionContext *ctx, bool *ok) const; + + Value property(ExecutionContext *ctx, String *name) const; + + // Section 9.12 + bool sameValue(Value other) const; + + void mark() const { + Managed *m = asManaged(); + if (m) + m->mark(); + } +}; + +inline Value Value::undefinedValue() +{ + Value v; +#if CPU(X86_64) + v.val = quint64(_Undefined_Type) << Tag_Shift; +#else + v.tag = _Undefined_Type; + v.int_32 = 0; +#endif + return v; +} + +inline Value Value::nullValue() +{ + Value v; +#if CPU(X86_64) + v.val = quint64(_Null_Type) << Tag_Shift; +#else + v.tag = _Null_Type; + v.int_32 = 0; +#endif + return v; +} + +inline Value Value::fromBoolean(Bool b) +{ + Value v; + v.tag = _Boolean_Type; + v.int_32 = (bool)b; + return v; +} + +inline Value Value::fromDouble(double d) +{ + Value v; + v.dbl = d; + return v; +} + +inline Value Value::fromInt32(int i) +{ + Value v; + v.tag = _Integer_Type; + v.int_32 = i; + return v; +} + +inline Value Value::fromUInt32(uint i) +{ + Value v; + if (i < INT_MAX) { + v.tag = _Integer_Type; + v.int_32 = (int)i; + } else { + v.dbl = i; + } + return v; +} + +inline Value Value::fromString(String *s) +{ + Value v; +#if CPU(X86_64) + v.val = (quint64)s; + v.val |= quint64(_String_Type) << Tag_Shift; +#else + v.tag = _String_Type; + v.s = s; +#endif + return v; +} + +inline Value Value::fromObject(Object *o) +{ + Value v; +#if CPU(X86_64) + v.val = (quint64)o; + v.val |= quint64(_Object_Type) << Tag_Shift; +#else + v.tag = _Object_Type; + v.o = o; +#endif + return v; +} + +inline int Value::toInt32(ExecutionContext *ctx) const +{ + if (isConvertibleToInt()) + return int_32; + double d; + if (isDouble()) + d = dbl; + else + d = __qmljs_to_number(*this, ctx); + + const double D32 = 4294967296.0; + const double D31 = D32 / 2.0; + + if ((d >= -D31 && d < D31)) + return static_cast<int>(d); + + return Value::toInt32(__qmljs_to_number(*this, ctx)); +} + +inline unsigned int Value::toUInt32(ExecutionContext *ctx) const { + if (isConvertibleToInt()) + return (unsigned) int_32; + double d; + if (isDouble()) + d = dbl; + else + d = __qmljs_to_number(*this, ctx); + + const double D32 = 4294967296.0; + if (dbl >= 0 && dbl < D32) + return static_cast<uint>(dbl); + return toUInt32(d); +} + +inline uint Value::asArrayIndex() const +{ + if (isInteger() && int_32 >= 0) + return (uint)int_32; + if (!isDouble()) + return UINT_MAX; + uint idx = (uint)dbl; + if (idx != dbl) + return UINT_MAX; + return idx; +} + +inline uint Value::asArrayLength(ExecutionContext *ctx, bool *ok) const +{ + *ok = true; + if (isConvertibleToInt() && int_32 >= 0) + return (uint)int_32; + if (isDouble()) { + uint idx = (uint)dbl; + if ((double)idx != dbl) { + *ok = false; + return UINT_MAX; + } + return idx; + } + if (isString()) + return stringValue()->toUInt(ok); + + uint idx = toUInt32(ctx); + double d = toNumber(ctx); + if (d != idx) { + *ok = false; + return UINT_MAX; + } + return idx; +} + + +inline Managed *Value::asManaged() const +{ + if (isObject() || isString()) + return managed(); + return 0; +} + +inline Object *Value::asObject() const +{ + return isObject() ? objectValue() : 0; +} + +inline FunctionObject *Value::asFunctionObject() const +{ + return isObject() ? managed()->asFunctionObject() : 0; +} + +inline BooleanObject *Value::asBooleanObject() const +{ + return isObject() ? managed()->asBooleanObject() : 0; +} + +inline NumberObject *Value::asNumberObject() const +{ + return isObject() ? managed()->asNumberObject() : 0; +} + +inline StringObject *Value::asStringObject() const +{ + return isObject() ? managed()->asStringObject() : 0; +} + +inline DateObject *Value::asDateObject() const +{ + return isObject() ? managed()->asDateObject() : 0; +} + +inline RegExpObject *Value::asRegExpObject() const +{ + return isObject() ? managed()->asRegExpObject() : 0; +} + +inline ArrayObject *Value::asArrayObject() const +{ + return isObject() ? managed()->asArrayObject() : 0; +} + +inline ErrorObject *Value::asErrorObject() const +{ + return isObject() ? managed()->asErrorObject() : 0; +} + + +} // namespace VM +} // namespace QQmlJS + +#endif diff --git a/src/v4/qv4_llvm_p.h b/src/v4/qv4_llvm_p.h new file mode 100644 index 0000000000..60625c8677 --- /dev/null +++ b/src/v4/qv4_llvm_p.h @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QV4_LLVM_P_H +#define QV4_LLVM_P_H + +#include "qv4global.h" +#include "qv4ir_p.h" + +#include <QtCore/QString> + +namespace QQmlJS { + +// Note: keep this enum in sync with the command-line option! +enum LLVMOutputType { + LLVMOutputJit = -1, + LLVMOutputIR = 0, // .ll + LLVMOutputBitcode = 1, // .bc + LLVMOutputAssembler = 2, // .s + LLVMOutputObject = 3 // .o +}; + +Q_V4_EXPORT int compileWithLLVM(IR::Module *module, const QString &fileName, LLVMOutputType outputType, int (*)(void *)); + +} // QQmlJS + +#endif // QV4_LLVM_P_H diff --git a/src/v4/qv4argumentsobject.cpp b/src/v4/qv4argumentsobject.cpp new file mode 100644 index 0000000000..f7327506e9 --- /dev/null +++ b/src/v4/qv4argumentsobject.cpp @@ -0,0 +1,161 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <qv4argumentsobject.h> + +namespace QQmlJS { +namespace VM { + + +ArgumentsObject::ArgumentsObject(ExecutionContext *context, int formalParameterCount, int actualParameterCount) + : context(context) +{ + type = Type_ArgumentsObject; + + defineDefaultProperty(context->engine->id_length, Value::fromInt32(actualParameterCount)); + if (context->strictMode) { + for (uint i = 0; i < context->argumentCount; ++i) + Object::__put__(context, QString::number(i), context->arguments[i]); + FunctionObject *thrower = context->engine->newBuiltinFunction(context, 0, __qmljs_throw_type_error); + PropertyDescriptor pd = PropertyDescriptor::fromAccessor(thrower, thrower); + pd.configurable = PropertyDescriptor::Disabled; + pd.enumberable = PropertyDescriptor::Disabled; + __defineOwnProperty__(context, QStringLiteral("callee"), &pd); + __defineOwnProperty__(context, QStringLiteral("caller"), &pd); + } else { + uint numAccessors = qMin(formalParameterCount, actualParameterCount); + context->engine->requireArgumentsAccessors(numAccessors); + for (uint i = 0; i < (uint)numAccessors; ++i) { + mappedArguments.append(context->argument(i)); + __defineOwnProperty__(context, i, &context->engine->argumentsAccessors.at(i)); + } + PropertyDescriptor pd; + pd.type = PropertyDescriptor::Data; + pd.writable = PropertyDescriptor::Enabled; + pd.configurable = PropertyDescriptor::Enabled; + pd.enumberable = PropertyDescriptor::Enabled; + for (uint i = numAccessors; i < qMin((uint)actualParameterCount, context->argumentCount); ++i) { + pd.value = context->argument(i); + __defineOwnProperty__(context, i, &pd); + } + defineDefaultProperty(context, QStringLiteral("callee"), Value::fromObject(context->function)); + isNonStrictArgumentsObject = true; + } +} + +bool ArgumentsObject::defineOwnProperty(ExecutionContext *ctx, uint index, const PropertyDescriptor *desc) +{ + PropertyDescriptor *pd = array.at(index); + PropertyDescriptor map; + bool isMapped = false; + if (pd && index < (uint)mappedArguments.size()) + isMapped = pd->isAccessor() && pd->get == context->engine->argumentsAccessors.at(index).get; + + if (isMapped) { + map = *pd; + pd->type = PropertyDescriptor::Data; + pd->writable = PropertyDescriptor::Enabled; + pd->configurable = PropertyDescriptor::Enabled; + pd->enumberable = PropertyDescriptor::Enabled; + pd->value = mappedArguments.at(index); + } + + isNonStrictArgumentsObject = false; + bool strict = ctx->strictMode; + ctx->strictMode = false; + bool result = Object::__defineOwnProperty__(ctx, index, desc); + ctx->strictMode = strict; + isNonStrictArgumentsObject = true; + + if (isMapped && desc->isData()) { + if (desc->type != PropertyDescriptor::Generic) { + Value arg = desc->value; + map.set->call(ctx, Value::fromObject(this), &arg, 1); + } + if (desc->writable != PropertyDescriptor::Disabled) + *pd = map; + } + + if (ctx->strictMode && !result) + __qmljs_throw_type_error(ctx); + return result; +} + +void ArgumentsObject::markObjects() +{ + for (int i = 0; i < mappedArguments.size(); ++i) { + Managed *m = mappedArguments.at(i).asManaged(); + if (m) + m->mark(); + } + Object::markObjects(); +} + + +Value ArgumentsGetterFunction::call(ExecutionContext *ctx, Value thisObject, Value *, int) +{ + Object *that = thisObject.asObject(); + if (!that) + __qmljs_throw_type_error(ctx); + ArgumentsObject *o = that->asArgumentsObject(); + if (!o) + __qmljs_throw_type_error(ctx); + + assert(index < o->context->argumentCount); + return o->context->argument(index); +} + +Value ArgumentsSetterFunction::call(ExecutionContext *ctx, Value thisObject, Value *args, int argc) +{ + Object *that = thisObject.asObject(); + if (!that) + __qmljs_throw_type_error(ctx); + ArgumentsObject *o = that->asArgumentsObject(); + if (!o) + __qmljs_throw_type_error(ctx); + + assert(index < o->context->argumentCount); + o->context->arguments[index] = argc ? args[0] : Value::undefinedValue(); + return Value::undefinedValue(); +} + + +} +} diff --git a/src/v4/qv4argumentsobject.h b/src/v4/qv4argumentsobject.h new file mode 100644 index 0000000000..adbaf5db1a --- /dev/null +++ b/src/v4/qv4argumentsobject.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4ARGUMENTSOBJECTS_H +#define QV4ARGUMENTSOBJECTS_H + +#include <qv4object.h> +#include <qv4functionobject.h> + +namespace QQmlJS { +namespace VM { + +struct ArgumentsGetterFunction: FunctionObject +{ + uint index; + + ArgumentsGetterFunction(ExecutionContext *scope, uint index) + : FunctionObject(scope), index(index) {} + + virtual Value call(ExecutionContext *ctx, Value thisObject, Value *, int); +}; + +struct ArgumentsSetterFunction: FunctionObject +{ + uint index; + + ArgumentsSetterFunction(ExecutionContext *scope, uint index) + : FunctionObject(scope), index(index) {} + + virtual Value call(ExecutionContext *ctx, Value thisObject, Value *args, int argc); +}; + + +struct ArgumentsObject: Object { + ExecutionContext *context; + QVector<Value> mappedArguments; + ArgumentsObject(ExecutionContext *context, int formalParameterCount, int actualParameterCount); + + bool defineOwnProperty(ExecutionContext *ctx, uint index, const PropertyDescriptor *desc); + + virtual void markObjects(); +}; + +} +} + +#endif + diff --git a/src/v4/qv4array.cpp b/src/v4/qv4array.cpp new file mode 100644 index 0000000000..9879b2f372 --- /dev/null +++ b/src/v4/qv4array.cpp @@ -0,0 +1,649 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4array.h" +#include "qmljs_runtime.h" +#include "qv4object.h" +#include "qv4functionobject.h" +#include <stdlib.h> + +#ifdef QT_QMAP_DEBUG +# include <qstring.h> +# include <qvector.h> +#endif + +namespace QQmlJS { +namespace VM { + +bool ArrayElementLessThan::operator()(const PropertyDescriptor &p1, const PropertyDescriptor &p2) const +{ + if (p1.type == PropertyDescriptor::Generic) + return false; + if (p2.type == PropertyDescriptor::Generic) + return true; + Value v1 = thisObject->getValue(m_context, &p1); + Value v2 = thisObject->getValue(m_context, &p2); + + if (v1.isUndefined()) + return false; + if (v2.isUndefined()) + return true; + if (!m_comparefn.isUndefined()) { + Value args[] = { v1, v2 }; + Value result = __qmljs_call_value(m_context, Value::undefinedValue(), m_comparefn, args, 2); + return result.toNumber(m_context) <= 0; + } + return v1.toString(m_context)->toQString() < v2.toString(m_context)->toQString(); +} + + +const SparseArrayNode *SparseArrayNode::nextNode() const +{ + const SparseArrayNode *n = this; + if (n->right) { + n = n->right; + while (n->left) + n = n->left; + } else { + const SparseArrayNode *y = n->parent(); + while (y && n == y->right) { + n = y; + y = n->parent(); + } + n = y; + } + return n; +} + +const SparseArrayNode *SparseArrayNode::previousNode() const +{ + const SparseArrayNode *n = this; + if (n->left) { + n = n->left; + while (n->right) + n = n->right; + } else { + const SparseArrayNode *y = n->parent(); + while (y && n == y->left) { + n = y; + y = n->parent(); + } + n = y; + } + return n; +} + +SparseArrayNode *SparseArrayNode::copy(SparseArray *d) const +{ + SparseArrayNode *n = d->createNode(size_left, 0, false); + n->value = value; + n->setColor(color()); + if (left) { + n->left = left->copy(d); + n->left->setParent(n); + } else { + n->left = 0; + } + if (right) { + n->right = right->copy(d); + n->right->setParent(n); + } else { + n->right = 0; + } + return n; +} + +/* + x y + \ / \ + y --> x b + / \ \ + a b a +*/ +void SparseArray::rotateLeft(SparseArrayNode *x) +{ + SparseArrayNode *&root = header.left; + SparseArrayNode *y = x->right; + x->right = y->left; + if (y->left != 0) + y->left->setParent(x); + y->setParent(x->parent()); + if (x == root) + root = y; + else if (x == x->parent()->left) + x->parent()->left = y; + else + x->parent()->right = y; + y->left = x; + x->setParent(y); + y->size_left += x->size_left; +} + + +/* + x y + / / \ + y --> a x + / \ / + a b b +*/ +void SparseArray::rotateRight(SparseArrayNode *x) +{ + SparseArrayNode *&root = header.left; + SparseArrayNode *y = x->left; + x->left = y->right; + if (y->right != 0) + y->right->setParent(x); + y->setParent(x->parent()); + if (x == root) + root = y; + else if (x == x->parent()->right) + x->parent()->right = y; + else + x->parent()->left = y; + y->right = x; + x->setParent(y); + x->size_left -= y->size_left; +} + + +void SparseArray::rebalance(SparseArrayNode *x) +{ + SparseArrayNode *&root = header.left; + x->setColor(SparseArrayNode::Red); + while (x != root && x->parent()->color() == SparseArrayNode::Red) { + if (x->parent() == x->parent()->parent()->left) { + SparseArrayNode *y = x->parent()->parent()->right; + if (y && y->color() == SparseArrayNode::Red) { + x->parent()->setColor(SparseArrayNode::Black); + y->setColor(SparseArrayNode::Black); + x->parent()->parent()->setColor(SparseArrayNode::Red); + x = x->parent()->parent(); + } else { + if (x == x->parent()->right) { + x = x->parent(); + rotateLeft(x); + } + x->parent()->setColor(SparseArrayNode::Black); + x->parent()->parent()->setColor(SparseArrayNode::Red); + rotateRight (x->parent()->parent()); + } + } else { + SparseArrayNode *y = x->parent()->parent()->left; + if (y && y->color() == SparseArrayNode::Red) { + x->parent()->setColor(SparseArrayNode::Black); + y->setColor(SparseArrayNode::Black); + x->parent()->parent()->setColor(SparseArrayNode::Red); + x = x->parent()->parent(); + } else { + if (x == x->parent()->left) { + x = x->parent(); + rotateRight(x); + } + x->parent()->setColor(SparseArrayNode::Black); + x->parent()->parent()->setColor(SparseArrayNode::Red); + rotateLeft(x->parent()->parent()); + } + } + } + root->setColor(SparseArrayNode::Black); +} + +void SparseArray::deleteNode(SparseArrayNode *z) +{ + SparseArrayNode *&root = header.left; + SparseArrayNode *y = z; + SparseArrayNode *x; + SparseArrayNode *x_parent; + if (y->left == 0) { + x = y->right; + if (y == mostLeftNode) { + if (x) + mostLeftNode = x; // It cannot have (left) children due the red black invariant. + else + mostLeftNode = y->parent(); + } + } else { + if (y->right == 0) { + x = y->left; + } else { + y = y->right; + while (y->left != 0) + y = y->left; + x = y->right; + } + } + if (y != z) { + z->left->setParent(y); + y->left = z->left; + if (y != z->right) { + x_parent = y->parent(); + if (x) + x->setParent(y->parent()); + y->parent()->left = x; + y->right = z->right; + z->right->setParent(y); + } else { + x_parent = y; + } + if (root == z) + root = y; + else if (z->parent()->left == z) + z->parent()->left = y; + else + z->parent()->right = y; + y->setParent(z->parent()); + // Swap the colors + SparseArrayNode::Color c = y->color(); + y->setColor(z->color()); + z->setColor(c); + y = z; + } else { + x_parent = y->parent(); + if (x) + x->setParent(y->parent()); + if (root == z) + root = x; + else if (z->parent()->left == z) + z->parent()->left = x; + else + z->parent()->right = x; + } + if (y->color() != SparseArrayNode::Red) { + while (x != root && (x == 0 || x->color() == SparseArrayNode::Black)) { + if (x == x_parent->left) { + SparseArrayNode *w = x_parent->right; + if (w->color() == SparseArrayNode::Red) { + w->setColor(SparseArrayNode::Black); + x_parent->setColor(SparseArrayNode::Red); + rotateLeft(x_parent); + w = x_parent->right; + } + if ((w->left == 0 || w->left->color() == SparseArrayNode::Black) && + (w->right == 0 || w->right->color() == SparseArrayNode::Black)) { + w->setColor(SparseArrayNode::Red); + x = x_parent; + x_parent = x_parent->parent(); + } else { + if (w->right == 0 || w->right->color() == SparseArrayNode::Black) { + if (w->left) + w->left->setColor(SparseArrayNode::Black); + w->setColor(SparseArrayNode::Red); + rotateRight(w); + w = x_parent->right; + } + w->setColor(x_parent->color()); + x_parent->setColor(SparseArrayNode::Black); + if (w->right) + w->right->setColor(SparseArrayNode::Black); + rotateLeft(x_parent); + break; + } + } else { + SparseArrayNode *w = x_parent->left; + if (w->color() == SparseArrayNode::Red) { + w->setColor(SparseArrayNode::Black); + x_parent->setColor(SparseArrayNode::Red); + rotateRight(x_parent); + w = x_parent->left; + } + if ((w->right == 0 || w->right->color() == SparseArrayNode::Black) && + (w->left == 0 || w->left->color() == SparseArrayNode::Black)) { + w->setColor(SparseArrayNode::Red); + x = x_parent; + x_parent = x_parent->parent(); + } else { + if (w->left == 0 || w->left->color() == SparseArrayNode::Black) { + if (w->right) + w->right->setColor(SparseArrayNode::Black); + w->setColor(SparseArrayNode::Red); + rotateLeft(w); + w = x_parent->left; + } + w->setColor(x_parent->color()); + x_parent->setColor(SparseArrayNode::Black); + if (w->left) + w->left->setColor(SparseArrayNode::Black); + rotateRight(x_parent); + break; + } + } + } + if (x) + x->setColor(SparseArrayNode::Black); + } + free(y); + --numEntries; +} + +void SparseArray::recalcMostLeftNode() +{ + mostLeftNode = &header; + while (mostLeftNode->left) + mostLeftNode = mostLeftNode->left; +} + +static inline int qMapAlignmentThreshold() +{ + // malloc on 32-bit platforms should return pointers that are 8-byte + // aligned or more while on 64-bit platforms they should be 16-byte aligned + // or more + return 2 * sizeof(void*); +} + +static inline void *qMapAllocate(int alloc, int alignment) +{ + return alignment > qMapAlignmentThreshold() + ? qMallocAligned(alloc, alignment) + : ::malloc(alloc); +} + +static inline void qMapDeallocate(SparseArrayNode *node, int alignment) +{ + if (alignment > qMapAlignmentThreshold()) + qFreeAligned(node); + else + ::free(node); +} + +SparseArrayNode *SparseArray::createNode(uint sl, SparseArrayNode *parent, bool left) +{ + SparseArrayNode *node = static_cast<SparseArrayNode *>(qMapAllocate(sizeof(SparseArrayNode), Q_ALIGNOF(SparseArrayNode))); + Q_CHECK_PTR(node); + + node->p = (quintptr)parent; + node->left = 0; + node->right = 0; + node->size_left = sl; + node->value = UINT_MAX; + ++numEntries; + + if (parent) { + if (left) { + parent->left = node; + if (parent == mostLeftNode) + mostLeftNode = node; + } else { + parent->right = node; + } + node->setParent(parent); + rebalance(node); + } + return node; +} + +void SparseArray::freeTree(SparseArrayNode *root, int alignment) +{ + if (root->left) + freeTree(root->left, alignment); + if (root->right) + freeTree(root->right, alignment); + qMapDeallocate(root, alignment); +} + +SparseArray::SparseArray() + : numEntries(0) +{ + header.p = 0; + header.left = 0; + header.right = 0; + mostLeftNode = &header; +} + +SparseArray::SparseArray(const SparseArray &other) +{ + header.p = 0; + header.right = 0; + if (other.header.left) { + header.left = other.header.left->copy(this); + header.left->setParent(&header); + recalcMostLeftNode(); + } +} + +SparseArrayNode *SparseArray::insert(uint akey) +{ + SparseArrayNode *n = root(); + SparseArrayNode *y = end(); + bool left = true; + uint s = akey; + while (n) { + y = n; + if (s == n->size_left) { + return n; + } else if (s < n->size_left) { + left = true; + n = n->left; + } else { + left = false; + s -= n->size_left; + n = n->right; + } + } + + return createNode(s, y, left); +} + +Array::Array(const Array &other) + : len(other.len) + , lengthProperty(0) + , values(other.values) + , sparse(0) +{ + freeList = other.freeList; + if (other.sparse) + sparse = new SparseArray(*other.sparse); +} + + +Value Array::indexOf(Value v, uint fromIndex, uint endIndex, ExecutionContext *ctx, Object *o) +{ + bool protoHasArray = false; + Object *p = o; + while ((p = p->prototype)) + if (p->array.length()) + protoHasArray = true; + + if (protoHasArray) { + // lets be safe and slow + for (uint i = fromIndex; i < endIndex; ++i) { + bool exists; + Value value = o->__get__(ctx, i, &exists); + if (exists && __qmljs_strict_equal(value, v)) + return Value::fromDouble(i); + } + } else if (sparse) { + for (SparseArrayNode *n = sparse->lowerBound(fromIndex); n && n->key() < endIndex; n = n->nextNode()) { + bool exists; + Value value = o->getValueChecked(ctx, descriptor(n->value), &exists); + if (exists && __qmljs_strict_equal(value, v)) + return Value::fromDouble(n->key()); + } + } else { + if ((int) endIndex > values.size()) + endIndex = values.size(); + PropertyDescriptor *pd = values.data() + offset; + PropertyDescriptor *end = pd + endIndex; + pd += fromIndex; + while (pd < end) { + bool exists; + Value value = o->getValueChecked(ctx, pd, &exists); + if (exists && __qmljs_strict_equal(value, v)) + return Value::fromDouble(pd - offset - values.constData()); + ++pd; + } + } + return Value::fromInt32(-1); +} + +void Array::concat(const Array &other) +{ + initSparse(); + int newLen = len + other.length(); + if (other.sparse) + initSparse(); + if (sparse) { + if (other.sparse) { + for (const SparseArrayNode *it = other.sparse->begin(); it != other.sparse->end(); it = it->nextNode()) + set(len + it->key(), other.descriptor(it->value)); + } else { + int oldSize = values.size(); + values.resize(oldSize + other.length()); + memcpy(values.data() + oldSize, other.values.constData() + other.offset, other.length()*sizeof(PropertyDescriptor)); + for (uint i = 0; i < other.length(); ++i) { + SparseArrayNode *n = sparse->insert(len + i); + n->value = oldSize + i; + } + } + } else { + int oldSize = values.size(); + values.resize(oldSize + other.length()); + memcpy(values.data() + oldSize, other.values.constData() + other.offset, other.length()*sizeof(PropertyDescriptor)); + } + setLengthUnchecked(newLen); +} + +void Array::sort(ExecutionContext *context, Object *thisObject, const Value &comparefn, uint len) +{ + if (sparse) { + context->throwUnimplemented("Array::sort unimplemented for sparse arrays"); + return; + delete sparse; + } + + ArrayElementLessThan lessThan(context, thisObject, comparefn); + if (len > values.size() - offset) + len = values.size() - offset; + PropertyDescriptor *begin = values.begin() + offset; + std::sort(begin, begin + len, lessThan); +} + + +void Array::initSparse() +{ + if (!sparse) { + sparse = new SparseArray; + for (int i = offset; i < values.size(); ++i) { + SparseArrayNode *n = sparse->insert(i - offset); + n->value = i; + } + + if (offset) { + int o = offset; + for (int i = 0; i < o - 1; ++i) { + values[i].type = PropertyDescriptor::Generic; + values[i].value = Value::fromInt32(i + 1); + } + values[o - 1].type = PropertyDescriptor::Generic; + values[o - 1].value = Value::fromInt32(values.size()); + freeList = 0; + } else { + freeList = values.size(); + } + } +} + +bool Array::setLength(uint newLen) { + if (lengthProperty && !lengthProperty->isWritable()) + return false; + uint oldLen = length(); + bool ok = true; + if (newLen < oldLen) { + if (sparse) { + SparseArrayNode *begin = sparse->lowerBound(newLen); + SparseArrayNode *it = sparse->end()->previousNode(); + while (1) { + PropertyDescriptor &pd = values[it->value]; + if (pd.type != PropertyDescriptor::Generic && !pd.isConfigurable()) { + ok = false; + newLen = it->key() + 1; + break; + } + pd.type = PropertyDescriptor::Generic; + pd.value.tag = Value::_Undefined_Type; + pd.value.int_32 = freeList; + freeList = it->value; + bool brk = (it == begin); + SparseArrayNode *prev = it->previousNode(); + sparse->erase(it); + if (brk) + break; + it = prev; + } + } else { + PropertyDescriptor *it = values.data() + values.size(); + const PropertyDescriptor *begin = values.constData() + offset + newLen; + while (--it >= begin) { + if (it->type != PropertyDescriptor::Generic && !it->isConfigurable()) { + ok = false; + newLen = it - values.data() + offset + 1; + break; + } + } + values.resize(newLen + offset); + } + } else { + if (newLen >= 0x100000) + initSparse(); + } + setLengthUnchecked(newLen); + return ok; +} + +void Array::markObjects() const +{ + uint i = sparse ? 0 : offset; + for (; i < (uint)values.size(); ++i) { + const PropertyDescriptor &pd = values.at(i); + if (pd.isData()) { + if (Managed *m = pd.value.asManaged()) + m->mark(); + } else if (pd.isAccessor()) { + if (pd.get) + pd.get->mark(); + if (pd.set) + pd.set->mark(); + } + } +} + +} +} diff --git a/src/v4/qv4array.h b/src/v4/qv4array.h new file mode 100644 index 0000000000..86d438e4e1 --- /dev/null +++ b/src/v4/qv4array.h @@ -0,0 +1,639 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4ARRAY_H +#define QV4ARRAY_H + +#include "qv4global.h" +#include <QtCore/qmap.h> +#include <qmljs_value.h> +#include <qv4propertydescriptor.h> +#include <assert.h> + +#ifdef Q_MAP_DEBUG +#include <QtCore/qdebug.h> +#endif + +#include <new> + +namespace QQmlJS { +namespace VM { + +struct SparseArray; + +class ArrayElementLessThan +{ +public: + inline ArrayElementLessThan(ExecutionContext *context, Object *thisObject, const Value &comparefn) + : m_context(context), thisObject(thisObject), m_comparefn(comparefn) {} + + bool operator()(const PropertyDescriptor &v1, const PropertyDescriptor &v2) const; + +private: + ExecutionContext *m_context; + Object *thisObject; + Value m_comparefn; +}; + + +struct SparseArrayNode +{ + quintptr p; + SparseArrayNode *left; + SparseArrayNode *right; + uint size_left; + uint value; + + enum Color { Red = 0, Black = 1 }; + enum { Mask = 3 }; // reserve the second bit as well + + const SparseArrayNode *nextNode() const; + SparseArrayNode *nextNode() { return const_cast<SparseArrayNode *>(const_cast<const SparseArrayNode *>(this)->nextNode()); } + const SparseArrayNode *previousNode() const; + SparseArrayNode *previousNode() { return const_cast<SparseArrayNode *>(const_cast<const SparseArrayNode *>(this)->previousNode()); } + + Color color() const { return Color(p & 1); } + void setColor(Color c) { if (c == Black) p |= Black; else p &= ~Black; } + SparseArrayNode *parent() const { return reinterpret_cast<SparseArrayNode *>(p & ~Mask); } + void setParent(SparseArrayNode *pp) { p = (p & Mask) | quintptr(pp); } + + uint key() const { + uint k = size_left; + const SparseArrayNode *n = this; + while (SparseArrayNode *p = n->parent()) { + if (p && p->right == n) + k += p->size_left; + n = p; + } + return k; + } + + SparseArrayNode *copy(SparseArray *d) const; + + SparseArrayNode *lowerBound(uint key); + SparseArrayNode *upperBound(uint key); +}; + + +inline SparseArrayNode *SparseArrayNode::lowerBound(uint akey) +{ + SparseArrayNode *n = this; + SparseArrayNode *last = 0; + while (n) { + if (akey <= n->size_left) { + last = n; + n = n->left; + } else { + akey -= n->size_left; + n = n->right; + } + } + return last; +} + + +inline SparseArrayNode *SparseArrayNode::upperBound(uint akey) +{ + SparseArrayNode *n = this; + SparseArrayNode *last = 0; + while (n) { + if (akey < n->size_left) { + last = n; + n = n->left; + } else { + akey -= n->size_left; + n = n->right; + } + } + return last; +} + + + +struct Q_V4_EXPORT SparseArray +{ + SparseArray(); + ~SparseArray() { + if (root()) + freeTree(header.left, Q_ALIGNOF(SparseArrayNode)); + } + + SparseArray(const SparseArray &other); +private: + SparseArray &operator=(const SparseArray &other); + + int numEntries; + SparseArrayNode header; + SparseArrayNode *mostLeftNode; + + void rotateLeft(SparseArrayNode *x); + void rotateRight(SparseArrayNode *x); + void rebalance(SparseArrayNode *x); + void recalcMostLeftNode(); + + SparseArrayNode *root() const { return header.left; } + + void deleteNode(SparseArrayNode *z); + + +public: + SparseArrayNode *createNode(uint sl, SparseArrayNode *parent, bool left); + void freeTree(SparseArrayNode *root, int alignment); + + SparseArrayNode *findNode(uint akey) const; + + uint pop_front(); + void push_front(uint at); + uint pop_back(uint len); + void push_back(uint at, uint len); + + QList<int> keys() const; + + const SparseArrayNode *end() const { return &header; } + SparseArrayNode *end() { return &header; } + const SparseArrayNode *begin() const { if (root()) return mostLeftNode; return end(); } + SparseArrayNode *begin() { if (root()) return mostLeftNode; return end(); } + + SparseArrayNode *erase(SparseArrayNode *n); + + SparseArrayNode *lowerBound(uint key); + const SparseArrayNode *lowerBound(uint key) const; + SparseArrayNode *upperBound(uint key); + const SparseArrayNode *upperBound(uint key) const; + SparseArrayNode *insert(uint akey); + + // STL compatibility + typedef uint key_type; + typedef int mapped_type; + typedef qptrdiff difference_type; + typedef int size_type; + +#ifdef Q_MAP_DEBUG + void dump() const; +#endif +}; + +inline SparseArrayNode *SparseArray::findNode(uint akey) const +{ + SparseArrayNode *n = root(); + + while (n) { + if (akey == n->size_left) { + return n; + } else if (akey < n->size_left) { + n = n->left; + } else { + akey -= n->size_left; + n = n->right; + } + } + + return 0; +} + +inline uint SparseArray::pop_front() +{ + uint idx = UINT_MAX ; + + SparseArrayNode *n = findNode(0); + if (n) { + idx = n->value; + deleteNode(n); + // adjust all size_left indices on the path to leftmost item by 1 + SparseArrayNode *n = root(); + while (n) { + n->size_left -= 1; + n = n->left; + } + } + return idx; +} + +inline void SparseArray::push_front(uint value) +{ + // adjust all size_left indices on the path to leftmost item by 1 + SparseArrayNode *n = root(); + while (n) { + n->size_left += 1; + n = n->left; + } + n = insert(0); + n->value = value; +} + +inline uint SparseArray::pop_back(uint len) +{ + uint idx = UINT_MAX; + if (!len) + return idx; + + SparseArrayNode *n = findNode(len - 1); + if (n) { + idx = n->value; + deleteNode(n); + } + return idx; +} + +inline void SparseArray::push_back(uint index, uint len) +{ + SparseArrayNode *n = insert(len); + n->value = index; +} + +#ifdef Q_MAP_DEBUG + +void SparseArray::dump() const +{ + const_iterator it = begin(); + qDebug() << "map dump:"; + while (it != end()) { + const SparseArrayNode *n = it.i; + int depth = 0; + while (n && n != root()) { + ++depth; + n = n->parent(); + } + QByteArray space(4*depth, ' '); + qDebug() << space << (it.i->color() == SparseArrayNode::Red ? "Red " : "Black") << it.i << it.i->left << it.i->right + << it.key() << it.value(); + ++it; + } + qDebug() << "---------"; +} +#endif + + +inline SparseArrayNode *SparseArray::erase(SparseArrayNode *n) +{ + if (n == end()) + return n; + + SparseArrayNode *next = n->nextNode(); + deleteNode(n); + return next; +} + +inline QList<int> SparseArray::keys() const +{ + QList<int> res; + res.reserve(numEntries); + SparseArrayNode *n = mostLeftNode; + while (n != end()) { + res.append(n->key()); + n = n->nextNode(); + } + return res; +} + +inline const SparseArrayNode *SparseArray::lowerBound(uint akey) const +{ + const SparseArrayNode *lb = root()->lowerBound(akey); + if (!lb) + lb = end(); + return lb; +} + + +inline SparseArrayNode *SparseArray::lowerBound(uint akey) +{ + SparseArrayNode *lb = root()->lowerBound(akey); + if (!lb) + lb = end(); + return lb; +} + + +inline const SparseArrayNode *SparseArray::upperBound(uint akey) const +{ + const SparseArrayNode *ub = root()->upperBound(akey); + if (!ub) + ub = end(); + return ub; +} + + +inline SparseArrayNode *SparseArray::upperBound(uint akey) +{ + SparseArrayNode *ub = root()->upperBound(akey); + if (!ub) + ub = end(); + return ub; +} + + +class Array +{ + friend struct ArrayPrototype; + + uint len; + PropertyDescriptor *lengthProperty; + union { + uint freeList; + uint offset; + }; + QVector<PropertyDescriptor> values; + SparseArray *sparse; + + void fillDescriptor(PropertyDescriptor *pd, Value v) + { + pd->type = PropertyDescriptor::Data; + pd->writable = PropertyDescriptor::Enabled; + pd->enumberable = PropertyDescriptor::Enabled; + pd->configurable = PropertyDescriptor::Enabled; + pd->value = v; + } + + uint allocValue() { + uint idx = freeList; + if (values.size() <= (int)freeList) + values.resize(++freeList); + else + freeList = values.at(freeList).value.integerValue(); + return idx; + } + + uint allocValue(Value v) { + uint idx = allocValue(); + PropertyDescriptor *pd = &values[idx]; + fillDescriptor(pd, v); + return idx; + } + void freeValue(int idx) { + PropertyDescriptor &pd = values[idx]; + pd.type = PropertyDescriptor::Generic; + pd.value.tag = Value::_Undefined_Type; + pd.value.int_32 = freeList; + freeList = idx; + } + + PropertyDescriptor *descriptor(uint index) { + PropertyDescriptor *pd = values.data() + index; + if (!sparse) + pd += offset; + return pd; + } + const PropertyDescriptor *descriptor(uint index) const { + const PropertyDescriptor *pd = values.data() + index; + if (!sparse) + pd += offset; + return pd; + } + + void getHeadRoom() { + assert(!sparse && !offset); + offset = qMax(values.size() >> 2, 16); + QVector<PropertyDescriptor> newValues(values.size() + offset); + memcpy(newValues.data() + offset, values.constData(), values.size()*sizeof(PropertyDescriptor)); + values = newValues; + } + +public: + Array() : len(0), lengthProperty(0), offset(0), sparse(0) {} + Array(const Array &other); + ~Array() { delete sparse; } + void initSparse(); + + uint length() const { return len; } + bool setLength(uint newLen); + + void setLengthProperty(PropertyDescriptor *pd) { lengthProperty = pd; } + PropertyDescriptor *getLengthProperty() { return lengthProperty; } + + void setLengthUnchecked(uint l) { + len = l; + if (lengthProperty) + lengthProperty->value = Value::fromUInt32(l); + } + + PropertyDescriptor *insert(uint index) { + PropertyDescriptor *pd; + if (!sparse && (index < 0x1000 || index < len + (len >> 2))) { + if (index + offset >= (uint)values.size()) { + values.resize(offset + index + 1); + for (uint i = len + 1; i < index; ++i) { + values[i].type = PropertyDescriptor::Generic; + values[i].value.tag = Value::_Undefined_Type; + } + } + pd = descriptor(index); + } else { + initSparse(); + SparseArrayNode *n = sparse->insert(index); + if (n->value == UINT_MAX) + n->value = allocValue(); + pd = descriptor(n->value); + } + if (index >= len) + setLengthUnchecked(index + 1); + return pd; + } + + void set(uint index, const PropertyDescriptor *pd) { + *insert(index) = *pd; + } + + void set(uint index, Value value) { + PropertyDescriptor *pd = insert(index); + fillDescriptor(pd, value); + } + + bool deleteIndex(uint index) { + if (index >= len) + return true; + PropertyDescriptor *pd = 0; + if (!sparse) { + pd = at(index); + } else { + SparseArrayNode *n = sparse->findNode(index); + if (n) + pd = descriptor(n->value); + } + if (!pd || pd->type == PropertyDescriptor::Generic) + return true; + if (!pd->isConfigurable()) + return false; + pd->type = PropertyDescriptor::Generic; + pd->value.tag = Value::_Undefined_Type; + if (sparse) { + pd->value.int_32 = freeList; + freeList = pd - values.constData(); + } + return true; + } + + PropertyDescriptor *at(uint index) { + if (!sparse) { + if (index >= values.size() - offset) + return 0; + return values.data() + index + offset; + } else { + SparseArrayNode *n = sparse->findNode(index); + if (!n) + return 0; + return values.data() + n->value; + } + } + + const PropertyDescriptor *nonSparseAt(uint index) const { + if (sparse) + return 0; + index += offset; + if (index >= (uint)values.size()) + return 0; + return values.constData() + index; + } + + PropertyDescriptor *nonSparseAtRef(uint index) { + if (sparse) + return 0; + index += offset; + if (index >= (uint)values.size()) + return 0; + return values.data() + index; + } + + const PropertyDescriptor *at(uint index) const { + if (!sparse) { + if (index >= values.size() - offset) + return 0; + return values.constData() + index + offset; + } else { + SparseArrayNode *n = sparse->findNode(index); + if (!n) + return 0; + return values.constData() + n->value; + } + } + + void markObjects() const; + + void push_front(Value v) { + if (!sparse) { + if (!offset) + getHeadRoom(); + + PropertyDescriptor pd; + fillDescriptor(&pd, v); + --offset; + values[offset] = pd; + } else { + uint idx = allocValue(v); + sparse->push_front(idx); + } + setLengthUnchecked(len + 1); + } + PropertyDescriptor *front() { + PropertyDescriptor *pd = 0; + if (!sparse) { + if (len) + pd = values.data() + offset; + } else { + SparseArrayNode *n = sparse->findNode(0); + if (n) + pd = descriptor(n->value); + } + if (pd && pd->type == PropertyDescriptor::Generic) + return 0; + return pd; + } + void pop_front() { + if (!len) + return; + if (!sparse) { + ++offset; + } else { + uint idx = sparse->pop_front(); + freeValue(idx); + } + setLengthUnchecked(len - 1); + } + void push_back(Value v) { + if (!sparse) { + PropertyDescriptor pd; + fillDescriptor(&pd, v); + values.append(pd); + } else { + uint idx = allocValue(v); + sparse->push_back(idx, len); + } + setLengthUnchecked(len + 1); + } + PropertyDescriptor *back() { + PropertyDescriptor *pd = 0; + if (!sparse) { + if (len) + pd = values.data() + offset + len; + } else { + SparseArrayNode *n = sparse->findNode(len - 1); + if (n) + pd = descriptor(n->value); + } + if (pd && pd->type == PropertyDescriptor::Generic) + return 0; + return pd; + } + void pop_back() { + if (!len) + return; + if (!sparse) { + values.resize(values.size() - 1); + } else { + uint idx = sparse->pop_back(len); + if (idx != UINT_MAX) + freeValue(idx); + } + setLengthUnchecked(len - 1); + } + + SparseArrayNode *sparseLowerBound(uint idx) { return sparse ? sparse->lowerBound(idx) : 0; } + SparseArrayNode *sparseBegin() { return sparse ? sparse->begin() : 0; } + SparseArrayNode *sparseEnd() { return sparse ? sparse->end() : 0; } + + void concat(const Array &other); + void sort(ExecutionContext *context, Object *thisObject, const Value &comparefn, uint len); + Value indexOf(Value v, uint fromIndex, uint len, ExecutionContext *ctx, Object *o); +}; + +} +} + +#endif // QMAP_H diff --git a/src/v4/qv4arrayobject.cpp b/src/v4/qv4arrayobject.cpp new file mode 100644 index 0000000000..cbf34d3077 --- /dev/null +++ b/src/v4/qv4arrayobject.cpp @@ -0,0 +1,798 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4arrayobject.h" +#include "qv4array.h" + +using namespace QQmlJS::VM; + +ArrayCtor::ArrayCtor(ExecutionContext *scope) + : FunctionObject(scope) +{ +} + +Value ArrayCtor::call(ExecutionContext *ctx) +{ + ArrayObject *a = ctx->engine->newArrayObject(ctx); + Array &value = a->array; + if (ctx->argumentCount == 1 && ctx->argument(0).isNumber()) { + bool ok; + uint len = ctx->argument(0).asArrayLength(ctx, &ok); + + if (!ok) { + ctx->throwRangeError(ctx->argument(0)); + return Value::undefinedValue(); + } + + value.setLengthUnchecked(len); + } else { + for (unsigned int i = 0; i < ctx->argumentCount; ++i) { + value.set(i, ctx->argument(i)); + } + } + + return Value::fromObject(a); +} + +void ArrayPrototype::init(ExecutionContext *ctx, const Value &ctor) +{ + ctor.objectValue()->defineReadonlyProperty(ctx->engine->id_prototype, Value::fromObject(this)); + ctor.objectValue()->defineReadonlyProperty(ctx->engine->id_length, Value::fromInt32(1)); + ctor.objectValue()->defineDefaultProperty(ctx, QStringLiteral("isArray"), method_isArray, 1); + defineDefaultProperty(ctx, QStringLiteral("constructor"), ctor); + defineDefaultProperty(ctx, QStringLiteral("toString"), method_toString, 0); + defineDefaultProperty(ctx, QStringLiteral("toLocaleString"), method_toLocaleString, 0); + defineDefaultProperty(ctx, QStringLiteral("concat"), method_concat, 1); + defineDefaultProperty(ctx, QStringLiteral("join"), method_join, 1); + defineDefaultProperty(ctx, QStringLiteral("pop"), method_pop, 0); + defineDefaultProperty(ctx, QStringLiteral("push"), method_push, 1); + defineDefaultProperty(ctx, QStringLiteral("reverse"), method_reverse, 0); + defineDefaultProperty(ctx, QStringLiteral("shift"), method_shift, 0); + defineDefaultProperty(ctx, QStringLiteral("slice"), method_slice, 2); + defineDefaultProperty(ctx, QStringLiteral("sort"), method_sort, 1); + defineDefaultProperty(ctx, QStringLiteral("splice"), method_splice, 2); + defineDefaultProperty(ctx, QStringLiteral("unshift"), method_unshift, 1); + defineDefaultProperty(ctx, QStringLiteral("indexOf"), method_indexOf, 1); + defineDefaultProperty(ctx, QStringLiteral("lastIndexOf"), method_lastIndexOf, 1); + defineDefaultProperty(ctx, QStringLiteral("every"), method_every, 1); + defineDefaultProperty(ctx, QStringLiteral("some"), method_some, 1); + defineDefaultProperty(ctx, QStringLiteral("forEach"), method_forEach, 1); + defineDefaultProperty(ctx, QStringLiteral("map"), method_map, 1); + defineDefaultProperty(ctx, QStringLiteral("filter"), method_filter, 1); + defineDefaultProperty(ctx, QStringLiteral("reduce"), method_reduce, 1); + defineDefaultProperty(ctx, QStringLiteral("reduceRight"), method_reduceRight, 1); +} + +uint ArrayPrototype::getLength(ExecutionContext *ctx, Object *o) +{ + if (o->isArrayObject()) + return o->array.length(); + return o->__get__(ctx, ctx->engine->id_length).toUInt32(ctx); +} + +Value ArrayPrototype::method_isArray(ExecutionContext *ctx) +{ + Value arg = ctx->argument(0); + bool isArray = arg.asArrayObject(); + return Value::fromBoolean(isArray); +} + +Value ArrayPrototype::method_toString(ExecutionContext *ctx) +{ + return method_join(ctx); +} + +Value ArrayPrototype::method_toLocaleString(ExecutionContext *ctx) +{ + return method_toString(ctx); +} + +Value ArrayPrototype::method_concat(ExecutionContext *ctx) +{ + Array result; + + if (ArrayObject *instance = ctx->thisObject.asArrayObject()) + result = instance->array; + else { + QString v = ctx->thisObject.toString(ctx)->toQString(); + result.set(0, Value::fromString(ctx, v)); + } + + for (uint i = 0; i < ctx->argumentCount; ++i) { + quint32 k = result.length(); + Value arg = ctx->argument(i); + + if (ArrayObject *elt = arg.asArrayObject()) + result.concat(elt->array); + + else + result.set(k, arg); + } + + return Value::fromObject(ctx->engine->newArrayObject(ctx, result)); +} + +Value ArrayPrototype::method_join(ExecutionContext *ctx) +{ + Value arg = ctx->argument(0); + + QString r4; + if (arg.isUndefined()) + r4 = QStringLiteral(","); + else + r4 = arg.toString(ctx)->toQString(); + + Value self = ctx->thisObject; + const Value length = self.property(ctx, ctx->engine->id_length); + const quint32 r2 = Value::toUInt32(length.isUndefined() ? 0 : length.toNumber(ctx)); + + static QSet<Object *> visitedArrayElements; + + if (! r2 || visitedArrayElements.contains(self.objectValue())) + return Value::fromString(ctx, QString()); + + // avoid infinite recursion + visitedArrayElements.insert(self.objectValue()); + + QString R; + + // ### FIXME + if (ArrayObject *a = self.asArrayObject()) { + for (uint i = 0; i < a->array.length(); ++i) { + if (i) + R += r4; + + Value e = a->__get__(ctx, i); + if (! (e.isUndefined() || e.isNull())) + R += e.toString(ctx)->toQString(); + } + } else { + // + // crazy! + // + Value r6 = self.property(ctx, ctx->engine->identifier(QStringLiteral("0"))); + if (!(r6.isUndefined() || r6.isNull())) + R = r6.toString(ctx)->toQString(); + + for (quint32 k = 1; k < r2; ++k) { + R += r4; + + String *name = Value::fromDouble(k).toString(ctx); + Value r12 = self.property(ctx, name); + + if (! (r12.isUndefined() || r12.isNull())) + R += r12.toString(ctx)->toQString(); + } + } + + visitedArrayElements.remove(self.objectValue()); + return Value::fromString(ctx, R); +} + +Value ArrayPrototype::method_pop(ExecutionContext *ctx) +{ + Object *instance = __qmljs_to_object(ctx->thisObject, ctx).objectValue(); + uint len = getLength(ctx, instance); + + if (!len) { + if (!instance->isArrayObject()) + instance->__put__(ctx, ctx->engine->id_length, Value::fromInt32(0)); + return Value::undefinedValue(); + } + + Value result = instance->__get__(ctx, len - 1); + + instance->__delete__(ctx, len - 1); + if (instance->isArrayObject()) + instance->array.setLengthUnchecked(len - 1); + else + instance->__put__(ctx, ctx->engine->id_length, Value::fromDouble(len - 1)); + return result; +} + +Value ArrayPrototype::method_push(ExecutionContext *ctx) +{ + Object *instance = __qmljs_to_object(ctx->thisObject, ctx).objectValue(); + uint len = getLength(ctx, instance); + + if (len + ctx->argumentCount < len) { + // ughh... + double l = len; + for (double i = 0; i < ctx->argumentCount; ++i) { + Value idx = Value::fromDouble(l + i); + instance->__put__(ctx, idx.toString(ctx), ctx->argument(i)); + } + double newLen = l + ctx->argumentCount; + if (!instance->isArrayObject()) + instance->__put__(ctx, ctx->engine->id_length, Value::fromDouble(newLen)); + else + ctx->throwRangeError(Value::fromString(ctx, QStringLiteral("Array.prototype.push: Overflow"))); + return Value::fromDouble(newLen); + } + + bool protoHasArray = false; + Object *p = instance; + while ((p = p->prototype)) + if (p->array.length()) + protoHasArray = true; + + if (!protoHasArray && len == instance->array.length()) { + for (uint i = 0; i < ctx->argumentCount; ++i) { + Value v = ctx->argument(i); + instance->array.push_back(v); + } + } else { + for (uint i = 0; i < ctx->argumentCount; ++i) + instance->__put__(ctx, len + i, ctx->argument(i)); + } + uint newLen = len + ctx->argumentCount; + if (!instance->isArrayObject()) + instance->__put__(ctx, ctx->engine->id_length, Value::fromDouble(newLen)); + + if (newLen < INT_MAX) + return Value::fromInt32(newLen); + return Value::fromDouble((double)newLen); + +} + +Value ArrayPrototype::method_reverse(ExecutionContext *ctx) +{ + Object *instance = __qmljs_to_object(ctx->thisObject, ctx).objectValue(); + uint length = getLength(ctx, instance); + + int lo = 0, hi = length - 1; + + for (; lo < hi; ++lo, --hi) { + bool loExists, hiExists; + Value lval = instance->__get__(ctx, lo, &loExists); + Value hval = instance->__get__(ctx, hi, &hiExists); + if (hiExists) + instance->__put__(ctx, lo, hval); + else + instance->__delete__(ctx, lo); + if (loExists) + instance->__put__(ctx, hi, lval); + else + instance->__delete__(ctx, hi); + } + return Value::fromObject(instance); +} + +Value ArrayPrototype::method_shift(ExecutionContext *ctx) +{ + Object *instance = __qmljs_to_object(ctx->thisObject, ctx).objectValue(); + uint len = getLength(ctx, instance); + + if (!len) { + if (!instance->isArrayObject()) + instance->__put__(ctx, ctx->engine->id_length, Value::fromInt32(0)); + return Value::undefinedValue(); + } + + Value result = instance->getValueChecked(ctx, instance->array.front()); + + bool protoHasArray = false; + Object *p = instance; + while ((p = p->prototype)) + if (p->array.length()) + protoHasArray = true; + + if (!protoHasArray && len >= instance->array.length()) { + instance->array.pop_front(); + } else { + // do it the slow way + for (uint k = 1; k < len; ++k) { + bool exists; + Value v = instance->__get__(ctx, k, &exists); + if (exists) + instance->__put__(ctx, k - 1, v); + else + instance->__delete__(ctx, k - 1); + } + instance->__delete__(ctx, len - 1); + } + + if (!instance->isArrayObject()) + instance->__put__(ctx, ctx->engine->id_length, Value::fromDouble(len - 1)); + return result; +} + +Value ArrayPrototype::method_slice(ExecutionContext *ctx) +{ + Object *o = ctx->thisObject.toObject(ctx).objectValue(); + + Array result; + uint len = o->__get__(ctx, ctx->engine->id_length).toUInt32(ctx); + double s = ctx->argument(0).toInteger(ctx); + uint start; + if (s < 0) + start = (uint)qMax(len + s, 0.); + else if (s > len) + start = len; + else + start = (uint) s; + uint end = len; + if (!ctx->argument(1).isUndefined()) { + double e = ctx->argument(1).toInteger(ctx); + if (e < 0) + end = (uint)qMax(len + e, 0.); + else if (e > len) + end = len; + else + end = (uint) e; + } + + uint n = 0; + for (uint i = start; i < end; ++i) { + bool exists; + Value v = o->__get__(ctx, i, &exists); + if (exists) + result.set(n, v); + ++n; + } + return Value::fromObject(ctx->engine->newArrayObject(ctx, result)); +} + +Value ArrayPrototype::method_sort(ExecutionContext *ctx) +{ + Object *instance = __qmljs_to_object(ctx->thisObject, ctx).objectValue(); + + uint len = getLength(ctx, instance); + + Value comparefn = ctx->argument(0); + instance->array.sort(ctx, instance, comparefn, len); + return ctx->thisObject; +} + +Value ArrayPrototype::method_splice(ExecutionContext *ctx) +{ + Object *instance = __qmljs_to_object(ctx->thisObject, ctx).objectValue(); + uint len = getLength(ctx, instance); + + ArrayObject *newArray = ctx->engine->newArrayObject(ctx); + + double rs = ctx->argument(0).toInteger(ctx); + uint start; + if (rs < 0) + start = (uint) qMax(0., len + rs); + else + start = (uint) qMin(rs, (double)len); + + uint deleteCount = (uint)qMin(qMax(ctx->argument(1).toInteger(ctx), 0.), (double)(len - start)); + + newArray->array.values.resize(deleteCount); + PropertyDescriptor *pd = newArray->array.values.data(); + for (uint i = 0; i < deleteCount; ++i) { + pd->type = PropertyDescriptor::Data; + pd->writable = PropertyDescriptor::Enabled; + pd->enumberable = PropertyDescriptor::Enabled; + pd->configurable = PropertyDescriptor::Enabled; + pd->value = instance->__get__(ctx, start + i); + ++pd; + } + newArray->array.setLengthUnchecked(deleteCount); + + uint itemCount = ctx->argumentCount < 2 ? 0 : ctx->argumentCount - 2; + + if (itemCount < deleteCount) { + for (uint k = start; k < len - deleteCount; ++k) { + bool exists; + Value v = instance->__get__(ctx, k + deleteCount, &exists); + if (exists) + instance->array.set(k + itemCount, v); + else + instance->__delete__(ctx, k + itemCount); + } + for (uint k = len; k > len - deleteCount + itemCount; --k) + instance->__delete__(ctx, k - 1); + } else if (itemCount > deleteCount) { + uint k = len - deleteCount; + while (k > start) { + bool exists; + Value v = instance->__get__(ctx, k + deleteCount - 1, &exists); + if (exists) + instance->array.set(k + itemCount - 1, v); + else + instance->__delete__(ctx, k + itemCount - 1); + --k; + } + } + + for (uint i = 0; i < itemCount; ++i) + instance->array.set(start + i, ctx->argument(i + 2)); + + ctx->strictMode = true; + instance->__put__(ctx, ctx->engine->id_length, Value::fromDouble(len - deleteCount + itemCount)); + + return Value::fromObject(newArray); +} + +Value ArrayPrototype::method_unshift(ExecutionContext *ctx) +{ + Object *instance = __qmljs_to_object(ctx->thisObject, ctx).objectValue(); + uint len = getLength(ctx, instance); + + bool protoHasArray = false; + Object *p = instance; + while ((p = p->prototype)) + if (p->array.length()) + protoHasArray = true; + + if (!protoHasArray && len >= instance->array.length()) { + for (int i = ctx->argumentCount - 1; i >= 0; --i) { + Value v = ctx->argument(i); + instance->array.push_front(v); + } + } else { + for (uint k = len; k > 0; --k) { + bool exists; + Value v = instance->__get__(ctx, k - 1, &exists); + if (exists) + instance->__put__(ctx, k + ctx->argumentCount - 1, v); + else + instance->__delete__(ctx, k + ctx->argumentCount - 1); + } + for (uint i = 0; i < ctx->argumentCount; ++i) + instance->__put__(ctx, i, ctx->argument(i)); + } + uint newLen = len + ctx->argumentCount; + if (!instance->isArrayObject()) + instance->__put__(ctx, ctx->engine->id_length, Value::fromDouble(newLen)); + + if (newLen < INT_MAX) + return Value::fromInt32(newLen); + return Value::fromDouble((double)newLen); +} + +Value ArrayPrototype::method_indexOf(ExecutionContext *ctx) +{ + Object *instance = __qmljs_to_object(ctx->thisObject, ctx).objectValue(); + uint len = getLength(ctx, instance); + if (!len) + return Value::fromInt32(-1); + + Value searchValue; + uint fromIndex = 0; + + if (ctx->argumentCount >= 1) + searchValue = ctx->argument(0); + else + searchValue = Value::undefinedValue(); + + if (ctx->argumentCount >= 2) { + double f = ctx->argument(1).toInteger(ctx); + if (f >= len) + return Value::fromInt32(-1); + if (f < 0) + f = qMax(len + f, 0.); + fromIndex = (uint) f; + } + + if (instance->isStringObject()) { + for (uint k = fromIndex; k < len; ++k) { + bool exists; + Value v = instance->__get__(ctx, k, &exists); + if (exists && __qmljs_strict_equal(v, searchValue)) + return Value::fromDouble(k); + } + return Value::fromInt32(-1); + } + + return instance->array.indexOf(searchValue, fromIndex, len, ctx, instance); +} + +Value ArrayPrototype::method_lastIndexOf(ExecutionContext *ctx) +{ + Object *instance = __qmljs_to_object(ctx->thisObject, ctx).objectValue(); + uint len = getLength(ctx, instance); + if (!len) + return Value::fromInt32(-1); + + Value searchValue; + uint fromIndex = len; + + if (ctx->argumentCount >= 1) + searchValue = ctx->argument(0); + else + searchValue = Value::undefinedValue(); + + if (ctx->argumentCount >= 2) { + double f = ctx->argument(1).toInteger(ctx); + if (f > 0) + f = qMin(f, (double)(len - 1)); + else if (f < 0) { + f = len + f; + if (f < 0) + return Value::fromInt32(-1); + } + fromIndex = (uint) f + 1; + } + + for (uint k = fromIndex; k > 0;) { + --k; + bool exists; + Value v = instance->__get__(ctx, k, &exists); + if (exists && __qmljs_strict_equal(v, searchValue)) + return Value::fromDouble(k); + } + return Value::fromInt32(-1); +} + +Value ArrayPrototype::method_every(ExecutionContext *ctx) +{ + Object *instance = __qmljs_to_object(ctx->thisObject, ctx).objectValue(); + + uint len = getLength(ctx, instance); + + FunctionObject *callback = ctx->argument(0).asFunctionObject(); + if (!callback) + __qmljs_throw_type_error(ctx); + + Value thisArg = ctx->argument(1); + + bool ok = true; + for (uint k = 0; ok && k < len; ++k) { + bool exists; + Value v = instance->__get__(ctx, k, &exists); + if (!exists) + continue; + + Value args[3]; + args[0] = v; + args[1] = Value::fromDouble(k); + args[2] = ctx->thisObject; + Value r = callback->call(ctx, thisArg, args, 3); + ok = __qmljs_to_boolean(r, ctx); + } + return Value::fromBoolean(ok); +} + +Value ArrayPrototype::method_some(ExecutionContext *ctx) +{ + Object *instance = __qmljs_to_object(ctx->thisObject, ctx).objectValue(); + + uint len = getLength(ctx, instance); + + FunctionObject *callback = ctx->argument(0).asFunctionObject(); + if (!callback) + __qmljs_throw_type_error(ctx); + + Value thisArg = ctx->argument(1); + + for (uint k = 0; k < len; ++k) { + bool exists; + Value v = instance->__get__(ctx, k, &exists); + if (!exists) + continue; + + Value args[3]; + args[0] = v; + args[1] = Value::fromDouble(k); + args[2] = ctx->thisObject; + Value r = callback->call(ctx, thisArg, args, 3); + if (__qmljs_to_boolean(r, ctx)) + return Value::fromBoolean(true); + } + return Value::fromBoolean(false); +} + +Value ArrayPrototype::method_forEach(ExecutionContext *ctx) +{ + Object *instance = __qmljs_to_object(ctx->thisObject, ctx).objectValue(); + + uint len = getLength(ctx, instance); + + FunctionObject *callback = ctx->argument(0).asFunctionObject(); + if (!callback) + __qmljs_throw_type_error(ctx); + + Value thisArg = ctx->argument(1); + + for (uint k = 0; k < len; ++k) { + bool exists; + Value v = instance->__get__(ctx, k, &exists); + if (!exists) + continue; + + Value args[3]; + args[0] = v; + args[1] = Value::fromDouble(k); + args[2] = ctx->thisObject; + callback->call(ctx, thisArg, args, 3); + } + return Value::undefinedValue(); +} + +Value ArrayPrototype::method_map(ExecutionContext *ctx) +{ + Object *instance = __qmljs_to_object(ctx->thisObject, ctx).objectValue(); + + uint len = getLength(ctx, instance); + + FunctionObject *callback = ctx->argument(0).asFunctionObject(); + if (!callback) + __qmljs_throw_type_error(ctx); + + Value thisArg = ctx->argument(1); + + ArrayObject *a = ctx->engine->newArrayObject(ctx); + a->array.setLength(len); + + for (uint k = 0; k < len; ++k) { + bool exists; + Value v = instance->__get__(ctx, k, &exists); + if (!exists) + continue; + + Value args[3]; + args[0] = v; + args[1] = Value::fromDouble(k); + args[2] = ctx->thisObject; + Value mapped = callback->call(ctx, thisArg, args, 3); + a->array.set(k, mapped); + } + return Value::fromObject(a); +} + +Value ArrayPrototype::method_filter(ExecutionContext *ctx) +{ + Object *instance = __qmljs_to_object(ctx->thisObject, ctx).objectValue(); + + uint len = getLength(ctx, instance); + + FunctionObject *callback = ctx->argument(0).asFunctionObject(); + if (!callback) + __qmljs_throw_type_error(ctx); + + Value thisArg = ctx->argument(1); + + ArrayObject *a = ctx->engine->newArrayObject(ctx); + + uint to = 0; + for (uint k = 0; k < len; ++k) { + bool exists; + Value v = instance->__get__(ctx, k, &exists); + if (!exists) + continue; + + Value args[3]; + args[0] = v; + args[1] = Value::fromDouble(k); + args[2] = ctx->thisObject; + Value selected = callback->call(ctx, thisArg, args, 3); + if (__qmljs_to_boolean(selected, ctx)) { + a->array.set(to, v); + ++to; + } + } + return Value::fromObject(a); +} + +Value ArrayPrototype::method_reduce(ExecutionContext *ctx) +{ + Object *instance = __qmljs_to_object(ctx->thisObject, ctx).objectValue(); + + uint len = getLength(ctx, instance); + + FunctionObject *callback = ctx->argument(0).asFunctionObject(); + if (!callback) + __qmljs_throw_type_error(ctx); + + uint k = 0; + Value acc; + if (ctx->argumentCount > 1) { + acc = ctx->argument(1); + } else { + bool kPresent = false; + while (k < len && !kPresent) { + Value v = instance->__get__(ctx, k, &kPresent); + if (kPresent) + acc = v; + ++k; + } + if (!kPresent) + __qmljs_throw_type_error(ctx); + } + + while (k < len) { + bool kPresent; + Value v = instance->__get__(ctx, k, &kPresent); + if (kPresent) { + Value args[4]; + args[0] = acc; + args[1] = v; + args[2] = Value::fromDouble(k); + args[3] = ctx->thisObject; + acc = callback->call(ctx, Value::undefinedValue(), args, 4); + } + ++k; + } + return acc; +} + +Value ArrayPrototype::method_reduceRight(ExecutionContext *ctx) +{ + Object *instance = __qmljs_to_object(ctx->thisObject, ctx).objectValue(); + + uint len = getLength(ctx, instance); + + FunctionObject *callback = ctx->argument(0).asFunctionObject(); + if (!callback) + __qmljs_throw_type_error(ctx); + + if (len == 0) { + if (ctx->argumentCount == 1) + __qmljs_throw_type_error(ctx); + return ctx->argument(1); + } + + uint k = len; + Value acc; + if (ctx->argumentCount > 1) { + acc = ctx->argument(1); + } else { + bool kPresent = false; + while (k > 0 && !kPresent) { + Value v = instance->__get__(ctx, k - 1, &kPresent); + if (kPresent) + acc = v; + --k; + } + if (!kPresent) + __qmljs_throw_type_error(ctx); + } + + while (k > 0) { + bool kPresent; + Value v = instance->__get__(ctx, k - 1, &kPresent); + if (kPresent) { + Value args[4]; + args[0] = acc; + args[1] = v; + args[2] = Value::fromDouble(k - 1); + args[3] = ctx->thisObject; + acc = callback->call(ctx, Value::undefinedValue(), args, 4); + } + --k; + } + return acc; +} + diff --git a/src/v4/qv4arrayobject.h b/src/v4/qv4arrayobject.h new file mode 100644 index 0000000000..8a15546c1c --- /dev/null +++ b/src/v4/qv4arrayobject.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4ARRAYOBJECT_H +#define QV4ARRAYOBJECT_H + +#include "qv4object.h" +#include "qv4functionobject.h" +#include <QtCore/qnumeric.h> + +namespace QQmlJS { +namespace VM { + + +struct ArrayCtor: FunctionObject +{ + ArrayCtor(ExecutionContext *scope); + + virtual Value call(ExecutionContext *ctx); +}; + +struct ArrayPrototype: ArrayObject +{ + ArrayPrototype(ExecutionContext *context) : ArrayObject(context) {} + + void init(ExecutionContext *ctx, const Value &ctor); + + static uint getLength(ExecutionContext *ctx, Object *o); + + static Value method_isArray(ExecutionContext *ctx); + static Value method_toString(ExecutionContext *ctx); + static Value method_toLocaleString(ExecutionContext *ctx); + static Value method_concat(ExecutionContext *ctx); + static Value method_join(ExecutionContext *ctx); + static Value method_pop(ExecutionContext *ctx); + static Value method_push(ExecutionContext *ctx); + static Value method_reverse(ExecutionContext *ctx); + static Value method_shift(ExecutionContext *ctx); + static Value method_slice(ExecutionContext *ctx); + static Value method_sort(ExecutionContext *ctx); + static Value method_splice(ExecutionContext *ctx); + static Value method_unshift(ExecutionContext *ctx); + static Value method_indexOf(ExecutionContext *ctx); + static Value method_lastIndexOf(ExecutionContext *ctx); + static Value method_every(ExecutionContext *ctx); + static Value method_some(ExecutionContext *ctx); + static Value method_forEach(ExecutionContext *ctx); + static Value method_map(ExecutionContext *ctx); + static Value method_filter(ExecutionContext *ctx); + static Value method_reduce(ExecutionContext *ctx); + static Value method_reduceRight(ExecutionContext *ctx); +}; + + +} // end of namespace VM +} // end of namespace QQmlJS + +#endif // QV4ECMAOBJECTS_P_H diff --git a/src/v4/qv4booleanobject.cpp b/src/v4/qv4booleanobject.cpp new file mode 100644 index 0000000000..9df0f23a6a --- /dev/null +++ b/src/v4/qv4booleanobject.cpp @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4booleanobject.h" + +using namespace QQmlJS::VM; + +BooleanCtor::BooleanCtor(ExecutionContext *scope) + : FunctionObject(scope) +{ +} + +Value BooleanCtor::construct(ExecutionContext *ctx) +{ + const double n = ctx->argument(0).toBoolean(ctx); + return Value::fromObject(ctx->engine->newBooleanObject(Value::fromBoolean(n))); +} + +Value BooleanCtor::call(ExecutionContext *ctx) +{ + bool value = ctx->argumentCount ? ctx->argument(0).toBoolean(ctx) : 0; + return Value::fromBoolean(value); +} + +void BooleanPrototype::init(ExecutionContext *ctx, const Value &ctor) +{ + ctor.objectValue()->defineReadonlyProperty(ctx->engine->id_prototype, Value::fromObject(this)); + ctor.objectValue()->defineReadonlyProperty(ctx->engine->id_length, Value::fromInt32(1)); + defineDefaultProperty(ctx, QStringLiteral("constructor"), ctor); + defineDefaultProperty(ctx, QStringLiteral("toString"), method_toString); + defineDefaultProperty(ctx, QStringLiteral("valueOf"), method_valueOf); +} + +Value BooleanPrototype::method_toString(ExecutionContext *ctx) +{ + bool result; + if (ctx->thisObject.isBoolean()) { + result = ctx->thisObject.booleanValue(); + } else { + BooleanObject *thisObject = ctx->thisObject.asBooleanObject(); + if (!thisObject) + ctx->throwTypeError(); + result = thisObject->value.booleanValue(); + } + + return Value::fromString(ctx, QLatin1String(result ? "true" : "false")); +} + +Value BooleanPrototype::method_valueOf(ExecutionContext *ctx) +{ + BooleanObject *thisObject = ctx->thisObject.asBooleanObject(); + if (!thisObject) + ctx->throwTypeError(); + + return thisObject->value; +} diff --git a/src/v4/qv4booleanobject.h b/src/v4/qv4booleanobject.h new file mode 100644 index 0000000000..44d87b1d50 --- /dev/null +++ b/src/v4/qv4booleanobject.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4BOOLEANOBJECT_H +#define QBOOLEANOBJECT_H + +#include "qv4object.h" +#include "qv4functionobject.h" +#include <QtCore/qnumeric.h> + +namespace QQmlJS { +namespace VM { + +struct BooleanCtor: FunctionObject +{ + BooleanCtor(ExecutionContext *scope); + + virtual Value construct(ExecutionContext *ctx); + virtual Value call(ExecutionContext *ctx); +}; + +struct BooleanPrototype: BooleanObject +{ + BooleanPrototype(): BooleanObject(Value::fromBoolean(false)) {} + void init(ExecutionContext *ctx, const Value &ctor); + + static Value method_toString(ExecutionContext *ctx); + static Value method_valueOf(ExecutionContext *ctx); +}; + + +} // end of namespace VM +} // end of namespace QQmlJS + +#endif // QV4ECMAOBJECTS_P_H diff --git a/src/v4/qv4codegen.cpp b/src/v4/qv4codegen.cpp new file mode 100644 index 0000000000..4c4f7dd1d6 --- /dev/null +++ b/src/v4/qv4codegen.cpp @@ -0,0 +1,2614 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4codegen_p.h" +#include "debugging.h" + +#include <QtCore/QCoreApplication> +#include <QtCore/QStringList> +#include <QtCore/QSet> +#include <QtCore/QBuffer> +#include <QtCore/QBitArray> +#include <QtCore/QStack> +#include <private/qqmljsast_p.h> +#include <qmljs_runtime.h> +#include <qmljs_environment.h> +#include <cmath> +#include <iostream> +#include <cassert> + +using namespace QQmlJS; +using namespace AST; + +namespace { +QTextStream qout(stdout, QIODevice::WriteOnly); + +void dfs(IR::BasicBlock *block, + QSet<IR::BasicBlock *> *V, + QVector<IR::BasicBlock *> *blocks) +{ + if (! V->contains(block)) { + V->insert(block); + + foreach (IR::BasicBlock *succ, block->out) + dfs(succ, V, blocks); + + blocks->append(block); + } +} + +struct ComputeUseDef: IR::StmtVisitor, IR::ExprVisitor +{ + IR::Function *_function; + IR::Stmt *_stmt; + + ComputeUseDef(IR::Function *function) + : _function(function) + , _stmt(0) {} + + void operator()(IR::Stmt *s) { + assert(! s->d); + s->d = new IR::Stmt::Data; + qSwap(_stmt, s); + _stmt->accept(this); + qSwap(_stmt, s); + } + + virtual void visitConst(IR::Const *) {} + virtual void visitString(IR::String *) {} + virtual void visitRegExp(IR::RegExp *) {} + virtual void visitName(IR::Name *) {} + virtual void visitClosure(IR::Closure *) {} + virtual void visitUnop(IR::Unop *e) { e->expr->accept(this); } + virtual void visitBinop(IR::Binop *e) { e->left->accept(this); e->right->accept(this); } + virtual void visitSubscript(IR::Subscript *e) { e->base->accept(this); e->index->accept(this); } + virtual void visitMember(IR::Member *e) { e->base->accept(this); } + virtual void visitExp(IR::Exp *s) { s->expr->accept(this); } + virtual void visitEnter(IR::Enter *) {} + virtual void visitLeave(IR::Leave *) {} + virtual void visitJump(IR::Jump *) {} + virtual void visitCJump(IR::CJump *s) { s->cond->accept(this); } + virtual void visitRet(IR::Ret *s) { s->expr->accept(this); } + + virtual void visitTemp(IR::Temp *e) { + if (e->index < 0) + return; + + if (! _stmt->d->uses.contains(e->index)) + _stmt->d->uses.append(e->index); + } + + virtual void visitCall(IR::Call *e) { + e->base->accept(this); + for (IR::ExprList *it = e->args; it; it = it->next) + it->expr->accept(this); + } + + virtual void visitNew(IR::New *e) { + e->base->accept(this); + for (IR::ExprList *it = e->args; it; it = it->next) + it->expr->accept(this); + } + + virtual void visitMove(IR::Move *s) { + if (IR::Temp *t = s->target->asTemp()) { + if (t->index >= 0) { + if (! _stmt->d->defs.contains(t->index)) + _stmt->d->defs.append(t->index); + } + } else { + s->target->accept(this); + } + s->source->accept(this); + } +}; + +void liveness(IR::Function *function) +{ + QSet<IR::BasicBlock *> V; + QVector<IR::BasicBlock *> blocks; + + ComputeUseDef computeUseDef(function); + foreach (IR::BasicBlock *block, function->basicBlocks) { + foreach (IR::Stmt *s, block->statements) + computeUseDef(s); + } + + dfs(function->basicBlocks.at(0), &V, &blocks); + + bool changed; + do { + changed = false; + + foreach (IR::BasicBlock *block, blocks) { + const QBitArray previousLiveIn = block->liveIn; + const QBitArray previousLiveOut = block->liveOut; + QBitArray live(function->tempCount); + foreach (IR::BasicBlock *succ, block->out) + live |= succ->liveIn; + block->liveOut = live; + for (int i = block->statements.size() - 1; i != -1; --i) { + IR::Stmt *s = block->statements.at(i); + s->d->liveOut = live; + foreach (unsigned d, s->d->defs) + live.clearBit(d); + foreach (unsigned u, s->d->uses) + live.setBit(u); + s->d->liveIn = live; + } + block->liveIn = live; + if (! changed) { + if (previousLiveIn != block->liveIn || previousLiveOut != block->liveOut) + changed = true; + } + } + } while (changed); +} + +} // end of anonymous namespace + +class Codegen::ScanFunctions: Visitor +{ +public: + ScanFunctions(Codegen *cg) + : _cg(cg) + , _env(0) + { + } + + void operator()(Node *node) + { + if (node) + node->accept(this); + } + + inline void enterEnvironment(Node *node) + { + Environment *e = _cg->newEnvironment(node, _env); + if (!e->isStrict) + e->isStrict = _cg->_strictMode; + _envStack.append(e); + _env = e; + } + + inline void leaveEnvironment() + { + _envStack.pop(); + _env = _envStack.isEmpty() ? 0 : _envStack.top(); + } + +protected: + using Visitor::visit; + using Visitor::endVisit; + + void checkDirectivePrologue(SourceElements *ast) + { + for (SourceElements *it = ast; it; it = it->next) { + if (StatementSourceElement *stmt = cast<StatementSourceElement *>(it->element)) { + if (ExpressionStatement *expr = cast<ExpressionStatement *>(stmt->statement)) { + if (StringLiteral *strLit = cast<StringLiteral *>(expr->expression)) { + if (strLit->value == QLatin1String("use strict")) { + _env->isStrict = true; + } else { + // TODO: give a warning. + } + continue; + } + } + } + + break; + } + } + + void checkName(const QStringRef &name, const SourceLocation &loc) + { + if (_env->isStrict) { + if (name == QLatin1String("implements") + || name == QLatin1String("interface") + || name == QLatin1String("let") + || name == QLatin1String("package") + || name == QLatin1String("private") + || name == QLatin1String("protected") + || name == QLatin1String("public") + || name == QLatin1String("static") + || name == QLatin1String("yield")) { + _cg->throwSyntaxError(loc, QCoreApplication::translate("qv4codegen", "Unexpected strict mode reserved word")); + } + } + } + void checkForArguments(AST::FormalParameterList *parameters) + { + while (parameters) { + if (parameters->name == QStringLiteral("arguments")) + _env->usesArgumentsObject = Environment::ArgumentsObjectNotUsed; + parameters = parameters->next; + } + } + + virtual bool visit(Program *ast) + { + enterEnvironment(ast); + checkDirectivePrologue(ast->elements); + return true; + } + + virtual void endVisit(Program *) + { + leaveEnvironment(); + } + + virtual bool visit(CallExpression *ast) + { + if (! _env->hasDirectEval) { + if (IdentifierExpression *id = cast<IdentifierExpression *>(ast->base)) { + if (id->name == QStringLiteral("eval")) { + if (_env->usesArgumentsObject == Environment::ArgumentsObjectUnknown) + _env->usesArgumentsObject = Environment::ArgumentsObjectUsed; + _env->hasDirectEval = true; + } + } + } + int argc = 0; + for (ArgumentList *it = ast->arguments; it; it = it->next) + ++argc; + _env->maxNumberOfArguments = qMax(_env->maxNumberOfArguments, argc); + return true; + } + + virtual bool visit(NewMemberExpression *ast) + { + int argc = 0; + for (ArgumentList *it = ast->arguments; it; it = it->next) + ++argc; + _env->maxNumberOfArguments = qMax(_env->maxNumberOfArguments, argc); + return true; + } + + virtual bool visit(VariableDeclaration *ast) + { + if (_env->isStrict && (ast->name == QLatin1String("eval") || ast->name == QLatin1String("arguments"))) + _cg->throwSyntaxError(ast->identifierToken, QCoreApplication::translate("qv4codegen", "Variable name may not be eval or arguments in strict mode")); + checkName(ast->name, ast->identifierToken); + if (ast->name == QLatin1String("arguments")) + _env->usesArgumentsObject = Environment::ArgumentsObjectNotUsed; + _env->enter(ast->name.toString(), ast->expression ? Environment::VariableDefinition : Environment::VariableDeclaration); + return true; + } + + virtual bool visit(IdentifierExpression *ast) + { + checkName(ast->name, ast->identifierToken); + if (_env->usesArgumentsObject == Environment::ArgumentsObjectUnknown && ast->name == QLatin1String("arguments")) + _env->usesArgumentsObject = Environment::ArgumentsObjectUsed; + return true; + } + + virtual bool visit(FunctionExpression *ast) + { + if (_env->isStrict && (ast->name == QLatin1String("eval") || ast->name == QLatin1String("arguments"))) + _cg->throwSyntaxError(ast->identifierToken, QCoreApplication::translate("qv4codegen", "Function name may not be eval or arguments in strict mode")); + enterFunction(ast, ast->name.toString(), ast->formals, ast->body); + return true; + } + + virtual void endVisit(FunctionExpression *) + { + leaveEnvironment(); + } + + virtual bool visit(PropertyGetterSetter *ast) + { + enterFunction(ast, QString(), ast->formals, ast->functionBody); + return true; + } + + virtual void endVisit(PropertyGetterSetter *) + { + leaveEnvironment(); + } + + virtual bool visit(FunctionDeclaration *ast) + { + if (_env->isStrict && (ast->name == QLatin1String("eval") || ast->name == QLatin1String("arguments"))) + _cg->throwSyntaxError(ast->identifierToken, QCoreApplication::translate("qv4codegen", "Function name may not be eval or arguments in strict mode")); + enterFunction(ast, ast->name.toString(), ast->formals, ast->body, ast); + return true; + } + + virtual void endVisit(FunctionDeclaration *) + { + leaveEnvironment(); + } + + virtual bool visit(WithStatement *ast) + { + if (_env->isStrict) { + _cg->throwSyntaxError(ast->withToken, QCoreApplication::translate("qv4codegen", "'with' statement is not allowed in strict mode")); + return false; + } + + return true; + } + +private: + void enterFunction(Node *ast, const QString &name, FormalParameterList *formals, FunctionBody *body, FunctionDeclaration *decl = 0) + { + bool wasStrict = false; + if (_env) { + _env->hasNestedFunctions = true; + _env->enter(name, Environment::FunctionDefinition, decl); + if (name == QLatin1String("arguments")) + _env->usesArgumentsObject = Environment::ArgumentsObjectNotUsed; + wasStrict = _env->isStrict; + } + + enterEnvironment(ast); + checkForArguments(formals); + + if (body) + checkDirectivePrologue(body->elements); + + if (wasStrict || _env->isStrict) { + QStringList args; + for (FormalParameterList *it = formals; it; it = it->next) { + QString arg = it->name.toString(); + if (args.contains(arg)) + _cg->throwSyntaxError(it->identifierToken, QCoreApplication::translate("qv4codegen", "Duplicate parameter name '%1' in strict mode").arg(arg)); + if (arg == QLatin1String("eval") || arg == QLatin1String("arguments")) + _cg->throwSyntaxError(it->identifierToken, QCoreApplication::translate("qv4codegen", "'%1' cannot be used as parameter name in strict mode").arg(arg)); + args += arg; + } + } + } + + + Codegen *_cg; + Environment *_env; + QStack<Environment *> _envStack; +}; + +Codegen::Codegen(VM::ExecutionContext *context, bool strict) + : _module(0) + , _function(0) + , _block(0) + , _exitBlock(0) + , _throwBlock(0) + , _returnAddress(0) + , _mode(GlobalCode) + , _env(0) + , _loop(0) + , _labelledStatement(0) + , _scopeAndFinally(0) + , _context(context) + , _strictMode(strict) + , _debugger(context->engine->debugger) + , _errorHandler(0) +{ +} + +Codegen::Codegen(ErrorHandler *errorHandler, bool strictMode) + : _module(0) + , _function(0) + , _block(0) + , _exitBlock(0) + , _throwBlock(0) + , _returnAddress(0) + , _mode(GlobalCode) + , _env(0) + , _loop(0) + , _labelledStatement(0) + , _scopeAndFinally(0) + , _context(0) + , _strictMode(strictMode) + , _debugger(0) + , _errorHandler(errorHandler) +{ +} + +IR::Function *Codegen::operator()(const QString &fileName, Program *node, + IR::Module *module, Mode mode, + const QStringList &inheritedLocals) +{ + assert(node); + + _fileName = fileName; + _module = module; + _env = 0; + + ScanFunctions scan(this); + scan(node); + + IR::Function *globalCode = defineFunction(QStringLiteral("%entry"), node, 0, + node->elements, mode, inheritedLocals); + if (_debugger) { + if (node->elements->element) { + SourceLocation loc = node->elements->element->firstSourceLocation(); + _debugger->setSourceLocation(globalCode, loc.startLine, loc.startColumn); + } + } + + foreach (IR::Function *function, _module->functions) { + linearize(function); + } + + qDeleteAll(_envMap); + _envMap.clear(); + + return globalCode; +} + +IR::Function *Codegen::operator()(const QString &fileName, AST::FunctionExpression *ast, IR::Module *module) +{ + _fileName = fileName; + _module = module; + _env = 0; + + ScanFunctions scan(this); + // fake a global environment + scan.enterEnvironment(0); + scan(ast); + scan.leaveEnvironment(); + + IR::Function *function = defineFunction(ast->name.toString(), ast, ast->formals, ast->body ? ast->body->elements : 0); + if (_debugger) + _debugger->setSourceLocation(function, ast->functionToken.startLine, ast->functionToken.startColumn); + + foreach (IR::Function *function, _module->functions) { + linearize(function); + } + + qDeleteAll(_envMap); + _envMap.clear(); + + return function; +} + + +void Codegen::enterEnvironment(Node *node) +{ + _env = _envMap.value(node); + assert(_env); +} + +void Codegen::leaveEnvironment() +{ + assert(_env); + _env = _env->parent; +} + +void Codegen::enterLoop(Statement *node, IR::BasicBlock *breakBlock, IR::BasicBlock *continueBlock) +{ + _loop = new Loop(node, breakBlock, continueBlock, _loop); + _loop->labelledStatement = _labelledStatement; // consume the enclosing labelled statement + _loop->scopeAndFinally = _scopeAndFinally; + _labelledStatement = 0; +} + +void Codegen::leaveLoop() +{ + Loop *current = _loop; + _loop = _loop->parent; + delete current; +} + +IR::Expr *Codegen::member(IR::Expr *base, const QString *name) +{ + if (base->asTemp() /*|| base->asName()*/) + return _block->MEMBER(base->asTemp(), name); + else { + const unsigned t = _block->newTemp(); + move(_block->TEMP(t), base); + return _block->MEMBER(_block->TEMP(t), name); + } +} + +IR::Expr *Codegen::subscript(IR::Expr *base, IR::Expr *index) +{ + if (! base->asTemp()) { + const unsigned t = _block->newTemp(); + move(_block->TEMP(t), base); + base = _block->TEMP(t); + } + + if (! index->asTemp()) { + const unsigned t = _block->newTemp(); + move(_block->TEMP(t), index); + index = _block->TEMP(t); + } + + assert(base->asTemp() && index->asTemp()); + return _block->SUBSCRIPT(base->asTemp(), index->asTemp()); +} + +IR::Expr *Codegen::argument(IR::Expr *expr) +{ + if (expr && ! expr->asTemp()) { + const unsigned t = _block->newTemp(); + move(_block->TEMP(t), expr); + expr = _block->TEMP(t); + } + return expr; +} + +// keeps references alive, converts other expressions to temps +IR::Expr *Codegen::reference(IR::Expr *expr) +{ + if (expr && !expr->asTemp() && !expr->asName() && !expr->asMember() && !expr->asSubscript()) { + const unsigned t = _block->newTemp(); + move(_block->TEMP(t), expr); + expr = _block->TEMP(t); + } + return expr; +} + +IR::Expr *Codegen::unop(IR::AluOp op, IR::Expr *expr) +{ + if (IR::Const *c = expr->asConst()) { + if (c->type == IR::NumberType) { + switch (op) { + case IR::OpNot: + return _block->CONST(IR::BoolType, !c->value); + case IR::OpUMinus: + return _block->CONST(IR::NumberType, -c->value); + case IR::OpUPlus: + return expr; + case IR::OpCompl: + return _block->CONST(IR::NumberType, ~VM::Value::toInt32(c->value)); + case IR::OpIncrement: + return _block->CONST(IR::NumberType, c->value + 1); + case IR::OpDecrement: + return _block->CONST(IR::NumberType, c->value - 1); + default: + break; + } + } + } + if (! expr->asTemp()) { + const unsigned t = _block->newTemp(); + move(_block->TEMP(t), expr); + expr = _block->TEMP(t); + } + assert(expr->asTemp()); + return _block->UNOP(op, expr->asTemp()); +} + +IR::Expr *Codegen::binop(IR::AluOp op, IR::Expr *left, IR::Expr *right) +{ + if (IR::Const *c1 = left->asConst()) { + if (IR::Const *c2 = right->asConst()) { + if (c1->type == IR::NumberType && c2->type == IR::NumberType) { + switch (op) { + case IR::OpAdd: return _block->CONST(IR::NumberType, c1->value + c2->value); + case IR::OpAnd: return _block->CONST(IR::BoolType, c1->value ? c2->value : 0); + case IR::OpBitAnd: return _block->CONST(IR::NumberType, int(c1->value) & int(c2->value)); + case IR::OpBitOr: return _block->CONST(IR::NumberType, int(c1->value) | int(c2->value)); + case IR::OpBitXor: return _block->CONST(IR::NumberType, int(c1->value) ^ int(c2->value)); + case IR::OpDiv: return _block->CONST(IR::NumberType, c1->value / c2->value); + case IR::OpEqual: return _block->CONST(IR::BoolType, c1->value == c2->value); + case IR::OpNotEqual: return _block->CONST(IR::BoolType, c1->value != c2->value); + case IR::OpStrictEqual: return _block->CONST(IR::BoolType, c1->value == c2->value); + case IR::OpStrictNotEqual: return _block->CONST(IR::BoolType, c1->value != c2->value); + case IR::OpGe: return _block->CONST(IR::BoolType, c1->value >= c2->value); + case IR::OpGt: return _block->CONST(IR::BoolType, c1->value > c2->value); + case IR::OpLe: return _block->CONST(IR::BoolType, c1->value <= c2->value); + case IR::OpLt: return _block->CONST(IR::BoolType, c1->value < c2->value); + case IR::OpLShift: return _block->CONST(IR::NumberType, VM::Value::toInt32(c1->value) << (VM::Value::toUInt32(c2->value) & 0x1f)); + case IR::OpMod: return _block->CONST(IR::NumberType, ::fmod(c1->value, c2->value)); + case IR::OpMul: return _block->CONST(IR::NumberType, c1->value * c2->value); + case IR::OpOr: return _block->CONST(IR::NumberType, c1->value ? c1->value : c2->value); + case IR::OpRShift: return _block->CONST(IR::NumberType, VM::Value::toInt32(c1->value) >> (VM::Value::toUInt32(c2->value) & 0x1f)); + case IR::OpSub: return _block->CONST(IR::NumberType, c1->value - c2->value); + case IR::OpURShift: return _block->CONST(IR::NumberType,VM::Value::toUInt32(c1->value) >> (VM::Value::toUInt32(c2->value) & 0x1f)); + + case IR::OpInstanceof: + case IR::OpIn: + assert(!"unreachabe"); + break; + + case IR::OpIfTrue: // unary ops + case IR::OpNot: + case IR::OpUMinus: + case IR::OpUPlus: + case IR::OpCompl: + case IR::OpIncrement: + case IR::OpDecrement: + case IR::OpInvalid: + break; + } + } + } + } else if (op == IR::OpAdd) { + if (IR::String *s1 = left->asString()) { + if (IR::String *s2 = right->asString()) { + return _block->STRING(_function->newString(*s1->value + *s2->value)); + } + } + } + + if (!left->asTemp() && !left->asConst()) { + const unsigned t = _block->newTemp(); + move(_block->TEMP(t), left); + left = _block->TEMP(t); + } + + if (!right->asTemp() && !right->asConst()) { + const unsigned t = _block->newTemp(); + move(_block->TEMP(t), right); + right = _block->TEMP(t); + } + + assert(left->asTemp() || left->asConst()); + assert(right->asTemp() || right->asConst()); + + return _block->BINOP(op, left, right); +} + +IR::Expr *Codegen::call(IR::Expr *base, IR::ExprList *args) +{ + base = reference(base); + return _block->CALL(base, args); +} + +void Codegen::move(IR::Expr *target, IR::Expr *source, IR::AluOp op) +{ + assert(target->isLValue()); + + if (!source->asTemp() && !source->asConst() && (op != IR::OpInvalid || ! target->asTemp())) { + unsigned t = _block->newTemp(); + _block->MOVE(_block->TEMP(t), source); + source = _block->TEMP(t); + } + + _block->MOVE(target, source, op); +} + +void Codegen::cjump(IR::Expr *cond, IR::BasicBlock *iftrue, IR::BasicBlock *iffalse) +{ + if (! (cond->asTemp() || cond->asBinop())) { + const unsigned t = _block->newTemp(); + move(_block->TEMP(t), cond); + cond = _block->TEMP(t); + } + _block->CJUMP(cond, iftrue, iffalse); +} + +void Codegen::accept(Node *node) +{ + if (node) + node->accept(this); +} + +void Codegen::statement(Statement *ast) +{ + accept(ast); +} + +void Codegen::statement(ExpressionNode *ast) +{ + if (! ast) { + return; + } else { + Result r(nx); + qSwap(_expr, r); + accept(ast); + qSwap(_expr, r); + if (r.format == ex) { + if (r->asCall()) { + _block->EXP(*r); // the nest nx representation for calls is EXP(CALL(c..)) + } else if (r->asTemp()) { + // there is nothing to do + } else { + unsigned t = _block->newTemp(); + move(_block->TEMP(t), *r); + } + } + } +} + +void Codegen::condition(ExpressionNode *ast, IR::BasicBlock *iftrue, IR::BasicBlock *iffalse) +{ + if (ast) { + Result r(iftrue, iffalse); + qSwap(_expr, r); + accept(ast); + qSwap(_expr, r); + if (r.format == ex) { + cjump(*r, r.iftrue, r.iffalse); + } + } +} + +Codegen::Result Codegen::expression(ExpressionNode *ast) +{ + Result r; + if (ast) { + qSwap(_expr, r); + accept(ast); + qSwap(_expr, r); + } + return r; +} + +QString Codegen::propertyName(PropertyName *ast) +{ + QString p; + if (ast) { + qSwap(_property, p); + accept(ast); + qSwap(_property, p); + } + return p; +} + +Codegen::Result Codegen::sourceElement(SourceElement *ast) +{ + Result r(nx); + if (ast) { + qSwap(_expr, r); + accept(ast); + qSwap(_expr, r); + } + return r; +} + +Codegen::UiMember Codegen::uiObjectMember(UiObjectMember *ast) +{ + UiMember m; + if (ast) { + qSwap(_uiMember, m); + accept(ast); + qSwap(_uiMember, m); + } + return m; +} + +void Codegen::functionBody(FunctionBody *ast) +{ + if (ast) + sourceElements(ast->elements); +} + +void Codegen::program(Program *ast) +{ + if (ast) { + sourceElements(ast->elements); + } +} + +void Codegen::sourceElements(SourceElements *ast) +{ + for (SourceElements *it = ast; it; it = it->next) { + sourceElement(it->element); + } +} + +void Codegen::variableDeclaration(VariableDeclaration *ast) +{ + IR::Expr *initializer = 0; + if (!ast->expression) + return; + Result expr = expression(ast->expression); + assert(expr.code); + initializer = *expr; + + if (! _env->parent || _function->insideWith) { + // it's global code. + move(_block->NAME(ast->name.toString(), ast->identifierToken.startLine, ast->identifierToken.startColumn), initializer); + } else { + const int index = _env->findMember(ast->name.toString()); + assert(index != -1); + move(_block->TEMP(index), initializer); + } +} + +void Codegen::variableDeclarationList(VariableDeclarationList *ast) +{ + for (VariableDeclarationList *it = ast; it; it = it->next) { + variableDeclaration(it->declaration); + } +} + + +bool Codegen::visit(ArgumentList *) +{ + assert(!"unreachable"); + return false; +} + +bool Codegen::visit(CaseBlock *) +{ + assert(!"unreachable"); + return false; +} + +bool Codegen::visit(CaseClause *) +{ + assert(!"unreachable"); + return false; +} + +bool Codegen::visit(CaseClauses *) +{ + assert(!"unreachable"); + return false; +} + +bool Codegen::visit(Catch *) +{ + assert(!"unreachable"); + return false; +} + +bool Codegen::visit(DefaultClause *) +{ + assert(!"unreachable"); + return false; +} + +bool Codegen::visit(ElementList *) +{ + assert(!"unreachable"); + return false; +} + +bool Codegen::visit(Elision *) +{ + assert(!"unreachable"); + return false; +} + +bool Codegen::visit(Finally *) +{ + assert(!"unreachable"); + return false; +} + +bool Codegen::visit(FormalParameterList *) +{ + assert(!"unreachable"); + return false; +} + +bool Codegen::visit(FunctionBody *) +{ + assert(!"unreachable"); + return false; +} + +bool Codegen::visit(Program *) +{ + assert(!"unreachable"); + return false; +} + +bool Codegen::visit(PropertyAssignmentList *) +{ + assert(!"unreachable"); + return false; +} + +bool Codegen::visit(PropertyNameAndValue *) +{ + assert(!"unreachable"); + return false; +} + +bool Codegen::visit(PropertyGetterSetter *) +{ + assert(!"unreachable"); + return false; +} + +bool Codegen::visit(SourceElements *) +{ + assert(!"unreachable"); + return false; +} + +bool Codegen::visit(StatementList *) +{ + assert(!"unreachable"); + return false; +} + +bool Codegen::visit(UiArrayMemberList *) +{ + assert(!"unreachable"); + return false; +} + +bool Codegen::visit(UiImport *) +{ + assert(!"unreachable"); + return false; +} + +bool Codegen::visit(UiImportList *) +{ + assert(!"unreachable"); + return false; +} + +bool Codegen::visit(UiObjectInitializer *) +{ + assert(!"unreachable"); + return false; +} + +bool Codegen::visit(UiObjectMemberList *) +{ + assert(!"unreachable"); + return false; +} + +bool Codegen::visit(UiParameterList *) +{ + assert(!"unreachable"); + return false; +} + +bool Codegen::visit(UiProgram *) +{ + assert(!"unreachable"); + return false; +} + +bool Codegen::visit(UiQualifiedId *) +{ + assert(!"unreachable"); + return false; +} + +bool Codegen::visit(VariableDeclaration *) +{ + assert(!"unreachable"); + return false; +} + +bool Codegen::visit(VariableDeclarationList *) +{ + assert(!"unreachable"); + return false; +} + +bool Codegen::visit(Expression *ast) +{ + statement(ast->left); + accept(ast->right); + return false; +} + +bool Codegen::visit(ArrayLiteral *ast) +{ + const unsigned t = _block->newTemp(); + move(_block->TEMP(t), _block->NEW(_block->NAME(QStringLiteral("Array"), ast->firstSourceLocation().startLine, ast->firstSourceLocation().startColumn))); + int index = 0; + unsigned value = 0; + for (ElementList *it = ast->elements; it; it = it->next) { + for (Elision *elision = it->elision; elision; elision = elision->next) + ++index; + Result expr = expression(it->expression); + + IR::ExprList *args = _function->New<IR::ExprList>(); + IR::ExprList *current = args; + current->expr = _block->TEMP(t); + current->next = _function->New<IR::ExprList>(); + current = current->next; + current->expr = _block->CONST(IR::NumberType, index); + current->next = _function->New<IR::ExprList>(); + current = current->next; + + if (!value) + value = _block->newTemp(); + move(_block->TEMP(value), *expr); + // __qmljs_builtin_define_property(Value object, String *name, Value val, ExecutionContext *ctx) + current->expr = _block->TEMP(value); + _block->EXP(_block->CALL(_block->NAME(IR::Name::builtin_define_array_property, 0, 0), args)); + + ++index; + } + if (ast->elision) { + for (Elision *elision = ast->elision->next; elision; elision = elision->next) + ++index; + // ### the new string leaks + move(member(_block->TEMP(t), _function->newString(QStringLiteral("length"))), _block->CONST(IR::NumberType, index + 1)); + } + _expr.code = _block->TEMP(t); + return false; +} + +bool Codegen::visit(ArrayMemberExpression *ast) +{ + Result base = expression(ast->base); + Result index = expression(ast->expression); + _expr.code = subscript(*base, *index); + return false; +} + +static IR::AluOp baseOp(int op) +{ + switch ((QSOperator::Op) op) { + case QSOperator::InplaceAnd: return IR::OpBitAnd; + case QSOperator::InplaceSub: return IR::OpSub; + case QSOperator::InplaceDiv: return IR::OpDiv; + case QSOperator::InplaceAdd: return IR::OpAdd; + case QSOperator::InplaceLeftShift: return IR::OpLShift; + case QSOperator::InplaceMod: return IR::OpMod; + case QSOperator::InplaceMul: return IR::OpMul; + case QSOperator::InplaceOr: return IR::OpBitOr; + case QSOperator::InplaceRightShift: return IR::OpRShift; + case QSOperator::InplaceURightShift: return IR::OpURShift; + case QSOperator::InplaceXor: return IR::OpBitXor; + default: return IR::OpInvalid; + } +} + +bool Codegen::visit(BinaryExpression *ast) +{ + if (ast->op == QSOperator::And) { + if (_expr.accept(cx)) { + IR::BasicBlock *iftrue = _function->newBasicBlock(); + condition(ast->left, iftrue, _expr.iffalse); + _block = iftrue; + condition(ast->right, _expr.iftrue, _expr.iffalse); + } else { + IR::BasicBlock *iftrue = _function->newBasicBlock(); + IR::BasicBlock *endif = _function->newBasicBlock(); + + const unsigned r = _block->newTemp(); + + move(_block->TEMP(r), *expression(ast->left)); + cjump(_block->TEMP(r), iftrue, endif); + _block = iftrue; + move(_block->TEMP(r), *expression(ast->right)); + _block->JUMP(endif); + + _expr.code = _block->TEMP(r); + _block = endif; + } + return false; + } else if (ast->op == QSOperator::Or) { + if (_expr.accept(cx)) { + IR::BasicBlock *iffalse = _function->newBasicBlock(); + condition(ast->left, _expr.iftrue, iffalse); + _block = iffalse; + condition(ast->right, _expr.iftrue, _expr.iffalse); + } else { + IR::BasicBlock *iffalse = _function->newBasicBlock(); + IR::BasicBlock *endif = _function->newBasicBlock(); + + const unsigned r = _block->newTemp(); + move(_block->TEMP(r), *expression(ast->left)); + cjump(_block->TEMP(r), endif, iffalse); + _block = iffalse; + move(_block->TEMP(r), *expression(ast->right)); + _block->JUMP(endif); + + _block = endif; + _expr.code = _block->TEMP(r); + } + return false; + } + + IR::Expr* left = *expression(ast->left); + throwSyntaxErrorOnEvalOrArgumentsInStrictMode(left, ast->left->lastSourceLocation()); + + switch (ast->op) { + case QSOperator::Or: + case QSOperator::And: + break; + + case QSOperator::Assign: { + IR::Expr* right = *expression(ast->right); + if (! (left->asTemp() || left->asName() || left->asSubscript() || left->asMember())) + throwReferenceError(ast->operatorToken, QCoreApplication::translate("qv4codegen", "left-hand side of assignment operator is not an lvalue")); + + if (_expr.accept(nx)) { + move(left, right); + } else { + const unsigned t = _block->newTemp(); + move(_block->TEMP(t), right); + move(left, _block->TEMP(t)); + _expr.code = left; + } + break; + } + + case QSOperator::InplaceAnd: + case QSOperator::InplaceSub: + case QSOperator::InplaceDiv: + case QSOperator::InplaceAdd: + case QSOperator::InplaceLeftShift: + case QSOperator::InplaceMod: + case QSOperator::InplaceMul: + case QSOperator::InplaceOr: + case QSOperator::InplaceRightShift: + case QSOperator::InplaceURightShift: + case QSOperator::InplaceXor: { + IR::Expr* right = *expression(ast->right); + if (!left->isLValue()) + throwSyntaxError(ast->operatorToken, QCoreApplication::translate("qv4codegen", "left-hand side of inplace operator is not an lvalue")); + + if (_expr.accept(nx)) { + move(left, right, baseOp(ast->op)); + } else { + const unsigned t = _block->newTemp(); + move(_block->TEMP(t), right); + move(left, _block->TEMP(t), baseOp(ast->op)); + _expr.code = left; + } + break; + } + + case QSOperator::In: + case QSOperator::InstanceOf: + case QSOperator::Equal: + case QSOperator::NotEqual: + case QSOperator::Ge: + case QSOperator::Gt: + case QSOperator::Le: + case QSOperator::Lt: + case QSOperator::StrictEqual: + case QSOperator::StrictNotEqual: { + if (!left->asTemp() && !left->asConst()) { + const unsigned t = _block->newTemp(); + move(_block->TEMP(t), left); + left = _block->TEMP(t); + } + + IR::Expr* right = *expression(ast->right); + + if (_expr.accept(cx)) { + cjump(binop(IR::binaryOperator(ast->op), left, right), _expr.iftrue, _expr.iffalse); + } else { + IR::Expr *e = binop(IR::binaryOperator(ast->op), left, right); + if (e->asConst() || e->asString()) + _expr.code = e; + else { + const unsigned t = _block->newTemp(); + move(_block->TEMP(t), e); + _expr.code = _block->TEMP(t); + } + } + break; + } + + case QSOperator::Add: + case QSOperator::BitAnd: + case QSOperator::BitOr: + case QSOperator::BitXor: + case QSOperator::Div: + case QSOperator::LShift: + case QSOperator::Mod: + case QSOperator::Mul: + case QSOperator::RShift: + case QSOperator::Sub: + case QSOperator::URShift: { + if (!left->asTemp() && !left->asConst()) { + const unsigned t = _block->newTemp(); + move(_block->TEMP(t), left); + left = _block->TEMP(t); + } + + IR::Expr* right = *expression(ast->right); + + IR::Expr *e = binop(IR::binaryOperator(ast->op), left, right); + if (e->asConst() || e->asString()) + _expr.code = e; + else { + const unsigned t = _block->newTemp(); + move(_block->TEMP(t), e); + _expr.code = _block->TEMP(t); + } + break; + } + + } // switch + + return false; +} + +bool Codegen::visit(CallExpression *ast) +{ + Result base = expression(ast->base); + IR::ExprList *args = 0, **args_it = &args; + for (ArgumentList *it = ast->arguments; it; it = it->next) { + Result arg = expression(it->expression); + IR::Expr *actual = argument(*arg); + *args_it = _function->New<IR::ExprList>(); + (*args_it)->init(actual); + args_it = &(*args_it)->next; + } + _expr.code = call(*base, args); + return false; +} + +bool Codegen::visit(ConditionalExpression *ast) +{ + IR::BasicBlock *iftrue = _function->newBasicBlock(); + IR::BasicBlock *iffalse = _function->newBasicBlock(); + IR::BasicBlock *endif = _function->newBasicBlock(); + + const unsigned t = _block->newTemp(); + + condition(ast->expression, iftrue, iffalse); + + _block = iftrue; + move(_block->TEMP(t), *expression(ast->ok)); + _block->JUMP(endif); + + _block = iffalse; + move(_block->TEMP(t), *expression(ast->ko)); + _block->JUMP(endif); + + _block = endif; + + _expr.code = _block->TEMP(t); + + return false; +} + +bool Codegen::visit(DeleteExpression *ast) +{ + IR::Expr* expr = *expression(ast->expression); + // Temporaries cannot be deleted + if (expr->asTemp() && expr->asTemp()->index < _env->members.size()) { + // Trying to delete a function argument might throw. + if (_function->isStrict && expr->asTemp()->index < 0) + throwSyntaxError(ast->deleteToken, "Delete of an unqualified identifier in strict mode."); + _expr.code = _block->CONST(IR::BoolType, 0); + return false; + } + if (_function->isStrict && expr->asName()) + throwSyntaxError(ast->deleteToken, "Delete of an unqualified identifier in strict mode."); + + // [[11.4.1]] Return true if it's not a reference + if (expr->asConst() || expr->asString()) { + _expr.code = _block->CONST(IR::BoolType, 1); + return false; + } + + // Return values from calls are also not a reference, but we have to + // perform the call to allow for side effects. + if (expr->asCall()) { + _block->EXP(expr); + _expr.code = _block->CONST(IR::BoolType, 1); + return false; + } + if (expr->asTemp() && expr->asTemp()->index >= _env->members.size()) { + _expr.code = _block->CONST(IR::BoolType, 1); + return false; + } + + IR::ExprList *args = _function->New<IR::ExprList>(); + args->init(reference(expr)); + _expr.code = call(_block->NAME(IR::Name::builtin_delete, ast->deleteToken.startLine, ast->deleteToken.startColumn), args); + return false; +} + +bool Codegen::visit(FalseLiteral *) +{ + if (_expr.accept(cx)) { + _block->JUMP(_expr.iffalse); + } else { + _expr.code = _block->CONST(IR::BoolType, 0); + } + return false; +} + +bool Codegen::visit(FieldMemberExpression *ast) +{ + Result base = expression(ast->base); + _expr.code = member(*base, _function->newString(ast->name.toString())); + return false; +} + +bool Codegen::visit(FunctionExpression *ast) +{ + IR::Function *function = defineFunction(ast->name.toString(), ast, ast->formals, ast->body ? ast->body->elements : 0); + if (_debugger) + _debugger->setSourceLocation(function, ast->functionToken.startLine, ast->functionToken.startColumn); + _expr.code = _block->CLOSURE(function); + return false; +} + +IR::Expr *Codegen::identifier(const QString &name, int line, int col) +{ + int index = _env->findMember(name); + + if (! _function->hasDirectEval && !_function->insideWith && _env->parent) { + if (index != -1) { + return _block->TEMP(index); + } + index = indexOfArgument(&name); + if (index != -1) { + return _block->TEMP(-(index + 1)); + } + } + + if (index >= _env->members.size()) { + // named local variable, e.g. in a catch statement + return _block->TEMP(index); + } + + return _block->NAME(name, line, col); + +} + +bool Codegen::visit(IdentifierExpression *ast) +{ + _expr.code = identifier(ast->name.toString(), ast->identifierToken.startLine, ast->identifierToken.startColumn); + return false; +} + +bool Codegen::visit(NestedExpression *ast) +{ + accept(ast->expression); + return false; +} + +bool Codegen::visit(NewExpression *ast) +{ + Result base = expression(ast->expression); + IR::Expr *expr = *base; + if (expr && !expr->asTemp() && !expr->asName() && !expr->asMember()) { + const unsigned t = _block->newTemp(); + move(_block->TEMP(t), expr); + expr = _block->TEMP(t); + } + _expr.code = _block->NEW(expr, 0); + return false; +} + +bool Codegen::visit(NewMemberExpression *ast) +{ + Result base = expression(ast->base); + IR::Expr *expr = *base; + if (expr && !expr->asTemp() && !expr->asName() && !expr->asMember()) { + const unsigned t = _block->newTemp(); + move(_block->TEMP(t), expr); + expr = _block->TEMP(t); + } + + IR::ExprList *args = 0, **args_it = &args; + for (ArgumentList *it = ast->arguments; it; it = it->next) { + Result arg = expression(it->expression); + IR::Expr *actual = argument(*arg); + *args_it = _function->New<IR::ExprList>(); + (*args_it)->init(actual); + args_it = &(*args_it)->next; + } + const unsigned t = _block->newTemp(); + move(_block->TEMP(t), _block->NEW(expr, args)); + _expr.code = _block->TEMP(t); + return false; +} + +bool Codegen::visit(NotExpression *ast) +{ + Result expr = expression(ast->expression); + const unsigned r = _block->newTemp(); + move(_block->TEMP(r), unop(IR::OpNot, *expr)); + _expr.code = _block->TEMP(r); + return false; +} + +bool Codegen::visit(NullExpression *) +{ + if (_expr.accept(cx)) _block->JUMP(_expr.iffalse); + else _expr.code = _block->CONST(IR::NullType, 0); + + return false; +} + +bool Codegen::visit(NumericLiteral *ast) +{ + if (_expr.accept(cx)) { + if (ast->value) _block->JUMP(_expr.iftrue); + else _block->JUMP(_expr.iffalse); + } else { + _expr.code = _block->CONST(IR::NumberType, ast->value); + } + return false; +} + +struct ObjectPropertyValue { + IR::Expr *value; + IR::Function *getter; + IR::Function *setter; +}; + +bool Codegen::visit(ObjectLiteral *ast) +{ + QMap<QString, ObjectPropertyValue> valueMap; + + const unsigned t = _block->newTemp(); + move(_block->TEMP(t), _block->NEW(_block->NAME(QStringLiteral("Object"), ast->firstSourceLocation().startLine, ast->firstSourceLocation().startColumn))); + for (PropertyAssignmentList *it = ast->properties; it; it = it->next) { + if (PropertyNameAndValue *nv = AST::cast<AST::PropertyNameAndValue *>(it->assignment)) { + QString name = propertyName(nv->name); + Result value = expression(nv->value); + ObjectPropertyValue &v = valueMap[name]; + if (v.getter || v.setter || (_function->isStrict && v.value)) + throwSyntaxError(nv->lastSourceLocation(), + QCoreApplication::translate("qv4codegen", "Illegal duplicate key '%1' in object literal").arg(name)); + + valueMap[name].value = *value; + } else if (PropertyGetterSetter *gs = AST::cast<AST::PropertyGetterSetter *>(it->assignment)) { + QString name = propertyName(gs->name); + IR::Function *function = defineFunction(name, gs, gs->formals, gs->functionBody ? gs->functionBody->elements : 0); + if (_debugger) + _debugger->setSourceLocation(function, gs->getSetToken.startLine, gs->getSetToken.startColumn); + ObjectPropertyValue &v = valueMap[name]; + if (v.value || + (gs->type == PropertyGetterSetter::Getter && v.getter) || + (gs->type == PropertyGetterSetter::Setter && v.setter)) + throwSyntaxError(gs->lastSourceLocation(), + QCoreApplication::translate("qv4codegen", "Illegal duplicate key '%1' in object literal").arg(name)); + if (gs->type == PropertyGetterSetter::Getter) + v.getter = function; + else + v.setter = function; + } else { + Q_UNREACHABLE(); + } + } + if (!valueMap.isEmpty()) { + unsigned value = 0; + unsigned getter = 0; + unsigned setter = 0; + for (QMap<QString, ObjectPropertyValue>::const_iterator it = valueMap.constBegin(); it != valueMap.constEnd(); ++it) { + IR::ExprList *args = _function->New<IR::ExprList>(); + IR::ExprList *current = args; + current->expr = _block->TEMP(t); + current->next = _function->New<IR::ExprList>(); + current = current->next; + current->expr = _block->NAME(it.key(), 0, 0); + current->next = _function->New<IR::ExprList>(); + current = current->next; + + if (it->value) { + if (!value) + value = _block->newTemp(); + move(_block->TEMP(value), it->value); + // __qmljs_builtin_define_property(Value object, String *name, Value val, ExecutionContext *ctx) + current->expr = _block->TEMP(value); + _block->EXP(_block->CALL(_block->NAME(IR::Name::builtin_define_property, 0, 0), args)); + } else { + if (!getter) { + getter = _block->newTemp(); + setter = _block->newTemp(); + } + move(_block->TEMP(getter), it->getter ? _block->CLOSURE(it->getter) : _block->CONST(IR::UndefinedType, 0)); + move(_block->TEMP(setter), it->setter ? _block->CLOSURE(it->setter) : _block->CONST(IR::UndefinedType, 0)); + + + // __qmljs_builtin_define_getter_setter(Value object, String *name, Value getter, Value setter, ExecutionContext *ctx); + current->expr = _block->TEMP(getter); + current->next = _function->New<IR::ExprList>(); + current = current->next; + current->expr = _block->TEMP(setter); + _block->EXP(_block->CALL(_block->NAME(IR::Name::builtin_define_getter_setter, 0, 0), args)); + } + } + } + + _expr.code = _block->TEMP(t); + return false; +} + +bool Codegen::visit(PostDecrementExpression *ast) +{ + Result expr = expression(ast->base); + if (!expr->isLValue()) + throwReferenceError(ast->base->lastSourceLocation(), "Invalid left-hand side expression in postfix operation"); + throwSyntaxErrorOnEvalOrArgumentsInStrictMode(*expr, ast->decrementToken); + + IR::ExprList *args = _function->New<IR::ExprList>(); + args->init(*expr); + _expr.code = call(_block->NAME(IR::Name::builtin_postdecrement, ast->lastSourceLocation().startLine, ast->lastSourceLocation().startColumn), args); + return false; +} + +bool Codegen::visit(PostIncrementExpression *ast) +{ + Result expr = expression(ast->base); + if (!expr->isLValue()) + throwReferenceError(ast->base->lastSourceLocation(), "Invalid left-hand side expression in postfix operation"); + throwSyntaxErrorOnEvalOrArgumentsInStrictMode(*expr, ast->incrementToken); + + IR::ExprList *args = _function->New<IR::ExprList>(); + args->init(*expr); + _expr.code = call(_block->NAME(IR::Name::builtin_postincrement, ast->lastSourceLocation().startLine, ast->lastSourceLocation().startColumn), args); + return false; +} + +bool Codegen::visit(PreDecrementExpression *ast) +{ + Result expr = expression(ast->expression); + throwSyntaxErrorOnEvalOrArgumentsInStrictMode(*expr, ast->decrementToken); + move(*expr, unop(IR::OpDecrement, *expr)); + if (_expr.accept(nx)) { + // nothing to do + } else { + _expr.code = *expr; + } + return false; +} + +bool Codegen::visit(PreIncrementExpression *ast) +{ + Result expr = expression(ast->expression); + throwSyntaxErrorOnEvalOrArgumentsInStrictMode(*expr, ast->incrementToken); + move(*expr, unop(IR::OpIncrement, *expr)); + if (_expr.accept(nx)) { + // nothing to do + } else { + _expr.code = *expr; + } + return false; +} + +bool Codegen::visit(RegExpLiteral *ast) +{ + _expr.code = _block->REGEXP(_function->newString(ast->pattern.toString()), ast->flags); + return false; +} + +bool Codegen::visit(StringLiteral *ast) +{ + _expr.code = _block->STRING(_function->newString(ast->value.toString())); + return false; +} + +bool Codegen::visit(ThisExpression *ast) +{ + _expr.code = _block->NAME(QStringLiteral("this"), ast->thisToken.startLine, ast->thisToken.startColumn); + return false; +} + +bool Codegen::visit(TildeExpression *ast) +{ + Result expr = expression(ast->expression); + const unsigned t = _block->newTemp(); + move(_block->TEMP(t), unop(IR::OpCompl, *expr)); + _expr.code = _block->TEMP(t); + return false; +} + +bool Codegen::visit(TrueLiteral *) +{ + if (_expr.accept(cx)) { + _block->JUMP(_expr.iftrue); + } else { + _expr.code = _block->CONST(IR::BoolType, 1); + } + return false; +} + +bool Codegen::visit(TypeOfExpression *ast) +{ + Result expr = expression(ast->expression); + IR::ExprList *args = _function->New<IR::ExprList>(); + args->init(reference(*expr)); + _expr.code = call(_block->NAME(IR::Name::builtin_typeof, ast->typeofToken.startLine, ast->typeofToken.startColumn), args); + return false; +} + +bool Codegen::visit(UnaryMinusExpression *ast) +{ + Result expr = expression(ast->expression); + const unsigned t = _block->newTemp(); + move(_block->TEMP(t), unop(IR::OpUMinus, *expr)); + _expr.code = _block->TEMP(t); + return false; +} + +bool Codegen::visit(UnaryPlusExpression *ast) +{ + Result expr = expression(ast->expression); + const unsigned t = _block->newTemp(); + move(_block->TEMP(t), unop(IR::OpUPlus, *expr)); + _expr.code = _block->TEMP(t); + return false; +} + +bool Codegen::visit(VoidExpression *ast) +{ + statement(ast->expression); + _expr.code = _block->CONST(IR::UndefinedType, 0); + return false; +} + +bool Codegen::visit(FunctionDeclaration * /*ast*/) +{ + _expr.accept(nx); + return false; +} + +void Codegen::linearize(IR::Function *function) +{ + IR::BasicBlock *exitBlock = function->basicBlocks.last(); + assert(exitBlock->isTerminated()); + assert(exitBlock->terminator()->asRet()); + + QSet<IR::BasicBlock *> V; + V.insert(exitBlock); + + QVector<IR::BasicBlock *> trace; + + for (int i = 0; i < function->basicBlocks.size(); ++i) { + IR::BasicBlock *block = function->basicBlocks.at(i); + if (!block->isTerminated() && (i + 1) < function->basicBlocks.size()) { + IR::BasicBlock *next = function->basicBlocks.at(i + 1); + block->JUMP(next); + } + } + + struct I { static void trace(IR::BasicBlock *block, QSet<IR::BasicBlock *> *V, + QVector<IR::BasicBlock *> *output) { + if (block == 0 || V->contains(block)) + return; + + V->insert(block); + block->index = output->size(); + output->append(block); + + if (IR::Stmt *term = block->terminator()) { + if (IR::Jump *j = term->asJump()) { + trace(j->target, V, output); + } else if (IR::CJump *cj = term->asCJump()) { + if (! V->contains(cj->iffalse)) + trace(cj->iffalse, V, output); + else + trace(cj->iftrue, V, output); + } + } + + // We could do this for each type above, but it is safer to have a + // "catchall" here + for (int ii = 0; ii < block->out.count(); ++ii) + trace(block->out.at(ii), V, output); + } + }; + + I::trace(function->basicBlocks.first(), &V, &trace); + + V.insert(exitBlock); + exitBlock->index = trace.size(); + trace.append(exitBlock); + + QVarLengthArray<IR::BasicBlock*> blocksToDelete; + foreach (IR::BasicBlock *b, function->basicBlocks) + if (!V.contains(b)) { + foreach (IR::BasicBlock *out, b->out) { + int idx = out->in.indexOf(b); + if (idx >= 0) + out->in.remove(idx); + } + blocksToDelete.append(b); + } + qDeleteAll(blocksToDelete); + function->basicBlocks = trace; + +#ifndef QV4_NO_LIVENESS + liveness(function); +#endif + + static bool showCode = !qgetenv("SHOW_CODE").isNull(); + if (showCode) { + QVector<IR::Stmt *> code; + QHash<IR::Stmt *, IR::BasicBlock *> leader; + + foreach (IR::BasicBlock *block, function->basicBlocks) { + leader.insert(block->statements.first(), block); + foreach (IR::Stmt *s, block->statements) { + code.append(s); + } + } + + QString name; + if (function->name && !function->name->isEmpty()) + name = *function->name; + else + name.sprintf("%p", function); + + qout << "function " << name << "("; + for (int i = 0; i < function->formals.size(); ++i) { + if (i != 0) + qout << ", "; + qout << *function->formals.at(i); + } + qout << ")" << endl + << "{" << endl; + + foreach (const QString *local, function->locals) { + qout << " var " << *local << ';' << endl; + } + + for (int i = 0; i < code.size(); ++i) { + IR::Stmt *s = code.at(i); + + if (IR::BasicBlock *bb = leader.value(s)) { + qout << endl; + QByteArray str; + str.append('L'); + str.append(QByteArray::number(bb->index)); + str.append(':'); + for (int i = 66 - str.length(); i; --i) + str.append(' '); + qout << str; + qout << "// predecessor blocks:"; + foreach (IR::BasicBlock *in, bb->in) + qout << " L" << in->index; + qout << endl; + } + IR::Stmt *n = (i + 1) < code.size() ? code.at(i + 1) : 0; + if (n && s->asJump() && s->asJump()->target == leader.value(n)) { + continue; + } + + QByteArray str; + QBuffer buf(&str); + buf.open(QIODevice::WriteOnly); + QTextStream out(&buf); + s->dump(out, IR::Stmt::MIR); + out.flush(); + +#ifndef QV4_NO_LIVENESS + for (int i = 60 - str.size(); i >= 0; --i) + str.append(' '); + + qout << " " << str; + + // if (! s->uses.isEmpty()) { + // qout << " // uses:"; + // foreach (unsigned use, s->uses) { + // qout << " %" << use; + // } + // } + + // if (! s->defs.isEmpty()) { + // qout << " // defs:"; + // foreach (unsigned def, s->defs) { + // qout << " %" << def; + // } + // } + + if (! s->d->liveOut.isEmpty()) { + qout << " // lives out:"; + for (int i = 0; i < s->d->liveOut.size(); ++i) { + if (s->d->liveOut.testBit(i)) + qout << " %" << i; + } + } +#else + qout << " " << str; +#endif + + qout << endl; + + if (n && s->asCJump() && s->asCJump()->iffalse != leader.value(n)) { + qout << " goto L" << s->asCJump()->iffalse << ";" << endl; + } + } + + qout << "}" << endl + << endl; + } +} + +IR::Function *Codegen::defineFunction(const QString &name, AST::Node *ast, + AST::FormalParameterList *formals, + AST::SourceElements *body, Mode mode, + const QStringList &inheritedLocals) +{ + qSwap(_mode, mode); // enter function code. + + ScopeAndFinally *scopeAndFinally = 0; + + enterEnvironment(ast); + IR::Function *function = _module->newFunction(name, _function); + + if (_debugger) + _debugger->addFunction(function); + IR::BasicBlock *entryBlock = function->newBasicBlock(); + IR::BasicBlock *exitBlock = function->newBasicBlock(IR::Function::DontInsertBlock); + IR::BasicBlock *throwBlock = function->newBasicBlock(); + function->hasDirectEval = _env->hasDirectEval; + function->usesArgumentsObject = (_env->usesArgumentsObject == Environment::ArgumentsObjectUsed); + function->maxNumberOfArguments = _env->maxNumberOfArguments; + function->isStrict = _env->isStrict; + + // variables in global code are properties of the global context object, not locals as with other functions. + if (_mode == FunctionCode) { + for (Environment::MemberMap::iterator it = _env->members.begin(); it != _env->members.end(); ++it) { + const QString &local = it.key(); + function->LOCAL(local); + unsigned t = entryBlock->newTemp(); + (*it).index = t; + } + } else { + if (!_env->isStrict) { + foreach (const QString &inheritedLocal, inheritedLocals) { + function->LOCAL(inheritedLocal); + unsigned tempIndex = entryBlock->newTemp(); + Environment::Member member = { Environment::UndefinedMember, tempIndex, 0 }; + _env->members.insert(inheritedLocal, member); + } + } + + IR::ExprList *args = 0; + for (Environment::MemberMap::const_iterator it = _env->members.constBegin(); it != _env->members.constEnd(); ++it) { + const QString &local = it.key(); + IR::ExprList *next = function->New<IR::ExprList>(); + next->expr = entryBlock->NAME(local, 0, 0); + next->next = args; + args = next; + } + if (args) { + IR::ExprList *next = function->New<IR::ExprList>(); + next->expr = entryBlock->CONST(IR::BoolType, mode == EvalCode); + next->next = args; + args = next; + + entryBlock->EXP(entryBlock->CALL(entryBlock->NAME(IR::Name::builtin_declare_vars, 0, 0), args)); + } + } + + unsigned returnAddress = entryBlock->newTemp(); + + entryBlock->MOVE(entryBlock->TEMP(returnAddress), entryBlock->CONST(IR::UndefinedType, 0)); + exitBlock->RET(exitBlock->TEMP(returnAddress)); + IR::ExprList *throwArgs = function->New<IR::ExprList>(); + throwArgs->expr = throwBlock->TEMP(returnAddress); + throwBlock->EXP(throwBlock->CALL(throwBlock->NAME(IR::Name::builtin_throw, /*line*/0, /*column*/0), throwArgs)); + Loop *loop = 0; + + qSwap(_function, function); + qSwap(_block, entryBlock); + qSwap(_exitBlock, exitBlock); + qSwap(_throwBlock, throwBlock); + qSwap(_returnAddress, returnAddress); + qSwap(_scopeAndFinally, scopeAndFinally); + qSwap(_loop, loop); + + for (FormalParameterList *it = formals; it; it = it->next) { + _function->RECEIVE(it->name.toString()); + } + + foreach (const Environment::Member &member, _env->members) { + if (member.function) { + IR::Function *function = defineFunction(member.function->name.toString(), member.function, member.function->formals, + member.function->body ? member.function->body->elements : 0); + if (_debugger) + _debugger->setSourceLocation(function, member.function->functionToken.startLine, member.function->functionToken.startColumn); + if (! _env->parent) { + move(_block->NAME(member.function->name.toString(), member.function->identifierToken.startLine, member.function->identifierToken.startColumn), + _block->CLOSURE(function)); + } else { + assert(member.index >= 0); + move(_block->TEMP(member.index), _block->CLOSURE(function)); + } + } + } + + sourceElements(body); + + _function->insertBasicBlock(_exitBlock); + + _block->JUMP(_exitBlock); + + qSwap(_function, function); + qSwap(_block, entryBlock); + qSwap(_exitBlock, exitBlock); + qSwap(_throwBlock, throwBlock); + qSwap(_returnAddress, returnAddress); + qSwap(_scopeAndFinally, scopeAndFinally); + qSwap(_loop, loop); + + leaveEnvironment(); + + qSwap(_mode, mode); + + return function; +} + +int Codegen::indexOfArgument(const QStringRef &string) const +{ + for (int i = _function->formals.size() - 1; i >= 0; --i) { + if (*_function->formals.at(i) == string) + return i; + } + return -1; +} + +bool Codegen::visit(IdentifierPropertyName *ast) +{ + _property = ast->id.toString(); + return false; +} + +bool Codegen::visit(NumericLiteralPropertyName *ast) +{ + _property = QString::number(ast->id, 'g', 16); + return false; +} + +bool Codegen::visit(StringLiteralPropertyName *ast) +{ + _property = ast->id.toString(); + return false; +} + +bool Codegen::visit(FunctionSourceElement *ast) +{ + statement(ast->declaration); + return false; +} + +bool Codegen::visit(StatementSourceElement *ast) +{ + statement(ast->statement); + return false; +} + +bool Codegen::visit(Block *ast) +{ + for (StatementList *it = ast->statements; it; it = it->next) { + statement(it->statement); + } + return false; +} + +bool Codegen::visit(BreakStatement *ast) +{ + if (!_loop) + throwSyntaxError(ast->lastSourceLocation(), QCoreApplication::translate("qv4codegen", "Break outside of loop")); + Loop *loop = 0; + if (ast->label.isEmpty()) + loop = _loop; + else { + for (loop = _loop; loop; loop = loop->parent) { + if (loop->labelledStatement && loop->labelledStatement->label == ast->label) + break; + } + if (!loop) + throwSyntaxError(ast->lastSourceLocation(), QCoreApplication::translate("qv4codegen", "Undefined label '%1'").arg(ast->label.toString())); + } + unwindException(loop->scopeAndFinally); + _block->JUMP(loop->breakBlock); + return false; +} + +bool Codegen::visit(ContinueStatement *ast) +{ + Loop *loop = 0; + if (ast->label.isEmpty()) { + for (loop = _loop; loop; loop = loop->parent) { + if (loop->continueBlock) + break; + } + } else { + for (loop = _loop; loop; loop = loop->parent) { + if (loop->labelledStatement && loop->labelledStatement->label == ast->label) { + if (!loop->continueBlock) + loop = 0; + break; + } + } + if (!loop) + throwSyntaxError(ast->lastSourceLocation(), QCoreApplication::translate("qv4codegen", "Undefined label '%1'").arg(ast->label.toString())); + } + if (!loop) + throwSyntaxError(ast->lastSourceLocation(), QCoreApplication::translate("qv4codegen", "continue outside of loop")); + unwindException(loop->scopeAndFinally); + _block->JUMP(loop->continueBlock); + return false; +} + +bool Codegen::visit(DebuggerStatement *) +{ + assert(!"not implemented"); + return false; +} + +bool Codegen::visit(DoWhileStatement *ast) +{ + IR::BasicBlock *loopbody = _function->newBasicBlock(); + IR::BasicBlock *loopcond = _function->newBasicBlock(); + IR::BasicBlock *loopend = _function->newBasicBlock(); + + enterLoop(ast, loopend, loopcond); + + _block->JUMP(loopbody); + + _block = loopbody; + statement(ast->statement); + _block->JUMP(loopcond); + + _block = loopcond; + condition(ast->expression, loopbody, loopend); + + _block = loopend; + + leaveLoop(); + + return false; +} + +bool Codegen::visit(EmptyStatement *) +{ + return false; +} + +bool Codegen::visit(ExpressionStatement *ast) +{ + if (_mode == EvalCode) { + Result e = expression(ast->expression); + if (*e) + move(_block->TEMP(_returnAddress), *e); + } else { + statement(ast->expression); + } + return false; +} + +bool Codegen::visit(ForEachStatement *ast) +{ + IR::BasicBlock *foreachin = _function->newBasicBlock(); + IR::BasicBlock *foreachbody = _function->newBasicBlock(); + IR::BasicBlock *foreachend = _function->newBasicBlock(); + + enterLoop(ast, foreachend, foreachin); + + int iterator = _block->newTemp(); + move(_block->TEMP(iterator), *expression(ast->expression)); + IR::ExprList *args = _function->New<IR::ExprList>(); + args->init(_block->TEMP(iterator)); + move(_block->TEMP(iterator), _block->CALL(_block->NAME(IR::Name::builtin_foreach_iterator_object, 0, 0), args)); + + _block->JUMP(foreachin); + + _block = foreachbody; + int temp = _block->newTemp(); + move(*expression(ast->initialiser), _block->TEMP(temp)); + statement(ast->statement); + _block->JUMP(foreachin); + + _block = foreachin; + + args = _function->New<IR::ExprList>(); + args->init(_block->TEMP(iterator)); + move(_block->TEMP(temp), _block->CALL(_block->NAME(IR::Name::builtin_foreach_next_property_name, 0, 0), args)); + int null = _block->newTemp(); + move(_block->TEMP(null), _block->CONST(IR::NullType, 0)); + cjump(_block->BINOP(IR::OpStrictNotEqual, _block->TEMP(temp), _block->TEMP(null)), foreachbody, foreachend); + _block = foreachend; + + leaveLoop(); + return false; +} + +bool Codegen::visit(ForStatement *ast) +{ + IR::BasicBlock *forcond = _function->newBasicBlock(); + IR::BasicBlock *forbody = _function->newBasicBlock(); + IR::BasicBlock *forstep = _function->newBasicBlock(); + IR::BasicBlock *forend = _function->newBasicBlock(); + + enterLoop(ast, forend, forstep); + + statement(ast->initialiser); + _block->JUMP(forcond); + + _block = forcond; + condition(ast->condition, forbody, forend); + + _block = forbody; + statement(ast->statement); + _block->JUMP(forstep); + + _block = forstep; + statement(ast->expression); + _block->JUMP(forcond); + _block = forend; + + leaveLoop(); + + return false; +} + +bool Codegen::visit(IfStatement *ast) +{ + IR::BasicBlock *iftrue = _function->newBasicBlock(); + IR::BasicBlock *iffalse = ast->ko ? _function->newBasicBlock() : 0; + IR::BasicBlock *endif = _function->newBasicBlock(); + condition(ast->expression, iftrue, ast->ko ? iffalse : endif); + + _block = iftrue; + statement(ast->ok); + _block->JUMP(endif); + + if (ast->ko) { + _block = iffalse; + statement(ast->ko); + _block->JUMP(endif); + } + + _block = endif; + + return false; +} + +bool Codegen::visit(LabelledStatement *ast) +{ + _labelledStatement = ast; + + if (AST::cast<AST::SwitchStatement *>(ast->statement) || + AST::cast<AST::WhileStatement *>(ast->statement) || + AST::cast<AST::DoWhileStatement *>(ast->statement) || + AST::cast<AST::ForStatement *>(ast->statement) || + AST::cast<AST::ForEachStatement *>(ast->statement) || + AST::cast<AST::LocalForStatement *>(ast->statement) || + AST::cast<AST::LocalForEachStatement *>(ast->statement)) { + statement(ast->statement); // labelledStatement will be associated with the ast->statement's loop. + } else { + IR::BasicBlock *breakBlock = _function->newBasicBlock(); + enterLoop(ast->statement, breakBlock, /*continueBlock*/ 0); + statement(ast->statement); + _block->JUMP(breakBlock); + _block = breakBlock; + leaveLoop(); + } + + return false; +} + +bool Codegen::visit(LocalForEachStatement *ast) +{ + IR::BasicBlock *foreachin = _function->newBasicBlock(); + IR::BasicBlock *foreachbody = _function->newBasicBlock(); + IR::BasicBlock *foreachend = _function->newBasicBlock(); + + enterLoop(ast, foreachend, foreachin); + + variableDeclaration(ast->declaration); + + int iterator = _block->newTemp(); + move(_block->TEMP(iterator), *expression(ast->expression)); + IR::ExprList *args = _function->New<IR::ExprList>(); + args->init(_block->TEMP(iterator)); + move(_block->TEMP(iterator), _block->CALL(_block->NAME(IR::Name::builtin_foreach_iterator_object, 0, 0), args)); + + _block->JUMP(foreachin); + + _block = foreachbody; + int temp = _block->newTemp(); + move(identifier(ast->declaration->name.toString()), _block->TEMP(temp)); + statement(ast->statement); + _block->JUMP(foreachin); + + _block = foreachin; + + args = _function->New<IR::ExprList>(); + args->init(_block->TEMP(iterator)); + move(_block->TEMP(temp), _block->CALL(_block->NAME(IR::Name::builtin_foreach_next_property_name, 0, 0), args)); + int null = _block->newTemp(); + move(_block->TEMP(null), _block->CONST(IR::NullType, 0)); + cjump(_block->BINOP(IR::OpStrictNotEqual, _block->TEMP(temp), _block->TEMP(null)), foreachbody, foreachend); + _block = foreachend; + + leaveLoop(); + return false; +} + +bool Codegen::visit(LocalForStatement *ast) +{ + IR::BasicBlock *forcond = _function->newBasicBlock(); + IR::BasicBlock *forbody = _function->newBasicBlock(); + IR::BasicBlock *forstep = _function->newBasicBlock(); + IR::BasicBlock *forend = _function->newBasicBlock(); + + enterLoop(ast, forend, forstep); + + variableDeclarationList(ast->declarations); + _block->JUMP(forcond); + + _block = forcond; + condition(ast->condition, forbody, forend); + + _block = forbody; + statement(ast->statement); + _block->JUMP(forstep); + + _block = forstep; + statement(ast->expression); + _block->JUMP(forcond); + _block = forend; + + leaveLoop(); + + return false; +} + +bool Codegen::visit(ReturnStatement *ast) +{ + if (_mode != FunctionCode) + throwSyntaxError(ast->returnToken, QCoreApplication::translate("qv4codegen", "Return statement outside of function")); + if (ast->expression) { + Result expr = expression(ast->expression); + move(_block->TEMP(_returnAddress), *expr); + } + unwindException(0); + + _block->JUMP(_exitBlock); + return false; +} + +bool Codegen::visit(SwitchStatement *ast) +{ + IR::BasicBlock *switchend = _function->newBasicBlock(); + + if (ast->block) { + Result lhs = expression(ast->expression); + IR::BasicBlock *switchcond = _block; + + QHash<Node *, IR::BasicBlock *> blockMap; + + enterLoop(ast, switchend, 0); + + for (CaseClauses *it = ast->block->clauses; it; it = it->next) { + CaseClause *clause = it->clause; + + _block = _function->newBasicBlock(); + blockMap[clause] = _block; + + for (StatementList *it2 = clause->statements; it2; it2 = it2->next) + statement(it2->statement); + } + + if (ast->block->defaultClause) { + _block = _function->newBasicBlock(); + blockMap[ast->block->defaultClause] = _block; + + for (StatementList *it2 = ast->block->defaultClause->statements; it2; it2 = it2->next) + statement(it2->statement); + } + + for (CaseClauses *it = ast->block->moreClauses; it; it = it->next) { + CaseClause *clause = it->clause; + + _block = _function->newBasicBlock(); + blockMap[clause] = _block; + + for (StatementList *it2 = clause->statements; it2; it2 = it2->next) + statement(it2->statement); + } + + leaveLoop(); + + _block->JUMP(switchend); + + _block = switchcond; + for (CaseClauses *it = ast->block->clauses; it; it = it->next) { + CaseClause *clause = it->clause; + Result rhs = expression(clause->expression); + IR::BasicBlock *iftrue = blockMap[clause]; + IR::BasicBlock *iffalse = _function->newBasicBlock(); + cjump(binop(IR::OpStrictEqual, *lhs, *rhs), iftrue, iffalse); + _block = iffalse; + } + + for (CaseClauses *it = ast->block->moreClauses; it; it = it->next) { + CaseClause *clause = it->clause; + Result rhs = expression(clause->expression); + IR::BasicBlock *iftrue = blockMap[clause]; + IR::BasicBlock *iffalse = _function->newBasicBlock(); + cjump(binop(IR::OpStrictEqual, *lhs, *rhs), iftrue, iffalse); + _block = iffalse; + } + + if (ast->block->defaultClause) { + _block->JUMP(blockMap[ast->block->defaultClause]); + } + } + + _block->JUMP(switchend); + + _block = switchend; + return false; +} + +bool Codegen::visit(ThrowStatement *ast) +{ + Result expr = expression(ast->expression); + move(_block->TEMP(_returnAddress), *expr); + _block->JUMP(_throwBlock); + return false; +} + +bool Codegen::visit(TryStatement *ast) +{ + if (_function->isStrict && ast->catchExpression && + (ast->catchExpression->name == QLatin1String("eval") || ast->catchExpression->name == QLatin1String("arguments"))) + throwSyntaxError(ast->catchExpression->identifierToken, QCoreApplication::translate("qv4codegen", "Catch variable name may not be eval or arguments in strict mode")); + + IR::BasicBlock *tryBody = _function->newBasicBlock(); + IR::BasicBlock *catchBody = ast->catchExpression ? _function->newBasicBlock() : 0; + // We always need a finally body to clean up the exception handler + IR::BasicBlock *finallyBody = _function->newBasicBlock(); + + int inCatch = 0; + if (catchBody) { + inCatch = _block->newTemp(); + move(_block->TEMP(inCatch), _block->CONST(IR::BoolType, false)); + } + + int hasException = _block->newTemp(); + move(_block->TEMP(hasException), _block->CALL(_block->NAME(IR::Name::builtin_create_exception_handler, 0, 0), 0)); + + // Pass the hidden "inCatch" and "hasException" TEMPs to the + // builtin_delete_exception_handler, in order to have those TEMPs alive for + // the duration of the exception handling block. + IR::ExprList *deleteExceptionArgs = _function->New<IR::ExprList>(); + deleteExceptionArgs->init(_block->TEMP(hasException)); + if (inCatch) { + deleteExceptionArgs->next = _function->New<IR::ExprList>(); + deleteExceptionArgs->next->init(_block->TEMP(inCatch)); + } + + ScopeAndFinally tcf(_scopeAndFinally, ast->finallyExpression, deleteExceptionArgs); + _scopeAndFinally = &tcf; + + _block->CJUMP(_block->TEMP(hasException), catchBody ? catchBody : finallyBody, tryBody); + + _block = tryBody; + statement(ast->statement); + _block->JUMP(finallyBody); + + // regular flow does not go into the catch statement + if (catchBody) { + _block = catchBody; + + if (inCatch != 0) { + // check if an exception got thrown within catch. Go to finally + // and then rethrow + IR::BasicBlock *b = _function->newBasicBlock(); + _block->CJUMP(_block->TEMP(inCatch), finallyBody, b); + _block = b; + } + // if we have finally we need to clear the exception here, so we don't rethrow + move(_block->TEMP(inCatch), _block->CONST(IR::BoolType, true)); + move(_block->TEMP(hasException), _block->CONST(IR::BoolType, false)); + + const int exception = _block->newTemp(); + move(_block->TEMP(exception), _block->CALL(_block->NAME(IR::Name::builtin_get_exception, 0, 0), 0)); + + // the variable used in the catch statement is local and hides any global + // variable with the same name. + const Environment::Member undefinedMember = { Environment::UndefinedMember, -1 , 0 }; + const Environment::Member catchMember = { Environment::VariableDefinition, exception, 0 }; + Environment::Member m = _env->members.value(ast->catchExpression->name.toString(), undefinedMember); + _env->members.insert(ast->catchExpression->name.toString(), catchMember); + + statement(ast->catchExpression->statement); + + // reset the variable name to the one from the outer scope + if (m.type == Environment::UndefinedMember) + _env->members.remove(ast->catchExpression->name.toString()); + else + _env->members.insert(ast->catchExpression->name.toString(), m); + _block->JUMP(finallyBody); + } + + _scopeAndFinally = tcf.parent; + + IR::BasicBlock *after = _function->newBasicBlock(); + _block = finallyBody; + + _block->EXP(_block->CALL(_block->NAME(IR::Name::builtin_delete_exception_handler, 0, 0), deleteExceptionArgs)); + int exception_to_rethrow = _block->newTemp(); + move(_block->TEMP(exception_to_rethrow), _block->CALL(_block->NAME(IR::Name::builtin_get_exception, 0, 0), 0)); + + if (ast->finallyExpression && ast->finallyExpression->statement) + statement(ast->finallyExpression->statement); + + IR::BasicBlock *rethrowBlock = _function->newBasicBlock(); + _block->CJUMP(_block->TEMP(hasException), rethrowBlock, after); + _block = rethrowBlock; + move(_block->TEMP(_returnAddress), _block->TEMP(exception_to_rethrow)); + _block->JUMP(_throwBlock); + + _block = after; + + return false; +} + +void Codegen::unwindException(Codegen::ScopeAndFinally *outest) +{ + ScopeAndFinally *scopeAndFinally = _scopeAndFinally; + qSwap(_scopeAndFinally, scopeAndFinally); + while (_scopeAndFinally != outest) { + if (_scopeAndFinally->popScope) { + _block->EXP(_block->CALL(_block->NAME(IR::Name::builtin_pop_scope, 0, 0))); + _scopeAndFinally = _scopeAndFinally->parent; + } else { + _block->EXP(_block->CALL(_block->NAME(IR::Name::builtin_delete_exception_handler, 0, 0), _scopeAndFinally->deleteExceptionArgs)); + ScopeAndFinally *tc = _scopeAndFinally; + _scopeAndFinally = tc->parent; + if (tc->finally && tc->finally->statement) + statement(tc->finally->statement); + } + } + qSwap(_scopeAndFinally, scopeAndFinally); +} + +bool Codegen::visit(VariableStatement *ast) +{ + variableDeclarationList(ast->declarations); + return false; +} + +bool Codegen::visit(WhileStatement *ast) +{ + IR::BasicBlock *whilecond = _function->newBasicBlock(); + IR::BasicBlock *whilebody = _function->newBasicBlock(); + IR::BasicBlock *whileend = _function->newBasicBlock(); + + enterLoop(ast, whileend, whilecond); + + _block->JUMP(whilecond); + _block = whilecond; + condition(ast->expression, whilebody, whileend); + + _block = whilebody; + statement(ast->statement); + _block->JUMP(whilecond); + + _block = whileend; + leaveLoop(); + + return false; +} + +bool Codegen::visit(WithStatement *ast) +{ + IR::BasicBlock *withBlock = _function->newBasicBlock(); + + _block->JUMP(withBlock); + _block = withBlock; + int withObject = _block->newTemp(); + _block->MOVE(_block->TEMP(withObject), *expression(ast->expression)); + IR::ExprList *args = _function->New<IR::ExprList>(); + args->init(_block->TEMP(withObject)); + _block->EXP(_block->CALL(_block->NAME(IR::Name::builtin_push_with_scope, 0, 0), args)); + + ++_function->insideWith; + { + ScopeAndFinally scope(_scopeAndFinally); + _scopeAndFinally = &scope; + statement(ast->statement); + _scopeAndFinally = scope.parent; + } + --_function->insideWith; + _block->EXP(_block->CALL(_block->NAME(IR::Name::builtin_pop_scope, 0, 0), 0)); + + IR::BasicBlock *next = _function->newBasicBlock(); + _block->JUMP(next); + _block = next; + + return false; +} + +bool Codegen::visit(UiArrayBinding *) +{ + assert(!"not implemented"); + return false; +} + +bool Codegen::visit(UiObjectBinding *) +{ + assert(!"not implemented"); + return false; +} + +bool Codegen::visit(UiObjectDefinition *) +{ + assert(!"not implemented"); + return false; +} + +bool Codegen::visit(UiPublicMember *) +{ + assert(!"not implemented"); + return false; +} + +bool Codegen::visit(UiScriptBinding *) +{ + assert(!"not implemented"); + return false; +} + +bool Codegen::visit(UiSourceElement *) +{ + assert(!"not implemented"); + return false; +} + +void Codegen::throwSyntaxErrorOnEvalOrArgumentsInStrictMode(IR::Expr *expr, const SourceLocation& loc) +{ + if (!_env->isStrict) + return; + IR::Name *n = expr->asName(); + if (!n) + return; + if (*n->id == QLatin1String("eval") || *n->id == QLatin1String("arguments")) + throwSyntaxError(loc, QCoreApplication::translate("qv4codegen", "Variable name may not be eval or arguments in strict mode")); +} + +void Codegen::throwSyntaxError(const SourceLocation &loc, const QString &detail) +{ + VM::DiagnosticMessage *msg = new VM::DiagnosticMessage; + msg->fileName = _fileName; + msg->offset = loc.begin(); + msg->startLine = loc.startLine; + msg->startColumn = loc.startColumn; + msg->message = detail; + if (_context) + _context->throwSyntaxError(msg); + else if (_errorHandler) + _errorHandler->syntaxError(msg); + else + Q_ASSERT(!"No error handler available."); +} + +void Codegen::throwReferenceError(const SourceLocation &loc, const QString &detail) +{ + if (_context) + _context->throwReferenceError(VM::Value::fromString(_context, detail)); + else if (_errorHandler) + throwSyntaxError(loc, detail); + else + Q_ASSERT(!"No error handler available."); +} diff --git a/src/v4/qv4codegen_p.h b/src/v4/qv4codegen_p.h new file mode 100644 index 0000000000..d83e129782 --- /dev/null +++ b/src/v4/qv4codegen_p.h @@ -0,0 +1,421 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4CODEGEN_P_H +#define QV4CODEGEN_P_H + +#include "qv4global.h" +#include "qv4ir_p.h" +#include <private/qqmljsastvisitor_p.h> +#include <QtCore/QStringList> +#include <assert.h> + +namespace QQmlJS { + +namespace AST { +class UiParameterList; +} + +namespace VM { +struct DiagnosticMessage; +struct ExecutionContext; +} + +namespace Debugging { +class Debugger; +} // namespace Debugging + +class ErrorHandler +{ +public: + virtual void syntaxError(VM::DiagnosticMessage *message) = 0; +}; + +class Q_V4_EXPORT Codegen: protected AST::Visitor +{ +public: + Codegen(VM::ExecutionContext *ctx, bool strict); + Codegen(ErrorHandler *errorHandler, bool strictMode); + + enum Mode { + GlobalCode, + EvalCode, + FunctionCode + }; + + IR::Function *operator()(const QString &fileName, AST::Program *ast, IR::Module *module, Mode mode = GlobalCode, const QStringList &inheritedLocals = QStringList()); + IR::Function *operator()(const QString &fileName, AST::FunctionExpression *ast, IR::Module *module); + +protected: + enum Format { ex, cx, nx }; + struct Result { + IR::Expr *code; + IR::BasicBlock *iftrue; + IR::BasicBlock *iffalse; + Format format; + Format requested; + + explicit Result(Format requested = ex) + : code(0) + , iftrue(0) + , iffalse(0) + , format(ex) + , requested(requested) {} + + explicit Result(IR::BasicBlock *iftrue, IR::BasicBlock *iffalse) + : code(0) + , iftrue(iftrue) + , iffalse(iffalse) + , format(ex) + , requested(cx) {} + + inline IR::Expr *operator*() const { Q_ASSERT(format == ex); return code; } + inline IR::Expr *operator->() const { Q_ASSERT(format == ex); return code; } + + bool accept(Format f) + { + if (requested == f) { + format = f; + return true; + } + return false; + } + }; + + struct Environment { + Environment *parent; + + enum MemberType { + UndefinedMember, + VariableDefinition, + VariableDeclaration, + FunctionDefinition + }; + struct Member { + MemberType type; + int index; + AST::FunctionDeclaration *function; + }; + typedef QMap<QString, Member> MemberMap; + + MemberMap members; + int maxNumberOfArguments; + bool hasDirectEval; + bool hasNestedFunctions; + bool isStrict; + enum UsesArgumentsObject { + ArgumentsObjectUnknown, + ArgumentsObjectNotUsed, + ArgumentsObjectUsed + }; + + UsesArgumentsObject usesArgumentsObject; + + Environment(Environment *parent) + : parent(parent) + , maxNumberOfArguments(0) + , hasDirectEval(false) + , hasNestedFunctions(false) + , isStrict(false) + , usesArgumentsObject(ArgumentsObjectUnknown) + { + if (parent && parent->isStrict) + isStrict = true; + } + + int findMember(const QString &name) const + { + MemberMap::const_iterator it = members.find(name); + if (it == members.end()) + return -1; + assert((*it).index != -1 || !parent); + return (*it).index; + } + + bool lookupMember(const QString &name, Environment **scope, int *index, int *distance) + { + Environment *it = this; + *distance = 0; + for (; it; it = it->parent, ++(*distance)) { + int idx = it->findMember(name); + if (idx != -1) { + *scope = it; + *index = idx; + return true; + } + } + return false; + } + + void enter(const QString &name, MemberType type, AST::FunctionDeclaration *function = 0) + { + if (! name.isEmpty()) { + MemberMap::iterator it = members.find(name); + if (it == members.end()) { + Member m; + m.index = -1; + m.type = type; + m.function = function; + members.insert(name, m); + } else { + if ((*it).type <= type) { + (*it).type = type; + (*it).function = function; + } + } + } + } + }; + + Environment *newEnvironment(AST::Node *node, Environment *parent) + { + Environment *env = new Environment(parent); + _envMap.insert(node, env); + return env; + } + + struct UiMember { + }; + + struct ScopeAndFinally { + ScopeAndFinally *parent; + AST::Finally *finally; + IR::ExprList *deleteExceptionArgs; + bool popScope; + + ScopeAndFinally(ScopeAndFinally *parent) : parent(parent), finally(0), deleteExceptionArgs(0), popScope(true) {} + ScopeAndFinally(ScopeAndFinally *parent, AST::Finally *finally, IR::ExprList *deleteExceptionArgs) + : parent(parent), finally(finally), deleteExceptionArgs(deleteExceptionArgs), popScope(false) + {} + }; + + struct Loop { + AST::LabelledStatement *labelledStatement; + AST::Statement *node; + IR::BasicBlock *breakBlock; + IR::BasicBlock *continueBlock; + Loop *parent; + ScopeAndFinally *scopeAndFinally; + + Loop(AST::Statement *node, IR::BasicBlock *breakBlock, IR::BasicBlock *continueBlock, Loop *parent) + : labelledStatement(0), node(node), breakBlock(breakBlock), continueBlock(continueBlock), parent(parent) {} + }; + + void enterEnvironment(AST::Node *node); + void leaveEnvironment(); + + void enterLoop(AST::Statement *node, IR::BasicBlock *breakBlock, IR::BasicBlock *continueBlock); + void leaveLoop(); + + + IR::Expr *member(IR::Expr *base, const QString *name); + IR::Expr *subscript(IR::Expr *base, IR::Expr *index); + IR::Expr *argument(IR::Expr *expr); + IR::Expr *reference(IR::Expr *expr); + IR::Expr *unop(IR::AluOp op, IR::Expr *expr); + IR::Expr *binop(IR::AluOp op, IR::Expr *left, IR::Expr *right); + IR::Expr *call(IR::Expr *base, IR::ExprList *args); + void move(IR::Expr *target, IR::Expr *source, IR::AluOp op = IR::OpInvalid); + void cjump(IR::Expr *cond, IR::BasicBlock *iftrue, IR::BasicBlock *iffalse); + + void linearize(IR::Function *function); + IR::Function *defineFunction(const QString &name, AST::Node *ast, + AST::FormalParameterList *formals, + AST::SourceElements *body, + Mode mode = FunctionCode, + const QStringList &inheritedLocals = QStringList()); + int indexOfArgument(const QStringRef &string) const; + + void unwindException(ScopeAndFinally *outest); + + void statement(AST::Statement *ast); + void statement(AST::ExpressionNode *ast); + void condition(AST::ExpressionNode *ast, IR::BasicBlock *iftrue, IR::BasicBlock *iffalse); + Result expression(AST::ExpressionNode *ast); + QString propertyName(AST::PropertyName *ast); + Result sourceElement(AST::SourceElement *ast); + UiMember uiObjectMember(AST::UiObjectMember *ast); + + void accept(AST::Node *node); + + void functionBody(AST::FunctionBody *ast); + void program(AST::Program *ast); + void sourceElements(AST::SourceElements *ast); + void variableDeclaration(AST::VariableDeclaration *ast); + void variableDeclarationList(AST::VariableDeclarationList *ast); + + IR::Expr *identifier(const QString &name, int line = 0, int col = 0); + + // nodes + virtual bool visit(AST::ArgumentList *ast); + virtual bool visit(AST::CaseBlock *ast); + virtual bool visit(AST::CaseClause *ast); + virtual bool visit(AST::CaseClauses *ast); + virtual bool visit(AST::Catch *ast); + virtual bool visit(AST::DefaultClause *ast); + virtual bool visit(AST::ElementList *ast); + virtual bool visit(AST::Elision *ast); + virtual bool visit(AST::Finally *ast); + virtual bool visit(AST::FormalParameterList *ast); + virtual bool visit(AST::FunctionBody *ast); + virtual bool visit(AST::Program *ast); + virtual bool visit(AST::PropertyNameAndValue *ast); + virtual bool visit(AST::PropertyAssignmentList *ast); + virtual bool visit(AST::PropertyGetterSetter *ast); + virtual bool visit(AST::SourceElements *ast); + virtual bool visit(AST::StatementList *ast); + virtual bool visit(AST::UiArrayMemberList *ast); + virtual bool visit(AST::UiImport *ast); + virtual bool visit(AST::UiImportList *ast); + virtual bool visit(AST::UiObjectInitializer *ast); + virtual bool visit(AST::UiObjectMemberList *ast); + virtual bool visit(AST::UiParameterList *ast); + virtual bool visit(AST::UiProgram *ast); + virtual bool visit(AST::UiQualifiedId *ast); + virtual bool visit(AST::VariableDeclaration *ast); + virtual bool visit(AST::VariableDeclarationList *ast); + + // expressions + virtual bool visit(AST::Expression *ast); + virtual bool visit(AST::ArrayLiteral *ast); + virtual bool visit(AST::ArrayMemberExpression *ast); + virtual bool visit(AST::BinaryExpression *ast); + virtual bool visit(AST::CallExpression *ast); + virtual bool visit(AST::ConditionalExpression *ast); + virtual bool visit(AST::DeleteExpression *ast); + virtual bool visit(AST::FalseLiteral *ast); + virtual bool visit(AST::FieldMemberExpression *ast); + virtual bool visit(AST::FunctionExpression *ast); + virtual bool visit(AST::IdentifierExpression *ast); + virtual bool visit(AST::NestedExpression *ast); + virtual bool visit(AST::NewExpression *ast); + virtual bool visit(AST::NewMemberExpression *ast); + virtual bool visit(AST::NotExpression *ast); + virtual bool visit(AST::NullExpression *ast); + virtual bool visit(AST::NumericLiteral *ast); + virtual bool visit(AST::ObjectLiteral *ast); + virtual bool visit(AST::PostDecrementExpression *ast); + virtual bool visit(AST::PostIncrementExpression *ast); + virtual bool visit(AST::PreDecrementExpression *ast); + virtual bool visit(AST::PreIncrementExpression *ast); + virtual bool visit(AST::RegExpLiteral *ast); + virtual bool visit(AST::StringLiteral *ast); + virtual bool visit(AST::ThisExpression *ast); + virtual bool visit(AST::TildeExpression *ast); + virtual bool visit(AST::TrueLiteral *ast); + virtual bool visit(AST::TypeOfExpression *ast); + virtual bool visit(AST::UnaryMinusExpression *ast); + virtual bool visit(AST::UnaryPlusExpression *ast); + virtual bool visit(AST::VoidExpression *ast); + virtual bool visit(AST::FunctionDeclaration *ast); + + // property names + virtual bool visit(AST::IdentifierPropertyName *ast); + virtual bool visit(AST::NumericLiteralPropertyName *ast); + virtual bool visit(AST::StringLiteralPropertyName *ast); + + // source elements + virtual bool visit(AST::FunctionSourceElement *ast); + virtual bool visit(AST::StatementSourceElement *ast); + + // statements + virtual bool visit(AST::Block *ast); + virtual bool visit(AST::BreakStatement *ast); + virtual bool visit(AST::ContinueStatement *ast); + virtual bool visit(AST::DebuggerStatement *ast); + virtual bool visit(AST::DoWhileStatement *ast); + virtual bool visit(AST::EmptyStatement *ast); + virtual bool visit(AST::ExpressionStatement *ast); + virtual bool visit(AST::ForEachStatement *ast); + virtual bool visit(AST::ForStatement *ast); + virtual bool visit(AST::IfStatement *ast); + virtual bool visit(AST::LabelledStatement *ast); + virtual bool visit(AST::LocalForEachStatement *ast); + virtual bool visit(AST::LocalForStatement *ast); + virtual bool visit(AST::ReturnStatement *ast); + virtual bool visit(AST::SwitchStatement *ast); + virtual bool visit(AST::ThrowStatement *ast); + virtual bool visit(AST::TryStatement *ast); + virtual bool visit(AST::VariableStatement *ast); + virtual bool visit(AST::WhileStatement *ast); + virtual bool visit(AST::WithStatement *ast); + + // ui object members + virtual bool visit(AST::UiArrayBinding *ast); + virtual bool visit(AST::UiObjectBinding *ast); + virtual bool visit(AST::UiObjectDefinition *ast); + virtual bool visit(AST::UiPublicMember *ast); + virtual bool visit(AST::UiScriptBinding *ast); + virtual bool visit(AST::UiSourceElement *ast); + + void throwSyntaxErrorOnEvalOrArgumentsInStrictMode(IR::Expr* expr, const AST::SourceLocation &loc); + + void throwSyntaxError(const AST::SourceLocation &loc, const QString &detail); + void throwReferenceError(const AST::SourceLocation &loc, const QString &detail); + +private: + QString _fileName; + Result _expr; + QString _property; + UiMember _uiMember; + IR::Module *_module; + IR::Function *_function; + IR::BasicBlock *_block; + IR::BasicBlock *_exitBlock; + IR::BasicBlock *_throwBlock; + unsigned _returnAddress; + Mode _mode; + Environment *_env; + Loop *_loop; + AST::LabelledStatement *_labelledStatement; + ScopeAndFinally *_scopeAndFinally; + QHash<AST::Node *, Environment *> _envMap; + QHash<AST::FunctionExpression *, int> _functionMap; + VM::ExecutionContext *_context; + bool _strictMode; + Debugging::Debugger *_debugger; + ErrorHandler *_errorHandler; + + class ScanFunctions; +}; + +} // end of namespace QQmlJS + +#endif // QV4CODEGEN_P_H diff --git a/src/v4/qv4dateobject.cpp b/src/v4/qv4dateobject.cpp new file mode 100644 index 0000000000..b9a4446087 --- /dev/null +++ b/src/v4/qv4dateobject.cpp @@ -0,0 +1,1313 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "qv4dateobject.h" +#include "qv4objectproto.h" +#include "qv4mm.h" +#include <QtCore/qnumeric.h> +#include <QtCore/qmath.h> +#include <QtCore/QDateTime> +#include <QtCore/QStringList> +#include <QtCore/QDebug> +#include <cmath> +#include <qmath.h> +#include <qnumeric.h> +#include <cassert> + +#include <private/qqmljsengine_p.h> +#include <private/qqmljslexer_p.h> +#include <private/qqmljsparser_p.h> +#include <private/qqmljsast_p.h> +#include <qv4ir_p.h> +#include <qv4codegen_p.h> +#include <qv4isel_masm_p.h> + +#ifndef Q_WS_WIN +# include <time.h> +# ifndef Q_OS_VXWORKS +# include <sys/time.h> +# else +# include "qplatformdefs.h" +# endif +#else +# include <windows.h> +#endif + +using namespace QQmlJS::VM; + +static const double HoursPerDay = 24.0; +static const double MinutesPerHour = 60.0; +static const double SecondsPerMinute = 60.0; +static const double msPerSecond = 1000.0; +static const double msPerMinute = 60000.0; +static const double msPerHour = 3600000.0; +static const double msPerDay = 86400000.0; + +static double LocalTZA = 0.0; // initialized at startup + +static inline double TimeWithinDay(double t) +{ + double r = ::fmod(t, msPerDay); + return (r >= 0) ? r : r + msPerDay; +} + +static inline int HourFromTime(double t) +{ + int r = int(::fmod(::floor(t / msPerHour), HoursPerDay)); + return (r >= 0) ? r : r + int(HoursPerDay); +} + +static inline int MinFromTime(double t) +{ + int r = int(::fmod(::floor(t / msPerMinute), MinutesPerHour)); + return (r >= 0) ? r : r + int(MinutesPerHour); +} + +static inline int SecFromTime(double t) +{ + int r = int(::fmod(::floor(t / msPerSecond), SecondsPerMinute)); + return (r >= 0) ? r : r + int(SecondsPerMinute); +} + +static inline int msFromTime(double t) +{ + int r = int(::fmod(t, msPerSecond)); + return (r >= 0) ? r : r + int(msPerSecond); +} + +static inline double Day(double t) +{ + return ::floor(t / msPerDay); +} + +static inline double DaysInYear(double y) +{ + if (::fmod(y, 4)) + return 365; + + else if (::fmod(y, 100)) + return 366; + + else if (::fmod(y, 400)) + return 365; + + return 366; +} + +static inline double DayFromYear(double y) +{ + return 365 * (y - 1970) + + ::floor((y - 1969) / 4) + - ::floor((y - 1901) / 100) + + ::floor((y - 1601) / 400); +} + +static inline double TimeFromYear(double y) +{ + return msPerDay * DayFromYear(y); +} + +static inline double YearFromTime(double t) +{ + int y = 1970; + y += (int) ::floor(t / (msPerDay * 365.2425)); + + double t2 = TimeFromYear(y); + return (t2 > t) ? y - 1 : ((t2 + msPerDay * DaysInYear(y)) <= t) ? y + 1 : y; +} + +static inline bool InLeapYear(double t) +{ + double x = DaysInYear(YearFromTime(t)); + if (x == 365) + return 0; + + assert(x == 366); + return 1; +} + +static inline double DayWithinYear(double t) +{ + return Day(t) - DayFromYear(YearFromTime(t)); +} + +static inline double MonthFromTime(double t) +{ + double d = DayWithinYear(t); + double l = InLeapYear(t); + + if (d < 31.0) + return 0; + + else if (d < 59.0 + l) + return 1; + + else if (d < 90.0 + l) + return 2; + + else if (d < 120.0 + l) + return 3; + + else if (d < 151.0 + l) + return 4; + + else if (d < 181.0 + l) + return 5; + + else if (d < 212.0 + l) + return 6; + + else if (d < 243.0 + l) + return 7; + + else if (d < 273.0 + l) + return 8; + + else if (d < 304.0 + l) + return 9; + + else if (d < 334.0 + l) + return 10; + + else if (d < 365.0 + l) + return 11; + + return qSNaN(); // ### assert? +} + +static inline double DateFromTime(double t) +{ + int m = (int) Value::toInteger(MonthFromTime(t)); + double d = DayWithinYear(t); + double l = InLeapYear(t); + + switch (m) { + case 0: return d + 1.0; + case 1: return d - 30.0; + case 2: return d - 58.0 - l; + case 3: return d - 89.0 - l; + case 4: return d - 119.0 - l; + case 5: return d - 150.0 - l; + case 6: return d - 180.0 - l; + case 7: return d - 211.0 - l; + case 8: return d - 242.0 - l; + case 9: return d - 272.0 - l; + case 10: return d - 303.0 - l; + case 11: return d - 333.0 - l; + } + + return qSNaN(); // ### assert +} + +static inline double WeekDay(double t) +{ + double r = ::fmod (Day(t) + 4.0, 7.0); + return (r >= 0) ? r : r + 7.0; +} + + +static inline double MakeTime(double hour, double min, double sec, double ms) +{ + return ((hour * MinutesPerHour + min) * SecondsPerMinute + sec) * msPerSecond + ms; +} + +static inline double DayFromMonth(double month, double leap) +{ + switch ((int) month) { + case 0: return 0; + case 1: return 31.0; + case 2: return 59.0 + leap; + case 3: return 90.0 + leap; + case 4: return 120.0 + leap; + case 5: return 151.0 + leap; + case 6: return 181.0 + leap; + case 7: return 212.0 + leap; + case 8: return 243.0 + leap; + case 9: return 273.0 + leap; + case 10: return 304.0 + leap; + case 11: return 334.0 + leap; + } + + return qSNaN(); // ### assert? +} + +static double MakeDay(double year, double month, double day) +{ + year += ::floor(month / 12.0); + + month = ::fmod(month, 12.0); + if (month < 0) + month += 12.0; + + double d = DayFromYear(year); + bool leap = InLeapYear(d*msPerDay); + + d += DayFromMonth(month, leap); + d += day - 1; + + return d; +} + +static inline double MakeDate(double day, double time) +{ + return day * msPerDay + time; +} + +static inline double DaylightSavingTA(double t) +{ +#ifndef Q_WS_WIN + long int tt = (long int)(t / msPerSecond); + struct tm tmtm; + if (!localtime_r((const time_t*)&tt, &tmtm)) + return 0; + return (tmtm.tm_isdst > 0) ? msPerHour : 0; +#else + Q_UNUSED(t); + /// ### implement me + return 0; +#endif +} + +static inline double LocalTime(double t) +{ + return t + LocalTZA + DaylightSavingTA(t); +} + +static inline double UTC(double t) +{ + return t - LocalTZA - DaylightSavingTA(t - LocalTZA); +} + +static inline double currentTime() +{ +#ifndef Q_WS_WIN + struct timeval tv; + + gettimeofday(&tv, 0); + return ::floor(tv.tv_sec * msPerSecond + (tv.tv_usec / 1000.0)); +#else + SYSTEMTIME st; + GetSystemTime(&st); + FILETIME ft; + SystemTimeToFileTime(&st, &ft); + LARGE_INTEGER li; + li.LowPart = ft.dwLowDateTime; + li.HighPart = ft.dwHighDateTime; + return double(li.QuadPart - Q_INT64_C(116444736000000000)) / 10000.0; +#endif +} + +static inline double TimeClip(double t) +{ + if (! qIsFinite(t) || fabs(t) > 8.64e15) + return qSNaN(); + return Value::toInteger(t); +} + +static inline double FromDateTime(const QDateTime &dt) +{ + if (!dt.isValid()) + return qSNaN(); + QDate date = dt.date(); + QTime taim = dt.time(); + int year = date.year(); + int month = date.month() - 1; + int day = date.day(); + int hours = taim.hour(); + int mins = taim.minute(); + int secs = taim.second(); + int ms = taim.msec(); + double t = MakeDate(MakeDay(year, month, day), + MakeTime(hours, mins, secs, ms)); + if (dt.timeSpec() == Qt::LocalTime) + t = UTC(t); + return TimeClip(t); +} + +static inline double ParseString(const QString &s) +{ + // first try the format defined in 15.9.1.15, only if that fails fall back to + // QDateTime for parsing + + // the define string format is YYYY-MM-DDTHH:mm:ss.sssZ + // It can be date or time only, and the second and later components + // of both fields are optional + // and extended syntax for negative and large positive years exists: +/-YYYYYY + + enum Format { + Year, + Month, + Day, + Hour, + Minute, + Second, + MilliSecond, + TimezoneHour, + TimezoneMinute, + Done + }; + + const QChar *ch = s.constData(); + const QChar *end = ch + s.length(); + + uint format = Year; + int current = 0; + int currentSize = 0; + bool extendedYear = false; + + int yearSign = 1; + int year = 0; + int month = 0; + int day = 1; + int hour = 0; + int minute = 0; + int second = 0; + int msec = 0; + int offsetSign = 1; + int offset = 0; + + bool error = false; + if (*ch == '+' || *ch == '-') { + extendedYear = true; + if (*ch == '-') + yearSign = -1; + ++ch; + } + while (ch <= end) { + if (*ch >= '0' && *ch <= '9') { + current *= 10; + current += ch->unicode() - '0'; + ++currentSize; + } else { // other char, delimits field + switch (format) { + case Year: + year = current; + if (extendedYear) + error = (currentSize != 6); + else + error = (currentSize != 4); + break; + case Month: + month = current - 1; + error = (currentSize != 2) || month > 11; + break; + case Day: + day = current; + error = (currentSize != 2) || day > 31; + break; + case Hour: + hour = current; + error = (currentSize != 2) || hour > 24; + break; + case Minute: + minute = current; + error = (currentSize != 2) || minute > 60; + break; + case Second: + second = current; + error = (currentSize != 2) || second > 60; + break; + case MilliSecond: + msec = current; + error = (currentSize != 3); + break; + case TimezoneHour: + offset = current*60; + error = (currentSize != 2) || offset > 23*60; + break; + case TimezoneMinute: + offset += current; + error = (currentSize != 2) || current >= 60; + break; + } + if (*ch == 'T') { + if (format >= Hour) + error = true; + format = Hour; + } else if (*ch == '-') { + if (format < Day) + ++format; + else if (format < Minute) + error = true; + else if (format >= TimezoneHour) + error = true; + else { + offsetSign = -1; + format = TimezoneHour; + } + } else if (*ch == ':') { + if (format != Hour && format != Minute && format != TimezoneHour) + error = true; + ++format; + } else if (*ch == '.') { + if (format != Second) + error = true; + ++format; + } else if (*ch == '+') { + if (format < Minute || format >= TimezoneHour) + error = true; + format = TimezoneHour; + } else if (*ch == 'Z' || *ch == 0) { + format = Done; + } + current = 0; + currentSize = 0; + } + if (error || format == Done) + break; + ++ch; + } + + if (!error) { + double t = MakeDate(MakeDay(year * yearSign, month, day), MakeTime(hour, minute, second, msec)); + t += offset * offsetSign * 60 * 1000; + return t; + } + + QDateTime dt = QDateTime::fromString(s, Qt::TextDate); + if (!dt.isValid()) + dt = QDateTime::fromString(s, Qt::ISODate); + if (!dt.isValid()) { + QStringList formats; + formats << QStringLiteral("M/d/yyyy") + << QStringLiteral("M/d/yyyy hh:mm") + << QStringLiteral("M/d/yyyy hh:mm A") + + << QStringLiteral("M/d/yyyy, hh:mm") + << QStringLiteral("M/d/yyyy, hh:mm A") + + << QStringLiteral("MMM d yyyy") + << QStringLiteral("MMM d yyyy hh:mm") + << QStringLiteral("MMM d yyyy hh:mm:ss") + << QStringLiteral("MMM d yyyy, hh:mm") + << QStringLiteral("MMM d yyyy, hh:mm:ss") + + << QStringLiteral("MMMM d yyyy") + << QStringLiteral("MMMM d yyyy hh:mm") + << QStringLiteral("MMMM d yyyy hh:mm:ss") + << QStringLiteral("MMMM d yyyy, hh:mm") + << QStringLiteral("MMMM d yyyy, hh:mm:ss") + + << QStringLiteral("MMM d, yyyy") + << QStringLiteral("MMM d, yyyy hh:mm") + << QStringLiteral("MMM d, yyyy hh:mm:ss") + + << QStringLiteral("MMMM d, yyyy") + << QStringLiteral("MMMM d, yyyy hh:mm") + << QStringLiteral("MMMM d, yyyy hh:mm:ss") + + << QStringLiteral("d MMM yyyy") + << QStringLiteral("d MMM yyyy hh:mm") + << QStringLiteral("d MMM yyyy hh:mm:ss") + << QStringLiteral("d MMM yyyy, hh:mm") + << QStringLiteral("d MMM yyyy, hh:mm:ss") + + << QStringLiteral("d MMMM yyyy") + << QStringLiteral("d MMMM yyyy hh:mm") + << QStringLiteral("d MMMM yyyy hh:mm:ss") + << QStringLiteral("d MMMM yyyy, hh:mm") + << QStringLiteral("d MMMM yyyy, hh:mm:ss") + + << QStringLiteral("d MMM, yyyy") + << QStringLiteral("d MMM, yyyy hh:mm") + << QStringLiteral("d MMM, yyyy hh:mm:ss") + + << QStringLiteral("d MMMM, yyyy") + << QStringLiteral("d MMMM, yyyy hh:mm") + << QStringLiteral("d MMMM, yyyy hh:mm:ss"); + + for (int i = 0; i < formats.size(); ++i) { + dt = QDateTime::fromString(s, formats.at(i)); + if (dt.isValid()) + break; + } + } + return FromDateTime(dt); +} + +/*! + \internal + + Converts the ECMA Date value \tt (in UTC form) to QDateTime + according to \a spec. +*/ +static inline QDateTime ToDateTime(double t, Qt::TimeSpec spec) +{ + if (std::isnan(t)) + return QDateTime(); + if (spec == Qt::LocalTime) + t = LocalTime(t); + int year = int(YearFromTime(t)); + int month = int(MonthFromTime(t) + 1); + int day = int(DateFromTime(t)); + int hours = HourFromTime(t); + int mins = MinFromTime(t); + int secs = SecFromTime(t); + int ms = msFromTime(t); + return QDateTime(QDate(year, month, day), QTime(hours, mins, secs, ms), spec); +} + +static inline QString ToString(double t) +{ + if (std::isnan(t)) + return QStringLiteral("Invalid Date"); + QString str = ToDateTime(t, Qt::LocalTime).toString() + QStringLiteral(" GMT"); + double tzoffset = LocalTZA + DaylightSavingTA(t); + if (tzoffset) { + int hours = static_cast<int>(::fabs(tzoffset) / 1000 / 60 / 60); + int mins = int(::fabs(tzoffset) / 1000 / 60) % 60; + str.append(QLatin1Char((tzoffset > 0) ? '+' : '-')); + if (hours < 10) + str.append(QLatin1Char('0')); + str.append(QString::number(hours)); + if (mins < 10) + str.append(QLatin1Char('0')); + str.append(QString::number(mins)); + } + return str; +} + +static inline QString ToUTCString(double t) +{ + if (std::isnan(t)) + return QStringLiteral("Invalid Date"); + return ToDateTime(t, Qt::UTC).toString() + QStringLiteral(" GMT"); +} + +static inline QString ToDateString(double t) +{ + return ToDateTime(t, Qt::LocalTime).date().toString(); +} + +static inline QString ToTimeString(double t) +{ + return ToDateTime(t, Qt::LocalTime).time().toString(); +} + +static inline QString ToLocaleString(double t) +{ + return ToDateTime(t, Qt::LocalTime).toString(Qt::LocaleDate); +} + +static inline QString ToLocaleDateString(double t) +{ + return ToDateTime(t, Qt::LocalTime).date().toString(Qt::LocaleDate); +} + +static inline QString ToLocaleTimeString(double t) +{ + return ToDateTime(t, Qt::LocalTime).time().toString(Qt::LocaleDate); +} + +static double getLocalTZA() +{ +#ifndef Q_WS_WIN + struct tm t; + time_t curr; + time(&curr); + localtime_r(&curr, &t); + time_t locl = mktime(&t); + gmtime_r(&curr, &t); + time_t globl = mktime(&t); + return double(locl - globl) * 1000.0; +#else + TIME_ZONE_INFORMATION tzInfo; + GetTimeZoneInformation(&tzInfo); + return -tzInfo.Bias * 60.0 * 1000.0; +#endif +} + + +DateCtor::DateCtor(ExecutionContext *scope) + : FunctionObject(scope) +{ +} + +Value DateCtor::construct(ExecutionContext *ctx) +{ + double t = 0; + + if (ctx->argumentCount == 0) + t = currentTime(); + + else if (ctx->argumentCount == 1) { + Value arg = ctx->argument(0); + if (DateObject *d = arg.asDateObject()) + arg = d->value; + else + arg = __qmljs_to_primitive(arg, ctx, PREFERREDTYPE_HINT); + + if (arg.isString()) + t = ParseString(arg.stringValue()->toQString()); + else + t = TimeClip(arg.toNumber(ctx)); + } + + else { // ctx->argumentCount > 1 + double year = ctx->argument(0).toNumber(ctx); + double month = ctx->argument(1).toNumber(ctx); + double day = ctx->argumentCount >= 3 ? ctx->argument(2).toNumber(ctx) : 1; + double hours = ctx->argumentCount >= 4 ? ctx->argument(3).toNumber(ctx) : 0; + double mins = ctx->argumentCount >= 5 ? ctx->argument(4).toNumber(ctx) : 0; + double secs = ctx->argumentCount >= 6 ? ctx->argument(5).toNumber(ctx) : 0; + double ms = ctx->argumentCount >= 7 ? ctx->argument(6).toNumber(ctx) : 0; + if (year >= 0 && year <= 99) + year += 1900; + t = MakeDate(MakeDay(year, month, day), MakeTime(hours, mins, secs, ms)); + t = TimeClip(UTC(t)); + } + + Object *d = ctx->engine->newDateObject(Value::fromDouble(t)); + return Value::fromObject(d); +} + +Value DateCtor::call(ExecutionContext *ctx) +{ + double t = currentTime(); + return Value::fromString(ctx, ToString(t)); +} + +void DatePrototype::init(ExecutionContext *ctx, const Value &ctor) +{ + ctor.objectValue()->defineReadonlyProperty(ctx->engine->id_prototype, Value::fromObject(this)); + ctor.objectValue()->defineReadonlyProperty(ctx->engine->id_length, Value::fromInt32(7)); + LocalTZA = getLocalTZA(); + + ctor.objectValue()->defineDefaultProperty(ctx, QStringLiteral("parse"), method_parse, 1); + ctor.objectValue()->defineDefaultProperty(ctx, QStringLiteral("UTC"), method_UTC, 7); + ctor.objectValue()->defineDefaultProperty(ctx, QStringLiteral("now"), method_now, 0); + + defineDefaultProperty(ctx, QStringLiteral("constructor"), ctor); + defineDefaultProperty(ctx, QStringLiteral("toString"), method_toString, 0); + defineDefaultProperty(ctx, QStringLiteral("toDateString"), method_toDateString, 0); + defineDefaultProperty(ctx, QStringLiteral("toTimeString"), method_toTimeString, 0); + defineDefaultProperty(ctx, QStringLiteral("toLocaleString"), method_toLocaleString, 0); + defineDefaultProperty(ctx, QStringLiteral("toLocaleDateString"), method_toLocaleDateString, 0); + defineDefaultProperty(ctx, QStringLiteral("toLocaleTimeString"), method_toLocaleTimeString, 0); + defineDefaultProperty(ctx, QStringLiteral("valueOf"), method_valueOf, 0); + defineDefaultProperty(ctx, QStringLiteral("getTime"), method_getTime, 0); + defineDefaultProperty(ctx, QStringLiteral("getYear"), method_getYear, 0); + defineDefaultProperty(ctx, QStringLiteral("getFullYear"), method_getFullYear, 0); + defineDefaultProperty(ctx, QStringLiteral("getUTCFullYear"), method_getUTCFullYear, 0); + defineDefaultProperty(ctx, QStringLiteral("getMonth"), method_getMonth, 0); + defineDefaultProperty(ctx, QStringLiteral("getUTCMonth"), method_getUTCMonth, 0); + defineDefaultProperty(ctx, QStringLiteral("getDate"), method_getDate, 0); + defineDefaultProperty(ctx, QStringLiteral("getUTCDate"), method_getUTCDate, 0); + defineDefaultProperty(ctx, QStringLiteral("getDay"), method_getDay, 0); + defineDefaultProperty(ctx, QStringLiteral("getUTCDay"), method_getUTCDay, 0); + defineDefaultProperty(ctx, QStringLiteral("getHours"), method_getHours, 0); + defineDefaultProperty(ctx, QStringLiteral("getUTCHours"), method_getUTCHours, 0); + defineDefaultProperty(ctx, QStringLiteral("getMinutes"), method_getMinutes, 0); + defineDefaultProperty(ctx, QStringLiteral("getUTCMinutes"), method_getUTCMinutes, 0); + defineDefaultProperty(ctx, QStringLiteral("getSeconds"), method_getSeconds, 0); + defineDefaultProperty(ctx, QStringLiteral("getUTCSeconds"), method_getUTCSeconds, 0); + defineDefaultProperty(ctx, QStringLiteral("getMilliseconds"), method_getMilliseconds, 0); + defineDefaultProperty(ctx, QStringLiteral("getUTCMilliseconds"), method_getUTCMilliseconds, 0); + defineDefaultProperty(ctx, QStringLiteral("getTimezoneOffset"), method_getTimezoneOffset, 0); + defineDefaultProperty(ctx, QStringLiteral("setTime"), method_setTime, 1); + defineDefaultProperty(ctx, QStringLiteral("setMilliseconds"), method_setMilliseconds, 1); + defineDefaultProperty(ctx, QStringLiteral("setUTCMilliseconds"), method_setUTCMilliseconds, 1); + defineDefaultProperty(ctx, QStringLiteral("setSeconds"), method_setSeconds, 2); + defineDefaultProperty(ctx, QStringLiteral("setUTCSeconds"), method_setUTCSeconds, 2); + defineDefaultProperty(ctx, QStringLiteral("setMinutes"), method_setMinutes, 3); + defineDefaultProperty(ctx, QStringLiteral("setUTCMinutes"), method_setUTCMinutes, 3); + defineDefaultProperty(ctx, QStringLiteral("setHours"), method_setHours, 4); + defineDefaultProperty(ctx, QStringLiteral("setUTCHours"), method_setUTCHours, 4); + defineDefaultProperty(ctx, QStringLiteral("setDate"), method_setDate, 1); + defineDefaultProperty(ctx, QStringLiteral("setUTCDate"), method_setUTCDate, 1); + defineDefaultProperty(ctx, QStringLiteral("setMonth"), method_setMonth, 2); + defineDefaultProperty(ctx, QStringLiteral("setUTCMonth"), method_setUTCMonth, 2); + defineDefaultProperty(ctx, QStringLiteral("setYear"), method_setYear, 1); + defineDefaultProperty(ctx, QStringLiteral("setFullYear"), method_setFullYear, 3); + defineDefaultProperty(ctx, QStringLiteral("setUTCFullYear"), method_setUTCFullYear, 3); + defineDefaultProperty(ctx, QStringLiteral("toUTCString"), method_toUTCString, 0); + defineDefaultProperty(ctx, QStringLiteral("toGMTString"), method_toUTCString, 0); + defineDefaultProperty(ctx, QStringLiteral("toISOString"), method_toISOString, 0); + defineDefaultProperty(ctx, QStringLiteral("toJSON"), method_toJSON, 1); +} + +double DatePrototype::getThisDate(ExecutionContext *ctx) +{ + if (DateObject *thisObject = ctx->thisObject.asDateObject()) + return thisObject->value.asDouble(); + else { + ctx->throwTypeError(); + return 0; + } +} + +Value DatePrototype::method_parse(ExecutionContext *ctx) +{ + return Value::fromDouble(ParseString(ctx->argument(0).toString(ctx)->toQString())); +} + +Value DatePrototype::method_UTC(ExecutionContext *ctx) +{ + const int numArgs = ctx->argumentCount; + if (numArgs >= 2) { + double year = ctx->argument(0).toNumber(ctx); + double month = ctx->argument(1).toNumber(ctx); + double day = numArgs >= 3 ? ctx->argument(2).toNumber(ctx) : 1; + double hours = numArgs >= 4 ? ctx->argument(3).toNumber(ctx) : 0; + double mins = numArgs >= 5 ? ctx->argument(4).toNumber(ctx) : 0; + double secs = numArgs >= 6 ? ctx->argument(5).toNumber(ctx) : 0; + double ms = numArgs >= 7 ? ctx->argument(6).toNumber(ctx) : 0; + if (year >= 0 && year <= 99) + year += 1900; + double t = MakeDate(MakeDay(year, month, day), + MakeTime(hours, mins, secs, ms)); + return Value::fromDouble(TimeClip(t)); + } + return Value::undefinedValue(); +} + +Value DatePrototype::method_now(ExecutionContext *ctx) +{ + Q_UNUSED(ctx); + double t = currentTime(); + return Value::fromDouble(t); +} + +Value DatePrototype::method_toString(ExecutionContext *ctx) +{ + double t = getThisDate(ctx); + return Value::fromString(ctx, ToString(t)); +} + +Value DatePrototype::method_toDateString(ExecutionContext *ctx) +{ + double t = getThisDate(ctx); + return Value::fromString(ctx, ToDateString(t)); +} + +Value DatePrototype::method_toTimeString(ExecutionContext *ctx) +{ + double t = getThisDate(ctx); + return Value::fromString(ctx, ToTimeString(t)); +} + +Value DatePrototype::method_toLocaleString(ExecutionContext *ctx) +{ + double t = getThisDate(ctx); + return Value::fromString(ctx, ToLocaleString(t)); +} + +Value DatePrototype::method_toLocaleDateString(ExecutionContext *ctx) +{ + double t = getThisDate(ctx); + return Value::fromString(ctx, ToLocaleDateString(t)); +} + +Value DatePrototype::method_toLocaleTimeString(ExecutionContext *ctx) +{ + double t = getThisDate(ctx); + return Value::fromString(ctx, ToLocaleTimeString(t)); +} + +Value DatePrototype::method_valueOf(ExecutionContext *ctx) +{ + double t = getThisDate(ctx); + return Value::fromDouble(t); +} + +Value DatePrototype::method_getTime(ExecutionContext *ctx) +{ + double t = getThisDate(ctx); + return Value::fromDouble(t); +} + +Value DatePrototype::method_getYear(ExecutionContext *ctx) +{ + double t = getThisDate(ctx); + if (! std::isnan(t)) + t = YearFromTime(LocalTime(t)) - 1900; + return Value::fromDouble(t); +} + +Value DatePrototype::method_getFullYear(ExecutionContext *ctx) +{ + double t = getThisDate(ctx); + if (! std::isnan(t)) + t = YearFromTime(LocalTime(t)); + return Value::fromDouble(t); +} + +Value DatePrototype::method_getUTCFullYear(ExecutionContext *ctx) +{ + double t = getThisDate(ctx); + if (! std::isnan(t)) + t = YearFromTime(t); + return Value::fromDouble(t); +} + +Value DatePrototype::method_getMonth(ExecutionContext *ctx) +{ + double t = getThisDate(ctx); + if (! std::isnan(t)) + t = MonthFromTime(LocalTime(t)); + return Value::fromDouble(t); +} + +Value DatePrototype::method_getUTCMonth(ExecutionContext *ctx) +{ + double t = getThisDate(ctx); + if (! std::isnan(t)) + t = MonthFromTime(t); + return Value::fromDouble(t); +} + +Value DatePrototype::method_getDate(ExecutionContext *ctx) +{ + double t = getThisDate(ctx); + if (! std::isnan(t)) + t = DateFromTime(LocalTime(t)); + return Value::fromDouble(t); +} + +Value DatePrototype::method_getUTCDate(ExecutionContext *ctx) +{ + double t = getThisDate(ctx); + if (! std::isnan(t)) + t = DateFromTime(t); + return Value::fromDouble(t); +} + +Value DatePrototype::method_getDay(ExecutionContext *ctx) +{ + double t = getThisDate(ctx); + if (! std::isnan(t)) + t = WeekDay(LocalTime(t)); + return Value::fromDouble(t); +} + +Value DatePrototype::method_getUTCDay(ExecutionContext *ctx) +{ + double t = getThisDate(ctx); + if (! std::isnan(t)) + t = WeekDay(t); + return Value::fromDouble(t); +} + +Value DatePrototype::method_getHours(ExecutionContext *ctx) +{ + double t = getThisDate(ctx); + if (! std::isnan(t)) + t = HourFromTime(LocalTime(t)); + return Value::fromDouble(t); +} + +Value DatePrototype::method_getUTCHours(ExecutionContext *ctx) +{ + double t = getThisDate(ctx); + if (! std::isnan(t)) + t = HourFromTime(t); + return Value::fromDouble(t); +} + +Value DatePrototype::method_getMinutes(ExecutionContext *ctx) +{ + double t = getThisDate(ctx); + if (! std::isnan(t)) + t = MinFromTime(LocalTime(t)); + return Value::fromDouble(t); +} + +Value DatePrototype::method_getUTCMinutes(ExecutionContext *ctx) +{ + double t = getThisDate(ctx); + if (! std::isnan(t)) + t = MinFromTime(t); + return Value::fromDouble(t); +} + +Value DatePrototype::method_getSeconds(ExecutionContext *ctx) +{ + double t = getThisDate(ctx); + if (! std::isnan(t)) + t = SecFromTime(LocalTime(t)); + return Value::fromDouble(t); +} + +Value DatePrototype::method_getUTCSeconds(ExecutionContext *ctx) +{ + double t = getThisDate(ctx); + if (! std::isnan(t)) + t = SecFromTime(t); + return Value::fromDouble(t); +} + +Value DatePrototype::method_getMilliseconds(ExecutionContext *ctx) +{ + double t = getThisDate(ctx); + if (! std::isnan(t)) + t = msFromTime(LocalTime(t)); + return Value::fromDouble(t); +} + +Value DatePrototype::method_getUTCMilliseconds(ExecutionContext *ctx) +{ + double t = getThisDate(ctx); + if (! std::isnan(t)) + t = msFromTime(t); + return Value::fromDouble(t); +} + +Value DatePrototype::method_getTimezoneOffset(ExecutionContext *ctx) +{ + double t = getThisDate(ctx); + if (! std::isnan(t)) + t = (t - LocalTime(t)) / msPerMinute; + return Value::fromDouble(t); +} + +Value DatePrototype::method_setTime(ExecutionContext *ctx) +{ + DateObject *self = ctx->thisObject.asDateObject(); + if (!self) + ctx->throwTypeError(); + + self->value.setDouble(TimeClip(ctx->argument(0).toNumber(ctx))); + return self->value; +} + +Value DatePrototype::method_setMilliseconds(ExecutionContext *ctx) +{ + DateObject *self = ctx->thisObject.asDateObject(); + if (!self) + ctx->throwTypeError(); + + double t = LocalTime(self->value.asDouble()); + double ms = ctx->argument(0).toNumber(ctx); + self->value.setDouble(TimeClip(UTC(MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), ms))))); + return self->value; +} + +Value DatePrototype::method_setUTCMilliseconds(ExecutionContext *ctx) +{ + DateObject *self = ctx->thisObject.asDateObject(); + if (!self) + ctx->throwTypeError(); + + double t = self->value.asDouble(); + double ms = ctx->argument(0).toNumber(ctx); + self->value.setDouble(TimeClip(UTC(MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), ms))))); + return self->value; +} + +Value DatePrototype::method_setSeconds(ExecutionContext *ctx) +{ + DateObject *self = ctx->thisObject.asDateObject(); + if (!self) + ctx->throwTypeError(); + + double t = LocalTime(self->value.asDouble()); + double sec = ctx->argument(0).toNumber(ctx); + double ms = (ctx->argumentCount < 2) ? msFromTime(t) : ctx->argument(1).toNumber(ctx); + t = TimeClip(UTC(MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), sec, ms)))); + self->value.setDouble(t); + return self->value; +} + +Value DatePrototype::method_setUTCSeconds(ExecutionContext *ctx) +{ + DateObject *self = ctx->thisObject.asDateObject(); + if (!self) + ctx->throwTypeError(); + + double t = self->value.asDouble(); + double sec = ctx->argument(0).toNumber(ctx); + double ms = (ctx->argumentCount < 2) ? msFromTime(t) : ctx->argument(1).toNumber(ctx); + t = TimeClip(UTC(MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), sec, ms)))); + self->value.setDouble(t); + return self->value; +} + +Value DatePrototype::method_setMinutes(ExecutionContext *ctx) +{ + DateObject *self = ctx->thisObject.asDateObject(); + if (!self) + ctx->throwTypeError(); + + double t = LocalTime(self->value.asDouble()); + double min = ctx->argument(0).toNumber(ctx); + double sec = (ctx->argumentCount < 2) ? SecFromTime(t) : ctx->argument(1).toNumber(ctx); + double ms = (ctx->argumentCount < 3) ? msFromTime(t) : ctx->argument(2).toNumber(ctx); + t = TimeClip(UTC(MakeDate(Day(t), MakeTime(HourFromTime(t), min, sec, ms)))); + self->value.setDouble(t); + return self->value; +} + +Value DatePrototype::method_setUTCMinutes(ExecutionContext *ctx) +{ + DateObject *self = ctx->thisObject.asDateObject(); + if (!self) + ctx->throwTypeError(); + + double t = self->value.asDouble(); + double min = ctx->argument(0).toNumber(ctx); + double sec = (ctx->argumentCount < 2) ? SecFromTime(t) : ctx->argument(1).toNumber(ctx); + double ms = (ctx->argumentCount < 3) ? msFromTime(t) : ctx->argument(2).toNumber(ctx); + t = TimeClip(UTC(MakeDate(Day(t), MakeTime(HourFromTime(t), min, sec, ms)))); + self->value.setDouble(t); + return self->value; +} + +Value DatePrototype::method_setHours(ExecutionContext *ctx) +{ + DateObject *self = ctx->thisObject.asDateObject(); + if (!self) + ctx->throwTypeError(); + + double t = LocalTime(self->value.asDouble()); + double hour = ctx->argument(0).toNumber(ctx); + double min = (ctx->argumentCount < 2) ? MinFromTime(t) : ctx->argument(1).toNumber(ctx); + double sec = (ctx->argumentCount < 3) ? SecFromTime(t) : ctx->argument(2).toNumber(ctx); + double ms = (ctx->argumentCount < 4) ? msFromTime(t) : ctx->argument(3).toNumber(ctx); + t = TimeClip(UTC(MakeDate(Day(t), MakeTime(hour, min, sec, ms)))); + self->value.setDouble(t); + return self->value; +} + +Value DatePrototype::method_setUTCHours(ExecutionContext *ctx) +{ + DateObject *self = ctx->thisObject.asDateObject(); + if (!self) + ctx->throwTypeError(); + + double t = self->value.asDouble(); + double hour = ctx->argument(0).toNumber(ctx); + double min = (ctx->argumentCount < 2) ? MinFromTime(t) : ctx->argument(1).toNumber(ctx); + double sec = (ctx->argumentCount < 3) ? SecFromTime(t) : ctx->argument(2).toNumber(ctx); + double ms = (ctx->argumentCount < 4) ? msFromTime(t) : ctx->argument(3).toNumber(ctx); + t = TimeClip(UTC(MakeDate(Day(t), MakeTime(hour, min, sec, ms)))); + self->value.setDouble(t); + return self->value; +} + +Value DatePrototype::method_setDate(ExecutionContext *ctx) +{ + DateObject *self = ctx->thisObject.asDateObject(); + if (!self) + ctx->throwTypeError(); + + double t = LocalTime(self->value.asDouble()); + double date = ctx->argument(0).toNumber(ctx); + t = TimeClip(UTC(MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), date), TimeWithinDay(t)))); + self->value.setDouble(t); + return self->value; +} + +Value DatePrototype::method_setUTCDate(ExecutionContext *ctx) +{ + DateObject *self = ctx->thisObject.asDateObject(); + if (!self) + ctx->throwTypeError(); + + double t = self->value.asDouble(); + double date = ctx->argument(0).toNumber(ctx); + t = TimeClip(UTC(MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), date), TimeWithinDay(t)))); + self->value.setDouble(t); + return self->value; +} + +Value DatePrototype::method_setMonth(ExecutionContext *ctx) +{ + DateObject *self = ctx->thisObject.asDateObject(); + if (!self) + ctx->throwTypeError(); + + double t = LocalTime(self->value.asDouble()); + double month = ctx->argument(0).toNumber(ctx); + double date = (ctx->argumentCount < 2) ? DateFromTime(t) : ctx->argument(1).toNumber(ctx); + t = TimeClip(UTC(MakeDate(MakeDay(YearFromTime(t), month, date), TimeWithinDay(t)))); + self->value.setDouble(t); + return self->value; +} + +Value DatePrototype::method_setUTCMonth(ExecutionContext *ctx) +{ + DateObject *self = ctx->thisObject.asDateObject(); + if (!self) + ctx->throwTypeError(); + + double t = self->value.asDouble(); + double month = ctx->argument(0).toNumber(ctx); + double date = (ctx->argumentCount < 2) ? DateFromTime(t) : ctx->argument(1).toNumber(ctx); + t = TimeClip(UTC(MakeDate(MakeDay(YearFromTime(t), month, date), TimeWithinDay(t)))); + self->value.setDouble(t); + return self->value; +} + +Value DatePrototype::method_setYear(ExecutionContext *ctx) +{ + DateObject *self = ctx->thisObject.asDateObject(); + if (!self) + ctx->throwTypeError(); + + double t = self->value.asDouble(); + if (std::isnan(t)) + t = 0; + else + t = LocalTime(t); + double year = ctx->argument(0).toNumber(ctx); + double r; + if (std::isnan(year)) { + r = qSNaN(); + } else { + if ((Value::toInteger(year) >= 0) && (Value::toInteger(year) <= 99)) + year += 1900; + r = MakeDay(year, MonthFromTime(t), DateFromTime(t)); + r = UTC(MakeDate(r, TimeWithinDay(t))); + r = TimeClip(r); + } + self->value.setDouble(r); + return self->value; +} + +Value DatePrototype::method_setUTCFullYear(ExecutionContext *ctx) +{ + DateObject *self = ctx->thisObject.asDateObject(); + if (!self) + ctx->throwTypeError(); + + double t = self->value.asDouble(); + double year = ctx->argument(0).toNumber(ctx); + double month = (ctx->argumentCount < 2) ? MonthFromTime(t) : ctx->argument(1).toNumber(ctx); + double date = (ctx->argumentCount < 3) ? DateFromTime(t) : ctx->argument(2).toNumber(ctx); + t = TimeClip(UTC(MakeDate(MakeDay(year, month, date), TimeWithinDay(t)))); + self->value.setDouble(t); + return self->value; +} + +Value DatePrototype::method_setFullYear(ExecutionContext *ctx) +{ + DateObject *self = ctx->thisObject.asDateObject(); + if (!self) + ctx->throwTypeError(); + + double t = LocalTime(self->value.asDouble()); + if (std::isnan(t)) + t = 0; + double year = ctx->argument(0).toNumber(ctx); + double month = (ctx->argumentCount < 2) ? MonthFromTime(t) : ctx->argument(1).toNumber(ctx); + double date = (ctx->argumentCount < 3) ? DateFromTime(t) : ctx->argument(2).toNumber(ctx); + t = TimeClip(UTC(MakeDate(MakeDay(year, month, date), TimeWithinDay(t)))); + self->value.setDouble(t); + return self->value; +} + +Value DatePrototype::method_toUTCString(ExecutionContext *ctx) +{ + DateObject *self = ctx->thisObject.asDateObject(); + if (!self) + ctx->throwTypeError(); + + double t = self->value.asDouble(); + return Value::fromString(ctx, ToUTCString(t)); +} + +static void addZeroPrefixedInt(QString &str, int num, int nDigits) +{ + str.resize(str.size() + nDigits); + + QChar *c = str.data() + str.size() - 1; + while (nDigits) { + *c = QChar(num % 10 + '0'); + num /= 10; + --c; + --nDigits; + } +} + +Value DatePrototype::method_toISOString(ExecutionContext *ctx) +{ + DateObject *self = ctx->thisObject.asDateObject(); + if (!self) + ctx->throwTypeError(); + + double t = self->value.asDouble(); + if (!std::isfinite(t)) + ctx->throwRangeError(ctx->thisObject); + + QString result; + int year = (int)YearFromTime(t); + if (year < 0 || year > 9999) { + if (qAbs(year) >= 1000000) + return Value::fromString(ctx, QStringLiteral("Invalid Date")); + result += year < 0 ? '-' : '+'; + year = qAbs(year); + addZeroPrefixedInt(result, year, 6); + } else { + addZeroPrefixedInt(result, year, 4); + } + result += '-'; + addZeroPrefixedInt(result, (int)MonthFromTime(t) + 1, 2); + result += '-'; + addZeroPrefixedInt(result, (int)DateFromTime(t), 2); + result += 'T'; + addZeroPrefixedInt(result, HourFromTime(t), 2); + result += ':'; + addZeroPrefixedInt(result, MinFromTime(t), 2); + result += ':'; + addZeroPrefixedInt(result, SecFromTime(t), 2); + result += '.'; + addZeroPrefixedInt(result, msFromTime(t), 3); + result += 'Z'; + + return Value::fromString(ctx, result); +} + +Value DatePrototype::method_toJSON(ExecutionContext *ctx) +{ + Value O = __qmljs_to_object(ctx->thisObject, ctx); + Value tv = __qmljs_to_primitive(O, ctx, NUMBER_HINT); + + if (tv.isNumber() && !std::isfinite(tv.toNumber(ctx))) + return Value::nullValue(); + + FunctionObject *toIso = O.objectValue()->__get__(ctx, ctx->engine->identifier(QStringLiteral("toISOString"))).asFunctionObject(); + + if (!toIso) + __qmljs_throw_type_error(ctx); + + return toIso->call(ctx, ctx->thisObject, 0, 0); +} diff --git a/src/v4/qv4dateobject.h b/src/v4/qv4dateobject.h new file mode 100644 index 0000000000..ede4473217 --- /dev/null +++ b/src/v4/qv4dateobject.h @@ -0,0 +1,125 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4DATEOBJECT_P_H +#define QV4DATEOBJECT_P_H + +#include "qv4object.h" +#include "qv4functionobject.h" +#include <QtCore/qnumeric.h> + +namespace QQmlJS { +namespace VM { + +struct DateObject: Object { + Value value; + DateObject(const Value &value): value(value) { type = Type_DateObject; } +}; + +struct DateCtor: FunctionObject +{ + DateCtor(ExecutionContext *scope); + + virtual Value construct(ExecutionContext *ctx); + virtual Value call(ExecutionContext *ctx); +}; + +struct DatePrototype: DateObject +{ + DatePrototype(): DateObject(Value::fromDouble(qSNaN())) {} + void init(ExecutionContext *ctx, const Value &ctor); + + static double getThisDate(ExecutionContext *ctx); + + static Value method_parse(ExecutionContext *ctx); + static Value method_UTC(ExecutionContext *ctx); + static Value method_now(ExecutionContext *ctx); + + static Value method_toString(ExecutionContext *ctx); + static Value method_toDateString(ExecutionContext *ctx); + static Value method_toTimeString(ExecutionContext *ctx); + static Value method_toLocaleString(ExecutionContext *ctx); + static Value method_toLocaleDateString(ExecutionContext *ctx); + static Value method_toLocaleTimeString(ExecutionContext *ctx); + static Value method_valueOf(ExecutionContext *ctx); + static Value method_getTime(ExecutionContext *ctx); + static Value method_getYear(ExecutionContext *ctx); + static Value method_getFullYear(ExecutionContext *ctx); + static Value method_getUTCFullYear(ExecutionContext *ctx); + static Value method_getMonth(ExecutionContext *ctx); + static Value method_getUTCMonth(ExecutionContext *ctx); + static Value method_getDate(ExecutionContext *ctx); + static Value method_getUTCDate(ExecutionContext *ctx); + static Value method_getDay(ExecutionContext *ctx); + static Value method_getUTCDay(ExecutionContext *ctx); + static Value method_getHours(ExecutionContext *ctx); + static Value method_getUTCHours(ExecutionContext *ctx); + static Value method_getMinutes(ExecutionContext *ctx); + static Value method_getUTCMinutes(ExecutionContext *ctx); + static Value method_getSeconds(ExecutionContext *ctx); + static Value method_getUTCSeconds(ExecutionContext *ctx); + static Value method_getMilliseconds(ExecutionContext *ctx); + static Value method_getUTCMilliseconds(ExecutionContext *ctx); + static Value method_getTimezoneOffset(ExecutionContext *ctx); + static Value method_setTime(ExecutionContext *ctx); + static Value method_setMilliseconds(ExecutionContext *ctx); + static Value method_setUTCMilliseconds(ExecutionContext *ctx); + static Value method_setSeconds(ExecutionContext *ctx); + static Value method_setUTCSeconds(ExecutionContext *ctx); + static Value method_setMinutes(ExecutionContext *ctx); + static Value method_setUTCMinutes(ExecutionContext *ctx); + static Value method_setHours(ExecutionContext *ctx); + static Value method_setUTCHours(ExecutionContext *ctx); + static Value method_setDate(ExecutionContext *ctx); + static Value method_setUTCDate(ExecutionContext *ctx); + static Value method_setMonth(ExecutionContext *ctx); + static Value method_setUTCMonth(ExecutionContext *ctx); + static Value method_setYear(ExecutionContext *ctx); + static Value method_setFullYear(ExecutionContext *ctx); + static Value method_setUTCFullYear(ExecutionContext *ctx); + static Value method_toUTCString(ExecutionContext *ctx); + static Value method_toISOString(ExecutionContext *ctx); + static Value method_toJSON(ExecutionContext *ctx); +}; + +} // end of namespace VM +} // end of namespace QQmlJS + +#endif // QV4ECMAOBJECTS_P_H diff --git a/src/v4/qv4errorobject.cpp b/src/v4/qv4errorobject.cpp new file mode 100644 index 0000000000..9d4a067f35 --- /dev/null +++ b/src/v4/qv4errorobject.cpp @@ -0,0 +1,257 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "qv4errorobject.h" +#include "qv4mm.h" +#include <QtCore/qnumeric.h> +#include <QtCore/qmath.h> +#include <QtCore/QDateTime> +#include <QtCore/QStringList> +#include <QtCore/QDebug> +#include <cmath> +#include <qmath.h> +#include <qnumeric.h> +#include <cassert> + +#include <private/qqmljsengine_p.h> +#include <private/qqmljslexer_p.h> +#include <private/qqmljsparser_p.h> +#include <private/qqmljsast_p.h> +#include <qv4ir_p.h> +#include <qv4codegen_p.h> +#include <qv4isel_masm_p.h> + +#ifndef Q_WS_WIN +# include <time.h> +# ifndef Q_OS_VXWORKS +# include <sys/time.h> +# else +# include "qplatformdefs.h" +# endif +#else +# include <windows.h> +#endif + +using namespace QQmlJS::VM; + +ErrorObject::ErrorObject(ExecutionEngine* engine, const Value &message) + : errorType(Error) +{ + type = Type_ErrorObject; + + if (message.type() != Value::Undefined_Type) + defineDefaultProperty(engine->identifier(QStringLiteral("message")), message); +} + +void ErrorObject::setNameProperty(ExecutionContext *ctx) +{ + defineDefaultProperty(ctx, QLatin1String("name"), Value::fromString(ctx, className())); +} + +SyntaxErrorObject::SyntaxErrorObject(ExecutionContext *ctx, DiagnosticMessage *message) + : ErrorObject(ctx->engine, message ? Value::fromString(message->buildFullMessage(ctx)) : ctx->argument(0)) + , msg(message) +{ + errorType = SyntaxError; + prototype = ctx->engine->syntaxErrorPrototype; + setNameProperty(ctx); +} + + + +EvalErrorObject::EvalErrorObject(ExecutionContext *ctx) + : ErrorObject(ctx->engine, ctx->argument(0)) +{ + errorType = EvalError; + setNameProperty(ctx); + prototype = ctx->engine->evalErrorPrototype; +} + +RangeErrorObject::RangeErrorObject(ExecutionContext *ctx) + : ErrorObject(ctx->engine, ctx->argument(0)) +{ + errorType = RangeError; + setNameProperty(ctx); + prototype = ctx->engine->rangeErrorPrototype; +} + +RangeErrorObject::RangeErrorObject(ExecutionContext *ctx, const QString &msg) + : ErrorObject(ctx->engine, Value::fromString(ctx,msg)) +{ + errorType = RangeError; + setNameProperty(ctx); + prototype = ctx->engine->rangeErrorPrototype; +} + +ReferenceErrorObject::ReferenceErrorObject(ExecutionContext *ctx) + : ErrorObject(ctx->engine, ctx->argument(0)) +{ + errorType = ReferenceError; + setNameProperty(ctx); + prototype = ctx->engine->referenceErrorPrototype; +} + +ReferenceErrorObject::ReferenceErrorObject(ExecutionContext *ctx, const QString &msg) + : ErrorObject(ctx->engine, Value::fromString(ctx,msg)) +{ + errorType = ReferenceError; + setNameProperty(ctx); + prototype = ctx->engine->referenceErrorPrototype; +} + +TypeErrorObject::TypeErrorObject(ExecutionContext *ctx) + : ErrorObject(ctx->engine, ctx->argument(0)) +{ + errorType = TypeError; + setNameProperty(ctx); + prototype = ctx->engine->typeErrorPrototype; +} + +TypeErrorObject::TypeErrorObject(ExecutionContext *ctx, const QString &msg) + : ErrorObject(ctx->engine, Value::fromString(ctx,msg)) +{ + errorType = TypeError; + setNameProperty(ctx); + prototype = ctx->engine->typeErrorPrototype; +} + +URIErrorObject::URIErrorObject(ExecutionContext *ctx) + : ErrorObject(ctx->engine, ctx->argument(0)) +{ + errorType = URIError; + setNameProperty(ctx); + prototype = ctx->engine->uRIErrorPrototype; +} + +URIErrorObject::URIErrorObject(ExecutionContext *ctx, Value msg) + : ErrorObject(ctx->engine, msg) +{ + errorType = URIError; + setNameProperty(ctx); + prototype = ctx->engine->uRIErrorPrototype; +} + + +ErrorCtor::ErrorCtor(ExecutionContext *scope) + : FunctionObject(scope) +{ +} + +Value ErrorCtor::construct(ExecutionContext *ctx) +{ + return Value::fromObject(ctx->engine->newErrorObject(ctx->argument(0))); +} + +Value ErrorCtor::call(ExecutionContext *ctx) +{ + return construct(ctx); +} + +Value EvalErrorCtor::construct(ExecutionContext *ctx) +{ + return Value::fromObject(new (ctx->engine->memoryManager) EvalErrorObject(ctx)); +} + +Value RangeErrorCtor::construct(ExecutionContext *ctx) +{ + return Value::fromObject(new (ctx->engine->memoryManager) RangeErrorObject(ctx)); +} + +Value ReferenceErrorCtor::construct(ExecutionContext *ctx) +{ + return Value::fromObject(new (ctx->engine->memoryManager) ReferenceErrorObject(ctx)); +} + +Value SyntaxErrorCtor::construct(ExecutionContext *ctx) +{ + return Value::fromObject(new (ctx->engine->memoryManager) SyntaxErrorObject(ctx, 0)); +} + +Value TypeErrorCtor::construct(ExecutionContext *ctx) +{ + return Value::fromObject(new (ctx->engine->memoryManager) TypeErrorObject(ctx)); +} + +Value URIErrorCtor::construct(ExecutionContext *ctx) +{ + return Value::fromObject(new (ctx->engine->memoryManager) URIErrorObject(ctx)); +} + +void ErrorPrototype::init(ExecutionContext *ctx, const Value &ctor, Object *obj) +{ + ctor.objectValue()->defineReadonlyProperty(ctx->engine->id_prototype, Value::fromObject(obj)); + ctor.objectValue()->defineReadonlyProperty(ctx->engine->id_length, Value::fromInt32(1)); + obj->defineDefaultProperty(ctx, QStringLiteral("constructor"), ctor); + obj->defineDefaultProperty(ctx, QStringLiteral("toString"), method_toString, 0); + obj->defineDefaultProperty(ctx, QStringLiteral("message"), Value::fromString(ctx, QString())); + obj->defineDefaultProperty(ctx, QStringLiteral("name"), Value::fromString(ctx, QStringLiteral("Error"))); +} + +Value ErrorPrototype::method_toString(ExecutionContext *ctx) +{ + Object *o = ctx->thisObject.asObject(); + if (!o) + __qmljs_throw_type_error(ctx); + + Value name = o->__get__(ctx, ctx->engine->newString(QString::fromLatin1("name"))); + QString qname; + if (name.isUndefined()) + qname = QString::fromLatin1("Error"); + else + qname = __qmljs_to_string(name, ctx).stringValue()->toQString(); + + Value message = o->__get__(ctx, ctx->engine->newString(QString::fromLatin1("message"))); + QString qmessage; + if (!message.isUndefined()) + qmessage = __qmljs_to_string(message, ctx).stringValue()->toQString(); + + QString str; + if (qname.isEmpty()) { + str = qmessage; + } else if (qmessage.isEmpty()) { + str = qname; + } else { + str = qname + QLatin1String(": ") + qmessage; + } + + return Value::fromString(ctx, str); +} diff --git a/src/v4/qv4errorobject.h b/src/v4/qv4errorobject.h new file mode 100644 index 0000000000..d2d2340fae --- /dev/null +++ b/src/v4/qv4errorobject.h @@ -0,0 +1,206 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4ERROROBJECT_H +#define QV4ERROROBJECT_H + +#include "qv4object.h" +#include "qv4functionobject.h" + +namespace QQmlJS { +namespace VM { + +struct ErrorObject: Object { + enum ErrorType { + Error, + EvalError, + RangeError, + ReferenceError, + SyntaxError, + TypeError, + URIError + }; + ErrorType errorType; + + ErrorObject(ExecutionEngine* engine, const Value &message); + + virtual struct SyntaxErrorObject *asSyntaxError() { return 0; } + +protected: + void setNameProperty(ExecutionContext *ctx); +}; + +struct EvalErrorObject: ErrorObject { + EvalErrorObject(ExecutionContext *ctx); +}; + +struct RangeErrorObject: ErrorObject { + RangeErrorObject(ExecutionContext *ctx); + RangeErrorObject(ExecutionContext *ctx, const QString &msg); +}; + +struct ReferenceErrorObject: ErrorObject { + ReferenceErrorObject(ExecutionContext *ctx); + ReferenceErrorObject(ExecutionContext *ctx, const QString &msg); +}; + +struct SyntaxErrorObject: ErrorObject { + SyntaxErrorObject(ExecutionContext *ctx, DiagnosticMessage *msg); + ~SyntaxErrorObject() { delete msg; } + + virtual SyntaxErrorObject *asSyntaxError() { return this; } + DiagnosticMessage *message() { return msg; } + +private: + DiagnosticMessage *msg; +}; + +struct TypeErrorObject: ErrorObject { + TypeErrorObject(ExecutionContext *ctx); + TypeErrorObject(ExecutionContext *ctx, const QString &msg); +}; + +struct URIErrorObject: ErrorObject { + URIErrorObject(ExecutionContext *ctx); + URIErrorObject(ExecutionContext *ctx, Value); +}; + +struct ErrorCtor: FunctionObject +{ + ErrorCtor(ExecutionContext *scope); + + virtual Value construct(ExecutionContext *ctx); + virtual Value call(ExecutionContext *ctx); +}; + +struct EvalErrorCtor: ErrorCtor +{ + EvalErrorCtor(ExecutionContext *scope): ErrorCtor(scope) {} + + virtual Value construct(ExecutionContext *ctx); +}; + +struct RangeErrorCtor: ErrorCtor +{ + RangeErrorCtor(ExecutionContext *scope): ErrorCtor(scope) {} + + virtual Value construct(ExecutionContext *ctx); +}; + +struct ReferenceErrorCtor: ErrorCtor +{ + ReferenceErrorCtor(ExecutionContext *scope): ErrorCtor(scope) {} + + virtual Value construct(ExecutionContext *ctx); +}; + +struct SyntaxErrorCtor: ErrorCtor +{ + SyntaxErrorCtor(ExecutionContext *scope): ErrorCtor(scope) {} + + virtual Value construct(ExecutionContext *ctx); +}; + +struct TypeErrorCtor: ErrorCtor +{ + TypeErrorCtor(ExecutionContext *scope): ErrorCtor(scope) {} + + virtual Value construct(ExecutionContext *ctx); +}; + +struct URIErrorCtor: ErrorCtor +{ + URIErrorCtor(ExecutionContext *scope): ErrorCtor(scope) {} + + virtual Value construct(ExecutionContext *ctx); +}; + + +struct ErrorPrototype: ErrorObject +{ + // ### shouldn't be undefined + ErrorPrototype(ExecutionEngine* engine): ErrorObject(engine, Value::undefinedValue()) {} + void init(ExecutionContext *ctx, const Value &ctor) { init(ctx, ctor, this); } + + static void init(ExecutionContext *ctx, const Value &ctor, Object *obj); + static Value method_toString(ExecutionContext *ctx); +}; + +struct EvalErrorPrototype: EvalErrorObject +{ + EvalErrorPrototype(ExecutionContext *ctx): EvalErrorObject(ctx) {} + void init(ExecutionContext *ctx, const Value &ctor) { ErrorPrototype::init(ctx, ctor, this); } +}; + +struct RangeErrorPrototype: RangeErrorObject +{ + RangeErrorPrototype(ExecutionContext *ctx): RangeErrorObject(ctx) {} + void init(ExecutionContext *ctx, const Value &ctor) { ErrorPrototype::init(ctx, ctor, this); } +}; + +struct ReferenceErrorPrototype: ReferenceErrorObject +{ + ReferenceErrorPrototype(ExecutionContext *ctx): ReferenceErrorObject(ctx) {} + void init(ExecutionContext *ctx, const Value &ctor) { ErrorPrototype::init(ctx, ctor, this); } +}; + +struct SyntaxErrorPrototype: SyntaxErrorObject +{ + SyntaxErrorPrototype(ExecutionContext *ctx): SyntaxErrorObject(ctx, 0) {} + void init(ExecutionContext *ctx, const Value &ctor) { ErrorPrototype::init(ctx, ctor, this); } +}; + +struct TypeErrorPrototype: TypeErrorObject +{ + TypeErrorPrototype(ExecutionContext *ctx): TypeErrorObject(ctx) {} + void init(ExecutionContext *ctx, const Value &ctor) { ErrorPrototype::init(ctx, ctor, this); } +}; + +struct URIErrorPrototype: URIErrorObject +{ + URIErrorPrototype(ExecutionContext *ctx): URIErrorObject(ctx) {} + void init(ExecutionContext *ctx, const Value &ctor) { ErrorPrototype::init(ctx, ctor, this); } +}; + + +} // end of namespace VM +} // end of namespace QQmlJS + +#endif // QV4ECMAOBJECTS_P_H diff --git a/src/v4/qv4functionobject.cpp b/src/v4/qv4functionobject.cpp new file mode 100644 index 0000000000..f5e37def0c --- /dev/null +++ b/src/v4/qv4functionobject.cpp @@ -0,0 +1,465 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4object.h" +#include "qv4ir_p.h" +#include "qv4isel_p.h" +#include "qv4objectproto.h" +#include "qv4stringobject.h" +#include "qv4mm.h" + +#include <private/qqmljsengine_p.h> +#include <private/qqmljslexer_p.h> +#include <private/qqmljsparser_p.h> +#include <private/qqmljsast_p.h> +#include <qv4ir_p.h> +#include <qv4codegen_p.h> +#include "private/qlocale_tools_p.h" + +#include <QtCore/qmath.h> +#include <QtCore/QDebug> +#include <cassert> +#include <typeinfo> +#include <iostream> +#include <alloca.h> + +using namespace QQmlJS::VM; + + +Function::~Function() +{ + delete[] codeData; +} + +void Function::mark() +{ + if (name) + name->mark(); + for (int i = 0; i < formals.size(); ++i) + formals.at(i)->mark(); + for (int i = 0; i < locals.size(); ++i) + locals.at(i)->mark(); + for (int i = 0; i < generatedValues.size(); ++i) + if (Managed *m = generatedValues.at(i).asManaged()) + m->mark(); + for (int i = 0; i < identifiers.size(); ++i) + identifiers.at(i)->mark(); +} + +FunctionObject::FunctionObject(ExecutionContext *scope) + : scope(scope) + , name(0) + , formalParameterList(0) + , varList(0) + , formalParameterCount(0) + , varCount(0) +{ + prototype = scope->engine->functionPrototype; + + type = Type_FunctionObject; + needsActivation = false; + usesArgumentsObject = false; + strictMode = false; +} + +bool FunctionObject::hasInstance(ExecutionContext *ctx, const Value &value) +{ + if (! value.isObject()) + return false; + + Value o = __get__(ctx, ctx->engine->id_prototype); + if (! o.isObject()) { + ctx->throwTypeError(); + return false; + } + + Object *v = value.objectValue(); + while (v) { + v = v->prototype; + + if (! v) + break; + else if (o.objectValue() == v) + return true; + } + + return false; +} + +Value FunctionObject::construct(ExecutionContext *context, Value *args, int argc) +{ + Object *obj = context->engine->newObject(); + Value proto = __get__(context, context->engine->id_prototype); + if (proto.isObject()) + obj->prototype = proto.objectValue(); + + uint size = requiredMemoryForExecutionContect(this, argc); + ExecutionContext *ctx = static_cast<ExecutionContext *>(needsActivation ? malloc(size) : alloca(size)); + + ctx->initCallContext(context, Value::fromObject(obj), this, args, argc); + Value result = construct(ctx); + ctx->leaveCallContext(); + + if (result.isObject()) + return result; + return Value::fromObject(obj); +} + +Value FunctionObject::call(ExecutionContext *context, Value thisObject, Value *args, int argc) +{ + uint size = requiredMemoryForExecutionContect(this, argc); + ExecutionContext *ctx = static_cast<ExecutionContext *>(needsActivation ? malloc(size) : alloca(size)); + + + ctx->initCallContext(context, thisObject, this, args, argc); + if (isBuiltinFunction) { + // Built-in functions allow for the this object to be null or undefined. This overrides + // the behaviour of changing thisObject to the global object if null/undefined and allows + // the built-in functions for example to throw a type error if null is passed. + if (thisObject.isNull() || thisObject.isUndefined()) + ctx->thisObject = thisObject; + } + Value result = call(ctx); + ctx->leaveCallContext(); + return result; +} + +void FunctionObject::markObjects() +{ + if (name) + name->mark(); + // these are marked in VM::Function: +// for (uint i = 0; i < formalParameterCount; ++i) +// formalParameterList[i]->mark(); +// for (uint i = 0; i < varCount; ++i) +// varList[i]->mark(); + scope->mark(); + Object::markObjects(); +} + +Value FunctionObject::call(ExecutionContext *ctx) +{ + Q_UNUSED(ctx); + return Value::undefinedValue(); +} + +Value FunctionObject::construct(ExecutionContext *ctx) +{ + return call(ctx); +} + + +FunctionCtor::FunctionCtor(ExecutionContext *scope) + : FunctionObject(scope) +{ +} + +// 15.3.2 +Value FunctionCtor::construct(ExecutionContext *ctx) +{ + MemoryManager::GCBlocker gcBlocker(ctx->engine->memoryManager); + + QString args; + QString body; + if (ctx->argumentCount > 0) { + for (uint i = 0; i < ctx->argumentCount - 1; ++i) { + if (i) + args += QLatin1String(", "); + args += ctx->argument(i).toString(ctx)->toQString(); + } + body = ctx->argument(ctx->argumentCount - 1).toString(ctx)->toQString(); + } + + QString function = QLatin1String("function(") + args + QLatin1String("){") + body + QLatin1String("}"); + + QQmlJS::Engine ee, *engine = ⅇ + Lexer lexer(engine); + lexer.setCode(function, 1, false); + Parser parser(engine); + + const bool parsed = parser.parseExpression(); + + if (!parsed) + ctx->throwSyntaxError(0); + + using namespace AST; + FunctionExpression *fe = AST::cast<FunctionExpression *>(parser.rootNode()); + if (!fe) + ctx->throwSyntaxError(0); + + IR::Module module; + + Codegen cg(ctx, ctx->strictMode); + IR::Function *irf = cg(QString(), fe, &module); + + QScopedPointer<EvalInstructionSelection> isel(ctx->engine->iselFactory->create(ctx->engine, &module)); + VM::Function *vmf = isel->vmFunction(irf); + + return Value::fromObject(ctx->engine->newScriptFunction(ctx->engine->rootContext, vmf)); +} + +// 15.3.1: This is equivalent to new Function(...) +Value FunctionCtor::call(ExecutionContext *ctx) +{ + return construct(ctx); +} + +void FunctionPrototype::init(ExecutionContext *ctx, const Value &ctor) +{ + ctor.objectValue()->defineReadonlyProperty(ctx->engine->id_prototype, Value::fromObject(this)); + ctor.objectValue()->defineReadonlyProperty(ctx->engine->id_length, Value::fromInt32(1)); + defineDefaultProperty(ctx, QStringLiteral("constructor"), ctor); + defineDefaultProperty(ctx, QStringLiteral("toString"), method_toString, 0); + defineDefaultProperty(ctx, QStringLiteral("apply"), method_apply, 2); + defineDefaultProperty(ctx, QStringLiteral("call"), method_call, 1); + defineDefaultProperty(ctx, QStringLiteral("bind"), method_bind, 1); + + defineReadonlyProperty(ctx->engine->id_length, Value::fromInt32(0)); +} + +Value FunctionPrototype::method_toString(ExecutionContext *ctx) +{ + FunctionObject *fun = ctx->thisObject.asFunctionObject(); + if (!fun) + ctx->throwTypeError(); + + return Value::fromString(ctx, QStringLiteral("function() { [code] }")); +} + +Value FunctionPrototype::method_apply(ExecutionContext *ctx) +{ + Value thisArg = ctx->argument(0); + + Value arg = ctx->argument(1); + QVector<Value> args; + + if (Object *arr = arg.asObject()) { + quint32 len = arr->__get__(ctx, ctx->engine->id_length).toUInt32(ctx); + for (quint32 i = 0; i < len; ++i) { + Value a = arr->__get__(ctx, i); + args.append(a); + } + } else if (!(arg.isUndefined() || arg.isNull())) { + ctx->throwTypeError(); + return Value::undefinedValue(); + } + + FunctionObject *o = ctx->thisObject.asFunctionObject(); + if (!o) + ctx->throwTypeError(); + + return o->call(ctx, thisArg, args.data(), args.size()); +} + +Value FunctionPrototype::method_call(ExecutionContext *ctx) +{ + Value thisArg = ctx->argument(0); + + QVector<Value> args(ctx->argumentCount ? ctx->argumentCount - 1 : 0); + if (ctx->argumentCount) + qCopy(ctx->arguments + 1, + ctx->arguments + ctx->argumentCount, args.begin()); + + FunctionObject *o = ctx->thisObject.asFunctionObject(); + if (!o) + ctx->throwTypeError(); + + return o->call(ctx, thisArg, args.data(), args.size()); +} + +Value FunctionPrototype::method_bind(ExecutionContext *ctx) +{ + FunctionObject *target = ctx->thisObject.asFunctionObject(); + if (!target) + ctx->throwTypeError(); + + Value boundThis = ctx->argument(0); + QVector<Value> boundArgs; + for (uint i = 1; i < ctx->argumentCount; ++i) + boundArgs += ctx->argument(i); + + + BoundFunction *f = ctx->engine->newBoundFunction(ctx, target, boundThis, boundArgs); + return Value::fromObject(f); +} + +ScriptFunction::ScriptFunction(ExecutionContext *scope, VM::Function *function) + : FunctionObject(scope) + , function(function) +{ + assert(function); + assert(function->code); + + // global function + if (!scope) + return; + + MemoryManager::GCBlocker gcBlocker(scope->engine->memoryManager); + + name = function->name; + needsActivation = function->needsActivation(); + usesArgumentsObject = function->usesArgumentsObject; + strictMode = function->isStrict; + formalParameterCount = function->formals.size(); + formalParameterList = function->formals.constData(); + defineReadonlyProperty(scope->engine->id_length, Value::fromInt32(formalParameterCount)); + + varCount = function->locals.size(); + varList = function->locals.constData(); + + Object *proto = scope->engine->newObject(); + proto->defineDefaultProperty(scope->engine->id_constructor, Value::fromObject(this)); + PropertyDescriptor *pd = members->insert(scope->engine->id_prototype); + pd->type = PropertyDescriptor::Data; + pd->writable = PropertyDescriptor::Enabled; + pd->enumberable = PropertyDescriptor::Disabled; + pd->configurable = PropertyDescriptor::Disabled; + pd->value = Value::fromObject(proto); + + if (scope->strictMode) { + FunctionObject *thrower = scope->engine->newBuiltinFunction(scope, 0, __qmljs_throw_type_error); + PropertyDescriptor pd = PropertyDescriptor::fromAccessor(thrower, thrower); + pd.configurable = PropertyDescriptor::Disabled; + pd.enumberable = PropertyDescriptor::Disabled; + __defineOwnProperty__(scope, QStringLiteral("caller"), &pd); + __defineOwnProperty__(scope, QStringLiteral("arguments"), &pd); + } +} + +ScriptFunction::~ScriptFunction() +{ +} + +Value ScriptFunction::call(VM::ExecutionContext *ctx) +{ + assert(function->code); + return function->code(ctx, function->codeData); +} + +void ScriptFunction::markObjects() +{ + function->mark(); + FunctionObject::markObjects(); +} + + +BuiltinFunctionOld::BuiltinFunctionOld(ExecutionContext *scope, String *name, Value (*code)(ExecutionContext *)) + : FunctionObject(scope) + , code(code) +{ + this->name = name; + isBuiltinFunction = true; +} + +Value BuiltinFunctionOld::construct(ExecutionContext *ctx) +{ + ctx->throwTypeError(); + return Value::undefinedValue(); +} + +BuiltinFunction::BuiltinFunction(ExecutionContext *scope, String *name, Value (*code)(ExecutionContext *, Value, Value *, int)) + : FunctionObject(scope) + , code(code) +{ + this->name = name; + isBuiltinFunction = true; +} + +Value BuiltinFunction::construct(ExecutionContext *ctx) +{ + ctx->throwTypeError(); + return Value::undefinedValue(); +} + + +BoundFunction::BoundFunction(ExecutionContext *scope, FunctionObject *target, Value boundThis, const QVector<Value> &boundArgs) + : FunctionObject(scope) + , target(target) + , boundThis(boundThis) + , boundArgs(boundArgs) +{ + int len = target->__get__(scope, scope->engine->id_length).toUInt32(scope); + len -= boundArgs.size(); + if (len < 0) + len = 0; + defineReadonlyProperty(scope->engine->id_length, Value::fromInt32(len)); + + FunctionObject *thrower = scope->engine->newBuiltinFunction(scope, 0, __qmljs_throw_type_error); + PropertyDescriptor pd = PropertyDescriptor::fromAccessor(thrower, thrower); + pd.configurable = PropertyDescriptor::Disabled; + pd.enumberable = PropertyDescriptor::Disabled; + *members->insert(scope->engine->id_arguments) = pd; + *members->insert(scope->engine->id_caller) = pd; +} + +Value BoundFunction::call(ExecutionContext *context, Value, Value *args, int argc) +{ + Value *newArgs = static_cast<Value *>(alloca(sizeof(Value)*(boundArgs.size() + argc))); + memcpy(newArgs, boundArgs.constData(), boundArgs.size()*sizeof(Value)); + memcpy(newArgs + boundArgs.size(), args, argc*sizeof(Value)); + + return target->call(context, boundThis, newArgs, boundArgs.size() + argc); +} + +Value BoundFunction::construct(ExecutionContext *context, Value *args, int argc) +{ + Value *newArgs = static_cast<Value *>(alloca(sizeof(Value)*(boundArgs.size() + argc))); + memcpy(newArgs, boundArgs.constData(), boundArgs.size()*sizeof(Value)); + memcpy(newArgs + boundArgs.size(), args, argc*sizeof(Value)); + + return target->construct(context, newArgs, boundArgs.size() + argc); +} + +bool BoundFunction::hasInstance(ExecutionContext *ctx, const Value &value) +{ + return target->hasInstance(ctx, value); +} + +void BoundFunction::markObjects() +{ + target->mark(); + if (Managed *m = boundThis.asManaged()) + m->mark(); + for (int i = 0; i < boundArgs.size(); ++i) + if (Managed *m = boundArgs.at(i).asManaged()) + m->mark(); + FunctionObject::markObjects(); +} diff --git a/src/v4/qv4functionobject.h b/src/v4/qv4functionobject.h new file mode 100644 index 0000000000..b651eedcae --- /dev/null +++ b/src/v4/qv4functionobject.h @@ -0,0 +1,229 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4FUNCTIONOBJECT_H +#define QV4FUNCTIONOBJECT_H + +#include "qv4global.h" +#include "qmljs_runtime.h" +#include "qmljs_engine.h" +#include "qmljs_environment.h" +#include "qv4object.h" +#include "qv4array.h" +#include "qv4string.h" +#include "qv4codegen_p.h" +#include "qv4isel_p.h" +#include "qv4managed.h" +#include "qv4propertydescriptor.h" +#include "qv4propertytable.h" +#include "qv4objectiterator.h" +#include "qv4regexp.h" + +#include <QtCore/QString> +#include <QtCore/QHash> +#include <QtCore/QScopedPointer> +#include <cstdio> +#include <cassert> + +#include <config.h> +#include <assembler/MacroAssemblerCodeRef.h> + +namespace QQmlJS { + +namespace VM { + +struct Value; +struct Function; +struct Object; +struct ObjectIterator; +struct BooleanObject; +struct NumberObject; +struct StringObject; +struct ArrayObject; +struct DateObject; +struct FunctionObject; +struct RegExpObject; +struct ErrorObject; +struct ArgumentsObject; +struct ExecutionContext; +struct ExecutionEngine; +class MemoryManager; + +struct ObjectPrototype; +struct StringPrototype; +struct NumberPrototype; +struct BooleanPrototype; +struct ArrayPrototype; +struct FunctionPrototype; +struct DatePrototype; +struct RegExpPrototype; +struct ErrorPrototype; +struct EvalErrorPrototype; +struct RangeErrorPrototype; +struct ReferenceErrorPrototype; +struct SyntaxErrorPrototype; +struct TypeErrorPrototype; +struct URIErrorPrototype; + +struct Function { + String *name; + + VM::Value (*code)(VM::ExecutionContext *, const uchar *); + const uchar *codeData; + JSC::MacroAssemblerCodeRef codeRef; + + QVector<String *> formals; + QVector<String *> locals; + QVector<Value> generatedValues; + QVector<String *> identifiers; + + bool hasNestedFunctions : 1; + bool hasDirectEval : 1; + bool usesArgumentsObject : 1; + bool isStrict : 1; + + Function(String *name) + : name(name) + , code(0) + , codeData(0) + , hasNestedFunctions(0) + , hasDirectEval(false) + , usesArgumentsObject(false) + , isStrict(false) + {} + ~Function(); + + inline bool needsActivation() const { return hasNestedFunctions || hasDirectEval || usesArgumentsObject; } + + void mark(); +}; + +struct Q_V4_EXPORT FunctionObject: Object { + ExecutionContext *scope; + String *name; + String * const *formalParameterList; + String * const *varList; + unsigned int formalParameterCount; + unsigned int varCount; + + FunctionObject(ExecutionContext *scope); + + virtual bool hasInstance(ExecutionContext *ctx, const Value &value); + + virtual Value construct(ExecutionContext *context, Value *args, int argc); + virtual Value call(ExecutionContext *context, Value thisObject, Value *args, int argc); + + virtual struct ScriptFunction *asScriptFunction() { return 0; } + + virtual void markObjects(); + +protected: + virtual Value call(ExecutionContext *ctx); + virtual Value construct(ExecutionContext *ctx); +}; + +struct FunctionCtor: FunctionObject +{ + FunctionCtor(ExecutionContext *scope); + + virtual Value construct(ExecutionContext *ctx); + virtual Value call(ExecutionContext *ctx); +}; + +struct FunctionPrototype: FunctionObject +{ + FunctionPrototype(ExecutionContext *ctx): FunctionObject(ctx) {} + void init(ExecutionContext *ctx, const Value &ctor); + + static Value method_toString(ExecutionContext *ctx); + static Value method_apply(ExecutionContext *ctx); + static Value method_call(ExecutionContext *ctx); + static Value method_bind(ExecutionContext *ctx); +}; + +struct BuiltinFunctionOld: FunctionObject { + Value (*code)(ExecutionContext *); + + BuiltinFunctionOld(ExecutionContext *scope, String *name, Value (*code)(ExecutionContext *)); + virtual Value call(ExecutionContext *ctx) { return code(ctx); } + virtual Value construct(ExecutionContext *ctx); +}; + +struct BuiltinFunction: FunctionObject { + Value (*code)(ExecutionContext *parentContext, Value thisObject, Value *args, int argc); + + BuiltinFunction(ExecutionContext *scope, String *name, Value (*code)(ExecutionContext *, Value, Value *, int)); + virtual Value call(ExecutionContext *context, Value thisObject, Value *args, int argc) { + return code(context, thisObject, args, argc); + } + virtual Value construct(ExecutionContext *ctx); +}; + +struct ScriptFunction: FunctionObject { + VM::Function *function; + + ScriptFunction(ExecutionContext *scope, VM::Function *function); + virtual ~ScriptFunction(); + + virtual Value call(ExecutionContext *ctx); + + virtual ScriptFunction *asScriptFunction() { return this; } + + virtual void markObjects(); +}; + +struct BoundFunction: FunctionObject { + FunctionObject *target; + Value boundThis; + QVector<Value> boundArgs; + + BoundFunction(ExecutionContext *scope, FunctionObject *target, Value boundThis, const QVector<Value> &boundArgs); + virtual ~BoundFunction() {} + + virtual Value call(ExecutionContext *context, Value thisObject, Value *args, int argc); + virtual Value construct(ExecutionContext *context, Value *args, int argc); + virtual bool hasInstance(ExecutionContext *ctx, const Value &value); + virtual void markObjects(); +}; + +} // namespace VM +} // namespace QQmlJS + +#endif // QMLJS_OBJECTS_H diff --git a/src/v4/qv4global.h b/src/v4/qv4global.h new file mode 100644 index 0000000000..6b1c9a21ec --- /dev/null +++ b/src/v4/qv4global.h @@ -0,0 +1,49 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QV4GLOBAL_H +#define QV4GLOBAL_H + +#include <QtCore/qglobal.h> + +QT_BEGIN_HEADER + +#ifndef QT_STATIC +# if defined(QT_BUILD_V4_LIB) +# define Q_V4_EXPORT Q_DECL_EXPORT +# else +# define Q_V4_EXPORT Q_DECL_IMPORT +# endif +#else +# define Q_V4_EXPORT +#endif + +QT_END_NAMESPACE + +#endif // QV4GLOBAL_H diff --git a/src/v4/qv4globalobject.cpp b/src/v4/qv4globalobject.cpp new file mode 100644 index 0000000000..74cd32d88a --- /dev/null +++ b/src/v4/qv4globalobject.cpp @@ -0,0 +1,670 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4globalobject.h" +#include "qv4mm.h" +#include "qmljs_value.h" +#include "qmljs_environment.h" + +#include <private/qqmljsengine_p.h> +#include <private/qqmljslexer_p.h> +#include <private/qqmljsparser_p.h> +#include <private/qqmljsast_p.h> +#include <qv4ir_p.h> +#include <qv4codegen_p.h> +#include "private/qlocale_tools_p.h" + +#include <QtCore/QDebug> +#include <QtCore/QString> +#include <iostream> +#include <alloca.h> + +using namespace QQmlJS::VM; + +static inline char toHex(char c) +{ + static const char hexnumbers[] = "0123456789ABCDEF"; + return hexnumbers[c & 0xf]; +} + +static int fromHex(QChar ch) +{ + ushort c = ch.unicode(); + if ((c >= '0') && (c <= '9')) + return c - '0'; + if ((c >= 'A') && (c <= 'F')) + return c - 'A' + 10; + if ((c >= 'a') && (c <= 'f')) + return c - 'a' + 10; + return -1; +} + +static QString escape(const QString &input) +{ + QString output; + output.reserve(input.size() * 3); + const int length = input.length(); + for (int i = 0; i < length; ++i) { + ushort uc = input.at(i).unicode(); + if (uc < 0x100) { + if ( (uc > 0x60 && uc < 0x7B) + || (uc > 0x3F && uc < 0x5B) + || (uc > 0x2C && uc < 0x3A) + || (uc == 0x2A) + || (uc == 0x2B) + || (uc == 0x5F)) { + output.append(QChar(uc)); + } else { + output.append('%'); + output.append(QChar(toHex(uc >> 4))); + output.append(QChar(toHex(uc))); + } + } else { + output.append('%'); + output.append('u'); + output.append(QChar(toHex(uc >> 12))); + output.append(QChar(toHex(uc >> 8))); + output.append(QChar(toHex(uc >> 4))); + output.append(QChar(toHex(uc))); + } + } + return output; +} + +static QString unescape(const QString &input) +{ + QString result; + result.reserve(input.length()); + int i = 0; + const int length = input.length(); + while (i < length) { + QChar c = input.at(i++); + if ((c == '%') && (i + 1 < length)) { + QChar a = input.at(i); + if ((a == 'u') && (i + 4 < length)) { + int d3 = fromHex(input.at(i+1)); + int d2 = fromHex(input.at(i+2)); + int d1 = fromHex(input.at(i+3)); + int d0 = fromHex(input.at(i+4)); + if ((d3 != -1) && (d2 != -1) && (d1 != -1) && (d0 != -1)) { + ushort uc = ushort((d3 << 12) | (d2 << 8) | (d1 << 4) | d0); + result.append(QChar(uc)); + i += 5; + } else { + result.append(c); + } + } else { + int d1 = fromHex(a); + int d0 = fromHex(input.at(i+1)); + if ((d1 != -1) && (d0 != -1)) { + c = (d1 << 4) | d0; + i += 2; + } + result.append(c); + } + } else { + result.append(c); + } + } + return result; +} + +static const char uriReserved[] = ";/?:@&=+$,"; +static const char uriUnescaped[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.!~*'()"; + +static void addEscapeSequence(QString &output, uchar ch) +{ + output.append(QLatin1Char('%')); + output.append(QLatin1Char(toHex(ch >> 4))); + output.append(QLatin1Char(toHex(ch & 0xf))); +} + +static QString encode(const QString &input, const QString &unescapedSet, bool *ok) +{ + *ok = true; + QString output; + const int length = input.length(); + int i = 0; + while (i < length) { + const QChar c = input.at(i); + if (!unescapedSet.contains(c)) { + uint uc = c.unicode(); + if ((uc >= 0xDC00) && (uc <= 0xDFFF)) { + *ok = false; + break; + } + if (!((uc < 0xD800) || (uc > 0xDBFF))) { + ++i; + if (i == length) { + *ok = false; + break; + } + const uint uc2 = input.at(i).unicode(); + if ((uc2 < 0xDC00) || (uc2 > 0xDFFF)) { + *ok = false; + break; + } + uc = ((uc - 0xD800) * 0x400) + (uc2 - 0xDC00) + 0x10000; + } + if (uc < 0x80) { + addEscapeSequence(output, (uchar)uc); + } else { + if (uc < 0x0800) { + addEscapeSequence(output, 0xc0 | ((uchar) (uc >> 6))); + } else { + + if (QChar::requiresSurrogates(uc)) { + addEscapeSequence(output, 0xf0 | ((uchar) (uc >> 18))); + addEscapeSequence(output, 0x80 | (((uchar) (uc >> 12)) & 0x3f)); + } else { + addEscapeSequence(output, 0xe0 | (((uchar) (uc >> 12)) & 0x3f)); + } + addEscapeSequence(output, 0x80 | (((uchar) (uc >> 6)) & 0x3f)); + } + addEscapeSequence(output, 0x80 | ((uchar) (uc&0x3f))); + } + } else { + output.append(c); + } + ++i; + } + if (i != length) + *ok = false; + return output; +} + +static QString decode(const QString &input, const QString &reservedSet, bool *ok) +{ + *ok = true; + QString output; + const int length = input.length(); + int i = 0; + const QChar percent = QLatin1Char('%'); + while (i < length) { + const QChar ch = input.at(i); + if (ch == percent) { + int start = i; + if (i + 2 >= length) + goto error; + + int d1 = fromHex(input.at(i+1)); + int d0 = fromHex(input.at(i+2)); + if ((d1 == -1) || (d0 == -1)) + goto error; + + int b = (d1 << 4) | d0; + i += 2; + if (b & 0x80) { + int uc; + int min_uc; + int need; + if ((b & 0xe0) == 0xc0) { + uc = b & 0x1f; + need = 1; + min_uc = 0x80; + } else if ((b & 0xf0) == 0xe0) { + uc = b & 0x0f; + need = 2; + min_uc = 0x800; + } else if ((b & 0xf8) == 0xf0) { + uc = b & 0x07; + need = 3; + min_uc = 0x10000; + } else { + goto error; + } + + if (i + (3 * need) >= length) + goto error; + + for (int j = 0; j < need; ++j) { + ++i; + if (input.at(i) != percent) + goto error; + + d1 = fromHex(input.at(i+1)); + d0 = fromHex(input.at(i+2)); + if ((d1 == -1) || (d0 == -1)) + goto error; + + b = (d1 << 4) | d0; + if ((b & 0xC0) != 0x80) + goto error; + + i += 2; + uc = (uc << 6) + (b & 0x3f); + } + if (uc < min_uc) + goto error; + + if (uc < 0x10000) { + output.append(QChar(uc)); + } else { + if (uc > 0x10FFFF) + goto error; + + ushort l = ushort(((uc - 0x10000) & 0x3FF) + 0xDC00); + ushort h = ushort((((uc - 0x10000) >> 10) & 0x3FF) + 0xD800); + output.append(QChar(h)); + output.append(QChar(l)); + } + } else { + QChar z(b); + if (!reservedSet.contains(z)) { + output.append(z); + } else { + output.append(input.mid(start, i - start + 1)); + } + } + } else { + output.append(ch); + } + ++i; + } + if (i != length) + *ok = false; + return output; + error: + *ok = false; + return QString(); +} + + +EvalFunction::EvalFunction(ExecutionContext *scope) + : FunctionObject(scope) +{ + name = scope->engine->id_eval; + defineReadonlyProperty(scope->engine->id_length, Value::fromInt32(1)); +} + +Value EvalFunction::evalCall(ExecutionContext *context, Value /*thisObject*/, Value *args, int argc, bool directCall) +{ + if (argc < 1) + return Value::undefinedValue(); + + ExecutionContext *ctx = context; + + if (!directCall) { + // the context for eval should be the global scope + while (ctx->parent) + ctx = ctx->parent; + } + + if (!args[0].isString()) + return args[0]; + + const QString code = args[0].stringValue()->toQString(); + bool inheritContext = !ctx->strictMode; + + QQmlJS::VM::Function *f = parseSource(context, QStringLiteral("eval code"), + code, QQmlJS::Codegen::EvalCode, + (directCall && context->strictMode), inheritContext); + + if (!f) + return Value::undefinedValue(); + + bool strict = f->isStrict || (directCall && context->strictMode); + + uint size = requiredMemoryForExecutionContect(this, argc); + ExecutionContext *k = static_cast<ExecutionContext *>(alloca(size)); + + if (strict) { + ctx = k; + ctx->initCallContext(context, context->thisObject, this, args, argc); + } + + // set the correct strict mode flag on the context + bool cstrict = ctx->strictMode; + ctx->strictMode = strict; + + Value result = f->code(ctx, f->codeData); + + ctx->strictMode = cstrict; + + if (strict) + ctx->leaveCallContext(); + + return result; +} + + +Value EvalFunction::call(ExecutionContext *context, Value thisObject, Value *args, int argc) +{ + // indirect call + return evalCall(context, thisObject, args, argc, false); +} + +Value EvalFunction::construct(ExecutionContext *ctx) +{ + ctx->throwTypeError(); + return Value::undefinedValue(); +} + +QQmlJS::VM::Function *EvalFunction::parseSource(QQmlJS::VM::ExecutionContext *ctx, + const QString &fileName, const QString &source, + QQmlJS::Codegen::Mode mode, + bool strictMode, bool inheritContext) +{ + using namespace QQmlJS; + + MemoryManager::GCBlocker gcBlocker(ctx->engine->memoryManager); + + VM::ExecutionEngine *vm = ctx->engine; + IR::Module module; + VM::Function *globalCode = 0; + + { + QQmlJS::Engine ee, *engine = ⅇ + Lexer lexer(engine); + lexer.setCode(source, 1, false); + Parser parser(engine); + + const bool parsed = parser.parseProgram(); + + VM::DiagnosticMessage *error = 0, **errIt = &error; + foreach (const QQmlJS::DiagnosticMessage &m, parser.diagnosticMessages()) { + if (m.isError()) { + *errIt = new VM::DiagnosticMessage; + (*errIt)->fileName = fileName; + (*errIt)->offset = m.loc.offset; + (*errIt)->length = m.loc.length; + (*errIt)->startLine = m.loc.startLine; + (*errIt)->startColumn = m.loc.startColumn; + (*errIt)->type = VM::DiagnosticMessage::Error; + (*errIt)->message = m.message; + errIt = &(*errIt)->next; + } else { + std::cerr << qPrintable(fileName) << ':' << m.loc.startLine << ':' << m.loc.startColumn + << ": warning: " << qPrintable(m.message) << std::endl; + } + } + if (error) + ctx->throwSyntaxError(error); + + if (parsed) { + using namespace AST; + Program *program = AST::cast<Program *>(parser.rootNode()); + if (!program) { + // if parsing was successful, and we have no program, then + // we're done...: + return 0; + } + + QStringList inheritedLocals; + if (inheritContext) + for (String * const *i = ctx->variables(), * const *ei = i + ctx->variableCount(); i < ei; ++i) + inheritedLocals.append(*i ? (*i)->toQString() : QString()); + + Codegen cg(ctx, strictMode); + IR::Function *globalIRCode = cg(fileName, program, &module, mode, inheritedLocals); + QScopedPointer<EvalInstructionSelection> isel(ctx->engine->iselFactory->create(vm, &module)); + if (globalIRCode) + globalCode = isel->vmFunction(globalIRCode); + } + + if (! globalCode) + // ### should be a syntax error + __qmljs_throw_type_error(ctx); + } + + return globalCode; +} + +static inline int toInt(const QChar &qc, int R) +{ + ushort c = qc.unicode(); + int v = -1; + if (c >= '0' && c <= '9') + v = c - '0'; + else if (c >= 'A' && c <= 'Z') + v = c - 'A' + 10; + else if (c >= 'a' && c <= 'z') + v = c - 'a' + 10; + if (v >= 0 && v < R) + return v; + else + return -1; +} + +// parseInt [15.1.2.2] +Value GlobalFunctions::method_parseInt(ExecutionContext *context) +{ + Value string = context->argument(0); + Value radix = context->argument(1); + int R = radix.isUndefined() ? 0 : radix.toInt32(context); + + // [15.1.2.2] step by step: + String *inputString = string.toString(context); // 1 + QString trimmed = inputString->toQString().trimmed(); // 2 + const QChar *pos = trimmed.constData(); + const QChar *end = pos + trimmed.length(); + + int sign = 1; // 3 + if (pos != end) { + if (*pos == QLatin1Char('-')) + sign = -1; // 4 + if (*pos == QLatin1Char('-') || *pos == QLatin1Char('+')) + ++pos; // 5 + } + bool stripPrefix = true; // 7 + if (R) { // 8 + if (R < 2 || R > 36) + return Value::fromDouble(nan("")); // 8a + if (R != 16) + stripPrefix = false; // 8b + } else { // 9 + R = 10; // 9a + } + if (stripPrefix) { // 10 + if ((end - pos >= 2) + && (pos[0] == QLatin1Char('0')) + && (pos[1] == QLatin1Char('x') || pos[1] == QLatin1Char('X'))) { // 10a + pos += 2; + R = 16; + } + } + // 11: Z is progressively built below + // 13: this is handled by the toInt function + if (pos == end) // 12 + return Value::fromDouble(nan("")); + bool overflow = false; + qint64 v_overflow; + unsigned overflow_digit_count = 0; + int d = toInt(*pos++, R); + if (d == -1) + return Value::fromDouble(nan("")); + qint64 v = d; + while (pos != end) { + d = toInt(*pos++, R); + if (d == -1) + break; + if (overflow) { + if (overflow_digit_count == 0) { + v_overflow = v; + v = 0; + } + ++overflow_digit_count; + v = v * R + d; + } else { + qint64 vNew = v * R + d; + if (vNew < v) { + overflow = true; + --pos; + } else { + v = vNew; + } + } + } + + if (overflow) { + double result = (double) v_overflow * pow(R, overflow_digit_count); + result += v; + return Value::fromDouble(sign * result); + } else { + return Value::fromDouble(sign * (double) v); // 15 + } +} + +// parseFloat [15.1.2.3] +Value GlobalFunctions::method_parseFloat(ExecutionContext *context) +{ + Value string = context->argument(0); + + // [15.1.2.3] step by step: + String *inputString = string.toString(context); // 1 + QString trimmed = inputString->toQString().trimmed(); // 2 + + // 4: + if (trimmed.startsWith(QLatin1String("Infinity")) + || trimmed.startsWith(QLatin1String("+Infinity"))) + return Value::fromDouble(INFINITY); + if (trimmed.startsWith("-Infinity")) + return Value::fromDouble(-INFINITY); + QByteArray ba = trimmed.toLatin1(); + bool ok; + const char *begin = ba.constData(); + const char *end = 0; + double d = qstrtod(begin, &end, &ok); + if (end - begin == 0) + return Value::fromDouble(nan("")); // 3 + else + return Value::fromDouble(d); +} + +/// isNaN [15.1.2.4] +Value GlobalFunctions::method_isNaN(ExecutionContext *context) +{ + const Value &v = context->argument(0); + if (v.integerCompatible()) + return Value::fromBoolean(false); + + double d = v.toNumber(context); + return Value::fromBoolean(std::isnan(d)); +} + +/// isFinite [15.1.2.5] +Value GlobalFunctions::method_isFinite(ExecutionContext *context) +{ + const Value &v = context->argument(0); + if (v.integerCompatible()) + return Value::fromBoolean(true); + + double d = v.toNumber(context); + return Value::fromBoolean(std::isfinite(d)); +} + +/// decodeURI [15.1.3.1] +Value GlobalFunctions::method_decodeURI(ExecutionContext *parentCtx, Value, Value *argv, int argc) +{ + if (argc == 0) + return Value::undefinedValue(); + + QString uriString = argv[0].toString(parentCtx)->toQString(); + bool ok; + QString out = decode(uriString, QString::fromUtf8(uriReserved) + QString::fromUtf8("#"), &ok); + if (!ok) + parentCtx->throwURIError(Value::fromString(parentCtx, QStringLiteral("malformed URI sequence"))); + + return Value::fromString(parentCtx, out); +} + +/// decodeURIComponent [15.1.3.2] +Value GlobalFunctions::method_decodeURIComponent(ExecutionContext *parentCtx, Value, Value *argv, int argc) +{ + if (argc == 0) + return Value::undefinedValue(); + + QString uriString = argv[0].toString(parentCtx)->toQString(); + bool ok; + QString out = decode(uriString, QString(), &ok); + if (!ok) + parentCtx->throwURIError(Value::fromString(parentCtx, QStringLiteral("malformed URI sequence"))); + + return Value::fromString(parentCtx, out); +} + +/// encodeURI [15.1.3.3] +Value GlobalFunctions::method_encodeURI(ExecutionContext *parentCtx, Value, Value *argv, int argc) +{ + if (argc == 0) + return Value::undefinedValue(); + + QString uriString = argv[0].toString(parentCtx)->toQString(); + bool ok; + QString out = encode(uriString, QLatin1String(uriReserved) + QLatin1String(uriUnescaped) + QString::fromUtf8("#"), &ok); + if (!ok) + parentCtx->throwURIError(Value::fromString(parentCtx, QStringLiteral("malformed URI sequence"))); + + return Value::fromString(parentCtx, out); +} + +/// encodeURIComponent [15.1.3.4] +Value GlobalFunctions::method_encodeURIComponent(ExecutionContext *parentCtx, Value, Value *argv, int argc) +{ + if (argc == 0) + return Value::undefinedValue(); + + QString uriString = argv[0].toString(parentCtx)->toQString(); + bool ok; + QString out = encode(uriString, QString(uriUnescaped), &ok); + if (!ok) + parentCtx->throwURIError(Value::fromString(parentCtx, QStringLiteral("malformed URI sequence"))); + + return Value::fromString(parentCtx, out); +} + +Value GlobalFunctions::method_escape(ExecutionContext *context) +{ + if (!context->argumentCount) + return Value::fromString(context, QStringLiteral("undefined")); + + QString str = context->argument(0).toString(context)->toQString(); + return Value::fromString(context, escape(str)); +} + +Value GlobalFunctions::method_unescape(ExecutionContext *context) +{ + if (!context->argumentCount) + return Value::fromString(context, QStringLiteral("undefined")); + + QString str = context->argument(0).toString(context)->toQString(); + return Value::fromString(context, unescape(str)); +} diff --git a/src/v4/qv4globalobject.h b/src/v4/qv4globalobject.h new file mode 100644 index 0000000000..6a586c1802 --- /dev/null +++ b/src/v4/qv4globalobject.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4GLOBALOBJECT_H +#define QV4GLOBALOBJECT_H + +#include "qv4global.h" +#include "qv4functionobject.h" + +namespace QQmlJS { + +namespace VM { + +struct Q_V4_EXPORT EvalFunction : FunctionObject +{ + EvalFunction(ExecutionContext *scope); + + static QQmlJS::VM::Function *parseSource(QQmlJS::VM::ExecutionContext *ctx, + const QString &fileName, + const QString &source, + QQmlJS::Codegen::Mode mode, bool strictMode, + bool inheritContext); + + virtual Value call(ExecutionContext *context, Value thisObject, Value *args, int argc); + Value evalCall(ExecutionContext *context, Value thisObject, Value *args, int argc, bool directCall); + + Value construct(ExecutionContext *ctx); +}; + +struct GlobalFunctions +{ + static Value method_parseInt(ExecutionContext *context); + static Value method_parseFloat(ExecutionContext *context); + static Value method_isNaN(ExecutionContext *context); + static Value method_isFinite(ExecutionContext *context); + static Value method_decodeURI(ExecutionContext *parentCtx, Value thisObject, Value *argv, int argc); + static Value method_decodeURIComponent(ExecutionContext *parentCtx, Value thisObject, Value *argv, int argc); + static Value method_encodeURI(ExecutionContext *parentCtx, Value thisObject, Value *argv, int argc); + static Value method_encodeURIComponent(ExecutionContext *parentCtx, Value thisObject, Value *argv, int argc); + static Value method_escape(ExecutionContext *context); + static Value method_unescape(ExecutionContext *context); +}; + +} // namespace VM +} // namespace QQmlJS + +#endif // QMLJS_OBJECTS_H diff --git a/src/v4/qv4ir.cpp b/src/v4/qv4ir.cpp new file mode 100644 index 0000000000..1c2a9d9e62 --- /dev/null +++ b/src/v4/qv4ir.cpp @@ -0,0 +1,670 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4ir_p.h" +#include <private/qqmljsast_p.h> + +#include <QtCore/qtextstream.h> +#include <QtCore/qdebug.h> +#include <cmath> +#include <cassert> + +QT_BEGIN_NAMESPACE + +namespace QQmlJS { +namespace IR { + +const char *typeName(Type t) +{ + switch (t) { + case UndefinedType: return "undefined"; + case NullType: return "null"; + case BoolType: return "bool"; + case NumberType: return "number"; + default: return "invalid"; + } +} + +const char *opname(AluOp op) +{ + switch (op) { + case OpInvalid: return "?"; + + case OpIfTrue: return "(bool)"; + case OpNot: return "!"; + case OpUMinus: return "-"; + case OpUPlus: return "+"; + case OpCompl: return "~"; + case OpIncrement: return "++"; + case OpDecrement: return "--"; + + case OpBitAnd: return "&"; + case OpBitOr: return "|"; + case OpBitXor: return "^"; + + case OpAdd: return "+"; + case OpSub: return "-"; + case OpMul: return "*"; + case OpDiv: return "/"; + case OpMod: return "%"; + + case OpLShift: return "<<"; + case OpRShift: return ">>"; + case OpURShift: return ">>>"; + + case OpGt: return ">"; + case OpLt: return "<"; + case OpGe: return ">="; + case OpLe: return "<="; + case OpEqual: return "=="; + case OpNotEqual: return "!="; + case OpStrictEqual: return "==="; + case OpStrictNotEqual: return "!=="; + + case OpInstanceof: return "instanceof"; + case OpIn: return "in"; + + case OpAnd: return "&&"; + case OpOr: return "||"; + + default: return "?"; + + } // switch +} + +AluOp binaryOperator(int op) +{ + switch (static_cast<QSOperator::Op>(op)) { + case QSOperator::Add: return OpAdd; + case QSOperator::And: return OpAnd; + case QSOperator::BitAnd: return OpBitAnd; + case QSOperator::BitOr: return OpBitOr; + case QSOperator::BitXor: return OpBitXor; + case QSOperator::Div: return OpDiv; + case QSOperator::Equal: return OpEqual; + case QSOperator::Ge: return OpGe; + case QSOperator::Gt: return OpGt; + case QSOperator::Le: return OpLe; + case QSOperator::LShift: return OpLShift; + case QSOperator::Lt: return OpLt; + case QSOperator::Mod: return OpMod; + case QSOperator::Mul: return OpMul; + case QSOperator::NotEqual: return OpNotEqual; + case QSOperator::Or: return OpOr; + case QSOperator::RShift: return OpRShift; + case QSOperator::StrictEqual: return OpStrictEqual; + case QSOperator::StrictNotEqual: return OpStrictNotEqual; + case QSOperator::Sub: return OpSub; + case QSOperator::URShift: return OpURShift; + case QSOperator::InstanceOf: return OpInstanceof; + case QSOperator::In: return OpIn; + default: return OpInvalid; + } +} + +void Const::dump(QTextStream &out) +{ + switch (type) { + case QQmlJS::IR::UndefinedType: + out << "undefined"; + break; + case QQmlJS::IR::NullType: + out << "null"; + break; + case QQmlJS::IR::BoolType: + out << (value ? "true" : "false"); + break; + default: + out << QString::number(value, 'g', 16); + break; + } +} + +void String::dump(QTextStream &out) +{ + out << '"' << escape(*value) << '"'; +} + +QString String::escape(const QString &s) +{ + QString r; + for (int i = 0; i < s.length(); ++i) { + const QChar ch = s.at(i); + if (ch == QLatin1Char('\n')) + r += QStringLiteral("\\n"); + else if (ch == QLatin1Char('\r')) + r += QStringLiteral("\\r"); + else if (ch == QLatin1Char('\\')) + r += QStringLiteral("\\\\"); + else if (ch == QLatin1Char('"')) + r += QStringLiteral("\\\""); + else if (ch == QLatin1Char('\'')) + r += QStringLiteral("\\'"); + else + r += ch; + } + return r; +} + +void RegExp::dump(QTextStream &out) +{ + char f[3]; + int i = 0; + if (flags & RegExp_Global) + f[i++] = 'g'; + if (flags & RegExp_IgnoreCase) + f[i++] = 'i'; + if (flags & RegExp_Multiline) + f[i++] = 'm'; + f[i] = 0; + + out << '/' << *value << '/' << f; +} + +void Name::init(const QString *id, quint32 line, quint32 column) +{ + this->id = id; + this->builtin = builtin_invalid; + this->line = line; + this->column = column; +} + +void Name::init(Builtin builtin, quint32 line, quint32 column) +{ + this->id = 0; + this->builtin = builtin; + this->line = line; + this->column = column; +} + +static const char *builtin_to_string(Name::Builtin b) +{ + switch (b) { + case Name::builtin_invalid: + return "builtin_invalid"; + case Name::builtin_typeof: + return "builtin_typeof"; + case Name::builtin_delete: + return "builtin_delete"; + case Name::builtin_postincrement: + return "builtin_postincrement"; + case Name::builtin_postdecrement: + return "builtin_postdecrement"; + case Name::builtin_throw: + return "builtin_throw"; + case Name::builtin_create_exception_handler: + return "builtin_create_exception_handler"; + case Name::builtin_delete_exception_handler: + return "builtin_delete_exception_handler"; + case Name::builtin_get_exception: + return "builtin_get_exception"; + case IR::Name::builtin_foreach_iterator_object: + return "builtin_foreach_iterator_object"; + case IR::Name::builtin_foreach_next_property_name: + return "builtin_foreach_next_property_name"; + case IR::Name::builtin_push_with_scope: + return "builtin_push_with_scope"; + case IR::Name::builtin_pop_scope: + return "builtin_pop_scope"; + case IR::Name::builtin_declare_vars: + return "builtin_declare_vars"; + case IR::Name::builtin_define_property: + return "builtin_define_property"; + case IR::Name::builtin_define_array_property: + return "builtin_define_array_property"; + case IR::Name::builtin_define_getter_setter: + return "builtin_define_getter_setter"; + } + return "builtin_(###FIXME)"; +}; + + +void Name::dump(QTextStream &out) +{ + if (id) + out << *id; + else + out << builtin_to_string(builtin); +} + +void Temp::dump(QTextStream &out) +{ + if (index < 0) { + out << '#' << -(index + 1); // negative and 1-based. + } else { + out << '%' << index; // temp + } +} + +void Closure::dump(QTextStream &out) +{ + QString name = value->name ? *value->name : QString(); + if (name.isEmpty()) + name.sprintf("%p", value); + out << "closure(" << name << ')'; +} + +void Unop::dump(QTextStream &out) +{ + out << opname(op); + expr->dump(out); +} + +void Binop::dump(QTextStream &out) +{ + left->dump(out); + out << ' ' << opname(op) << ' '; + right->dump(out); +} + +void Call::dump(QTextStream &out) +{ + base->dump(out); + out << '('; + for (ExprList *it = args; it; it = it->next) { + if (it != args) + out << ", "; + it->expr->dump(out); + } + out << ')'; +} + +void New::dump(QTextStream &out) +{ + out << "new "; + base->dump(out); + out << '('; + for (ExprList *it = args; it; it = it->next) { + if (it != args) + out << ", "; + it->expr->dump(out); + } + out << ')'; +} + +void Subscript::dump(QTextStream &out) +{ + base->dump(out); + out << '['; + index->dump(out); + out << ']'; +} + +void Member::dump(QTextStream &out) +{ + base->dump(out); + out << '.' << *name; +} + +void Exp::dump(QTextStream &out, Mode) +{ + out << "(void) "; + expr->dump(out); + out << ';'; +} + +void Enter::dump(QTextStream &out, Mode) +{ + out << "%enter("; + expr->dump(out); + out << ");"; +} + +void Leave::dump(QTextStream &out, Mode) +{ + out << "%leave"; + out << ';'; +} + +void Move::dump(QTextStream &out, Mode) +{ + target->dump(out); + out << ' '; + if (op != OpInvalid) + out << opname(op); + out << "= "; +// if (source->type != target->type) +// out << typeName(source->type) << "_to_" << typeName(target->type) << '('; + source->dump(out); +// if (source->type != target->type) +// out << ')'; + out << ';'; +} + +void Jump::dump(QTextStream &out, Mode mode) +{ + Q_UNUSED(mode); + out << "goto " << 'L' << target->index << ';'; +} + +void CJump::dump(QTextStream &out, Mode mode) +{ + Q_UNUSED(mode); + out << "if ("; + cond->dump(out); + if (mode == HIR) + out << ") goto " << 'L' << iftrue->index << "; else goto " << 'L' << iffalse->index << ';'; + else + out << ") goto " << 'L' << iftrue->index << ";"; +} + +void Ret::dump(QTextStream &out, Mode) +{ + out << "return"; + if (expr) { + out << ' '; + expr->dump(out); + } + out << ';'; +} + +Function *Module::newFunction(const QString &name, Function *outer) +{ + Function *f = new Function(this, name); + functions.append(f); + if (!outer) { + assert(!rootFunction); + rootFunction = f; + } else { + outer->nestedFunctions.append(f); + } + return f; +} + +Module::~Module() +{ + foreach (Function *f, functions) { + delete f; + } +} + +Function::~Function() +{ + // destroy the Stmt::Data blocks manually, because memory pool cleanup won't + // call the Stmt destructors. + foreach (IR::BasicBlock *b, basicBlocks) + foreach (IR::Stmt *s, b->statements) + s->destroyData(); + + qDeleteAll(basicBlocks); + pool = 0; + module = 0; +} + + +const QString *Function::newString(const QString &text) +{ + return &*strings.insert(text); +} + +BasicBlock *Function::newBasicBlock(BasicBlockInsertMode mode) +{ + BasicBlock *block = new BasicBlock(this); + return mode == InsertBlock ? insertBasicBlock(block) : block; +} + +void Function::dump(QTextStream &out, Stmt::Mode mode) +{ + QString n = name ? *name : QString(); + if (n.isEmpty()) + n.sprintf("%p", this); + out << "function " << n << "() {" << endl; + foreach (const QString *formal, formals) + out << "\treceive " << *formal << ';' << endl; + foreach (const QString *local, locals) + out << "\tlocal " << *local << ';' << endl; + foreach (BasicBlock *bb, basicBlocks) + bb->dump(out, mode); + out << '}' << endl; +} + +unsigned BasicBlock::newTemp() +{ + return function->tempCount++; +} + +Temp *BasicBlock::TEMP(int index) +{ + Temp *e = function->New<Temp>(); + e->init(index); + return e; +} + +Expr *BasicBlock::CONST(Type type, double value) +{ + Const *e = function->New<Const>(); + e->init(type, value); + return e; +} + +Expr *BasicBlock::STRING(const QString *value) +{ + String *e = function->New<String>(); + e->init(value); + return e; +} + +Expr *BasicBlock::REGEXP(const QString *value, int flags) +{ + RegExp *e = function->New<RegExp>(); + e->init(value, flags); + return e; +} + +Name *BasicBlock::NAME(const QString &id, quint32 line, quint32 column) +{ + Name *e = function->New<Name>(); + e->init(function->newString(id), line, column); + return e; +} + +Name *BasicBlock::NAME(Name::Builtin builtin, quint32 line, quint32 column) +{ + Name *e = function->New<Name>(); + e->init(builtin, line, column); + return e; +} + +Closure *BasicBlock::CLOSURE(Function *function) +{ + Closure *clos = function->New<Closure>(); + clos->init(function); + return clos; +} + +Expr *BasicBlock::UNOP(AluOp op, Temp *expr) +{ + Unop *e = function->New<Unop>(); + e->init(op, expr); + return e; +} + +Expr *BasicBlock::BINOP(AluOp op, Expr *left, Expr *right) +{ + Binop *e = function->New<Binop>(); + e->init(op, left, right); + return e; +} + +Expr *BasicBlock::CALL(Expr *base, ExprList *args) +{ + Call *e = function->New<Call>(); + e->init(base, args); + return e; +} + +Expr *BasicBlock::NEW(Expr *base, ExprList *args) +{ + New *e = function->New<New>(); + e->init(base, args); + return e; +} + +Expr *BasicBlock::SUBSCRIPT(Temp *base, Temp *index) +{ + Subscript *e = function->New<Subscript>(); + e->init(base, index); + return e; +} + +Expr *BasicBlock::MEMBER(Temp *base, const QString *name) +{ + Member*e = function->New<Member>(); + e->init(base, name); + return e; +} + +Stmt *BasicBlock::EXP(Expr *expr) +{ + if (isTerminated()) + return 0; + + Exp *s = function->New<Exp>(); + s->init(expr); + statements.append(s); + return s; +} + +Stmt *BasicBlock::ENTER(Expr *expr) +{ + if (isTerminated()) + return 0; + + Enter *s = function->New<Enter>(); + s->init(expr); + statements.append(s); + return s; +} + +Stmt *BasicBlock::LEAVE() +{ + if (isTerminated()) + return 0; + + Leave *s = function->New<Leave>(); + s->init(); + statements.append(s); + return s; +} + +Stmt *BasicBlock::MOVE(Expr *target, Expr *source, AluOp op) +{ + if (isTerminated()) + return 0; + + Move *s = function->New<Move>(); + s->init(target, source, op); + statements.append(s); + return s; +} + +Stmt *BasicBlock::JUMP(BasicBlock *target) +{ + if (isTerminated()) + return 0; + + Jump *s = function->New<Jump>(); + s->init(target); + statements.append(s); + + assert(! out.contains(target)); + out.append(target); + + assert(! target->in.contains(this)); + target->in.append(this); + + return s; +} + +Stmt *BasicBlock::CJUMP(Expr *cond, BasicBlock *iftrue, BasicBlock *iffalse) +{ + if (isTerminated()) + return 0; + + if (iftrue == iffalse) { + MOVE(TEMP(newTemp()), cond); + return JUMP(iftrue); + } + + CJump *s = function->New<CJump>(); + s->init(cond, iftrue, iffalse); + statements.append(s); + + assert(! out.contains(iftrue)); + out.append(iftrue); + + assert(! iftrue->in.contains(this)); + iftrue->in.append(this); + + assert(! out.contains(iffalse)); + out.append(iffalse); + + assert(! iffalse->in.contains(this)); + iffalse->in.append(this); + + return s; +} + +Stmt *BasicBlock::RET(Temp *expr) +{ + if (isTerminated()) + return 0; + + Ret *s = function->New<Ret>(); + s->init(expr); + statements.append(s); + return s; +} + +void BasicBlock::dump(QTextStream &out, Stmt::Mode mode) +{ + out << 'L' << index << ':' << endl; + foreach (Stmt *s, statements) { + out << '\t'; + s->dump(out, mode); + out << endl; + } +} + +} // end of namespace IR +} // end of namespace QQmlJS + +QT_END_NAMESPACE diff --git a/src/v4/qv4ir_p.h b/src/v4/qv4ir_p.h new file mode 100644 index 0000000000..4e60fa96dd --- /dev/null +++ b/src/v4/qv4ir_p.h @@ -0,0 +1,724 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4IR_P_H +#define QV4IR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qv4global.h" +#include <private/qqmljsmemorypool_p.h> + +#include <QtCore/QVector> +#include <QtCore/QString> +#include <QtCore/QBitArray> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +class QTextStream; +class QQmlType; + +namespace QQmlJS { + +namespace VM { +struct ExecutionContext; +struct Value; +} + +namespace IR { + +struct BasicBlock; +struct Function; +struct Module; + +struct Stmt; +struct Expr; + +// expressions +struct Const; +struct String; +struct RegExp; +struct Name; +struct Temp; +struct Closure; +struct Unop; +struct Binop; +struct Call; +struct New; +struct Subscript; +struct Member; + +// statements +struct Exp; +struct Enter; +struct Leave; +struct Move; +struct Jump; +struct CJump; +struct Ret; + +enum AluOp { + OpInvalid = 0, + + OpIfTrue, + OpNot, + OpUMinus, + OpUPlus, + OpCompl, + OpIncrement, + OpDecrement, + + OpBitAnd, + OpBitOr, + OpBitXor, + + OpAdd, + OpSub, + OpMul, + OpDiv, + OpMod, + + OpLShift, + OpRShift, + OpURShift, + + OpGt, + OpLt, + OpGe, + OpLe, + OpEqual, + OpNotEqual, + OpStrictEqual, + OpStrictNotEqual, + + OpInstanceof, + OpIn, + + OpAnd, + OpOr, + + LastAluOp = OpOr +}; +AluOp binaryOperator(int op); +const char *opname(IR::AluOp op); + +enum Type { + UndefinedType, + NullType, + BoolType, + NumberType +}; + +struct ExprVisitor { + virtual ~ExprVisitor() {} + virtual void visitConst(Const *) = 0; + virtual void visitString(String *) = 0; + virtual void visitRegExp(RegExp *) = 0; + virtual void visitName(Name *) = 0; + virtual void visitTemp(Temp *) = 0; + virtual void visitClosure(Closure *) = 0; + virtual void visitUnop(Unop *) = 0; + virtual void visitBinop(Binop *) = 0; + virtual void visitCall(Call *) = 0; + virtual void visitNew(New *) = 0; + virtual void visitSubscript(Subscript *) = 0; + virtual void visitMember(Member *) = 0; +}; + +struct StmtVisitor { + virtual ~StmtVisitor() {} + virtual void visitExp(Exp *) = 0; + virtual void visitEnter(Enter *) = 0; + virtual void visitLeave(Leave *) = 0; + virtual void visitMove(Move *) = 0; + virtual void visitJump(Jump *) = 0; + virtual void visitCJump(CJump *) = 0; + virtual void visitRet(Ret *) = 0; +}; + +struct Expr { + virtual ~Expr() {} + virtual void accept(ExprVisitor *) = 0; + virtual bool isLValue() { return false; } + virtual Const *asConst() { return 0; } + virtual String *asString() { return 0; } + virtual RegExp *asRegExp() { return 0; } + virtual Name *asName() { return 0; } + virtual Temp *asTemp() { return 0; } + virtual Closure *asClosure() { return 0; } + virtual Unop *asUnop() { return 0; } + virtual Binop *asBinop() { return 0; } + virtual Call *asCall() { return 0; } + virtual New *asNew() { return 0; } + virtual Subscript *asSubscript() { return 0; } + virtual Member *asMember() { return 0; } + virtual void dump(QTextStream &out) = 0; +}; + +struct ExprList { + Expr *expr; + ExprList *next; + + void init(Expr *expr, ExprList *next = 0) + { + this->expr = expr; + this->next = next; + } +}; + +struct Const: Expr { + Type type; + double value; + + void init(Type type, double value) + { + this->type = type; + this->value = value; + } + + virtual void accept(ExprVisitor *v) { v->visitConst(this); } + virtual Const *asConst() { return this; } + + virtual void dump(QTextStream &out); +}; + +struct String: Expr { + const QString *value; + + void init(const QString *value) + { + this->value = value; + } + + virtual void accept(ExprVisitor *v) { v->visitString(this); } + virtual String *asString() { return this; } + + virtual void dump(QTextStream &out); + static QString escape(const QString &s); +}; + +struct RegExp: Expr { + // needs to be compatible with the flags in the lexer + enum Flags { + RegExp_Global = 0x01, + RegExp_IgnoreCase = 0x02, + RegExp_Multiline = 0x04 + }; + + const QString *value; + int flags; + + void init(const QString *value, int flags) + { + this->value = value; + this->flags = flags; + } + + virtual void accept(ExprVisitor *v) { v->visitRegExp(this); } + virtual RegExp *asRegExp() { return this; } + + virtual void dump(QTextStream &out); +}; + +struct Name: Expr { + enum Builtin { + builtin_invalid, + builtin_typeof, + builtin_delete, + builtin_postincrement, + builtin_postdecrement, + builtin_throw, + builtin_create_exception_handler, + builtin_delete_exception_handler, + builtin_get_exception, + builtin_foreach_iterator_object, + builtin_foreach_next_property_name, + builtin_push_with_scope, + builtin_pop_scope, + builtin_declare_vars, + builtin_define_property, + builtin_define_array_property, + builtin_define_getter_setter + }; + + const QString *id; + Builtin builtin; + quint32 line; + quint32 column; + + void init(const QString *id, quint32 line, quint32 column); + void init(Builtin builtin, quint32 line, quint32 column); + + virtual void accept(ExprVisitor *v) { v->visitName(this); } + virtual bool isLValue() { return true; } + virtual Name *asName() { return this; } + + virtual void dump(QTextStream &out); +}; + +struct Temp: Expr { + int index; + + void init(int index) + { + this->index = index; + } + + virtual void accept(ExprVisitor *v) { v->visitTemp(this); } + virtual bool isLValue() { return true; } + virtual Temp *asTemp() { return this; } + + virtual void dump(QTextStream &out); +}; + +struct Closure: Expr { + Function *value; + + void init(Function *value) + { + this->value = value; + } + + virtual void accept(ExprVisitor *v) { v->visitClosure(this); } + virtual Closure *asClosure() { return this; } + + virtual void dump(QTextStream &out); +}; + +struct Unop: Expr { + AluOp op; + Temp *expr; + + void init(AluOp op, Temp *expr) + { + this->op = op; + this->expr = expr; + } + + virtual void accept(ExprVisitor *v) { v->visitUnop(this); } + virtual Unop *asUnop() { return this; } + + virtual void dump(QTextStream &out); +}; + +struct Binop: Expr { + AluOp op; + Expr *left; // Temp or Const + Expr *right; // Temp or Const + + void init(AluOp op, Expr *left, Expr *right) + { + this->op = op; + this->left = left; + this->right = right; + } + + virtual void accept(ExprVisitor *v) { v->visitBinop(this); } + virtual Binop *asBinop() { return this; } + + virtual void dump(QTextStream &out); +}; + +struct Call: Expr { + Expr *base; // Name, Member, Temp + ExprList *args; // List of Temps + + void init(Expr *base, ExprList *args) + { + this->base = base; + this->args = args; + } + + Expr *onlyArgument() const { + if (args && ! args->next) + return args->expr; + return 0; + } + + virtual void accept(ExprVisitor *v) { v->visitCall(this); } + virtual Call *asCall() { return this; } + + virtual void dump(QTextStream &out); +}; + +struct New: Expr { + Expr *base; // Name, Member, Temp + ExprList *args; // List of Temps + + void init(Expr *base, ExprList *args) + { + this->base = base; + this->args = args; + } + + Expr *onlyArgument() const { + if (args && ! args->next) + return args->expr; + return 0; + } + + virtual void accept(ExprVisitor *v) { v->visitNew(this); } + virtual New *asNew() { return this; } + + virtual void dump(QTextStream &out); +}; + +struct Subscript: Expr { + Temp *base; + Temp *index; + + void init(Temp *base, Temp *index) + { + this->base = base; + this->index = index; + } + + virtual void accept(ExprVisitor *v) { v->visitSubscript(this); } + virtual bool isLValue() { return true; } + virtual Subscript *asSubscript() { return this; } + + virtual void dump(QTextStream &out); +}; + +struct Member: Expr { + Temp *base; + const QString *name; + + void init(Temp *base, const QString *name) + { + this->base = base; + this->name = name; + } + + virtual void accept(ExprVisitor *v) { v->visitMember(this); } + virtual bool isLValue() { return true; } + virtual Member *asMember() { return this; } + + virtual void dump(QTextStream &out); +}; + +struct Stmt { + enum Mode { + HIR, + MIR + }; + + struct Data { + QVector<unsigned> uses; + QVector<unsigned> defs; + QBitArray liveIn; + QBitArray liveOut; + }; + + Data *d; + + Stmt(): d(0) {} + virtual ~Stmt() { Q_UNREACHABLE(); } + virtual Stmt *asTerminator() { return 0; } + + virtual void accept(StmtVisitor *) = 0; + virtual Exp *asExp() { return 0; } + virtual Move *asMove() { return 0; } + virtual Enter *asEnter() { return 0; } + virtual Leave *asLeave() { return 0; } + virtual Jump *asJump() { return 0; } + virtual CJump *asCJump() { return 0; } + virtual Ret *asRet() { return 0; } + virtual void dump(QTextStream &out, Mode mode = HIR) = 0; + + void destroyData() { + delete d; + d = 0; + } +}; + +struct Exp: Stmt { + Expr *expr; + + void init(Expr *expr) + { + this->expr = expr; + } + + virtual void accept(StmtVisitor *v) { v->visitExp(this); } + virtual Exp *asExp() { return this; } + + virtual void dump(QTextStream &out, Mode); +}; + +struct Move: Stmt { + Expr *target; // LHS - Temp, Name, Member or Subscript + Expr *source; + AluOp op; + + void init(Expr *target, Expr *source, AluOp op) + { + this->target = target; + this->source = source; + this->op = op; + } + + virtual void accept(StmtVisitor *v) { v->visitMove(this); } + virtual Move *asMove() { return this; } + + virtual void dump(QTextStream &out, Mode); +}; + +struct Enter: Stmt { + Expr *expr; + + void init(Expr *expr) + { + this->expr = expr; + } + + virtual void accept(StmtVisitor *v) { v->visitEnter(this); } + virtual Enter *asEnter() { return this; } + + virtual void dump(QTextStream &out, Mode); +}; + +struct Leave: Stmt { + void init() {} + + virtual void accept(StmtVisitor *v) { v->visitLeave(this); } + virtual Leave *asLeave() { return this; } + + virtual void dump(QTextStream &out, Mode); +}; + +struct Jump: Stmt { + BasicBlock *target; + + void init(BasicBlock *target) + { + this->target = target; + } + + virtual Stmt *asTerminator() { return this; } + + virtual void accept(StmtVisitor *v) { v->visitJump(this); } + virtual Jump *asJump() { return this; } + + virtual void dump(QTextStream &out, Mode mode); +}; + +struct CJump: Stmt { + Expr *cond; // Temp, Binop + BasicBlock *iftrue; + BasicBlock *iffalse; + + void init(Expr *cond, BasicBlock *iftrue, BasicBlock *iffalse) + { + this->cond = cond; + this->iftrue = iftrue; + this->iffalse = iffalse; + } + + virtual Stmt *asTerminator() { return this; } + + virtual void accept(StmtVisitor *v) { v->visitCJump(this); } + virtual CJump *asCJump() { return this; } + + virtual void dump(QTextStream &out, Mode mode); +}; + +struct Ret: Stmt { + Temp *expr; + + void init(Temp *expr) + { + this->expr = expr; + } + + virtual Stmt *asTerminator() { return this; } + + virtual void accept(StmtVisitor *v) { v->visitRet(this); } + virtual Ret *asRet() { return this; } + + virtual void dump(QTextStream &out, Mode); +}; + +struct Q_V4_EXPORT Module { + MemoryPool pool; + QVector<Function *> functions; + Function *rootFunction; + + Function *newFunction(const QString &name, Function *outer); + + Module() : rootFunction(0) {} + ~Module(); +}; + +struct Function { + Module *module; + MemoryPool *pool; + const QString *name; + QVector<BasicBlock *> basicBlocks; + int tempCount; + int maxNumberOfArguments; + QSet<QString> strings; + QList<const QString *> formals; + QList<const QString *> locals; + QVector<Function *> nestedFunctions; + + int insideWith; + + uint hasDirectEval: 1; + uint usesArgumentsObject : 1; + uint isStrict: 1; + uint unused : 29; + + template <typename _Tp> _Tp *New() { return new (pool->allocate(sizeof(_Tp))) _Tp(); } + + Function(Module *module, const QString &name) + : module(module) + , pool(&module->pool) + , tempCount(0) + , maxNumberOfArguments(0) + , insideWith(0) + , hasDirectEval(false) + , usesArgumentsObject(false) + , isStrict(false) + , unused(0) + { this->name = newString(name); } + + ~Function(); + + enum BasicBlockInsertMode { + InsertBlock, + DontInsertBlock + }; + + BasicBlock *newBasicBlock(BasicBlockInsertMode mode = InsertBlock); + const QString *newString(const QString &text); + + void RECEIVE(const QString &name) { formals.append(newString(name)); } + void LOCAL(const QString &name) { locals.append(newString(name)); } + + inline BasicBlock *insertBasicBlock(BasicBlock *block) { basicBlocks.append(block); return block; } + + void dump(QTextStream &out, Stmt::Mode mode = Stmt::HIR); +}; + +struct BasicBlock { + Function *function; + QVector<Stmt *> statements; + QVector<BasicBlock *> in; + QVector<BasicBlock *> out; + QBitArray liveIn; + QBitArray liveOut; + int index; + int offset; + + BasicBlock(Function *function): function(function), index(-1), offset(-1) {} + ~BasicBlock() {} + + template <typename Instr> inline Instr i(Instr i) { statements.append(i); return i; } + + inline bool isEmpty() const { + return statements.isEmpty(); + } + + inline Stmt *terminator() const { + if (! statements.isEmpty() && statements.at(statements.size() - 1)->asTerminator() != 0) + return statements.at(statements.size() - 1); + return 0; + } + + inline bool isTerminated() const { + if (terminator() != 0) + return true; + return false; + } + + unsigned newTemp(); + + Temp *TEMP(int index); + + Expr *CONST(Type type, double value); + Expr *STRING(const QString *value); + Expr *REGEXP(const QString *value, int flags); + + Name *NAME(const QString &id, quint32 line, quint32 column); + Name *NAME(Name::Builtin builtin, quint32 line, quint32 column); + + Closure *CLOSURE(Function *function); + + Expr *UNOP(AluOp op, Temp *expr); + Expr *BINOP(AluOp op, Expr *left, Expr *right); + Expr *CALL(Expr *base, ExprList *args = 0); + Expr *NEW(Expr *base, ExprList *args = 0); + Expr *SUBSCRIPT(Temp *base, Temp *index); + Expr *MEMBER(Temp *base, const QString *name); + + Stmt *EXP(Expr *expr); + Stmt *ENTER(Expr *expr); + Stmt *LEAVE(); + + Stmt *MOVE(Expr *target, Expr *source, AluOp op = IR::OpInvalid); + + Stmt *JUMP(BasicBlock *target); + Stmt *CJUMP(Expr *cond, BasicBlock *iftrue, BasicBlock *iffalse); + Stmt *RET(Temp *expr); + + void dump(QTextStream &out, Stmt::Mode mode = Stmt::HIR); +}; + +} // end of namespace IR + +} // end of namespace QQmlJS + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QV4IR_P_H diff --git a/src/v4/qv4isel_llvm.cpp b/src/v4/qv4isel_llvm.cpp new file mode 100644 index 0000000000..091936813d --- /dev/null +++ b/src/v4/qv4isel_llvm.cpp @@ -0,0 +1,1388 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-parameter" +#endif // __clang__ + +#include <llvm/Analysis/Passes.h> +#include <llvm/Analysis/Verifier.h> +#include <llvm/Assembly/PrintModulePass.h> +#include <llvm/Bitcode/ReaderWriter.h> +#include <llvm/ExecutionEngine/ExecutionEngine.h> +#include <llvm/ExecutionEngine/JIT.h> +#include <llvm/ExecutionEngine/JITMemoryManager.h> +#include <llvm/Support/FormattedStream.h> +#include <llvm/Support/Host.h> +#include <llvm/Support/MemoryBuffer.h> +#include <llvm/Support/raw_ostream.h> +#include <llvm/Support/system_error.h> +#include <llvm/Support/TargetRegistry.h> +#include <llvm/Support/TargetSelect.h> +#include <llvm/Target/TargetMachine.h> +#include <llvm/Transforms/Scalar.h> +#include <llvm/Transforms/IPO.h> +#include <llvm/Linker.h> + +#ifdef __clang__ +# pragma clang diagnostic pop +#endif // __clang__ + +#include <QtCore/QFileInfo> +#include <QtCore/QLibrary> +#include <QtCore/QStringList> +#include <QtCore/QTextStream> +#include <cstdio> +#include <iostream> + +// These includes have to come last, because WTF/Platform.h defines some macros +// with very unfriendly names that collide with class fields in LLVM. +#include "qv4isel_llvm_p.h" +#include "qv4_llvm_p.h" +#include "qv4ir_p.h" +#include "qv4string.h" +#include "qv4global.h" + +namespace QQmlJS { + +Q_V4_EXPORT int compileWithLLVM(IR::Module *module, const QString &fileName, LLVMOutputType outputType, int (*exec)(void *)) +{ + Q_ASSERT(module); + Q_ASSERT(exec || outputType != LLVMOutputJit); + + // TODO: should this be done here? + LLVMInitializeX86TargetInfo(); + LLVMInitializeX86Target(); + LLVMInitializeX86AsmPrinter(); + LLVMInitializeX86AsmParser(); + LLVMInitializeX86Disassembler(); + LLVMInitializeX86TargetMC(); + + //---- + + llvm::InitializeNativeTarget(); + LLVM::InstructionSelection llvmIsel(llvm::getGlobalContext()); + + const QString moduleName = QFileInfo(fileName).fileName(); + llvm::StringRef moduleId(moduleName.toUtf8().constData()); + llvm::Module *llvmModule = new llvm::Module(moduleId, llvmIsel.getContext()); + + if (outputType == LLVMOutputJit) { + // The execution engine takes ownership of the model. No need to delete it anymore. + std::string errStr; + llvm::ExecutionEngine *execEngine = llvm::EngineBuilder(llvmModule) +// .setUseMCJIT(true) + .setErrorStr(&errStr).create(); + if (!execEngine) { + std::cerr << "Could not create LLVM JIT: " << errStr << std::endl; + return EXIT_FAILURE; + } + + llvm::FunctionPassManager functionPassManager(llvmModule); + // Set up the optimizer pipeline. Start with registering info about how the + // target lays out data structures. + functionPassManager.add(new llvm::DataLayout(*execEngine->getDataLayout())); + // Promote allocas to registers. + functionPassManager.add(llvm::createPromoteMemoryToRegisterPass()); + // Provide basic AliasAnalysis support for GVN. + functionPassManager.add(llvm::createBasicAliasAnalysisPass()); + // Do simple "peephole" optimizations and bit-twiddling optzns. + functionPassManager.add(llvm::createInstructionCombiningPass()); + // Reassociate expressions. + functionPassManager.add(llvm::createReassociatePass()); + // Eliminate Common SubExpressions. + functionPassManager.add(llvm::createGVNPass()); + // Simplify the control flow graph (deleting unreachable blocks, etc). + functionPassManager.add(llvm::createCFGSimplificationPass()); + + functionPassManager.doInitialization(); + + llvmIsel.buildLLVMModule(module, llvmModule, &functionPassManager); + + llvm::Function *entryPoint = llvmModule->getFunction("%entry"); + Q_ASSERT(entryPoint); + void *funcPtr = execEngine->getPointerToFunction(entryPoint); + return exec(funcPtr); + } else { + llvm::FunctionPassManager functionPassManager(llvmModule); + // Set up the optimizer pipeline. + // Promote allocas to registers. + functionPassManager.add(llvm::createPromoteMemoryToRegisterPass()); + // Provide basic AliasAnalysis support for GVN. + functionPassManager.add(llvm::createBasicAliasAnalysisPass()); + // Do simple "peephole" optimizations and bit-twiddling optzns. + functionPassManager.add(llvm::createInstructionCombiningPass()); + // Reassociate expressions. + functionPassManager.add(llvm::createReassociatePass()); + // Eliminate Common SubExpressions. + functionPassManager.add(llvm::createGVNPass()); + // Simplify the control flow graph (deleting unreachable blocks, etc). + functionPassManager.add(llvm::createCFGSimplificationPass()); + + functionPassManager.doInitialization(); + + llvmIsel.buildLLVMModule(module, llvmModule, &functionPassManager); + + // TODO: if output type is .ll, print the module to file + + const std::string triple = llvm::sys::getDefaultTargetTriple(); + + std::string err; + const llvm::Target *target = llvm::TargetRegistry::lookupTarget(triple, err); + if (! err.empty()) { + std::cerr << err << ", triple: " << triple << std::endl; + assert(!"cannot create target for the host triple"); + } + + std::string cpu; + std::string features; + llvm::TargetOptions options; + llvm::TargetMachine *targetMachine = target->createTargetMachine(triple, cpu, features, options, llvm::Reloc::PIC_); + assert(targetMachine); + + llvm::TargetMachine::CodeGenFileType ft; + QString ofName; + + if (outputType == LLVMOutputObject) { + ft = llvm::TargetMachine::CGFT_ObjectFile; + ofName = fileName + QLatin1String(".o"); + } else if (outputType == LLVMOutputAssembler) { + ft = llvm::TargetMachine::CGFT_AssemblyFile; + ofName = fileName + QLatin1String(".s"); + } else { + // ft is not used. + ofName = fileName + QLatin1String(".ll"); + } + + llvm::raw_fd_ostream dest(ofName.toUtf8().constData(), err, llvm::raw_fd_ostream::F_Binary); + llvm::formatted_raw_ostream destf(dest); + if (!err.empty()) { + std::cerr << err << std::endl; + delete llvmModule; + } + + llvm::PassManager globalPassManager; + globalPassManager.add(llvm::createScalarReplAggregatesPass()); + globalPassManager.add(llvm::createInstructionCombiningPass()); + globalPassManager.add(llvm::createGlobalOptimizerPass()); + globalPassManager.add(llvm::createFunctionInliningPass(25)); +// globalPassManager.add(llvm::createFunctionInliningPass(125)); + + if (outputType == LLVMOutputObject || outputType == LLVMOutputAssembler) { + if (targetMachine->addPassesToEmitFile(globalPassManager, destf, ft)) { + std::cerr << err << " (probably no DataLayout in TargetMachine)" << std::endl; + } else { + globalPassManager.run(*llvmModule); + + destf.flush(); + dest.flush(); + } + } else { // .ll + globalPassManager.run(*llvmModule); + llvmModule->print(destf, 0); + + destf.flush(); + dest.flush(); + } + + delete llvmModule; + return EXIT_SUCCESS; + } +} + +} // QQmlJS + +using namespace QQmlJS; +using namespace QQmlJS::LLVM; + +namespace { +QTextStream qerr(stderr, QIODevice::WriteOnly); +} + +InstructionSelection::InstructionSelection(llvm::LLVMContext &context) + : llvm::IRBuilder<>(context) + , _llvmModule(0) + , _llvmFunction(0) + , _llvmValue(0) + , _numberTy(0) + , _valueTy(0) + , _contextPtrTy(0) + , _stringPtrTy(0) + , _functionTy(0) + , _allocaInsertPoint(0) + , _function(0) + , _block(0) + , _fpm(0) +{ +} + +void InstructionSelection::buildLLVMModule(IR::Module *module, llvm::Module *llvmModule, llvm::FunctionPassManager *fpm) +{ + qSwap(_llvmModule, llvmModule); + qSwap(_fpm, fpm); + + _numberTy = getDoubleTy(); + + std::string err; + + llvm::OwningPtr<llvm::MemoryBuffer> buffer; + qDebug()<<"llvm runtime:"<<LLVM_RUNTIME; + llvm::error_code ec = llvm::MemoryBuffer::getFile(llvm::StringRef(LLVM_RUNTIME), buffer); + if (ec) { + qWarning() << ec.message().c_str(); + assert(!"cannot load QML/JS LLVM runtime, you can generate the runtime with the command `make llvm_runtime'"); + } + + llvm::Module *llvmRuntime = llvm::getLazyBitcodeModule(buffer.get(), getContext(), &err); + if (! err.empty()) { + qWarning() << err.c_str(); + assert(!"cannot load QML/JS LLVM runtime"); + } + + err.clear(); + llvm::Linker::LinkModules(_llvmModule, llvmRuntime, llvm::Linker::DestroySource, &err); + if (! err.empty()) { + qWarning() << err.c_str(); + assert(!"cannot link the QML/JS LLVM runtime"); + } + + _valueTy = _llvmModule->getTypeByName("struct.QQmlJS::VM::Value"); + _contextPtrTy = _llvmModule->getTypeByName("struct.QQmlJS::VM::ExecutionContext")->getPointerTo(); + _stringPtrTy = _llvmModule->getTypeByName("struct.QQmlJS::VM::String")->getPointerTo(); + + { + llvm::Type *args[] = { _contextPtrTy }; + _functionTy = llvm::FunctionType::get(getVoidTy(), llvm::makeArrayRef(args), false); + } + + + foreach (IR::Function *function, module->functions) + (void) compileLLVMFunction(function); + qSwap(_fpm, fpm); + qSwap(_llvmModule, llvmModule); +} + +void InstructionSelection::callBuiltinInvalid(IR::Name *func, IR::ExprList *args, IR::Temp *result) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::callBuiltinTypeofMember(IR::Temp *base, const QString &name, IR::Temp *result) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::callBuiltinTypeofSubscript(IR::Temp *base, IR::Temp *index, IR::Temp *result) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::callBuiltinTypeofName(const QString &name, IR::Temp *result) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::callBuiltinTypeofValue(IR::Temp *value, IR::Temp *result) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::callBuiltinDeleteMember(IR::Temp *base, const QString &name, IR::Temp *result) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::callBuiltinDeleteSubscript(IR::Temp *base, IR::Temp *index, IR::Temp *result) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::callBuiltinDeleteName(const QString &name, IR::Temp *result) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::callBuiltinDeleteValue(IR::Temp *result) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::callBuiltinPostDecrementMember(IR::Temp *base, const QString &name, IR::Temp *result) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::callBuiltinPostDecrementSubscript(IR::Temp *base, IR::Temp *index, IR::Temp *result) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::callBuiltinPostDecrementName(const QString &name, IR::Temp *result) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::callBuiltinPostDecrementValue(IR::Temp *value, IR::Temp *result) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::callBuiltinPostIncrementMember(IR::Temp *base, const QString &name, IR::Temp *result) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::callBuiltinPostIncrementSubscript(IR::Temp *base, IR::Temp *index, IR::Temp *result) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::callBuiltinPostIncrementName(const QString &name, IR::Temp *result) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::callBuiltinPostIncrementValue(IR::Temp *value, IR::Temp *result) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::callBuiltinThrow(IR::Temp *arg) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::callBuiltinCreateExceptionHandler(IR::Temp *result) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::callBuiltinDeleteExceptionHandler() +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::callBuiltinGetException(IR::Temp *result) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::callBuiltinForeachIteratorObject(IR::Temp *arg, IR::Temp *result) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::callBuiltinForeachNextPropertyname(IR::Temp *arg, IR::Temp *result) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::callBuiltinPushWithScope(IR::Temp *arg) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::callBuiltinPopScope() +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::callBuiltinDeclareVar(bool deletable, const QString &name) +{ + llvm::ConstantInt *isDeletable = getInt1(deletable != 0); + llvm::Value *varName = getIdentifier(name); + CreateCall3(getRuntimeFunction("__qmljs_builtin_declare_var"), + _llvmFunction->arg_begin(), isDeletable, varName); +} + +void InstructionSelection::callBuiltinDefineGetterSetter(IR::Temp *object, const QString &name, IR::Temp *getter, IR::Temp *setter) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::callBuiltinDefineProperty(IR::Temp *object, const QString &name, IR::Temp *value) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::callBuiltinDefineArrayProperty(IR::Temp *object, int index, IR::Temp *value) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::callValue(IR::Temp *value, IR::ExprList *args, IR::Temp *result) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::callProperty(IR::Temp *base, const QString &name, IR::ExprList *args, IR::Temp *result) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::callSubscript(IR::Temp *base, IR::Temp *index, IR::ExprList *args, IR::Temp *result) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::constructActivationProperty(IR::Name *func, + IR::ExprList *args, + IR::Temp *result) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::constructProperty(IR::Temp *base, const QString &name, IR::ExprList *args, IR::Temp *result) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::constructValue(IR::Temp *value, IR::ExprList *args, IR::Temp *result) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::loadThisObject(IR::Temp *temp) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::loadConst(IR::Const *con, IR::Temp *temp) +{ + llvm::Value *target = getLLVMTemp(temp); + llvm::Value *source = CreateLoad(createValue(con)); + CreateStore(source, target); +} + +void InstructionSelection::loadString(const QString &str, IR::Temp *targetTemp) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::loadRegexp(IR::RegExp *sourceRegexp, IR::Temp *targetTemp) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::getActivationProperty(const QString &name, IR::Temp *temp) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); +} + +void InstructionSelection::setActivationProperty(IR::Expr *source, const QString &targetName) +{ + llvm::Value *name = getIdentifier(targetName); + llvm::Value *src = toValuePtr(source); + CreateCall3(getRuntimeFunction("__qmljs_llvm_set_activation_property"), + _llvmFunction->arg_begin(), name, src); +} + +void InstructionSelection::initClosure(IR::Closure *closure, IR::Temp *target) +{ + IR::Function *f = closure->value; + QString name; + if (f->name) + name = *f->name; + + llvm::Value *args[] = { + _llvmFunction->arg_begin(), + getLLVMTemp(target), + getIdentifier(name), + getInt1(f->hasDirectEval), + getInt1(f->usesArgumentsObject), + getInt1(f->isStrict), + getInt1(!f->nestedFunctions.isEmpty()), + genStringList(f->formals, "formals", "formal"), + getInt32(f->formals.size()), + genStringList(f->locals, "locals", "local"), + getInt32(f->locals.size()) + }; + llvm::Function *callee = _llvmModule->getFunction("__qmljs_llvm_init_closure"); + CreateCall(callee, args); +} + +void InstructionSelection::getProperty(IR::Temp *sourceBase, const QString &sourceName, IR::Temp *target) +{ + llvm::Value *base = getLLVMTempReference(sourceBase); + llvm::Value *name = getIdentifier(sourceName); + llvm::Value *t = getLLVMTemp(target); + CreateCall4(getRuntimeFunction("__qmljs_llvm_get_property"), + _llvmFunction->arg_begin(), t, base, name); +} + +void InstructionSelection::setProperty(IR::Expr *source, IR::Temp *targetBase, const QString &targetName) +{ + llvm::Value *base = getLLVMTempReference(targetBase); + llvm::Value *name = getIdentifier(targetName); + llvm::Value *src = toValuePtr(source); + CreateCall4(getRuntimeFunction("__qmljs_llvm_set_property"), + _llvmFunction->arg_begin(), base, name, src); +} + +void InstructionSelection::getElement(IR::Temp *sourceBase, IR::Temp *sourceIndex, IR::Temp *target) +{ + // TODO + assert(!"TODO!"); + Q_UNREACHABLE(); + + llvm::Value *base = getLLVMTempReference(sourceBase); + llvm::Value *index = getLLVMTempReference(sourceIndex); + llvm::Value *t = getLLVMTemp(target); + CreateCall4(getRuntimeFunction("__qmljs_llvm_get_element"), + _llvmFunction->arg_begin(), t, base, index); +} + +void InstructionSelection::setElement(IR::Expr *source, IR::Temp *targetBase, IR::Temp *targetIndex) +{ + llvm::Value *base = getLLVMTempReference(targetBase); + llvm::Value *index = getLLVMTempReference(targetIndex); + llvm::Value *src = toValuePtr(source); + CreateCall4(getRuntimeFunction("__qmljs_llvm_set_element"), + _llvmFunction->arg_begin(), base, index, src); +} + +void InstructionSelection::copyValue(IR::Temp *sourceTemp, IR::Temp *targetTemp) +{ + llvm::Value *t = getLLVMTemp(targetTemp); + llvm::Value *s = getLLVMTemp(sourceTemp); + CreateStore(s, t); +} + +void InstructionSelection::unop(IR::AluOp oper, IR::Temp *sourceTemp, IR::Temp *targetTemp) +{ + const char *opName = 0; + switch (oper) { + case IR::OpNot: opName = "__qmljs_not"; break; + case IR::OpUMinus: opName = "__qmljs_uminus"; break; + case IR::OpUPlus: opName = "__qmljs_uplus"; break; + case IR::OpCompl: opName = "__qmljs_compl"; break; + case IR::OpIncrement: opName = "__qmljs_increment"; break; + case IR::OpDecrement: opName = "__qmljs_decrement"; break; + default: assert(!"unreachable"); break; + } + + if (opName) { + llvm::Value *t = getLLVMTemp(targetTemp); + llvm::Value *s = getLLVMTemp(sourceTemp); + CreateCall3(getRuntimeFunction(opName), + _llvmFunction->arg_begin(), t, s); + } +} + +void InstructionSelection::binop(IR::AluOp oper, IR::Expr *leftSource, IR::Expr *rightSource, IR::Temp *target) +{ + const char *opName = 0; + switch (oper) { + case IR::OpBitAnd: opName = "__qmljs_llvm_bit_and"; break; + case IR::OpBitOr: opName = "__qmljs_llvm_bit_or"; break; + case IR::OpBitXor: opName = "__qmljs_llvm_bit_xor"; break; + case IR::OpAdd: opName = "__qmljs_llvm_add"; break; + case IR::OpSub: opName = "__qmljs_llvm_sub"; break; + case IR::OpMul: opName = "__qmljs_llvm_mul"; break; + case IR::OpDiv: opName = "__qmljs_llvm_div"; break; + case IR::OpMod: opName = "__qmljs_llvm_mod"; break; + case IR::OpLShift: opName = "__qmljs_llvm_shl"; break; + case IR::OpRShift: opName = "__qmljs_llvm_shr"; break; + case IR::OpURShift: opName = "__qmljs_llvm_ushr"; break; + default: + Q_UNREACHABLE(); + break; + } + + if (opName) { + llvm::Value *t = getLLVMTemp(target); + llvm::Value *s1 = toValuePtr(leftSource); + llvm::Value *s2 = toValuePtr(rightSource); + CreateCall4(getRuntimeFunction(opName), + _llvmFunction->arg_begin(), t, s1, s2); + return; + } +} + +void InstructionSelection::inplaceNameOp(IR::AluOp oper, IR::Expr *sourceExpr, const QString &targetName) +{ + const char *opName = 0; + switch (oper) { + case IR::OpBitAnd: opName = "__qmljs_llvm_inplace_bit_and_name"; break; + case IR::OpBitOr: opName = "__qmljs_llvm_inplace_bit_or_name"; break; + case IR::OpBitXor: opName = "__qmljs_llvm_inplace_bit_xor_name"; break; + case IR::OpAdd: opName = "__qmljs_llvm_inplace_add_name"; break; + case IR::OpSub: opName = "__qmljs_llvm_inplace_sub_name"; break; + case IR::OpMul: opName = "__qmljs_llvm_inplace_mul_name"; break; + case IR::OpDiv: opName = "__qmljs_llvm_inplace_div_name"; break; + case IR::OpMod: opName = "__qmljs_llvm_inplace_mod_name"; break; + case IR::OpLShift: opName = "__qmljs_llvm_inplace_shl_name"; break; + case IR::OpRShift: opName = "__qmljs_llvm_inplace_shr_name"; break; + case IR::OpURShift: opName = "__qmljs_llvm_inplace_ushr_name"; break; + default: + Q_UNREACHABLE(); + break; + } + + if (opName) { + llvm::Value *dst = getIdentifier(targetName); + llvm::Value *src = toValuePtr(sourceExpr); + CreateCall3(getRuntimeFunction(opName), + _llvmFunction->arg_begin(), dst, src); + return; + } +} + +void InstructionSelection::inplaceElementOp(IR::AluOp oper, IR::Expr *sourceExpr, IR::Temp *targetBaseTemp, IR::Temp *targetIndexTemp) +{ + const char *opName = 0; + switch (oper) { + case IR::OpBitAnd: opName = "__qmljs_llvm_inplace_bit_and_element"; break; + case IR::OpBitOr: opName = "__qmljs_llvm_inplace_bit_or_element"; break; + case IR::OpBitXor: opName = "__qmljs_llvm_inplace_bit_xor_element"; break; + case IR::OpAdd: opName = "__qmljs_llvm_inplace_add_element"; break; + case IR::OpSub: opName = "__qmljs_llvm_inplace_sub_element"; break; + case IR::OpMul: opName = "__qmljs_llvm_inplace_mul_element"; break; + case IR::OpDiv: opName = "__qmljs_llvm_inplace_div_element"; break; + case IR::OpMod: opName = "__qmljs_llvm_inplace_mod_element"; break; + case IR::OpLShift: opName = "__qmljs_llvm_inplace_shl_element"; break; + case IR::OpRShift: opName = "__qmljs_llvm_inplace_shr_element"; break; + case IR::OpURShift: opName = "__qmljs_llvm_inplace_ushr_element"; break; + default: + Q_UNREACHABLE(); + break; + } + + if (opName) { + llvm::Value *base = getLLVMTemp(targetBaseTemp); + llvm::Value *index = getLLVMTemp(targetIndexTemp); + llvm::Value *value = toValuePtr(sourceExpr); + CreateCall4(getRuntimeFunction(opName), + _llvmFunction->arg_begin(), base, index, value); + } +} + +void InstructionSelection::inplaceMemberOp(IR::AluOp oper, IR::Expr *source, IR::Temp *targetBase, const QString &targetName) +{ + const char *opName = 0; + switch (oper) { + case IR::OpBitAnd: opName = "__qmljs_llvm_inplace_bit_and_member"; break; + case IR::OpBitOr: opName = "__qmljs_llvm_inplace_bit_or_member"; break; + case IR::OpBitXor: opName = "__qmljs_llvm_inplace_bit_xor_member"; break; + case IR::OpAdd: opName = "__qmljs_llvm_inplace_add_member"; break; + case IR::OpSub: opName = "__qmljs_llvm_inplace_sub_member"; break; + case IR::OpMul: opName = "__qmljs_llvm_inplace_mul_member"; break; + case IR::OpDiv: opName = "__qmljs_llvm_inplace_div_member"; break; + case IR::OpMod: opName = "__qmljs_llvm_inplace_mod_member"; break; + case IR::OpLShift: opName = "__qmljs_llvm_inplace_shl_member"; break; + case IR::OpRShift: opName = "__qmljs_llvm_inplace_shr_member"; break; + case IR::OpURShift: opName = "__qmljs_llvm_inplace_ushr_member"; break; + default: + Q_UNREACHABLE(); + break; + } + + if (opName) { + llvm::Value *base = getLLVMTemp(targetBase); + llvm::Value *member = getIdentifier(targetName); + llvm::Value *value = toValuePtr(source); + CreateCall4(getRuntimeFunction(opName), + _llvmFunction->arg_begin(), value, base, member); + } +} + +llvm::Function *InstructionSelection::getLLVMFunction(IR::Function *function) +{ + llvm::Function *&f = _functionMap[function]; + if (! f) { + QString name = QStringLiteral("__qmljs_native_"); + if (function->name) { + if (*function->name == QStringLiteral("%entry")) + name = *function->name; + else + name += *function->name; + } + f = llvm::Function::Create(_functionTy, llvm::Function::ExternalLinkage, // ### make it internal + qPrintable(name), _llvmModule); + } + return f; +} + +llvm::Function *InstructionSelection::compileLLVMFunction(IR::Function *function) +{ + llvm::Function *llvmFunction = getLLVMFunction(function); + + QHash<IR::BasicBlock *, llvm::BasicBlock *> blockMap; + QVector<llvm::Value *> tempMap; + + qSwap(_llvmFunction, llvmFunction); + qSwap(_function, function); + qSwap(_tempMap, tempMap); + qSwap(_blockMap, blockMap); + + // create the LLVM blocks + foreach (IR::BasicBlock *block, _function->basicBlocks) + (void) getLLVMBasicBlock(block); + + // entry block + SetInsertPoint(getLLVMBasicBlock(_function->basicBlocks.first())); + + llvm::Instruction *allocaInsertPoint = new llvm::BitCastInst(llvm::UndefValue::get(getInt32Ty()), + getInt32Ty(), "", GetInsertBlock()); + qSwap(_allocaInsertPoint, allocaInsertPoint); + + for (int i = 0; i < _function->tempCount; ++i) { + llvm::AllocaInst *t = newLLVMTemp(_valueTy); + _tempMap.append(t); + } + + foreach (llvm::Value *t, _tempMap) { + CreateStore(llvm::Constant::getNullValue(_valueTy), t); + } + +// CreateCall(getRuntimeFunction("__qmljs_llvm_init_this_object"), +// _llvmFunction->arg_begin()); + + foreach (IR::BasicBlock *block, _function->basicBlocks) { + qSwap(_block, block); + SetInsertPoint(getLLVMBasicBlock(_block)); + foreach (IR::Stmt *s, _block->statements) + s->accept(this); + qSwap(_block, block); + } + + qSwap(_allocaInsertPoint, allocaInsertPoint); + + allocaInsertPoint->eraseFromParent(); + + qSwap(_blockMap, blockMap); + qSwap(_tempMap, tempMap); + qSwap(_function, function); + qSwap(_llvmFunction, llvmFunction); + + // Validate the generated code, checking for consistency. + llvm::verifyFunction(*llvmFunction); + // Optimize the function. + if (_fpm) + _fpm->run(*llvmFunction); + + return llvmFunction; +} + +llvm::BasicBlock *InstructionSelection::getLLVMBasicBlock(IR::BasicBlock *block) +{ + llvm::BasicBlock *&llvmBlock = _blockMap[block]; + if (! llvmBlock) + llvmBlock = llvm::BasicBlock::Create(getContext(), llvm::Twine(), + _llvmFunction); + return llvmBlock; +} + +llvm::Value *InstructionSelection::getLLVMTempReference(IR::Expr *expr) +{ + if (IR::Temp *t = expr->asTemp()) + return getLLVMTemp(t); + + assert(!"TODO!"); + llvm::Value *addr = newLLVMTemp(_valueTy); +// CreateStore(getLLVMValue(expr), addr); + return addr; +} + +llvm::Value *InstructionSelection::getLLVMCondition(IR::Expr *expr) +{ + llvm::Value *value = 0; + if (IR::Temp *t = expr->asTemp()) { + value = getLLVMTemp(t); + } else { + assert(!"TODO!"); + Q_UNREACHABLE(); + +#if 0 + value = getLLVMValue(expr); + if (! value) { + Q_UNIMPLEMENTED(); + return getInt1(false); + } + + llvm::Value *tmp = newLLVMTemp(_valueTy); + CreateStore(value, tmp); + value = tmp; +#endif + } + + return CreateCall2(getRuntimeFunction("__qmljs_llvm_to_boolean"), + _llvmFunction->arg_begin(), + value); +} + +llvm::Value *InstructionSelection::getLLVMTemp(IR::Temp *temp) +{ + if (temp->index < 0) { + const int index = -temp->index -1; + return CreateCall2(getRuntimeFunction("__qmljs_llvm_get_argument"), + _llvmFunction->arg_begin(), getInt32(index)); + } + + return _tempMap[temp->index]; +} + +llvm::Value *InstructionSelection::getStringPtr(const QString &s) +{ + llvm::Value *&value = _stringMap[s]; + if (! value) { + const QByteArray bytes = s.toUtf8(); + value = CreateGlobalStringPtr(llvm::StringRef(bytes.constData(), bytes.size())); + _stringMap[s] = value; + } + return value; +} + +llvm::Value *InstructionSelection::getIdentifier(const QString &s) +{ + llvm::Value *str = getStringPtr(s); + llvm::Value *id = CreateCall2(getRuntimeFunction("__qmljs_identifier_from_utf8"), + _llvmFunction->arg_begin(), str); + return id; +} + +void InstructionSelection::visitJump(IR::Jump *s) +{ + CreateBr(getLLVMBasicBlock(s->target)); +} + +void InstructionSelection::visitCJump(IR::CJump *s) +{ + CreateCondBr(getLLVMCondition(s->cond), + getLLVMBasicBlock(s->iftrue), + getLLVMBasicBlock(s->iffalse)); +} + +void InstructionSelection::visitRet(IR::Ret *s) +{ + IR::Temp *t = s->expr->asTemp(); + assert(t != 0); + llvm::Value *result = getLLVMTemp(t); + llvm::Value *ctx = _llvmFunction->arg_begin(); + CreateCall2(getRuntimeFunction("__qmljs_llvm_return"), ctx, result); + CreateRetVoid(); +} + +#if 0 +void InstructionSelection::visitString(IR::String *e) +{ + llvm::Value *tmp = newLLVMTemp(_valueTy); + CreateCall3(getRuntimeFunction("__qmljs_llvm_init_string"), + _llvmFunction->arg_begin(), tmp, + getStringPtr(*e->value)); + _llvmValue = CreateLoad(tmp); +} +#endif + +llvm::AllocaInst *InstructionSelection::newLLVMTemp(llvm::Type *type, llvm::Value *size) +{ + llvm::AllocaInst *addr = new llvm::AllocaInst(type, size, llvm::Twine(), _allocaInsertPoint); + return addr; +} + +llvm::Value * InstructionSelection::genArguments(IR::ExprList *exprs, int &argc) +{ + llvm::Value *args = 0; + + argc = 0; + for (IR::ExprList *it = exprs; it; it = it->next) + ++argc; + + if (argc) + args = newLLVMTemp(_valueTy, getInt32(argc)); + else + args = llvm::Constant::getNullValue(_valueTy->getPointerTo()); + + int i = 0; + for (IR::ExprList *it = exprs; it; it = it->next) { +// llvm::Value *arg = getLLVMValue(it->expr); +// CreateStore(arg, CreateConstGEP1_32(args, i++)); + } + + return args; +} + +void InstructionSelection::genCallMember(IR::Call *e, llvm::Value *result) +{ + if (! result) + result = newLLVMTemp(_valueTy); + + IR::Member *m = e->base->asMember(); + llvm::Value *thisObject = getLLVMTemp(m->base->asTemp()); + llvm::Value *name = getIdentifier(*m->name); + + int argc = 0; + llvm::Value *args = genArguments(e->args, argc); + + llvm::Value *actuals[] = { + _llvmFunction->arg_begin(), + result, + thisObject, + name, + args, + getInt32(argc) + }; + + CreateCall(getRuntimeFunction("__qmljs_llvm_call_property"), llvm::ArrayRef<llvm::Value *>(actuals)); + _llvmValue = CreateLoad(result); +} + +void InstructionSelection::genConstructMember(IR::New *e, llvm::Value *result) +{ + if (! result) + result = newLLVMTemp(_valueTy); + + IR::Member *m = e->base->asMember(); + llvm::Value *thisObject = getLLVMTemp(m->base->asTemp()); + llvm::Value *name = getIdentifier(*m->name); + + int argc = 0; + llvm::Value *args = genArguments(e->args, argc); + + llvm::Value *actuals[] = { + _llvmFunction->arg_begin(), + result, + thisObject, + name, + args, + getInt32(argc) + }; + + CreateCall(getRuntimeFunction("__qmljs_llvm_construct_property"), llvm::ArrayRef<llvm::Value *>(actuals)); + _llvmValue = CreateLoad(result); +} + +void InstructionSelection::genCallTemp(IR::Call *e, llvm::Value *result) +{ + if (! result) + result = newLLVMTemp(_valueTy); + + llvm::Value *func = getLLVMTempReference(e->base); + + int argc = 0; + llvm::Value *args = genArguments(e->args, argc); + + llvm::Value *thisObject = llvm::Constant::getNullValue(_valueTy->getPointerTo()); + + llvm::Value *actuals[] = { + _llvmFunction->arg_begin(), + result, + thisObject, + func, + args, + getInt32(argc) + }; + + CreateCall(getRuntimeFunction("__qmljs_llvm_call_value"), actuals); + + _llvmValue = CreateLoad(result); +} + +void InstructionSelection::genConstructTemp(IR::New *e, llvm::Value *result) +{ + if (! result) + result = newLLVMTemp(_valueTy); + + llvm::Value *func = getLLVMTempReference(e->base); + + int argc = 0; + llvm::Value *args = genArguments(e->args, argc); + + llvm::Value *actuals[] = { + _llvmFunction->arg_begin(), + result, + func, + args, + getInt32(argc) + }; + + CreateCall(getRuntimeFunction("__qmljs_llvm_construct_value"), actuals); + + _llvmValue = CreateLoad(result); +} + +void InstructionSelection::genCallName(IR::Call *e, llvm::Value *result) +{ + IR::Name *base = e->base->asName(); + + if (! result) + result = newLLVMTemp(_valueTy); + + if (! base->id) { + switch (base->builtin) { + case IR::Name::builtin_invalid: + break; + + case IR::Name::builtin_typeof: + CreateCall3(getRuntimeFunction("__qmljs_llvm_typeof"), + _llvmFunction->arg_begin(), result, getLLVMTempReference(e->args->expr)); + _llvmValue = CreateLoad(result); + return; + + case IR::Name::builtin_throw: + CreateCall2(getRuntimeFunction("__qmljs_llvm_throw"), + _llvmFunction->arg_begin(), getLLVMTempReference(e->args->expr)); + _llvmValue = llvm::UndefValue::get(_valueTy); + return; + + case IR::Name::builtin_create_exception_handler: + CreateCall2(getRuntimeFunction("__qmljs_llvm_create_exception_handler"), + _llvmFunction->arg_begin(), result); + _llvmValue = CreateLoad(result); + return; + + case IR::Name::builtin_delete_exception_handler: + CreateCall(getRuntimeFunction("__qmljs_llvm_delete_exception_handler"), + _llvmFunction->arg_begin()); + return; + + case IR::Name::builtin_get_exception: + CreateCall2(getRuntimeFunction("__qmljs_llvm_get_exception"), + _llvmFunction->arg_begin(), result); + _llvmValue = CreateLoad(result); + return; + + case IR::Name::builtin_foreach_iterator_object: + CreateCall3(getRuntimeFunction("__qmljs_llvm_foreach_iterator_object"), + _llvmFunction->arg_begin(), result, getLLVMTempReference(e->args->expr)); + _llvmValue = CreateLoad(result); + return; + + case IR::Name::builtin_foreach_next_property_name: + CreateCall2(getRuntimeFunction("__qmljs_llvm_foreach_next_property_name"), + result, getLLVMTempReference(e->args->expr)); + _llvmValue = CreateLoad(result); + return; + + case IR::Name::builtin_delete: { + if (IR::Subscript *subscript = e->args->expr->asSubscript()) { + CreateCall4(getRuntimeFunction("__qmljs_llvm_delete_subscript"), + _llvmFunction->arg_begin(), + result, + getLLVMTempReference(subscript->base), + getLLVMTempReference(subscript->index)); + _llvmValue = CreateLoad(result); + return; + } else if (IR::Member *member = e->args->expr->asMember()) { + CreateCall4(getRuntimeFunction("__qmljs_llvm_delete_member"), + _llvmFunction->arg_begin(), + result, + getLLVMTempReference(member->base), + getIdentifier(*member->name)); + _llvmValue = CreateLoad(result); + return; + } else if (IR::Name *name = e->args->expr->asName()) { + CreateCall3(getRuntimeFunction("__qmljs_llvm_delete_property"), + _llvmFunction->arg_begin(), + result, + getIdentifier(*name->id)); + _llvmValue = CreateLoad(result); + return; + } else { + CreateCall3(getRuntimeFunction("__qmljs_llvm_delete_value"), + _llvmFunction->arg_begin(), + result, + getLLVMTempReference(e->args->expr)); + _llvmValue = CreateLoad(result); + return; + } + } break; + + default: + Q_UNREACHABLE(); + } + } else { + llvm::Value *name = getIdentifier(*base->id); + + int argc = 0; + llvm::Value *args = genArguments(e->args, argc); + + CreateCall5(getRuntimeFunction("__qmljs_llvm_call_activation_property"), + _llvmFunction->arg_begin(), result, name, args, getInt32(argc)); + + _llvmValue = CreateLoad(result); + } +} + +void InstructionSelection::genConstructName(IR::New *e, llvm::Value *result) +{ + IR::Name *base = e->base->asName(); + + if (! result) + result = newLLVMTemp(_valueTy); + + if (! base->id) { + Q_UNREACHABLE(); + } else { + llvm::Value *name = getIdentifier(*base->id); + + int argc = 0; + llvm::Value *args = genArguments(e->args, argc); + + CreateCall5(getRuntimeFunction("__qmljs_llvm_construct_activation_property"), + _llvmFunction->arg_begin(), result, name, args, getInt32(argc)); + + _llvmValue = CreateLoad(result); + } +} + +#if 0 +void InstructionSelection::visitCall(IR::Call *e) +{ + if (e->base->asMember()) { + genCallMember(e); + } else if (e->base->asTemp()) { + genCallTemp(e); + } else if (e->base->asName()) { + genCallName(e); + } else if (IR::Temp *t = e->base->asTemp()) { + llvm::Value *base = getLLVMTemp(t); + + int argc = 0; + llvm::Value *args = genArguments(e->args, argc); + + llvm::Value *result = newLLVMTemp(_valueTy); + CreateStore(llvm::Constant::getNullValue(_valueTy), result); + CreateCall5(getRuntimeFunction("__qmljs_llvm_call_value"), + _llvmFunction->arg_begin(), result, base, args, getInt32(argc)); + _llvmValue = CreateLoad(result); + } else { + Q_UNIMPLEMENTED(); + } +} +#endif + +#if 0 +void InstructionSelection::visitNew(IR::New *e) +{ + if (e->base->asMember()) { + genConstructMember(e); + } else if (e->base->asTemp()) { + genConstructTemp(e); + } else if (e->base->asName()) { + genConstructName(e); + } else if (IR::Temp *t = e->base->asTemp()) { + llvm::Value *base = getLLVMTemp(t); + + int argc = 0; + llvm::Value *args = genArguments(e->args, argc); + + llvm::Value *result = newLLVMTemp(_valueTy); + CreateStore(llvm::Constant::getNullValue(_valueTy), result); + CreateCall5(getRuntimeFunction("__qmljs_llvm_construct_value"), + _llvmFunction->arg_begin(), result, base, args, getInt32(argc)); + _llvmValue = CreateLoad(result); + } else { + Q_UNIMPLEMENTED(); + } +} +#endif + +#if 0 +void InstructionSelection::visitSubscript(IR::Subscript *e) +{ + llvm::Value *result = newLLVMTemp(_valueTy); + llvm::Value *base = getLLVMTempReference(e->base); + llvm::Value *index = getLLVMTempReference(e->index); + CreateCall4(getRuntimeFunction("__qmljs_llvm_get_element"), + _llvmFunction->arg_begin(), result, base, index); + _llvmValue = CreateLoad(result); +} +#endif + +#if 0 +void InstructionSelection::visitMember(IR::Member *e) +{ + llvm::Value *result = newLLVMTemp(_valueTy); + llvm::Value *base = getLLVMTempReference(e->base); + llvm::Value *name = getIdentifier(*e->name); + + CreateCall4(getRuntimeFunction("__qmljs_llvm_get_property"), + _llvmFunction->arg_begin(), result, base, name); + _llvmValue = CreateLoad(result); +} +#endif + +llvm::Function *InstructionSelection::getRuntimeFunction(llvm::StringRef str) +{ + llvm::Function *func = _llvmModule->getFunction(str); + if (!func) { + std::cerr << "Cannot find runtime function \"" + << str.str() << "\"!" << std::endl; + assert(func); + } + return func; +} + +llvm::Value *InstructionSelection::createValue(IR::Const *e) +{ + llvm::Value *tmp = newLLVMTemp(_valueTy); + + switch (e->type) { + case IR::UndefinedType: + CreateCall(getRuntimeFunction("__qmljs_llvm_init_undefined"), tmp); + break; + + case IR::NullType: + CreateCall(getRuntimeFunction("__qmljs_llvm_init_null"), tmp); + break; + + case IR::BoolType: + CreateCall2(getRuntimeFunction("__qmljs_llvm_init_boolean"), tmp, + getInt1(e->value ? 1 : 0)); + break; + + case IR::NumberType: + CreateCall2(getRuntimeFunction("__qmljs_llvm_init_number"), tmp, + llvm::ConstantFP::get(_numberTy, e->value)); + break; + + default: + Q_UNREACHABLE(); + } + + return tmp; +} + +llvm::Value *InstructionSelection::toValuePtr(IR::Expr *e) +{ + if (IR::Temp *t = e->asTemp()) { + return getLLVMTemp(t); + } else if (IR::Const *c = e->asConst()) { + return createValue(c); + } else { + Q_UNREACHABLE(); + } +} + +llvm::Value *InstructionSelection::genStringList(const QList<const QString *> &strings, const char *arrayName, const char *elementName) +{ + llvm::Value *array = CreateAlloca(_stringPtrTy, getInt32(strings.size()), + arrayName); + for (int i = 0, ei = strings.size(); i < ei; ++i) { + llvm::Value *el; + if (const QString *string = strings.at(i)) + el = getIdentifier(*string); + else + el = llvm::Constant::getNullValue(_stringPtrTy); + llvm::Value *ptr = CreateGEP(array, getInt32(i), elementName); + CreateStore(el, ptr); + } + + return array; +} diff --git a/src/v4/qv4isel_llvm_p.h b/src/v4/qv4isel_llvm_p.h new file mode 100644 index 0000000000..46fd2fefa4 --- /dev/null +++ b/src/v4/qv4isel_llvm_p.h @@ -0,0 +1,177 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4ISEL_LLVM_P_H +#define QV4ISEL_LLVM_P_H + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-parameter" +#endif // __clang__ + +#include <llvm/Module.h> +#include <llvm/PassManager.h> +#include <llvm/IRBuilder.h> + +#ifdef __clang__ +# pragma clang diagnostic pop +#endif // __clang__ + +#include "qv4isel_p.h" +#include "qv4ir_p.h" + +namespace QQmlJS { +namespace LLVM { + +class InstructionSelection: + public llvm::IRBuilder<>, + public IR::InstructionSelection +{ +public: + InstructionSelection(llvm::LLVMContext &context); + + void buildLLVMModule(IR::Module *module, llvm::Module *llvmModule, llvm::FunctionPassManager *fpm); + +public: // methods from InstructionSelection: + virtual void callBuiltinInvalid(IR::Name *func, IR::ExprList *args, IR::Temp *result); + virtual void callBuiltinTypeofMember(IR::Temp *base, const QString &name, IR::Temp *result); + virtual void callBuiltinTypeofSubscript(IR::Temp *base, IR::Temp *index, IR::Temp *result); + virtual void callBuiltinTypeofName(const QString &name, IR::Temp *result); + virtual void callBuiltinTypeofValue(IR::Temp *value, IR::Temp *result); + virtual void callBuiltinDeleteMember(IR::Temp *base, const QString &name, IR::Temp *result); + virtual void callBuiltinDeleteSubscript(IR::Temp *base, IR::Temp *index, IR::Temp *result); + virtual void callBuiltinDeleteName(const QString &name, IR::Temp *result); + virtual void callBuiltinDeleteValue(IR::Temp *result); + virtual void callBuiltinPostDecrementMember(IR::Temp *base, const QString &name, IR::Temp *result); + virtual void callBuiltinPostDecrementSubscript(IR::Temp *base, IR::Temp *index, IR::Temp *result); + virtual void callBuiltinPostDecrementName(const QString &name, IR::Temp *result); + virtual void callBuiltinPostDecrementValue(IR::Temp *value, IR::Temp *result); + virtual void callBuiltinPostIncrementMember(IR::Temp *base, const QString &name, IR::Temp *result); + virtual void callBuiltinPostIncrementSubscript(IR::Temp *base, IR::Temp *index, IR::Temp *result); + virtual void callBuiltinPostIncrementName(const QString &name, IR::Temp *result); + virtual void callBuiltinPostIncrementValue(IR::Temp *value, IR::Temp *result); + virtual void callBuiltinThrow(IR::Temp *arg); + virtual void callBuiltinCreateExceptionHandler(IR::Temp *result); + virtual void callBuiltinDeleteExceptionHandler(); + virtual void callBuiltinGetException(IR::Temp *result); + virtual void callBuiltinForeachIteratorObject(IR::Temp *arg, IR::Temp *result); + virtual void callBuiltinForeachNextPropertyname(IR::Temp *arg, IR::Temp *result); + virtual void callBuiltinPushWithScope(IR::Temp *arg); + virtual void callBuiltinPopScope(); + virtual void callBuiltinDeclareVar(bool deletable, const QString &name); + virtual void callBuiltinDefineGetterSetter(IR::Temp *object, const QString &name, IR::Temp *getter, IR::Temp *setter); + virtual void callBuiltinDefineProperty(IR::Temp *object, const QString &name, IR::Temp *value); + virtual void callBuiltinDefineArrayProperty(IR::Temp *object, int index, IR::Temp *value); + virtual void callValue(IR::Temp *value, IR::ExprList *args, IR::Temp *result); + virtual void callProperty(IR::Temp *base, const QString &name, IR::ExprList *args, IR::Temp *result); + virtual void callSubscript(IR::Temp *base, IR::Temp *index, IR::ExprList *args, IR::Temp *result); + virtual void constructActivationProperty(IR::Name *func, IR::ExprList *args, IR::Temp *result); + virtual void constructProperty(IR::Temp *base, const QString &name, IR::ExprList *args, IR::Temp *result); + virtual void constructValue(IR::Temp *value, IR::ExprList *args, IR::Temp *result); + virtual void loadThisObject(IR::Temp *temp); + virtual void loadConst(IR::Const *con, IR::Temp *temp); + virtual void loadString(const QString &str, IR::Temp *targetTemp); + virtual void loadRegexp(IR::RegExp *sourceRegexp, IR::Temp *targetTemp); + virtual void getActivationProperty(const QString &name, IR::Temp *temp); + virtual void setActivationProperty(IR::Expr *source, const QString &targetName); + virtual void initClosure(IR::Closure *closure, IR::Temp *target); + virtual void getProperty(IR::Temp *sourceBase, const QString &sourceName, IR::Temp *target); + virtual void setProperty(IR::Expr *source, IR::Temp *targetBase, const QString &targetName); + virtual void getElement(IR::Temp *sourceBase, IR::Temp *sourceIndex, IR::Temp *target); + virtual void setElement(IR::Expr *source, IR::Temp *targetBase, IR::Temp *targetIndex); + virtual void copyValue(IR::Temp *sourceTemp, IR::Temp *targetTemp); + virtual void unop(IR::AluOp oper, IR::Temp *sourceTemp, IR::Temp *targetTemp); + virtual void binop(IR::AluOp oper, IR::Expr *leftSource, IR::Expr *rightSource, IR::Temp *target); + virtual void inplaceNameOp(IR::AluOp oper, IR::Expr *sourceExpr, const QString &targetName); + virtual void inplaceElementOp(IR::AluOp oper, IR::Expr *sourceExpr, IR::Temp *targetBaseTemp, IR::Temp *targetIndexTemp); + virtual void inplaceMemberOp(IR::AluOp oper, IR::Expr *source, IR::Temp *targetBase, const QString &targetName); + +public: // visitor methods for StmtVisitor: + virtual void visitJump(IR::Jump *); + virtual void visitCJump(IR::CJump *); + virtual void visitRet(IR::Ret *); + +private: + llvm::Function *getRuntimeFunction(llvm::StringRef str); + llvm::Function *getLLVMFunction(IR::Function *function); + llvm::Function *compileLLVMFunction(IR::Function *function); + llvm::BasicBlock *getLLVMBasicBlock(IR::BasicBlock *block); + llvm::Value *getLLVMTempReference(IR::Expr *expr); + llvm::Value *getLLVMCondition(IR::Expr *expr); + llvm::Value *getLLVMTemp(IR::Temp *temp); + llvm::Value *getStringPtr(const QString &s); + llvm::Value *getIdentifier(const QString &s); + llvm::AllocaInst *newLLVMTemp(llvm::Type *type, llvm::Value *size = 0); + llvm::Value * genArguments(IR::ExprList *args, int &argc); + void genCallTemp(IR::Call *e, llvm::Value *result = 0); + void genCallName(IR::Call *e, llvm::Value *result = 0); + void genCallMember(IR::Call *e, llvm::Value *result = 0); + void genConstructTemp(IR::New *e, llvm::Value *result = 0); + void genConstructName(IR::New *e, llvm::Value *result = 0); + void genConstructMember(IR::New *e, llvm::Value *result = 0); + llvm::Value *createValue(IR::Const *e); + llvm::Value *toValuePtr(IR::Expr *e); + llvm::Value *genStringList(const QList<const QString *> &strings, + const char *arrayName, const char *elementName); + + +private: + llvm::Module *_llvmModule; + llvm::Function *_llvmFunction; + llvm::Value *_llvmValue; + llvm::Type *_numberTy; + llvm::Type *_valueTy; + llvm::Type *_contextPtrTy; + llvm::Type *_stringPtrTy; + llvm::FunctionType *_functionTy; + llvm::Instruction *_allocaInsertPoint; + IR::Function *_function; + IR::BasicBlock *_block; + QHash<IR::Function *, llvm::Function *> _functionMap; + QHash<IR::BasicBlock *, llvm::BasicBlock *> _blockMap; + QVector<llvm::Value *> _tempMap; + QHash<QString, llvm::Value *> _stringMap; + llvm::FunctionPassManager *_fpm; +}; + +} // LLVM namespace +} // QQmlJS namespace + +#endif // QV4ISEL_LLVM_P_H diff --git a/src/v4/qv4isel_masm.cpp b/src/v4/qv4isel_masm.cpp new file mode 100644 index 0000000000..a4a136c656 --- /dev/null +++ b/src/v4/qv4isel_masm.cpp @@ -0,0 +1,933 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4isel_masm_p.h" +#include "qmljs_runtime.h" +#include "qv4object.h" +#include "qv4functionobject.h" +#include "qv4regexpobject.h" + +#include <assembler/LinkBuffer.h> +#include <WTFStubs.h> + +#include <sys/mman.h> +#include <iostream> +#include <cassert> + +#ifndef NO_UDIS86 +# include <udis86.h> +#endif + +using namespace QQmlJS; +using namespace QQmlJS::MASM; +using namespace QQmlJS::VM; + +Assembler::Assembler(IR::Function* function) + : _function(function) +{ +} + +void Assembler::registerBlock(IR::BasicBlock* block) +{ + _addrs[block] = label(); +} + +void Assembler::jumpToBlock(IR::BasicBlock* current, IR::BasicBlock *target) +{ + if (current->index + 1 != target->index) + _patches[target].append(jump()); +} + +void Assembler::addPatch(IR::BasicBlock* targetBlock, Jump targetJump) +{ + _patches[targetBlock].append(targetJump); +} + +Assembler::Pointer Assembler::loadTempAddress(RegisterID reg, IR::Temp *t) +{ + int32_t offset = 0; + if (t->index < 0) { + const int arg = -t->index - 1; + loadPtr(Address(ContextRegister, offsetof(ExecutionContext, arguments)), reg); + offset = arg * sizeof(Value); + } else if (t->index < _function->locals.size()) { + loadPtr(Address(ContextRegister, offsetof(ExecutionContext, locals)), reg); + offset = t->index * sizeof(Value); + } else { + const int arg = _function->maxNumberOfArguments + t->index - _function->locals.size() + 1; + // StackFrameRegister points to its old value on the stack, so even for the first temp we need to + // subtract at least sizeof(Value). + offset = - sizeof(Value) * (arg + 1); + reg = StackFrameRegister; + } + return Pointer(reg, offset); +} + +template <typename Result, typename Source> +void Assembler::copyValue(Result result, Source source) +{ +#ifdef VALUE_FITS_IN_REGISTER + // Use ReturnValueRegister as "scratch" register because loadArgument + // and storeArgument are functions that may need a scratch register themselves. + loadArgument(source, ReturnValueRegister); + storeArgument(ReturnValueRegister, result); +#else + loadDouble(source, FPGpr0); + storeDouble(FPGpr0, result); +#endif +} + +void Assembler::storeValue(VM::Value value, IR::Temp* destination) +{ + Address addr = loadTempAddress(ScratchRegister, destination); + storeValue(value, addr); +} + +void Assembler::enterStandardStackFrame(int locals) +{ +#if CPU(ARM) + push(JSC::ARMRegisters::lr); +#endif + push(StackFrameRegister); + move(StackPointerRegister, StackFrameRegister); + + // space for the locals and the ContextRegister + int32_t frameSize = locals * sizeof(QQmlJS::VM::Value) + sizeof(void*); + +#if CPU(X86) || CPU(X86_64) + frameSize = (frameSize + 15) & ~15; // align on 16 byte boundaries for MMX +#endif + subPtr(TrustedImm32(frameSize), StackPointerRegister); + +#if CPU(X86) || CPU(ARM) + for (int saveReg = CalleeSavedFirstRegister; saveReg <= CalleeSavedLastRegister; ++saveReg) + push(static_cast<RegisterID>(saveReg)); +#endif + // save the ContextRegister + storePtr(ContextRegister, StackPointerRegister); +} + +void Assembler::leaveStandardStackFrame(int locals) +{ + // restore the ContextRegister + loadPtr(StackPointerRegister, ContextRegister); + +#if CPU(X86) || CPU(ARM) + for (int saveReg = CalleeSavedLastRegister; saveReg >= CalleeSavedFirstRegister; --saveReg) + pop(static_cast<RegisterID>(saveReg)); +#endif + // space for the locals and the ContextRegister + int32_t frameSize = locals * sizeof(QQmlJS::VM::Value) + sizeof(void*); +#if CPU(X86) || CPU(X86_64) + frameSize = (frameSize + 15) & ~15; // align on 16 byte boundaries for MMX +#endif + addPtr(TrustedImm32(frameSize), StackPointerRegister); + + pop(StackFrameRegister); +#if CPU(ARM) + pop(JSC::ARMRegisters::lr); +#endif +} + + + +#define OP(op) \ + { isel_stringIfy(op), op, 0, 0 } + +#define INLINE_OP(op, memOp, immOp) \ + { isel_stringIfy(op), op, memOp, immOp } + +#define NULL_OP \ + { 0, 0, 0, 0 } + +const Assembler::BinaryOperationInfo Assembler::binaryOperations[QQmlJS::IR::LastAluOp + 1] = { + NULL_OP, // OpInvalid + NULL_OP, // OpIfTrue + NULL_OP, // OpNot + NULL_OP, // OpUMinus + NULL_OP, // OpUPlus + NULL_OP, // OpCompl + NULL_OP, // OpIncrement + NULL_OP, // OpDecrement + + INLINE_OP(__qmljs_bit_and, &Assembler::inline_and32, &Assembler::inline_and32), // OpBitAnd + INLINE_OP(__qmljs_bit_or, &Assembler::inline_or32, &Assembler::inline_or32), // OpBitOr + INLINE_OP(__qmljs_bit_xor, &Assembler::inline_xor32, &Assembler::inline_xor32), // OpBitXor + + INLINE_OP(__qmljs_add, &Assembler::inline_add32, &Assembler::inline_add32), // OpAdd + INLINE_OP(__qmljs_sub, &Assembler::inline_sub32, &Assembler::inline_sub32), // OpSub + INLINE_OP(__qmljs_mul, &Assembler::inline_mul32, &Assembler::inline_mul32), // OpMul + + OP(__qmljs_div), // OpDiv + OP(__qmljs_mod), // OpMod + + INLINE_OP(__qmljs_shl, &Assembler::inline_shl32, &Assembler::inline_shl32), // OpLShift + INLINE_OP(__qmljs_shr, &Assembler::inline_shr32, &Assembler::inline_shr32), // OpRShift + INLINE_OP(__qmljs_ushr, &Assembler::inline_ushr32, &Assembler::inline_ushr32), // OpURShift + + OP(__qmljs_gt), // OpGt + OP(__qmljs_lt), // OpLt + OP(__qmljs_ge), // OpGe + OP(__qmljs_le), // OpLe + OP(__qmljs_eq), // OpEqual + OP(__qmljs_ne), // OpNotEqual + OP(__qmljs_se), // OpStrictEqual + OP(__qmljs_sne), // OpStrictNotEqual + + OP(__qmljs_instanceof), // OpInstanceof + OP(__qmljs_in), // OpIn + + NULL_OP, // OpAnd + NULL_OP // OpOr +}; + +void Assembler::generateBinOp(IR::AluOp operation, IR::Temp* target, IR::Expr* left, IR::Expr* right) +{ + const BinaryOperationInfo& info = binaryOperations[operation]; + if (!info.fallbackImplementation) { + assert(!"unreachable"); + return; + } + + Value leftConst = Value::undefinedValue(); + Value rightConst = Value::undefinedValue(); + + bool canDoInline = info.inlineMemRegOp && info.inlineImmRegOp; + + if (canDoInline) { + if (left->asConst()) { + leftConst = convertToValue(left->asConst()); + canDoInline = canDoInline && leftConst.tryIntegerConversion(); + } + if (right->asConst()) { + rightConst = convertToValue(right->asConst()); + canDoInline = canDoInline && rightConst.tryIntegerConversion(); + } + } + + Jump binOpFinished; + + if (canDoInline) { + + Jump leftTypeCheck; + if (left->asTemp()) { + Address typeAddress = loadTempAddress(ScratchRegister, left->asTemp()); + typeAddress.offset += offsetof(VM::Value, tag); + leftTypeCheck = branch32(NotEqual, typeAddress, TrustedImm32(VM::Value::_Integer_Type)); + } + + Jump rightTypeCheck; + if (right->asTemp()) { + Address typeAddress = loadTempAddress(ScratchRegister, right->asTemp()); + typeAddress.offset += offsetof(VM::Value, tag); + rightTypeCheck = branch32(NotEqual, typeAddress, TrustedImm32(VM::Value::_Integer_Type)); + } + + if (left->asTemp()) { + Address leftValue = loadTempAddress(ScratchRegister, left->asTemp()); + leftValue.offset += offsetof(VM::Value, int_32); + load32(leftValue, IntegerOpRegister); + } else { // left->asConst() + move(TrustedImm32(leftConst.integerValue()), IntegerOpRegister); + } + + Jump overflowCheck; + + if (right->asTemp()) { + Address rightValue = loadTempAddress(ScratchRegister, right->asTemp()); + rightValue.offset += offsetof(VM::Value, int_32); + + overflowCheck = (this->*info.inlineMemRegOp)(rightValue, IntegerOpRegister); + } else { // right->asConst() + overflowCheck = (this->*info.inlineImmRegOp)(TrustedImm32(rightConst.integerValue()), IntegerOpRegister); + } + + Address resultAddr = loadTempAddress(ScratchRegister, target); + Address resultValueAddr = resultAddr; + resultValueAddr.offset += offsetof(VM::Value, int_32); + store32(IntegerOpRegister, resultValueAddr); + + Address resultTypeAddr = resultAddr; + resultTypeAddr.offset += offsetof(VM::Value, tag); + store32(TrustedImm32(VM::Value::_Integer_Type), resultTypeAddr); + + binOpFinished = jump(); + + if (leftTypeCheck.isSet()) + leftTypeCheck.link(this); + if (rightTypeCheck.isSet()) + rightTypeCheck.link(this); + if (overflowCheck.isSet()) + overflowCheck.link(this); + } + + // Fallback + generateFunctionCallImp(target, info.name, info.fallbackImplementation, left, right, ContextRegister); + + if (binOpFinished.isSet()) + binOpFinished.link(this); +} +#if OS(LINUX) +static void printDisassembledOutputWithCalls(const char* output, const QHash<void*, const char*>& functions) +{ + QByteArray processedOutput(output); + for (QHash<void*, const char*>::ConstIterator it = functions.begin(), end = functions.end(); + it != end; ++it) { + QByteArray ptrString = QByteArray::number(qlonglong(it.key()), 16); + ptrString.prepend("0x"); + processedOutput = processedOutput.replace(ptrString, it.value()); + } + fprintf(stderr, "%s\n", processedOutput.constData()); +} +#endif + +void Assembler::link(VM::Function *vmFunc) +{ + QHashIterator<IR::BasicBlock *, QVector<Jump> > it(_patches); + while (it.hasNext()) { + it.next(); + IR::BasicBlock *block = it.key(); + Label target = _addrs.value(block); + assert(target.isSet()); + foreach (Jump jump, it.value()) + jump.linkTo(target, this); + } + + JSC::JSGlobalData dummy; + JSC::LinkBuffer linkBuffer(dummy, this, 0); + QHash<void*, const char*> functions; + foreach (CallToLink ctl, _callsToLink) { + linkBuffer.link(ctl.call, ctl.externalFunction); + functions[ctl.externalFunction.value()] = ctl.functionName; + } + + static bool showCode = !qgetenv("SHOW_CODE").isNull(); + if (showCode) { +#if OS(LINUX) + char* disasmOutput = 0; + size_t disasmLength = 0; + FILE* disasmStream = open_memstream(&disasmOutput, &disasmLength); + WTF::setDataFile(disasmStream); +#endif + + QByteArray name = _function->name->toUtf8(); + vmFunc->codeRef = linkBuffer.finalizeCodeWithDisassembly("%s", name.data()); + + WTF::setDataFile(stderr); +#if OS(LINUX) + fclose(disasmStream); +#if CPU(X86) || CPU(X86_64) + printDisassembledOutputWithCalls(disasmOutput, functions); +#endif + free(disasmOutput); +#endif + } else { + vmFunc->codeRef = linkBuffer.finalizeCodeWithoutDisassembly(); + } + + vmFunc->code = (Value (*)(VM::ExecutionContext *, const uchar *)) vmFunc->codeRef.code().executableAddress(); +} + +InstructionSelection::InstructionSelection(VM::ExecutionEngine *engine, IR::Module *module) + : EvalInstructionSelection(engine, module) + , _block(0) + , _function(0) + , _vmFunction(0) + , _asm(0) +{ +} + +InstructionSelection::~InstructionSelection() +{ + delete _asm; +} + +void InstructionSelection::run(VM::Function *vmFunction, IR::Function *function) +{ + qSwap(_function, function); + qSwap(_vmFunction, vmFunction); + Assembler* oldAssembler = _asm; + _asm = new Assembler(_function); + + int locals = (_function->tempCount - _function->locals.size() + _function->maxNumberOfArguments) + 1; + locals = (locals + 1) & ~1; + _asm->enterStandardStackFrame(locals); + + int contextPointer = 0; +#ifndef VALUE_FITS_IN_REGISTER + // When the return VM value doesn't fit into a register, then + // the caller provides a pointer for storage as first argument. + // That shifts the index the context pointer argument by one. + contextPointer++; +#endif +#if CPU(X86) + _asm->loadPtr(addressForArgument(contextPointer), Assembler::ContextRegister); +#elif CPU(X86_64) || CPU(ARM) + _asm->move(_asm->registerForArgument(contextPointer), Assembler::ContextRegister); +#else + assert(!"TODO"); +#endif + + foreach (IR::BasicBlock *block, _function->basicBlocks) { + _block = block; + _asm->registerBlock(_block); + foreach (IR::Stmt *s, block->statements) { + s->accept(this); + } + } + + _asm->leaveStandardStackFrame(locals); +#ifndef VALUE_FITS_IN_REGISTER + // Emulate ret(n) instruction + // Pop off return address into scratch register ... + _asm->pop(Assembler::ScratchRegister); + // ... and overwrite the invisible argument with + // the return address. + _asm->poke(Assembler::ScratchRegister); +#endif + _asm->ret(); + + _asm->link(_vmFunction); + + qSwap(_vmFunction, vmFunction); + qSwap(_function, function); + delete _asm; + _asm = oldAssembler; +} + +void InstructionSelection::callBuiltinInvalid(IR::Name *func, IR::ExprList *args, IR::Temp *result) +{ + callRuntimeMethod(result, __qmljs_call_activation_property, func, args); +} + +void InstructionSelection::callBuiltinTypeofMember(IR::Temp *base, const QString &name, IR::Temp *result) +{ + generateFunctionCall(result, __qmljs_builtin_typeof_member, base, identifier(name), Assembler::ContextRegister); +} + +void InstructionSelection::callBuiltinTypeofSubscript(IR::Temp *base, IR::Temp *index, IR::Temp *result) +{ + generateFunctionCall(result, __qmljs_builtin_typeof_element, base, index, Assembler::ContextRegister); +} + +void InstructionSelection::callBuiltinTypeofName(const QString &name, IR::Temp *result) +{ + generateFunctionCall(result, __qmljs_builtin_typeof_name, identifier(name), Assembler::ContextRegister); +} + +void InstructionSelection::callBuiltinTypeofValue(IR::Temp *value, IR::Temp *result) +{ + generateFunctionCall(result, __qmljs_builtin_typeof, value, Assembler::ContextRegister); +} + +void InstructionSelection::callBuiltinDeleteMember(IR::Temp *base, const QString &name, IR::Temp *result) +{ + generateFunctionCall(result, __qmljs_delete_member, Assembler::ContextRegister, base, identifier(name)); +} + +void InstructionSelection::callBuiltinDeleteSubscript(IR::Temp *base, IR::Temp *index, IR::Temp *result) +{ + generateFunctionCall(result, __qmljs_delete_subscript, Assembler::ContextRegister, base, index); +} + +void InstructionSelection::callBuiltinDeleteName(const QString &name, IR::Temp *result) +{ + generateFunctionCall(result, __qmljs_delete_name, Assembler::ContextRegister, identifier(name)); +} + +void InstructionSelection::callBuiltinDeleteValue(IR::Temp *result) +{ + _asm->storeValue(Value::fromBoolean(false), result); +} + +void InstructionSelection::callBuiltinPostIncrementMember(IR::Temp *base, const QString &name, IR::Temp *result) +{ + generateFunctionCall(result, __qmljs_builtin_post_increment_member, base, identifier(name), Assembler::ContextRegister); +} + +void InstructionSelection::callBuiltinPostIncrementSubscript(IR::Temp *base, IR::Temp *index, IR::Temp *result) +{ + generateFunctionCall(result, __qmljs_builtin_post_increment_element, base, index, Assembler::ContextRegister); +} + +void InstructionSelection::callBuiltinPostIncrementName(const QString &name, IR::Temp *result) +{ + generateFunctionCall(result, __qmljs_builtin_post_increment_name, identifier(name), Assembler::ContextRegister); +} + +void InstructionSelection::callBuiltinPostIncrementValue(IR::Temp *value, IR::Temp *result) +{ + generateFunctionCall(result, __qmljs_builtin_post_increment, Assembler::PointerToValue(value), Assembler::ContextRegister); +} + +void InstructionSelection::callBuiltinPostDecrementMember(IR::Temp *base, const QString &name, IR::Temp *result) +{ + generateFunctionCall(result, __qmljs_builtin_post_decrement_member, base, identifier(name), Assembler::ContextRegister); +} + +void InstructionSelection::callBuiltinPostDecrementSubscript(IR::Temp *base, IR::Temp *index, IR::Temp *result) +{ + generateFunctionCall(result, __qmljs_builtin_post_decrement_element, base, index, Assembler::ContextRegister); +} + +void InstructionSelection::callBuiltinPostDecrementName(const QString &name, IR::Temp *result) +{ + generateFunctionCall(result, __qmljs_builtin_post_decrement_name, identifier(name), Assembler::ContextRegister); +} + +void InstructionSelection::callBuiltinPostDecrementValue(IR::Temp *value, IR::Temp *result) +{ + generateFunctionCall(result, __qmljs_builtin_post_decrement, Assembler::PointerToValue(value), Assembler::ContextRegister); +} + +void InstructionSelection::callBuiltinThrow(IR::Temp *arg) +{ + generateFunctionCall(Assembler::Void, __qmljs_builtin_throw, arg, Assembler::ContextRegister); +} + +void InstructionSelection::callBuiltinCreateExceptionHandler(IR::Temp *result) +{ + generateFunctionCall(Assembler::ReturnValueRegister, __qmljs_create_exception_handler, Assembler::ContextRegister); + generateFunctionCall(Assembler::ReturnValueRegister, setjmp, Assembler::ReturnValueRegister); + Address addr = _asm->loadTempAddress(Assembler::ScratchRegister, result); + _asm->store32(Assembler::ReturnValueRegister, addr); + addr.offset += 4; + _asm->store32(Assembler::TrustedImm32(Value::Boolean_Type), addr); +} + +void InstructionSelection::callBuiltinDeleteExceptionHandler() +{ + generateFunctionCall(Assembler::Void, __qmljs_delete_exception_handler, Assembler::ContextRegister); +} + +void InstructionSelection::callBuiltinGetException(IR::Temp *result) +{ + generateFunctionCall(result, __qmljs_get_exception, Assembler::ContextRegister); +} + +void InstructionSelection::callBuiltinForeachIteratorObject(IR::Temp *arg, IR::Temp *result) +{ + generateFunctionCall(result, __qmljs_foreach_iterator_object, arg, Assembler::ContextRegister); +} + +void InstructionSelection::callBuiltinForeachNextPropertyname(IR::Temp *arg, IR::Temp *result) +{ + generateFunctionCall(result, __qmljs_foreach_next_property_name, arg); +} + +void InstructionSelection::callBuiltinPushWithScope(IR::Temp *arg) +{ + generateFunctionCall(Assembler::ContextRegister, __qmljs_builtin_push_with_scope, arg, Assembler::ContextRegister); +} + +void InstructionSelection::callBuiltinPopScope() +{ + generateFunctionCall(Assembler::ContextRegister, __qmljs_builtin_pop_scope, Assembler::ContextRegister); +} + +void InstructionSelection::callBuiltinDeclareVar(bool deletable, const QString &name) +{ + generateFunctionCall(Assembler::Void, __qmljs_builtin_declare_var, Assembler::ContextRegister, + Assembler::TrustedImm32(deletable), identifier(name)); +} + +void InstructionSelection::callBuiltinDefineGetterSetter(IR::Temp *object, const QString &name, IR::Temp *getter, IR::Temp *setter) +{ + generateFunctionCall(Assembler::Void, __qmljs_builtin_define_getter_setter, + object, identifier(name), getter, setter, Assembler::ContextRegister); +} + +void InstructionSelection::callBuiltinDefineProperty(IR::Temp *object, const QString &name, IR::Temp *value) +{ + generateFunctionCall(Assembler::Void, __qmljs_builtin_define_property, + object, identifier(name), value, Assembler::ContextRegister); +} + +void InstructionSelection::callBuiltinDefineArrayProperty(IR::Temp *object, int index, IR::Temp *value) +{ + generateFunctionCall(Assembler::Void, __qmljs_builtin_define_array_property, + object, Assembler::TrustedImm32(index), value, Assembler::ContextRegister); +} + +void InstructionSelection::callValue(IR::Temp *value, IR::ExprList *args, IR::Temp *result) +{ + int argc = prepareVariableArguments(args); + IR::Temp* thisObject = 0; + generateFunctionCall(result, __qmljs_call_value, Assembler::ContextRegister, thisObject, value, baseAddressForCallArguments(), Assembler::TrustedImm32(argc)); +} + +void InstructionSelection::loadThisObject(IR::Temp *temp) +{ + generateFunctionCall(temp, __qmljs_get_thisObject, Assembler::ContextRegister); +} + +void InstructionSelection::loadConst(IR::Const *sourceConst, IR::Temp *targetTemp) +{ + _asm->storeValue(convertToValue(sourceConst), targetTemp); +} + +void InstructionSelection::loadString(const QString &str, IR::Temp *targetTemp) +{ + Value v = Value::fromString(identifier(str)); + _asm->storeValue(v, targetTemp); +} + +void InstructionSelection::loadRegexp(IR::RegExp *sourceRegexp, IR::Temp *targetTemp) +{ + Value v = Value::fromObject(engine()->newRegExpObject(*sourceRegexp->value, + sourceRegexp->flags)); + _vmFunction->generatedValues.append(v); + _asm->storeValue(v, targetTemp); +} + +void InstructionSelection::getActivationProperty(const QString &name, IR::Temp *temp) +{ + String *propertyName = identifier(name); + generateFunctionCall(temp, __qmljs_get_activation_property, Assembler::ContextRegister, propertyName); +} + +void InstructionSelection::setActivationProperty(IR::Expr *source, const QString &targetName) +{ + String *propertyName = identifier(targetName); + generateFunctionCall(Assembler::Void, __qmljs_set_activation_property, Assembler::ContextRegister, propertyName, source); +} + +void InstructionSelection::initClosure(IR::Closure *closure, IR::Temp *target) +{ + VM::Function *vmFunc = vmFunction(closure->value); + assert(vmFunc); + generateFunctionCall(target, __qmljs_init_closure, Assembler::TrustedImmPtr(vmFunc), Assembler::ContextRegister); +} + +void InstructionSelection::getProperty(IR::Temp *base, const QString &name, IR::Temp *target) +{ + generateFunctionCall(target, __qmljs_get_property, Assembler::ContextRegister, base, identifier(name)); +} + +void InstructionSelection::setProperty(IR::Expr *source, IR::Temp *targetBase, const QString &targetName) +{ + generateFunctionCall(Assembler::Void, __qmljs_set_property, Assembler::ContextRegister, targetBase, identifier(targetName), source); +} + +void InstructionSelection::getElement(IR::Temp *base, IR::Temp *index, IR::Temp *target) +{ + generateFunctionCall(target, __qmljs_get_element, Assembler::ContextRegister, base, index); +} + +void InstructionSelection::setElement(IR::Expr *source, IR::Temp *targetBase, IR::Temp *targetIndex) +{ + generateFunctionCall(Assembler::Void, __qmljs_set_element, Assembler::ContextRegister, targetBase, targetIndex, source); +} + +void InstructionSelection::copyValue(IR::Temp *sourceTemp, IR::Temp *targetTemp) +{ + _asm->copyValue(targetTemp, sourceTemp); +} + +#define setOp(op, opName, operation) \ + do { op = operation; opName = isel_stringIfy(operation); } while (0) + +void InstructionSelection::unop(IR::AluOp oper, IR::Temp *sourceTemp, IR::Temp *targetTemp) +{ + Value (*op)(const Value value, ExecutionContext *ctx) = 0; + const char *opName = 0; + switch (oper) { + case IR::OpIfTrue: assert(!"unreachable"); break; + case IR::OpNot: setOp(op, opName, __qmljs_not); break; + case IR::OpUMinus: setOp(op, opName, __qmljs_uminus); break; + case IR::OpUPlus: setOp(op, opName, __qmljs_uplus); break; + case IR::OpCompl: setOp(op, opName, __qmljs_compl); break; + case IR::OpIncrement: setOp(op, opName, __qmljs_increment); break; + case IR::OpDecrement: setOp(op, opName, __qmljs_decrement); break; + default: assert(!"unreachable"); break; + } // switch + + if (op) + _asm->generateFunctionCallImp(targetTemp, opName, op, sourceTemp, + Assembler::ContextRegister); +} + +void InstructionSelection::binop(IR::AluOp oper, IR::Expr *leftSource, IR::Expr *rightSource, IR::Temp *target) +{ + _asm->generateBinOp(oper, target, leftSource, rightSource); +} + +void InstructionSelection::inplaceNameOp(IR::AluOp oper, IR::Expr *sourceExpr, const QString &targetName) +{ + void (*op)(const Value value, String *name, ExecutionContext *ctx) = 0; + const char *opName = 0; + switch (oper) { + case IR::OpBitAnd: setOp(op, opName, __qmljs_inplace_bit_and_name); break; + case IR::OpBitOr: setOp(op, opName, __qmljs_inplace_bit_or_name); break; + case IR::OpBitXor: setOp(op, opName, __qmljs_inplace_bit_xor_name); break; + case IR::OpAdd: setOp(op, opName, __qmljs_inplace_add_name); break; + case IR::OpSub: setOp(op, opName, __qmljs_inplace_sub_name); break; + case IR::OpMul: setOp(op, opName, __qmljs_inplace_mul_name); break; + case IR::OpDiv: setOp(op, opName, __qmljs_inplace_div_name); break; + case IR::OpMod: setOp(op, opName, __qmljs_inplace_mod_name); break; + case IR::OpLShift: setOp(op, opName, __qmljs_inplace_shl_name); break; + case IR::OpRShift: setOp(op, opName, __qmljs_inplace_shr_name); break; + case IR::OpURShift: setOp(op, opName, __qmljs_inplace_ushr_name); break; + default: + Q_UNREACHABLE(); + break; + } + if (op) { + _asm->generateFunctionCallImp(Assembler::Void, opName, op, sourceExpr, identifier(targetName), Assembler::ContextRegister); + } +} + +void InstructionSelection::inplaceElementOp(IR::AluOp oper, IR::Expr *sourceExpr, IR::Temp *targetBaseTemp, IR::Temp *targetIndexTemp) +{ + void (*op)(Value base, Value index, Value value, ExecutionContext *ctx) = 0; + const char *opName = 0; + switch (oper) { + case IR::OpBitAnd: setOp(op, opName, __qmljs_inplace_bit_and_element); break; + case IR::OpBitOr: setOp(op, opName, __qmljs_inplace_bit_or_element); break; + case IR::OpBitXor: setOp(op, opName, __qmljs_inplace_bit_xor_element); break; + case IR::OpAdd: setOp(op, opName, __qmljs_inplace_add_element); break; + case IR::OpSub: setOp(op, opName, __qmljs_inplace_sub_element); break; + case IR::OpMul: setOp(op, opName, __qmljs_inplace_mul_element); break; + case IR::OpDiv: setOp(op, opName, __qmljs_inplace_div_element); break; + case IR::OpMod: setOp(op, opName, __qmljs_inplace_mod_element); break; + case IR::OpLShift: setOp(op, opName, __qmljs_inplace_shl_element); break; + case IR::OpRShift: setOp(op, opName, __qmljs_inplace_shr_element); break; + case IR::OpURShift: setOp(op, opName, __qmljs_inplace_ushr_element); break; + default: + Q_UNREACHABLE(); + break; + } + + if (op) { + _asm->generateFunctionCallImp(Assembler::Void, opName, op, targetBaseTemp, targetIndexTemp, sourceExpr, Assembler::ContextRegister); + } +} + +void InstructionSelection::inplaceMemberOp(IR::AluOp oper, IR::Expr *source, IR::Temp *targetBase, const QString &targetName) +{ + void (*op)(Value value, Value base, String *name, ExecutionContext *ctx) = 0; + const char *opName = 0; + switch (oper) { + case IR::OpBitAnd: setOp(op, opName, __qmljs_inplace_bit_and_member); break; + case IR::OpBitOr: setOp(op, opName, __qmljs_inplace_bit_or_member); break; + case IR::OpBitXor: setOp(op, opName, __qmljs_inplace_bit_xor_member); break; + case IR::OpAdd: setOp(op, opName, __qmljs_inplace_add_member); break; + case IR::OpSub: setOp(op, opName, __qmljs_inplace_sub_member); break; + case IR::OpMul: setOp(op, opName, __qmljs_inplace_mul_member); break; + case IR::OpDiv: setOp(op, opName, __qmljs_inplace_div_member); break; + case IR::OpMod: setOp(op, opName, __qmljs_inplace_mod_member); break; + case IR::OpLShift: setOp(op, opName, __qmljs_inplace_shl_member); break; + case IR::OpRShift: setOp(op, opName, __qmljs_inplace_shr_member); break; + case IR::OpURShift: setOp(op, opName, __qmljs_inplace_ushr_member); break; + default: + Q_UNREACHABLE(); + break; + } + + if (op) { + String* member = identifier(targetName); + _asm->generateFunctionCallImp(Assembler::Void, opName, op, source, targetBase, member, Assembler::ContextRegister); + } +} + +void InstructionSelection::callProperty(IR::Temp *base, const QString &name, + IR::ExprList *args, IR::Temp *result) +{ + assert(base != 0); + + int argc = prepareVariableArguments(args); + generateFunctionCall(result, __qmljs_call_property, + Assembler::ContextRegister, base, identifier(name), + baseAddressForCallArguments(), + Assembler::TrustedImm32(argc)); +} + +void InstructionSelection::callSubscript(IR::Temp *base, IR::Temp *index, IR::ExprList *args, IR::Temp *result) +{ + assert(base != 0); + + int argc = prepareVariableArguments(args); + generateFunctionCall(result, __qmljs_call_element, + Assembler::ContextRegister, base, index, + baseAddressForCallArguments(), + Assembler::TrustedImm32(argc)); +} + +String *InstructionSelection::identifier(const QString &s) +{ + String *str = engine()->identifier(s); + _vmFunction->identifiers.append(str); + return str; +} + +void InstructionSelection::constructActivationProperty(IR::Name *func, IR::ExprList *args, IR::Temp *result) +{ + assert(func != 0); + + callRuntimeMethod(result, __qmljs_construct_activation_property, func, args); +} + +void InstructionSelection::constructProperty(IR::Temp *base, const QString &name, IR::ExprList *args, IR::Temp *result) +{ + int argc = prepareVariableArguments(args); + generateFunctionCall(result, __qmljs_construct_property, Assembler::ContextRegister, base, identifier(name), baseAddressForCallArguments(), Assembler::TrustedImm32(argc)); +} + +void InstructionSelection::constructValue(IR::Temp *value, IR::ExprList *args, IR::Temp *result) +{ + assert(value != 0); + + int argc = prepareVariableArguments(args); + generateFunctionCall(result, __qmljs_construct_value, Assembler::ContextRegister, value, baseAddressForCallArguments(), Assembler::TrustedImm32(argc)); +} + +void InstructionSelection::visitJump(IR::Jump *s) +{ + _asm->jumpToBlock(_block, s->target); +} + +void InstructionSelection::visitCJump(IR::CJump *s) +{ + if (IR::Temp *t = s->cond->asTemp()) { + Address temp = _asm->loadTempAddress(Assembler::ScratchRegister, t); + Address tag = temp; + tag.offset += offsetof(VM::Value, tag); + Assembler::Jump booleanConversion = _asm->branch32(Assembler::NotEqual, tag, Assembler::TrustedImm32(VM::Value::Boolean_Type)); + + Address data = temp; + data.offset += offsetof(VM::Value, int_32); + _asm->load32(data, Assembler::ReturnValueRegister); + Assembler::Jump testBoolean = _asm->jump(); + + booleanConversion.link(_asm); + { + generateFunctionCall(Assembler::ReturnValueRegister, __qmljs_to_boolean, t, Assembler::ContextRegister); + } + + testBoolean.link(_asm); + Assembler::Jump target = _asm->branch32(Assembler::NotEqual, Assembler::ReturnValueRegister, Assembler::TrustedImm32(0)); + _asm->addPatch(s->iftrue, target); + + _asm->jumpToBlock(_block, s->iffalse); + return; + } else if (IR::Binop *b = s->cond->asBinop()) { + if ((b->left->asTemp() || b->left->asConst()) && + (b->right->asTemp() || b->right->asConst())) { + Bool (*op)(const Value, const Value, ExecutionContext *ctx) = 0; + const char *opName = 0; + switch (b->op) { + default: Q_UNREACHABLE(); assert(!"todo"); break; + case IR::OpGt: setOp(op, opName, __qmljs_cmp_gt); break; + case IR::OpLt: setOp(op, opName, __qmljs_cmp_lt); break; + case IR::OpGe: setOp(op, opName, __qmljs_cmp_ge); break; + case IR::OpLe: setOp(op, opName, __qmljs_cmp_le); break; + case IR::OpEqual: setOp(op, opName, __qmljs_cmp_eq); break; + case IR::OpNotEqual: setOp(op, opName, __qmljs_cmp_ne); break; + case IR::OpStrictEqual: setOp(op, opName, __qmljs_cmp_se); break; + case IR::OpStrictNotEqual: setOp(op, opName, __qmljs_cmp_sne); break; + case IR::OpInstanceof: setOp(op, opName, __qmljs_cmp_instanceof); break; + case IR::OpIn: setOp(op, opName, __qmljs_cmp_in); break; + } // switch + + _asm->generateFunctionCallImp(Assembler::ReturnValueRegister, opName, op, b->left, b->right, Assembler::ContextRegister); + + Assembler::Jump target = _asm->branch32(Assembler::NotEqual, Assembler::ReturnValueRegister, Assembler::TrustedImm32(0)); + _asm->addPatch(s->iftrue, target); + + _asm->jumpToBlock(_block, s->iffalse); + return; + } else { + assert(!"wip"); + } + Q_UNIMPLEMENTED(); + } + Q_UNIMPLEMENTED(); + assert(!"TODO"); +} + +void InstructionSelection::visitRet(IR::Ret *s) +{ + if (IR::Temp *t = s->expr->asTemp()) { +#ifdef VALUE_FITS_IN_REGISTER + _asm->copyValue(Assembler::ReturnValueRegister, t); +#else + _asm->loadPtr(addressForArgument(0), Assembler::ReturnValueRegister); + _asm->copyValue(Address(Assembler::ReturnValueRegister, 0), t); +#endif + return; + } + Q_UNIMPLEMENTED(); + Q_UNUSED(s); +} + +int InstructionSelection::prepareVariableArguments(IR::ExprList* args) +{ + int argc = 0; + for (IR::ExprList *it = args; it; it = it->next) { + ++argc; + } + + int i = 0; + for (IR::ExprList *it = args; it; it = it->next, ++i) { + IR::Temp *arg = it->expr->asTemp(); + assert(arg != 0); + _asm->copyValue(argumentAddressForCall(i), arg); + } + + return argc; +} + +void InstructionSelection::callRuntimeMethodImp(IR::Temp *result, const char* name, ActivationMethod method, IR::Expr *base, IR::ExprList *args) +{ + IR::Name *baseName = base->asName(); + assert(baseName != 0); + + int argc = prepareVariableArguments(args); + _asm->generateFunctionCallImp(result, name, method, Assembler::ContextRegister, identifier(*baseName->id), baseAddressForCallArguments(), Assembler::TrustedImm32(argc)); +} + +void InstructionSelection::callRuntimeMethodImp(IR::Temp *result, const char* name, BuiltinMethod method, IR::ExprList *args) +{ + int argc = prepareVariableArguments(args); + _asm->generateFunctionCallImp(result, name, method, Assembler::ContextRegister, baseAddressForCallArguments(), Assembler::TrustedImm32(argc)); +} + + diff --git a/src/v4/qv4isel_masm_p.h b/src/v4/qv4isel_masm_p.h new file mode 100644 index 0000000000..de0971f0cb --- /dev/null +++ b/src/v4/qv4isel_masm_p.h @@ -0,0 +1,798 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4ISEL_MASM_P_H +#define QV4ISEL_MASM_P_H + +#include "qv4global.h" +#include "qv4ir_p.h" +#include "qv4isel_p.h" +#include "qv4isel_util_p.h" +#include "qv4object.h" +#include "qmljs_runtime.h" + +#include <QtCore/QHash> +#include <config.h> +#include <wtf/Vector.h> +#include <assembler/MacroAssembler.h> + +namespace QQmlJS { +namespace MASM { + +class Assembler : public JSC::MacroAssembler +{ +public: + Assembler(IR::Function* function); +#if CPU(X86) + +#undef VALUE_FITS_IN_REGISTER + + static const RegisterID StackFrameRegister = JSC::X86Registers::ebp; + static const RegisterID StackPointerRegister = JSC::X86Registers::esp; + static const RegisterID ContextRegister = JSC::X86Registers::esi; + static const RegisterID ReturnValueRegister = JSC::X86Registers::eax; + static const RegisterID ScratchRegister = JSC::X86Registers::ecx; + static const RegisterID CalleeSavedFirstRegister = ScratchRegister; + static const RegisterID CalleeSavedLastRegister = ScratchRegister; + static const RegisterID IntegerOpRegister = JSC::X86Registers::eax; + static const FPRegisterID FPGpr0 = JSC::X86Registers::xmm0; + + static const int RegisterSize = 4; + + static const int RegisterArgumentCount = 0; + static RegisterID registerForArgument(int) + { + assert(false); + // Not reached. + return JSC::X86Registers::eax; + } +#elif CPU(X86_64) + +#define VALUE_FITS_IN_REGISTER + + static const RegisterID StackFrameRegister = JSC::X86Registers::ebp; + static const RegisterID StackPointerRegister = JSC::X86Registers::esp; + static const RegisterID ContextRegister = JSC::X86Registers::r14; + static const RegisterID ReturnValueRegister = JSC::X86Registers::eax; + static const RegisterID ScratchRegister = JSC::X86Registers::r10; + static const RegisterID IntegerOpRegister = JSC::X86Registers::eax; + static const FPRegisterID FPGpr0 = JSC::X86Registers::xmm0; + + static const int RegisterSize = 8; + + static const int RegisterArgumentCount = 6; + static RegisterID registerForArgument(int index) + { + static RegisterID regs[RegisterArgumentCount] = { + JSC::X86Registers::edi, + JSC::X86Registers::esi, + JSC::X86Registers::edx, + JSC::X86Registers::ecx, + JSC::X86Registers::r8, + JSC::X86Registers::r9 + }; + assert(index >= 0 && index < RegisterArgumentCount); + return regs[index]; + }; +#elif CPU(ARM) + +#undef VALUE_FITS_IN_REGISTER + + static const RegisterID StackFrameRegister = JSC::ARMRegisters::r4; + static const RegisterID StackPointerRegister = JSC::ARMRegisters::sp; + static const RegisterID ContextRegister = JSC::ARMRegisters::r5; + static const RegisterID ReturnValueRegister = JSC::ARMRegisters::r0; + static const RegisterID ScratchRegister = JSC::ARMRegisters::r6; + static const RegisterID CalleeSavedFirstRegister = JSC::ARMRegisters::r4; + static const RegisterID CalleeSavedLastRegister = JSC::ARMRegisters::r11; + static const RegisterID IntegerOpRegister = JSC::X86Registers::r0; + static const FPRegisterID FPGpr0 = JSC::ARMRegisters::d0; + + static const int RegisterSize = 4; + + static const RegisterID RegisterArgument1 = JSC::ARMRegisters::r0; + static const RegisterID RegisterArgument2 = JSC::ARMRegisters::r1; + static const RegisterID RegisterArgument3 = JSC::ARMRegisters::r2; + static const RegisterID RegisterArgument4 = JSC::ARMRegisters::r3; + + static const int RegisterArgumentCount = 4; + static RegisterID registerForArgument(int index) + { + assert(index >= 0 && index < RegisterArgumentCount); + return static_cast<RegisterID>(JSC::ARMRegisters::r0 + index); + }; +#else +#error Argh. +#endif + + // Explicit type to allow distinguishing between + // pushing an address itself or the value it points + // to onto the stack when calling functions. + struct Pointer : public Address + { + explicit Pointer(const Address& addr) + : Address(addr) + {} + explicit Pointer(RegisterID reg, int32_t offset) + : Address(reg, offset) + {} + }; + + struct VoidType {}; + static const VoidType Void; + + + typedef JSC::FunctionPtr FunctionPtr; + + struct CallToLink { + Call call; + FunctionPtr externalFunction; + const char* functionName; + }; + struct PointerToValue { + PointerToValue(IR::Temp *value) : value(value) {} + IR::Temp *value; + }; + + void callAbsolute(const char* functionName, FunctionPtr function) { + CallToLink ctl; + ctl.call = call(); + ctl.externalFunction = function; + ctl.functionName = functionName; + _callsToLink.append(ctl); + } + + void registerBlock(IR::BasicBlock*); + void jumpToBlock(IR::BasicBlock* current, IR::BasicBlock *target); + void addPatch(IR::BasicBlock* targetBlock, Jump targetJump); + + Pointer loadTempAddress(RegisterID reg, IR::Temp *t); + + void loadArgument(RegisterID source, RegisterID dest) + { + move(source, dest); + } + + void loadArgument(TrustedImmPtr ptr, RegisterID dest) + { + move(TrustedImmPtr(ptr), dest); + } + + void loadArgument(const Pointer& ptr, RegisterID dest) + { + addPtr(TrustedImm32(ptr.offset), ptr.base, dest); + } + + void loadArgument(PointerToValue temp, RegisterID dest) + { + assert(temp.value); + + Pointer addr = loadTempAddress(dest, temp.value); + loadArgument(addr, dest); + } + +#ifdef VALUE_FITS_IN_REGISTER + void loadArgument(IR::Temp* temp, RegisterID dest) + { + if (!temp) { + VM::Value undefined = VM::Value::undefinedValue(); + move(TrustedImm64(undefined.val), dest); + } else { + Pointer addr = loadTempAddress(dest, temp); + load64(addr, dest); + } + } + + void loadArgument(IR::Const* c, RegisterID dest) + { + VM::Value v = convertToValue(c); + move(TrustedImm64(v.val), dest); + } + + void loadArgument(IR::Expr* expr, RegisterID dest) + { + if (!expr) { + VM::Value undefined = VM::Value::undefinedValue(); + move(TrustedImm64(undefined.val), dest); + } else if (expr->asTemp()){ + loadArgument(expr->asTemp(), dest); + } else if (expr->asConst()) { + loadArgument(expr->asConst(), dest); + } else { + assert(!"unimplemented expression type in loadArgument"); + } + } +#else + void loadArgument(IR::Expr*, RegisterID) + { + assert(!"unimplemented: expression in loadArgument"); + } +#endif + + void loadArgument(VM::String* string, RegisterID dest) + { + loadArgument(TrustedImmPtr(string), dest); + } + + void loadArgument(TrustedImm32 imm32, RegisterID dest) + { + xorPtr(dest, dest); + if (imm32.m_value) + move(imm32, dest); + } + + void storeArgument(RegisterID src, IR::Temp *temp) + { + if (temp) { + Pointer addr = loadTempAddress(ScratchRegister, temp); +#ifdef VALUE_FITS_IN_REGISTER + store64(src, addr); +#else + // If the value doesn't fit into a register, then the + // register contains the address to where the argument + // (return value) is stored. Copy it from there. + copyValue(addr, Pointer(src, 0)); +#endif + } + } + +#ifdef VALUE_FITS_IN_REGISTER + void storeArgument(RegisterID src, const Pointer &dest) + { + store64(src, dest); + } +#endif + + void storeArgument(RegisterID src, RegisterID dest) + { + move(src, dest); + } + + void storeArgument(RegisterID, VoidType) + { + } + + using JSC::MacroAssembler::push; + + void push(const Pointer& ptr) + { + addPtr(TrustedImm32(ptr.offset), ptr.base, ScratchRegister); + push(ScratchRegister); + } + + void push(VM::Value value) + { +#ifdef VALUE_FITS_IN_REGISTER + move(TrustedImm64(value.val), ScratchRegister); + push(ScratchRegister); +#else + move(TrustedImm32(value.tag), ScratchRegister); + push(ScratchRegister); + move(TrustedImm32(value.int_32), ScratchRegister); + push(ScratchRegister); +#endif + } + + void push(PointerToValue temp) + { + assert (temp.value); + + Pointer ptr = loadTempAddress(ScratchRegister, temp.value); + push(ptr); + } + + void push(IR::Temp* temp) + { + if (temp) { + Address addr = loadTempAddress(ScratchRegister, temp); + addr.offset += 4; + push(addr); + addr.offset -= 4; + push(addr); + } else { + VM::Value undefined = VM::Value::undefinedValue(); + push(undefined); + } + } + + void push(IR::Const* c) + { + VM::Value v = convertToValue(c); + push(v); + } + + void push(IR::Expr* e) + { + if (!e) { + VM::Value undefined = VM::Value::undefinedValue(); + push(undefined); + } else if (IR::Const *c = e->asConst()) + push(c); + else if (IR::Temp *t = e->asTemp()) { + push(t); + } else { + assert(!"Trying to push an expression that is not a Temp or Const"); + } + } + + void push(TrustedImmPtr ptr) + { + move(TrustedImmPtr(ptr), ScratchRegister); + push(ScratchRegister); + } + + void push(VM::String* name) + { + push(TrustedImmPtr(name)); + } + + using JSC::MacroAssembler::loadDouble; + void loadDouble(IR::Temp* temp, FPRegisterID dest) + { + Pointer ptr = loadTempAddress(ScratchRegister, temp); + loadDouble(ptr, dest); + } + + using JSC::MacroAssembler::storeDouble; + void storeDouble(FPRegisterID source, IR::Temp* temp) + { + Pointer ptr = loadTempAddress(ScratchRegister, temp); + storeDouble(source, ptr); + } + + template <typename Result, typename Source> + void copyValue(Result result, Source source); + + void storeValue(VM::Value value, Address destination) + { +#ifdef VALUE_FITS_IN_REGISTER + store64(TrustedImm64(value.val), destination); +#else + store32(TrustedImm32(value.int_32), destination); + destination.offset += 4; + store32(TrustedImm32(value.tag), destination); +#endif + } + + void storeValue(VM::Value value, IR::Temp* temp); + + void enterStandardStackFrame(int locals); + void leaveStandardStackFrame(int locals); + + void callFunctionPrologue() + { +#if CPU(X86) + // Callee might clobber it :( + push(ContextRegister); +#endif + } + void callFunctionEpilogue() + { +#if CPU(X86) + pop(ContextRegister); +#endif + } + + static inline int sizeOfArgument(VoidType) + { return 0; } + static inline int sizeOfArgument(RegisterID) + { return RegisterSize; } + static inline int sizeOfArgument(IR::Temp*) + { return 8; } // Size of value + static inline int sizeOfArgument(IR::Expr*) + { return 8; } // Size of value + static inline int sizeOfArgument(const Pointer&) + { return sizeof(void*); } + static inline int sizeOfArgument(VM::String* string) + { return sizeof(string); } + static inline int sizeOfArgument(const PointerToValue &) + { return sizeof(void *); } + static inline int sizeOfArgument(TrustedImmPtr) + { return sizeof(void*); } + static inline int sizeOfArgument(TrustedImm32) + { return 4; } + + struct ArgumentLoader + { + ArgumentLoader(Assembler* _assembler, int totalNumberOfArguments) + : assembler(_assembler) + , stackSpaceForArguments(0) + , currentRegisterIndex(qMin(totalNumberOfArguments - 1, RegisterArgumentCount - 1)) + { + } + + template <typename T> + void load(T argument) + { + if (currentRegisterIndex >= 0) { + assembler->loadArgument(argument, registerForArgument(currentRegisterIndex)); + --currentRegisterIndex; + } else { + assembler->push(argument); + stackSpaceForArguments += sizeOfArgument(argument); + } + } + + void load(VoidType) + { + if (currentRegisterIndex >= 0) + --currentRegisterIndex; + } + + Assembler *assembler; + int stackSpaceForArguments; + int currentRegisterIndex; + }; + + template <typename ArgRet, typename Arg1, typename Arg2, typename Arg3, typename Arg4, typename Arg5> + void generateFunctionCallImp(ArgRet r, const char* functionName, FunctionPtr function, Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5) + { + callFunctionPrologue(); + + int totalNumberOfArgs = 5; + + // If necessary reserve space for the return value on the stack and + // pass the pointer to it as the first hidden parameter. + bool returnValueOnStack = false; + int sizeOfReturnValueOnStack = sizeOfArgument(r); + if (sizeOfReturnValueOnStack > RegisterSize) { + sub32(TrustedImm32(sizeOfReturnValueOnStack), StackPointerRegister); + ++totalNumberOfArgs; + returnValueOnStack = true; + } + + ArgumentLoader l(this, totalNumberOfArgs); + l.load(arg5); + l.load(arg4); + l.load(arg3); + l.load(arg2); + l.load(arg1); + + if (returnValueOnStack) { + // Load address of return value + l.load(Pointer(StackPointerRegister, l.stackSpaceForArguments)); + } + + callAbsolute(functionName, function); + + int stackSizeToCorrect = l.stackSpaceForArguments; + if (returnValueOnStack) { + stackSizeToCorrect -= sizeof(void*); // Callee removed the hidden argument (address of return value) + stackSizeToCorrect += sizeOfReturnValueOnStack; + } + + storeArgument(ReturnValueRegister, r); + + if (stackSizeToCorrect) + add32(TrustedImm32(stackSizeToCorrect), StackPointerRegister); + + callFunctionEpilogue(); + } + + template <typename ArgRet, typename Arg1, typename Arg2, typename Arg3, typename Arg4> + void generateFunctionCallImp(ArgRet r, const char* functionName, FunctionPtr function, Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4) + { + generateFunctionCallImp(r, functionName, function, arg1, arg2, arg3, arg4, VoidType()); + } + + template <typename ArgRet, typename Arg1, typename Arg2, typename Arg3> + void generateFunctionCallImp(ArgRet r, const char* functionName, FunctionPtr function, Arg1 arg1, Arg2 arg2, Arg3 arg3) + { + generateFunctionCallImp(r, functionName, function, arg1, arg2, arg3, VoidType(), VoidType()); + } + + template <typename ArgRet, typename Arg1, typename Arg2> + void generateFunctionCallImp(ArgRet r, const char* functionName, FunctionPtr function, Arg1 arg1, Arg2 arg2) + { + generateFunctionCallImp(r, functionName, function, arg1, arg2, VoidType(), VoidType(), VoidType()); + } + + template <typename ArgRet, typename Arg1> + void generateFunctionCallImp(ArgRet r, const char* functionName, FunctionPtr function, Arg1 arg1) + { + generateFunctionCallImp(r, functionName, function, arg1, VoidType(), VoidType(), VoidType(), VoidType()); + } + + typedef Jump (Assembler::*MemRegBinOp)(Address, RegisterID); + typedef Jump (Assembler::*ImmRegBinOp)(TrustedImm32, RegisterID); + + struct BinaryOperationInfo { + const char *name; + VM::Value (*fallbackImplementation)(const VM::Value, const VM::Value, VM::ExecutionContext *); + MemRegBinOp inlineMemRegOp; + ImmRegBinOp inlineImmRegOp; + }; + + static const BinaryOperationInfo binaryOperations[QQmlJS::IR::LastAluOp + 1]; + + void generateBinOp(IR::AluOp operation, IR::Temp* target, IR::Expr* left, IR::Expr* right); + + Jump inline_add32(Address addr, RegisterID reg) + { + return branchAdd32(Overflow, addr, reg); + } + + Jump inline_add32(TrustedImm32 imm, RegisterID reg) + { + return branchAdd32(Overflow, imm, reg); + } + + Jump inline_sub32(Address addr, RegisterID reg) + { + return branchSub32(Overflow, addr, reg); + } + + Jump inline_sub32(TrustedImm32 imm, RegisterID reg) + { + return branchSub32(Overflow, imm, reg); + } + + Jump inline_mul32(Address addr, RegisterID reg) + { + return branchMul32(Overflow, addr, reg); + } + + Jump inline_mul32(TrustedImm32 imm, RegisterID reg) + { + return branchMul32(Overflow, imm, reg, reg); + } + + Jump inline_shl32(Address addr, RegisterID reg) + { + load32(addr, ScratchRegister); + and32(TrustedImm32(0x1f), ScratchRegister); + lshift32(ScratchRegister, reg); + return Jump(); + } + + Jump inline_shl32(TrustedImm32 imm, RegisterID reg) + { + imm.m_value &= 0x1f; + lshift32(imm, reg); + return Jump(); + } + + Jump inline_shr32(Address addr, RegisterID reg) + { + load32(addr, ScratchRegister); + and32(TrustedImm32(0x1f), ScratchRegister); + rshift32(ScratchRegister, reg); + return Jump(); + } + + Jump inline_shr32(TrustedImm32 imm, RegisterID reg) + { + imm.m_value &= 0x1f; + rshift32(imm, reg); + return Jump(); + } + + Jump inline_ushr32(Address addr, RegisterID reg) + { + load32(addr, ScratchRegister); + and32(TrustedImm32(0x1f), ScratchRegister); + urshift32(ScratchRegister, reg); + return Jump(); + } + + Jump inline_ushr32(TrustedImm32 imm, RegisterID reg) + { + imm.m_value &= 0x1f; + urshift32(imm, reg); + return Jump(); + } + + Jump inline_and32(Address addr, RegisterID reg) + { + and32(addr, reg); + return Jump(); + } + + Jump inline_and32(TrustedImm32 imm, RegisterID reg) + { + and32(imm, reg); + return Jump(); + } + + Jump inline_or32(Address addr, RegisterID reg) + { + or32(addr, reg); + return Jump(); + } + + Jump inline_or32(TrustedImm32 imm, RegisterID reg) + { + or32(imm, reg); + return Jump(); + } + + Jump inline_xor32(Address addr, RegisterID reg) + { + xor32(addr, reg); + return Jump(); + } + + Jump inline_xor32(TrustedImm32 imm, RegisterID reg) + { + xor32(imm, reg); + return Jump(); + } + + void link(VM::Function *vmFunc); + +private: + IR::Function* _function; + QHash<IR::BasicBlock *, Label> _addrs; + QHash<IR::BasicBlock *, QVector<Jump> > _patches; + QList<CallToLink> _callsToLink; +}; + +class Q_V4_EXPORT InstructionSelection: + protected IR::InstructionSelection, + public EvalInstructionSelection +{ +public: + InstructionSelection(VM::ExecutionEngine *engine, IR::Module *module); + ~InstructionSelection(); + + virtual void run(VM::Function *vmFunction, IR::Function *function); + +protected: + virtual void callBuiltinInvalid(IR::Name *func, IR::ExprList *args, IR::Temp *result); + virtual void callBuiltinTypeofMember(IR::Temp *base, const QString &name, IR::Temp *result); + virtual void callBuiltinTypeofSubscript(IR::Temp *base, IR::Temp *index, IR::Temp *result); + virtual void callBuiltinTypeofName(const QString &name, IR::Temp *result); + virtual void callBuiltinTypeofValue(IR::Temp *value, IR::Temp *result); + virtual void callBuiltinDeleteMember(IR::Temp *base, const QString &name, IR::Temp *result); + virtual void callBuiltinDeleteSubscript(IR::Temp *base, IR::Temp *index, IR::Temp *result); + virtual void callBuiltinDeleteName(const QString &name, IR::Temp *result); + virtual void callBuiltinDeleteValue(IR::Temp *result); + virtual void callBuiltinPostDecrementMember(IR::Temp *base, const QString &name, IR::Temp *result); + virtual void callBuiltinPostDecrementSubscript(IR::Temp *base, IR::Temp *index, IR::Temp *result); + virtual void callBuiltinPostDecrementName(const QString &name, IR::Temp *result); + virtual void callBuiltinPostDecrementValue(IR::Temp *value, IR::Temp *result); + virtual void callBuiltinPostIncrementMember(IR::Temp *base, const QString &name, IR::Temp *result); + virtual void callBuiltinPostIncrementSubscript(IR::Temp *base, IR::Temp *index, IR::Temp *result); + virtual void callBuiltinPostIncrementName(const QString &name, IR::Temp *result); + virtual void callBuiltinPostIncrementValue(IR::Temp *value, IR::Temp *result); + virtual void callBuiltinThrow(IR::Temp *arg); + virtual void callBuiltinCreateExceptionHandler(IR::Temp *result); + virtual void callBuiltinDeleteExceptionHandler(); + virtual void callBuiltinGetException(IR::Temp *result); + virtual void callBuiltinForeachIteratorObject(IR::Temp *arg, IR::Temp *result); + virtual void callBuiltinForeachNextPropertyname(IR::Temp *arg, IR::Temp *result); + virtual void callBuiltinPushWithScope(IR::Temp *arg); + virtual void callBuiltinPopScope(); + virtual void callBuiltinDeclareVar(bool deletable, const QString &name); + virtual void callBuiltinDefineGetterSetter(IR::Temp *object, const QString &name, IR::Temp *getter, IR::Temp *setter); + virtual void callBuiltinDefineProperty(IR::Temp *object, const QString &name, IR::Temp *value); + virtual void callBuiltinDefineArrayProperty(IR::Temp *object, int index, IR::Temp *value); + virtual void callProperty(IR::Temp *base, const QString &name, IR::ExprList *args, IR::Temp *result); + virtual void callSubscript(IR::Temp *base, IR::Temp *index, IR::ExprList *args, IR::Temp *result); + virtual void callValue(IR::Temp *value, IR::ExprList *args, IR::Temp *result); + virtual void loadThisObject(IR::Temp *temp); + virtual void loadConst(IR::Const *sourceConst, IR::Temp *targetTemp); + virtual void loadString(const QString &str, IR::Temp *targetTemp); + virtual void loadRegexp(IR::RegExp *sourceRegexp, IR::Temp *targetTemp); + virtual void getActivationProperty(const QString &name, IR::Temp *temp); + virtual void setActivationProperty(IR::Expr *source, const QString &targetName); + virtual void initClosure(IR::Closure *closure, IR::Temp *target); + virtual void getProperty(IR::Temp *base, const QString &name, IR::Temp *target); + virtual void setProperty(IR::Expr *source, IR::Temp *targetBase, const QString &targetName); + virtual void getElement(IR::Temp *base, IR::Temp *index, IR::Temp *target); + virtual void setElement(IR::Expr *source, IR::Temp *targetBase, IR::Temp *targetIndex); + virtual void copyValue(IR::Temp *sourceTemp, IR::Temp *targetTemp); + virtual void unop(IR::AluOp oper, IR::Temp *sourceTemp, IR::Temp *targetTemp); + virtual void binop(IR::AluOp oper, IR::Expr *leftSource, IR::Expr *rightSource, IR::Temp *target); + virtual void inplaceNameOp(IR::AluOp oper, IR::Expr *sourceExpr, const QString &targetName); + virtual void inplaceElementOp(IR::AluOp oper, IR::Expr *sourceExpr, IR::Temp *targetBaseTemp, IR::Temp *targetIndexTemp); + virtual void inplaceMemberOp(IR::AluOp oper, IR::Expr *source, IR::Temp *targetBase, const QString &targetName); + + typedef Assembler::Address Address; + typedef Assembler::Pointer Pointer; + + Address addressForArgument(int index) const + { + if (index < Assembler::RegisterArgumentCount) + return Address(_asm->registerForArgument(index), 0); + + // StackFrameRegister points to its old value on the stack, and above + // it we have the return address, hence the need to step over two + // values before reaching the first argument. + return Address(Assembler::StackFrameRegister, (index - Assembler::RegisterArgumentCount + 2) * sizeof(void*)); + } + + // Some run-time functions take (Value* args, int argc). This function is for populating + // the args. + Pointer argumentAddressForCall(int argument) + { + const int index = _function->maxNumberOfArguments - argument; + return Pointer(Assembler::StackFrameRegister, sizeof(VM::Value) * (-index) + - sizeof(void*) // size of ebp + ); + } + Pointer baseAddressForCallArguments() + { + return argumentAddressForCall(0); + } + + VM::String *identifier(const QString &s); + virtual void constructActivationProperty(IR::Name *func, IR::ExprList *args, IR::Temp *result); + virtual void constructProperty(IR::Temp *base, const QString &name, IR::ExprList *args, IR::Temp *result); + virtual void constructValue(IR::Temp *value, IR::ExprList *args, IR::Temp *result); + + virtual void visitJump(IR::Jump *); + virtual void visitCJump(IR::CJump *); + virtual void visitRet(IR::Ret *); + +private: + #define isel_stringIfyx(s) #s + #define isel_stringIfy(s) isel_stringIfyx(s) + + #define generateFunctionCall(t, function, ...) \ + _asm->generateFunctionCallImp(t, isel_stringIfy(function), function, __VA_ARGS__) + + int prepareVariableArguments(IR::ExprList* args); + + typedef VM::Value (*ActivationMethod)(VM::ExecutionContext *, VM::String *name, VM::Value *args, int argc); + typedef VM::Value (*BuiltinMethod)(VM::ExecutionContext *, VM::Value *args, int argc); + void callRuntimeMethodImp(IR::Temp *result, const char* name, ActivationMethod method, IR::Expr *base, IR::ExprList *args); + void callRuntimeMethodImp(IR::Temp *result, const char* name, BuiltinMethod method, IR::ExprList *args); +#define callRuntimeMethod(result, function, ...) \ + callRuntimeMethodImp(result, isel_stringIfy(function), function, __VA_ARGS__) + + IR::BasicBlock *_block; + IR::Function* _function; + VM::Function* _vmFunction; + Assembler* _asm; +}; + +class Q_V4_EXPORT ISelFactory: public EvalISelFactory +{ +public: + virtual ~ISelFactory() {} + virtual EvalInstructionSelection *create(VM::ExecutionEngine *engine, IR::Module *module) + { return new InstructionSelection(engine, module); } +}; + +} // end of namespace MASM +} // end of namespace QQmlJS + +#endif // QV4ISEL_MASM_P_H diff --git a/src/v4/qv4isel_p.cpp b/src/v4/qv4isel_p.cpp new file mode 100644 index 0000000000..b408be3673 --- /dev/null +++ b/src/v4/qv4isel_p.cpp @@ -0,0 +1,414 @@ +#include "debugging.h" +#include "qmljs_engine.h" +#include "qv4ir_p.h" +#include "qv4isel_p.h" +#include "qv4isel_util_p.h" +#include "qv4functionobject.h" + +#include <QString> + +#include <cassert> + +namespace { +QTextStream qout(stderr, QIODevice::WriteOnly); +} // anonymous namespace + +using namespace QQmlJS; +using namespace QQmlJS::IR; + +EvalInstructionSelection::EvalInstructionSelection(VM::ExecutionEngine *engine, Module *module) + : _engine(engine) +{ + assert(engine); + assert(module); + + createFunctionMapping(engine, module->rootFunction); + foreach (IR::Function *f, module->functions) { + assert(_irToVM.contains(f)); + } +} + +EvalInstructionSelection::~EvalInstructionSelection() +{} + +EvalISelFactory::~EvalISelFactory() +{} + +VM::Function *EvalInstructionSelection::createFunctionMapping(VM::ExecutionEngine *engine, Function *irFunction) +{ + VM::Function *vmFunction = engine->newFunction(irFunction->name ? *irFunction->name : QString()); + _irToVM.insert(irFunction, vmFunction); + + vmFunction->hasDirectEval = irFunction->hasDirectEval; + vmFunction->usesArgumentsObject = irFunction->usesArgumentsObject; + vmFunction->hasNestedFunctions = !irFunction->nestedFunctions.isEmpty(); + vmFunction->isStrict = irFunction->isStrict; + + foreach (const QString *formal, irFunction->formals) + if (formal) + vmFunction->formals.append(engine->identifier(*formal)); + foreach (const QString *local, irFunction->locals) + if (local) + vmFunction->locals.append(engine->identifier(*local)); + + foreach (IR::Function *function, irFunction->nestedFunctions) + createFunctionMapping(engine, function); + + + if (engine->debugger) + engine->debugger->mapFunction(vmFunction, irFunction); + + return vmFunction; +} + +VM::Function *EvalInstructionSelection::vmFunction(Function *f) { + VM::Function *function = _irToVM[f]; + if (!function->code) + run(function, f); + return function; +} + +void InstructionSelection::visitMove(IR::Move *s) +{ + if (s->op == IR::OpInvalid) { + if (IR::Name *n = s->target->asName()) { + if (s->source->asTemp() || s->source->asConst()) { + setActivationProperty(s->source, *n->id); + return; + } + } else if (IR::Temp *t = s->target->asTemp()) { + if (IR::Name *n = s->source->asName()) { + if (*n->id == QStringLiteral("this")) // TODO: `this' should be a builtin. + loadThisObject(t); + else + getActivationProperty(*n->id, t); + return; + } else if (IR::Const *c = s->source->asConst()) { + loadConst(c, t); + return; + } else if (IR::Temp *t2 = s->source->asTemp()) { + copyValue(t2, t); + return; + } else if (IR::String *str = s->source->asString()) { + loadString(*str->value, t); + return; + } else if (IR::RegExp *re = s->source->asRegExp()) { + loadRegexp(re, t); + return; + } else if (IR::Closure *clos = s->source->asClosure()) { + initClosure(clos, t); + return; + } else if (IR::New *ctor = s->source->asNew()) { + if (Name *func = ctor->base->asName()) { + constructActivationProperty(func, ctor->args, t); + return; + } else if (IR::Member *member = ctor->base->asMember()) { + constructProperty(member->base->asTemp(), *member->name, ctor->args, t); + return; + } else if (IR::Temp *value = ctor->base->asTemp()) { + constructValue(value, ctor->args, t); + return; + } + } else if (IR::Member *m = s->source->asMember()) { + if (IR::Temp *base = m->base->asTemp()) { + getProperty(base, *m->name, t); + return; + } + } else if (IR::Subscript *ss = s->source->asSubscript()) { + getElement(ss->base->asTemp(), ss->index->asTemp(), t); + return; + } else if (IR::Unop *u = s->source->asUnop()) { + if (IR::Temp *e = u->expr->asTemp()) { + unop(u->op, e, t); + return; + } + } else if (IR::Binop *b = s->source->asBinop()) { + if ((b->left->asTemp() || b->left->asConst()) + && (b->right->asTemp() || b->right->asConst())) { + binop(b->op, b->left, b->right, t); + return; + } + } else if (IR::Call *c = s->source->asCall()) { + if (c->base->asName()) { + callBuiltin(c, t); + return; + } else if (Member *member = c->base->asMember()) { + callProperty(member->base, *member->name, c->args, t); + return; + } else if (Subscript *s = c->base->asSubscript()) { + callSubscript(s->base, s->index, c->args, t); + return; + } else if (IR::Temp *value = c->base->asTemp()) { + callValue(value, c->args, t); + return; + } + } + } else if (IR::Member *m = s->target->asMember()) { + if (IR::Temp *base = m->base->asTemp()) { + if (s->source->asTemp() || s->source->asConst()) { + setProperty(s->source, base, *m->name); + return; + } + } + } else if (IR::Subscript *ss = s->target->asSubscript()) { + if (s->source->asTemp() || s->source->asConst()) { + setElement(s->source, ss->base->asTemp(), ss->index->asTemp()); + return; + } + } + } else { + // inplace assignment, e.g. x += 1, ++x, ... + if (IR::Temp *t = s->target->asTemp()) { + if (s->source->asTemp() || s->source->asConst()) { + binop(s->op, t, s->source, t); + return; + } + } else if (IR::Name *n = s->target->asName()) { + if (s->source->asTemp() || s->source->asConst()) { + inplaceNameOp(s->op, s->source, *n->id); + return; + } + } else if (IR::Subscript *ss = s->target->asSubscript()) { + if (s->source->asTemp() || s->source->asConst()) { + inplaceElementOp(s->op, s->source, ss->base->asTemp(), + ss->index->asTemp()); + return; + } + } else if (IR::Member *m = s->target->asMember()) { + if (s->source->asTemp() || s->source->asConst()) { + inplaceMemberOp(s->op, s->source, m->base->asTemp(), *m->name); + return; + } + } + } + + // For anything else...: + Q_UNIMPLEMENTED(); + s->dump(qout, IR::Stmt::MIR); + qout << endl; + assert(!"TODO"); +} + +InstructionSelection::~InstructionSelection() +{ +} + +void InstructionSelection::visitEnter(Enter *) +{ + Q_UNREACHABLE(); +} + +void InstructionSelection::visitLeave(Leave *) +{ + Q_UNREACHABLE(); +} + +void InstructionSelection::visitExp(IR::Exp *s) +{ + if (IR::Call *c = s->expr->asCall()) { + // These are calls where the result is ignored. + if (c->base->asName()) { + callBuiltin(c, 0); + } else if (Temp *value = c->base->asTemp()) { + callValue(value, c->args, 0); + } else if (Member *member = c->base->asMember()) { + callProperty(member->base, *member->name, c->args, 0); + } else if (Subscript *s = c->base->asSubscript()) { + callSubscript(s->base, s->index, c->args, 0); + } else { + Q_UNIMPLEMENTED(); + } + } else { + Q_UNIMPLEMENTED(); + } +} + +void InstructionSelection::callBuiltin(IR::Call *call, IR::Temp *result) +{ + IR::Name *baseName = call->base->asName(); + assert(baseName != 0); + + switch (baseName->builtin) { + case IR::Name::builtin_invalid: + callBuiltinInvalid(baseName, call->args, result); + return; + + case IR::Name::builtin_typeof: { + if (IR::Member *m = call->args->expr->asMember()) { + callBuiltinTypeofMember(m->base->asTemp(), *m->name, result); + return; + } else if (IR::Subscript *ss = call->args->expr->asSubscript()) { + callBuiltinTypeofSubscript(ss->base->asTemp(), ss->index->asTemp(), result); + return; + } else if (IR::Name *n = call->args->expr->asName()) { + callBuiltinTypeofName(*n->id, result); + return; + } else if (IR::Temp *arg = call->args->expr->asTemp()){ + assert(arg != 0); + callBuiltinTypeofValue(arg, result); + return; + } + } break; + + case IR::Name::builtin_delete: { + if (IR::Member *m = call->args->expr->asMember()) { + callBuiltinDeleteMember(m->base->asTemp(), *m->name, result); + return; + } else if (IR::Subscript *ss = call->args->expr->asSubscript()) { + callBuiltinDeleteSubscript(ss->base->asTemp(), ss->index->asTemp(), result); + return; + } else if (IR::Name *n = call->args->expr->asName()) { + callBuiltinDeleteName(*n->id, result); + return; + } else if (call->args->expr->asTemp()){ + // TODO: should throw in strict mode + callBuiltinDeleteValue(result); + return; + } + } break; + + case IR::Name::builtin_postincrement: { + if (IR::Member *m = call->args->expr->asMember()) { + callBuiltinPostIncrementMember(m->base->asTemp(), *m->name, result); + return; + } else if (IR::Subscript *ss = call->args->expr->asSubscript()) { + callBuiltinPostIncrementSubscript(ss->base->asTemp(), ss->index->asTemp(), result); + return; + } else if (IR::Name *n = call->args->expr->asName()) { + callBuiltinPostIncrementName(*n->id, result); + return; + } else if (IR::Temp *arg = call->args->expr->asTemp()){ + assert(arg != 0); + callBuiltinPostIncrementValue(arg, result); + return; + } + } break; + + case IR::Name::builtin_postdecrement: { + if (IR::Member *m = call->args->expr->asMember()) { + callBuiltinPostDecrementMember(m->base->asTemp(), *m->name, result); + return; + } else if (IR::Subscript *ss = call->args->expr->asSubscript()) { + callBuiltinPostDecrementSubscript(ss->base->asTemp(), ss->index->asTemp(), result); + return; + } else if (IR::Name *n = call->args->expr->asName()) { + callBuiltinPostDecrementName(*n->id, result); + return; + } else if (IR::Temp *arg = call->args->expr->asTemp()){ + assert(arg != 0); + callBuiltinPostDecrementValue(arg, result); + return; + } + } break; + + case IR::Name::builtin_throw: { + IR::Temp *arg = call->args->expr->asTemp(); + assert(arg != 0); + callBuiltinThrow(arg); + } return; + + case IR::Name::builtin_create_exception_handler: + callBuiltinCreateExceptionHandler(result); + return; + + case IR::Name::builtin_delete_exception_handler: + callBuiltinDeleteExceptionHandler(); + return; + + case IR::Name::builtin_get_exception: + callBuiltinGetException(result); + return; + + case IR::Name::builtin_foreach_iterator_object: { + IR::Temp *arg = call->args->expr->asTemp(); + assert(arg != 0); + callBuiltinForeachIteratorObject(arg, result); + } return; + + case IR::Name::builtin_foreach_next_property_name: { + IR::Temp *arg = call->args->expr->asTemp(); + assert(arg != 0); + callBuiltinForeachNextPropertyname(arg, result); + } return; + case IR::Name::builtin_push_with_scope: { + IR::Temp *arg = call->args->expr->asTemp(); + assert(arg != 0); + callBuiltinPushWithScope(arg); + } return; + + case IR::Name::builtin_pop_scope: + callBuiltinPopScope(); + return; + + case IR::Name::builtin_declare_vars: { + if (!call->args) + return; + IR::Const *deletable = call->args->expr->asConst(); + assert(deletable->type == IR::BoolType); + for (IR::ExprList *it = call->args->next; it; it = it->next) { + IR::Name *arg = it->expr->asName(); + assert(arg != 0); + callBuiltinDeclareVar(deletable->value != 0, *arg->id); + } + } return; + + case IR::Name::builtin_define_getter_setter: { + if (!call->args) + return; + IR::ExprList *args = call->args; + IR::Temp *object = args->expr->asTemp(); + assert(object); + args = args->next; + assert(args); + IR::Name *name = args->expr->asName(); + args = args->next; + assert(args); + IR::Temp *getter = args->expr->asTemp(); + args = args->next; + assert(args); + IR::Temp *setter = args->expr->asTemp(); + + callBuiltinDefineGetterSetter(object, *name->id, getter, setter); + } return; + + case IR::Name::builtin_define_property: { + if (!call->args) + return; + IR::ExprList *args = call->args; + IR::Temp *object = args->expr->asTemp(); + assert(object); + args = args->next; + assert(args); + IR::Name *name = args->expr->asName(); + args = args->next; + assert(args); + IR::Temp *value = args->expr->asTemp(); + + callBuiltinDefineProperty(object, *name->id, value); + } return; + + case IR::Name::builtin_define_array_property: { + if (!call->args) + return; + IR::ExprList *args = call->args; + IR::Temp *object = args->expr->asTemp(); + assert(object); + args = args->next; + assert(args); + IR::Const *index = args->expr->asConst(); + args = args->next; + assert(args); + IR::Temp *value = args->expr->asTemp(); + + callBuiltinDefineArrayProperty(object, int(index->value), value); + } return; + + default: + break; + } + + Q_UNIMPLEMENTED(); + call->dump(qout); qout << endl; + assert(!"TODO!"); + Q_UNREACHABLE(); +} diff --git a/src/v4/qv4isel_p.h b/src/v4/qv4isel_p.h new file mode 100644 index 0000000000..d4f7052373 --- /dev/null +++ b/src/v4/qv4isel_p.h @@ -0,0 +1,144 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QV4ISEL_P_H +#define QV4ISEL_P_H + +#include "qv4global.h" +#include "qv4ir_p.h" + +#include <qglobal.h> +#include <QHash> + +namespace QQmlJS { + +namespace VM { +struct ExecutionEngine; +struct Function; +} // namespace VM + +class Q_V4_EXPORT EvalInstructionSelection +{ +public: + EvalInstructionSelection(VM::ExecutionEngine *engine, IR::Module *module); + virtual ~EvalInstructionSelection() = 0; + + VM::Function *vmFunction(IR::Function *f); + +protected: + VM::Function *createFunctionMapping(VM::ExecutionEngine *engine, IR::Function *irFunction); + VM::ExecutionEngine *engine() const { return _engine; } + virtual void run(VM::Function *vmFunction, IR::Function *function) = 0; + +private: + VM::ExecutionEngine *_engine; + QHash<IR::Function *, VM::Function *> _irToVM; +}; + +class Q_V4_EXPORT EvalISelFactory +{ +public: + virtual ~EvalISelFactory() = 0; + virtual EvalInstructionSelection *create(VM::ExecutionEngine *engine, IR::Module *module) = 0; +}; + +namespace IR { +class Q_V4_EXPORT InstructionSelection: protected IR::StmtVisitor +{ +public: + virtual ~InstructionSelection() = 0; + +public: // visitor methods for StmtVisitor: + virtual void visitMove(IR::Move *s); + virtual void visitEnter(IR::Enter *); + virtual void visitLeave(IR::Leave *); + virtual void visitExp(IR::Exp *s); + +public: // to implement by subclasses: + virtual void callBuiltinInvalid(IR::Name *func, IR::ExprList *args, IR::Temp *result) = 0; + virtual void callBuiltinTypeofMember(IR::Temp *base, const QString &name, IR::Temp *result) = 0; + virtual void callBuiltinTypeofSubscript(IR::Temp *base, IR::Temp *index, IR::Temp *result) = 0; + virtual void callBuiltinTypeofName(const QString &name, IR::Temp *result) = 0; + virtual void callBuiltinTypeofValue(IR::Temp *value, IR::Temp *result) = 0; + virtual void callBuiltinDeleteMember(IR::Temp *base, const QString &name, IR::Temp *result) = 0; + virtual void callBuiltinDeleteSubscript(IR::Temp *base, IR::Temp *index, IR::Temp *result) = 0; + virtual void callBuiltinDeleteName(const QString &name, IR::Temp *result) = 0; + virtual void callBuiltinDeleteValue(IR::Temp *result) = 0; + virtual void callBuiltinPostDecrementMember(IR::Temp *base, const QString &name, IR::Temp *result) = 0; + virtual void callBuiltinPostDecrementSubscript(IR::Temp *base, IR::Temp *index, IR::Temp *result) = 0; + virtual void callBuiltinPostDecrementName(const QString &name, IR::Temp *result) = 0; + virtual void callBuiltinPostDecrementValue(IR::Temp *value, IR::Temp *result) = 0; + virtual void callBuiltinPostIncrementMember(IR::Temp *base, const QString &name, IR::Temp *result) = 0; + virtual void callBuiltinPostIncrementSubscript(IR::Temp *base, IR::Temp *index, IR::Temp *result) = 0; + virtual void callBuiltinPostIncrementName(const QString &name, IR::Temp *result) = 0; + virtual void callBuiltinPostIncrementValue(IR::Temp *value, IR::Temp *result) = 0; + virtual void callBuiltinThrow(IR::Temp *arg) = 0; + virtual void callBuiltinCreateExceptionHandler(IR::Temp *result) = 0; + virtual void callBuiltinDeleteExceptionHandler() = 0; + virtual void callBuiltinGetException(IR::Temp *result) = 0; + virtual void callBuiltinForeachIteratorObject(IR::Temp *arg, IR::Temp *result) = 0; + virtual void callBuiltinForeachNextPropertyname(IR::Temp *arg, IR::Temp *result) = 0; + virtual void callBuiltinPushWithScope(IR::Temp *arg) = 0; + virtual void callBuiltinPopScope() = 0; + virtual void callBuiltinDeclareVar(bool deletable, const QString &name) = 0; + virtual void callBuiltinDefineGetterSetter(IR::Temp *object, const QString &name, IR::Temp *getter, IR::Temp *setter) = 0; + virtual void callBuiltinDefineProperty(IR::Temp *object, const QString &name, IR::Temp *value) = 0; + virtual void callBuiltinDefineArrayProperty(IR::Temp *object, int index, IR::Temp *value) = 0; + virtual void callValue(IR::Temp *value, IR::ExprList *args, IR::Temp *result) = 0; + virtual void callProperty(IR::Temp *base, const QString &name, IR::ExprList *args, IR::Temp *result) = 0; + virtual void callSubscript(IR::Temp *base, IR::Temp *index, IR::ExprList *args, IR::Temp *result) = 0; + virtual void constructActivationProperty(IR::Name *func, IR::ExprList *args, IR::Temp *result) = 0; + virtual void constructProperty(IR::Temp *base, const QString &name, IR::ExprList *args, IR::Temp *result) = 0; + virtual void constructValue(IR::Temp *value, IR::ExprList *args, IR::Temp *result) = 0; + virtual void loadThisObject(IR::Temp *temp) = 0; + virtual void loadConst(IR::Const *sourceConst, IR::Temp *targetTemp) = 0; + virtual void loadString(const QString &str, IR::Temp *targetTemp) = 0; + virtual void loadRegexp(IR::RegExp *sourceRegexp, IR::Temp *targetTemp) = 0; + virtual void getActivationProperty(const QString &name, IR::Temp *temp) = 0; + virtual void setActivationProperty(IR::Expr *source, const QString &targetName) = 0; + virtual void initClosure(IR::Closure *closure, IR::Temp *target) = 0; + virtual void getProperty(IR::Temp *base, const QString &name, IR::Temp *target) = 0; + virtual void setProperty(IR::Expr *source, IR::Temp *targetBase, const QString &targetName) = 0; + virtual void getElement(IR::Temp *base, IR::Temp *index, IR::Temp *target) = 0; + virtual void setElement(IR::Expr *source, IR::Temp *targetBase, IR::Temp *targetIndex) = 0; + virtual void copyValue(IR::Temp *sourceTemp, IR::Temp *targetTemp) = 0; + virtual void unop(IR::AluOp oper, IR::Temp *sourceTemp, IR::Temp *targetTemp) = 0; + virtual void binop(IR::AluOp oper, IR::Expr *leftSource, IR::Expr *rightSource, IR::Temp *target) = 0; + virtual void inplaceNameOp(IR::AluOp oper, IR::Expr *sourceExpr, const QString &targetName) = 0; + virtual void inplaceElementOp(IR::AluOp oper, IR::Expr *sourceExpr, IR::Temp *targetBaseTemp, IR::Temp *targetIndexTemp) = 0; + virtual void inplaceMemberOp(IR::AluOp oper, IR::Expr *source, IR::Temp *targetBase, const QString &targetName) = 0; + +private: + void callBuiltin(IR::Call *c, IR::Temp *temp); +}; +} // namespace IR + +} // namespace QQmlJS + +#endif // QV4ISEL_P_H diff --git a/src/v4/qv4isel_util_p.h b/src/v4/qv4isel_util_p.h new file mode 100644 index 0000000000..693af03e88 --- /dev/null +++ b/src/v4/qv4isel_util_p.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QV4ISEL_UTIL_P_H +#define QV4ISEL_UTIL_P_H + +#include "qmljs_runtime.h" +#include "qv4ir_p.h" + +namespace QQmlJS { + +inline VM::Value convertToValue(IR::Const *c) +{ + switch (c->type) { + case IR::NullType: + return VM::Value::nullValue(); + case IR::UndefinedType: + return VM::Value::undefinedValue(); + case IR::BoolType: + return VM::Value::fromBoolean(c->value != 0); + case IR::NumberType: { + int ival = (int)c->value; + // +0 != -0, so we need to convert to double when negating 0 + if (ival == c->value && c->value != -0) { + return VM::Value::fromInt32(ival); + } else { + return VM::Value::fromDouble(c->value); + } + } + default: + Q_UNREACHABLE(); + } +} + +} // namespace QQmlJS + +#endif // QV4ISEL_UTIL_P_H diff --git a/src/v4/qv4jsonobject.cpp b/src/v4/qv4jsonobject.cpp new file mode 100644 index 0000000000..fa39476f4e --- /dev/null +++ b/src/v4/qv4jsonobject.cpp @@ -0,0 +1,935 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <qv4jsonobject.h> +#include <qv4objectproto.h> +#include <qv4numberobject.h> +#include <qv4stringobject.h> +#include <qv4booleanobject.h> +#include <qv4objectiterator.h> +#include <qjsondocument.h> +#include <qstack.h> +#include <qstringlist.h> + + +namespace QQmlJS { +namespace VM { + +//#define PARSER_DEBUG +#ifdef PARSER_DEBUG +static int indent = 0; +#define BEGIN qDebug() << QByteArray(4*indent++, ' ').constData() +#define END --indent +#define DEBUG qDebug() << QByteArray(4*indent, ' ').constData() +#else +#define BEGIN if (1) ; else qDebug() +#define END do {} while (0) +#define DEBUG if (1) ; else qDebug() +#endif + + +class Parser +{ +public: + Parser(ExecutionContext *context, const QChar *json, int length); + + Value parse(QJsonParseError *error); + +private: + inline bool eatSpace(); + inline QChar nextToken(); + + Value parseObject(); + Value parseArray(); + bool parseMember(Object *o); + bool parseString(QString *string); + bool parseValue(Value *val); + bool parseNumber(Value *val); + + ExecutionContext *context; + const QChar *head; + const QChar *json; + const QChar *end; + + int nestingLevel; + QJsonParseError::ParseError lastError; +}; + +static const int nestingLimit = 1024; + + +Parser::Parser(ExecutionContext *context, const QChar *json, int length) + : context(context), head(json), json(json), nestingLevel(0), lastError(QJsonParseError::NoError) +{ + end = json + length; +} + + + +/* + +begin-array = ws %x5B ws ; [ left square bracket + +begin-object = ws %x7B ws ; { left curly bracket + +end-array = ws %x5D ws ; ] right square bracket + +end-object = ws %x7D ws ; } right curly bracket + +name-separator = ws %x3A ws ; : colon + +value-separator = ws %x2C ws ; , comma + +Insignificant whitespace is allowed before or after any of the six +structural characters. + +ws = *( + %x20 / ; Space + %x09 / ; Horizontal tab + %x0A / ; Line feed or New line + %x0D ; Carriage return + ) + +*/ + +enum { + Space = 0x20, + Tab = 0x09, + LineFeed = 0x0a, + Return = 0x0d, + BeginArray = 0x5b, + BeginObject = 0x7b, + EndArray = 0x5d, + EndObject = 0x7d, + NameSeparator = 0x3a, + ValueSeparator = 0x2c, + Quote = 0x22 +}; + +bool Parser::eatSpace() +{ + while (json < end) { + if (*json > Space) + break; + if (*json != Space && + *json != Tab && + *json != LineFeed && + *json != Return) + break; + ++json; + } + return (json < end); +} + +QChar Parser::nextToken() +{ + if (!eatSpace()) + return 0; + QChar token = *json++; + switch (token.unicode()) { + case BeginArray: + case BeginObject: + case NameSeparator: + case ValueSeparator: + case EndArray: + case EndObject: + eatSpace(); + case Quote: + break; + default: + token = 0; + break; + } + return token; +} + +/* + JSON-text = object / array +*/ +Value Parser::parse(QJsonParseError *error) +{ +#ifdef PARSER_DEBUG + indent = 0; + qDebug() << ">>>>> parser begin"; +#endif + + eatSpace(); + + Value v; + if (!parseValue(&v)) { +#ifdef PARSER_DEBUG + qDebug() << ">>>>> parser error"; +#endif + if (lastError == QJsonParseError::NoError) + lastError = QJsonParseError::IllegalValue; + error->offset = json - head; + error->error = lastError; + return Value::undefinedValue(); + } + + // some input left... + if (eatSpace()) { + lastError = QJsonParseError::IllegalValue; + error->offset = json - head; + error->error = lastError; + return Value::undefinedValue(); + } + + END; + error->offset = 0; + error->error = QJsonParseError::NoError; + return v; +} + +/* + object = begin-object [ member *( value-separator member ) ] + end-object +*/ + +Value Parser::parseObject() +{ + if (++nestingLevel > nestingLimit) { + lastError = QJsonParseError::DeepNesting; + return Value::undefinedValue(); + } + + BEGIN << "parseObject pos=" << json; + + Object *o = context->engine->newObject(); + Value objectVal = Value::fromObject(o); + + QChar token = nextToken(); + while (token == Quote) { + if (!parseMember(o)) + return Value::undefinedValue(); + token = nextToken(); + if (token != ValueSeparator) + break; + token = nextToken(); + if (token == EndObject) { + lastError = QJsonParseError::MissingObject; + return Value::undefinedValue(); + } + } + + DEBUG << "end token=" << token; + if (token != EndObject) { + lastError = QJsonParseError::UnterminatedObject; + return Value::undefinedValue(); + } + + END; + + --nestingLevel; + return objectVal; +} + +/* + member = string name-separator value +*/ +bool Parser::parseMember(Object *o) +{ + BEGIN << "parseMember"; + if (!o->members) + o->members.reset(new PropertyTable()); + + QString key; + if (!parseString(&key)) + return false; + QChar token = nextToken(); + if (token != NameSeparator) { + lastError = QJsonParseError::MissingNameSeparator; + return false; + } + Value val; + if (!parseValue(&val)) + return false; + + PropertyDescriptor *p = o->members->insert(context->engine->identifier(key)); + p->value = val; + + END; + return true; +} + +/* + array = begin-array [ value *( value-separator value ) ] end-array +*/ +Value Parser::parseArray() +{ + BEGIN << "parseArray"; + Array array; + + if (++nestingLevel > nestingLimit) { + lastError = QJsonParseError::DeepNesting; + return Value::undefinedValue(); + } + + if (!eatSpace()) { + lastError = QJsonParseError::UnterminatedArray; + return Value::undefinedValue(); + } + if (*json == EndArray) { + nextToken(); + } else { + uint index = 0; + while (1) { + Value val; + if (!parseValue(&val)) + return Value::undefinedValue(); + array.set(index, val); + QChar token = nextToken(); + if (token == EndArray) + break; + else if (token != ValueSeparator) { + if (!eatSpace()) + lastError = QJsonParseError::UnterminatedArray; + else + lastError = QJsonParseError::MissingValueSeparator; + return Value::undefinedValue(); + } + ++index; + } + } + + DEBUG << "size =" << array.length(); + END; + + --nestingLevel; + return Value::fromObject(context->engine->newArrayObject(context, array)); +} + +/* +value = false / null / true / object / array / number / string + +*/ + +bool Parser::parseValue(Value *val) +{ + BEGIN << "parse Value" << *json; + + switch ((json++)->unicode()) { + case 'n': + if (end - json < 4) { + lastError = QJsonParseError::IllegalValue; + return false; + } + if (*json++ == 'u' && + *json++ == 'l' && + *json++ == 'l') { + *val = Value::nullValue(); + DEBUG << "value: null"; + END; + return true; + } + lastError = QJsonParseError::IllegalValue; + return false; + case 't': + if (end - json < 4) { + lastError = QJsonParseError::IllegalValue; + return false; + } + if (*json++ == 'r' && + *json++ == 'u' && + *json++ == 'e') { + *val = Value::fromBoolean(true); + DEBUG << "value: true"; + END; + return true; + } + lastError = QJsonParseError::IllegalValue; + return false; + case 'f': + if (end - json < 5) { + lastError = QJsonParseError::IllegalValue; + return false; + } + if (*json++ == 'a' && + *json++ == 'l' && + *json++ == 's' && + *json++ == 'e') { + *val = Value::fromBoolean(false); + DEBUG << "value: false"; + END; + return true; + } + lastError = QJsonParseError::IllegalValue; + return false; + case Quote: { + QString value; + if (!parseString(&value)) + return false; + DEBUG << "value: string"; + END; + *val = Value::fromString(context, value); + return true; + } + case BeginArray: { + *val = parseArray(); + if (val->isUndefined()) + return false; + DEBUG << "value: array"; + END; + return true; + } + case BeginObject: { + *val = parseObject(); + if (val->isUndefined()) + return false; + DEBUG << "value: object"; + END; + return true; + } + case EndArray: + lastError = QJsonParseError::MissingObject; + return false; + default: + --json; + if (!parseNumber(val)) + return false; + DEBUG << "value: number"; + END; + } + + return true; +} + + + + + +/* + number = [ minus ] int [ frac ] [ exp ] + decimal-point = %x2E ; . + digit1-9 = %x31-39 ; 1-9 + e = %x65 / %x45 ; e E + exp = e [ minus / plus ] 1*DIGIT + frac = decimal-point 1*DIGIT + int = zero / ( digit1-9 *DIGIT ) + minus = %x2D ; - + plus = %x2B ; + + zero = %x30 ; 0 + +*/ + +bool Parser::parseNumber(Value *val) +{ + BEGIN << "parseNumber" << *json; + + const QChar *start = json; + bool isInt = true; + + // minus + if (json < end && *json == '-') + ++json; + + // int = zero / ( digit1-9 *DIGIT ) + if (json < end && *json == '0') { + ++json; + } else { + while (json < end && *json >= '0' && *json <= '9') + ++json; + } + + // frac = decimal-point 1*DIGIT + if (json < end && *json == '.') { + isInt = false; + ++json; + while (json < end && *json >= '0' && *json <= '9') + ++json; + } + + // exp = e [ minus / plus ] 1*DIGIT + if (json < end && (*json == 'e' || *json == 'E')) { + isInt = false; + ++json; + if (json < end && (*json == '-' || *json == '+')) + ++json; + while (json < end && *json >= '0' && *json <= '9') + ++json; + } + + QString number(start, json - start); + DEBUG << "numberstring" << number; + + if (isInt) { + bool ok; + int n = number.toInt(&ok); + if (ok && n < (1<<25) && n > -(1<<25)) { + *val = Value::fromInt32(n); + END; + return true; + } + } + + bool ok; + double d; + d = number.toDouble(&ok); + + if (!ok) { + lastError = QJsonParseError::IllegalNumber; + return false; + } + + * val = Value::fromDouble(d); + + END; + return true; +} + +/* + + string = quotation-mark *char quotation-mark + + char = unescaped / + escape ( + %x22 / ; " quotation mark U+0022 + %x5C / ; \ reverse solidus U+005C + %x2F / ; / solidus U+002F + %x62 / ; b backspace U+0008 + %x66 / ; f form feed U+000C + %x6E / ; n line feed U+000A + %x72 / ; r carriage return U+000D + %x74 / ; t tab U+0009 + %x75 4HEXDIG ) ; uXXXX U+XXXX + + escape = %x5C ; \ + + quotation-mark = %x22 ; " + + unescaped = %x20-21 / %x23-5B / %x5D-10FFFF + */ +static inline bool addHexDigit(QChar digit, uint *result) +{ + ushort d = digit.unicode(); + *result <<= 4; + if (d >= '0' && d <= '9') + *result |= (d - '0'); + else if (d >= 'a' && d <= 'f') + *result |= (d - 'a') + 10; + else if (d >= 'A' && d <= 'F') + *result |= (d - 'A') + 10; + else + return false; + return true; +} + +static inline bool scanEscapeSequence(const QChar *&json, const QChar *end, uint *ch) +{ + ++json; + if (json >= end) + return false; + + DEBUG << "scan escape"; + uint escaped = (json++)->unicode(); + switch (escaped) { + case '"': + *ch = '"'; break; + case '\\': + *ch = '\\'; break; + case '/': + *ch = '/'; break; + case 'b': + *ch = 0x8; break; + case 'f': + *ch = 0xc; break; + case 'n': + *ch = 0xa; break; + case 'r': + *ch = 0xd; break; + case 't': + *ch = 0x9; break; + case 'u': { + *ch = 0; + if (json > end - 4) + return false; + for (int i = 0; i < 4; ++i) { + if (!addHexDigit(*json, ch)) + return false; + ++json; + } + if (*ch <= 0x1f) + return false; + return true; + } + default: + return false; + } + return true; +} + + +bool Parser::parseString(QString *string) +{ + BEGIN << "parse string stringPos=" << json; + + while (json < end) { + if (*json == '"') + break; + else if (*json == '\\') { + uint ch = 0; + if (!scanEscapeSequence(json, end, &ch)) { + lastError = QJsonParseError::IllegalEscapeSequence; + return false; + } + qDebug() << "scanEscape" << hex << ch; + if (QChar::requiresSurrogates(ch)) { + *string += QChar::highSurrogate(ch); + *string += QChar::lowSurrogate(ch); + } else { + *string += QChar(ch); + } + } else { + if (json->unicode() <= 0x1f) { + lastError = QJsonParseError::IllegalEscapeSequence; + return false; + } + *string += *json; + ++json; + } + } + ++json; + + if (json > end) { + lastError = QJsonParseError::UnterminatedString; + return false; + } + + END; + return true; +} + + +struct Stringify +{ + ExecutionContext *ctx; + FunctionObject *replacerFunction; + QVector<String *> propertyList; + QString gap; + QString indent; + + QStack<Object *> stack; + + Stringify(ExecutionContext *ctx) : ctx(ctx), replacerFunction(0) {} + + QString Str(const QString &key, Value value); + QString JA(ArrayObject *a); + QString JO(Object *o); + + QString makeMember(const QString &key, Value v); +}; + +static QString quote(const QString &str) +{ + QString product = "\""; + for (int i = 0; i < str.length(); ++i) { + QChar c = str.at(i); + switch (c.unicode()) { + case '"': + product += "\\\""; + break; + case '\\': + product += "\\\\"; + break; + case '\b': + product += "\\b"; + break; + case '\f': + product += "\\f"; + break; + case '\n': + product += "\\n"; + break; + case '\r': + product += "\\r"; + break; + case '\t': + product += "\\t"; + break; + default: + if (c.unicode() <= 0x1f) { + product += "\\u00"; + product += c.unicode() > 0xf ? '1' : '0'; + product += "0123456789abcdef"[c.unicode() & 0xf]; + } else { + product += c; + } + } + } + product += '"'; + return product; +} + +QString Stringify::Str(const QString &key, Value value) +{ + QString result; + + if (Object *o = value.asObject()) { + FunctionObject *toJSON = o->__get__(ctx, ctx->engine->identifier(QStringLiteral("toJSON"))).asFunctionObject(); + if (toJSON) { + Value arg = Value::fromString(ctx, key); + value = toJSON->call(ctx, value, &arg, 1); + } + } + + if (replacerFunction) { + Object *holder = ctx->engine->newObject(); + Value holderValue = Value::fromObject(holder); + holder->__put__(ctx, QString(), value); + Value args[2]; + args[0] = Value::fromString(ctx, key); + args[1] = value; + value = replacerFunction->call(ctx, holderValue, args, 2); + } + + if (Object *o = value.asObject()) { + if (NumberObject *n = o->asNumberObject()) + value = n->value; + else if (StringObject *so = o->asStringObject()) + value = so->value; + else if (BooleanObject *b =o->asBooleanObject()) + value = b->value; + } + + if (value.isNull()) + return QStringLiteral("null"); + if (value.isBoolean()) + return value.booleanValue() ? QStringLiteral("true") : QStringLiteral("false"); + if (value.isString()) + return quote(value.stringValue()->toQString()); + + if (value.isNumber()) { + double d = value.toNumber(ctx); + return std::isfinite(d) ? value.toString(ctx)->toQString() : QStringLiteral("null"); + } + + if (Object *o = value.asObject()) { + if (!o->asFunctionObject()) { + if (o->asArrayObject()) + return JA(static_cast<ArrayObject *>(o)); + else + return JO(o); + } + } + + return QString(); +} + +QString Stringify::makeMember(const QString &key, Value v) +{ + QString strP = Str(key, v); + if (!strP.isEmpty()) { + QString member = quote(key) + ':'; + if (!gap.isEmpty()) + member += ' '; + member += strP; + return member; + } + return QString(); +} + +QString Stringify::JO(Object *o) +{ + if (stack.contains(o)) + ctx->throwTypeError(); + + QString result; + stack.push(o); + QString stepback = indent; + indent += gap; + + QStringList partial; + if (propertyList.isEmpty()) { + ObjectIterator it(ctx, o, ObjectIterator::EnumberableOnly); + + while (1) { + String *name; + uint index; + PropertyDescriptor *pd = it.next(&name, &index); + if (!pd) + break; + Value v = o->getValueChecked(ctx, pd); + QString key; + if (name) + key = name->toQString(); + else + key = QString::number(index); + QString member = makeMember(key, v); + if (!member.isEmpty()) + partial += member; + } + } else { + for (int i = 0; i < propertyList.size(); ++i) { + bool exists; + Value v = o->__get__(ctx, propertyList.at(i), &exists); + if (!exists) + continue; + QString member = makeMember(propertyList.at(i)->toQString(), v); + if (!member.isEmpty()) + partial += member; + } + } + + if (partial.isEmpty()) { + result = QStringLiteral("{}"); + } else if (gap.isEmpty()) { + result = "{" + partial.join(",") + "}"; + } else { + QString separator = ",\n" + indent; + result = "{\n" + indent + partial.join(separator) + "\n" + stepback + "}"; + } + + indent = stepback; + stack.pop(); + return result; +} + +QString Stringify::JA(ArrayObject *a) +{ + if (stack.contains(a)) + ctx->throwTypeError(); + + QString result; + stack.push(a); + QString stepback = indent; + indent += gap; + + QStringList partial; + uint len = a->array.length(); + for (uint i = 0; i < len; ++i) { + bool exists; + Value v = a->__get__(ctx, i, &exists); + if (!exists) { + partial += QStringLiteral("null"); + continue; + } + QString strP = Str(QString::number(i), v); + if (!strP.isEmpty()) + partial += strP; + else + partial += QStringLiteral("null"); + } + + if (partial.isEmpty()) { + result = QStringLiteral("[]"); + } else if (gap.isEmpty()) { + result = "[" + partial.join(",") + "]"; + } else { + QString separator = ",\n" + indent; + result = "[\n" + indent + partial.join(separator) + "\n" + stepback + "]"; + } + + indent = stepback; + stack.pop(); + return result; +} + + +JsonObject::JsonObject(ExecutionContext *context) + : Object() +{ + type = Type_JSONObject; + prototype = context->engine->objectPrototype; + + defineDefaultProperty(context, QStringLiteral("parse"), method_parse, 2); + defineDefaultProperty(context, QStringLiteral("stringify"), method_stringify, 3); +} + + +Value JsonObject::method_parse(ExecutionContext *ctx) +{ + QString jtext = ctx->argument(0).toString(ctx)->toQString(); + + DEBUG << "parsing source = " << jtext; + Parser parser(ctx, jtext.constData(), jtext.length()); + QJsonParseError error; + Value result = parser.parse(&error); + if (error.error != QJsonParseError::NoError) { + DEBUG << "parse error" << error.errorString(); + ctx->throwSyntaxError(0); + } + + return result; +} + +Value JsonObject::method_stringify(ExecutionContext *ctx) +{ + Stringify stringify(ctx); + + Object *o = ctx->argument(1).asObject(); + if (o) { + stringify.replacerFunction = o->asFunctionObject(); + if (o->isArrayObject()) { + for (uint i = 0; i < o->array.length(); ++i) { + Value v = o->__get__(ctx, i); + if (v.asNumberObject() || v.asStringObject() || v.isNumber()) + v = __qmljs_to_string(v, ctx); + if (v.isString()) { + String *s = v.stringValue(); + if (!stringify.propertyList.contains(s)) + stringify.propertyList.append(s); + } + } + } + } + + Value s = ctx->argument(2); + if (NumberObject *n = s.asNumberObject()) + s = n->value; + else if (StringObject *so = s.asStringObject()) + s = so->value; + + if (s.isNumber()) { + stringify.gap = QString(qMin(10, (int)s.toInteger(ctx)), ' '); + } else if (s.isString()) { + stringify.gap = s.stringValue()->toQString().left(10); + } + + + QString result = stringify.Str(QString(), ctx->argument(0)); + if (result.isEmpty()) + return Value::undefinedValue(); + return Value::fromString(ctx, result); +} + + + +} +} diff --git a/src/v4/qv4jsonobject.h b/src/v4/qv4jsonobject.h new file mode 100644 index 0000000000..7c1df7c40b --- /dev/null +++ b/src/v4/qv4jsonobject.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4JSONOBJECTS_H +#define QV4SJONOBJECTS_H + +#include <qv4object.h> + +namespace QQmlJS { +namespace VM { + +struct JsonObject : Object { + JsonObject(ExecutionContext *context); + + static Value method_parse(ExecutionContext *ctx); + static Value method_stringify(ExecutionContext *ctx); + +}; + +} +} + +#endif + diff --git a/src/v4/qv4managed.cpp b/src/v4/qv4managed.cpp new file mode 100644 index 0000000000..8156878508 --- /dev/null +++ b/src/v4/qv4managed.cpp @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4managed.h" +#include "qv4mm.h" +#include "qv4errorobject.h" + +using namespace QQmlJS::VM; + +Managed::~Managed() +{ + nextFree = 0; + inUse = 0; +} + +void *Managed::operator new(size_t size, MemoryManager *mm) +{ + assert(mm); + + return mm->allocManaged(size); +} + +void Managed::operator delete(void *ptr) +{ + if (!ptr) + return; + + Managed *m = static_cast<Managed *>(ptr); + m->~Managed(); +} + + +QString Managed::className() const +{ + const char *s = 0; + switch (Type(type)) { + case Type_Invalid: + case Type_String: + return QString(); + case Type_Object: + s = "Object"; + break; + case Type_ArrayObject: + s = "Array"; + break; + case Type_FunctionObject: + s = "Function"; + break; + case Type_BooleanObject: + s = "Boolean"; + break; + case Type_NumberObject: + s = "Number"; + break; + case Type_StringObject: + s = "String"; + break; + case Type_DateObject: + s = "Date"; + break; + case Type_RegExpObject: + s = "RegExp"; + break; + case Type_ErrorObject: + switch (static_cast<const ErrorObject *>(this)->errorType) { + case ErrorObject::Error: + s = "Error"; + break; + case ErrorObject::EvalError: + s = "EvalError"; + break; + case ErrorObject::RangeError: + s = "RangeError"; + break; + case ErrorObject::ReferenceError: + s = "ReferenceError"; + break; + case ErrorObject::SyntaxError: + s = "SyntaxError"; + break; + case ErrorObject::TypeError: + s = "TypeError"; + break; + case ErrorObject::URIError: + s = "URIError"; + break; + } + break; + case Type_ArgumentsObject: + s = "Arguments"; + break; + case Type_JSONObject: + s = "JSON"; + break; + case Type_MathObject: + s = "Math"; + break; + case Type_ForeachIteratorObject: + s = "__ForeachIterator"; + break; + } + return QString::fromLatin1(s); +} diff --git a/src/v4/qv4managed.h b/src/v4/qv4managed.h new file mode 100644 index 0000000000..dc4d1f2243 --- /dev/null +++ b/src/v4/qv4managed.h @@ -0,0 +1,173 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QMLJS_MANAGED_H +#define QMLJS_MANAGED_H + +#include <QtCore/QString> +#include <QtCore/QVector> +#include <QtCore/QDebug> +#include <wtf/Platform.h> +#include "qv4global.h" + +namespace QQmlJS { + +namespace VM { + +class MemoryManager; +struct String; +struct Object; +struct ObjectPrototype; +struct ExecutionContext; +struct ScriptFunction; + +struct BooleanObject; +struct NumberObject; +struct StringObject; +struct ArrayObject; +struct DateObject; +struct FunctionObject; +struct RegExpObject; +struct ErrorObject; +struct ArgumentsObject; +struct JSONObject; +struct ForeachIteratorObject; + +struct Q_V4_EXPORT Managed +{ +private: + void *operator new(size_t); + Managed(const Managed &other); + void operator = (const Managed &other); + +protected: + Managed() + : markBit(0) + , inUse(1) + , extensible(1) + , isNonStrictArgumentsObject(0) + , isBuiltinFunction(0) + , needsActivation(0) + , usesArgumentsObject(0) + , strictMode(0) + , type(Type_Invalid) + , unused(0) + , stringHash(0) + {} + virtual ~Managed(); + +public: + void *operator new(size_t size, MemoryManager *mm); + void operator delete(void *ptr); + + inline void mark() { + if (markBit) + return; + markBit = 1; + if (type != Type_String) + markObjects(); + } + + enum Type { + Type_Invalid, + Type_String, + Type_Object, + Type_ArrayObject, + Type_FunctionObject, + Type_BooleanObject, + Type_NumberObject, + Type_StringObject, + Type_DateObject, + Type_RegExpObject, + Type_ErrorObject, + Type_ArgumentsObject, + Type_JSONObject, + Type_MathObject, + Type_ForeachIteratorObject + }; + + String *asString() { return reinterpret_cast<String *>(this); } + Object *asObject() { return reinterpret_cast<Object *>(this); } + ArrayObject *asArrayObject() { return type == Type_ArrayObject ? reinterpret_cast<ArrayObject *>(this) : 0; } + FunctionObject *asFunctionObject() { return type == Type_FunctionObject ? reinterpret_cast<FunctionObject *>(this) : 0; } + BooleanObject *asBooleanObject() { return type == Type_BooleanObject ? reinterpret_cast<BooleanObject *>(this) : 0; } + NumberObject *asNumberObject() { return type == Type_NumberObject ? reinterpret_cast<NumberObject *>(this) : 0; } + StringObject *asStringObject() { return type == Type_StringObject ? reinterpret_cast<StringObject *>(this) : 0; } + DateObject *asDateObject() { return type == Type_DateObject ? reinterpret_cast<DateObject *>(this) : 0; } + RegExpObject *asRegExpObject() { return type == Type_RegExpObject ? reinterpret_cast<RegExpObject *>(this) : 0; } + ErrorObject *asErrorObject() { return type == Type_ErrorObject ? reinterpret_cast<ErrorObject *>(this) : 0; } + ArgumentsObject *asArgumentsObject() { return type == Type_ArgumentsObject ? reinterpret_cast<ArgumentsObject *>(this) : 0; } + JSONObject *asJSONObject() { return type == Type_JSONObject ? reinterpret_cast<JSONObject *>(this) : 0; } + ForeachIteratorObject *asForeachIteratorObject() { return type == Type_ForeachIteratorObject ? reinterpret_cast<ForeachIteratorObject *>(this) : 0; } + + bool isArrayObject() const { return type == Type_ArrayObject; } + bool isStringObject() const { return type == Type_StringObject; } + + QString className() const; + +protected: + virtual void markObjects() {} + + union { + Managed *nextFree; + struct { + quintptr markBit : 1; + quintptr inUse : 1; + quintptr extensible : 1; // used by Object + quintptr isNonStrictArgumentsObject : 1; + quintptr isBuiltinFunction : 1; // used by FunctionObject + quintptr needsActivation : 1; // used by FunctionObject + quintptr usesArgumentsObject : 1; // used by FunctionObject + quintptr strictMode : 1; // used by FunctionObject + quintptr type : 8; + quintptr unused : 16; + mutable quintptr stringHash : 32; + }; + }; + +private: + friend class MemoryManager; + friend struct ExecutionContext; +}; + +} +} + +#endif diff --git a/src/v4/qv4mathobject.cpp b/src/v4/qv4mathobject.cpp new file mode 100644 index 0000000000..e40faba861 --- /dev/null +++ b/src/v4/qv4mathobject.cpp @@ -0,0 +1,299 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4mathobject.h" + +#include <cmath> +#include <qmath.h> +#include <qnumeric.h> + +using namespace QQmlJS::VM; + +static const double qt_PI = 2.0 * ::asin(1.0); + +MathObject::MathObject(ExecutionContext *ctx) +{ + type = Type_MathObject; + + defineReadonlyProperty(ctx->engine, QStringLiteral("E"), Value::fromDouble(::exp(1.0))); + defineReadonlyProperty(ctx->engine, QStringLiteral("LN2"), Value::fromDouble(::log(2.0))); + defineReadonlyProperty(ctx->engine, QStringLiteral("LN10"), Value::fromDouble(::log(10.0))); + defineReadonlyProperty(ctx->engine, QStringLiteral("LOG2E"), Value::fromDouble(1.0/::log(2.0))); + defineReadonlyProperty(ctx->engine, QStringLiteral("LOG10E"), Value::fromDouble(1.0/::log(10.0))); + defineReadonlyProperty(ctx->engine, QStringLiteral("PI"), Value::fromDouble(qt_PI)); + defineReadonlyProperty(ctx->engine, QStringLiteral("SQRT1_2"), Value::fromDouble(::sqrt(0.5))); + defineReadonlyProperty(ctx->engine, QStringLiteral("SQRT2"), Value::fromDouble(::sqrt(2.0))); + + defineDefaultProperty(ctx, QStringLiteral("abs"), method_abs, 1); + defineDefaultProperty(ctx, QStringLiteral("acos"), method_acos, 1); + defineDefaultProperty(ctx, QStringLiteral("asin"), method_asin, 0); + defineDefaultProperty(ctx, QStringLiteral("atan"), method_atan, 1); + defineDefaultProperty(ctx, QStringLiteral("atan2"), method_atan2, 2); + defineDefaultProperty(ctx, QStringLiteral("ceil"), method_ceil, 1); + defineDefaultProperty(ctx, QStringLiteral("cos"), method_cos, 1); + defineDefaultProperty(ctx, QStringLiteral("exp"), method_exp, 1); + defineDefaultProperty(ctx, QStringLiteral("floor"), method_floor, 1); + defineDefaultProperty(ctx, QStringLiteral("log"), method_log, 1); + defineDefaultProperty(ctx, QStringLiteral("max"), method_max, 2); + defineDefaultProperty(ctx, QStringLiteral("min"), method_min, 2); + defineDefaultProperty(ctx, QStringLiteral("pow"), method_pow, 2); + defineDefaultProperty(ctx, QStringLiteral("random"), method_random, 0); + defineDefaultProperty(ctx, QStringLiteral("round"), method_round, 1); + defineDefaultProperty(ctx, QStringLiteral("sin"), method_sin, 1); + defineDefaultProperty(ctx, QStringLiteral("sqrt"), method_sqrt, 1); + defineDefaultProperty(ctx, QStringLiteral("tan"), method_tan, 1); +} + +/* copies the sign from y to x and returns the result */ +static double copySign(double x, double y) +{ + uchar *xch = (uchar *)&x; + uchar *ych = (uchar *)&y; + if (QSysInfo::ByteOrder == QSysInfo::BigEndian) + xch[0] = (xch[0] & 0x7f) | (ych[0] & 0x80); + else + xch[7] = (xch[7] & 0x7f) | (ych[7] & 0x80); + return x; +} + +Value MathObject::method_abs(ExecutionContext *ctx) +{ + double v = ctx->argument(0).toNumber(ctx); + if (v == 0) // 0 | -0 + return Value::fromDouble(0); + + return Value::fromDouble(v < 0 ? -v : v); +} + +Value MathObject::method_acos(ExecutionContext *ctx) +{ + double v = ctx->argument(0).toNumber(ctx); + if (v > 1) + return Value::fromDouble(qSNaN()); + + return Value::fromDouble(::acos(v)); +} + +Value MathObject::method_asin(ExecutionContext *ctx) +{ + double v = ctx->argument(0).toNumber(ctx); + if (v > 1) + return Value::fromDouble(qSNaN()); + else + return Value::fromDouble(::asin(v)); +} + +Value MathObject::method_atan(ExecutionContext *ctx) +{ + double v = ctx->argument(0).toNumber(ctx); + if (v == 0.0) + return Value::fromDouble(v); + else + return Value::fromDouble(::atan(v)); +} + +Value MathObject::method_atan2(ExecutionContext *ctx) +{ + double v1 = ctx->argument(0).toNumber(ctx); + double v2 = ctx->argument(1).toNumber(ctx); + if ((v1 < 0) && qIsFinite(v1) && qIsInf(v2) && (copySign(1.0, v2) == 1.0)) { + return Value::fromDouble(copySign(0, -1.0)); + } + if ((v1 == 0.0) && (v2 == 0.0)) { + if ((copySign(1.0, v1) == 1.0) && (copySign(1.0, v2) == -1.0)) { + return Value::fromDouble(qt_PI); + } else if ((copySign(1.0, v1) == -1.0) && (copySign(1.0, v2) == -1.0)) { + return Value::fromDouble(-qt_PI); + } + } + return Value::fromDouble(::atan2(v1, v2)); +} + +Value MathObject::method_ceil(ExecutionContext *ctx) +{ + double v = ctx->argument(0).toNumber(ctx); + if (v < 0.0 && v > -1.0) + return Value::fromDouble(copySign(0, -1.0)); + else + return Value::fromDouble(::ceil(v)); +} + +Value MathObject::method_cos(ExecutionContext *ctx) +{ + double v = ctx->argument(0).toNumber(ctx); + return Value::fromDouble(::cos(v)); +} + +Value MathObject::method_exp(ExecutionContext *ctx) +{ + double v = ctx->argument(0).toNumber(ctx); + if (qIsInf(v)) { + if (copySign(1.0, v) == -1.0) + return Value::fromDouble(0); + else + return Value::fromDouble(qInf()); + } else { + return Value::fromDouble(::exp(v)); + } +} + +Value MathObject::method_floor(ExecutionContext *ctx) +{ + double v = ctx->argument(0).toNumber(ctx); + return Value::fromDouble(::floor(v)); +} + +Value MathObject::method_log(ExecutionContext *ctx) +{ + double v = ctx->argument(0).toNumber(ctx); + if (v < 0) + return Value::fromDouble(qSNaN()); + else + return Value::fromDouble(::log(v)); +} + +Value MathObject::method_max(ExecutionContext *ctx) +{ + double mx = -qInf(); + for (unsigned i = 0; i < ctx->argumentCount; ++i) { + double x = ctx->argument(i).toNumber(ctx); + if (x > mx || std::isnan(x)) + mx = x; + } + return Value::fromDouble(mx); +} + +Value MathObject::method_min(ExecutionContext *ctx) +{ + double mx = qInf(); + for (unsigned i = 0; i < ctx->argumentCount; ++i) { + double x = ctx->argument(i).toNumber(ctx); + if ((x == 0 && mx == x && copySign(1.0, x) == -1.0) + || (x < mx) || std::isnan(x)) { + mx = x; + } + } + return Value::fromDouble(mx); +} + +Value MathObject::method_pow(ExecutionContext *parentCtx, Value, Value *argv, int argc) +{ + double x = argc > 0 ? argv[0].toNumber(parentCtx) : qSNaN(); + double y = argc > 1 ? argv[1].toNumber(parentCtx) : qSNaN(); + + if (std::isnan(y)) + return Value::fromDouble(qSNaN()); + + if (y == 0) { + return Value::fromDouble(1); + } else if (((x == 1) || (x == -1)) && std::isinf(y)) { + return Value::fromDouble(qSNaN()); + } else if (((x == 0) && copySign(1.0, x) == 1.0) && (y < 0)) { + return Value::fromDouble(qInf()); + } else if ((x == 0) && copySign(1.0, x) == -1.0) { + if (y < 0) { + if (::fmod(-y, 2.0) == 1.0) + return Value::fromDouble(-qInf()); + else + return Value::fromDouble(qInf()); + } else if (y > 0) { + if (::fmod(y, 2.0) == 1.0) + return Value::fromDouble(copySign(0, -1.0)); + else + return Value::fromDouble(0); + } + } + +#ifdef Q_OS_AIX + else if (qIsInf(x) && copySign(1.0, x) == -1.0) { + if (y > 0) { + if (::fmod(y, 2.0) == 1.0) + return Value::fromDouble(-qInf()); + else + return Value::fromDouble(qInf()); + } else if (y < 0) { + if (::fmod(-y, 2.0) == 1.0) + return Value::fromDouble(copySign(0, -1.0)); + else + return Value::fromDouble(0); + } + } +#endif + else { + return Value::fromDouble(::pow(x, y)); + } + // ### + return Value::fromDouble(qSNaN()); +} + +Value MathObject::method_random(ExecutionContext */*ctx*/) +{ + return Value::fromDouble(qrand() / (double) RAND_MAX); +} + +Value MathObject::method_round(ExecutionContext *ctx) +{ + double v = ctx->argument(0).toNumber(ctx); + v = copySign(::floor(v + 0.5), v); + return Value::fromDouble(v); +} + +Value MathObject::method_sin(ExecutionContext *ctx) +{ + double v = ctx->argument(0).toNumber(ctx); + return Value::fromDouble(::sin(v)); +} + +Value MathObject::method_sqrt(ExecutionContext *ctx) +{ + double v = ctx->argument(0).toNumber(ctx); + return Value::fromDouble(::sqrt(v)); +} + +Value MathObject::method_tan(ExecutionContext *ctx) +{ + double v = ctx->argument(0).toNumber(ctx); + if (v == 0.0) + return Value::fromDouble(v); + else + return Value::fromDouble(::tan(v)); +} + diff --git a/src/v4/qv4mathobject.h b/src/v4/qv4mathobject.h new file mode 100644 index 0000000000..c8428d2942 --- /dev/null +++ b/src/v4/qv4mathobject.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4MATHOBJECT_H +#define QV$MATHOBJECT_H + +#include "qv4object.h" + +namespace QQmlJS { + +namespace VM { + +struct MathObject: Object +{ + MathObject(ExecutionContext *ctx); + virtual QString className() { return QStringLiteral("Math"); } + + static Value method_abs(ExecutionContext *ctx); + static Value method_acos(ExecutionContext *ctx); + static Value method_asin(ExecutionContext *ctx); + static Value method_atan(ExecutionContext *ctx); + static Value method_atan2(ExecutionContext *ctx); + static Value method_ceil(ExecutionContext *ctx); + static Value method_cos(ExecutionContext *ctx); + static Value method_exp(ExecutionContext *ctx); + static Value method_floor(ExecutionContext *ctx); + static Value method_log(ExecutionContext *ctx); + static Value method_max(ExecutionContext *ctx); + static Value method_min(ExecutionContext *ctx); + static Value method_pow(ExecutionContext *parentCtx, Value thisObject, Value *argv, int argc); + static Value method_random(ExecutionContext *ctx); + static Value method_round(ExecutionContext *ctx); + static Value method_sin(ExecutionContext *ctx); + static Value method_sqrt(ExecutionContext *ctx); + static Value method_tan(ExecutionContext *ctx); +}; + +} // namespace VM +} // namespace QQmlJS + +#endif // QMLJS_OBJECTS_H diff --git a/src/v4/qv4mm.cpp b/src/v4/qv4mm.cpp new file mode 100644 index 0000000000..c77ec1eaba --- /dev/null +++ b/src/v4/qv4mm.cpp @@ -0,0 +1,396 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "qmljs_engine.h" +#include "qv4object.h" +#include "qv4objectproto.h" +#include "qv4mm.h" +#include "PageAllocation.h" +#include "StdLibExtras.h" + +#include <QTime> +#include <QVector> +#include <QVector> +#include <QMap> + +#include <iostream> +#include <cstdlib> +#include <alloca.h> + +using namespace QQmlJS::VM; +using namespace WTF; + +static const std::size_t CHUNK_SIZE = 65536; + +struct MemoryManager::Data +{ + bool enableGC; + bool gcBlocked; + bool scribble; + bool aggressiveGC; + ExecutionEngine *engine; + + enum { MaxItemSize = 256 }; + Managed *smallItems[MaxItemSize/16]; + uint nChunks[MaxItemSize/16]; + struct Chunk { + PageAllocation memory; + int chunkSize; + }; + + QVector<Chunk> heapChunks; + QHash<Managed *, uint> protectedObject; + + // statistics: +#ifdef DETAILED_MM_STATS + QVector<unsigned> allocSizeCounters; +#endif // DETAILED_MM_STATS + + Data(bool enableGC) + : enableGC(enableGC) + , gcBlocked(false) + , engine(0) + { + memset(smallItems, 0, sizeof(smallItems)); + memset(nChunks, 0, sizeof(nChunks)); + scribble = qgetenv("MM_NO_SCRIBBLE").isEmpty(); + aggressiveGC = !qgetenv("MM_AGGRESSIVE_GC").isEmpty(); + } + + ~Data() + { + for (QVector<Chunk>::iterator i = heapChunks.begin(), ei = heapChunks.end(); i != ei; ++i) + i->memory.deallocate(); + } +}; + +namespace QQmlJS { namespace VM { + +bool operator<(const MemoryManager::Data::Chunk &a, const MemoryManager::Data::Chunk &b) +{ + return a.memory.base() < b.memory.base(); +} + +} } // namespace QQmlJS::VM + +MemoryManager::MemoryManager() + : m_d(new Data(true)) +{ + setEnableGC(true); +} + +Managed *MemoryManager::alloc(std::size_t size) +{ + if (m_d->aggressiveGC) + runGC(); +#ifdef DETAILED_MM_STATS + willAllocate(size); +#endif // DETAILED_MM_STATS + + assert(size >= 16); + assert(size % 16 == 0); + + size_t pos = size >> 4; + + // fits into a small bucket + assert(size < MemoryManager::Data::MaxItemSize); + + Managed *m = m_d->smallItems[pos]; + if (m) + goto found; + + // try to free up space, otherwise allocate + if (!m_d->aggressiveGC) { + runGC(); + m = m_d->smallItems[pos]; + if (m) + goto found; + } + + // no free item available, allocate a new chunk + { + // allocate larger chunks at a time to avoid excessive GC, but cap at 64M chunks + uint shift = ++m_d->nChunks[pos]; + if (shift > 10) + shift = 10; + std::size_t allocSize = std::max(size, CHUNK_SIZE*(1 << shift)); + allocSize = roundUpToMultipleOf(WTF::pageSize(), allocSize); + Data::Chunk allocation; + allocation.memory = PageAllocation::allocate(allocSize, OSAllocator::JSGCHeapPages); + allocation.chunkSize = size; + m_d->heapChunks.append(allocation); + qSort(m_d->heapChunks); + char *chunk = (char *)allocation.memory.base(); + char *end = chunk + allocation.memory.size() - size; + memset(chunk, 0, allocation.memory.size()); + Managed **last = &m_d->smallItems[pos]; + while (chunk <= end) { + Managed *o = reinterpret_cast<Managed *>(chunk); + o->inUse = 0; + o->markBit = 0; + *last = o; + last = &o->nextFree; + chunk += size; + } + *last = 0; + m = m_d->smallItems[pos]; + } + + found: + m_d->smallItems[pos] = m->nextFree; + return m; +} + +void MemoryManager::scribble(Managed *obj, int c, int size) const +{ + if (m_d->scribble) + ::memset((void *)(obj + 1), c, size - sizeof(Managed)); +} + +void MemoryManager::mark() +{ + m_d->engine->markObjects(); + + for (ExecutionContext *ctxt = engine()->current; ctxt; ctxt = ctxt->parent) + ctxt->mark(); + + for (QHash<Managed *, uint>::const_iterator it = m_d->protectedObject.begin(); it != m_d->protectedObject.constEnd(); ++it) + it.key()->mark(); + + collectFromStack(); + + return; +} + +std::size_t MemoryManager::sweep() +{ + std::size_t freedCount = 0; + + for (QVector<Data::Chunk>::iterator i = m_d->heapChunks.begin(), ei = m_d->heapChunks.end(); i != ei; ++i) + freedCount += sweep(reinterpret_cast<char*>(i->memory.base()), i->memory.size(), i->chunkSize); + + return freedCount; +} + +std::size_t MemoryManager::sweep(char *chunkStart, std::size_t chunkSize, size_t size) +{ +// qDebug("chunkStart @ %p, size=%x, pos=%x (%x)", chunkStart, size, size>>4, m_d->smallItems[size >> 4]); + std::size_t freedCount = 0; + + Managed **f = &m_d->smallItems[size >> 4]; + + for (char *chunk = chunkStart, *chunkEnd = chunk + chunkSize - size; chunk <= chunkEnd; ) { + Managed *m = reinterpret_cast<Managed *>(chunk); +// qDebug("chunk @ %p, size = %lu, in use: %s, mark bit: %s", +// chunk, m->size, (m->inUse ? "yes" : "no"), (m->markBit ? "true" : "false")); + + assert((intptr_t) chunk % 16 == 0); + + chunk = chunk + size; + if (m->inUse) { + if (m->markBit) { + m->markBit = 0; + } else { +// qDebug() << "-- collecting it." << m << *f << &m->nextFree; + m->~Managed(); + + m->nextFree = *f; + f = &m->nextFree; + //scribble(m, 0x99, size); + ++freedCount; + } + } + } + + return freedCount; +} + +bool MemoryManager::isGCBlocked() const +{ + return m_d->gcBlocked; +} + +void MemoryManager::setGCBlocked(bool blockGC) +{ + m_d->gcBlocked = blockGC; +} + +void MemoryManager::runGC() +{ + if (!m_d->enableGC || m_d->gcBlocked) { +// qDebug() << "Not running GC."; + return; + } + +// QTime t; t.start(); + +// qDebug() << ">>>>>>>>runGC"; + + mark(); +// std::cerr << "GC: marked " << marks +// << " objects in " << t.elapsed() +// << "ms" << std::endl; + +// t.restart(); + /*std::size_t freedCount =*/ sweep(); +// std::cerr << "GC: sweep freed " << freedCount +// << " objects in " << t.elapsed() +// << "ms" << std::endl; +} + +void MemoryManager::setEnableGC(bool enableGC) +{ + m_d->enableGC = enableGC; +} + +MemoryManager::~MemoryManager() +{ + sweep(); +} + +void MemoryManager::protect(Managed *m) +{ + ++m_d->protectedObject[m]; +} + +void MemoryManager::unprotect(Managed *m) +{ + if (!--m_d->protectedObject[m]) + m_d->protectedObject.remove(m); +} + +static inline void add(QVector<Managed *> &values, const Value &v) +{ + if (Object *o = v.asObject()) + values.append(o); +} + +void MemoryManager::setExecutionEngine(ExecutionEngine *engine) +{ + m_d->engine = engine; +} + +void MemoryManager::dumpStats() const +{ +#ifdef DETAILED_MM_STATS + std::cerr << "=================" << std::endl; + std::cerr << "Allocation stats:" << std::endl; + std::cerr << "Requests for each chunk size:" << std::endl; + for (int i = 0; i < m_d->allocSizeCounters.size(); ++i) { + if (unsigned count = m_d->allocSizeCounters[i]) { + std::cerr << "\t" << (i << 4) << " bytes chunks: " << count << std::endl; + } + } +#endif // DETAILED_MM_STATS +} + +ExecutionEngine *MemoryManager::engine() const +{ + return m_d->engine; +} + +#ifdef DETAILED_MM_STATS +void MemoryManager::willAllocate(std::size_t size) +{ + unsigned alignedSize = (size + 15) >> 4; + QVector<unsigned> &counters = m_d->allocSizeCounters; + if ((unsigned) counters.size() < alignedSize + 1) + counters.resize(alignedSize + 1); + counters[alignedSize]++; +} + +#endif // DETAILED_MM_STATS + +void MemoryManager::collectFromStack() const +{ + if (!m_d->heapChunks.count()) + return; + + quintptr valueOnStack = 0; + +#if USE(PTHREADS) +#if OS(DARWIN) + void* stackTop = 0; + stackTop = pthread_get_stackaddr_np(pthread_self()); + quintptr *top = static_cast<quintptr *>(stackTop); +#else + void* stackBottom = 0; + pthread_attr_t attr; + pthread_getattr_np(pthread_self(), &attr); + size_t stackSize = 0; + pthread_attr_getstack(&attr, &stackBottom, &stackSize); + pthread_attr_destroy(&attr); + + quintptr *top = static_cast<quintptr *>(stackBottom) + stackSize/sizeof(quintptr); +#endif +#endif +// qDebug() << "stack:" << hex << stackTop << stackSize << (stackTop + stackSize); + + quintptr *current = (&valueOnStack) + 1; +// qDebug() << "collectFromStack" << top << current << &valueOnStack; + + char** heapChunkBoundaries = (char**)alloca(m_d->heapChunks.count() * 2 * sizeof(char*)); + char** heapChunkBoundariesEnd = heapChunkBoundaries + 2 * m_d->heapChunks.count(); + int i = 0; + for (QVector<Data::Chunk>::Iterator it = m_d->heapChunks.begin(), end = + m_d->heapChunks.end(); it != end; ++it) { + heapChunkBoundaries[i++] = reinterpret_cast<char*>(it->memory.base()); + heapChunkBoundaries[i++] = reinterpret_cast<char*>(it->memory.base()) + it->memory.size(); + } + + for (; current < top; ++current) { + char* genericPtr = +#if CPU(X86_64) + reinterpret_cast<char *>((*current) & ~(quint64(Value::Type_Mask) << Value::Tag_Shift)); +#else + reinterpret_cast<char *>(*current); +#endif + + if (genericPtr < *heapChunkBoundaries || genericPtr >= *(heapChunkBoundariesEnd - 1)) + continue; + int index = qLowerBound(heapChunkBoundaries, heapChunkBoundariesEnd, genericPtr) - heapChunkBoundaries; + // An odd index means the pointer is _before_ the end of a heap chunk and therefore valid. + if (index & 1) { + int size = m_d->heapChunks.at(index >> 1).chunkSize; + Managed *m = reinterpret_cast<Managed *>(genericPtr); +// qDebug() << " inside" << size << m; + + if (((quintptr)m - (quintptr)heapChunkBoundaries[index-1]) % size) + // wrongly aligned value, skip it + continue; + + if (!m->inUse) + // Skip pointers to already freed objects, they are bogus as well + continue; + + m->mark(); +// qDebug() << " marking"; + } + } +} diff --git a/src/v4/qv4mm.h b/src/v4/qv4mm.h new file mode 100644 index 0000000000..6f747f53d2 --- /dev/null +++ b/src/v4/qv4mm.h @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QV4GC_H +#define QV4GC_H + +#include "qv4global.h" +#include "qv4object.h" + +#include <QScopedPointer> + +#define DETAILED_MM_STATS + +namespace QQmlJS { +namespace VM { + +struct Managed; + +class Q_V4_EXPORT MemoryManager +{ + MemoryManager(const MemoryManager &); + MemoryManager &operator=(const MemoryManager&); + +public: + struct Data; + + class GCBlocker + { + public: + GCBlocker(MemoryManager *mm) + : mm(mm) + , wasBlocked(mm->isGCBlocked()) + { + mm->setGCBlocked(true); + } + + ~GCBlocker() + { + mm->setGCBlocked(wasBlocked); + } + + private: + MemoryManager *mm; + bool wasBlocked; + }; + +public: + MemoryManager(); + ~MemoryManager(); + + void protect(Managed *m); + void unprotect(Managed *m); + + // TODO: this is only for 64bit (and x86 with SSE/AVX), so exend it for other architectures to be slightly more efficient (meaning, align on 8-byte boundaries). + // Note: all occurances of "16" in alloc/dealloc are also due to the alignment. + static inline std::size_t align(std::size_t size) + { return (size + 15) & ~0xf; } + + inline Managed *allocManaged(std::size_t size) + { + size = align(size); + Managed *o = alloc(size); + return o; + } + + bool isGCBlocked() const; + void setGCBlocked(bool blockGC); + void runGC(); + + void setEnableGC(bool enableGC); + void setExecutionEngine(ExecutionEngine *engine); + + void dumpStats() const; + +protected: + /// expects size to be aligned + // TODO: try to inline + Managed *alloc(std::size_t size); + + void scribble(Managed *obj, int c, int size) const; + + ExecutionEngine *engine() const; + +#ifdef DETAILED_MM_STATS + void willAllocate(std::size_t size); +#endif // DETAILED_MM_STATS + +private: + void collectFromStack() const; + void mark(); + std::size_t sweep(); + std::size_t sweep(char *chunkStart, std::size_t chunkSize, size_t size); + +protected: + QScopedPointer<Data> m_d; +}; + + +} // namespace VM +} // namespace QQmlJS + +#endif // QV4GC_H diff --git a/src/v4/qv4numberobject.cpp b/src/v4/qv4numberobject.cpp new file mode 100644 index 0000000000..08711a8187 --- /dev/null +++ b/src/v4/qv4numberobject.cpp @@ -0,0 +1,233 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4numberobject.h" +#include <QtCore/qnumeric.h> +#include <QtCore/qmath.h> +#include <QtCore/QDebug> +#include <cassert> + + +using namespace QQmlJS::VM; + + +NumberCtor::NumberCtor(ExecutionContext *scope) + : FunctionObject(scope) +{ +} + +Value NumberCtor::construct(ExecutionContext *ctx) +{ + double d = ctx->argumentCount ? ctx->argument(0).toNumber(ctx) : 0; + return Value::fromObject(ctx->engine->newNumberObject(Value::fromDouble(d))); +} + +Value NumberCtor::call(ExecutionContext *ctx) +{ + double d = ctx->argumentCount ? ctx->argument(0).toNumber(ctx) : 0; + return Value::fromDouble(d); +} + +void NumberPrototype::init(ExecutionContext *ctx, const Value &ctor) +{ + ctor.objectValue()->defineReadonlyProperty(ctx->engine->id_prototype, Value::fromObject(this)); + ctor.objectValue()->defineReadonlyProperty(ctx->engine->id_length, Value::fromInt32(1)); + + ctor.objectValue()->defineReadonlyProperty(ctx->engine, QStringLiteral("NaN"), Value::fromDouble(qSNaN())); + ctor.objectValue()->defineReadonlyProperty(ctx->engine, QStringLiteral("NEGATIVE_INFINITY"), Value::fromDouble(-qInf())); + ctor.objectValue()->defineReadonlyProperty(ctx->engine, QStringLiteral("POSITIVE_INFINITY"), Value::fromDouble(qInf())); + ctor.objectValue()->defineReadonlyProperty(ctx->engine, QStringLiteral("MAX_VALUE"), Value::fromDouble(1.7976931348623158e+308)); + +#ifdef __INTEL_COMPILER +# pragma warning( push ) +# pragma warning(disable: 239) +#endif + ctor.objectValue()->defineReadonlyProperty(ctx->engine, QStringLiteral("MIN_VALUE"), Value::fromDouble(5e-324)); +#ifdef __INTEL_COMPILER +# pragma warning( pop ) +#endif + + defineDefaultProperty(ctx, QStringLiteral("constructor"), ctor); + defineDefaultProperty(ctx, QStringLiteral("toString"), method_toString); + defineDefaultProperty(ctx, QStringLiteral("toLocaleString"), method_toLocaleString); + defineDefaultProperty(ctx, QStringLiteral("valueOf"), method_valueOf); + defineDefaultProperty(ctx, QStringLiteral("toFixed"), method_toFixed, 1); + defineDefaultProperty(ctx, QStringLiteral("toExponential"), method_toExponential); + defineDefaultProperty(ctx, QStringLiteral("toPrecision"), method_toPrecision); +} + +Value NumberPrototype::method_toString(ExecutionContext *ctx) +{ + double num; + if (ctx->thisObject.isNumber()) { + num = ctx->thisObject.asDouble(); + } else { + NumberObject *thisObject = ctx->thisObject.asNumberObject(); + if (!thisObject) + ctx->throwTypeError(); + num = thisObject->value.asDouble(); + } + + Value arg = ctx->argument(0); + if (!arg.isUndefined()) { + int radix = arg.toInt32(ctx); + if (radix < 2 || radix > 36) { + ctx->throwError(QString::fromLatin1("Number.prototype.toString: %0 is not a valid radix") + .arg(radix)); + return Value::undefinedValue(); + } + + if (std::isnan(num)) { + return Value::fromString(ctx, QStringLiteral("NaN")); + } else if (qIsInf(num)) { + return Value::fromString(ctx, QLatin1String(num < 0 ? "-Infinity" : "Infinity")); + } + + if (radix != 10) { + QString str; + bool negative = false; + if (num < 0) { + negative = true; + num = -num; + } + double frac = num - ::floor(num); + num = Value::toInteger(num); + do { + char c = (char)::fmod(num, radix); + c = (c < 10) ? (c + '0') : (c - 10 + 'a'); + str.prepend(QLatin1Char(c)); + num = ::floor(num / radix); + } while (num != 0); + if (frac != 0) { + str.append(QLatin1Char('.')); + do { + frac = frac * radix; + char c = (char)::floor(frac); + c = (c < 10) ? (c + '0') : (c - 10 + 'a'); + str.append(QLatin1Char(c)); + frac = frac - ::floor(frac); + } while (frac != 0); + } + if (negative) + str.prepend(QLatin1Char('-')); + return Value::fromString(ctx, str); + } + } + + String *str = Value::fromDouble(num).toString(ctx); + return Value::fromString(str); +} + +Value NumberPrototype::method_toLocaleString(ExecutionContext *ctx) +{ + NumberObject *thisObject = ctx->thisObject.asNumberObject(); + if (!thisObject) + ctx->throwTypeError(); + + String *str = thisObject->value.toString(ctx); + return Value::fromString(str); +} + +Value NumberPrototype::method_valueOf(ExecutionContext *ctx) +{ + NumberObject *thisObject = ctx->thisObject.asNumberObject(); + if (!thisObject) + ctx->throwTypeError(); + + return thisObject->value; +} + +Value NumberPrototype::method_toFixed(ExecutionContext *ctx) +{ + NumberObject *thisObject = ctx->thisObject.asNumberObject(); + if (!thisObject) + ctx->throwTypeError(); + + double fdigits = 0; + + if (ctx->argumentCount > 0) + fdigits = ctx->argument(0).toInteger(ctx); + + if (std::isnan(fdigits)) + fdigits = 0; + + if (fdigits < 0 || fdigits > 20) + ctx->throwRangeError(ctx->thisObject); + + double v = thisObject->value.asDouble(); + QString str; + if (std::isnan(v)) + str = QString::fromLatin1("NaN"); + else if (qIsInf(v)) + str = QString::fromLatin1(v < 0 ? "-Infinity" : "Infinity"); + else + str = QString::number(v, 'f', int (fdigits)); + return Value::fromString(ctx, str); +} + +Value NumberPrototype::method_toExponential(ExecutionContext *ctx) +{ + NumberObject *thisObject = ctx->thisObject.asNumberObject(); + if (!thisObject) + ctx->throwTypeError(); + + double fdigits = 0; + + if (ctx->argumentCount > 0) + fdigits = ctx->argument(0).toInteger(ctx); + + QString z = QString::number(thisObject->value.asDouble(), 'e', int (fdigits)); + return Value::fromString(ctx, z); +} + +Value NumberPrototype::method_toPrecision(ExecutionContext *ctx) +{ + NumberObject *thisObject = ctx->thisObject.asNumberObject(); + if (!thisObject) + ctx->throwTypeError(); + + double fdigits = 0; + + if (ctx->argumentCount > 0) + fdigits = ctx->argument(0).toInteger(ctx); + + return Value::fromString(ctx, QString::number(thisObject->value.asDouble(), 'g', int (fdigits))); +} diff --git a/src/v4/qv4numberobject.h b/src/v4/qv4numberobject.h new file mode 100644 index 0000000000..85f9e1ff2b --- /dev/null +++ b/src/v4/qv4numberobject.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4NUMBEROBJECT_H +#define QV4NUMBEROBJECT_H + +#include "qv4object.h" +#include "qv4functionobject.h" +#include <QtCore/qnumeric.h> + +namespace QQmlJS { +namespace VM { + +struct NumberCtor: FunctionObject +{ + NumberCtor(ExecutionContext *scope); + + virtual Value construct(ExecutionContext *ctx); + virtual Value call(ExecutionContext *ctx); +}; + +struct NumberPrototype: NumberObject +{ + NumberPrototype(): NumberObject(Value::fromDouble(0)) {} + void init(ExecutionContext *ctx, const Value &ctor); + + static Value method_toString(ExecutionContext *ctx); + static Value method_toLocaleString(ExecutionContext *ctx); + static Value method_valueOf(ExecutionContext *ctx); + static Value method_toFixed(ExecutionContext *ctx); + static Value method_toExponential(ExecutionContext *ctx); + static Value method_toPrecision(ExecutionContext *ctx); +}; + + +} // end of namespace VM +} // end of namespace QQmlJS + +#endif // QV4ECMAOBJECTS_P_H diff --git a/src/v4/qv4object.cpp b/src/v4/qv4object.cpp new file mode 100644 index 0000000000..dadf8479c2 --- /dev/null +++ b/src/v4/qv4object.cpp @@ -0,0 +1,674 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4object.h" +#include "qv4ir_p.h" +#include "qv4isel_p.h" +#include "qv4objectproto.h" +#include "qv4stringobject.h" +#include "qv4argumentsobject.h" +#include "qv4mm.h" + +#include <private/qqmljsengine_p.h> +#include <private/qqmljslexer_p.h> +#include <private/qqmljsparser_p.h> +#include <private/qqmljsast_p.h> +#include <qv4ir_p.h> +#include <qv4codegen_p.h> +#include "private/qlocale_tools_p.h" + +#include <QtCore/qmath.h> +#include <QtCore/QDebug> +#include <cassert> +#include <typeinfo> +#include <iostream> +#include <alloca.h> + +using namespace QQmlJS::VM; + + +// +// Object +// +Object::~Object() +{ +} + +void Object::__put__(ExecutionContext *ctx, const QString &name, const Value &value) +{ + __put__(ctx, ctx->engine->identifier(name), value); +} + +Value Object::getValue(ExecutionContext *ctx, const PropertyDescriptor *p) const +{ + if (p->isData()) + return p->value; + if (!p->get) + return Value::undefinedValue(); + + return p->get->call(ctx, Value::fromObject(const_cast<Object *>(this)), 0, 0); +} + +Value Object::getValueChecked(ExecutionContext *ctx, const PropertyDescriptor *p) const +{ + if (!p || p->type == PropertyDescriptor::Generic) + return Value::undefinedValue(); + return getValue(ctx, p); +} + +Value Object::getValueChecked(ExecutionContext *ctx, const PropertyDescriptor *p, bool *exists) const +{ + *exists = p && p->type != PropertyDescriptor::Generic; + if (!*exists) + return Value::undefinedValue(); + return getValue(ctx, p); +} + +void Object::inplaceBinOp(Value rhs, String *name, BinOp op, ExecutionContext *ctx) +{ + bool hasProperty = false; + Value v = __get__(ctx, name, &hasProperty); + Value result = op(v, rhs, ctx); + __put__(ctx, name, result); +} + +void Object::inplaceBinOp(Value rhs, Value index, BinOp op, ExecutionContext *ctx) +{ + uint idx = index.asArrayIndex(); + if (idx < UINT_MAX) { + bool hasProperty = false; + Value v = __get__(ctx, idx, &hasProperty); + v = op(v, rhs, ctx); + __put__(ctx, idx, v); + return; + } + String *name = index.toString(ctx); + assert(name); + inplaceBinOp(rhs, name, op, ctx); +} + +void Object::defineDefaultProperty(String *name, Value value) +{ + if (!members) + members.reset(new PropertyTable()); + PropertyDescriptor *pd = members->insert(name); + pd->type = PropertyDescriptor::Data; + pd->writable = PropertyDescriptor::Enabled; + pd->enumberable = PropertyDescriptor::Disabled; + pd->configurable = PropertyDescriptor::Enabled; + pd->value = value; +} + +void Object::defineDefaultProperty(ExecutionContext *context, const QString &name, Value value) +{ + defineDefaultProperty(context->engine->identifier(name), value); +} + +void Object::defineDefaultProperty(ExecutionContext *context, const QString &name, Value (*code)(ExecutionContext *), int argumentCount) +{ + Q_UNUSED(argumentCount); + String *s = context->engine->identifier(name); + FunctionObject* function = context->engine->newBuiltinFunction(context, s, code); + function->defineReadonlyProperty(context->engine->id_length, Value::fromInt32(argumentCount)); + defineDefaultProperty(s, Value::fromObject(function)); +} + +void Object::defineDefaultProperty(ExecutionContext *context, const QString &name, Value (*code)(ExecutionContext *, Value, Value *, int), int argumentCount) +{ + Q_UNUSED(argumentCount); + String *s = context->engine->identifier(name); + FunctionObject* function = context->engine->newBuiltinFunction(context, s, code); + function->defineReadonlyProperty(context->engine->id_length, Value::fromInt32(argumentCount)); + defineDefaultProperty(s, Value::fromObject(function)); +} + +void Object::defineReadonlyProperty(ExecutionEngine *engine, const QString &name, Value value) +{ + defineReadonlyProperty(engine->identifier(name), value); +} + +void Object::defineReadonlyProperty(String *name, Value value) +{ + if (!members) + members.reset(new PropertyTable()); + PropertyDescriptor *pd = members->insert(name); + pd->type = PropertyDescriptor::Data; + pd->writable = PropertyDescriptor::Disabled; + pd->enumberable = PropertyDescriptor::Disabled; + pd->configurable = PropertyDescriptor::Disabled; + pd->value = value; +} + +void Object::markObjects() +{ + if (prototype) + prototype->mark(); + + if (members) { + for (PropertyTable::iterator it = members->begin(), eit = members->end(); it < eit; ++it) { + if (!(*it)) + continue; + (*it)->name->mark(); + PropertyDescriptor &pd = (*it)->descriptor; + if (pd.isData()) { + if (Managed *m = pd.value.asManaged()) + m->mark(); + } else if (pd.isAccessor()) { + if (pd.get) + pd.get->mark(); + if (pd.set) + pd.set->mark(); + } + } + } + array.markObjects(); +} + +// Section 8.12.1 +PropertyDescriptor *Object::__getOwnProperty__(ExecutionContext *ctx, String *name) +{ + uint idx = name->asArrayIndex(); + if (idx != String::InvalidArrayIndex) + return __getOwnProperty__(ctx, idx); + + if (members) + return members->find(name); + return 0; +} + +PropertyDescriptor *Object::__getOwnProperty__(ExecutionContext *ctx, uint index) +{ + PropertyDescriptor *p = array.at(index); + if(p && p->type != PropertyDescriptor::Generic) + return p; + if (isStringObject()) + return static_cast<StringObject *>(this)->getIndex(ctx, index); + + return 0; +} + +// Section 8.12.2 +PropertyDescriptor *Object::__getPropertyDescriptor__(ExecutionContext *ctx, String *name) +{ + uint idx = name->asArrayIndex(); + if (idx != String::InvalidArrayIndex) + return __getPropertyDescriptor__(ctx, idx); + + + Object *o = this; + while (o) { + if (o->members) { + if (PropertyDescriptor *p = o->members->find(name)) + return p; + } + o = o->prototype; + } + return 0; +} + +PropertyDescriptor *Object::__getPropertyDescriptor__(ExecutionContext *ctx, uint index) +{ + Object *o = this; + while (o) { + PropertyDescriptor *p = o->array.at(index); + if(p && p->type != PropertyDescriptor::Generic) + return p; + if (o->isStringObject()) { + p = static_cast<StringObject *>(o)->getIndex(ctx, index); + if (p) + return p; + } + o = o->prototype; + } + return 0; +} + +// Section 8.12.3 +Value Object::__get__(ExecutionContext *ctx, String *name, bool *hasProperty) +{ + uint idx = name->asArrayIndex(); + if (idx != String::InvalidArrayIndex) + return __get__(ctx, idx, hasProperty); + + if (name->isEqualTo(ctx->engine->id___proto__)) { + if (hasProperty) + *hasProperty = true; + return Value::fromObject(prototype); + } + + if (PropertyDescriptor *p = __getPropertyDescriptor__(ctx, name)) { + if (hasProperty) + *hasProperty = true; + return getValue(ctx, p); + } + + if (hasProperty) + *hasProperty = false; + return Value::undefinedValue(); +} + +Value Object::__get__(ExecutionContext *ctx, uint index, bool *hasProperty) +{ + const PropertyDescriptor *p = __getPropertyDescriptor__(ctx, index); + if (p && p->type != PropertyDescriptor::Generic) { + if (hasProperty) + *hasProperty = true; + return getValue(ctx, p); + } + + if (hasProperty) + *hasProperty = false; + return Value::undefinedValue(); +} + + +// Section 8.12.5 +void Object::__put__(ExecutionContext *ctx, String *name, Value value) +{ + uint idx = name->asArrayIndex(); + if (idx != String::InvalidArrayIndex) + return __put__(ctx, idx, value); + + PropertyDescriptor *pd = __getOwnProperty__(ctx, name); + // clause 1 + if (pd) { + if (pd->isAccessor()) { + if (pd->set) + goto cont; + goto reject; + } else if (!pd->isWritable()) + goto reject; + else if (isArrayObject() && name->isEqualTo(ctx->engine->id_length)) { + bool ok; + uint l = value.asArrayLength(ctx, &ok); + if (!ok) + ctx->throwRangeError(value); + ok = array.setLength(l); + if (!ok) + goto reject; + } else { + pd->value = value; + } + return; + } else if (!prototype) { + if (!extensible) + goto reject; + } else { + if (PropertyDescriptor *p = prototype->__getPropertyDescriptor__(ctx, name)) { + if (p->isAccessor()) { + if (p->set) + goto cont; + goto reject; + } + if (!extensible) + goto reject; + if (!p->isWritable()) + goto reject; + } else { + if (!extensible) + goto reject; + } + } + + cont: + + if (!members) + members.reset(new PropertyTable()); + + + // clause 4 + if (!pd && prototype) + pd = prototype->__getPropertyDescriptor__(ctx, name); + + // Clause 5 + if (pd && pd->isAccessor()) { + assert(pd->set != 0); + + Value args[1]; + args[0] = value; + pd->set->call(ctx, Value::fromObject(this), args, 1); + return; + } + + { + PropertyDescriptor *p = members->insert(name); + p->type = PropertyDescriptor::Data; + p->value = value; + p->configurable = PropertyDescriptor::Enabled; + p->enumberable = PropertyDescriptor::Enabled; + p->writable = PropertyDescriptor::Enabled; + return; + } + + reject: + if (ctx->strictMode) + __qmljs_throw_type_error(ctx); +} + +void Object::__put__(ExecutionContext *ctx, uint index, Value value) +{ + PropertyDescriptor *pd = __getOwnProperty__(ctx, index); + // clause 1 + if (pd) { + if (pd->isAccessor()) { + if (pd->set) + goto cont; + goto reject; + } else if (!pd->isWritable()) + goto reject; + else + pd->value = value; + return; + } else if (!prototype) { + if (!extensible) + goto reject; + } else { + if (PropertyDescriptor *p = prototype->__getPropertyDescriptor__(ctx, index)) { + if (p->isAccessor()) { + if (p->set) + goto cont; + goto reject; + } + if (!extensible) + goto reject; + if (!p->isWritable()) + goto reject; + } else { + if (!extensible) + goto reject; + } + } + + cont: + + // clause 4 + if (!pd && prototype) + pd = prototype->__getPropertyDescriptor__(ctx, index); + + // Clause 5 + if (pd && pd->isAccessor()) { + assert(pd->set != 0); + + Value args[1]; + args[0] = value; + pd->set->call(ctx, Value::fromObject(this), args, 1); + return; + } + + array.set(index, value); + return; + + reject: + if (ctx->strictMode) + __qmljs_throw_type_error(ctx); +} + +// Section 8.12.6 +bool Object::__hasProperty__(const ExecutionContext *ctx, String *name) const +{ + uint idx = name->asArrayIndex(); + if (idx != String::InvalidArrayIndex) + return __hasProperty__(ctx, idx); + + if (members && members->find(name) != 0) + return true; + + return prototype ? prototype->__hasProperty__(ctx, name) : false; +} + +bool Object::__hasProperty__(const ExecutionContext *ctx, uint index) const +{ + const PropertyDescriptor *p = array.at(index); + if (p && p->type != PropertyDescriptor::Generic) + return true; + + return prototype ? prototype->__hasProperty__(ctx, index) : false; +} + +// Section 8.12.7 +bool Object::__delete__(ExecutionContext *ctx, String *name) +{ + uint idx = name->asArrayIndex(); + if (idx != String::InvalidArrayIndex) + return __delete__(ctx, idx); + + if (members) { + if (PropertyTableEntry *entry = members->findEntry(name)) { + if (entry->descriptor.isConfigurable()) { + members->remove(entry); + return true; + } + if (ctx->strictMode) + __qmljs_throw_type_error(ctx); + return false; + } + } + return true; +} + +bool Object::__delete__(ExecutionContext *ctx, uint index) +{ + if (array.deleteIndex(index)) + return true; + if (ctx->strictMode) + __qmljs_throw_type_error(ctx); + return false; +} + +// Section 8.12.9 +bool Object::__defineOwnProperty__(ExecutionContext *ctx, String *name, const PropertyDescriptor *desc) +{ + uint idx = name->asArrayIndex(); + if (idx != String::InvalidArrayIndex) + return __defineOwnProperty__(ctx, idx, desc); + + PropertyDescriptor *current; + + if (isArrayObject() && name->isEqualTo(ctx->engine->id_length)) { + PropertyDescriptor *lp = array.getLengthProperty(); + if (desc->isEmpty() || desc->isSubset(lp)) + return true; + if (!lp->isWritable() || desc->type == PropertyDescriptor::Accessor || desc->isConfigurable() || desc->isEnumerable()) + goto reject; + bool succeeded = true; + if (desc->type == PropertyDescriptor::Data) { + bool ok; + uint l = desc->value.asArrayLength(ctx, &ok); + if (!ok) + ctx->throwRangeError(desc->value); + succeeded = array.setLength(l); + } + if (desc->writable == PropertyDescriptor::Disabled) + lp->writable = PropertyDescriptor::Disabled; + if (!succeeded) + goto reject; + return true; + } + + if (!members) + members.reset(new PropertyTable()); + + // Clause 1 + current = __getOwnProperty__(ctx, name); + if (!current) { + // clause 3 + if (!extensible) + goto reject; + // clause 4 + PropertyDescriptor *pd = members->insert(name); + *pd = *desc; + pd->fullyPopulated(); + return true; + } + + return __defineOwnProperty__(ctx, current, desc); +reject: + if (ctx->strictMode) + __qmljs_throw_type_error(ctx); + return false; +} + +bool Object::__defineOwnProperty__(ExecutionContext *ctx, uint index, const PropertyDescriptor *desc) +{ + PropertyDescriptor *current; + + // 15.4.5.1, 4b + if (isArrayObject() && index >= array.length() && !array.getLengthProperty()->isWritable()) + goto reject; + + if (isNonStrictArgumentsObject) + return static_cast<ArgumentsObject *>(this)->defineOwnProperty(ctx, index, desc); + + // Clause 1 + current = __getOwnProperty__(ctx, index); + if (!current) { + // clause 3 + if (!extensible) + goto reject; + // clause 4 + PropertyDescriptor *pd = array.insert(index); + *pd = *desc; + pd->fullyPopulated(); + return true; + } + + return __defineOwnProperty__(ctx, current, desc); +reject: + if (ctx->strictMode) + __qmljs_throw_type_error(ctx); + return false; +} + +bool Object::__defineOwnProperty__(ExecutionContext *ctx, PropertyDescriptor *current, const PropertyDescriptor *desc) +{ + // clause 5 + if (desc->isEmpty()) + return true; + + // clause 6 + if (desc->isSubset(current)) + return true; + + // clause 7 + if (!current->isConfigurable()) { + if (desc->isConfigurable()) + goto reject; + if (desc->enumberable != PropertyDescriptor::Undefined && desc->enumberable != current->enumberable) + goto reject; + } + + // clause 8 + if (desc->isGeneric()) + goto accept; + + // clause 9 + if (current->isData() != desc->isData()) { + // 9a + if (!current->isConfigurable()) + goto reject; + if (current->isData()) { + // 9b + current->type = PropertyDescriptor::Accessor; + current->writable = PropertyDescriptor::Undefined; + current->get = 0; + current->set = 0; + } else { + // 9c + current->type = PropertyDescriptor::Data; + current->writable = PropertyDescriptor::Disabled; + current->value = Value::undefinedValue(); + } + } else if (current->isData() && desc->isData()) { // clause 10 + if (!current->isConfigurable() && !current->isWritable()) { + if (desc->isWritable() || !current->value.sameValue(desc->value)) + goto reject; + } + } else { // clause 10 + assert(current->isAccessor() && desc->isAccessor()); + if (!current->isConfigurable()) { + if (desc->get && !(current->get == desc->get || (!current->get && (quintptr)desc->get == 0x1))) + goto reject; + if (desc->set && !(current->set == desc->set || (!current->set && (quintptr)desc->set == 0x1))) + goto reject; + } + } + + accept: + + *current += *desc; + return true; + reject: + if (ctx->strictMode) + __qmljs_throw_type_error(ctx); + return false; +} + + +bool Object::__defineOwnProperty__(ExecutionContext *ctx, const QString &name, const PropertyDescriptor *desc) +{ + return __defineOwnProperty__(ctx, ctx->engine->identifier(name), desc); +} + + +void ArrayObject::init(ExecutionContext *context) +{ + type = Type_ArrayObject; + + if (!members) + members.reset(new PropertyTable()); + PropertyDescriptor *pd = members->insert(context->engine->id_length); + pd->type = PropertyDescriptor::Data; + pd->writable = PropertyDescriptor::Enabled; + pd->enumberable = PropertyDescriptor::Disabled; + pd->configurable = PropertyDescriptor::Disabled; + pd->value = Value::fromInt32(0); + array.setLengthProperty(pd); +} + + + +void ForEachIteratorObject::markObjects() +{ + Object::markObjects(); + if (it.object) + it.object->mark(); +} + diff --git a/src/v4/qv4object.h b/src/v4/qv4object.h new file mode 100644 index 0000000000..73d62bf209 --- /dev/null +++ b/src/v4/qv4object.h @@ -0,0 +1,192 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QMLJS_OBJECTS_H +#define QMLJS_OBJECTS_H + +#include "qv4global.h" +#include "qmljs_runtime.h" +#include "qmljs_engine.h" +#include "qmljs_environment.h" +#include "qv4array.h" +#include "qv4string.h" +#include "qv4codegen_p.h" +#include "qv4isel_p.h" +#include "qv4managed.h" +#include "qv4propertydescriptor.h" +#include "qv4propertytable.h" +#include "qv4objectiterator.h" +#include "qv4regexp.h" + +#include <QtCore/QString> +#include <QtCore/QHash> +#include <QtCore/QScopedPointer> +#include <cstdio> +#include <cassert> + +namespace QQmlJS { + +namespace VM { + +struct Value; +struct Function; +struct Object; +struct ObjectIterator; +struct BooleanObject; +struct NumberObject; +struct StringObject; +struct ArrayObject; +struct DateObject; +struct FunctionObject; +struct RegExpObject; +struct ErrorObject; +struct ArgumentsObject; +struct ExecutionContext; +struct ExecutionEngine; +class MemoryManager; + +struct ObjectPrototype; +struct StringPrototype; +struct NumberPrototype; +struct BooleanPrototype; +struct ArrayPrototype; +struct FunctionPrototype; +struct DatePrototype; +struct RegExpPrototype; +struct ErrorPrototype; +struct EvalErrorPrototype; +struct RangeErrorPrototype; +struct ReferenceErrorPrototype; +struct SyntaxErrorPrototype; +struct TypeErrorPrototype; +struct URIErrorPrototype; + + +struct Q_V4_EXPORT Object: Managed { + Object *prototype; + QScopedPointer<PropertyTable> members; + Array array; + + Object() + : prototype(0) { type = Type_Object; } + Object(const Array &a) + : prototype(0), array(a) {} + + virtual ~Object(); + + PropertyDescriptor *__getOwnProperty__(ExecutionContext *ctx, String *name); + PropertyDescriptor *__getOwnProperty__(ExecutionContext *ctx, uint index); + PropertyDescriptor *__getPropertyDescriptor__(ExecutionContext *ctx, String *name); + PropertyDescriptor *__getPropertyDescriptor__(ExecutionContext *ctx, uint index); + + Value __get__(ExecutionContext *ctx, String *name, bool *hasProperty = 0); + Value __get__(ExecutionContext *ctx, uint index, bool *hasProperty = 0); + + void __put__(ExecutionContext *ctx, String *name, Value value); + void __put__(ExecutionContext *ctx, uint index, Value value); + + bool __hasProperty__(const ExecutionContext *ctx, String *name) const; + bool __hasProperty__(const ExecutionContext *ctx, uint index) const; + bool __delete__(ExecutionContext *ctx, String *name); + bool __delete__(ExecutionContext *ctx, uint index); + bool __defineOwnProperty__(ExecutionContext *ctx, PropertyDescriptor *current, const PropertyDescriptor *desc); + bool __defineOwnProperty__(ExecutionContext *ctx, String *name, const PropertyDescriptor *desc); + bool __defineOwnProperty__(ExecutionContext *ctx, uint index, const PropertyDescriptor *desc); + bool __defineOwnProperty__(ExecutionContext *ctx, const QString &name, const PropertyDescriptor *desc); + + // + // helpers + // + void __put__(ExecutionContext *ctx, const QString &name, const Value &value); + + Value getValue(ExecutionContext *ctx, const PropertyDescriptor *p) const; + Value getValueChecked(ExecutionContext *ctx, const PropertyDescriptor *p) const; + Value getValueChecked(ExecutionContext *ctx, const PropertyDescriptor *p, bool *exists) const; + + void inplaceBinOp(Value rhs, String *name, BinOp op, ExecutionContext *ctx); + void inplaceBinOp(Value rhs, Value index, BinOp op, ExecutionContext *ctx); + + /* The spec default: Writable: true, Enumerable: false, Configurable: true */ + void defineDefaultProperty(String *name, Value value); + void defineDefaultProperty(ExecutionContext *context, const QString &name, Value value); + void defineDefaultProperty(ExecutionContext *context, const QString &name, Value (*code)(ExecutionContext *), int count = 0); + void defineDefaultProperty(ExecutionContext *context, const QString &name, Value (*code)(ExecutionContext *, Value, Value *, int), int argumentCount = 0); + /* Fixed: Writable: false, Enumerable: false, Configurable: false */ + void defineReadonlyProperty(ExecutionEngine *engine, const QString &name, Value value); + void defineReadonlyProperty(String *name, Value value); + +protected: + virtual void markObjects(); + + friend struct ObjectIterator; +}; + +struct ForEachIteratorObject: Object { + ObjectIterator it; + ForEachIteratorObject(ExecutionContext *ctx, Object *o) + : it(ctx, o, ObjectIterator::EnumberableOnly|ObjectIterator::WithProtoChain) { type = Type_ForeachIteratorObject; } + + Value nextPropertyName() { return it.nextPropertyNameAsString(); } + +protected: + virtual void markObjects(); +}; + +struct BooleanObject: Object { + Value value; + BooleanObject(const Value &value): value(value) { type = Type_BooleanObject; } +}; + +struct NumberObject: Object { + Value value; + NumberObject(const Value &value): value(value) { type = Type_NumberObject; } +}; + +struct ArrayObject: Object { + ArrayObject(ExecutionContext *ctx) { init(ctx); } + ArrayObject(ExecutionContext *ctx, const Array &value): Object(value) { init(ctx); array.setLengthUnchecked(array.length()); } + void init(ExecutionContext *context); +}; + + +} // namespace VM +} // namespace QQmlJS + +#endif // QMLJS_OBJECTS_H diff --git a/src/v4/qv4objectiterator.cpp b/src/v4/qv4objectiterator.cpp new file mode 100644 index 0000000000..8da2e7bdba --- /dev/null +++ b/src/v4/qv4objectiterator.cpp @@ -0,0 +1,166 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qv4objectiterator.h" +#include "qv4object.h" +#include "qv4stringobject.h" + +namespace QQmlJS { +namespace VM { + +ObjectIterator::ObjectIterator(ExecutionContext *context, Object *o, uint flags) + : context(context) + , object(o) + , current(o) + , arrayNode(0) + , arrayIndex(0) + , tableIndex(0) + , flags(flags) +{ + if (current && current->asStringObject()) + this->flags |= CurrentIsString; +} + +PropertyDescriptor *ObjectIterator::next(String **name, uint *index) +{ + PropertyDescriptor *p = 0; + *name = 0; + *index = UINT_MAX; + while (1) { + if (!current) + break; + + if (flags & CurrentIsString) { + StringObject *s = static_cast<StringObject *>(current); + uint slen = s->value.stringValue()->toQString().length(); + while (arrayIndex < slen) { + *index = arrayIndex; + ++arrayIndex; + return s->__getOwnProperty__(context, *index); + } + flags &= ~CurrentIsString; + arrayNode = current->array.sparseBegin(); + // iterate until we're past the end of the string + while (arrayNode && arrayNode->key() < slen) + arrayNode = arrayNode->nextNode(); + } + + if (!arrayIndex) + arrayNode = current->array.sparseBegin(); + + // sparse arrays + if (arrayNode) { + while (arrayNode != current->array.sparseEnd()) { + int k = arrayNode->key(); + p = current->array.at(k); + arrayNode = arrayNode->nextNode(); + if (p && (!(flags & EnumberableOnly) || p->isEnumerable())) { + arrayIndex = k + 1; + *index = k; + return p; + } + } + arrayNode = 0; + arrayIndex = UINT_MAX; + } + // dense arrays + while (arrayIndex < current->array.length()) { + p = current->array.at(arrayIndex); + ++arrayIndex; + if (p && p->type != PropertyDescriptor::Generic && (!(flags & EnumberableOnly) || p->isEnumerable())) { + *index = arrayIndex - 1; + return p; + } + } + + if (!current->members || tableIndex >= (uint)current->members->_propertyCount) { + if (flags & WithProtoChain) + current = current->prototype; + else + current = 0; + if (current && current->asStringObject()) + flags |= CurrentIsString; + else + flags &= ~CurrentIsString; + + + arrayIndex = 0; + tableIndex = 0; + continue; + } + PropertyTableEntry *pt = current->members->_properties[tableIndex]; + ++tableIndex; + // ### check that it's not a repeated attribute + if (pt && (!(flags & EnumberableOnly) || pt->descriptor.isEnumerable())) { + *name = pt->name; + p = &pt->descriptor; + return p; + } + } + return 0; +} + +Value ObjectIterator::nextPropertyName() +{ + uint index; + String *name; + next(&name, &index); + if (name) + return Value::fromString(name); + if (index < UINT_MAX) + return Value::fromDouble(index); + return Value::nullValue(); +} + +Value ObjectIterator::nextPropertyNameAsString() +{ + uint index; + String *name; + next(&name, &index); + if (name) + return Value::fromString(name); + if (index < UINT_MAX) + return __qmljs_to_string(Value::fromDouble(index), context); + return Value::nullValue(); +} + +} +} + diff --git a/src/v4/qv4objectiterator.h b/src/v4/qv4objectiterator.h new file mode 100644 index 0000000000..baecc25424 --- /dev/null +++ b/src/v4/qv4objectiterator.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4OBJECTITERATOR_H +#define QV4OBJECTITERATOR_H + +#include "qmljs_value.h" + +namespace QQmlJS { +namespace VM { + +struct SparseArrayNode; +struct Object; +struct PropertyDescriptor; + +struct ObjectIterator +{ + enum Flags { + NoFlags = 0, + EnumberableOnly = 0x1, + WithProtoChain = 0x2, + CurrentIsString = 0x4 + }; + + ExecutionContext *context; + Object *object; + Object *current; + SparseArrayNode *arrayNode; + uint arrayIndex; + uint tableIndex; + uint flags; + + ObjectIterator(ExecutionContext *context, Object *o, uint flags); + PropertyDescriptor *next(String **name, uint *index); + Value nextPropertyName(); + Value nextPropertyNameAsString(); +}; + +} +} + +#endif diff --git a/src/v4/qv4objectproto.cpp b/src/v4/qv4objectproto.cpp new file mode 100644 index 0000000000..98f205d1d8 --- /dev/null +++ b/src/v4/qv4objectproto.cpp @@ -0,0 +1,558 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "qv4objectproto.h" +#include "qv4mm.h" +#include <QtCore/qnumeric.h> +#include <QtCore/qmath.h> +#include <QtCore/QDateTime> +#include <QtCore/QStringList> +#include <QtCore/QDebug> +#include <cassert> + +#include <private/qqmljsengine_p.h> +#include <private/qqmljslexer_p.h> +#include <private/qqmljsparser_p.h> +#include <private/qqmljsast_p.h> +#include <qv4ir_p.h> +#include <qv4codegen_p.h> +#include <qv4isel_masm_p.h> + +#ifndef Q_WS_WIN +# include <time.h> +# ifndef Q_OS_VXWORKS +# include <sys/time.h> +# else +# include "qplatformdefs.h" +# endif +#else +# include <windows.h> +#endif + +using namespace QQmlJS::VM; + + +// +// Object +// +ObjectCtor::ObjectCtor(ExecutionContext *scope) + : FunctionObject(scope) +{ +} + +Value ObjectCtor::construct(ExecutionContext *ctx) +{ + if (!ctx->argumentCount || ctx->argument(0).isUndefined() || ctx->argument(0).isNull()) + return ctx->thisObject; + return __qmljs_to_object(ctx->argument(0), ctx); +} + +Value ObjectCtor::call(ExecutionContext *ctx) +{ + if (!ctx->argumentCount || ctx->argument(0).isUndefined() || ctx->argument(0).isNull()) + return Value::fromObject(ctx->engine->newObject()); + return __qmljs_to_object(ctx->argument(0), ctx); +} + +void ObjectPrototype::init(ExecutionContext *ctx, const Value &ctor) +{ + ctor.objectValue()->defineReadonlyProperty(ctx->engine->id_prototype, Value::fromObject(this)); + ctor.objectValue()->defineReadonlyProperty(ctx->engine->id_length, Value::fromInt32(1)); + ctor.objectValue()->defineDefaultProperty(ctx, QStringLiteral("getPrototypeOf"), method_getPrototypeOf, 1); + ctor.objectValue()->defineDefaultProperty(ctx, QStringLiteral("getOwnPropertyDescriptor"), method_getOwnPropertyDescriptor, 2); + ctor.objectValue()->defineDefaultProperty(ctx, QStringLiteral("getOwnPropertyNames"), method_getOwnPropertyNames, 1); + ctor.objectValue()->defineDefaultProperty(ctx, QStringLiteral("create"), method_create, 2); + ctor.objectValue()->defineDefaultProperty(ctx, QStringLiteral("defineProperty"), method_defineProperty, 3); + ctor.objectValue()->defineDefaultProperty(ctx, QStringLiteral("defineProperties"), method_defineProperties, 2); + ctor.objectValue()->defineDefaultProperty(ctx, QStringLiteral("seal"), method_seal, 1); + ctor.objectValue()->defineDefaultProperty(ctx, QStringLiteral("freeze"), method_freeze, 1); + ctor.objectValue()->defineDefaultProperty(ctx, QStringLiteral("preventExtensions"), method_preventExtensions, 1); + ctor.objectValue()->defineDefaultProperty(ctx, QStringLiteral("isSealed"), method_isSealed, 1); + ctor.objectValue()->defineDefaultProperty(ctx, QStringLiteral("isFrozen"), method_isFrozen, 1); + ctor.objectValue()->defineDefaultProperty(ctx, QStringLiteral("isExtensible"), method_isExtensible, 1); + ctor.objectValue()->defineDefaultProperty(ctx, QStringLiteral("keys"), method_keys, 1); + + defineDefaultProperty(ctx, QStringLiteral("constructor"), ctor); + defineDefaultProperty(ctx, QStringLiteral("toString"), method_toString, 0); + defineDefaultProperty(ctx, QStringLiteral("toLocaleString"), method_toLocaleString, 0); + defineDefaultProperty(ctx, QStringLiteral("valueOf"), method_valueOf, 0); + defineDefaultProperty(ctx, QStringLiteral("hasOwnProperty"), method_hasOwnProperty, 1); + defineDefaultProperty(ctx, QStringLiteral("isPrototypeOf"), method_isPrototypeOf, 1); + defineDefaultProperty(ctx, QStringLiteral("propertyIsEnumerable"), method_propertyIsEnumerable, 1); + defineDefaultProperty(ctx, QStringLiteral("__defineGetter__"), method_defineGetter, 0); + defineDefaultProperty(ctx, QStringLiteral("__defineSetter__"), method_defineSetter, 0); +} + +Value ObjectPrototype::method_getPrototypeOf(ExecutionContext *ctx) +{ + Value o = ctx->argument(0); + if (! o.isObject()) + ctx->throwTypeError(); + + Object *p = o.objectValue()->prototype; + return p ? Value::fromObject(p) : Value::nullValue(); +} + +Value ObjectPrototype::method_getOwnPropertyDescriptor(ExecutionContext *ctx) +{ + Value O = ctx->argument(0); + if (!O.isObject()) + ctx->throwTypeError(); + + String *name = ctx->argument(1).toString(ctx); + PropertyDescriptor *desc = O.objectValue()->__getOwnProperty__(ctx, name); + return fromPropertyDescriptor(ctx, desc); +} + +Value ObjectPrototype::method_getOwnPropertyNames(ExecutionContext *ctx) +{ + Object *O = ctx->argument(0).asObject(); + if (!O) + ctx->throwTypeError(); + + ArrayObject *array = ctx->engine->newArrayObject(ctx)->asArrayObject(); + Array &a = array->array; + ObjectIterator it(ctx, O, ObjectIterator::NoFlags); + while (1) { + Value v = it.nextPropertyNameAsString(); + if (v.isNull()) + break; + a.push_back(v); + } + return Value::fromObject(array); +} + +Value ObjectPrototype::method_create(ExecutionContext *ctx) +{ + Value O = ctx->argument(0); + if (!O.isObject() && !O.isNull()) + ctx->throwTypeError(); + + Object *newObject = ctx->engine->newObject(); + newObject->prototype = O.objectValue(); + + Value objValue = Value::fromObject(newObject); + if (ctx->argumentCount > 1 && !ctx->argument(1).isUndefined()) { + ctx->arguments[0] = objValue; + method_defineProperties(ctx); + } + + return objValue; +} + +Value ObjectPrototype::method_defineProperty(ExecutionContext *ctx) +{ + Value O = ctx->argument(0); + if (!O.isObject()) + ctx->throwTypeError(); + + String *name = ctx->argument(1).toString(ctx); + + Value attributes = ctx->argument(2); + PropertyDescriptor pd; + toPropertyDescriptor(ctx, attributes, &pd); + + if (!O.objectValue()->__defineOwnProperty__(ctx, name, &pd)) + __qmljs_throw_type_error(ctx); + + return O; +} + +Value ObjectPrototype::method_defineProperties(ExecutionContext *ctx) +{ + Value O = ctx->argument(0); + if (!O.isObject()) + ctx->throwTypeError(); + + Object *o = ctx->argument(1).toObject(ctx).objectValue(); + + ObjectIterator it(ctx, o, ObjectIterator::EnumberableOnly); + while (1) { + uint index; + String *name; + PropertyDescriptor *pd = it.next(&name, &index); + if (!pd) + break; + PropertyDescriptor n; + toPropertyDescriptor(ctx, o->getValue(ctx, pd), &n); + bool ok; + if (name) + ok = O.objectValue()->__defineOwnProperty__(ctx, name, &n); + else + ok = O.objectValue()->__defineOwnProperty__(ctx, index, &n); + if (!ok) + __qmljs_throw_type_error(ctx); + } + + return O; +} + +Value ObjectPrototype::method_seal(ExecutionContext *ctx) +{ + if (!ctx->argument(0).isObject()) + __qmljs_throw_type_error(ctx); + + Object *o = ctx->argument(0).objectValue(); + o->extensible = false; + + ObjectIterator it(ctx, o, ObjectIterator::NoFlags); + while (1) { + uint index; + String *name; + PropertyDescriptor *pd = it.next(&name, &index); + if (!pd) + break; + pd->configurable = PropertyDescriptor::Disabled; + } + return ctx->argument(0); +} + +Value ObjectPrototype::method_freeze(ExecutionContext *ctx) +{ + if (!ctx->argument(0).isObject()) + __qmljs_throw_type_error(ctx); + + Object *o = ctx->argument(0).objectValue(); + o->extensible = false; + + ObjectIterator it(ctx, o, ObjectIterator::NoFlags); + while (1) { + uint index; + String *name; + PropertyDescriptor *pd = it.next(&name, &index); + if (!pd) + break; + if (pd->type == PropertyDescriptor::Data) + pd->writable = PropertyDescriptor::Disabled; + pd->configurable = PropertyDescriptor::Disabled; + } + return ctx->argument(0); +} + +Value ObjectPrototype::method_preventExtensions(ExecutionContext *ctx) +{ + if (!ctx->argument(0).isObject()) + __qmljs_throw_type_error(ctx); + + Object *o = ctx->argument(0).objectValue(); + o->extensible = false; + return ctx->argument(0); +} + +Value ObjectPrototype::method_isSealed(ExecutionContext *ctx) +{ + if (!ctx->argument(0).isObject()) + __qmljs_throw_type_error(ctx); + + Object *o = ctx->argument(0).objectValue(); + if (o->extensible) + return Value::fromBoolean(false); + + ObjectIterator it(ctx, o, ObjectIterator::NoFlags); + while (1) { + uint index; + String *name; + PropertyDescriptor *pd = it.next(&name, &index); + if (!pd) + break; + if (pd->configurable != PropertyDescriptor::Disabled) + return Value::fromBoolean(false); + } + return Value::fromBoolean(true); +} + +Value ObjectPrototype::method_isFrozen(ExecutionContext *ctx) +{ + if (!ctx->argument(0).isObject()) + __qmljs_throw_type_error(ctx); + + Object *o = ctx->argument(0).objectValue(); + if (o->extensible) + return Value::fromBoolean(false); + + ObjectIterator it(ctx, o, ObjectIterator::NoFlags); + while (1) { + uint index; + String *name; + PropertyDescriptor *pd = it.next(&name, &index); + if (!pd) + break; + if (pd->isWritable() || pd->isConfigurable()) + return Value::fromBoolean(false); + } + return Value::fromBoolean(true); +} + +Value ObjectPrototype::method_isExtensible(ExecutionContext *ctx) +{ + if (!ctx->argument(0).isObject()) + __qmljs_throw_type_error(ctx); + + Object *o = ctx->argument(0).objectValue(); + return Value::fromBoolean(o->extensible); +} + +Value ObjectPrototype::method_keys(ExecutionContext *ctx) +{ + if (!ctx->argument(0).isObject()) + __qmljs_throw_type_error(ctx); + + Object *o = ctx->argument(0).objectValue(); + + ArrayObject *a = ctx->engine->newArrayObject(ctx); + + ObjectIterator it(ctx, o, ObjectIterator::EnumberableOnly); + while (1) { + uint index; + String *name; + PropertyDescriptor *pd = it.next(&name, &index); + if (!pd) + break; + Value key; + if (name) { + key = Value::fromString(name); + } else { + key = Value::fromDouble(index); + key = __qmljs_to_string(key, ctx); + } + a->array.push_back(key); + } + + return Value::fromObject(a); +} + +Value ObjectPrototype::method_toString(ExecutionContext *ctx) +{ + if (ctx->thisObject.isUndefined()) { + return Value::fromString(ctx, QStringLiteral("[object Undefined]")); + } else if (ctx->thisObject.isNull()) { + return Value::fromString(ctx, QStringLiteral("[object Null]")); + } else { + Value obj = __qmljs_to_object(ctx->thisObject, ctx); + QString className = obj.objectValue()->className(); + return Value::fromString(ctx, QString::fromUtf8("[object %1]").arg(className)); + } +} + +Value ObjectPrototype::method_toLocaleString(ExecutionContext *ctx) +{ + Object *o = __qmljs_to_object(ctx->thisObject, ctx).objectValue(); + Value ts = o->__get__(ctx, ctx->engine->identifier(QStringLiteral("toString"))); + FunctionObject *f = ts.asFunctionObject(); + if (!f) + __qmljs_throw_type_error(ctx); + return f->call(ctx, Value::fromObject(o), 0, 0); +} + +Value ObjectPrototype::method_valueOf(ExecutionContext *ctx) +{ + return ctx->thisObject.toObject(ctx); +} + +Value ObjectPrototype::method_hasOwnProperty(ExecutionContext *ctx) +{ + String *P = ctx->argument(0).toString(ctx); + Value O = ctx->thisObject.toObject(ctx); + bool r = O.objectValue()->__getOwnProperty__(ctx, P) != 0; + return Value::fromBoolean(r); +} + +Value ObjectPrototype::method_isPrototypeOf(ExecutionContext *ctx) +{ + Value V = ctx->argument(0); + if (! V.isObject()) + return Value::fromBoolean(false); + + Object *O = ctx->thisObject.toObject(ctx).objectValue(); + Object *proto = V.objectValue()->prototype; + while (proto) { + if (O == proto) + return Value::fromBoolean(true); + proto = proto->prototype; + } + return Value::fromBoolean(false); +} + +Value ObjectPrototype::method_propertyIsEnumerable(ExecutionContext *ctx) +{ + String *p = ctx->argument(0).toString(ctx); + + Object *o = ctx->thisObject.toObject(ctx).objectValue(); + PropertyDescriptor *pd = o->__getOwnProperty__(ctx, p); + return Value::fromBoolean(pd && pd->isEnumerable()); +} + +Value ObjectPrototype::method_defineGetter(ExecutionContext *ctx) +{ + if (ctx->argumentCount < 2) + __qmljs_throw_type_error(ctx); + String *prop = ctx->argument(0).toString(ctx); + + FunctionObject *f = ctx->argument(1).asFunctionObject(); + if (!f) + __qmljs_throw_type_error(ctx); + + Object *o = ctx->thisObject.toObject(ctx).objectValue(); + + PropertyDescriptor pd = PropertyDescriptor::fromAccessor(f, 0); + pd.configurable = PropertyDescriptor::Enabled; + pd.enumberable = PropertyDescriptor::Enabled; + o->__defineOwnProperty__(ctx, prop, &pd); + return Value::undefinedValue(); +} + +Value ObjectPrototype::method_defineSetter(ExecutionContext *ctx) +{ + if (ctx->argumentCount < 2) + __qmljs_throw_type_error(ctx); + String *prop = ctx->argument(0).toString(ctx); + + FunctionObject *f = ctx->argument(1).asFunctionObject(); + if (!f) + __qmljs_throw_type_error(ctx); + + Object *o = ctx->thisObject.toObject(ctx).objectValue(); + + PropertyDescriptor pd = PropertyDescriptor::fromAccessor(0, f); + pd.configurable = PropertyDescriptor::Enabled; + pd.enumberable = PropertyDescriptor::Enabled; + o->__defineOwnProperty__(ctx, prop, &pd); + return Value::undefinedValue(); +} + +void ObjectPrototype::toPropertyDescriptor(ExecutionContext *ctx, Value v, PropertyDescriptor *desc) +{ + if (!v.isObject()) + __qmljs_throw_type_error(ctx); + + Object *o = v.objectValue(); + + desc->type = PropertyDescriptor::Generic; + + desc->enumberable = PropertyDescriptor::Undefined; + if (o->__hasProperty__(ctx, ctx->engine->id_enumerable)) + desc->enumberable = __qmljs_to_boolean(o->__get__(ctx, ctx->engine->id_enumerable), ctx) ? PropertyDescriptor::Enabled : PropertyDescriptor::Disabled; + + desc->configurable = PropertyDescriptor::Undefined; + if (o->__hasProperty__(ctx, ctx->engine->id_configurable)) + desc->configurable = __qmljs_to_boolean(o->__get__(ctx, ctx->engine->id_configurable), ctx) ? PropertyDescriptor::Enabled : PropertyDescriptor::Disabled; + + desc->get = 0; + if (o->__hasProperty__(ctx, ctx->engine->id_get)) { + Value get = o->__get__(ctx, ctx->engine->id_get); + FunctionObject *f = get.asFunctionObject(); + if (f) { + desc->get = f; + } else if (get.isUndefined()) { + desc->get = (FunctionObject *)0x1; + } else { + __qmljs_throw_type_error(ctx); + } + desc->type = PropertyDescriptor::Accessor; + } + + desc->set = 0; + if (o->__hasProperty__(ctx, ctx->engine->id_set)) { + Value set = o->__get__(ctx, ctx->engine->id_set); + FunctionObject *f = set.asFunctionObject(); + if (f) { + desc->set = f; + } else if (set.isUndefined()) { + desc->set = (FunctionObject *)0x1; + } else { + __qmljs_throw_type_error(ctx); + } + desc->type = PropertyDescriptor::Accessor; + } + + desc->writable = PropertyDescriptor::Undefined; + if (o->__hasProperty__(ctx, ctx->engine->id_writable)) { + if (desc->isAccessor()) + __qmljs_throw_type_error(ctx); + desc->writable = __qmljs_to_boolean(o->__get__(ctx, ctx->engine->id_writable), ctx) ? PropertyDescriptor::Enabled : PropertyDescriptor::Disabled; + // writable forces it to be a data descriptor + desc->value = Value::undefinedValue(); + } + + if (o->__hasProperty__(ctx, ctx->engine->id_value)) { + if (desc->isAccessor()) + __qmljs_throw_type_error(ctx); + desc->value = o->__get__(ctx, ctx->engine->id_value); + desc->type = PropertyDescriptor::Data; + } + +} + + +Value ObjectPrototype::fromPropertyDescriptor(ExecutionContext *ctx, const PropertyDescriptor *desc) +{ + if (!desc) + return Value::undefinedValue(); + + ExecutionEngine *engine = ctx->engine; +// Let obj be the result of creating a new object as if by the expression new Object() where Object is the standard built-in constructor with that name. + Object *o = engine->newObject(); + + PropertyDescriptor pd; + pd.type = PropertyDescriptor::Data; + pd.writable = PropertyDescriptor::Enabled; + pd.enumberable = PropertyDescriptor::Enabled; + pd.configurable = PropertyDescriptor::Enabled; + + if (desc->isData()) { + pd.value = desc->value; + o->__defineOwnProperty__(ctx, engine->identifier(QStringLiteral("value")), &pd); + pd.value = Value::fromBoolean(desc->writable == PropertyDescriptor::Enabled ? true : false); + o->__defineOwnProperty__(ctx, engine->identifier(QStringLiteral("writable")), &pd); + } else { + pd.value = desc->get ? Value::fromObject(desc->get) : Value::undefinedValue(); + o->__defineOwnProperty__(ctx, engine->identifier(QStringLiteral("get")), &pd); + pd.value = desc->set ? Value::fromObject(desc->set) : Value::undefinedValue(); + o->__defineOwnProperty__(ctx, engine->identifier(QStringLiteral("set")), &pd); + } + pd.value = Value::fromBoolean(desc->enumberable == PropertyDescriptor::Enabled ? true : false); + o->__defineOwnProperty__(ctx, engine->identifier(QStringLiteral("enumerable")), &pd); + pd.value = Value::fromBoolean(desc->configurable == PropertyDescriptor::Enabled ? true : false); + o->__defineOwnProperty__(ctx, engine->identifier(QStringLiteral("configurable")), &pd); + + return Value::fromObject(o); +} diff --git a/src/v4/qv4objectproto.h b/src/v4/qv4objectproto.h new file mode 100644 index 0000000000..93527a28ef --- /dev/null +++ b/src/v4/qv4objectproto.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4ECMAOBJECTS_P_H +#define QV4ECMAOBJECTS_P_H + +#include "qv4object.h" +#include "qv4functionobject.h" +#include <QtCore/qnumeric.h> + +namespace QQmlJS { +namespace VM { + +struct ObjectCtor: FunctionObject +{ + ObjectCtor(ExecutionContext *scope); + + virtual Value construct(ExecutionContext *ctx); + virtual Value call(ExecutionContext *ctx); +}; + +struct ObjectPrototype: Object +{ + void init(ExecutionContext *ctx, const Value &ctor); + + static Value method_getPrototypeOf(ExecutionContext *ctx); + static Value method_getOwnPropertyDescriptor(ExecutionContext *ctx); + static Value method_getOwnPropertyNames(ExecutionContext *ctx); + static Value method_create(ExecutionContext *ctx); + static Value method_defineProperty(ExecutionContext *ctx); + static Value method_defineProperties(ExecutionContext *ctx); + static Value method_seal(ExecutionContext *ctx); + static Value method_freeze(ExecutionContext *ctx); + static Value method_preventExtensions(ExecutionContext *ctx); + static Value method_isSealed(ExecutionContext *ctx); + static Value method_isFrozen(ExecutionContext *ctx); + static Value method_isExtensible(ExecutionContext *ctx); + static Value method_keys(ExecutionContext *ctx); + + static Value method_toString(ExecutionContext *ctx); + static Value method_toLocaleString(ExecutionContext *ctx); + static Value method_valueOf(ExecutionContext *ctx); + static Value method_hasOwnProperty(ExecutionContext *ctx); + static Value method_isPrototypeOf(ExecutionContext *ctx); + static Value method_propertyIsEnumerable(ExecutionContext *ctx); + + static Value method_defineGetter(ExecutionContext *ctx); + static Value method_defineSetter(ExecutionContext *ctx); + + static void toPropertyDescriptor(ExecutionContext *ctx, Value v, PropertyDescriptor *desc); + static Value fromPropertyDescriptor(ExecutionContext *ctx, const PropertyDescriptor *desc); +}; + + +} // end of namespace VM +} // end of namespace QQmlJS + +#endif // QV4ECMAOBJECTS_P_H diff --git a/src/v4/qv4propertydescriptor.h b/src/v4/qv4propertydescriptor.h new file mode 100644 index 0000000000..dc9e1c556d --- /dev/null +++ b/src/v4/qv4propertydescriptor.h @@ -0,0 +1,169 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4PROPERTYDESCRIPTOR_H +#define QV4PROPERTYDESCRIPTOR_H + +#include "qmljs_value.h" + +namespace QQmlJS { +namespace VM { + +struct FunctionObject; + +struct PropertyDescriptor { + enum Type { + Generic, + Data, + Accessor + }; + enum State { + Undefined, + Disabled, + Enabled + }; + union { + Value value; + struct { + FunctionObject *get; + FunctionObject *set; + }; + }; + uint type : 8; + uint writable : 8; + uint enumberable : 8; + uint configurable : 8; + + static inline PropertyDescriptor fromValue(Value v) { + PropertyDescriptor pd; + pd.value = v; + pd.type = Data; + pd.writable = Undefined; + pd.enumberable = Undefined; + pd.configurable = Undefined; + return pd; + } + static inline PropertyDescriptor fromAccessor(FunctionObject *getter, FunctionObject *setter) { + PropertyDescriptor pd; + pd.get = getter; + pd.set = setter; + pd.type = Accessor; + pd.writable = Undefined; + pd.enumberable = Undefined; + pd.configurable = Undefined; + return pd; + } + + // Section 8.10 + inline void fullyPopulated() { + if (type == Generic) { + type = Data; + value = Value::undefinedValue(); + } + if (type == Data) { + if (writable == Undefined) + writable = Disabled; + } else { + writable = Undefined; + if ((quintptr)get == 0x1) + get = 0; + if ((quintptr)set == 0x1) + set = 0; + } + if (enumberable == Undefined) + enumberable = Disabled; + if (configurable == Undefined) + configurable = Disabled; + } + + inline bool isData() const { return type == Data || writable != Undefined; } + inline bool isAccessor() const { return type == Accessor; } + inline bool isGeneric() const { return type == Generic && writable == Undefined; } + + inline bool isWritable() const { return writable == Enabled; } + inline bool isEnumerable() const { return enumberable == Enabled; } + inline bool isConfigurable() const { return configurable == Enabled; } + + inline bool isEmpty() const { + return type == Generic && writable == Undefined && enumberable == Undefined && configurable == Undefined; + } + inline bool isSubset(PropertyDescriptor *other) const { + if (type != Generic && type != other->type) + return false; + if (enumberable != Undefined && enumberable != other->enumberable) + return false; + if (configurable != Undefined && configurable != other->configurable) + return false; + if (writable != Undefined && writable != other->writable) + return false; + if (type == Data && !value.sameValue(other->value)) + return false; + if (type == Accessor) { + if (get != other->get) + return false; + if (set != other->set) + return false; + } + return true; + } + inline void operator+=(const PropertyDescriptor &other) { + if (other.enumberable != Undefined) + enumberable = other.enumberable; + if (other.configurable != Undefined) + configurable = other.configurable; + if (other.writable != Undefined) + writable = other.writable; + if (other.type == Accessor) { + type = Accessor; + if (other.get) + get = ((quintptr)other.get == 0x1) ? 0 : other.get; + if (other.set) + set = ((quintptr)other.set == 0x1) ? 0 : other.set; + } else if (other.type == Data){ + type = Data; + value = other.value; + } + } +}; + +} +} + +#endif diff --git a/src/v4/qv4propertytable.h b/src/v4/qv4propertytable.h new file mode 100644 index 0000000000..3bd47679b5 --- /dev/null +++ b/src/v4/qv4propertytable.h @@ -0,0 +1,224 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4PROPERTYTABLE_H +#define QV4PROPERTYTABLE_H + +#include "qmljs_value.h" +#include "qv4propertydescriptor.h" + +namespace QQmlJS { +namespace VM { + +struct ObjectIterator; + +struct PropertyTableEntry { + PropertyDescriptor descriptor; + String *name; + PropertyTableEntry *next; + int index; + + inline PropertyTableEntry(String *name) + : name(name), + next(0), + index(-1) + { } + + inline bool hasName(String *n) const { return name->isEqualTo(n); } + inline unsigned hashValue() const { return name->hashValue(); } +}; + +class PropertyTable +{ + Q_DISABLE_COPY(PropertyTable) + +public: + PropertyTable() + : _properties(0) + , _buckets(0) + , _freeList(0) + , _propertyCount(0) + , _bucketCount(0) + , _primeIdx(-1) + , _allocated(0) + {} + + ~PropertyTable() + { + qDeleteAll(_properties, _properties + _propertyCount); + delete[] _properties; + delete[] _buckets; + } + + typedef PropertyTableEntry **iterator; + inline iterator begin() const { return _properties; } + inline iterator end() const { return _properties + _propertyCount; } + + void remove(PropertyTableEntry *prop) + { + PropertyTableEntry **bucket = _buckets + (prop->hashValue() % _bucketCount); + if (*bucket == prop) { + *bucket = prop->next; + } else { + for (PropertyTableEntry *it = *bucket; it; it = it->next) { + if (it->next == prop) { + it->next = it->next->next; + break; + } + } + } + + _properties[prop->index] = 0; + prop->next = _freeList; + _freeList = prop; + } + + PropertyTableEntry *findEntry(String *name) const + { + if (_properties) { + for (PropertyTableEntry *prop = _buckets[name->hashValue() % _bucketCount]; prop; prop = prop->next) { + if (prop && (prop->name == name || prop->hasName(name))) + return prop; + } + } + + return 0; + } + + PropertyDescriptor *find(String *name) const + { + if (_properties) { + for (PropertyTableEntry *prop = _buckets[name->hashValue() % _bucketCount]; prop; prop = prop->next) { + if (prop && (prop->name == name || prop->hasName(name))) + return &prop->descriptor; + } + } + + return 0; + } + + PropertyDescriptor *insert(String *name) + { + if (PropertyTableEntry *prop = findEntry(name)) + return &prop->descriptor; + + if (_propertyCount == _allocated) { + if (! _allocated) + _allocated = 4; + else + _allocated *= 2; + + PropertyTableEntry **properties = new PropertyTableEntry*[_allocated]; + std::copy(_properties, _properties + _propertyCount, properties); + delete[] _properties; + _properties = properties; + } + + PropertyTableEntry *prop; + if (_freeList) { + prop = _freeList; + prop->name = name; + _freeList = _freeList->next; + } else { + prop = new PropertyTableEntry(name); + } + + prop->index = _propertyCount; + _properties[_propertyCount] = prop; + ++_propertyCount; + + if (! _buckets || 3 * _propertyCount >= 2 * _bucketCount) { + rehash(); + } else { + PropertyTableEntry *&bucket = _buckets[prop->hashValue() % _bucketCount]; + prop->next = bucket; + bucket = prop; + } + + return &prop->descriptor; + } + +private: + void rehash() + { + _bucketCount = nextPrime(); + + delete[] _buckets; + _buckets = new PropertyTableEntry *[_bucketCount]; + std::fill(_buckets, _buckets + _bucketCount, (PropertyTableEntry *) 0); + + for (int i = 0; i < _propertyCount; ++i) { + PropertyTableEntry *prop = _properties[i]; + if (prop) { + PropertyTableEntry *&bucket = _buckets[prop->hashValue() % _bucketCount]; + prop->next = bucket; + bucket = prop; + } + } + } + + inline int nextPrime() + { + // IMPORTANT: do not add more primes without checking if _primeIdx needs more bits! + static const int primes[] = { + 11, 23, 47, 97, 197, 397, 797, 1597, 3203, 6421, 12853, 25717, 51437, 102877 + }; + + if (_primeIdx < (int) (sizeof(primes)/sizeof(int))) + return primes[++_primeIdx]; + else + return _bucketCount * 2 + 1; // Yes, we're degrading here. But who needs more than about 68000 properties? + } + +private: + friend struct ObjectIterator; + PropertyTableEntry **_properties; + PropertyTableEntry **_buckets; + PropertyTableEntry *_freeList; + int _propertyCount; + int _bucketCount; + int _primeIdx: 5; + int _allocated: 27; +}; + +} +} + +#endif diff --git a/src/v4/qv4regexp.cpp b/src/v4/qv4regexp.cpp new file mode 100644 index 0000000000..bcef815e25 --- /dev/null +++ b/src/v4/qv4regexp.cpp @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4regexp.h" + +#include "qmljs_engine.h" + +namespace QQmlJS { +namespace VM { + +uint RegExp::match(const QString &string, int start, uint *matchOffsets) +{ + if (!isValid()) + return JSC::Yarr::offsetNoMatch; + + return JSC::Yarr::interpret(m_byteCode.get(), WTF::String(string).characters16(), string.length(), start, matchOffsets); +} + +RegExp::RegExp(ExecutionEngine* engine, const QString &pattern, bool ignoreCase, bool multiline) + : m_pattern(pattern) + , m_subPatternCount(0) + , m_ignoreCase(ignoreCase) + , m_multiLine(multiline) +{ + if (!engine) + return; + const char* error = 0; + JSC::Yarr::YarrPattern yarrPattern(WTF::String(pattern), ignoreCase, multiline, &error); + if (error) + return; + m_subPatternCount = yarrPattern.m_numSubpatterns; + m_byteCode = JSC::Yarr::byteCompile(yarrPattern, &engine->bumperPointerAllocator); +} + +} // end of namespace VM +} // end of namespace QQmlJS + + diff --git a/src/v4/qv4regexp.h b/src/v4/qv4regexp.h new file mode 100644 index 0000000000..7eae033bec --- /dev/null +++ b/src/v4/qv4regexp.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4REGEXP_H +#define QV4REGEXP_H + +#include <QString> +#include <QVector> + +#include <wtf/RefCounted.h> +#include <wtf/RefPtr.h> +#include <wtf/FastAllocBase.h> +#include <wtf/BumpPointerAllocator.h> + +#include <limits.h> + +#include <yarr/Yarr.h> +#include <yarr/YarrInterpreter.h> + +namespace QQmlJS { +namespace VM { + +struct ExecutionEngine; + +class RegExp : public RefCounted<RegExp> +{ +public: + static PassRefPtr<RegExp> create(ExecutionEngine* engine, const QString& pattern, bool ignoreCase = false, bool multiline = false) + { return adoptRef(new RegExp(engine, pattern, ignoreCase, multiline)); } + + QString pattern() const { return m_pattern; } + + bool isValid() const { return m_byteCode.get(); } + + uint match(const QString& string, int start, uint *matchOffsets); + + bool ignoreCase() const { return m_ignoreCase; } + bool multiLine() const { return m_multiLine; } + int captureCount() const { return m_subPatternCount + 1; } + +private: + Q_DISABLE_COPY(RegExp); + RegExp(ExecutionEngine* engine, const QString& pattern, bool ignoreCase, bool multiline); + + const QString m_pattern; + OwnPtr<JSC::Yarr::BytecodePattern> m_byteCode; + int m_subPatternCount; + const bool m_ignoreCase; + const bool m_multiLine; +}; + +} // end of namespace VM +} // end of namespace QQmlJS + +#endif // QV4REGEXP_H diff --git a/src/v4/qv4regexpobject.cpp b/src/v4/qv4regexpobject.cpp new file mode 100644 index 0000000000..1f78c56782 --- /dev/null +++ b/src/v4/qv4regexpobject.cpp @@ -0,0 +1,237 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4regexpobject.h" +#include "qv4ir_p.h" +#include "qv4isel_p.h" +#include "qv4objectproto.h" +#include "qv4stringobject.h" +#include "qv4mm.h" + +#include <private/qqmljsengine_p.h> +#include <private/qqmljslexer_p.h> +#include <private/qqmljsparser_p.h> +#include <private/qqmljsast_p.h> +#include <qv4ir_p.h> +#include <qv4codegen_p.h> +#include "private/qlocale_tools_p.h" + +#include <QtCore/qmath.h> +#include <QtCore/QDebug> +#include <cassert> +#include <typeinfo> +#include <iostream> +#include <alloca.h> + +using namespace QQmlJS::VM; + + +RegExpObject::RegExpObject(ExecutionEngine *engine, PassRefPtr<RegExp> value, bool global) + : value(value) + , global(global) +{ + type = Type_RegExpObject; + + if (!members) + members.reset(new PropertyTable()); + lastIndexProperty = members->insert(engine->identifier(QStringLiteral("lastIndex"))); + lastIndexProperty->type = PropertyDescriptor::Data; + lastIndexProperty->writable = PropertyDescriptor::Enabled; + lastIndexProperty->enumberable = PropertyDescriptor::Disabled; + lastIndexProperty->configurable = PropertyDescriptor::Disabled; + lastIndexProperty->value = Value::fromInt32(0); + if (!this->value.get()) + return; + defineReadonlyProperty(engine->identifier(QStringLiteral("source")), Value::fromString(engine->newString(this->value->pattern()))); + defineReadonlyProperty(engine->identifier(QStringLiteral("global")), Value::fromBoolean(global)); + defineReadonlyProperty(engine->identifier(QStringLiteral("ignoreCase")), Value::fromBoolean(this->value->ignoreCase())); + defineReadonlyProperty(engine->identifier(QStringLiteral("multiline")), Value::fromBoolean(this->value->multiLine())); +} + + +RegExpCtor::RegExpCtor(ExecutionContext *scope) + : FunctionObject(scope) +{ +} + +Value RegExpCtor::construct(ExecutionContext *ctx) +{ + Value r = ctx->argumentCount > 0 ? ctx->argument(0) : Value::undefinedValue(); + Value f = ctx->argumentCount > 1 ? ctx->argument(1) : Value::undefinedValue(); + if (RegExpObject *re = r.asRegExpObject()) { + if (!f.isUndefined()) + ctx->throwTypeError(); + + RegExpObject *o = ctx->engine->newRegExpObject(re->value, re->global); + return Value::fromObject(o); + } + + if (r.isUndefined()) + r = Value::fromString(ctx, QString()); + else if (!r.isString()) + r = __qmljs_to_string(r, ctx); + + bool global = false; + bool ignoreCase = false; + bool multiLine = false; + if (!f.isUndefined()) { + f = __qmljs_to_string(f, ctx); + QString str = f.stringValue()->toQString(); + for (int i = 0; i < str.length(); ++i) { + if (str.at(i) == QChar('g') && !global) { + global = true; + } else if (str.at(i) == QChar('i') && !ignoreCase) { + ignoreCase = true; + } else if (str.at(i) == QChar('m') && !multiLine) { + multiLine = true; + } else { + ctx->throwSyntaxError(0); + } + } + } + + RefPtr<RegExp> re = RegExp::create(ctx->engine, r.stringValue()->toQString(), ignoreCase, multiLine); + if (!re->isValid()) + ctx->throwSyntaxError(0); + + RegExpObject *o = ctx->engine->newRegExpObject(re, global); + return Value::fromObject(o); +} + +Value RegExpCtor::call(ExecutionContext *ctx) +{ + if (ctx->argumentCount > 0 && ctx->argument(0).asRegExpObject()) { + if (ctx->argumentCount == 1 || ctx->argument(1).isUndefined()) + return ctx->argument(0); + } + + return construct(ctx); +} + +void RegExpPrototype::init(ExecutionContext *ctx, const Value &ctor) +{ + ctor.objectValue()->defineReadonlyProperty(ctx->engine->id_prototype, Value::fromObject(this)); + ctor.objectValue()->defineReadonlyProperty(ctx->engine->id_length, Value::fromInt32(2)); + defineDefaultProperty(ctx, QStringLiteral("constructor"), ctor); + defineDefaultProperty(ctx, QStringLiteral("exec"), method_exec, 1); + defineDefaultProperty(ctx, QStringLiteral("test"), method_test, 1); + defineDefaultProperty(ctx, QStringLiteral("toString"), method_toString, 0); + defineDefaultProperty(ctx, QStringLiteral("compile"), method_compile, 2); +} + +Value RegExpPrototype::method_exec(ExecutionContext *ctx) +{ + RegExpObject *r = ctx->thisObject.asRegExpObject(); + if (!r) + ctx->throwTypeError(); + + Value arg = ctx->argument(0); + arg = __qmljs_to_string(arg, ctx); + QString s = arg.stringValue()->toQString(); + + int offset = r->global ? r->lastIndexProperty->value.toInt32(ctx) : 0; + if (offset < 0 || offset > s.length()) { + r->lastIndexProperty->value = Value::fromInt32(0); + return Value::nullValue(); + } + + uint* matchOffsets = (uint*)alloca(r->value->captureCount() * 2 * sizeof(uint)); + int result = r->value->match(s, offset, matchOffsets); + if (result == -1) { + r->lastIndexProperty->value = Value::fromInt32(0); + return Value::nullValue(); + } + + // fill in result data + ArrayObject *array = ctx->engine->newArrayObject(ctx)->asArrayObject(); + for (int i = 0; i < r->value->captureCount(); ++i) { + int start = matchOffsets[i * 2]; + int end = matchOffsets[i * 2 + 1]; + Value entry = Value::undefinedValue(); + if (start != -1 && end != -1) + entry = Value::fromString(ctx, s.mid(start, end - start)); + array->array.push_back(entry); + } + + array->__put__(ctx, QLatin1String("index"), Value::fromInt32(result)); + array->__put__(ctx, QLatin1String("input"), arg); + + if (r->global) + r->lastIndexProperty->value = Value::fromInt32(matchOffsets[1]); + + return Value::fromObject(array); +} + +Value RegExpPrototype::method_test(ExecutionContext *ctx) +{ + Value r = method_exec(ctx); + return Value::fromBoolean(!r.isNull()); +} + +Value RegExpPrototype::method_toString(ExecutionContext *ctx) +{ + RegExpObject *r = ctx->thisObject.asRegExpObject(); + if (!r) + ctx->throwTypeError(); + + QString result = QChar('/') + r->value->pattern(); + result += QChar('/'); + // ### 'g' option missing + if (r->value->ignoreCase()) + result += QChar('i'); + if (r->value->multiLine()) + result += QChar('m'); + return Value::fromString(ctx, result); +} + +Value RegExpPrototype::method_compile(ExecutionContext *ctx) +{ + RegExpObject *r = ctx->thisObject.asRegExpObject(); + if (!r) + ctx->throwTypeError(); + + RegExpObject *re = ctx->engine->regExpCtor.asFunctionObject()->construct(ctx, ctx->arguments, ctx->argumentCount).asRegExpObject(); + + r->value = re->value; + r->global = re->global; + return Value::undefinedValue(); +} + diff --git a/src/v4/qv4regexpobject.h b/src/v4/qv4regexpobject.h new file mode 100644 index 0000000000..50241f2daf --- /dev/null +++ b/src/v4/qv4regexpobject.h @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4REGEXPOBJECT_H +#define QV4REGEXPOBJECT_H + +#include "qmljs_runtime.h" +#include "qmljs_engine.h" +#include "qmljs_environment.h" +#include "qv4functionobject.h" +#include "qv4array.h" +#include "qv4string.h" +#include "qv4codegen_p.h" +#include "qv4isel_p.h" +#include "qv4managed.h" +#include "qv4propertydescriptor.h" +#include "qv4propertytable.h" +#include "qv4objectiterator.h" +#include "qv4regexp.h" + +#include <QtCore/QString> +#include <QtCore/QHash> +#include <QtCore/QScopedPointer> +#include <cstdio> +#include <cassert> + +namespace QQmlJS { + +namespace VM { + +struct RegExpObject: Object { + RefPtr<RegExp> value; + PropertyDescriptor *lastIndexProperty; + bool global; + RegExpObject(ExecutionEngine *engine, PassRefPtr<RegExp> value, bool global); +}; + + +struct RegExpCtor: FunctionObject +{ + RegExpCtor(ExecutionContext *scope); + + virtual Value construct(ExecutionContext *ctx); + virtual Value call(ExecutionContext *ctx); +}; + +struct RegExpPrototype: RegExpObject +{ + RegExpPrototype(ExecutionEngine* engine): RegExpObject(engine, RegExp::create(0, QString()), false) {} + void init(ExecutionContext *ctx, const Value &ctor); + + static Value method_exec(ExecutionContext *ctx); + static Value method_test(ExecutionContext *ctx); + static Value method_toString(ExecutionContext *ctx); + static Value method_compile(ExecutionContext *ctx); +}; + + + +} // namespace VM +} // namespace QQmlJS + +#endif // QMLJS_OBJECTS_H diff --git a/src/v4/qv4string.cpp b/src/v4/qv4string.cpp new file mode 100644 index 0000000000..6e0c14d108 --- /dev/null +++ b/src/v4/qv4string.cpp @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4string.h" +#include "qmljs_runtime.h" +#include <QtCore/QHash> + +namespace QQmlJS { +namespace VM { + +static uint toArrayIndex(const QChar *ch, const QChar *end) +{ + uint i = ch->unicode() - '0'; + if (i > 9) + return String::InvalidArrayIndex; + ++ch; + // reject "01", "001", ... + if (i == 0 && ch != end) + return String::InvalidArrayIndex; + + while (ch < end) { + uint x = ch->unicode() - '0'; + if (x > 9) + return String::InvalidArrayIndex; + uint n = i*10 + x; + if (n < i) + // overflow + return String::InvalidArrayIndex; + i = n; + ++ch; + } + return i; +} + +uint String::asArrayIndexSlow() const +{ + if (stringHash < LargestHashedArrayIndex) + return stringHash; + + const QChar *ch = _text.constData(); + const QChar *end = ch + _text.length(); + return toArrayIndex(ch, end); +} + +uint String::toUInt(bool *ok) const +{ + *ok = true; + + if (stringHash == InvalidHashValue) + createHashValue(); + if (stringHash < LargestHashedArrayIndex) + return stringHash; + + double d = __qmljs_string_to_number(this); + uint l = (uint)d; + if (d == l) + return l; + *ok = false; + return UINT_MAX; +} + +void String::createHashValue() const +{ + const QChar *ch = _text.constData(); + const QChar *end = ch + _text.length(); + + // array indices get their number as hash value, for large numbers we set to INT_MAX + stringHash = toArrayIndex(ch, end); + if (stringHash < UINT_MAX) { + if (stringHash > INT_MAX) + stringHash = INT_MAX; + return; + } + + uint h = 0xffffffff; + while (ch < end) { + h = 31 * h + ch->unicode(); + ++ch; + } + + // set highest bit to mark it as a non number + stringHash = h | 0xf0000000; +} + +} +} diff --git a/src/v4/qv4string.h b/src/v4/qv4string.h new file mode 100644 index 0000000000..34e241218c --- /dev/null +++ b/src/v4/qv4string.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4STRING_H +#define QV4STRING_H + +#include <QtCore/qstring.h> +#include <qv4managed.h> + +namespace QQmlJS { +namespace VM { + +struct String : public Managed { + String(const QString &text) + : _text(text) { type = Type_String; stringHash = InvalidHashValue; } + + inline bool isEqualTo(const String *other) const { + if (this == other) + return true; + else if (other && hashValue() == other->hashValue()) + return toQString() == other->toQString(); + return false; + } + + inline const QString &toQString() const { + return _text; + } + + inline unsigned hashValue() const { + if (stringHash == InvalidHashValue) + createHashValue(); + + return stringHash; + } + enum { + InvalidArrayIndex = 0xffffffff, + LargestHashedArrayIndex = 0x7fffffff, + InvalidHashValue = 0xffffffff + }; + uint asArrayIndex() const { + if (stringHash == InvalidHashValue) + createHashValue(); + if (stringHash > LargestHashedArrayIndex) + return InvalidArrayIndex; + if (stringHash < LargestHashedArrayIndex) + return stringHash; + return asArrayIndexSlow(); + } + uint asArrayIndexSlow() const; + uint toUInt(bool *ok) const; + +private: + void createHashValue() const; + + QString _text; +}; + +} +} + +#endif diff --git a/src/v4/qv4stringobject.cpp b/src/v4/qv4stringobject.cpp new file mode 100644 index 0000000000..81968cc55a --- /dev/null +++ b/src/v4/qv4stringobject.cpp @@ -0,0 +1,722 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "qv4stringobject.h" +#include "qv4regexpobject.h" +#include "qv4objectproto.h" +#include "qv4mm.h" +#include <QtCore/qnumeric.h> +#include <QtCore/qmath.h> +#include <QtCore/QDateTime> +#include <QtCore/QStringList> +#include <QtCore/QDebug> +#include <cmath> +#include <qmath.h> +#include <qnumeric.h> +#include <cassert> + +#include <private/qqmljsengine_p.h> +#include <private/qqmljslexer_p.h> +#include <private/qqmljsparser_p.h> +#include <private/qqmljsast_p.h> +#include <qv4ir_p.h> +#include <qv4codegen_p.h> +#include <qv4isel_masm_p.h> + +#ifndef Q_WS_WIN +# include <time.h> +# ifndef Q_OS_VXWORKS +# include <sys/time.h> +# else +# include "qplatformdefs.h" +# endif +#else +# include <windows.h> +#endif + +using namespace QQmlJS::VM; + +StringObject::StringObject(ExecutionContext *ctx, const Value &value) + : value(value) +{ + type = Type_StringObject; + + tmpProperty.type = PropertyDescriptor::Data; + tmpProperty.enumberable = PropertyDescriptor::Enabled; + tmpProperty.writable = PropertyDescriptor::Disabled; + tmpProperty.configurable = PropertyDescriptor::Disabled; + tmpProperty.value = Value::undefinedValue(); + + assert(value.isString()); + defineReadonlyProperty(ctx->engine->id_length, Value::fromUInt32(value.stringValue()->toQString().length())); +} + +PropertyDescriptor *StringObject::getIndex(ExecutionContext *ctx, uint index) +{ + QString str = value.stringValue()->toQString(); + if (index >= (uint)str.length()) + return 0; + String *result = ctx->engine->newString(str.mid(index, 1)); + tmpProperty.value = Value::fromString(result); + return &tmpProperty; +} + +void StringObject::markObjects() +{ + value.stringValue()->mark(); + Object::markObjects(); +} + + +StringCtor::StringCtor(ExecutionContext *scope) + : FunctionObject(scope) +{ +} + +Value StringCtor::construct(ExecutionContext *ctx) +{ + Value value; + if (ctx->argumentCount) + value = Value::fromString(ctx->argument(0).toString(ctx)); + else + value = Value::fromString(ctx, QString()); + return Value::fromObject(ctx->engine->newStringObject(ctx, value)); +} + +Value StringCtor::call(ExecutionContext *ctx) +{ + Value value; + if (ctx->argumentCount) + value = Value::fromString(ctx->argument(0).toString(ctx)); + else + value = Value::fromString(ctx, QString()); + return value; +} + +void StringPrototype::init(ExecutionContext *ctx, const Value &ctor) +{ + ctor.objectValue()->defineReadonlyProperty(ctx->engine->id_prototype, Value::fromObject(this)); + ctor.objectValue()->defineReadonlyProperty(ctx->engine->id_length, Value::fromInt32(1)); + ctor.objectValue()->defineDefaultProperty(ctx, QStringLiteral("fromCharCode"), method_fromCharCode, 1); + + defineDefaultProperty(ctx, QStringLiteral("constructor"), ctor); + defineDefaultProperty(ctx, QStringLiteral("toString"), method_toString); + defineDefaultProperty(ctx, QStringLiteral("valueOf"), method_toString); // valueOf and toString are identical + defineDefaultProperty(ctx, QStringLiteral("charAt"), method_charAt, 1); + defineDefaultProperty(ctx, QStringLiteral("charCodeAt"), method_charCodeAt, 1); + defineDefaultProperty(ctx, QStringLiteral("concat"), method_concat, 1); + defineDefaultProperty(ctx, QStringLiteral("indexOf"), method_indexOf, 1); + defineDefaultProperty(ctx, QStringLiteral("lastIndexOf"), method_lastIndexOf, 1); + defineDefaultProperty(ctx, QStringLiteral("localeCompare"), method_localeCompare, 1); + defineDefaultProperty(ctx, QStringLiteral("match"), method_match, 1); + defineDefaultProperty(ctx, QStringLiteral("replace"), method_replace, 2); + defineDefaultProperty(ctx, QStringLiteral("search"), method_search, 1); + defineDefaultProperty(ctx, QStringLiteral("slice"), method_slice, 2); + defineDefaultProperty(ctx, QStringLiteral("split"), method_split, 2); + defineDefaultProperty(ctx, QStringLiteral("substr"), method_substr, 2); + defineDefaultProperty(ctx, QStringLiteral("substring"), method_substring, 2); + defineDefaultProperty(ctx, QStringLiteral("toLowerCase"), method_toLowerCase); + defineDefaultProperty(ctx, QStringLiteral("toLocaleLowerCase"), method_toLocaleLowerCase); + defineDefaultProperty(ctx, QStringLiteral("toUpperCase"), method_toUpperCase); + defineDefaultProperty(ctx, QStringLiteral("toLocaleUpperCase"), method_toLocaleUpperCase); + defineDefaultProperty(ctx, QStringLiteral("trim"), method_trim); +} + +static QString getThisString(ExecutionContext *ctx) +{ + String* str = 0; + Value thisObject = ctx->thisObject; + if (StringObject *thisString = thisObject.asStringObject()) + str = thisString->value.stringValue(); + else if (thisObject.isUndefined() || thisObject.isNull()) + ctx->throwTypeError(); + else + str = ctx->thisObject.toString(ctx); + return str->toQString(); +} + +static QString getThisString(ExecutionContext *parentCtx, Value thisObject) +{ + if (thisObject.isString()) + return thisObject.stringValue()->toQString(); + + String* str = 0; + if (StringObject *thisString = thisObject.asStringObject()) + str = thisString->value.stringValue(); + else if (thisObject.isUndefined() || thisObject.isNull()) + parentCtx->throwTypeError(); + else + str = thisObject.toString(parentCtx); + return str->toQString(); +} + +Value StringPrototype::method_toString(ExecutionContext *parentCtx, Value thisObject, Value *, int) +{ + if (thisObject.isString()) + return thisObject; + + StringObject *o = thisObject.asStringObject(); + if (!o) + parentCtx->throwTypeError(); + return o->value; +} + +Value StringPrototype::method_charAt(ExecutionContext *parentCtx, Value thisObject, Value *argv, int argc) +{ + const QString str = getThisString(parentCtx, thisObject); + + int pos = 0; + if (argc > 0) + pos = (int) argv[0].toInteger(parentCtx); + + QString result; + if (pos >= 0 && pos < str.length()) + result += str.at(pos); + + return Value::fromString(parentCtx, result); +} + +Value StringPrototype::method_charCodeAt(ExecutionContext *parentCtx, Value thisObject, Value *argv, int argc) +{ + const QString str = getThisString(parentCtx, thisObject); + + int pos = 0; + if (argc > 0) + pos = (int) argv[0].toInteger(parentCtx); + + + if (pos >= 0 && pos < str.length()) + return Value::fromInt32(str.at(pos).unicode()); + + return Value::fromDouble(qSNaN()); +} + +Value StringPrototype::method_concat(ExecutionContext *parentCtx, Value thisObject, Value *argv, int argc) +{ + QString value = getThisString(parentCtx, thisObject); + + for (int i = 0; i < argc; ++i) { + Value v = __qmljs_to_string(argv[i], parentCtx); + assert(v.isString()); + value += v.stringValue()->toQString(); + } + + return Value::fromString(parentCtx, value); +} + +Value StringPrototype::method_indexOf(ExecutionContext *parentCtx, Value thisObject, Value *argv, int argc) +{ + QString value = getThisString(parentCtx, thisObject); + + QString searchString; + if (argc) + searchString = argv[0].toString(parentCtx)->toQString(); + + int pos = 0; + if (argc > 1) + pos = (int) argv[1].toInteger(parentCtx); + + int index = -1; + if (! value.isEmpty()) + index = value.indexOf(searchString, qMin(qMax(pos, 0), value.length())); + + return Value::fromDouble(index); +} + +Value StringPrototype::method_lastIndexOf(ExecutionContext *parentCtx, Value thisObject, Value *argv, int argc) +{ + const QString value = getThisString(parentCtx, thisObject); + + QString searchString; + if (argc) { + Value v = __qmljs_to_string(argv[0], parentCtx); + searchString = v.stringValue()->toQString(); + } + + Value posArg = argc > 1 ? argv[1] : Value::undefinedValue(); + double position = __qmljs_to_number(posArg, parentCtx); + if (std::isnan(position)) + position = +qInf(); + else + position = trunc(position); + + int pos = trunc(qMin(qMax(position, 0.0), double(value.length()))); + if (!searchString.isEmpty() && pos == value.length()) + --pos; + int index = value.lastIndexOf(searchString, pos); + return Value::fromDouble(index); +} + +Value StringPrototype::method_localeCompare(ExecutionContext *parentCtx, Value thisObject, Value *argv, int argc) +{ + const QString value = getThisString(parentCtx, thisObject); + const QString that = (argc ? argv[0] : Value::undefinedValue()).toString(parentCtx)->toQString(); + return Value::fromDouble(QString::localeAwareCompare(value, that)); +} + +Value StringPrototype::method_match(ExecutionContext *parentCtx, Value thisObject, Value *argv, int argc) +{ + if (thisObject.isUndefined() || thisObject.isNull()) + __qmljs_throw_type_error(parentCtx); + + String *s = thisObject.toString(parentCtx); + + Value regexp = argc ? argv[0] : Value::undefinedValue(); + RegExpObject *rx = regexp.asRegExpObject(); + if (!rx) + rx = parentCtx->engine->regExpCtor.asFunctionObject()->construct(parentCtx, ®exp, 1).asRegExpObject(); + + if (!rx) + // ### CHECK + __qmljs_throw_type_error(parentCtx); + + bool global = rx->global; + + // ### use the standard builtin function, not the one that might be redefined in the proto + FunctionObject *exec = parentCtx->engine->regExpPrototype->__get__(parentCtx, parentCtx->engine->identifier(QStringLiteral("exec")), 0).asFunctionObject(); + + Value arg = Value::fromString(s); + if (!global) + return exec->call(parentCtx, Value::fromObject(rx), &arg, 1); + + String *lastIndex = parentCtx->engine->identifier(QStringLiteral("lastIndex")); + rx->__put__(parentCtx, lastIndex, Value::fromInt32(0)); + ArrayObject *a = parentCtx->engine->newArrayObject(parentCtx); + + double previousLastIndex = 0; + uint n = 0; + while (1) { + Value result = exec->call(parentCtx, Value::fromObject(rx), &arg, 1); + if (result.isNull()) + break; + assert(result.isObject()); + double thisIndex = rx->__get__(parentCtx, lastIndex, 0).toInteger(parentCtx); + if (previousLastIndex == thisIndex) { + previousLastIndex = thisIndex + 1; + rx->__put__(parentCtx, lastIndex, Value::fromDouble(previousLastIndex)); + } else { + previousLastIndex = thisIndex; + } + Value matchStr = result.objectValue()->__get__(parentCtx, (uint)0, (bool *)0); + a->array.set(n, matchStr); + ++n; + } + if (!n) + return Value::nullValue(); + + return Value::fromObject(a); + +} + +static QString makeReplacementString(const QString &input, const QString& replaceValue, uint* matchOffsets, int captureCount) +{ + QString result; + result.reserve(replaceValue.length()); + for (int i = 0; i < replaceValue.length(); ++i) { + if (replaceValue.at(i) == QLatin1Char('$') && i < replaceValue.length() - 1) { + char ch = replaceValue.at(++i).toLatin1(); + uint substStart = JSC::Yarr::offsetNoMatch; + uint substEnd = JSC::Yarr::offsetNoMatch; + if (ch == '$') { + result += ch; + continue; + } else if (ch == '&') { + substStart = matchOffsets[0]; + substEnd = matchOffsets[1]; + } else if (ch == '`') { + substStart = 0; + substEnd = matchOffsets[0]; + } else if (ch == '\'') { + substStart = matchOffsets[1]; + substEnd = input.length(); + } else if (ch >= '1' && ch <= '9') { + char capture = ch - '0'; + if (capture > 0 && capture < captureCount) { + substStart = matchOffsets[capture * 2]; + substEnd = matchOffsets[capture * 2 + 1]; + } + } else if (ch == '0' && i < replaceValue.length() - 1) { + int capture = (ch - '0') * 10; + ch = replaceValue.at(++i).toLatin1(); + capture += ch - '0'; + if (capture > 0 && capture < captureCount) { + substStart = matchOffsets[capture * 2]; + substEnd = matchOffsets[capture * 2 + 1]; + } + } + if (substStart != JSC::Yarr::offsetNoMatch && substEnd != JSC::Yarr::offsetNoMatch) + result += input.midRef(substStart, substEnd - substStart); + } else { + result += replaceValue.at(i); + } + } + return result; +} + +Value StringPrototype::method_replace(ExecutionContext *ctx) +{ + QString string; + if (StringObject *thisString = ctx->thisObject.asStringObject()) + string = thisString->value.stringValue()->toQString(); + else + string = ctx->thisObject.toString(ctx)->toQString(); + + int numCaptures = 0; + QVarLengthArray<uint, 16> matchOffsets; + int numStringMatches = 0; + + Value searchValue = ctx->argument(0); + RegExpObject *regExp = searchValue.asRegExpObject(); + if (regExp) { + uint offset = 0; + while (true) { + int oldSize = matchOffsets.size(); + matchOffsets.resize(matchOffsets.size() + regExp->value->captureCount() * 2); + if (regExp->value->match(string, offset, matchOffsets.data() + oldSize) == JSC::Yarr::offsetNoMatch) { + matchOffsets.resize(oldSize); + break; + } + if (!regExp->global) + break; + offset = qMax(offset + 1, matchOffsets[oldSize + 1]); + } + if (regExp->global) + regExp->lastIndexProperty->value = Value::fromUInt32(0); + numStringMatches = matchOffsets.size() / (regExp->value->captureCount() * 2); + numCaptures = regExp->value->captureCount(); + } else { + numCaptures = 1; + QString searchString = searchValue.toString(ctx)->toQString(); + int idx = string.indexOf(searchString); + if (idx != -1) { + numStringMatches = 1; + matchOffsets.resize(2); + matchOffsets[0] = idx; + matchOffsets[1] = idx + searchString.length(); + } + } + + QString result = string; + Value replaceValue = ctx->argument(1); + if (FunctionObject* searchCallback = replaceValue.asFunctionObject()) { + int replacementDelta = 0; + int argc = numCaptures + 2; + Value *args = (Value*)alloca((numCaptures + 2) * sizeof(Value)); + for (int i = 0; i < numStringMatches; ++i) { + for (int k = 0; k < numCaptures; ++k) { + int idx = (i * numCaptures + k) * 2; + uint start = matchOffsets[idx]; + uint end = matchOffsets[idx + 1]; + Value entry = Value::undefinedValue(); + if (start != JSC::Yarr::offsetNoMatch && end != JSC::Yarr::offsetNoMatch) + entry = Value::fromString(ctx, string.mid(start, end - start)); + args[k] = entry; + } + uint matchStart = matchOffsets[i * numCaptures * 2]; + uint matchEnd = matchOffsets[i * numCaptures * 2 + 1]; + args[numCaptures] = Value::fromUInt32(matchStart); + args[numCaptures + 1] = Value::fromString(ctx, string); + Value replacement = searchCallback->call(ctx, Value::undefinedValue(), args, argc); + QString replacementString = replacement.toString(ctx)->toQString(); + result.replace(replacementDelta + matchStart, matchEnd - matchStart, replacementString); + replacementDelta += replacementString.length() - matchEnd + matchStart; + } + } else { + QString newString = replaceValue.toString(ctx)->toQString(); + int replacementDelta = 0; + + for (int i = 0; i < numStringMatches; ++i) { + int baseIndex = i * numCaptures * 2; + uint matchStart = matchOffsets[baseIndex]; + uint matchEnd = matchOffsets[baseIndex + 1]; + if (matchStart == JSC::Yarr::offsetNoMatch) + continue; + + QString replacement = makeReplacementString(string, newString, matchOffsets.data() + baseIndex, numCaptures); + result.replace(replacementDelta + matchStart, matchEnd - matchStart, replacement); + replacementDelta += replacement.length() - matchEnd + matchStart; + } + } + + return Value::fromString(ctx, result); +} + +Value StringPrototype::method_search(ExecutionContext *ctx) +{ + QString string; + if (StringObject *thisString = ctx->thisObject.asStringObject()) + string = thisString->value.stringValue()->toQString(); + else + string = ctx->thisObject.toString(ctx)->toQString(); + + Value regExpValue = ctx->argument(0); + RegExpObject *regExp = regExpValue.asRegExpObject(); + if (!regExp) { + regExpValue = ctx->engine->regExpCtor.asFunctionObject()->construct(ctx, ®ExpValue, 1); + regExp = regExpValue.asRegExpObject(); + } + uint* matchOffsets = (uint*)alloca(regExp->value->captureCount() * 2 * sizeof(uint)); + uint result = regExp->value->match(string, /*offset*/0, matchOffsets); + if (result == JSC::Yarr::offsetNoMatch) + return Value::fromInt32(-1); + return Value::fromUInt32(result); +} + +Value StringPrototype::method_slice(ExecutionContext *ctx) +{ + const QString text = getThisString(ctx); + const double length = text.length(); + + double start = ctx->argument(0).toInteger(ctx); + double end = ctx->argument(1).isUndefined() + ? length : ctx->argument(1).toInteger(ctx); + + if (start < 0) + start = qMax(length + start, 0.); + else + start = qMin(start, length); + + if (end < 0) + end = qMax(length + end, 0.); + else + end = qMin(end, length); + + const int intStart = int(start); + const int intEnd = int(end); + + int count = qMax(0, intEnd - intStart); + return Value::fromString(ctx, text.mid(intStart, count)); +} + +Value StringPrototype::method_split(ExecutionContext *ctx) +{ + QString text; + if (StringObject *thisObject = ctx->thisObject.asStringObject()) + text = thisObject->value.stringValue()->toQString(); + else + text = ctx->thisObject.toString(ctx)->toQString(); + + Value separatorValue = ctx->argumentCount > 0 ? ctx->argument(0) : Value::undefinedValue(); + Value limitValue = ctx->argumentCount > 1 ? ctx->argument(1) : Value::undefinedValue(); + + ArrayObject* array = ctx->engine->newArrayObject(ctx); + Value result = Value::fromObject(array); + + if (separatorValue.isUndefined()) { + if (limitValue.isUndefined()) { + array->array.push_back(Value::fromString(ctx, text)); + return result; + } + return Value::fromString(ctx, text.left(limitValue.toInteger(ctx))); + } + + uint limit = limitValue.isUndefined() ? UINT_MAX : limitValue.toUInt32(ctx); + + if (limit == 0) + return result; + + if (RegExpObject* re = separatorValue.asRegExpObject()) { + if (re->value->pattern().isEmpty()) { + re = 0; + separatorValue = Value::fromString(ctx, QString()); + } + } + + if (RegExpObject* re = separatorValue.asRegExpObject()) { + uint offset = 0; + uint* matchOffsets = (uint*)alloca(re->value->captureCount() * 2 * sizeof(uint)); + while (true) { + uint result = re->value->match(text, offset, matchOffsets); + if (result == JSC::Yarr::offsetNoMatch) + break; + + array->array.push_back(Value::fromString(ctx, text.mid(offset, matchOffsets[0] - offset))); + offset = qMax(offset + 1, matchOffsets[1]); + + if (array->array.length() >= limit) + break; + + for (int i = 1; i < re->value->captureCount(); ++i) { + uint start = matchOffsets[i * 2]; + uint end = matchOffsets[i * 2 + 1]; + array->array.push_back(Value::fromString(ctx, text.mid(start, end - start))); + if (array->array.length() >= limit) + break; + } + } + if (array->array.length() < limit) + array->array.push_back(Value::fromString(ctx, text.mid(offset))); + } else { + QString separator = separatorValue.toString(ctx)->toQString(); + if (separator.isEmpty()) { + for (uint i = 0; i < qMin(limit, uint(text.length())); ++i) + array->array.push_back(Value::fromString(ctx, text.mid(i, 1))); + return result; + } + + int start = 0; + int end; + while ((end = text.indexOf(separator, start)) != -1) { + array->array.push_back(Value::fromString(ctx, text.mid(start, end - start))); + start = end + separator.size(); + if (array->array.length() >= limit) + break; + } + if (array->array.length() < limit && start != -1) + array->array.push_back(Value::fromString(ctx, text.mid(start))); + } + return result; +} + +Value StringPrototype::method_substr(ExecutionContext *parentCtx, Value thisObject, Value *argv, int argc) +{ + const QString value = getThisString(parentCtx, thisObject); + + double start = 0; + if (argc > 0) + start = argv[0].toInteger(parentCtx); + + double length = +qInf(); + if (argc > 1) + length = argv[1].toInteger(parentCtx); + + double count = value.length(); + if (start < 0) + start = qMax(count + start, 0.0); + + length = qMin(qMax(length, 0.0), count - start); + + qint32 x = Value::toInt32(start); + qint32 y = Value::toInt32(length); + return Value::fromString(parentCtx, value.mid(x, y)); +} + +Value StringPrototype::method_substring(ExecutionContext *parentCtx, Value thisObject, Value *argv, int argc) +{ + QString value = getThisString(parentCtx, thisObject); + int length = value.length(); + + double start = 0; + double end = length; + + if (argc > 0) + start = argv[0].toInteger(parentCtx); + + Value endValue = argc > 1 ? argv[1] : Value::undefinedValue(); + if (!endValue.isUndefined()) + end = endValue.toInteger(parentCtx); + + if (std::isnan(start) || start < 0) + start = 0; + + if (std::isnan(end) || end < 0) + end = 0; + + if (start > length) + start = length; + + if (end > length) + end = length; + + if (start > end) { + double was = start; + start = end; + end = was; + } + + qint32 x = (int)start; + qint32 y = (int)(end - start); + return Value::fromString(parentCtx, value.mid(x, y)); +} + +Value StringPrototype::method_toLowerCase(ExecutionContext *ctx) +{ + QString value = getThisString(ctx); + return Value::fromString(ctx, value.toLower()); +} + +Value StringPrototype::method_toLocaleLowerCase(ExecutionContext *ctx) +{ + return method_toLowerCase(ctx); +} + +Value StringPrototype::method_toUpperCase(ExecutionContext *ctx) +{ + QString value = getThisString(ctx); + return Value::fromString(ctx, value.toUpper()); +} + +Value StringPrototype::method_toLocaleUpperCase(ExecutionContext *ctx) +{ + return method_toUpperCase(ctx); +} + +Value StringPrototype::method_fromCharCode(ExecutionContext *parentCtx, Value, Value *argv, int argc) +{ + QString str(argc, Qt::Uninitialized); + QChar *ch = str.data(); + for (int i = 0; i < argc; ++i) { + *ch = QChar(argv[i].toUInt16(parentCtx)); + ++ch; + } + return Value::fromString(parentCtx, str); +} + +Value StringPrototype::method_trim(ExecutionContext *ctx) +{ + if (ctx->thisObject.isNull() || ctx->thisObject.isUndefined()) + __qmljs_throw_type_error(ctx); + + QString s = __qmljs_to_string(ctx->thisObject, ctx).stringValue()->toQString(); + const QChar *chars = s.constData(); + int start, end; + for (start = 0; start < s.length(); ++start) { + if (!chars[start].isSpace() && chars[start].unicode() != 0xfeff) + break; + } + for (end = s.length() - 1; end >= start; --end) { + if (!chars[end].isSpace() && chars[end].unicode() != 0xfeff) + break; + } + + return Value::fromString(ctx, QString(chars + start, end - start + 1)); +} diff --git a/src/v4/qv4stringobject.h b/src/v4/qv4stringobject.h new file mode 100644 index 0000000000..1becd97a46 --- /dev/null +++ b/src/v4/qv4stringobject.h @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4STRINGOBJECT_P_H +#define QV4STRINGOBJECT_P_H + +#include "qv4object.h" +#include "qv4functionobject.h" +#include <QtCore/qnumeric.h> + +namespace QQmlJS { +namespace VM { + +struct StringObject: Object { + Value value; + PropertyDescriptor tmpProperty; + StringObject(ExecutionContext *ctx, const Value &value); + + PropertyDescriptor *getIndex(ExecutionContext *ctx, uint index); + +protected: + virtual void markObjects(); +}; + +struct StringCtor: FunctionObject +{ + StringCtor(ExecutionContext *scope); + + virtual Value construct(ExecutionContext *ctx); + virtual Value call(ExecutionContext *ctx); +}; + +struct StringPrototype: StringObject +{ + StringPrototype(ExecutionContext *ctx): StringObject(ctx, Value::fromString(ctx, QString())) {} + void init(ExecutionContext *ctx, const Value &ctor); + + static Value method_toString(ExecutionContext *parentCtx, Value thisObject, Value *argv, int argc); + static Value method_charAt(ExecutionContext *parentCtx, Value thisObject, Value *argv, int argc); + static Value method_charCodeAt(ExecutionContext *, Value thisObject, Value *argv, int argc); + static Value method_concat(ExecutionContext *parentCtx, Value thisObject, Value *argv, int argc); + static Value method_indexOf(ExecutionContext *parentCtx, Value thisObject, Value *argv, int argc); + static Value method_lastIndexOf(ExecutionContext *parentCtx, Value thisObject, Value *argv, int argc); + static Value method_localeCompare(ExecutionContext *parentCtx, Value thisObject, Value *argv, int argc); + static Value method_match(ExecutionContext *parentCtx, Value thisObject, Value *argv, int argc); + static Value method_replace(ExecutionContext *ctx); + static Value method_search(ExecutionContext *ctx); + static Value method_slice(ExecutionContext *ctx); + static Value method_split(ExecutionContext *ctx); + static Value method_substr(ExecutionContext *parentCtx, Value thisObject, Value *argv, int argc); + static Value method_substring(ExecutionContext *parentCtx, Value thisObject, Value *argv, int argc); + static Value method_toLowerCase(ExecutionContext *ctx); + static Value method_toLocaleLowerCase(ExecutionContext *ctx); + static Value method_toUpperCase(ExecutionContext *ctx); + static Value method_toLocaleUpperCase(ExecutionContext *ctx); + static Value method_fromCharCode(ExecutionContext *parentCtx, Value thisObject, Value *argv, int argc); + static Value method_trim(ExecutionContext *ctx); +}; + +} // end of namespace VM +} // end of namespace QQmlJS + +#endif // QV4ECMAOBJECTS_P_H diff --git a/src/v4/qv4syntaxchecker.cpp b/src/v4/qv4syntaxchecker.cpp new file mode 100644 index 0000000000..fcda486af2 --- /dev/null +++ b/src/v4/qv4syntaxchecker.cpp @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4syntaxchecker_p.h" + +using namespace QQmlJS; + +SyntaxChecker::SyntaxChecker() + : Lexer(&m_engine) + , m_stateStack(128) +{ +} + +void QQmlJS::SyntaxChecker::clearText() +{ + m_code.clear(); + m_tokens.clear(); +} + +void SyntaxChecker::appendText(const QString &text) +{ + m_code += text; +} + +QString SyntaxChecker::text() const +{ + return m_code; +} + +bool SyntaxChecker::canEvaluate() +{ + int yyaction = 0; + int yytoken = -1; + int yytos = -1; + + setCode(m_code, 1); + + m_tokens.clear(); + m_tokens.append(T_FEED_JS_PROGRAM); + + do { + if (++yytos == m_stateStack.size()) + m_stateStack.resize(m_stateStack.size() * 2); + + m_stateStack[yytos] = yyaction; + +again: + if (yytoken == -1 && action_index[yyaction] != -TERMINAL_COUNT) { + if (m_tokens.isEmpty()) + yytoken = lex(); + else + yytoken = m_tokens.takeFirst(); + } + + yyaction = t_action(yyaction, yytoken); + if (yyaction > 0) { + if (yyaction == ACCEPT_STATE) { + --yytos; + return true; + } + yytoken = -1; + } else if (yyaction < 0) { + const int ruleno = -yyaction - 1; + yytos -= rhs[ruleno]; + yyaction = nt_action(m_stateStack[yytos], lhs[ruleno] - TERMINAL_COUNT); + } + } while (yyaction); + + const int errorState = m_stateStack[yytos]; + if (t_action(errorState, T_AUTOMATIC_SEMICOLON) && canInsertAutomaticSemicolon(yytoken)) { + yyaction = errorState; + m_tokens.prepend(yytoken); + yytoken = T_SEMICOLON; + goto again; + } + + if (yytoken != EOF_SYMBOL) + return true; + + return false; +} diff --git a/src/v4/qv4syntaxchecker_p.h b/src/v4/qv4syntaxchecker_p.h new file mode 100644 index 0000000000..38e123762e --- /dev/null +++ b/src/v4/qv4syntaxchecker_p.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the V4VM module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4SYNTAXCHECKER_P_H +#define QV4SYNTAXCHECKER_P_H + +#include <private/qqmljslexer_p.h> +#include <private/qqmljsengine_p.h> + +#include <QtCore/QVector> +#include <QtCore/QString> +#include <QtCore/QList> + +namespace QQmlJS { + +class SyntaxChecker: Lexer +{ +public: + SyntaxChecker(); + + QString text() const; + void clearText(); + void appendText(const QString &text); + + bool canEvaluate(); + +private: + Engine m_engine; + QVector<int> m_stateStack; + QList<int> m_tokens; + QString m_code; +}; + +} // end of QQmlJS namespace + +#endif // QV4SYNTAXCHECKER_P_H diff --git a/src/v4/v4.pro b/src/v4/v4.pro new file mode 100644 index 0000000000..6d990ed234 --- /dev/null +++ b/src/v4/v4.pro @@ -0,0 +1,155 @@ +TARGET = QtV4 +QT_PRIVATE = core-private qmldevtools-private + +CONFIG += internal_module + +LLVM_CONFIG=llvm-config + +OBJECTS_DIR=.obj + +load(qt_build_config) +load(qt_module) + +CONFIG += warn_off + +#win32-msvc*|win32-icc:QMAKE_LFLAGS += /BASE:0x66000000 #TODO ??! + + +# Pick up the qmake variable or environment variable for LLVM_INSTALL_DIR. If either was set, change the LLVM_CONFIG to use that. +isEmpty(LLVM_INSTALL_DIR):LLVM_INSTALL_DIR=$$(LLVM_INSTALL_DIR) +!isEmpty(LLVM_INSTALL_DIR):LLVM_CONFIG=$$LLVM_INSTALL_DIR/bin/llvm-config + +!macx-clang*:LIBS += -rdynamic + +SOURCES += \ + qv4codegen.cpp \ + qv4ir.cpp \ + qmljs_engine.cpp \ + qmljs_environment.cpp \ + qmljs_runtime.cpp \ + qmljs_value.cpp \ + qv4syntaxchecker.cpp \ + qv4isel_masm.cpp \ + llvm_runtime.cpp \ + qv4isel_p.cpp \ + debugging.cpp \ + qv4mm.cpp \ + qv4managed.cpp \ + qv4array.cpp \ + qv4arrayobject.cpp \ + qv4argumentsobject.cpp \ + qv4booleanobject.cpp \ + qv4dateobject.cpp \ + qv4errorobject.cpp \ + qv4functionobject.cpp \ + qv4globalobject.cpp \ + qv4jsonobject.cpp \ + qv4mathobject.cpp \ + qv4numberobject.cpp \ + qv4object.cpp \ + qv4objectproto.cpp \ + qv4regexpobject.cpp \ + qv4stringobject.cpp \ + qv4string.cpp \ + qv4objectiterator.cpp \ + qv4regexp.cpp + +HEADERS += \ + qv4global.h \ + qv4codegen_p.h \ + qv4ir_p.h \ + qmljs_engine.h \ + qmljs_environment.h \ + qmljs_runtime.h \ + qmljs_math.h \ + qmljs_value.h \ + qv4syntaxchecker_p.h \ + qv4isel_masm_p.h \ + qv4isel_p.h \ + qv4isel_util_p.h \ + debugging.h \ + qv4mm.h \ + qv4managed.h \ + qv4array.h \ + qv4arrayobject.h \ + qv4argumentsobject.h \ + qv4booleanobject.h \ + qv4dateobject.h \ + qv4errorobject.h \ + qv4functionobject.h \ + qv4globalobject.h \ + qv4jsonobject.h \ + qv4mathobject.h \ + qv4numberobject.h \ + qv4object.h \ + qv4objectproto.h \ + qv4regexpobject.h \ + qv4stringobject.h \ + qv4string.h \ + qv4propertydescriptor.h \ + qv4propertytable.h \ + qv4objectiterator.h \ + qv4regexp.h + +llvm { + +SOURCES += \ + qv4isel_llvm.cpp + +HEADERS += \ + qv4isel_llvm_p.h \ + qv4_llvm_p.h + +LLVM_RUNTIME_BC = $$PWD/llvm_runtime.bc +DEFINES += LLVM_RUNTIME="\"\\\"$$LLVM_RUNTIME_BC\\\"\"" + +INCLUDEPATH += \ + $$system($$LLVM_CONFIG --includedir) + +QMAKE_CXXFLAGS += $$system($$LLVM_CONFIG --cppflags) -fvisibility-inlines-hidden +QMAKE_CXXFLAGS -= -pedantic +QMAKE_CXXFLAGS -= -Wcovered-switch-default + +LIBS += \ + $$system($$LLVM_CONFIG --ldflags) \ + $$system($$LLVM_CONFIG --libs core jit bitreader linker ipo target x86 arm native) + +QMAKE_EXTRA_TARGETS += gen_llvm_runtime + +GEN_LLVM_RUNTIME_FLAGS = $$system($$LLVM_CONFIG --cppflags) +GEN_LLVM_RUNTIME_FLAGS -= -pedantic + +gen_llvm_runtime.target = llvm_runtime +gen_llvm_runtime.commands = clang -O2 -emit-llvm -c $(INCPATH) $$GEN_LLVM_RUNTIME_FLAGS -DQMLJS_LLVM_RUNTIME llvm_runtime.cpp -o $$LLVM_RUNTIME_BC + + +} else { + +DEFINES += QMLJS_NO_LLVM + +} + +# Use SSE2 floating point math on 32 bit instead of the default +# 387 to make test results pass on 32 and on 64 bit builds. +linux-g++*:isEqual(QT_ARCH,i386) { + QMAKE_CFLAGS += -march=pentium4 -msse2 -mfpmath=sse + QMAKE_CXXFLAGS += -march=pentium4 -msse2 -mfpmath=sse +} + +TESTSCRIPT=$$PWD/../../tests/test262.py +V4CMD = $$OUT_PWD/../tools/v4 + +checktarget.target = check +checktarget.commands = python $$TESTSCRIPT --command=$$V4CMD --parallel --with-test-expectations --update-expectations +checktarget.depends = all +QMAKE_EXTRA_TARGETS += checktarget + +checkmothtarget.target = check-interpreter +checkmothtarget.commands = python $$TESTSCRIPT --command=\"$$V4CMD --interpret\" --parallel --with-test-expectations +checkmothtarget.depends = all +QMAKE_EXTRA_TARGETS += checkmothtarget + + +include(moth/moth.pri) +include(../3rdparty/masm/masm.pri) +include(../3rdparty/double-conversion/double-conversion.pri) |