c919ade017
All inputs of IL instructions are now non-speculative, SelectRepresentations pass now always inserts non-speculative unboxing instructions. In call specializer, where JIT compiler speculates, speculative Unbox instructions are now explicitly added for inputs which may have incorrect type. Unbox instructions now have 'value_mode' (either kHasValidType or kCheckType) instead of speculative mode. Added canonicalization rule to change value mode if compiler can prove that value has correct type. Also, cleaned up speculations around UnboxedInt32 representation in JIT mode, which aligns this representation with other unboxed representations. TEST=ci Change-Id: I52f5c959c3f6515b1a7c9e8ab15b420beed0e3f5 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/391492 Commit-Queue: Alexander Markov <alexmarkov@google.com> Reviewed-by: Slava Egorov <vegorov@google.com>
726 lines
28 KiB
C++
726 lines
28 KiB
C++
// 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) { \
|
|
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) { \
|
|
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 be a Float32x4.
|
|
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_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;
|
|
}
|
|
|
|
} // namespace compiler
|
|
} // namespace dart
|