Skip to content

Commit

Permalink
[VM] Extend subtype-test mechanism with support for generic methods
Browse files Browse the repository at this point in the history
Until now the subtype-test cache mechanism did not work (i.e. could
return the wrong result) for partially instantiated generic closures.

Additionally, closures which close over generic methods were always
handled in runtime.  This caused a servere performance regression for
any code hitting this (e.g. code which uses `package:stack_trace`).

Fixes dart-lang/sdk#34051
Fixes dart-lang/sdk#34054

Change-Id: Idb73e6f348c2fe0c737f42c57009f5f7a636c9a6
Reviewed-on: https://dart-review.googlesource.com/68369
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Régis Crelier <regis@google.com>
  • Loading branch information
mkustermann authored and commit-bot@chromium.org committed Aug 8, 2018
1 parent 278d962 commit 575a8f8
Show file tree
Hide file tree
Showing 18 changed files with 1,016 additions and 590 deletions.
2 changes: 1 addition & 1 deletion runtime/vm/compiler/assembler/assembler_arm64.h
Original file line number Diff line number Diff line change
Expand Up @@ -1511,7 +1511,7 @@ class Assembler : public ValueObject {
void CompareObject(Register reg, const Object& object);

void LoadClassId(Register result, Register object);
// Overwrites class_id register.
// Overwrites class_id register (it will be tagged afterwards).
void LoadClassById(Register result, Register class_id);
void LoadClass(Register result, Register object);
void CompareClassId(Register object,
Expand Down
2 changes: 1 addition & 1 deletion runtime/vm/compiler/assembler/assembler_x64.h
Original file line number Diff line number Diff line change
Expand Up @@ -771,7 +771,7 @@ class Assembler : public ValueObject {
// Loading and comparing classes of objects.
void LoadClassId(Register result, Register object);

// Overwrites class_id register.
// Overwrites class_id register (it will be tagged afterwards).
void LoadClassById(Register result, Register class_id);

void LoadClass(Register result, Register object);
Expand Down
6 changes: 6 additions & 0 deletions runtime/vm/compiler/backend/flow_graph_compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,11 @@ class FlowGraphCompiler : public ValueObject {
Label* is_instance_lbl,
Label* is_not_instance_label);

RawSubtypeTestCache* GenerateFunctionTypeTest(TokenPosition token_pos,
const AbstractType& dst_type,
Label* is_instance_lbl,
Label* is_not_instance_label);

RawSubtypeTestCache* GenerateSubtype1TestCacheLookup(
TokenPosition token_pos,
const Class& type_class,
Expand All @@ -825,6 +830,7 @@ class FlowGraphCompiler : public ValueObject {
kTestTypeOneArg,
kTestTypeTwoArgs,
kTestTypeFourArgs,
kTestTypeSixArgs,
};

RawSubtypeTestCache* GenerateCallSubtypeTestStub(
Expand Down
147 changes: 90 additions & 57 deletions runtime/vm/compiler/backend/flow_graph_compiler_arm.cc
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,10 @@ RawSubtypeTestCache* FlowGraphCompiler::GenerateCallSubtypeTestStub(
ASSERT(instantiator_type_arguments_reg == R2);
ASSERT(function_type_arguments_reg == R1);
__ BranchLink(*StubCode::Subtype4TestCache_entry());
} else if (test_kind == kTestTypeSixArgs) {
ASSERT(instantiator_type_arguments_reg == R2);
ASSERT(function_type_arguments_reg == R1);
__ BranchLink(*StubCode::Subtype6TestCache_entry());
} else {
UNREACHABLE();
}
Expand All @@ -244,8 +248,9 @@ FlowGraphCompiler::GenerateInstantiatedTypeWithArgumentsTest(
Label* is_not_instance_lbl) {
__ Comment("InstantiatedTypeWithArgumentsTest");
ASSERT(type.IsInstantiated());
ASSERT(!type.IsFunctionType());
const Class& type_class = Class::ZoneHandle(zone(), type.type_class());
ASSERT(type.IsFunctionType() || (type_class.NumTypeArguments() > 0));
ASSERT(type_class.NumTypeArguments() > 0);
const Register kInstanceReg = R0;
Error& bound_error = Error::Handle(zone());
const Type& int_type = Type::Handle(zone(), Type::IntType());
Expand All @@ -259,45 +264,43 @@ FlowGraphCompiler::GenerateInstantiatedTypeWithArgumentsTest(
} else {
__ b(is_not_instance_lbl, EQ);
}
// A function type test requires checking the function signature.
if (!type.IsFunctionType()) {
const intptr_t num_type_args = type_class.NumTypeArguments();
const intptr_t num_type_params = type_class.NumTypeParameters();
const intptr_t from_index = num_type_args - num_type_params;
const TypeArguments& type_arguments =
TypeArguments::ZoneHandle(zone(), type.arguments());
const bool is_raw_type = type_arguments.IsNull() ||
type_arguments.IsRaw(from_index, num_type_params);
if (is_raw_type) {
const Register kClassIdReg = R2;
// dynamic type argument, check only classes.
__ LoadClassId(kClassIdReg, kInstanceReg);
__ CompareImmediate(kClassIdReg, type_class.id());
__ b(is_instance_lbl, EQ);
// List is a very common case.
if (IsListClass(type_class)) {
GenerateListTypeCheck(kClassIdReg, is_instance_lbl);
}
return GenerateSubtype1TestCacheLookup(
token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
const intptr_t num_type_args = type_class.NumTypeArguments();
const intptr_t num_type_params = type_class.NumTypeParameters();
const intptr_t from_index = num_type_args - num_type_params;
const TypeArguments& type_arguments =
TypeArguments::ZoneHandle(zone(), type.arguments());
const bool is_raw_type = type_arguments.IsNull() ||
type_arguments.IsRaw(from_index, num_type_params);
if (is_raw_type) {
const Register kClassIdReg = R2;
// dynamic type argument, check only classes.
__ LoadClassId(kClassIdReg, kInstanceReg);
__ CompareImmediate(kClassIdReg, type_class.id());
__ b(is_instance_lbl, EQ);
// List is a very common case.
if (IsListClass(type_class)) {
GenerateListTypeCheck(kClassIdReg, is_instance_lbl);
}
// If one type argument only, check if type argument is Object or dynamic.
if (type_arguments.Length() == 1) {
const AbstractType& tp_argument =
AbstractType::ZoneHandle(zone(), type_arguments.TypeAt(0));
ASSERT(!tp_argument.IsMalformed());
if (tp_argument.IsType()) {
ASSERT(tp_argument.HasResolvedTypeClass());
// Check if type argument is dynamic or Object.
const Type& object_type = Type::Handle(zone(), Type::ObjectType());
if (object_type.IsSubtypeOf(tp_argument, NULL, NULL, Heap::kOld)) {
// Instance class test only necessary.
return GenerateSubtype1TestCacheLookup(
token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
}
return GenerateSubtype1TestCacheLookup(
token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
}
// If one type argument only, check if type argument is Object or dynamic.
if (type_arguments.Length() == 1) {
const AbstractType& tp_argument =
AbstractType::ZoneHandle(zone(), type_arguments.TypeAt(0));
ASSERT(!tp_argument.IsMalformed());
if (tp_argument.IsType()) {
ASSERT(tp_argument.HasResolvedTypeClass());
// Check if type argument is dynamic or Object.
const Type& object_type = Type::Handle(zone(), Type::ObjectType());
if (object_type.IsSubtypeOf(tp_argument, NULL, NULL, Heap::kOld)) {
// Instance class test only necessary.
return GenerateSubtype1TestCacheLookup(
token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
}
}
}

// Regular subtype test cache involving instance's type arguments.
const Register kInstantiatorTypeArgumentsReg = kNoRegister;
const Register kFunctionTypeArgumentsReg = kNoRegister;
Expand Down Expand Up @@ -332,10 +335,7 @@ bool FlowGraphCompiler::GenerateInstantiatedTypeNoArgumentsTest(
Label* is_not_instance_lbl) {
__ Comment("InstantiatedTypeNoArgumentsTest");
ASSERT(type.IsInstantiated());
if (type.IsFunctionType()) {
// Fallthrough.
return true;
}
ASSERT(!type.IsFunctionType());
const Class& type_class = Class::Handle(zone(), type.type_class());
ASSERT(type_class.NumTypeArguments() == 0);

Expand Down Expand Up @@ -428,12 +428,17 @@ RawSubtypeTestCache* FlowGraphCompiler::GenerateUninstantiatedTypeTest(
Label* is_instance_lbl,
Label* is_not_instance_lbl) {
__ Comment("UninstantiatedTypeTest");
const Register kInstanceReg = R0;
const Register kInstantiatorTypeArgumentsReg = R2;
const Register kFunctionTypeArgumentsReg = R1;
const Register kTempReg = kNoRegister;
ASSERT(!type.IsInstantiated());
ASSERT(!type.IsFunctionType());
// Skip check if destination is a dynamic type.
if (type.IsTypeParameter()) {
const TypeParameter& type_param = TypeParameter::Cast(type);
const Register kInstantiatorTypeArgumentsReg = R2;
const Register kFunctionTypeArgumentsReg = R1;
const AbstractType& bound = AbstractType::Handle(type_param.bound());

__ ldm(IA, SP,
(1 << kFunctionTypeArgumentsReg) |
(1 << kInstantiatorTypeArgumentsReg));
Expand Down Expand Up @@ -466,32 +471,30 @@ RawSubtypeTestCache* FlowGraphCompiler::GenerateUninstantiatedTypeTest(
Label fall_through;
__ b(&fall_through);

// If it's guaranteed, by type-parameter bound, that the type parameter will
// never have a value of a function type, then we can safely do a 4-type
// test instead of a 6-type test.
auto test_kind = !bound.IsTopType() && !bound.IsFunctionType() &&
!bound.IsDartFunctionType() && bound.IsType()
? kTestTypeSixArgs
: kTestTypeFourArgs;

__ Bind(&not_smi);
// R0: instance.
// R2: instantiator type arguments.
// R1: function type arguments.
const Register kInstanceReg = R0;
const Register kTempReg = kNoRegister;
const SubtypeTestCache& type_test_cache = SubtypeTestCache::ZoneHandle(
zone(), GenerateCallSubtypeTestStub(
kTestTypeFourArgs, kInstanceReg,
kInstantiatorTypeArgumentsReg, kFunctionTypeArgumentsReg,
kTempReg, is_instance_lbl, is_not_instance_lbl));
test_kind, kInstanceReg, kInstantiatorTypeArgumentsReg,
kFunctionTypeArgumentsReg, kTempReg, is_instance_lbl,
is_not_instance_lbl));
__ Bind(&fall_through);
return type_test_cache.raw();
}
if (type.IsType()) {
const Register kInstanceReg = R0;
const Register kInstantiatorTypeArgumentsReg = R2;
const Register kFunctionTypeArgumentsReg = R1;
__ tst(kInstanceReg, Operand(kSmiTagMask)); // Is instance Smi?
__ b(is_not_instance_lbl, EQ);
__ BranchIfSmi(kInstanceReg, is_not_instance_lbl);
__ ldm(IA, SP,
(1 << kFunctionTypeArgumentsReg) |
(1 << kInstantiatorTypeArgumentsReg));
// Uninstantiated type class is known at compile time, but the type
// arguments are determined at runtime by the instantiator(s).
const Register kTempReg = kNoRegister;
return GenerateCallSubtypeTestStub(kTestTypeFourArgs, kInstanceReg,
kInstantiatorTypeArgumentsReg,
kFunctionTypeArgumentsReg, kTempReg,
Expand All @@ -500,6 +503,30 @@ RawSubtypeTestCache* FlowGraphCompiler::GenerateUninstantiatedTypeTest(
return SubtypeTestCache::null();
}

// Generates function type check.
//
// See [GenerateUninstantiatedTypeTest] for calling convention.
RawSubtypeTestCache* FlowGraphCompiler::GenerateFunctionTypeTest(
TokenPosition token_pos,
const AbstractType& type,
Label* is_instance_lbl,
Label* is_not_instance_lbl) {
const Register kInstanceReg = R0;
const Register kInstantiatorTypeArgumentsReg = R2;
const Register kFunctionTypeArgumentsReg = R1;
__ BranchIfSmi(kInstanceReg, is_not_instance_lbl);
__ ldm(
IA, SP,
(1 << kFunctionTypeArgumentsReg) | (1 << kInstantiatorTypeArgumentsReg));
// Uninstantiated type class is known at compile time, but the type
// arguments are determined at runtime by the instantiator(s).
const Register kTempReg = kNoRegister;
return GenerateCallSubtypeTestStub(kTestTypeSixArgs, kInstanceReg,
kInstantiatorTypeArgumentsReg,
kFunctionTypeArgumentsReg, kTempReg,
is_instance_lbl, is_not_instance_lbl);
}

// Inputs:
// - R0: instance being type checked (preserved).
// - R2: optional instantiator type arguments (preserved).
Expand All @@ -517,12 +544,18 @@ RawSubtypeTestCache* FlowGraphCompiler::GenerateInlineInstanceof(
Label* is_instance_lbl,
Label* is_not_instance_lbl) {
__ Comment("InlineInstanceof");

if (type.IsFunctionType()) {
return GenerateFunctionTypeTest(token_pos, type, is_instance_lbl,
is_not_instance_lbl);
}

if (type.IsInstantiated()) {
const Class& type_class = Class::ZoneHandle(zone(), type.type_class());
// A class equality check is only applicable with a dst type (not a
// function type) of a non-parameterized class or with a raw dst type of
// a parameterized class.
if (type.IsFunctionType() || (type_class.NumTypeArguments() > 0)) {
if (type_class.NumTypeArguments() > 0) {
return GenerateInstantiatedTypeWithArgumentsTest(
token_pos, type, is_instance_lbl, is_not_instance_lbl);
// Fall through to runtime call.
Expand Down
Loading

0 comments on commit 575a8f8

Please sign in to comment.