// Copyright 2019 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. extern class JSArgumentsObject extends JSObject {} type JSArgumentsObjectWithLength = JSSloppyArgumentsObject|JSStrictArgumentsObject; @export macro IsJSArgumentsObjectWithLength(implicit context: Context)(o: Object): bool { return Is(o); } // Just a starting shape for JSObject; properties can move after initialization. extern shape JSSloppyArgumentsObject extends JSArgumentsObject { length: JSAny; callee: JSAny; } // Just a starting shape for JSObject; properties can move after initialization. extern shape JSStrictArgumentsObject extends JSArgumentsObject { length: JSAny; } // Helper class to access FAST_ and SLOW_SLOPPY_ARGUMENTS_ELEMENTS, dividing // arguments into two types for a given SloppyArgumentsElements object: // mapped and unmapped. // // For clarity SloppyArgumentsElements fields are qualified with "elements." // below. // // Mapped arguments are actual arguments. Unmapped arguments are values added // to the arguments object after it was created for the call. Mapped arguments // are stored in the context at indexes given by elements.mapped_entries[key]. // Unmapped arguments are stored as regular indexed properties in the arguments // array which can be accessed from elements.arguments. // // elements.length is min(number_of_actual_arguments, // number_of_formal_arguments) for a concrete call to a function. // // Once a SloppyArgumentsElements is generated, lookup of an argument with index // |key| in |elements| works as follows: // // If key >= elements.length then attempt to look in the unmapped arguments // array and return the value at key, missing to the runtime if the unmapped // arguments array is not a fixed array or if key >= elements.arguments.length. // // Otherwise, t = elements.mapped_entries[key]. If t is the hole, then the // entry has been deleted from the arguments object, and value is looked up in // the unmapped arguments array, as described above. Otherwise, t is a Smi // index into the context array specified at elements.context, and the return // value is elements.context[t]. // // A graphic representation of a SloppyArgumentsElements object and a // corresponding unmapped arguments FixedArray: // // SloppyArgumentsElements // +---+-----------------------+ // | Context context | // +---------------------------+ // | FixedArray arguments +----+ HOLEY_ELEMENTS // +---------------------------+ v-----+-----------+ // | 0 | Object mapped_entries | | 0 | the_hole | // |...| ... | | ... | ... | // |n-1| Object mapped_entries | | n-1 | the_hole | // +---------------------------+ | n | element_1 | // | ... | ... | // |n+m-1| element_m | // +-----------------+ // // The elements.arguments backing store kind depends on the ElementsKind of // the outer JSArgumentsObject: // - FAST_SLOPPY_ARGUMENTS_ELEMENTS: HOLEY_ELEMENTS // - SLOW_SLOPPY_ARGUMENTS_ELEMENTS: DICTIONARY_ELEMENTS @export class SloppyArgumentsElements extends FixedArrayBase { context: Context; arguments: FixedArray|NumberDictionary; @cppRelaxedLoad mapped_entries[length]: Smi|TheHole; } macro NewSloppyArgumentsElements( length: Smi, context: Context, arguments: FixedArray, it: Iterator): SloppyArgumentsElements { return new SloppyArgumentsElements{length, context, arguments, mapped_entries: ...it}; } extern class AliasedArgumentsEntry extends Struct { aliased_context_slot: Smi; } // TODO(danno): This should be a namespace {} once supported namespace arguments { macro NewJSStrictArgumentsObject(implicit context: Context)( elements: FixedArray): JSStrictArgumentsObject { const map = GetStrictArgumentsMap(); return new JSStrictArgumentsObject{ map, properties_or_hash: kEmptyFixedArray, elements, length: elements.length }; } macro NewJSSloppyArgumentsObject(implicit context: Context)( elements: FixedArrayBase, callee: JSFunction): JSSloppyArgumentsObject { const map = GetSloppyArgumentsMap(); return new JSSloppyArgumentsObject{ map, properties_or_hash: kEmptyFixedArray, elements, length: elements.length, callee }; } macro NewJSFastAliasedArgumentsObject(implicit context: Context)( elements: FixedArrayBase, length: Smi, callee: JSFunction): JSSloppyArgumentsObject { // TODO(danno): FastAliasedArguments should really be a type for itself const map = GetFastAliasedArgumentsMap(); return new JSSloppyArgumentsObject{ map, properties_or_hash: kEmptyFixedArray, elements, length, callee }; } struct ParameterMapIterator { macro Next(): Smi labels NoMore { if (this.currentIndex == this.endInterationIndex) goto NoMore; this.currentIndex--; return Convert(this.currentIndex); } currentIndex: intptr; const endInterationIndex: intptr; } macro NewParameterMapIterator( context: Context, formalParameterCount: intptr, mappedCount: intptr): ParameterMapIterator { const flags = context.GetScopeInfo().flags; let contextHeaderSize: intptr = ContextSlot::MIN_CONTEXT_SLOTS; if (flags.has_context_extension_slot) ++contextHeaderSize; if (flags.receiver_variable == FromConstexpr(VariableAllocationInfo::CONTEXT)) { ++contextHeaderSize; } // Copy the parameter slots and the holes in the arguments. // We need to fill in mapped_count slots. They index the context, // where parameters are stored in reverse order, at // context_header_size .. context_header_size+argument_count-1 // The mapped parameter thus need to get indices // context_header_size+parameter_count-1 .. // context_header_size+argument_count-mapped_count // We loop from right to left. const afterLastContextIndex = contextHeaderSize + formalParameterCount; const firstContextIndex = afterLastContextIndex - mappedCount; return ParameterMapIterator{ currentIndex: afterLastContextIndex, endInterationIndex: firstContextIndex }; } struct ParameterValueIterator { macro Next(): Object labels NoMore() { if (this.mapped_count != 0) { this.mapped_count--; return TheHole; } if (this.current == this.arguments.length) goto NoMore; return this.arguments[this.current++]; } mapped_count: intptr; const arguments: Arguments; current: intptr; } macro NewParameterValueIterator( mappedCount: intptr, arguments: Arguments): ParameterValueIterator { return ParameterValueIterator{ mapped_count: mappedCount, arguments, current: mappedCount }; } macro NewAllArguments(implicit context: Context)( frame: FrameWithArguments, argumentCount: intptr): JSArray { const map = GetFastPackedElementsJSArrayMap(); const arguments = GetFrameArguments(frame, argumentCount); const it = ArgumentsIterator{arguments, current: 0}; const elements = NewFixedArray(argumentCount, it); return NewJSArray(map, elements); } macro NewRestArgumentsElements( frame: FrameWithArguments, formalParameterCount: intptr, argumentCount: intptr): FixedArray { const length = (formalParameterCount >= argumentCount) ? 0 : argumentCount - formalParameterCount; const arguments = GetFrameArguments(frame, argumentCount); const it = ArgumentsIterator{arguments, current: formalParameterCount}; return NewFixedArray(length, it); } macro NewRestArguments(implicit context: Context)(info: FrameWithArgumentsInfo): JSArray { const argumentCount = Convert(info.argument_count); const formalParameterCount = Convert(info.formal_parameter_count); const map = GetFastPackedElementsJSArrayMap(); const elements = NewRestArgumentsElements(info.frame, formalParameterCount, argumentCount); return NewJSArray(map, elements); } macro NewStrictArgumentsElements( frame: FrameWithArguments, argumentCount: intptr): FixedArray { const arguments = GetFrameArguments(frame, argumentCount); const it = ArgumentsIterator{arguments, current: 0}; return NewFixedArray(argumentCount, it); } macro NewStrictArguments(implicit context: Context)( info: FrameWithArgumentsInfo): JSStrictArgumentsObject { const argumentCount = Convert(info.argument_count); const elements = NewStrictArgumentsElements(info.frame, argumentCount); return NewJSStrictArgumentsObject(elements); } macro NewSloppyArgumentsElements( frame: FrameWithArguments, formalParameterCount: intptr, argumentCount: intptr): FixedArray { const arguments = GetFrameArguments(frame, argumentCount); if (formalParameterCount == 0) { const it = ArgumentsIterator{arguments, current: 0}; return NewFixedArray(argumentCount, it); } const mappedCount = IntPtrMin(formalParameterCount, argumentCount); const it = NewParameterValueIterator(mappedCount, arguments); return NewFixedArray(argumentCount, it); } macro NewSloppyArguments(implicit context: Context)( info: FrameWithArgumentsInfo, callee: JSFunction): JSSloppyArgumentsObject { const argumentCount = Convert(info.argument_count); const formalParameterCount = Convert(info.formal_parameter_count); const parameterValues = arguments::NewSloppyArgumentsElements( info.frame, formalParameterCount, argumentCount); if (formalParameterCount == 0) { return NewJSSloppyArgumentsObject(parameterValues, callee); } const mappedCount = IntPtrMin(formalParameterCount, argumentCount); let paramIter = NewParameterMapIterator(context, formalParameterCount, mappedCount); const elementsLength = Convert(mappedCount); const elements = NewSloppyArgumentsElements( elementsLength, context, parameterValues, paramIter); const length = Convert(argumentCount); return NewJSFastAliasedArgumentsObject(elements, length, callee); } } // namespace arguments @export macro EmitFastNewAllArguments(implicit context: Context)( frame: FrameWithArguments, argc: intptr): JSArray { return arguments::NewAllArguments(frame, argc); } @export macro EmitFastNewRestArguments(implicit context: Context)(_f: JSFunction): JSArray { const info = GetFrameWithArgumentsInfo(); return arguments::NewRestArguments(info); } @export macro EmitFastNewStrictArguments(implicit context: Context)(_f: JSFunction): JSStrictArgumentsObject { const info = GetFrameWithArgumentsInfo(); return arguments::NewStrictArguments(info); } @export macro EmitFastNewSloppyArguments(implicit context: Context)(f: JSFunction): JSSloppyArgumentsObject { const info = GetFrameWithArgumentsInfo(); return arguments::NewSloppyArguments(info, f); } builtin NewSloppyArgumentsElements( frame: FrameWithArguments, formalParameterCount: intptr, argumentCount: Smi): FixedArray { return arguments::NewSloppyArgumentsElements( frame, formalParameterCount, Convert(argumentCount)); } builtin NewStrictArgumentsElements( frame: FrameWithArguments, _formalParameterCount: intptr, argumentCount: Smi): FixedArray { return arguments::NewStrictArgumentsElements( frame, Convert(argumentCount)); } builtin NewRestArgumentsElements( frame: FrameWithArguments, formalParameterCount: intptr, argumentCount: Smi): FixedArray { return arguments::NewRestArgumentsElements( frame, formalParameterCount, Convert(argumentCount)); } builtin FastNewSloppyArguments(implicit context: Context)(f: JSFunction): JSSloppyArgumentsObject { return EmitFastNewSloppyArguments(f); } builtin FastNewStrictArguments(implicit context: Context)(f: JSFunction): JSStrictArgumentsObject { return EmitFastNewStrictArguments(f); } builtin FastNewRestArguments(implicit context: Context)(f: JSFunction): JSArray { return EmitFastNewRestArguments(f); } macro AccessSloppyArgumentsCommon( receiver: JSObject, keyObject: Object): &Object labels Bailout { const key = Cast(keyObject) otherwise Bailout; const elements = Cast(receiver.elements) otherwise Bailout; try { if (OutOfBounds(key, elements.length)) goto Unmapped; const mappedIndex = elements.mapped_entries[key]; typeswitch (mappedIndex) { case (contextIndex: Smi): { return &(elements.context.elements[contextIndex]); } case (TheHole): { goto Unmapped; } } } label Unmapped { typeswitch (elements.arguments) { case (NumberDictionary): { goto Bailout; } case (arguments: FixedArray): { if (OutOfBounds(key, arguments.length)) goto Bailout; if (arguments.objects[key] == TheHole) goto Bailout; return &(arguments.objects[key]); } } } } @export macro SloppyArgumentsLoad(receiver: JSObject, keyObject: Object): JSAny labels Bailout { return UnsafeCast( *AccessSloppyArgumentsCommon(receiver, keyObject) otherwise Bailout); } @export macro SloppyArgumentsHas(receiver: JSObject, keyObject: Object): JSAny labels Bailout { AccessSloppyArgumentsCommon(receiver, keyObject) otherwise Bailout; return True; } @export macro SloppyArgumentsStore(receiver: JSObject, keyObject: Object, value: JSAny): JSAny labels Bailout { let destination = AccessSloppyArgumentsCommon(receiver, keyObject) otherwise Bailout; *destination = value; return value; }