Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions JSTests/stress/arrow-function-captured-arguments-aliased.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
function createOptAll(count) {
count = 0;

return () => {
arguments[0]++;

return count;
};
}

function createOpt500(count) {
count = 0;

return () => {
arguments[0]++;

if (arguments[0] < 500)
return arguments[0];

return count;
};
}

function createOpt2000(count) {
count = 0;

return () => {
arguments[0]++;

if (arguments[0] < 2000)
return arguments[0];

return count;
};
}

function createOpt5000(count) {
count = 0;

return () => {
arguments[0]++;

if (arguments[0] < 5000)
return arguments[0];

return count;
};
}

function main() {
const testCount = 10000;
const createOptFuncs = [createOptAll, createOpt500, createOpt2000, createOpt5000];
for (createOptFunc of createOptFuncs) {
const opt = createOptFunc(0);
for (let i = 0; i < testCount; i++) {
opt();
}

const expectedResult = testCount+1;
let actualResult = opt();
if (actualResult != expectedResult)
print("Expected " + expectedResult + ", got " + actualResult);
}
}

main();
7 changes: 6 additions & 1 deletion Source/JavaScriptCore/bytecode/CodeBlock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -601,7 +601,12 @@ bool CodeBlock::finishCreation(VM& vm, ScriptExecutable* ownerExecutable, Unlink
ConcurrentJSLocker locker(symbolTable->m_lock);
auto iter = symbolTable->find(locker, ident.impl());
ASSERT(iter != symbolTable->end(locker));
iter->value.prepareToWatch();
if (bytecode.m_getPutInfo.initializationMode() == InitializationMode::ScopedArgumentInitialization) {
ASSERT(bytecode.m_value.isArgument());
unsigned argumentIndex = bytecode.m_value.toArgument() - 1;
symbolTable->prepareToWatchScopedArgument(iter->value, argumentIndex);
} else
iter->value.prepareToWatch();
metadata.m_watchpointSet = iter->value.watchpointSet();
} else
metadata.m_watchpointSet = nullptr;
Expand Down
18 changes: 12 additions & 6 deletions Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -539,17 +539,23 @@ BytecodeGenerator::BytecodeGenerator(VM& vm, FunctionNode* functionNode, Unlinke
m_outOfMemoryDuringConstruction = true;
return;
}

unsigned varOrAnonymous = UINT_MAX;

if (UniquedStringImpl* name = visibleNameForParameter(parameters.at(i).first)) {
VarOffset varOffset(offset);
SymbolTableEntry entry(varOffset);
// Stores to these variables via the ScopedArguments object will not do
// notifyWrite(), since that would be cumbersome. Also, watching formal
// parameters when "arguments" is in play is unlikely to be super profitable.
// So, we just disable it.
entry.disableWatching(m_vm);
functionSymbolTable->set(NoLockingNecessary, name, entry);

IGNORE_GCC_WARNINGS_BEGIN("dangling-reference")
const Identifier& ident =
static_cast<const BindingNode*>(parameters.at(i).first)->boundProperty();
IGNORE_GCC_WARNINGS_END

varOrAnonymous = addConstant(ident);
}
OpPutToScope::emit(this, m_lexicalEnvironmentRegister, UINT_MAX, virtualRegisterForArgumentIncludingThis(1 + i), GetPutInfo(ThrowIfNotFound, ResolvedClosureVar, InitializationMode::NotInitialization, ecmaMode), SymbolTableOrScopeDepth::symbolTable(VirtualRegister { symbolTableConstantIndex }), offset.offset());

OpPutToScope::emit(this, m_lexicalEnvironmentRegister, varOrAnonymous, virtualRegisterForArgumentIncludingThis(1 + i), GetPutInfo(ThrowIfNotFound, ResolvedClosureVar, InitializationMode::ScopedArgumentInitialization, ecmaMode), SymbolTableOrScopeDepth::symbolTable(VirtualRegister { symbolTableConstantIndex }), offset.offset());
}

// This creates a scoped arguments object and copies the overflow arguments into the
Expand Down
3 changes: 3 additions & 0 deletions Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8103,6 +8103,9 @@ void ByteCodeParser::parseBlock(unsigned limit)
// Must happen after the store. See comment for GetGlobalVar.
addToGraph(NotifyWrite, OpInfo(watchpoints));
}

// Keep scope alive until after put.
addToGraph(Phantom, scopeNode);
break;
}

Expand Down
2 changes: 1 addition & 1 deletion Source/JavaScriptCore/llint/LowLevelInterpreter32_64.asm
Original file line number Diff line number Diff line change
Expand Up @@ -2821,7 +2821,7 @@ llintOpWithMetadata(op_put_to_scope, OpPutToScope, macro (size, get, dispatch, m
loadi OpPutToScope::Metadata::m_getPutInfo + GetPutInfo::m_operand[t5], t0
andi InitializationModeMask, t0
rshifti InitializationModeShift, t0
bineq t0, NotInitialization, .noNeedForTDZCheck
bilt t0, NotInitialization, .noNeedForTDZCheck
loadp OpPutToScope::Metadata::m_operand[t5], t0
loadi TagOffset[t0], t0
bieq t0, EmptyValueTag, .pDynamic
Expand Down
2 changes: 1 addition & 1 deletion Source/JavaScriptCore/llint/LowLevelInterpreter64.asm
Original file line number Diff line number Diff line change
Expand Up @@ -3007,7 +3007,7 @@ llintOpWithMetadata(op_put_to_scope, OpPutToScope, macro (size, get, dispatch, m
loadi OpPutToScope::Metadata::m_getPutInfo + GetPutInfo::m_operand[t5], t0
andi InitializationModeMask, t0
rshifti InitializationModeShift, t0
bineq t0, NotInitialization, .noNeedForTDZCheck
bilt t0, NotInitialization, .noNeedForTDZCheck
loadp OpPutToScope::Metadata::m_operand[t5], t0
loadq [t0], t0
bqeq t0, ValueEmpty, .pDynamic
Expand Down
7 changes: 5 additions & 2 deletions Source/JavaScriptCore/runtime/GetPutInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ enum ResolveType : unsigned {
enum class InitializationMode : unsigned {
Initialization, // "let x = 20;"
ConstInitialization, // "const x = 20;"
NotInitialization // "x = 20;"
NotInitialization, // "x = 20;"
ScopedArgumentInitialization // Assign to scoped argument, which also has NotInitialization semantics.
};

ALWAYS_INLINE const char* resolveModeName(ResolveMode resolveMode)
Expand Down Expand Up @@ -120,7 +121,8 @@ ALWAYS_INLINE const char* initializationModeName(InitializationMode initializati
static const char* const names[] = {
"Initialization",
"ConstInitialization",
"NotInitialization"
"NotInitialization",
"ScopedArgumentInitialization"
};
return names[static_cast<unsigned>(initializationMode)];
}
Expand All @@ -132,6 +134,7 @@ ALWAYS_INLINE bool isInitialization(InitializationMode initializationMode)
case InitializationMode::ConstInitialization:
return true;
case InitializationMode::NotInitialization:
case InitializationMode::ScopedArgumentInitialization:
return false;
}
ASSERT_NOT_REACHED();
Expand Down
1 change: 1 addition & 0 deletions Source/JavaScriptCore/runtime/ResourceExhaustion.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ namespace JSC {

enum ResourceExhaustionCode {
StructureIDExhaustion = 30,
MemoryExhaustion = 31,
};

#define RELEASE_ASSERT_RESOURCE_AVAILABLE(assertion, resourceExhaustionCode, failureMessage) do { \
Expand Down
1 change: 1 addition & 0 deletions Source/JavaScriptCore/runtime/ScopedArguments.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ void ScopedArguments::unmapArgument(JSGlobalObject* globalObject, uint32_t i)
return;
}
m_table.set(vm, this, maybeCloned);
m_table->clearWatchpointSet(i);
} else
storage()[i - namedLength].clear();
}
Expand Down
13 changes: 11 additions & 2 deletions Source/JavaScriptCore/runtime/ScopedArguments.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

#include "GenericArguments.h"
#include "JSLexicalEnvironment.h"
#include "Watchpoint.h"

namespace JSC {

Expand Down Expand Up @@ -111,9 +112,17 @@ class ScopedArguments final : public GenericArguments<ScopedArguments> {
{
ASSERT_WITH_SECURITY_IMPLICATION(isMappedArgument(i));
unsigned namedLength = m_table->length();
if (i < namedLength)
if (i < namedLength) {
m_scope->variableAt(m_table->get(i)).set(vm, m_scope.get(), value);
else

auto* watchpointSet = m_table->getWatchpointSet(i);
if (watchpointSet) {
#if ASSERT_ENABLED
ASSERT(m_scope->symbolTable()->hasScopedWatchpointSet(watchpointSet));
#endif
watchpointSet->touch(vm, "Write to ScopedArgument.");
}
} else
storage()[i - namedLength].set(vm, this, value);
}

Expand Down
17 changes: 16 additions & 1 deletion Source/JavaScriptCore/runtime/ScopedArgumentsTable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ ScopedArgumentsTable* ScopedArgumentsTable::tryCreate(VM& vm, uint32_t length)
result->m_arguments = ArgumentsPtr::tryCreate(length);
if (UNLIKELY(!result->m_arguments))
return nullptr;
result->m_watchpointSets.fill(nullptr, length);
return result;
}

Expand All @@ -80,6 +81,7 @@ ScopedArgumentsTable* ScopedArgumentsTable::tryClone(VM& vm)
return nullptr;
for (unsigned i = m_length; i--;)
result->at(i) = this->at(i);
result->m_watchpointSets = this->m_watchpointSets;
return result;
}

Expand All @@ -93,14 +95,18 @@ ScopedArgumentsTable* ScopedArgumentsTable::trySetLength(VM& vm, uint32_t newLen
newArguments.at(i, newLength) = this->at(i);
m_length = newLength;
m_arguments = WTFMove(newArguments);
m_watchpointSets.resize(newLength);
return this;
}

ScopedArgumentsTable* result = tryCreate(vm, newLength);
if (UNLIKELY(!result))
return nullptr;
for (unsigned i = std::min(m_length, newLength); i--;)
m_watchpointSets.resize(newLength);
for (unsigned i = std::min(m_length, newLength); i--;) {
result->at(i) = this->at(i);
result->m_watchpointSets[i] = this->m_watchpointSets[i];
}
return result;
}

Expand All @@ -119,6 +125,15 @@ ScopedArgumentsTable* ScopedArgumentsTable::trySet(VM& vm, uint32_t i, ScopeOffs
return result;
}

void ScopedArgumentsTable::trySetWatchpointSet(uint32_t i, WatchpointSet* watchpoints)
{
ASSERT(watchpoints);
if (i >= m_watchpointSets.size())
return;

m_watchpointSets[i] = watchpoints;
}

Structure* ScopedArgumentsTable::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
{
return Structure::create(vm, globalObject, prototype, TypeInfo(CellType, StructureFlags), info());
Expand Down
8 changes: 7 additions & 1 deletion Source/JavaScriptCore/runtime/ScopedArgumentsTable.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@

namespace JSC {

class WatchpointSet;

// This class's only job is to hold onto the list of ScopeOffsets for each argument that a
// function has. Most of the time, the BytecodeGenerator will create one of these and it will
// never be modified subsequently. There is a rare case where a ScopedArguments object is created
Expand Down Expand Up @@ -67,14 +69,17 @@ class ScopedArgumentsTable final : public JSCell {
ScopedArgumentsTable* trySetLength(VM&, uint32_t newLength);

ScopeOffset get(uint32_t i) const { return at(i); }
WatchpointSet* getWatchpointSet(uint32_t i) const { return m_watchpointSets.at(i); }

void lock()
{
m_locked = true;
}

ScopedArgumentsTable* trySet(VM&, uint32_t index, ScopeOffset);

void trySetWatchpointSet(uint32_t index, WatchpointSet* watchpoints);
void clearWatchpointSet(uint32_t index) { m_watchpointSets[index] = nullptr; }

DECLARE_INFO;

static Structure* createStructure(VM&, JSGlobalObject*, JSValue prototype);
Expand All @@ -96,6 +101,7 @@ class ScopedArgumentsTable final : public JSCell {
uint32_t m_length;
bool m_locked; // Being locked means that there are multiple references to this object and none of them expect to see the others' modifications. This means that modifications need to make a copy first.
ArgumentsPtr m_arguments;
Vector<WatchpointSet*> m_watchpointSets;
};

} // namespace JSC
58 changes: 50 additions & 8 deletions Source/JavaScriptCore/runtime/SymbolTable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

#include "CodeBlock.h"
#include "JSCJSValueInlines.h"
#include "ResourceExhaustion.h"
#include "TypeProfiler.h"

#include <wtf/CommaPrinter.h>
Expand Down Expand Up @@ -150,19 +151,44 @@ SymbolTable* SymbolTable::cloneScopePart(VM& vm)
result->m_nestedLexicalScope = m_nestedLexicalScope;
result->m_scopeType = m_scopeType;

HashMap<VarOffset, uint32_t> varOffsetToArgIndexMap;

if (this->arguments()) {
// Copy the arguments, but not the WatchpointSets. We create new WatchpointSets as appropriate when we create the SymbolTableEntry
// copies below and propogate the new watchpointSets to the new ScopedArgumentsTable.
auto length = this->arguments()->length();
ScopedArgumentsTable* arguments = ScopedArgumentsTable::tryCreate(vm, length);
RELEASE_ASSERT_RESOURCE_AVAILABLE(arguments, MemoryExhaustion, "Crash intentionally because memory is exhausted.");

for (uint32_t index = 0; index < length; ++index) {
ScopeOffset offset = this->arguments()->get(index);

arguments->trySet(vm, index, offset);
if (this->arguments()->getWatchpointSet(index))
varOffsetToArgIndexMap.set(VarOffset(offset), index);
}

result->m_arguments.set(vm, result, arguments);
}

bool hasScopedArgumentWatchpoints = !varOffsetToArgIndexMap.isEmpty();

for (auto iter = m_map.begin(), end = m_map.end(); iter != end; ++iter) {
if (!iter->value.varOffset().isScope())
continue;
result->m_map.add(
iter->key,
SymbolTableEntry(iter->value.varOffset(), iter->value.getAttributes()));
SymbolTableEntry entry(iter->value.varOffset(), iter->value.getAttributes());

if (hasScopedArgumentWatchpoints) {
auto findIter = varOffsetToArgIndexMap.find(iter->value.varOffset());
if (findIter != varOffsetToArgIndexMap.end())
result->prepareToWatchScopedArgument(entry, findIter->value);
}

result->m_map.add(iter->key, WTFMove(entry));
}

result->m_maxScopeOffset = m_maxScopeOffset;

if (ScopedArgumentsTable* arguments = this->arguments())
result->m_arguments.set(vm, result, arguments);


if (m_rareData) {
result->m_rareData = makeUnique<SymbolTableRareData>();

Expand Down Expand Up @@ -285,6 +311,22 @@ RefPtr<TypeSet> SymbolTable::globalTypeSetForVariable(const ConcurrentJSLocker&
return iter->value;
}

#if ASSERT_ENABLED
bool SymbolTable::hasScopedWatchpointSet(WatchpointSet* watchpointSet)
{
for (auto iter = m_map.begin(), end = m_map.end(); iter != end; ++iter) {
if (!iter->value.varOffset().isScope())
continue;

auto* entryWatchpointSet = iter->value.watchpointSet();
if (entryWatchpointSet && entryWatchpointSet == watchpointSet)
return true;
}

return false;
}
#endif

void SymbolTable::dump(PrintStream& out) const
{
ConcurrentJSLocker locker(m_lock);
Expand Down
Loading