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

#include "src/builtins/builtins-iterator-gen.h"

#include "src/heap/factory-inl.h"

namespace v8 {
namespace internal {

using compiler::Node;

Node* IteratorBuiltinsAssembler::GetIteratorMethod(Node* context,
                                                   Node* object) {
  return GetProperty(context, object, factory()->iterator_symbol());
}

IteratorRecord IteratorBuiltinsAssembler::GetIterator(Node* context,
                                                      Node* object,
                                                      Label* if_exception,
                                                      Variable* exception) {
  Node* method = GetIteratorMethod(context, object);
  return GetIterator(context, object, method, if_exception, exception);
}

IteratorRecord IteratorBuiltinsAssembler::GetIterator(Node* context,
                                                      Node* object,
                                                      Node* method,
                                                      Label* if_exception,
                                                      Variable* exception) {
  GotoIfException(method, if_exception, exception);

  Label if_not_callable(this, Label::kDeferred), if_callable(this);
  GotoIf(TaggedIsSmi(method), &if_not_callable);
  Branch(IsCallable(method), &if_callable, &if_not_callable);

  BIND(&if_not_callable);
  {
    Node* ret = CallRuntime(Runtime::kThrowTypeError, context,
                            SmiConstant(MessageTemplate::kNotIterable), object);
    GotoIfException(ret, if_exception, exception);
    Unreachable();
  }

  BIND(&if_callable);
  {
    Callable callable = CodeFactory::Call(isolate());
    Node* iterator = CallJS(callable, context, method, object);
    GotoIfException(iterator, if_exception, exception);

    Label get_next(this), if_notobject(this, Label::kDeferred);
    GotoIf(TaggedIsSmi(iterator), &if_notobject);
    Branch(IsJSReceiver(iterator), &get_next, &if_notobject);

    BIND(&if_notobject);
    {
      Node* ret = CallRuntime(Runtime::kThrowSymbolIteratorInvalid, context);
      GotoIfException(ret, if_exception, exception);
      Unreachable();
    }

    BIND(&get_next);
    Node* const next = GetProperty(context, iterator, factory()->next_string());
    GotoIfException(next, if_exception, exception);

    return IteratorRecord{TNode<JSReceiver>::UncheckedCast(iterator),
                          TNode<Object>::UncheckedCast(next)};
  }
}

Node* IteratorBuiltinsAssembler::IteratorStep(
    Node* context, const IteratorRecord& iterator, Label* if_done,
    Node* fast_iterator_result_map, Label* if_exception, Variable* exception) {
  DCHECK_NOT_NULL(if_done);
  // 1. a. Let result be ? Invoke(iterator, "next", « »).
  Callable callable = CodeFactory::Call(isolate());
  Node* result = CallJS(callable, context, iterator.next, iterator.object);
  GotoIfException(result, if_exception, exception);

  // 3. If Type(result) is not Object, throw a TypeError exception.
  Label if_notobject(this, Label::kDeferred), return_result(this);
  GotoIf(TaggedIsSmi(result), &if_notobject);
  Node* result_map = LoadMap(result);

  if (fast_iterator_result_map != nullptr) {
    // Fast iterator result case:
    Label if_generic(this);

    // 4. Return result.
    GotoIfNot(WordEqual(result_map, fast_iterator_result_map), &if_generic);

    // IteratorComplete
    // 2. Return ToBoolean(? Get(iterResult, "done")).
    Node* done = LoadObjectField(result, JSIteratorResult::kDoneOffset);
    BranchIfToBooleanIsTrue(done, if_done, &return_result);

    BIND(&if_generic);
  }

  // Generic iterator result case:
  {
    // 3. If Type(result) is not Object, throw a TypeError exception.
    GotoIfNot(IsJSReceiverMap(result_map), &if_notobject);

    // IteratorComplete
    // 2. Return ToBoolean(? Get(iterResult, "done")).
    Node* done = GetProperty(context, result, factory()->done_string());
    GotoIfException(done, if_exception, exception);
    BranchIfToBooleanIsTrue(done, if_done, &return_result);
  }

  BIND(&if_notobject);
  {
    Node* ret =
        CallRuntime(Runtime::kThrowIteratorResultNotAnObject, context, result);
    GotoIfException(ret, if_exception, exception);
    Unreachable();
  }

  BIND(&return_result);
  return result;
}

Node* IteratorBuiltinsAssembler::IteratorValue(Node* context, Node* result,
                                               Node* fast_iterator_result_map,
                                               Label* if_exception,
                                               Variable* exception) {
  CSA_ASSERT(this, IsJSReceiver(result));

  Label exit(this);
  VARIABLE(var_value, MachineRepresentation::kTagged);
  if (fast_iterator_result_map != nullptr) {
    // Fast iterator result case:
    Label if_generic(this);
    Node* map = LoadMap(result);
    GotoIfNot(WordEqual(map, fast_iterator_result_map), &if_generic);
    var_value.Bind(LoadObjectField(result, JSIteratorResult::kValueOffset));
    Goto(&exit);

    BIND(&if_generic);
  }

  // Generic iterator result case:
  {
    Node* value = GetProperty(context, result, factory()->value_string());
    GotoIfException(value, if_exception, exception);
    var_value.Bind(value);
    Goto(&exit);
  }

  BIND(&exit);
  return var_value.value();
}

void IteratorBuiltinsAssembler::IteratorCloseOnException(
    Node* context, const IteratorRecord& iterator, Label* if_exception,
    Variable* exception) {
  // Perform ES #sec-iteratorclose when an exception occurs. This simpler
  // algorithm does not include redundant steps which are never reachable from
  // the spec IteratorClose algorithm.
  DCHECK_NOT_NULL(if_exception);
  DCHECK_NOT_NULL(exception);
  CSA_ASSERT(this, IsNotTheHole(exception->value()));
  CSA_ASSERT(this, IsJSReceiver(iterator.object));

  // Let return be ? GetMethod(iterator, "return").
  Node* method =
      GetProperty(context, iterator.object, factory()->return_string());
  GotoIfException(method, if_exception, exception);

  // If return is undefined, return Completion(completion).
  GotoIf(Word32Or(IsUndefined(method), IsNull(method)), if_exception);

  {
    // Let innerResult be Call(return, iterator, « »).
    // If an exception occurs, the original exception remains bound
    Node* inner_result =
        CallJS(CodeFactory::Call(isolate()), context, method, iterator.object);
    GotoIfException(inner_result, if_exception, nullptr);

    // (If completion.[[Type]] is throw) return Completion(completion).
    Goto(if_exception);
  }
}

void IteratorBuiltinsAssembler::IteratorCloseOnException(
    Node* context, const IteratorRecord& iterator, Variable* exception) {
  Label rethrow(this, Label::kDeferred);
  IteratorCloseOnException(context, iterator, &rethrow, exception);

  BIND(&rethrow);
  CallRuntime(Runtime::kReThrow, context, exception->value());
  Unreachable();
}

}  // namespace internal
}  // namespace v8
