[vm/compiler] Add new optimization pass which inlines typed data accesses

Based on the unified typed data layout, we can now inline accesses to
typed data interface classes if there are no 3rd party implementations
of those interfaces.

Example: If a receiver is of type Uint8List and we call `[]` or `[]=` we
will inline the byte access.

Instead of changing the existing inliner / call specializer we add this
as an extra pass: If the inliner / call specializer infer that the
receiver type is e.g. internal typed data then it will perform the
inlining itself using more optimized LoadIndexed instruction.

=> Only if those existing optimization passes have not been able to inline
   the access will we, later on in the compilation pipeline, run a
   specialized pass which will inline the accesses using LoadUntagged +
   LoadIndexed (which is slightly less efficient than using only LoadIndexed
   for internal typed data).

As a first step this is only done for AOT.

For ease of writing tests matching certain IR graphs this CL also adds a
IR pattern matcher.

Issue https://github.com/dart-lang/sdk/issues/35154

Cq-Include-Trybots: luci.dart.try:vm-canary-linux-debug-try, vm-dartkb-linux-debug-x64-try, vm-dartkb-linux-release-x64-try, vm-kernel-asan-linux-release-x64-try, vm-kernel-checked-linux-release-x64-try, vm-kernel-linux-debug-ia32-try, vm-kernel-linux-debug-simdbc64-try, vm-kernel-linux-debug-x64-try, vm-kernel-linux-product-x64-try, vm-kernel-linux-release-ia32-try, vm-kernel-linux-release-simarm-try, vm-kernel-linux-release-simarm64-try, vm-kernel-linux-release-simdbc64-try, vm-kernel-linux-release-x64-try, vm-kernel-optcounter-threshold-linux-release-ia32-try, vm-kernel-optcounter-threshold-linux-release-x64-try, vm-kernel-precomp-android-release-arm-try, vm-kernel-precomp-bare-linux-release-simarm-try, vm-kernel-precomp-bare-linux-release-simarm64-try, vm-kernel-precomp-bare-linux-release-x64-try, vm-kernel-precomp-linux-debug-x64-try, vm-kernel-precomp-linux-product-x64-try, vm-kernel-precomp-linux-release-simarm-try, vm-kernel-precomp-linux-release-simarm64-try, vm-kernel-precomp-linux-release-x64-try, vm-kernel-precomp-obfuscate-linux-release-x64-try, vm-kernel-precomp-win-release-simarm64-try, vm-kernel-precomp-win-release-x64-try, vm-kernel-reload-linux-debug-x64-try, vm-kernel-reload-linux-release-x64-try, vm-kernel-reload-rollback-linux-debug-x64-try, vm-kernel-reload-rollback-linux-release-x64-try, vm-kernel-win-debug-ia32-try, vm-kernel-win-debug-x64-try, vm-kernel-win-product-x64-try, vm-kernel-win-release-ia32-try, vm-kernel-win-release-x64-try

Change-Id: I5f2e01a55f46b473f64478b05679f65b9fd7c4c8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/98662
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
This commit is contained in:
Martin Kustermann
2019-04-05 15:51:34 +00:00
committed by commit-bot@chromium.org
parent dbc9ff11bf
commit dbfd00f44d
14 changed files with 1165 additions and 47 deletions
+16 -13
View File
@@ -1088,19 +1088,6 @@ void ClassFinalizer::FinalizeTypesInClass(const Class& cls) {
interface_class.AddDirectImplementor(cls,
/* is_mixin = */ i == mixin_index);
}
if (FLAG_use_cha_deopt) {
// Invalidate all CHA code which depends on knowing the implementors of any
// of the interfaces implemented by this new class.
ClassTable* class_table = thread->isolate()->class_table();
GrowableArray<intptr_t> cids;
InterfaceFinder finder(zone, class_table, &cids);
finder.FindAllInterfaces(cls);
for (intptr_t j = 0; j < cids.length(); ++j) {
interface_class = class_table->At(cids[j]);
interface_class.DisableCHAImplementorUsers();
}
}
}
void ClassFinalizer::FinalizeClass(const Class& cls) {
@@ -1169,6 +1156,22 @@ void ClassFinalizer::FinalizeClass(const Class& cls) {
RemoveCHAOptimizedCode(cls, cids);
}
if (FLAG_use_cha_deopt) {
Zone* zone = thread->zone();
ClassTable* class_table = thread->isolate()->class_table();
auto& interface_class = Class::Handle(zone);
// We scan every interface this [cls] implements and invalidate all CHA code
// which depends on knowing the implementors of that interface.
GrowableArray<intptr_t> cids;
InterfaceFinder finder(zone, class_table, &cids);
finder.FindAllInterfaces(cls);
for (intptr_t j = 0; j < cids.length(); ++j) {
interface_class = class_table->At(cids[j]);
interface_class.DisableCHAImplementorUsers();
}
}
if (cls.is_enum_class()) {
AllocateEnumValues(cls);
}
@@ -767,8 +767,6 @@ static void EnsureICData(Zone* zone,
// TODO(dartbug.com/30635) Evaluate how much this can be shared with
// JitCallSpecializer.
void AotCallSpecializer::VisitInstanceCall(InstanceCallInstr* instr) {
ASSERT(FLAG_precompiled_mode);
// Type test is special as it always gets converted into inlined code.
const Token::Kind op_kind = instr->token_kind();
if (Token::IsTypeTestOperator(op_kind)) {
+4 -2
View File
@@ -5055,13 +5055,15 @@ StoreIndexedInstr::StoreIndexedInstr(Value* array,
intptr_t class_id,
AlignmentType alignment,
intptr_t deopt_id,
TokenPosition token_pos)
TokenPosition token_pos,
SpeculativeMode speculative_mode)
: TemplateInstruction(deopt_id),
emit_store_barrier_(emit_store_barrier),
index_scale_(index_scale),
class_id_(class_id),
alignment_(StrengthenAlignment(class_id, alignment)),
token_pos_(token_pos) {
token_pos_(token_pos),
speculative_mode_(speculative_mode) {
SetInputAt(kArrayPos, array);
SetInputAt(kIndexPos, index);
SetInputAt(kValuePos, value);
+14 -3
View File
@@ -4723,7 +4723,8 @@ class StoreIndexedInstr : public TemplateInstruction<3, NoThrow> {
intptr_t class_id,
AlignmentType alignment,
intptr_t deopt_id,
TokenPosition token_pos);
TokenPosition token_pos,
SpeculativeMode speculative_mode = kGuardInputs);
DECLARE_INSTRUCTION(StoreIndexed)
enum { kArrayPos = 0, kIndexPos = 1, kValuePos = 2 };
@@ -4747,6 +4748,8 @@ class StoreIndexedInstr : public TemplateInstruction<3, NoThrow> {
(emit_store_barrier_ == kEmitStoreBarrier);
}
virtual SpeculativeMode speculative_mode() const { return speculative_mode_; }
virtual bool ComputeCanDeoptimize() const { return false; }
virtual Representation RequiredInputRepresentation(intptr_t idx) const;
@@ -4773,6 +4776,7 @@ class StoreIndexedInstr : public TemplateInstruction<3, NoThrow> {
const intptr_t class_id_;
const AlignmentType alignment_;
const TokenPosition token_pos_;
const SpeculativeMode speculative_mode_;
DISALLOW_COPY_AND_ASSIGN(StoreIndexedInstr);
};
@@ -6275,6 +6279,7 @@ class CheckedSmiOpInstr : public TemplateDefinition<2, Throws> {
virtual bool ComputeCanDeoptimize() const { return false; }
virtual CompileType ComputeType() const;
virtual bool RecomputeType();
virtual bool HasUnknownSideEffects() const { return true; }
@@ -7022,8 +7027,10 @@ class DoubleToDoubleInstr : public TemplateDefinition<1, NoThrow, Pure> {
class DoubleToFloatInstr : public TemplateDefinition<1, NoThrow, Pure> {
public:
DoubleToFloatInstr(Value* value, intptr_t deopt_id)
: TemplateDefinition(deopt_id) {
DoubleToFloatInstr(Value* value,
intptr_t deopt_id,
SpeculativeMode speculative_mode = kGuardInputs)
: TemplateDefinition(deopt_id), speculative_mode_(speculative_mode) {
SetInputAt(0, value);
}
@@ -7048,6 +7055,8 @@ class DoubleToFloatInstr : public TemplateDefinition<1, NoThrow, Pure> {
return kUnboxedDouble;
}
virtual SpeculativeMode speculative_mode() const { return speculative_mode_; }
virtual intptr_t DeoptimizationTarget() const { return GetDeoptId(); }
virtual bool AttributesEqual(Instruction* other) const { return true; }
@@ -7055,6 +7064,8 @@ class DoubleToFloatInstr : public TemplateDefinition<1, NoThrow, Pure> {
virtual Definition* Canonicalize(FlowGraph* flow_graph);
private:
const SpeculativeMode speculative_mode_;
DISALLOW_COPY_AND_ASSIGN(DoubleToFloatInstr);
};
+211 -25
View File
@@ -9,6 +9,7 @@
#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/call_specializer.h"
#include "vm/compiler/compiler_pass.h"
@@ -21,11 +22,12 @@
namespace dart {
RawLibrary* LoadTestScript(const char* script,
Dart_NativeEntryResolver resolver) {
Dart_NativeEntryResolver resolver,
const char* lib_uri) {
Dart_Handle api_lib;
{
TransitionVMToNative transition(Thread::Current());
api_lib = TestCase::LoadTestScript(script, resolver);
api_lib = TestCase::LoadTestScript(script, resolver, lib_uri);
}
auto& lib = Library::Handle();
lib ^= Api::UnwrapHandle(api_lib);
@@ -59,64 +61,248 @@ FlowGraph* TestPipeline::Run(bool is_aot,
auto pipeline = CompilationPipeline::New(zone, function_);
auto parsed_function = new (zone)
parsed_function_ = new (zone)
ParsedFunction(thread, Function::ZoneHandle(zone, function_.raw()));
pipeline->ParseFunction(parsed_function);
pipeline->ParseFunction(parsed_function_);
// Extract type feedback before the graph is built, as the graph
// builder uses it to attach it to nodes.
auto ic_data_array = new (zone) ZoneGrowableArray<const ICData*>();
ic_data_array_ = new (zone) ZoneGrowableArray<const ICData*>();
if (!is_aot) {
function_.RestoreICDataMap(ic_data_array, /*clone_ic_data=*/false);
function_.RestoreICDataMap(ic_data_array_, /*clone_ic_data=*/false);
}
FlowGraph* flow_graph = pipeline->BuildFlowGraph(
zone, parsed_function, ic_data_array, osr_id, optimized);
flow_graph_ = pipeline->BuildFlowGraph(zone, parsed_function_, ic_data_array_,
osr_id, optimized);
if (is_aot) {
flow_graph->PopulateWithICData(function_);
flow_graph_->PopulateWithICData(function_);
}
BlockScheduler block_scheduler(flow_graph);
BlockScheduler block_scheduler(flow_graph_);
const bool reorder_blocks =
FlowGraph::ShouldReorderBlocks(function_, optimized);
if (reorder_blocks) {
if (!is_aot && reorder_blocks) {
block_scheduler.AssignEdgeWeights();
}
SpeculativeInliningPolicy speculative_policy(/*enable_blacklist=*/false);
CompilerPassState pass_state(thread, flow_graph, &speculative_policy);
pass_state.block_scheduler = &block_scheduler;
pass_state.reorder_blocks = reorder_blocks;
pass_state_ = new CompilerPassState(thread, flow_graph_, &speculative_policy);
pass_state_->block_scheduler = &block_scheduler;
pass_state_->reorder_blocks = reorder_blocks;
if (optimized) {
pass_state.inline_id_to_function.Add(&function_);
pass_state_->inline_id_to_function.Add(&function_);
// We do not add the token position now because we don't know the
// position of the inlined call until later. A side effect of this
// is that the length of |inline_id_to_function| is always larger
// than the length of |inline_id_to_token_pos| by one.
// Top scope function has no caller (-1). We do this because we expect
// all token positions to be at an inlined call.
pass_state.caller_inline_id.Add(-1);
pass_state_->caller_inline_id.Add(-1);
JitCallSpecializer jit_call_specializer(flow_graph, &speculative_policy);
AotCallSpecializer aot_call_specializer(/*precompiler=*/nullptr, flow_graph,
&speculative_policy);
JitCallSpecializer jit_call_specializer(flow_graph_, &speculative_policy);
AotCallSpecializer aot_call_specializer(/*precompiler=*/nullptr,
flow_graph_, &speculative_policy);
if (is_aot) {
pass_state.call_specializer = &aot_call_specializer;
pass_state_->call_specializer = &aot_call_specializer;
} else {
pass_state.call_specializer = &jit_call_specializer;
pass_state_->call_specializer = &jit_call_specializer;
}
const auto mode = is_aot ? CompilerPass::kJIT : CompilerPass::kAOT;
const auto mode = is_aot ? CompilerPass::kAOT : CompilerPass::kJIT;
if (passes.size() > 0) {
CompilerPass::RunPipelineWithPasses(&pass_state, passes);
CompilerPass::RunPipelineWithPasses(pass_state_, passes);
} else {
CompilerPass::RunPipeline(mode, &pass_state);
CompilerPass::RunPipeline(mode, pass_state_);
}
}
return flow_graph;
return flow_graph_;
}
void TestPipeline::CompileGraphAndAttachFunction() {
Zone* zone = thread_->zone();
const bool optimized = true;
SpeculativeInliningPolicy speculative_policy(/*enable_blacklist=*/false);
ASSERT(pass_state_->inline_id_to_function.length() ==
pass_state_->caller_inline_id.length());
ObjectPoolBuilder object_pool_builder;
Assembler assembler(&object_pool_builder, /*use_far_branches=*/true);
FlowGraphCompiler graph_compiler(
&assembler, flow_graph_, *parsed_function_, optimized,
&speculative_policy, pass_state_->inline_id_to_function,
pass_state_->inline_id_to_token_pos, pass_state_->caller_inline_id,
ic_data_array_);
graph_compiler.CompileGraph();
const auto& deopt_info_array =
Array::Handle(zone, graph_compiler.CreateDeoptInfo(&assembler));
const auto pool_attachment = Code::PoolAttachment::kAttachPool;
const auto& code = Code::Handle(Code::FinalizeCode(
&graph_compiler, &assembler, pool_attachment, optimized, nullptr));
code.set_is_optimized(optimized);
code.set_owner(function_);
graph_compiler.FinalizePcDescriptors(code);
code.set_deopt_info_array(deopt_info_array);
graph_compiler.FinalizeStackMaps(code);
graph_compiler.FinalizeVarDescriptors(code);
graph_compiler.FinalizeExceptionHandlers(code);
graph_compiler.FinalizeCatchEntryMovesMap(code);
graph_compiler.FinalizeStaticCallTargetsTable(code);
graph_compiler.FinalizeCodeSourceMap(code);
if (optimized) {
function_.InstallOptimizedCode(code);
} else {
function_.set_unoptimized_code(code);
function_.AttachCode(code);
}
}
bool ILMatcher::TryMatch(std::initializer_list<MatchCode> match_codes) {
std::vector<MatchCode> qcodes = match_codes;
if (trace_) {
OS::PrintErr("ILMatcher: Matching the following graph\n");
FlowGraphPrinter::PrintGraph("ILMatcher", flow_graph_);
OS::PrintErr("ILMatcher: Starting match at %s:\n", cursor_->ToCString());
}
Instruction* cursor = cursor_;
for (size_t i = 0; i < qcodes.size(); ++i) {
Instruction** capture = qcodes[i].capture_;
if (trace_) {
OS::PrintErr(" matching %30s @ %s\n",
MatchOpCodeToCString(qcodes[i].opcode()),
cursor->ToCString());
}
auto next = MatchInternal(qcodes, i, cursor);
if (next == nullptr) {
if (trace_) {
OS::PrintErr(" -> Match failed\n");
}
cursor = next;
break;
}
if (capture != nullptr) {
*capture = cursor;
}
cursor = next;
}
if (cursor != nullptr) {
cursor_ = cursor;
return true;
}
return false;
}
Instruction* ILMatcher::MatchInternal(std::vector<MatchCode> match_codes,
size_t i,
Instruction* cursor) {
const MatchOpCode opcode = match_codes[i].opcode();
if (opcode == kMatchAndMoveBranchTrue) {
auto branch = cursor->AsBranch();
if (branch == nullptr) return nullptr;
return branch->true_successor();
}
if (opcode == kMatchAndMoveBranchFalse) {
auto branch = cursor->AsBranch();
if (branch == nullptr) return nullptr;
return branch->false_successor();
}
if (opcode == kMoveAny) {
return cursor->next();
}
if (opcode == kMoveParallelMoves) {
while (cursor != nullptr && cursor->IsParallelMove()) {
cursor = cursor->next();
}
return cursor;
}
if (opcode == kMoveGlob) {
ASSERT((i + 1) < match_codes.size());
while (true) {
if (cursor == nullptr) return nullptr;
if (MatchInternal(match_codes, i + 1, cursor) != nullptr) {
return cursor;
}
if (auto as_goto = cursor->AsGoto()) {
cursor = as_goto->successor();
} else {
cursor = cursor->next();
}
}
}
if (opcode == kMatchAndMoveGoto) {
if (auto goto_instr = cursor->AsGoto()) {
return goto_instr->successor();
}
}
switch (opcode) {
#define EMIT_CASE(Instruction, _) \
case kMatch##Instruction: { \
if (cursor->Is##Instruction()) { \
return cursor; \
} \
return nullptr; \
} \
case kMatchAndMove##Instruction: { \
if (cursor->Is##Instruction()) { \
return cursor->next(); \
} \
return nullptr; \
}
FOR_EACH_INSTRUCTION(EMIT_CASE)
#undef EMIT_CASE
default:
UNREACHABLE();
}
UNREACHABLE();
return nullptr;
}
const char* ILMatcher::MatchOpCodeToCString(MatchOpCode opcode) {
if (opcode == kMatchAndMoveBranchTrue) {
return "kMatchAndMoveBranchTrue";
}
if (opcode == kMatchAndMoveBranchFalse) {
return "kMatchAndMoveBranchFalse";
}
if (opcode == kMoveAny) {
return "kMoveAny";
}
if (opcode == kMoveParallelMoves) {
return "kMoveParallelMoves";
}
if (opcode == kMoveGlob) {
return "kMoveGlob";
}
switch (opcode) {
#define EMIT_CASE(Instruction, _) \
case kMatch##Instruction: \
return "kMatch" #Instruction; \
case kMatchAndMove##Instruction: \
return "kMatchAndMove" #Instruction;
FOR_EACH_INSTRUCTION(EMIT_CASE)
#undef EMIT_CASE
default:
UNREACHABLE();
}
UNREACHABLE();
return nullptr;
}
} // namespace dart
+120 -2
View File
@@ -5,11 +5,15 @@
#ifndef RUNTIME_VM_COMPILER_BACKEND_IL_TEST_HELPER_H_
#define RUNTIME_VM_COMPILER_BACKEND_IL_TEST_HELPER_H_
#include <vector>
#include "include/dart_api.h"
#include "platform/allocation.h"
#include "vm/compiler/backend/il.h"
#include "vm/compiler/compiler_pass.h"
#include "vm/compiler/compiler_state.h"
#include "vm/unit_test.h"
// The helpers in this file make it easier to write C++ unit tests which assert
// that Dart code gets turned into certain IR.
@@ -47,18 +51,20 @@ class RawFunction;
class RawLibrary;
RawLibrary* LoadTestScript(const char* script,
Dart_NativeEntryResolver resolver = nullptr);
Dart_NativeEntryResolver resolver = nullptr,
const char* lib_uri = USER_TEST_URI);
RawFunction* GetFunction(const Library& lib, const char* name);
void Invoke(const Library& lib, const char* name);
class TestPipeline {
class TestPipeline : public ValueObject {
public:
explicit TestPipeline(const Function& function)
: function_(function),
thread_(Thread::Current()),
compiler_state_(thread_) {}
~TestPipeline() { delete pass_state_; }
FlowGraph* RunJITPasses(std::initializer_list<CompilerPass::Id> passes) {
return Run(/*is_aot=*/false, passes);
@@ -67,12 +73,124 @@ class TestPipeline {
return Run(/*is_aot=*/true, passes);
}
void CompileGraphAndAttachFunction();
private:
// As a side-effect this will populate
// - [ic_data_array_]
// - [parsed_function_]
// - [pass_state_]
// - [flow_graph_]
FlowGraph* Run(bool is_aot, std::initializer_list<CompilerPass::Id> passes);
const Function& function_;
Thread* thread_;
CompilerState compiler_state_;
ZoneGrowableArray<const ICData*>* ic_data_array_ = nullptr;
ParsedFunction* parsed_function_ = nullptr;
CompilerPassState* pass_state_ = nullptr;
FlowGraph* flow_graph_ = nullptr;
};
// Match opcodes used for [ILMatcher], see below.
enum MatchOpCode {
// Emit a match and match-and-move code for every instruction.
#define DEFINE_MATCH_OPCODES(Instruction, _) \
kMatch##Instruction, kMatchAndMove##Instruction,
FOR_EACH_INSTRUCTION(DEFINE_MATCH_OPCODES)
#undef DEFINE_MATCH_OPCODES
// Matches a branch and moves left.
kMatchAndMoveBranchTrue,
// Matches a branch and moves right.
kMatchAndMoveBranchFalse,
// Moves forward across any instruction.
kMoveAny,
// Moves over all parallel moves.
kMoveParallelMoves,
// Moves forward until the next match code matches.
kMoveGlob,
};
// Match codes used for [ILMatcher], see below.
class MatchCode {
public:
MatchCode(MatchOpCode opcode) // NOLINT
: opcode_(opcode), capture_(nullptr) {}
MatchCode(MatchOpCode opcode, Instruction** capture)
: opcode_(opcode), capture_(capture) {}
#define DEFINE_TYPED_CONSTRUCTOR(Type, ignored) \
MatchCode(MatchOpCode opcode, Type##Instr** capture) \
: opcode_(opcode), capture_(reinterpret_cast<Instruction**>(capture)) { \
RELEASE_ASSERT(opcode == kMatch##Type || opcode == kMatchAndMove##Type); \
}
FOR_EACH_INSTRUCTION(DEFINE_TYPED_CONSTRUCTOR)
#undef DEFINE_TYPED_CONSTRUCTOR
MatchOpCode opcode() { return opcode_; }
private:
friend class ILMatcher;
MatchOpCode opcode_;
Instruction** capture_;
};
// Used for matching a sequence of IL instructions including capturing support.
//
// Example:
//
// TargetEntryInstr* entry = ....;
// BranchInstr* branch = nullptr;
//
// ILMatcher matcher(flow_graph, entry);
// if (matcher.TryMatch({ kMoveGlob, {kMatchBranch, &branch}, })) {
// EXPECT(branch->operation_cid() == kMintCid);
// ...
// }
//
// This match will start at [entry], follow any number instructions (including
// [GotoInstr]s until a [BranchInstr] is found).
//
// If the match was successful, this returns `true` and updates the current
// value for the cursor.
class ILMatcher : public ValueObject {
public:
ILMatcher(FlowGraph* flow_graph, Instruction* cursor, bool trace = true)
: flow_graph_(flow_graph),
cursor_(cursor),
// clang-format off
#if !defined(PRODUCT)
trace_(trace) {}
#else
trace_(false) {}
#endif
// clang-format on
Instruction* value() { return cursor_; }
// From the current [value] according to match_codes.
//
// Returns `true` if the match was successful and cursor has been updated,
// otherwise returns `false`.
bool TryMatch(std::initializer_list<MatchCode> match_codes);
private:
Instruction* MatchInternal(std::vector<MatchCode> match_codes,
size_t i,
Instruction* cursor);
const char* MatchOpCodeToCString(MatchOpCode code);
FlowGraph* flow_graph_;
Instruction* cursor_;
bool trace_;
};
} // namespace dart
@@ -1458,6 +1458,10 @@ CompileType CheckedSmiOpInstr::ComputeType() const {
return CompileType::Dynamic();
}
bool CheckedSmiOpInstr::RecomputeType() {
return UpdateType(ComputeType());
}
CompileType CheckedSmiComparisonInstr::ComputeType() const {
if (Isolate::Current()->can_use_strong_mode_types()) {
CompileType* type = call()->Type();
@@ -0,0 +1,426 @@
// 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.
#include <vector>
#include "vm/compiler/backend/il_printer.h"
#include "vm/compiler/backend/il_test_helper.h"
#include "vm/compiler/call_specializer.h"
#include "vm/compiler/compiler_pass.h"
#include "vm/object.h"
#include "vm/unit_test.h"
namespace dart {
#if defined(DART_PRECOMPILER) && !defined(TARGET_ARCH_DBC)
// This test asserts that we are inlining accesses to typed data interfaces
// (e.g. Uint8List) if there are no instantiated 3rd party classes.
ISOLATE_UNIT_TEST_CASE(IRTest_TypedDataAOT_Inlining) {
const char* kScript =
R"(
import 'dart:typed_data';
void foo(Uint8List list, int from) {
if (from >= list.length) {
list[from];
}
}
)";
const auto& root_library = Library::Handle(LoadTestScript(kScript));
const auto& function = Function::Handle(GetFunction(root_library, "foo"));
TestPipeline pipeline(function);
FlowGraph* flow_graph = pipeline.RunAOTPasses({});
auto entry = flow_graph->graph_entry()->normal_entry();
EXPECT(entry != nullptr);
CheckNullInstr* check_null = nullptr;
LoadFieldInstr* load_field = nullptr;
GenericCheckBoundInstr* bounds_check = nullptr;
Instruction* load_untagged = nullptr;
LoadIndexedInstr* load_indexed = nullptr;
ILMatcher cursor(flow_graph, entry);
RELEASE_ASSERT(cursor.TryMatch({
kMoveGlob,
{kMatchAndMoveCheckNull, &check_null},
{kMatchAndMoveLoadField, &load_field},
kMoveGlob,
kMatchAndMoveBranchTrue,
kMoveGlob,
{kMatchAndMoveGenericCheckBound, &bounds_check},
{kMatchAndMoveLoadUntagged, &load_untagged},
kMoveParallelMoves,
{kMatchAndMoveLoadIndexed, &load_indexed},
kMoveGlob,
kMatchReturn,
}));
EXPECT(load_field->InputAt(0)->definition()->IsParameter());
EXPECT(bounds_check->InputAt(0)->definition() == load_field);
EXPECT(load_untagged->InputAt(0)->definition()->IsParameter());
EXPECT(load_indexed->InputAt(0)->definition() == load_untagged);
}
// This test asserts that we are not inlining accesses to typed data interfaces
// (e.g. Uint8List) if there are instantiated 3rd party classes (e.g.
// UnmodifiableUint8ListView).
ISOLATE_UNIT_TEST_CASE(IRTest_TypedDataAOT_NotInlining) {
const char* kScript =
R"(
import 'dart:typed_data';
createThirdPartyUint8List() => UnmodifiableUint8ListView(Uint8List(10));
void foo(Uint8List list, int from) {
if (from >= list.length) {
list[from];
}
}
)";
const auto& root_library = Library::Handle(LoadTestScript(kScript));
// Firstly we ensure a non internal/external/view Uint8List is allocated.
Invoke(root_library, "createThirdPartyUint8List");
// Now we ensure that we don't perform the inlining of the `list[from]`
// access.
const auto& function = Function::Handle(GetFunction(root_library, "foo"));
TestPipeline pipeline(function);
FlowGraph* flow_graph = pipeline.RunAOTPasses({});
auto entry = flow_graph->graph_entry()->normal_entry();
EXPECT(entry != nullptr);
InstanceCallInstr* length_call = nullptr;
PushArgumentInstr* pusharg1 = nullptr;
PushArgumentInstr* pusharg2 = nullptr;
InstanceCallInstr* index_get_call = nullptr;
ILMatcher cursor(flow_graph, entry);
RELEASE_ASSERT(cursor.TryMatch({
kMoveGlob,
{kMatchAndMoveInstanceCall, &length_call},
kMoveGlob,
kMatchAndMoveBranchTrue,
kMoveGlob,
{kMatchAndMovePushArgument, &pusharg1},
{kMatchAndMovePushArgument, &pusharg2},
{kMatchAndMoveInstanceCall, &index_get_call},
kMoveGlob,
kMatchReturn,
}));
EXPECT(length_call->Selector() == Symbols::GetLength().raw());
EXPECT(pusharg1->InputAt(0)->definition()->IsParameter());
EXPECT(pusharg2->InputAt(0)->definition()->IsParameter());
EXPECT(index_get_call->Selector() == Symbols::IndexToken().raw());
}
// This test asserts that we are inlining get:length, [] and []= for all typed
// data interfaces. It also ensures that the asserted IR actually works by
// exercising it.
ISOLATE_UNIT_TEST_CASE(IRTest_TypedDataAOT_FunctionalGetSet) {
const char* kTemplate =
R"(
import 'dart:typed_data';
void reverseList(%s list) {
final length = list.length;
final halfLength = length ~/ 2;
for (int i = 0; i < halfLength; ++i) {
final tmp = list[length-i-1];
list[length-i-1] = list[i];
list[i] = tmp;
}
}
)";
std::initializer_list<MatchCode> expected_il = {
// Before loop
kMoveGlob,
kMatchAndMoveCheckNull,
kMatchAndMoveLoadField,
kMoveGlob,
kMatchAndMoveBranchTrue,
// Loop
kMoveGlob,
// Load 1
kMatchAndMoveGenericCheckBound,
kMoveGlob,
kMatchAndMoveLoadUntagged,
kMoveParallelMoves,
kMatchAndMoveLoadIndexed,
kMoveGlob,
// Load 2
kMatchAndMoveGenericCheckBound,
kMoveGlob,
kMatchAndMoveLoadUntagged,
kMoveParallelMoves,
kMatchAndMoveLoadIndexed,
kMoveGlob,
// Store 1
kMatchAndMoveGenericCheckBound,
kMoveGlob,
kMoveParallelMoves,
kMatchAndMoveLoadUntagged,
kMoveParallelMoves,
kMatchAndMoveStoreIndexed,
kMoveGlob,
// Store 2
kMoveParallelMoves,
kMatchAndMoveLoadUntagged,
kMoveParallelMoves,
kMatchAndMoveStoreIndexed,
kMoveGlob,
// Exit the loop.
kMatchAndMoveBranchFalse,
kMoveGlob,
kMatchReturn,
};
char script_buffer[1024];
auto& lib = Library::Handle();
auto& function = Function::Handle();
auto& view = TypedDataView::Handle();
auto& arguments = Array::Handle();
auto& result = Object::Handle();
auto run_reverse_list = [&](const char* name, const TypedDataBase& data) {
// Fill in the template with the [name].
Utils::SNPrint(script_buffer, sizeof(script_buffer), kTemplate, name);
// Create a new library, load the function and compile it using our AOT
// pipeline.
lib = LoadTestScript(script_buffer, nullptr, name);
function = GetFunction(lib, "reverseList");
TestPipeline pipeline(function);
FlowGraph* flow_graph = pipeline.RunAOTPasses({});
auto entry = flow_graph->graph_entry()->normal_entry();
// Ensure the IL matches what we expect.
ILMatcher cursor(flow_graph, entry);
EXPECT(cursor.TryMatch(expected_il));
// Class ids are numbered from internal/view/external.
const classid_t view_cid = data.GetClassId() + 1;
ASSERT(RawObject::IsTypedDataViewClassId(view_cid));
// First and last element are not in the view, i.e.
// view[0:view.length()-1] = data[1:data.length()-2]
const intptr_t length_in_bytes =
(data.LengthInBytes() - 2 * data.ElementSizeInBytes());
view = TypedDataView::New(view_cid, data, data.ElementSizeInBytes(),
length_in_bytes / data.ElementSizeInBytes());
ASSERT(data.ElementType() == view.ElementType());
arguments = Array::New(1);
arguments.SetAt(0, view);
result = DartEntry::InvokeFunction(function, arguments);
EXPECT(result.IsNull());
};
const auto& uint8_list =
TypedData::Handle(TypedData::New(kTypedDataUint8ArrayCid, 16));
const auto& uint8c_list =
TypedData::Handle(TypedData::New(kTypedDataUint8ClampedArrayCid, 16));
const auto& int16_list =
TypedData::Handle(TypedData::New(kTypedDataInt16ArrayCid, 16));
const auto& uint16_list =
TypedData::Handle(TypedData::New(kTypedDataUint16ArrayCid, 16));
const auto& int32_list =
TypedData::Handle(TypedData::New(kTypedDataInt32ArrayCid, 16));
const auto& uint32_list =
TypedData::Handle(TypedData::New(kTypedDataUint32ArrayCid, 16));
const auto& int64_list =
TypedData::Handle(TypedData::New(kTypedDataInt64ArrayCid, 16));
const auto& uint64_list =
TypedData::Handle(TypedData::New(kTypedDataUint64ArrayCid, 16));
const auto& float32_list =
TypedData::Handle(TypedData::New(kTypedDataFloat32ArrayCid, 16));
const auto& float64_list =
TypedData::Handle(TypedData::New(kTypedDataFloat64ArrayCid, 16));
const auto& int8_list =
TypedData::Handle(TypedData::New(kTypedDataInt8ArrayCid, 16));
for (intptr_t i = 0; i < 16; ++i) {
int8_list.SetInt8(i, i);
uint8_list.SetUint8(i, i);
uint8c_list.SetUint8(i, i);
int16_list.SetInt16(2 * i, i);
uint16_list.SetUint16(2 * i, i);
int32_list.SetInt32(4 * i, i);
uint32_list.SetUint32(4 * i, i);
int64_list.SetInt64(8 * i, i);
uint64_list.SetUint64(8 * i, i);
float32_list.SetFloat32(4 * i, i + 0.5);
float64_list.SetFloat64(8 * i, i + 0.7);
}
run_reverse_list("Uint8List", int8_list);
run_reverse_list("Int8List", uint8_list);
run_reverse_list("Uint8ClampedList", uint8c_list);
run_reverse_list("Int16List", int16_list);
run_reverse_list("Uint16List", uint16_list);
run_reverse_list("Int32List", int32_list);
run_reverse_list("Uint32List", uint32_list);
run_reverse_list("Int64List", int64_list);
run_reverse_list("Uint64List", uint64_list);
run_reverse_list("Float32List", float32_list);
run_reverse_list("Float64List", float64_list);
for (intptr_t i = 0; i < 16; ++i) {
// Only the values in the view are reversed.
const bool in_view = i >= 1 && i < 15;
const int64_t expected_value = in_view ? (16 - i - 1) : i;
const uint64_t expected_uvalue = in_view ? (16 - i - 1) : i;
const float expected_fvalue = (in_view ? (16 - i - 1) : i) + 0.5;
const double expected_dvalue = (in_view ? (16 - i - 1) : i) + 0.7;
EXPECT(int8_list.GetInt8(i) == expected_value);
EXPECT(uint8_list.GetUint8(i) == expected_uvalue);
EXPECT(uint8c_list.GetUint8(i) == expected_uvalue);
EXPECT(int16_list.GetInt16(2 * i) == expected_value);
EXPECT(uint16_list.GetUint16(2 * i) == expected_uvalue);
EXPECT(int32_list.GetInt32(4 * i) == expected_value);
EXPECT(uint32_list.GetUint32(4 * i) == expected_uvalue);
EXPECT(int64_list.GetInt64(8 * i) == expected_value);
EXPECT(uint64_list.GetUint64(8 * i) == expected_uvalue);
EXPECT(float32_list.GetFloat32(4 * i) == expected_fvalue);
EXPECT(float64_list.GetFloat64(8 * i) == expected_dvalue);
}
}
// This test asserts that we get errors if receiver, index or value are null.
ISOLATE_UNIT_TEST_CASE(IRTest_TypedDataAOT_FunctionalIndexError) {
const char* kTemplate =
R"(
import 'dart:typed_data';
void getList(%s list, int index, %s value) {
list[index] = value;
}
)";
std::initializer_list<MatchCode> expected_il = {
// Receiver null check
kMoveGlob,
kMatchAndMoveCheckNull,
// Index null check
kMoveGlob,
kMatchAndMoveCheckNull,
// Value null check
kMoveGlob,
kMatchAndMoveCheckNull,
// LoadField length
kMoveGlob,
kMatchAndMoveLoadField,
// Bounds check
kMoveGlob,
kMatchAndMoveGenericCheckBound,
// Store value.
kMoveGlob,
kMatchAndMoveLoadUntagged,
kMoveParallelMoves,
kMatchAndMoveStoreIndexed,
// Return
kMoveGlob,
kMatchReturn,
};
char script_buffer[1024];
auto& lib = Library::Handle();
auto& function = Function::Handle();
auto& arguments = Array::Handle();
auto& result = Object::Handle();
const intptr_t kIndex = 1;
const intptr_t kLastStage = 3;
auto run_test = [&](const char* name, const char* type,
const TypedDataBase& data, const Object& value,
int stage) {
// Fill in the template with the [name].
Utils::SNPrint(script_buffer, sizeof(script_buffer), kTemplate, name, type);
// Create a new library, load the function and compile it using our AOT
// pipeline.
lib = LoadTestScript(script_buffer, nullptr, name);
function = GetFunction(lib, "getList");
TestPipeline pipeline(function);
FlowGraph* flow_graph = pipeline.RunAOTPasses({});
auto entry = flow_graph->graph_entry()->normal_entry();
// Ensure the IL matches what we expect.
ILMatcher cursor(flow_graph, entry, /*trace=*/false);
EXPECT(cursor.TryMatch(expected_il));
arguments = Array::New(3);
arguments.SetAt(0, stage == 0 ? Object::null_object() : data);
arguments.SetAt(
1, stage == 1 ? Object::null_object() : Smi::Handle(Smi::New(kIndex)));
arguments.SetAt(2, stage == 2 ? Object::null_object() : value);
result = DartEntry::InvokeFunction(function, arguments);
if (stage == kLastStage) {
// The last stage must be successful
EXPECT(result.IsNull());
} else {
// Ensure we get an error.
EXPECT(result.IsUnhandledException());
result = UnhandledException::Cast(result).exception();
}
};
const auto& uint8_list =
TypedData::Handle(TypedData::New(kTypedDataUint8ArrayCid, 16));
const auto& uint8c_list =
TypedData::Handle(TypedData::New(kTypedDataUint8ClampedArrayCid, 16));
const auto& int16_list =
TypedData::Handle(TypedData::New(kTypedDataInt16ArrayCid, 16));
const auto& uint16_list =
TypedData::Handle(TypedData::New(kTypedDataUint16ArrayCid, 16));
const auto& int32_list =
TypedData::Handle(TypedData::New(kTypedDataInt32ArrayCid, 16));
const auto& uint32_list =
TypedData::Handle(TypedData::New(kTypedDataUint32ArrayCid, 16));
const auto& int64_list =
TypedData::Handle(TypedData::New(kTypedDataInt64ArrayCid, 16));
const auto& uint64_list =
TypedData::Handle(TypedData::New(kTypedDataUint64ArrayCid, 16));
const auto& float32_list =
TypedData::Handle(TypedData::New(kTypedDataFloat32ArrayCid, 16));
const auto& float64_list =
TypedData::Handle(TypedData::New(kTypedDataFloat64ArrayCid, 16));
const auto& int8_list =
TypedData::Handle(TypedData::New(kTypedDataInt8ArrayCid, 16));
const auto& int_value = Integer::Handle(Integer::New(42));
const auto& float_value = Double::Handle(Double::New(4.2));
for (intptr_t stage = 0; stage <= kLastStage; ++stage) {
run_test("Uint8List", "int", int8_list, int_value, stage);
run_test("Int8List", "int", uint8_list, int_value, stage);
run_test("Uint8ClampedList", "int", uint8c_list, int_value, stage);
run_test("Int16List", "int", int16_list, int_value, stage);
run_test("Uint16List", "int", uint16_list, int_value, stage);
run_test("Int32List", "int", int32_list, int_value, stage);
run_test("Uint32List", "int", uint32_list, int_value, stage);
run_test("Int64List", "int", int64_list, int_value, stage);
run_test("Uint64List", "int", uint64_list, int_value, stage);
run_test("Float32List", "double", float32_list, float_value, stage);
run_test("Float64List", "double", float64_list, float_value, stage);
}
}
#endif // defined(DART_PRECOMPILER) && !defined(TARGET_ARCH_DBC)
} // namespace dart
+261
View File
@@ -17,6 +17,13 @@ namespace dart {
#define I (isolate())
#define Z (zone())
static void RefineUseTypes(Definition* instr) {
CompileType* new_type = instr->Type();
for (Value::Iterator it(instr->input_use_list()); !it.Done(); it.Advance()) {
it.Current()->RefineReachingType(new_type);
}
}
static bool ShouldInlineSimd() {
return FlowGraphCompiler::SupportsUnboxedSimd128();
}
@@ -1626,5 +1633,259 @@ bool CallSpecializer::SpecializeTestCidsForNumericTypes(
return true; // May deoptimize since we have not identified all 'true' tests.
}
void TypedDataSpecializer::Optimize(FlowGraph* flow_graph) {
TypedDataSpecializer optimizer(flow_graph);
optimizer.VisitBlocks();
}
void TypedDataSpecializer::EnsureIsInitialized() {
if (initialized_) return;
initialized_ = true;
int_type_ = Type::IntType();
double_type_ = Type::Double();
const auto& typed_data = Library::Handle(
Z, Library::LookupLibrary(thread_, Symbols::DartTypedData()));
auto& td_class = Class::Handle(Z);
auto& direct_implementors = GrowableObjectArray::Handle(Z);
#define INIT_HANDLE(iface, member_name, type, cid) \
td_class = typed_data.LookupClass(Symbols::iface()); \
ASSERT(!td_class.IsNull()); \
direct_implementors = td_class.direct_implementors(); \
if (!HasThirdPartyImplementor(direct_implementors)) { \
member_name = td_class.RareType(); \
}
PUBLIC_TYPED_DATA_CLASS_LIST(INIT_HANDLE)
#undef INIT_HANDLE
}
bool TypedDataSpecializer::HasThirdPartyImplementor(
const GrowableObjectArray& direct_implementors) {
// Check if there are non internal/external/view implementors.
for (intptr_t i = 0; i < direct_implementors.Length(); ++i) {
implementor_ ^= direct_implementors.At(i);
// We only consider [implementor_] a 3rd party implementor if it was
// finalized by the class finalizer, since only then can we have concrete
// instances of the [implementor_].
if (implementor_.is_finalized()) {
const classid_t cid = implementor_.id();
if (!RawObject::IsTypedDataClassId(cid) &&
!RawObject::IsTypedDataViewClassId(cid) &&
!RawObject::IsExternalTypedDataClassId(cid)) {
return true;
}
}
}
return false;
}
void TypedDataSpecializer::VisitInstanceCall(InstanceCallInstr* call) {
TryInlineCall(call);
}
void TypedDataSpecializer::VisitStaticCall(StaticCallInstr* call) {
TryInlineCall(call);
}
void TypedDataSpecializer::TryInlineCall(TemplateDartCall<0>* call) {
const bool is_length_getter = call->Selector() == Symbols::GetLength().raw();
const bool is_index_get = call->Selector() == Symbols::IndexToken().raw();
const bool is_index_set =
call->Selector() == Symbols::AssignIndexToken().raw();
if (is_length_getter || is_index_get || is_index_set) {
EnsureIsInitialized();
const intptr_t receiver_index = call->FirstArgIndex();
CompileType* receiver_type = call->ArgumentAt(receiver_index + 0)->Type();
CompileType* index_type = nullptr;
if (is_index_get || is_index_set) {
index_type = call->ArgumentAt(receiver_index + 1)->Type();
}
CompileType* value_type = nullptr;
if (is_index_set) {
value_type = call->ArgumentAt(receiver_index + 2)->Type();
}
auto& type_class = Class::Handle(zone_);
#define TRY_INLINE(iface, member_name, type, cid) \
if (!member_name.IsNull()) { \
if (receiver_type->IsAssignableTo(member_name)) { \
if (is_length_getter) { \
type_class = member_name.type_class(); \
ReplaceWithLengthGetter(call); \
} else if (is_index_get) { \
if (!index_type->IsNullableInt()) return; \
type_class = member_name.type_class(); \
ReplaceWithIndexGet(call, cid); \
} else { \
if (!index_type->IsNullableInt()) return; \
if (!value_type->IsAssignableTo(type)) return; \
type_class = member_name.type_class(); \
ReplaceWithIndexSet(call, cid); \
} \
return; \
} \
}
PUBLIC_TYPED_DATA_CLASS_LIST(TRY_INLINE)
#undef INIT_HANDLE
}
}
void TypedDataSpecializer::ReplaceWithLengthGetter(TemplateDartCall<0>* call) {
const intptr_t receiver_idx = call->FirstArgIndex();
auto array = call->PushArgumentAt(receiver_idx + 0)->value()->definition();
if (array->Type()->is_nullable()) {
AppendNullCheck(call, &array);
}
Definition* length = AppendLoadLength(call, array);
flow_graph_->ReplaceCurrentInstruction(current_iterator(), call, length);
RefineUseTypes(length);
}
void TypedDataSpecializer::ReplaceWithIndexGet(TemplateDartCall<0>* call,
classid_t cid) {
const intptr_t receiver_idx = call->FirstArgIndex();
auto array = call->PushArgumentAt(receiver_idx + 0)->value()->definition();
auto index = call->PushArgumentAt(receiver_idx + 1)->value()->definition();
if (array->Type()->is_nullable()) {
AppendNullCheck(call, &array);
}
if (index->Type()->is_nullable()) {
AppendNullCheck(call, &index);
}
AppendBoundsCheck(call, array, &index);
Definition* value = AppendLoadIndexed(call, array, index, cid);
flow_graph_->ReplaceCurrentInstruction(current_iterator(), call, value);
RefineUseTypes(value);
}
void TypedDataSpecializer::ReplaceWithIndexSet(TemplateDartCall<0>* call,
classid_t cid) {
const intptr_t receiver_idx = call->FirstArgIndex();
auto array = call->PushArgumentAt(receiver_idx + 0)->value()->definition();
auto index = call->PushArgumentAt(receiver_idx + 1)->value()->definition();
auto value = call->PushArgumentAt(receiver_idx + 2)->value()->definition();
if (array->Type()->is_nullable()) {
AppendNullCheck(call, &array);
}
if (index->Type()->is_nullable()) {
AppendNullCheck(call, &index);
}
if (value->Type()->is_nullable()) {
AppendNullCheck(call, &value);
}
AppendBoundsCheck(call, array, &index);
AppendStoreIndexed(call, array, index, value, cid);
RELEASE_ASSERT(!call->HasUses());
flow_graph_->ReplaceCurrentInstruction(current_iterator(), call, nullptr);
}
void TypedDataSpecializer::AppendNullCheck(TemplateDartCall<0>* call,
Definition** value) {
auto check =
new (Z) CheckNullInstr(new (Z) Value(*value), Symbols::OptimizedOut(),
call->deopt_id(), call->token_pos());
flow_graph_->InsertBefore(call, check, call->env(), FlowGraph::kValue);
// Use data dependency as control dependency.
*value = check;
}
void TypedDataSpecializer::AppendBoundsCheck(TemplateDartCall<0>* call,
Definition* array,
Definition** index) {
auto length = new (Z) LoadFieldInstr(
new (Z) Value(array), Slot::TypedDataBase_length(), call->token_pos());
flow_graph_->InsertBefore(call, length, call->env(), FlowGraph::kValue);
auto check = new (Z) GenericCheckBoundInstr(
new (Z) Value(length), new (Z) Value(*index), DeoptId::kNone);
flow_graph_->InsertBefore(call, check, call->env(), FlowGraph::kValue);
// Use data dependency as control dependency.
*index = check;
}
Definition* TypedDataSpecializer::AppendLoadLength(TemplateDartCall<0>* call,
Definition* array) {
auto length = new (Z) LoadFieldInstr(
new (Z) Value(array), Slot::TypedDataBase_length(), call->token_pos());
flow_graph_->InsertBefore(call, length, call->env(), FlowGraph::kValue);
return length;
}
Definition* TypedDataSpecializer::AppendLoadIndexed(TemplateDartCall<0>* call,
Definition* array,
Definition* index,
classid_t cid) {
const intptr_t element_size = TypedDataBase::ElementSizeFor(cid);
const intptr_t index_scale = element_size;
auto data = new (Z) LoadUntaggedInstr(new (Z) Value(array),
TypedDataBase::data_field_offset());
flow_graph_->InsertBefore(call, data, call->env(), FlowGraph::kValue);
Definition* load = new (Z)
LoadIndexedInstr(new (Z) Value(data), new (Z) Value(index), index_scale,
cid, kAlignedAccess, DeoptId::kNone, call->token_pos());
flow_graph_->InsertBefore(call, load, call->env(), FlowGraph::kValue);
if (cid == kTypedDataFloat32ArrayCid) {
load = new (Z) FloatToDoubleInstr(new (Z) Value(load), call->deopt_id());
flow_graph_->InsertBefore(call, load, call->env(), FlowGraph::kValue);
}
return load;
}
void TypedDataSpecializer::AppendStoreIndexed(TemplateDartCall<0>* call,
Definition* array,
Definition* index,
Definition* value,
classid_t cid) {
const intptr_t element_size = TypedDataBase::ElementSizeFor(cid);
const intptr_t index_scale = element_size;
const auto deopt_id = call->deopt_id();
if (cid == kTypedDataFloat32ArrayCid) {
value = new (Z) DoubleToFloatInstr(new (Z) Value(value), deopt_id,
Instruction::kNotSpeculative);
flow_graph_->InsertBefore(call, value, call->env(), FlowGraph::kValue);
} else if (cid == kTypedDataInt32ArrayCid) {
value = new (Z) UnboxInt32Instr(UnboxInt32Instr::kTruncate,
new (Z) Value(value), deopt_id);
flow_graph_->InsertBefore(call, value, call->env(), FlowGraph::kValue);
} else if (cid == kTypedDataUint32ArrayCid) {
value = new (Z) UnboxUint32Instr(new (Z) Value(value), deopt_id);
ASSERT(value->AsUnboxInteger()->is_truncating());
flow_graph_->InsertBefore(call, value, call->env(), FlowGraph::kValue);
}
auto data = new (Z) LoadUntaggedInstr(new (Z) Value(array),
TypedDataBase::data_field_offset());
flow_graph_->InsertBefore(call, data, call->env(), FlowGraph::kValue);
auto store = new (Z) StoreIndexedInstr(
new (Z) Value(data), new (Z) Value(index), new (Z) Value(value),
kNoStoreBarrier, index_scale, cid, kAlignedAccess, DeoptId::kNone,
call->token_pos(), Instruction::kNotSpeculative);
flow_graph_->InsertBefore(call, store, call->env(), FlowGraph::kEffect);
}
} // namespace dart
#endif // DART_PRECOMPILED_RUNTIME
+100
View File
@@ -178,6 +178,106 @@ class CallSpecializer : public FlowGraphVisitor {
FlowGraph* flow_graph_;
};
#define PUBLIC_TYPED_DATA_CLASS_LIST(V) \
V(Int8List, int8_list_type_, int_type_, kTypedDataInt8ArrayCid) \
V(Uint8List, uint8_list_type_, int_type_, kTypedDataUint8ArrayCid) \
V(Uint8ClampedList, uint8_clamped_type_, int_type_, \
kTypedDataUint8ClampedArrayCid) \
V(Int16List, int16_list_type_, int_type_, kTypedDataInt16ArrayCid) \
V(Uint16List, uint16_list_type_, int_type_, kTypedDataUint16ArrayCid) \
V(Int32List, int32_list_type_, int_type_, kTypedDataInt32ArrayCid) \
V(Uint32List, uint32_list_type_, int_type_, kTypedDataUint32ArrayCid) \
V(Int64List, int64_list_type_, int_type_, kTypedDataInt64ArrayCid) \
V(Uint64List, uint64_list_type_, int_type_, kTypedDataUint64ArrayCid) \
V(Float32List, float32_list_type_, double_type_, kTypedDataFloat32ArrayCid) \
V(Float64List, float64_list_type_, double_type_, kTypedDataFloat64ArrayCid)
// Specializes instance/static calls with receiver type being a typed data
// interface (if that interface is only implemented by internal/external/view
// typed data classes).
//
// For example:
//
// foo(Uint8List bytes) => bytes[0];
//
// Would be translated to something like this:
//
// v0 <- Constant(0)
//
// // Ensures the list is non-null.
// v1 <- ParameterInstr(0)
// v2 <- CheckNull(v1)
//
// // Load the length & perform bounds checks
// v3 <- LoadField(v2, "TypedDataBase.length");
// v4 <- GenericCheckBounds(v3, v0);
//
// // Directly access the byte, independent of whether `bytes` is
// // _Uint8List, _Uint8ArrayView or _ExternalUint8Array.
// v5 <- LoadUntagged(v1, "TypedDataBase.data");
// v5 <- LoadIndexed(v5, v4)
//
class TypedDataSpecializer : public FlowGraphVisitor {
public:
static void Optimize(FlowGraph* flow_graph);
virtual void VisitInstanceCall(InstanceCallInstr* instr);
virtual void VisitStaticCall(StaticCallInstr* instr);
private:
// clang-format off
explicit TypedDataSpecializer(FlowGraph* flow_graph)
: FlowGraphVisitor(flow_graph->reverse_postorder()),
thread_(Thread::Current()),
zone_(thread_->zone()),
flow_graph_(flow_graph),
#define ALLOCATE_HANDLE(iface, member_name, type, cid) \
member_name(AbstractType::Handle(zone_)),
PUBLIC_TYPED_DATA_CLASS_LIST(ALLOCATE_HANDLE)
#undef INIT_HANDLE
int_type_(AbstractType::Handle()),
double_type_(AbstractType::Handle()),
implementor_(Class::Handle()) {
}
// clang-format on
void EnsureIsInitialized();
bool HasThirdPartyImplementor(const GrowableObjectArray& direct_implementors);
void TryInlineCall(TemplateDartCall<0>* call);
void ReplaceWithLengthGetter(TemplateDartCall<0>* call);
void ReplaceWithIndexGet(TemplateDartCall<0>* call, classid_t cid);
void ReplaceWithIndexSet(TemplateDartCall<0>* call, classid_t cid);
void AppendNullCheck(TemplateDartCall<0>* call, Definition** array);
void AppendBoundsCheck(TemplateDartCall<0>* call,
Definition* array,
Definition** index);
Definition* AppendLoadLength(TemplateDartCall<0>* call, Definition* array);
Definition* AppendLoadIndexed(TemplateDartCall<0>* call,
Definition* array,
Definition* index,
classid_t cid);
void AppendStoreIndexed(TemplateDartCall<0>* call,
Definition* array,
Definition* index,
Definition* value,
classid_t cid);
Zone* zone() const { return zone_; }
Thread* thread_;
Zone* zone_;
FlowGraph* flow_graph_;
bool initialized_ = false;
#define DEF_HANDLE(iface, member_name, type, cid) AbstractType& member_name;
PUBLIC_TYPED_DATA_CLASS_LIST(DEF_HANDLE)
#undef DEF_HANDLE
AbstractType& int_type_;
AbstractType& double_type_;
Class& implementor_;
};
} // namespace dart
#endif // RUNTIME_VM_COMPILER_CALL_SPECIALIZER_H_
+6
View File
@@ -241,6 +241,9 @@ void CompilerPass::RunPipeline(PipelineMode mode,
// unreachable code.
INVOKE_PASS(ApplyICData);
}
if (mode == kAOT) {
INVOKE_PASS(OptimizeTypedDataAccesses);
}
#endif
INVOKE_PASS(WidenSmiToInt32);
INVOKE_PASS(SelectRepresentations);
@@ -371,6 +374,9 @@ COMPILER_PASS(OptimizeBranches, {
ConstantPropagator::OptimizeBranches(flow_graph);
});
COMPILER_PASS(OptimizeTypedDataAccesses,
{ TypedDataSpecializer::Optimize(flow_graph); });
COMPILER_PASS(TryCatchOptimization, {
OptimizeCatchEntryStates(flow_graph, /*is_aot=*/FLAG_precompiled_mode);
});
+1
View File
@@ -36,6 +36,7 @@ namespace dart {
V(LICM) \
V(OptimisticallySpecializeSmiPhis) \
V(OptimizeBranches) \
V(OptimizeTypedDataAccesses) \
V(RangeAnalysis) \
V(ReorderBlocks) \
V(SelectRepresentations) \
+1
View File
@@ -163,5 +163,6 @@ compiler_sources_tests = [
"backend/redundancy_elimination_test.cc",
"backend/slot_test.cc",
"backend/type_propagator_test.cc",
"backend/typed_data_aot_test.cc",
"cha_test.cc",
]
+1
View File
@@ -209,6 +209,7 @@ class ObjectPointerVisitor;
V(LanguageError, "LanguageError") \
V(LeftShiftOperator, "<<") \
V(Length, "length") \
V(GetLength, "get:length") \
V(LessEqualOperator, "<=") \
V(LibraryClass, "Library") \
V(LibraryPrefix, "LibraryPrefix") \