// 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.

@generateCppClass
extern class JSArgumentsObject extends JSObject {
}

type JSArgumentsObjectWithLength =
    JSSloppyArgumentsObject|JSStrictArgumentsObject;

@export
macro IsJSArgumentsObjectWithLength(implicit context: Context)(o: Object):
    bool {
  return Is<JSArgumentsObjectWithLength>(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 fron 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.
//
// 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;
  mapped_entries[length]: Smi|TheHole;
}

macro NewSloppyArgumentsElements<Iterator: type>(
    length: Smi, context: Context, arguments: FixedArray,
    it: Iterator): SloppyArgumentsElements {
  return new
  SloppyArgumentsElements{length, context, arguments, mapped_entries: ...it};
}

@generateCppClass
@generatePrint
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<Smi>(this.currentIndex);
  }
  currentIndex: intptr;
  const endInterationIndex: intptr;
}

macro NewParameterMapIterator(
    context: Context, formalParameterCount: intptr,
    mappedCount: intptr): ParameterMapIterator {
  const flags = context.GetScopeInfo().flags;
  let contextHeaderSize: intptr = MIN_CONTEXT_SLOTS;
  if (flags.has_context_extension_slot) ++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<intptr>(info.argument_count);
  const formalParameterCount = Convert<intptr>(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<intptr>(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<intptr>(info.argument_count);
  const formalParameterCount = Convert<intptr>(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<Smi>(mappedCount);
  const elements = NewSloppyArgumentsElements(
      elementsLength, context, parameterValues, paramIter);
  const length = Convert<Smi>(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<intptr>(argumentCount));
}

builtin NewStrictArgumentsElements(
    frame: FrameWithArguments, _formalParameterCount: intptr,
    argumentCount: Smi): FixedArray {
  return arguments::NewStrictArgumentsElements(
      frame, Convert<intptr>(argumentCount));
}

builtin NewRestArgumentsElements(
    frame: FrameWithArguments, formalParameterCount: intptr,
    argumentCount: Smi): FixedArray {
  return arguments::NewRestArgumentsElements(
      frame, formalParameterCount, Convert<intptr>(argumentCount));
}