// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// Class for intrinsifying functions.

#include "vm/compiler/graph_intrinsifier.h"
#include "vm/compiler/backend/block_builder.h"
#include "vm/compiler/backend/flow_graph.h"
#include "vm/compiler/backend/flow_graph_compiler.h"
#include "vm/compiler/backend/il.h"
#include "vm/compiler/backend/il_printer.h"
#include "vm/compiler/backend/inliner.h"
#include "vm/compiler/backend/linearscan.h"
#include "vm/compiler/backend/range_analysis.h"
#include "vm/compiler/compiler_pass.h"
#include "vm/compiler/intrinsifier.h"
#include "vm/compiler/jit/compiler.h"
#include "vm/cpu.h"
#include "vm/flag_list.h"

namespace dart {

DECLARE_FLAG(bool, print_flow_graph);
DECLARE_FLAG(bool, print_flow_graph_optimized);

class GraphIntrinsicCodeGenScope {
 public:
  explicit GraphIntrinsicCodeGenScope(FlowGraphCompiler* compiler)
      : compiler_(compiler), old_is_optimizing_(compiler->is_optimizing()) {
    compiler_->is_optimizing_ = true;
  }
  ~GraphIntrinsicCodeGenScope() {
    compiler_->is_optimizing_ = old_is_optimizing_;
  }

 private:
  FlowGraphCompiler* compiler_;
  bool old_is_optimizing_;
};

namespace compiler {

static void EmitCodeFor(FlowGraphCompiler* compiler, FlowGraph* graph) {
  // For graph intrinsics we run the linearscan register allocator, which will
  // pass opt=true for MakeLocationSummary. We therefore also have to ensure
  // `compiler->is_optimizing()` is set to true during EmitNativeCode.
  GraphIntrinsicCodeGenScope optimizing_scope(compiler);

  compiler->assembler()->Comment("Graph intrinsic begin");
  for (intptr_t i = 0; i < graph->reverse_postorder().length(); i++) {
    BlockEntryInstr* block = graph->reverse_postorder()[i];
    if (block->IsGraphEntry()) continue;  // No code for graph entry needed.

    if (block->HasParallelMove()) {
      block->parallel_move()->EmitNativeCode(compiler);
    }

    for (ForwardInstructionIterator it(block); !it.Done(); it.Advance()) {
      Instruction* instr = it.Current();
      if (FLAG_code_comments) compiler->EmitComment(instr);
      // Calls are not supported in intrinsics code.
      ASSERT(instr->IsParallelMove() ||
             (instr->locs() != nullptr && !instr->locs()->always_calls()));
      instr->EmitNativeCode(compiler);
    }
  }
  compiler->assembler()->Comment("Graph intrinsic end");
}

bool GraphIntrinsifier::GraphIntrinsify(const ParsedFunction& parsed_function,
                                        FlowGraphCompiler* compiler) {
  ASSERT(!parsed_function.function().HasOptionalParameters());
  PrologueInfo prologue_info(-1, -1);

  auto graph_entry =
      new GraphEntryInstr(parsed_function, Compiler::kNoOSRDeoptId);

  intptr_t block_id = 1;  // 0 is GraphEntry.
  graph_entry->set_normal_entry(
      new FunctionEntryInstr(graph_entry, block_id, kInvalidTryIndex,
                             CompilerState::Current().GetNextDeoptId()));

  FlowGraph* graph =
      new FlowGraph(parsed_function, graph_entry, block_id, prologue_info,
                    FlowGraph::CompilationMode::kIntrinsic);
  compiler->set_intrinsic_flow_graph(*graph);

  const Function& function = parsed_function.function();

  switch (function.recognized_kind()) {
#define EMIT_CASE(library, class_name, function_name, enum_name, fp)           \
  case MethodRecognizer::k##enum_name:                                         \
    if (!Build_##enum_name(graph)) return false;                               \
    break;

    GRAPH_INTRINSICS_LIST(EMIT_CASE);
#undef EMIT_CASE
    default:
      return false;
  }

  if (FLAG_support_il_printer && FLAG_print_flow_graph &&
      FlowGraphPrinter::ShouldPrint(function)) {
    THR_Print("Intrinsic graph before\n");
    FlowGraphPrinter printer(*graph);
    printer.PrintBlocks();
  }

  // Prepare for register allocation (cf. FinalizeGraph).
  graph->RemoveRedefinitions();

  // Ensure dominators are re-computed. Normally this is done during SSA
  // construction (which we don't do for graph intrinsics).
  GrowableArray<BitVector*> dominance_frontier;
  graph->ComputeDominators(&dominance_frontier);

  CompilerPassState state(parsed_function.thread(), graph,
                          /*speculative_inlining_policy*/ nullptr);
  CompilerPass::RunGraphIntrinsicPipeline(&state);

  if (FLAG_support_il_printer && FLAG_print_flow_graph &&
      FlowGraphPrinter::ShouldPrint(function)) {
    THR_Print("Intrinsic graph after\n");
    FlowGraphPrinter printer(*graph);
    printer.PrintBlocks();
  }
  EmitCodeFor(compiler, graph);
  return true;
}

static Representation RepresentationForCid(intptr_t cid) {
  switch (cid) {
    case kDoubleCid:
      return kUnboxedDouble;
    case kFloat32x4Cid:
      return kUnboxedFloat32x4;
    case kInt32x4Cid:
      return kUnboxedInt32x4;
    case kFloat64x2Cid:
      return kUnboxedFloat64x2;
    default:
      UNREACHABLE();
      return kNoRepresentation;
  }
}

// Notes about the graph intrinsics:
//
// IR instructions which would jump to a deoptimization sequence on failure
// instead branch to the intrinsic slow path.
//
static Definition* PrepareIndexedOp(FlowGraph* flow_graph,
                                    BlockBuilder* builder,
                                    Definition* array,
                                    Definition* index,
                                    const Slot& length_field) {
  Definition* length = builder->AddDefinition(
      new LoadFieldInstr(new Value(array), length_field, InstructionSource()));
  // Note that the intrinsifier must always use deopting array bound
  // checks, because intrinsics currently don't support calls.
  Definition* safe_index = new CheckArrayBoundInstr(
      new Value(length), new Value(index), DeoptId::kNone);
  builder->AddDefinition(safe_index);
  return safe_index;
}

static void VerifyParameterIsBoxed(BlockBuilder* builder, intptr_t arg_index) {
  const auto& function = builder->function();
  if (function.is_unboxed_parameter_at(arg_index)) {
    FATAL("Unsupported unboxed parameter %" Pd " in %s", arg_index,
          function.ToFullyQualifiedCString());
  }
}

static Definition* CreateBoxedParameterIfNeeded(BlockBuilder* builder,
                                                Definition* value,
                                                Representation representation,
                                                intptr_t arg_index) {
  const auto& function = builder->function();
  if (function.is_unboxed_parameter_at(arg_index)) {
    return builder->AddDefinition(
        BoxInstr::Create(representation, new Value(value)));
  } else {
    return value;
  }
}

static Definition* CreateBoxedResultIfNeeded(BlockBuilder* builder,
                                             Definition* value,
                                             Representation representation) {
  const auto& function = builder->function();
  ASSERT(!function.has_unboxed_record_return());
  Definition* result = value;
  if (representation == kUnboxedFloat) {
    result = builder->AddDefinition(
        new FloatToDoubleInstr(new Value(result), DeoptId::kNone));
    representation = kUnboxedDouble;
  }
  if (!function.has_unboxed_return()) {
    result = builder->AddDefinition(BoxInstr::Create(
        Boxing::NativeRepresentation(representation), new Value(result)));
  }
  return result;
}

static Definition* CreateUnboxedResultIfNeeded(BlockBuilder* builder,
                                               Definition* value) {
  const auto& function = builder->function();
  ASSERT(!function.has_unboxed_record_return());
  if (function.has_unboxed_return() && value->representation() == kTagged) {
    return builder->AddUnboxInstr(FlowGraph::ReturnRepresentationOf(function),
                                  new Value(value),
                                  UnboxInstr::ValueMode::kHasValidType);
  } else {
    return value;
  }
}

static bool IntrinsifyArraySetIndexed(FlowGraph* flow_graph,
                                      intptr_t array_cid) {
  GraphEntryInstr* graph_entry = flow_graph->graph_entry();
  auto normal_entry = graph_entry->normal_entry();
  BlockBuilder builder(flow_graph, normal_entry, /*with_frame=*/false);

  Definition* array = builder.AddParameter(0);
  Definition* index = builder.AddParameter(1);
  Definition* value = builder.AddParameter(2);

  VerifyParameterIsBoxed(&builder, 0);
  VerifyParameterIsBoxed(&builder, 2);

  index = CreateBoxedParameterIfNeeded(&builder, index, kUnboxedInt64, 1);
  index = PrepareIndexedOp(flow_graph, &builder, array, index,
                           Slot::GetLengthFieldForArrayCid(array_cid));

  // Value check/conversion.
  auto const rep = RepresentationUtils::RepresentationOfArrayElement(array_cid);
  if (IsClampedTypedDataBaseClassId(array_cid)) {
#if defined(TARGET_ARCH_IS_32_BIT)
    // On 32-bit architectures, clamping operations need the exact value
    // for proper operations. On 64-bit architectures, kUnboxedIntPtr
    // maps to kUnboxedInt64. All other situations get away with
    // truncating even non-smi values.
    builder.AddInstruction(
        new CheckSmiInstr(new Value(value), DeoptId::kNone, builder.Source()));
#endif
  }
  if (RepresentationUtils::IsUnboxedInteger(rep)) {
    // Use same truncating unbox-instruction for int32 and uint32.
    auto const unbox_rep = rep == kUnboxedInt32 ? kUnboxedUint32 : rep;
    value = builder.AddUnboxInstr(unbox_rep, new Value(value),
                                  UnboxInstr::ValueMode::kCheckType);
  } else if (RepresentationUtils::IsUnboxed(rep)) {
    Zone* zone = flow_graph->zone();
    Cids* value_check = Cids::CreateMonomorphic(zone, Boxing::BoxCid(rep));
    builder.AddInstruction(new CheckClassInstr(new Value(value), DeoptId::kNone,
                                               *value_check, builder.Source()));
    value = builder.AddUnboxInstr(rep, new Value(value),
                                  UnboxInstr::ValueMode::kHasValidType);
  }

  if (IsExternalTypedDataClassId(array_cid)) {
    array = builder.AddDefinition(new LoadFieldInstr(
        new Value(array), Slot::PointerBase_data(),
        InnerPointerAccess::kCannotBeInnerPointer, builder.Source()));
  }
  // No store barrier.
  ASSERT(IsExternalTypedDataClassId(array_cid) ||
         IsTypedDataClassId(array_cid));
  builder.AddInstruction(new StoreIndexedInstr(
      new Value(array), new Value(index), new Value(value), kNoStoreBarrier,
      /*index_unboxed=*/false,
      /*index_scale=*/target::Instance::ElementSizeFor(array_cid), array_cid,
      kAlignedAccess, DeoptId::kNone, builder.Source()));
  // Return null.
  Definition* null_def = builder.AddNullDefinition();
  builder.AddReturn(new Value(null_def));
  return true;
}

#define DEFINE_ARRAY_SETTER_INTRINSIC(enum_name)                               \
  bool GraphIntrinsifier::Build_##enum_name##SetIndexed(                       \
      FlowGraph* flow_graph) {                                                 \
    if (FLAG_target_address_sanitizer) return false;                           \
    if (FLAG_target_memory_sanitizer) return false;                            \
    if (FLAG_target_thread_sanitizer) return false;                            \
    return IntrinsifyArraySetIndexed(                                          \
        flow_graph, MethodRecognizer::MethodKindToReceiverCid(                 \
                        MethodRecognizer::k##enum_name##SetIndexed));          \
  }

DEFINE_ARRAY_SETTER_INTRINSIC(Int8Array)
DEFINE_ARRAY_SETTER_INTRINSIC(Uint8Array)
DEFINE_ARRAY_SETTER_INTRINSIC(ExternalUint8Array)
DEFINE_ARRAY_SETTER_INTRINSIC(Uint8ClampedArray)
DEFINE_ARRAY_SETTER_INTRINSIC(ExternalUint8ClampedArray)
DEFINE_ARRAY_SETTER_INTRINSIC(Int16Array)
DEFINE_ARRAY_SETTER_INTRINSIC(Uint16Array)
DEFINE_ARRAY_SETTER_INTRINSIC(Int32Array)
DEFINE_ARRAY_SETTER_INTRINSIC(Uint32Array)
DEFINE_ARRAY_SETTER_INTRINSIC(Int64Array)
DEFINE_ARRAY_SETTER_INTRINSIC(Uint64Array)

#undef DEFINE_ARRAY_SETTER_INTRINSIC

#define DEFINE_FLOAT_ARRAY_SETTER_INTRINSIC(enum_name)                         \
  bool GraphIntrinsifier::Build_##enum_name##SetIndexed(                       \
      FlowGraph* flow_graph) {                                                 \
    if (FLAG_target_address_sanitizer) return false;                           \
    if (FLAG_target_memory_sanitizer) return false;                            \
    if (FLAG_target_thread_sanitizer) return false;                            \
    return IntrinsifyArraySetIndexed(                                          \
        flow_graph, MethodRecognizer::MethodKindToReceiverCid(                 \
                        MethodRecognizer::k##enum_name##SetIndexed));          \
  }

DEFINE_FLOAT_ARRAY_SETTER_INTRINSIC(Float64Array)
DEFINE_FLOAT_ARRAY_SETTER_INTRINSIC(Float32Array)

#undef DEFINE_FLOAT_ARRAY_SETTER_INTRINSIC

#define DEFINE_SIMD_ARRAY_SETTER_INTRINSIC(enum_name)                          \
  bool GraphIntrinsifier::Build_##enum_name##SetIndexed(                       \
      FlowGraph* flow_graph) {                                                 \
    if (!FlowGraphCompiler::SupportsUnboxedSimd128()) {                        \
      return false;                                                            \
    }                                                                          \
    return IntrinsifyArraySetIndexed(                                          \
        flow_graph, MethodRecognizer::MethodKindToReceiverCid(                 \
                        MethodRecognizer::k##enum_name##SetIndexed));          \
  }

DEFINE_SIMD_ARRAY_SETTER_INTRINSIC(Float32x4Array)
DEFINE_SIMD_ARRAY_SETTER_INTRINSIC(Int32x4Array)
DEFINE_SIMD_ARRAY_SETTER_INTRINSIC(Float64x2Array)

#undef DEFINE_SIMD_ARRAY_SETTER_INTRINSIC

static bool BuildSimdOp(FlowGraph* flow_graph, intptr_t cid, Token::Kind kind) {
  if (!FlowGraphCompiler::SupportsUnboxedSimd128()) return false;

  auto const rep = RepresentationForCid(cid);

  Zone* zone = flow_graph->zone();
  GraphEntryInstr* graph_entry = flow_graph->graph_entry();
  auto normal_entry = graph_entry->normal_entry();
  BlockBuilder builder(flow_graph, normal_entry, /*with_frame=*/false);

  Definition* left = builder.AddParameter(0);
  Definition* right = builder.AddParameter(1);

  VerifyParameterIsBoxed(&builder, 0);
  VerifyParameterIsBoxed(&builder, 1);

  Cids* value_check = Cids::CreateMonomorphic(zone, cid);
  // Check argument. Receiver (left) is known to match cid by dispatch.
  builder.AddInstruction(new CheckClassInstr(new Value(right), DeoptId::kNone,
                                             *value_check, builder.Source()));
  Definition* left_simd = builder.AddUnboxInstr(
      rep, new Value(left), UnboxInstr::ValueMode::kHasValidType);

  Definition* right_simd = builder.AddUnboxInstr(
      rep, new Value(right), UnboxInstr::ValueMode::kHasValidType);

  Definition* unboxed_result = builder.AddDefinition(SimdOpInstr::Create(
      SimdOpInstr::KindForOperator(cid, kind), new Value(left_simd),
      new Value(right_simd), DeoptId::kNone));
  Definition* result = CreateBoxedResultIfNeeded(&builder, unboxed_result, rep);

  builder.AddReturn(new Value(result));
  return true;
}

bool GraphIntrinsifier::Build_Float32x4Mul(FlowGraph* flow_graph) {
  return BuildSimdOp(flow_graph, kFloat32x4Cid, Token::kMUL);
}

bool GraphIntrinsifier::Build_Float32x4Div(FlowGraph* flow_graph) {
  return BuildSimdOp(flow_graph, kFloat32x4Cid, Token::kDIV);
}

bool GraphIntrinsifier::Build_Float32x4Sub(FlowGraph* flow_graph) {
  return BuildSimdOp(flow_graph, kFloat32x4Cid, Token::kSUB);
}

bool GraphIntrinsifier::Build_Float32x4Add(FlowGraph* flow_graph) {
  return BuildSimdOp(flow_graph, kFloat32x4Cid, Token::kADD);
}

bool GraphIntrinsifier::Build_Int32x4Add(FlowGraph* flow_graph) {
  return BuildSimdOp(flow_graph, kInt32x4Cid, Token::kADD);
}

bool GraphIntrinsifier::Build_Int32x4Sub(FlowGraph* flow_graph) {
  return BuildSimdOp(flow_graph, kInt32x4Cid, Token::kSUB);
}

bool GraphIntrinsifier::Build_Int32x4BitAnd(FlowGraph* flow_graph) {
  return BuildSimdOp(flow_graph, kInt32x4Cid, Token::kBIT_AND);
}

bool GraphIntrinsifier::Build_Int32x4BitOr(FlowGraph* flow_graph) {
  return BuildSimdOp(flow_graph, kInt32x4Cid, Token::kBIT_OR);
}

bool GraphIntrinsifier::Build_Int32x4BitXor(FlowGraph* flow_graph) {
  return BuildSimdOp(flow_graph, kInt32x4Cid, Token::kBIT_XOR);
}

bool GraphIntrinsifier::Build_Float64x2Mul(FlowGraph* flow_graph) {
  return BuildSimdOp(flow_graph, kFloat64x2Cid, Token::kMUL);
}

bool GraphIntrinsifier::Build_Float64x2Div(FlowGraph* flow_graph) {
  return BuildSimdOp(flow_graph, kFloat64x2Cid, Token::kDIV);
}

bool GraphIntrinsifier::Build_Float64x2Sub(FlowGraph* flow_graph) {
  return BuildSimdOp(flow_graph, kFloat64x2Cid, Token::kSUB);
}

bool GraphIntrinsifier::Build_Float64x2Add(FlowGraph* flow_graph) {
  return BuildSimdOp(flow_graph, kFloat64x2Cid, Token::kADD);
}

static bool BuildFloat32x4Get(FlowGraph* flow_graph,
                              MethodRecognizer::Kind kind) {
  if (!FlowGraphCompiler::SupportsUnboxedSimd128()) {
    return false;
  }
  GraphEntryInstr* graph_entry = flow_graph->graph_entry();
  auto normal_entry = graph_entry->normal_entry();
  BlockBuilder builder(flow_graph, normal_entry, /*with_frame=*/false);

  Definition* receiver = builder.AddParameter(0);

  const auto& function = flow_graph->function();
  Definition* unboxed_receiver =
      !function.is_unboxed_parameter_at(0)
          ? builder.AddUnboxInstr(kUnboxedFloat32x4, new Value(receiver),
                                  UnboxInstr::ValueMode::kHasValidType)
          : receiver;

  Definition* unboxed_result = builder.AddDefinition(
      SimdOpInstr::Create(kind, new Value(unboxed_receiver), DeoptId::kNone));

  Definition* result =
      CreateBoxedResultIfNeeded(&builder, unboxed_result, kUnboxedDouble);

  builder.AddReturn(new Value(result));
  return true;
}

bool GraphIntrinsifier::Build_Float32x4GetX(FlowGraph* flow_graph) {
  return BuildFloat32x4Get(flow_graph, MethodRecognizer::kFloat32x4GetX);
}

bool GraphIntrinsifier::Build_Float32x4GetY(FlowGraph* flow_graph) {
  return BuildFloat32x4Get(flow_graph, MethodRecognizer::kFloat32x4GetY);
}

bool GraphIntrinsifier::Build_Float32x4GetZ(FlowGraph* flow_graph) {
  return BuildFloat32x4Get(flow_graph, MethodRecognizer::kFloat32x4GetZ);
}

bool GraphIntrinsifier::Build_Float32x4GetW(FlowGraph* flow_graph) {
  return BuildFloat32x4Get(flow_graph, MethodRecognizer::kFloat32x4GetW);
}

static bool BuildLoadField(FlowGraph* flow_graph, const Slot& field) {
  GraphEntryInstr* graph_entry = flow_graph->graph_entry();
  auto normal_entry = graph_entry->normal_entry();
  BlockBuilder builder(flow_graph, normal_entry, /*with_frame=*/false);

  Definition* array = builder.AddParameter(0);
  VerifyParameterIsBoxed(&builder, 0);

  Definition* length = builder.AddDefinition(
      new LoadFieldInstr(new Value(array), field, builder.Source()));

  length = CreateUnboxedResultIfNeeded(&builder, length);
  builder.AddReturn(new Value(length));
  return true;
}

bool GraphIntrinsifier::Build_ObjectArrayLength(FlowGraph* flow_graph) {
  return BuildLoadField(flow_graph, Slot::Array_length());
}

bool GraphIntrinsifier::Build_GrowableArrayLength(FlowGraph* flow_graph) {
  return BuildLoadField(flow_graph, Slot::GrowableObjectArray_length());
}

bool GraphIntrinsifier::Build_StringBaseLength(FlowGraph* flow_graph) {
  return BuildLoadField(flow_graph, Slot::String_length());
}

bool GraphIntrinsifier::Build_TypedListBaseLength(FlowGraph* flow_graph) {
  return BuildLoadField(flow_graph, Slot::TypedDataBase_length());
}

bool GraphIntrinsifier::Build_ByteDataViewLength(FlowGraph* flow_graph) {
  return BuildLoadField(flow_graph, Slot::TypedDataBase_length());
}

bool GraphIntrinsifier::Build_GrowableArrayCapacity(FlowGraph* flow_graph) {
  GraphEntryInstr* graph_entry = flow_graph->graph_entry();
  auto normal_entry = graph_entry->normal_entry();
  BlockBuilder builder(flow_graph, normal_entry, /*with_frame=*/false);

  Definition* array = builder.AddParameter(0);
  VerifyParameterIsBoxed(&builder, 0);

  Definition* backing_store = builder.AddDefinition(new LoadFieldInstr(
      new Value(array), Slot::GrowableObjectArray_data(), builder.Source()));
  Definition* capacity = builder.AddDefinition(new LoadFieldInstr(
      new Value(backing_store), Slot::Array_length(), builder.Source()));
  capacity = CreateUnboxedResultIfNeeded(&builder, capacity);
  builder.AddReturn(new Value(capacity));
  return true;
}

bool GraphIntrinsifier::Build_ObjectArraySetIndexedUnchecked(
    FlowGraph* flow_graph) {
  GraphEntryInstr* graph_entry = flow_graph->graph_entry();
  auto normal_entry = graph_entry->normal_entry();
  BlockBuilder builder(flow_graph, normal_entry, /*with_frame=*/false);

  Definition* array = builder.AddParameter(0);
  Definition* index = builder.AddParameter(1);
  Definition* value = builder.AddParameter(2);

  VerifyParameterIsBoxed(&builder, 0);
  VerifyParameterIsBoxed(&builder, 2);

  index = CreateBoxedParameterIfNeeded(&builder, index, kUnboxedInt64, 1);
  index = PrepareIndexedOp(flow_graph, &builder, array, index,
                           Slot::Array_length());

  builder.AddInstruction(new StoreIndexedInstr(
      new Value(array), new Value(index), new Value(value), kEmitStoreBarrier,
      /*index_unboxed=*/false,
      /*index_scale=*/target::Instance::ElementSizeFor(kArrayCid), kArrayCid,
      kAlignedAccess, DeoptId::kNone, builder.Source()));
  // Return null.
  Definition* null_def = builder.AddNullDefinition();
  builder.AddReturn(new Value(null_def));
  return true;
}

bool GraphIntrinsifier::Build_GrowableArraySetIndexedUnchecked(
    FlowGraph* flow_graph) {
  GraphEntryInstr* graph_entry = flow_graph->graph_entry();
  auto normal_entry = graph_entry->normal_entry();
  BlockBuilder builder(flow_graph, normal_entry, /*with_frame=*/false);

  Definition* array = builder.AddParameter(0);
  Definition* index = builder.AddParameter(1);
  Definition* value = builder.AddParameter(2);

  VerifyParameterIsBoxed(&builder, 0);
  VerifyParameterIsBoxed(&builder, 2);

  index = CreateBoxedParameterIfNeeded(&builder, index, kUnboxedInt64, 1);
  index = PrepareIndexedOp(flow_graph, &builder, array, index,
                           Slot::GrowableObjectArray_length());

  Definition* backing_store = builder.AddDefinition(new LoadFieldInstr(
      new Value(array), Slot::GrowableObjectArray_data(), builder.Source()));

  builder.AddInstruction(new StoreIndexedInstr(
      new Value(backing_store), new Value(index), new Value(value),
      kEmitStoreBarrier, /*index_unboxed=*/false,
      /*index_scale=*/target::Instance::ElementSizeFor(kArrayCid), kArrayCid,
      kAlignedAccess, DeoptId::kNone, builder.Source()));
  // Return null.
  Definition* null_def = builder.AddNullDefinition();
  builder.AddReturn(new Value(null_def));
  return true;
}

bool GraphIntrinsifier::Build_GrowableArraySetData(FlowGraph* flow_graph) {
  GraphEntryInstr* graph_entry = flow_graph->graph_entry();
  auto normal_entry = graph_entry->normal_entry();
  BlockBuilder builder(flow_graph, normal_entry, /*with_frame=*/false);

  Definition* growable_array = builder.AddParameter(0);
  Definition* data = builder.AddParameter(1);
  Zone* zone = flow_graph->zone();

  VerifyParameterIsBoxed(&builder, 0);
  VerifyParameterIsBoxed(&builder, 1);

  Cids* value_check = Cids::CreateMonomorphic(zone, kArrayCid);
  builder.AddInstruction(new CheckClassInstr(new Value(data), DeoptId::kNone,
                                             *value_check, builder.Source()));

  builder.AddInstruction(new StoreFieldInstr(
      Slot::GrowableObjectArray_data(), new Value(growable_array),
      new Value(data), kEmitStoreBarrier, builder.Source()));
  // Return null.
  Definition* null_def = builder.AddNullDefinition();
  builder.AddReturn(new Value(null_def));
  return true;
}

bool GraphIntrinsifier::Build_GrowableArraySetLength(FlowGraph* flow_graph) {
  GraphEntryInstr* graph_entry = flow_graph->graph_entry();
  auto normal_entry = graph_entry->normal_entry();
  BlockBuilder builder(flow_graph, normal_entry, /*with_frame=*/false);

  Definition* growable_array = builder.AddParameter(0);
  Definition* length = builder.AddParameter(1);

  VerifyParameterIsBoxed(&builder, 0);
  VerifyParameterIsBoxed(&builder, 1);

  builder.AddInstruction(
      new CheckSmiInstr(new Value(length), DeoptId::kNone, builder.Source()));
  builder.AddInstruction(new StoreFieldInstr(
      Slot::GrowableObjectArray_length(), new Value(growable_array),
      new Value(length), kNoStoreBarrier, builder.Source()));
  Definition* null_def = builder.AddNullDefinition();
  builder.AddReturn(new Value(null_def));
  return true;
}

static bool BuildUnarySmiOp(FlowGraph* flow_graph, Token::Kind op_kind) {
  ASSERT(!flow_graph->function().has_unboxed_return());
  ASSERT(!flow_graph->function().is_unboxed_parameter_at(0));
  GraphEntryInstr* graph_entry = flow_graph->graph_entry();
  auto normal_entry = graph_entry->normal_entry();
  BlockBuilder builder(flow_graph, normal_entry, /*with_frame=*/false);
  Definition* left = builder.AddParameter(0);
  builder.AddInstruction(
      new CheckSmiInstr(new Value(left), DeoptId::kNone, builder.Source()));
  Definition* result = builder.AddDefinition(
      new UnarySmiOpInstr(op_kind, new Value(left), DeoptId::kNone));
  builder.AddReturn(new Value(result));
  return true;
}

bool GraphIntrinsifier::Build_Smi_bitNegate(FlowGraph* flow_graph) {
  return BuildUnarySmiOp(flow_graph, Token::kBIT_NOT);
}

bool GraphIntrinsifier::Build_Integer_negate(FlowGraph* flow_graph) {
  return BuildUnarySmiOp(flow_graph, Token::kNEGATE);
}

static bool BuildBinarySmiOp(FlowGraph* flow_graph, Token::Kind op_kind) {
  ASSERT(!flow_graph->function().has_unboxed_return());
  ASSERT(!flow_graph->function().is_unboxed_parameter_at(0));
  ASSERT(!flow_graph->function().is_unboxed_parameter_at(1));
  GraphEntryInstr* graph_entry = flow_graph->graph_entry();
  auto normal_entry = graph_entry->normal_entry();
  BlockBuilder builder(flow_graph, normal_entry, /*with_frame=*/false);
  Definition* left = builder.AddParameter(0);
  Definition* right = builder.AddParameter(1);
  builder.AddInstruction(
      new CheckSmiInstr(new Value(left), DeoptId::kNone, builder.Source()));
  builder.AddInstruction(
      new CheckSmiInstr(new Value(right), DeoptId::kNone, builder.Source()));
  Definition* result = builder.AddDefinition(new BinarySmiOpInstr(
      op_kind, new Value(left), new Value(right), DeoptId::kNone));
  builder.AddReturn(new Value(result));
  return true;
}

bool GraphIntrinsifier::Build_Integer_add(FlowGraph* flow_graph) {
  return BuildBinarySmiOp(flow_graph, Token::kADD);
}

bool GraphIntrinsifier::Build_Integer_sub(FlowGraph* flow_graph) {
  return BuildBinarySmiOp(flow_graph, Token::kSUB);
}

bool GraphIntrinsifier::Build_Integer_mul(FlowGraph* flow_graph) {
  return BuildBinarySmiOp(flow_graph, Token::kMUL);
}

bool GraphIntrinsifier::Build_Integer_mod(FlowGraph* flow_graph) {
  return BuildBinarySmiOp(flow_graph, Token::kMOD);
}

bool GraphIntrinsifier::Build_Integer_truncDivide(FlowGraph* flow_graph) {
  return BuildBinarySmiOp(flow_graph, Token::kTRUNCDIV);
}

bool GraphIntrinsifier::Build_Integer_bitAnd(FlowGraph* flow_graph) {
  return BuildBinarySmiOp(flow_graph, Token::kBIT_AND);
}

bool GraphIntrinsifier::Build_Integer_bitOr(FlowGraph* flow_graph) {
  return BuildBinarySmiOp(flow_graph, Token::kBIT_OR);
}

bool GraphIntrinsifier::Build_Integer_bitXor(FlowGraph* flow_graph) {
  return BuildBinarySmiOp(flow_graph, Token::kBIT_XOR);
}

bool GraphIntrinsifier::Build_Integer_sar(FlowGraph* flow_graph) {
  return BuildBinarySmiOp(flow_graph, Token::kSHR);
}

bool GraphIntrinsifier::Build_Integer_shr(FlowGraph* flow_graph) {
  return BuildBinarySmiOp(flow_graph, Token::kUSHR);
}

static Definition* ConvertOrUnboxDoubleParameter(
    BlockBuilder* builder,
    Definition* value,
    intptr_t index,
    UnboxInstr::ValueMode value_mode) {
  const auto& function = builder->function();
  if (function.is_unboxed_double_parameter_at(index)) {
    return value;
  } else if (function.is_unboxed_integer_parameter_at(index)) {
    if (compiler::target::kWordSize == 4) {
      // Int64ToDoubleInstr is not implemented in 32-bit platforms
      return nullptr;
    }
    auto to_double = new Int64ToDoubleInstr(new Value(value), DeoptId::kNone);
    return builder->AddDefinition(to_double);
  } else {
    ASSERT(!function.is_unboxed_parameter_at(index));
    return builder->AddUnboxInstr(kUnboxedDouble, value, value_mode);
  }
}

bool GraphIntrinsifier::Build_DoubleFlipSignBit(FlowGraph* flow_graph) {
  GraphEntryInstr* graph_entry = flow_graph->graph_entry();
  auto normal_entry = graph_entry->normal_entry();
  BlockBuilder builder(flow_graph, normal_entry, /*with_frame=*/false);

  Definition* receiver = builder.AddParameter(0);
  Definition* unboxed_value = ConvertOrUnboxDoubleParameter(
      &builder, receiver, 0, UnboxInstr::ValueMode::kHasValidType);
  if (unboxed_value == nullptr) {
    return false;
  }
  Definition* unboxed_result = builder.AddDefinition(new UnaryDoubleOpInstr(
      Token::kNEGATE, new Value(unboxed_value), DeoptId::kNone));
  Definition* result =
      CreateBoxedResultIfNeeded(&builder, unboxed_result, kUnboxedDouble);
  builder.AddReturn(new Value(result));
  return true;
}

static Definition* GetThreadLocalValue(FlowGraph* flow_graph,
                                       BlockBuilder* builder) {
  Definition* thread = builder->AddDefinition(new LoadThreadInstr());
  Definition* array = builder->AddDefinition(
      new LoadFieldInstr(new Value(thread),
                         /*slot=*/Slot::Thread_thread_locals(),
                         InnerPointerAccess::kNotUntagged, builder->Source(),
                         /*calls_initializer=*/false, DeoptId::kNone,
                         compiler::Assembler::kRelaxedNonAtomic));
  Definition* index = builder->AddParameter(0);

  Definition* safe_index =
      PrepareIndexedOp(flow_graph, builder, array, index, Slot::Array_length());

  Definition* local = builder->AddDefinition(new LoadIndexedInstr(
      new Value(array), new Value(safe_index), /*index_unboxed=*/false,
      /*index_scale=*/target::Instance::ElementSizeFor(kArrayCid), kArrayCid,
      kAlignedAccess, DeoptId::kNone, builder->Source(),
      new CompileType(CompileType::FromAbstractType(
          Type::ZoneHandle(Type::ObjectType()), CompileType::kCanBeNull,
          CompileType::kCanBeSentinel))));
  return local;
}

bool GraphIntrinsifier::Build_ThreadLocalGetValue(FlowGraph* flow_graph) {
  GraphEntryInstr* graph_entry = flow_graph->graph_entry();
  auto normal_entry = graph_entry->normal_entry();
  BlockBuilder builder(flow_graph, normal_entry, /*with_frame=*/false);
  Definition* local = GetThreadLocalValue(flow_graph, &builder);

  builder.AddReturn(new Value(local));
  return true;
}

bool GraphIntrinsifier::Build_ThreadLocalHasValue(FlowGraph* flow_graph) {
  GraphEntryInstr* graph_entry = flow_graph->graph_entry();
  auto normal_entry = graph_entry->normal_entry();
  BlockBuilder builder(flow_graph, normal_entry, /*with_frame=*/false);
  Definition* local = GetThreadLocalValue(flow_graph, &builder);

  Definition* sentinel = flow_graph->GetConstant(Object::sentinel());
  Definition* result = builder.AddDefinition(new StrictCompareInstr(
      builder.Source(), Token::kNE_STRICT, new Value(local),
      new Value(sentinel), /*needs_number_check=*/false, DeoptId::kNone));

  builder.AddReturn(new Value(result));
  return true;
}

}  // namespace compiler
}  // namespace dart
