mirror of
https://github.com/dart-lang/sdk
synced 2024-10-04 18:47:55 +00:00
[vm,aot] Preliminary support for dynamic interface in AOT
TEST=runtime/tests/vm/dart/dynamic_module_pragmas_il_test.dart Change-Id: I6efd24f55726db858711d5f77beabf5659e288a4 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/371563 Reviewed-by: Slava Egorov <vegorov@google.com> Commit-Queue: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
parent
1e5f96514c
commit
0c9067c626
|
@ -43,11 +43,11 @@ InstanceConstant pragmaConstant(CoreTypes coreTypes, String pragmaName) {
|
|||
});
|
||||
}
|
||||
|
||||
void parseAndAnnotate(YamlList items, String pragmaName, Uri baseUri,
|
||||
void parseAndAnnotate(YamlList? items, String pragmaName, Uri baseUri,
|
||||
Component component, CoreTypes coreTypes, LibraryIndex libraryIndex,
|
||||
{required bool allowMembers, bool onlyInstanceMembers = false}) {
|
||||
if (items == null) return;
|
||||
final pragma = pragmaConstant(coreTypes, pragmaName);
|
||||
|
||||
for (final item in items) {
|
||||
final nodes = findNodes(item, baseUri, libraryIndex, component,
|
||||
allowMembers: allowMembers, onlyInstanceMembers: onlyInstanceMembers);
|
||||
|
|
|
@ -760,6 +760,10 @@ final class _DispatchableInvocation extends _Invocation {
|
|||
ConeType receiver,
|
||||
Map<Member, _ReceiverTypeBuilder> targets,
|
||||
TypeFlowAnalysis typeFlowAnalysis) {
|
||||
if (kPrintTrace) {
|
||||
tracePrint(
|
||||
"Collecting targets for dynamically extendable receiver $receiver");
|
||||
}
|
||||
final cls = receiver.cls as _TFClassImpl;
|
||||
// Collect possible targets among dynamically extendable
|
||||
// subtypes as they may have allocated subtypes at run time.
|
||||
|
@ -770,6 +774,10 @@ final class _DispatchableInvocation extends _Invocation {
|
|||
Member? target = extendableSubtype.getDispatchTarget(selector);
|
||||
if (target != null) {
|
||||
if (areArgumentsValidFor(target)) {
|
||||
if (kPrintTrace) {
|
||||
tracePrint(
|
||||
"Found target $target in a dynamically extendable subtype $extendableSubtype");
|
||||
}
|
||||
// Overwrite previously added receiver type builder.
|
||||
targets[target] = receiverTypeBuilder;
|
||||
isDynamicallyOverridden = isDynamicallyOverridden ||
|
||||
|
@ -779,13 +787,19 @@ final class _DispatchableInvocation extends _Invocation {
|
|||
assert(selector is DynamicSelector);
|
||||
_recordMismatchedDynamicInvocation(target, typeFlowAnalysis);
|
||||
}
|
||||
} else {
|
||||
isDynamicallyOverridden = true;
|
||||
}
|
||||
}
|
||||
if (selector is DynamicSelector) {
|
||||
targets[noSuchMethodMarker] = receiverTypeBuilder;
|
||||
isDynamicallyOverridden = true;
|
||||
}
|
||||
return isDynamicallyOverridden && !selector.name.isPrivate;
|
||||
if (kPrintTrace) {
|
||||
tracePrint(
|
||||
"isDynamicallyOverridden = $isDynamicallyOverridden, isPrivate = ${selector.name.isPrivate}");
|
||||
}
|
||||
return !isDynamicallyOverridden || selector.name.isPrivate;
|
||||
}
|
||||
|
||||
void _recordMismatchedDynamicInvocation(
|
||||
|
|
229
runtime/tests/vm/dart/dynamic_module_pragmas_il_test.dart
Normal file
229
runtime/tests/vm/dart/dynamic_module_pragmas_il_test.dart
Normal file
|
@ -0,0 +1,229 @@
|
|||
// Copyright (c) 2024, 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.
|
||||
|
||||
// Test for @pragma('dyn-module:extendable') and @pragma('dyn-module:can-be-overridden').
|
||||
|
||||
import 'package:vm/testing/il_matchers.dart';
|
||||
|
||||
@pragma('vm:never-inline')
|
||||
void myprint(Object message) {
|
||||
print(message);
|
||||
}
|
||||
|
||||
abstract class A1 {
|
||||
void foo();
|
||||
void bar();
|
||||
void baz();
|
||||
}
|
||||
|
||||
class B1 extends A1 {
|
||||
void foo() {
|
||||
myprint('B1.foo');
|
||||
}
|
||||
|
||||
void bar() {
|
||||
myprint('B1.bar');
|
||||
}
|
||||
|
||||
@pragma('vm:never-inline')
|
||||
void baz() {
|
||||
myprint('B1.baz');
|
||||
}
|
||||
}
|
||||
|
||||
class C1 extends B1 {
|
||||
@pragma('vm:never-inline')
|
||||
void baz() {
|
||||
myprint('C1.baz');
|
||||
}
|
||||
}
|
||||
|
||||
@pragma('vm:never-inline')
|
||||
@pragma('vm:testing:print-flow-graph')
|
||||
void callA1(A1 obj) {
|
||||
obj.foo();
|
||||
obj.bar();
|
||||
obj.baz();
|
||||
}
|
||||
|
||||
@pragma('vm:never-inline')
|
||||
@pragma('vm:testing:print-flow-graph')
|
||||
void testIsA1(obj) {
|
||||
myprint(obj is A1);
|
||||
myprint(obj is B1);
|
||||
myprint(obj is C1);
|
||||
}
|
||||
|
||||
abstract class A2 {
|
||||
void foo();
|
||||
void bar();
|
||||
void baz();
|
||||
}
|
||||
|
||||
@pragma('dyn-module:extendable')
|
||||
class B2 extends A2 {
|
||||
void foo() {
|
||||
myprint('B2.foo');
|
||||
}
|
||||
|
||||
@pragma('dyn-module:can-be-overridden')
|
||||
void bar() {
|
||||
myprint('B2.bar');
|
||||
}
|
||||
|
||||
@pragma('vm:never-inline')
|
||||
void baz() {
|
||||
myprint('B2.baz');
|
||||
}
|
||||
}
|
||||
|
||||
class C2 extends B2 {
|
||||
@pragma('vm:never-inline')
|
||||
void baz() {
|
||||
myprint('C2.baz');
|
||||
}
|
||||
}
|
||||
|
||||
class D2 extends B2 {}
|
||||
|
||||
@pragma('vm:never-inline')
|
||||
@pragma('vm:testing:print-flow-graph')
|
||||
void callA2(A2 obj) {
|
||||
obj.foo();
|
||||
obj.bar();
|
||||
obj.baz();
|
||||
}
|
||||
|
||||
@pragma('vm:never-inline')
|
||||
@pragma('vm:testing:print-flow-graph')
|
||||
void testIsA2(obj) {
|
||||
myprint(obj is A2);
|
||||
myprint(obj is B2);
|
||||
myprint(obj is C2);
|
||||
}
|
||||
|
||||
List objs = [Object(), B1(), C1(), B2(), C2()];
|
||||
|
||||
main() {
|
||||
for (final obj in objs) {
|
||||
testIsA1(obj);
|
||||
testIsA2(obj);
|
||||
if (obj is A1) {
|
||||
callA1(obj);
|
||||
}
|
||||
if (obj is A2) {
|
||||
callA2(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void matchIL$callA1(FlowGraph graph) {
|
||||
graph.dump();
|
||||
graph.match([
|
||||
match.block('Graph', []),
|
||||
match.block('Function', [
|
||||
'obj' << match.Parameter(index: 0),
|
||||
match.CheckStackOverflow(),
|
||||
match.MoveArgument(match.any),
|
||||
match.StaticCall(match.any),
|
||||
match.MoveArgument(match.any),
|
||||
match.StaticCall(match.any),
|
||||
'cid' << match.LoadClassId('obj'),
|
||||
match.MoveArgument('obj'),
|
||||
match.DispatchTableCall('cid'),
|
||||
match.DartReturn(match.any),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
void matchIL$callA2(FlowGraph graph) {
|
||||
graph.dump();
|
||||
graph.match([
|
||||
match.block('Graph', []),
|
||||
match.block('Function', [
|
||||
'obj' << match.Parameter(index: 0),
|
||||
match.CheckStackOverflow(),
|
||||
match.MoveArgument(match.any),
|
||||
match.StaticCall(match.any),
|
||||
'cid1' << match.LoadClassId('obj'),
|
||||
match.Branch(match.TestRange('cid1'), ifTrue: 'B7', ifFalse: 'B8'),
|
||||
]),
|
||||
'B7' <<
|
||||
match.block('Target', [
|
||||
match.MoveArgument('obj'),
|
||||
match.DispatchTableCall('cid1'),
|
||||
match.Goto('B9'),
|
||||
]),
|
||||
'B8' <<
|
||||
match.block('Target', [
|
||||
match.MoveArgument('obj'),
|
||||
match.InstanceCall('obj'),
|
||||
match.Goto('B9'),
|
||||
]),
|
||||
'B9' <<
|
||||
match.block('Join', [
|
||||
'cid2' << match.LoadClassId('obj'),
|
||||
match.Branch(match.TestRange('cid2'), ifTrue: 'B10', ifFalse: 'B11'),
|
||||
]),
|
||||
'B10' <<
|
||||
match.block('Target', [
|
||||
match.MoveArgument('obj'),
|
||||
match.DispatchTableCall('cid2'),
|
||||
match.Goto('B12'),
|
||||
]),
|
||||
'B11' <<
|
||||
match.block('Target', [
|
||||
match.MoveArgument('obj'),
|
||||
match.InstanceCall('obj'),
|
||||
match.Goto('B12'),
|
||||
]),
|
||||
'B12' <<
|
||||
match.block('Join', [
|
||||
match.DartReturn(match.any),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
void matchIL$testIsA1(FlowGraph graph) {
|
||||
graph.dump();
|
||||
graph.match([
|
||||
match.block('Graph', []),
|
||||
match.block('Function', [
|
||||
'obj' << match.Parameter(index: 0),
|
||||
match.CheckStackOverflow(),
|
||||
'cid' << match.LoadClassId('obj'),
|
||||
'test1' << match.TestRange('cid'),
|
||||
match.MoveArgument('test1'),
|
||||
match.StaticCall('test1'),
|
||||
match.MoveArgument('test1'),
|
||||
match.StaticCall('test1'),
|
||||
'test2' << match.EqualityCompare('cid', match.any, kind: '=='),
|
||||
match.MoveArgument('test2'),
|
||||
match.StaticCall('test2'),
|
||||
match.DartReturn(match.any),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
void matchIL$testIsA2(FlowGraph graph) {
|
||||
graph.dump();
|
||||
graph.match([
|
||||
match.block('Graph', []),
|
||||
match.block('Function', [
|
||||
'obj' << match.Parameter(index: 0),
|
||||
match.CheckStackOverflow(),
|
||||
'test1' << match.InstanceOf('obj', match.any, match.any),
|
||||
match.MoveArgument('test1'),
|
||||
match.StaticCall('test1'),
|
||||
'test2' << match.InstanceOf('obj', match.any, match.any),
|
||||
match.MoveArgument('test2'),
|
||||
match.StaticCall('test2'),
|
||||
'cid' << match.LoadClassId('obj'),
|
||||
'test3' << match.EqualityCompare('cid', match.any, kind: '=='),
|
||||
match.MoveArgument('test3'),
|
||||
match.StaticCall('test3'),
|
||||
match.DartReturn(match.any),
|
||||
]),
|
||||
]);
|
||||
}
|
|
@ -612,6 +612,9 @@ void ClassFinalizer::FinalizeTypesInClass(const Class& cls) {
|
|||
if (is_future_subtype && !cls.is_abstract()) {
|
||||
MarkClassCanBeFuture(zone, cls);
|
||||
}
|
||||
if (cls.is_dynamically_extendable()) {
|
||||
MarkClassHasDynamicallyExtendableSubtypes(zone, cls);
|
||||
}
|
||||
|
||||
RegisterClassInHierarchy(zone, cls);
|
||||
#endif // defined(DART_PRECOMPILED_RUNTIME)
|
||||
|
@ -678,6 +681,26 @@ void ClassFinalizer::MarkClassCanBeFuture(Zone* zone, const Class& cls) {
|
|||
MarkClassCanBeFuture(zone, base);
|
||||
}
|
||||
}
|
||||
|
||||
void ClassFinalizer::MarkClassHasDynamicallyExtendableSubtypes(
|
||||
Zone* zone,
|
||||
const Class& cls) {
|
||||
if (cls.has_dynamically_extendable_subtypes()) return;
|
||||
|
||||
cls.set_has_dynamically_extendable_subtypes(true);
|
||||
|
||||
Class& base = Class::Handle(zone, cls.SuperClass());
|
||||
if (!base.IsNull()) {
|
||||
MarkClassHasDynamicallyExtendableSubtypes(zone, base);
|
||||
}
|
||||
auto& interfaces = Array::Handle(zone, cls.interfaces());
|
||||
auto& type = AbstractType::Handle(zone);
|
||||
for (intptr_t i = 0; i < interfaces.Length(); ++i) {
|
||||
type ^= interfaces.At(i);
|
||||
base = type.type_class();
|
||||
MarkClassHasDynamicallyExtendableSubtypes(zone, base);
|
||||
}
|
||||
}
|
||||
#endif // defined(DART_PRECOMPILED_RUNTIME)
|
||||
|
||||
void ClassFinalizer::FinalizeClass(const Class& cls) {
|
||||
|
|
|
@ -57,6 +57,11 @@ class ClassFinalizer : public AllStatic {
|
|||
|
||||
// Mark [cls], its superclass and superinterfaces as can_be_future().
|
||||
static void MarkClassCanBeFuture(Zone* zone, const Class& cls);
|
||||
|
||||
// Mark [cls] and all its superclasses and superinterfaces as
|
||||
// has_dynamically_extendable_subtypes().
|
||||
static void MarkClassHasDynamicallyExtendableSubtypes(Zone* zone,
|
||||
const Class& cls);
|
||||
#endif // !defined(DART_PRECOMPILED_RUNTIME)
|
||||
|
||||
// Ensures members of the class are loaded, class layout is finalized and size
|
||||
|
|
|
@ -534,7 +534,7 @@ bool AotCallSpecializer::TryOptimizeIntegerOperation(TemplateDartCall<0>* instr,
|
|||
left_type->IsNullableInt() && right_type->IsNullableInt();
|
||||
|
||||
if (auto* call = instr->AsInstanceCall()) {
|
||||
if (!call->CanReceiverBeSmiBasedOnInterfaceTarget(zone())) {
|
||||
if (!call->CanReceiverBeSmiBasedOnInterfaceTarget(Z)) {
|
||||
has_nullable_int_args = false;
|
||||
}
|
||||
}
|
||||
|
@ -793,7 +793,8 @@ void AotCallSpecializer::VisitInstanceCall(InstanceCallInstr* instr) {
|
|||
// Check if the single target is a polymorphic target, if it is,
|
||||
// we don't have one target.
|
||||
const Function& target = targets.FirstTarget();
|
||||
has_one_target = !target.is_polymorphic_target();
|
||||
has_one_target =
|
||||
!target.is_polymorphic_target() && !target.IsDynamicallyOverridden();
|
||||
}
|
||||
|
||||
if (has_one_target) {
|
||||
|
@ -1154,12 +1155,18 @@ bool AotCallSpecializer::TryReplaceInstanceOfWithRangeCheck(
|
|||
|
||||
void AotCallSpecializer::ReplaceInstanceCallsWithDispatchTableCalls() {
|
||||
ASSERT(current_iterator_ == nullptr);
|
||||
const intptr_t max_block_id = flow_graph()->max_block_id();
|
||||
for (BlockIterator block_it = flow_graph()->reverse_postorder_iterator();
|
||||
!block_it.Done(); block_it.Advance()) {
|
||||
ForwardInstructionIterator it(block_it.Current());
|
||||
current_iterator_ = ⁢
|
||||
for (; !it.Done(); it.Advance()) {
|
||||
while (!it.Done()) {
|
||||
Instruction* instr = it.Current();
|
||||
// Advance to the next instruction before replacing a call,
|
||||
// as call can be replaced with a diamond and the rest of
|
||||
// the instructions can be moved to a new basic block.
|
||||
if (!it.Done()) it.Advance();
|
||||
|
||||
if (auto call = instr->AsInstanceCall()) {
|
||||
TryReplaceWithDispatchTableCall(call);
|
||||
} else if (auto call = instr->AsPolymorphicInstanceCall()) {
|
||||
|
@ -1168,6 +1175,9 @@ void AotCallSpecializer::ReplaceInstanceCallsWithDispatchTableCalls() {
|
|||
}
|
||||
current_iterator_ = nullptr;
|
||||
}
|
||||
if (flow_graph()->max_block_id() != max_block_id) {
|
||||
flow_graph()->DiscoverBlocks();
|
||||
}
|
||||
}
|
||||
|
||||
const Function& AotCallSpecializer::InterfaceTargetForTableDispatch(
|
||||
|
@ -1202,28 +1212,132 @@ void AotCallSpecializer::TryReplaceWithDispatchTableCall(
|
|||
precompiler_->selector_map()->GetSelector(interface_target);
|
||||
|
||||
if (selector == nullptr) {
|
||||
#if defined(DEBUG)
|
||||
if (!interface_target.IsDynamicallyOverridden()) {
|
||||
// Target functions were removed by tree shaking. This call is dead code,
|
||||
// or the receiver is always null.
|
||||
#if defined(DEBUG)
|
||||
AddCheckNull(receiver->CopyWithType(Z), call->function_name(),
|
||||
DeoptId::kNone, call->env(), call);
|
||||
StopInstr* stop = new (Z) StopInstr("Dead instance call executed.");
|
||||
InsertBefore(call, stop, call->env(), FlowGraph::kEffect);
|
||||
}
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
const bool receiver_can_be_smi =
|
||||
call->CanReceiverBeSmiBasedOnInterfaceTarget(zone());
|
||||
call->CanReceiverBeSmiBasedOnInterfaceTarget(Z);
|
||||
auto load_cid = new (Z) LoadClassIdInstr(receiver->CopyWithType(Z),
|
||||
kUnboxedUword, receiver_can_be_smi);
|
||||
InsertBefore(call, load_cid, call->env(), FlowGraph::kValue);
|
||||
|
||||
const auto& cls = Class::Handle(Z, interface_target.Owner());
|
||||
if (cls.has_dynamically_extendable_subtypes()) {
|
||||
ReplaceWithConditionalDispatchTableCall(call, load_cid, interface_target,
|
||||
selector);
|
||||
return;
|
||||
}
|
||||
|
||||
auto dispatch_table_call = DispatchTableCallInstr::FromCall(
|
||||
Z, call, new (Z) Value(load_cid), interface_target, selector);
|
||||
call->ReplaceWith(dispatch_table_call, current_iterator());
|
||||
}
|
||||
|
||||
static void InheritDeoptTargetIfNeeded(Zone* zone,
|
||||
Instruction* instr,
|
||||
Instruction* from) {
|
||||
if (from->env() != nullptr) {
|
||||
instr->InheritDeoptTarget(zone, from);
|
||||
}
|
||||
}
|
||||
|
||||
void AotCallSpecializer::ReplaceWithConditionalDispatchTableCall(
|
||||
InstanceCallBaseInstr* call,
|
||||
LoadClassIdInstr* load_cid,
|
||||
const Function& interface_target,
|
||||
const compiler::TableSelector* selector) {
|
||||
BlockEntryInstr* current_block = call->GetBlock();
|
||||
const bool has_uses = call->HasUses();
|
||||
|
||||
const intptr_t num_cids = isolate_group()->class_table()->NumCids();
|
||||
auto* compare = new (Z) TestRangeInstr(
|
||||
call->source(), new (Z) Value(load_cid), 0, num_cids, kUnboxedUword);
|
||||
|
||||
BranchInstr* branch = new (Z) BranchInstr(compare, DeoptId::kNone);
|
||||
InheritDeoptTargetIfNeeded(Z, branch, call);
|
||||
|
||||
TargetEntryInstr* true_target =
|
||||
new (Z) TargetEntryInstr(flow_graph()->allocate_block_id(),
|
||||
current_block->try_index(), DeoptId::kNone);
|
||||
InheritDeoptTargetIfNeeded(Z, true_target, call);
|
||||
*branch->true_successor_address() = true_target;
|
||||
|
||||
TargetEntryInstr* false_target =
|
||||
new (Z) TargetEntryInstr(flow_graph()->allocate_block_id(),
|
||||
current_block->try_index(), DeoptId::kNone);
|
||||
InheritDeoptTargetIfNeeded(Z, false_target, call);
|
||||
*branch->false_successor_address() = false_target;
|
||||
|
||||
JoinEntryInstr* join =
|
||||
new (Z) JoinEntryInstr(flow_graph()->allocate_block_id(),
|
||||
current_block->try_index(), DeoptId::kNone);
|
||||
InheritDeoptTargetIfNeeded(Z, join, call);
|
||||
|
||||
current_block->ReplaceAsPredecessorWith(join);
|
||||
|
||||
for (intptr_t i = 0, n = current_block->dominated_blocks().length(); i < n;
|
||||
++i) {
|
||||
BlockEntryInstr* block = current_block->dominated_blocks()[i];
|
||||
join->AddDominatedBlock(block);
|
||||
}
|
||||
current_block->ClearDominatedBlocks();
|
||||
current_block->AddDominatedBlock(join);
|
||||
current_block->AddDominatedBlock(true_target);
|
||||
current_block->AddDominatedBlock(false_target);
|
||||
|
||||
PhiInstr* phi = nullptr;
|
||||
if (has_uses) {
|
||||
phi = new (Z) PhiInstr(join, 2);
|
||||
phi->mark_alive();
|
||||
flow_graph()->AllocateSSAIndex(phi);
|
||||
join->InsertPhi(phi);
|
||||
phi->UpdateType(*call->Type());
|
||||
phi->set_representation(call->representation());
|
||||
call->ReplaceUsesWith(phi);
|
||||
}
|
||||
|
||||
GotoInstr* true_goto = new (Z) GotoInstr(join, DeoptId::kNone);
|
||||
InheritDeoptTargetIfNeeded(Z, true_goto, call);
|
||||
true_target->LinkTo(true_goto);
|
||||
true_target->set_last_instruction(true_goto);
|
||||
|
||||
GotoInstr* false_goto = new (Z) GotoInstr(join, DeoptId::kNone);
|
||||
InheritDeoptTargetIfNeeded(Z, false_goto, call);
|
||||
false_target->LinkTo(false_goto);
|
||||
false_target->set_last_instruction(false_goto);
|
||||
|
||||
auto dispatch_table_call = DispatchTableCallInstr::FromCall(
|
||||
Z, call, new (Z) Value(load_cid), interface_target, selector);
|
||||
ASSERT(dispatch_table_call->representation() == call->representation());
|
||||
InsertBefore(true_goto, dispatch_table_call, call->env(),
|
||||
has_uses ? FlowGraph::kValue : FlowGraph::kEffect);
|
||||
|
||||
call->previous()->AppendInstruction(branch);
|
||||
call->set_previous(nullptr);
|
||||
join->LinkTo(call->next());
|
||||
call->set_next(nullptr);
|
||||
call->UnuseAllInputs(); // So it can be re-added to the graph.
|
||||
call->InsertBefore(false_goto);
|
||||
InheritDeoptTargetIfNeeded(Z, call, call); // Restore env use list.
|
||||
|
||||
if (has_uses) {
|
||||
phi->SetInputAt(0, new (Z) Value(dispatch_table_call));
|
||||
dispatch_table_call->AddInputUse(phi->InputAt(0));
|
||||
phi->SetInputAt(1, new (Z) Value(call));
|
||||
call->AddInputUse(phi->InputAt(1));
|
||||
}
|
||||
}
|
||||
|
||||
#endif // DART_PRECOMPILER
|
||||
|
||||
} // namespace dart
|
||||
|
|
|
@ -57,6 +57,11 @@ class AotCallSpecializer : public CallSpecializer {
|
|||
// it by a dispatch table call.
|
||||
void TryReplaceWithDispatchTableCall(InstanceCallBaseInstr* call);
|
||||
const Function& InterfaceTargetForTableDispatch(InstanceCallBaseInstr* call);
|
||||
void ReplaceWithConditionalDispatchTableCall(
|
||||
InstanceCallBaseInstr* call,
|
||||
LoadClassIdInstr* load_cid,
|
||||
const Function& interface_target,
|
||||
const compiler::TableSelector* selector);
|
||||
|
||||
// Try to replace a call with a more specialized instruction working on
|
||||
// integers (e.g. BinaryInt64OpInstr, EqualityCompareInstr,
|
||||
|
|
|
@ -1888,6 +1888,7 @@ void Precompiler::CollectDynamicFunctionNames() {
|
|||
ASSERT(!farray.IsNull());
|
||||
if (farray.Length() == 1) {
|
||||
function ^= farray.At(0);
|
||||
if (function.IsDynamicallyOverridden()) continue;
|
||||
|
||||
// It looks like there is exactly one target for the given name. Though we
|
||||
// have to be careful: e.g. A name like `dyn:get:foo` might have a target
|
||||
|
@ -1910,9 +1911,8 @@ void Precompiler::CollectDynamicFunctionNames() {
|
|||
}
|
||||
}
|
||||
|
||||
farray ^= table.GetOrNull(Symbols::GetRuntimeType());
|
||||
|
||||
get_runtime_type_is_unique_ = !farray.IsNull() && (farray.Length() == 1);
|
||||
function ^= functions_map.GetOrNull(Symbols::GetRuntimeType());
|
||||
get_runtime_type_is_unique_ = !function.IsNull();
|
||||
|
||||
if (FLAG_print_unique_targets) {
|
||||
UniqueFunctionsMap::Iterator unique_iter(&functions_map);
|
||||
|
|
|
@ -531,7 +531,8 @@ FlowGraph::ToCheck FlowGraph::CheckForInstanceCall(
|
|||
}
|
||||
|
||||
// Useful receiver class information?
|
||||
if (receiver_class.IsNull()) {
|
||||
if (receiver_class.IsNull() ||
|
||||
receiver_class.has_dynamically_extendable_subtypes()) {
|
||||
return ToCheck::kCheckCid;
|
||||
} else if (call->HasICData()) {
|
||||
// If the static class type does not match information found in ICData
|
||||
|
|
|
@ -319,6 +319,9 @@ bool HierarchyInfo::CanUseSubtypeRangeCheckFor(const AbstractType& type) {
|
|||
|
||||
Zone* zone = thread()->zone();
|
||||
const Class& type_class = Class::Handle(zone, type.type_class());
|
||||
if (type_class.has_dynamically_extendable_subtypes()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We can use class id range checks only if we don't have to test type
|
||||
// arguments.
|
||||
|
@ -364,6 +367,9 @@ bool HierarchyInfo::CanUseGenericSubtypeRangeCheckFor(
|
|||
Zone* zone = thread()->zone();
|
||||
const Class& type_class = Class::Handle(zone, type.type_class());
|
||||
const intptr_t num_type_parameters = type_class.NumTypeParameters();
|
||||
if (type_class.has_dynamically_extendable_subtypes()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This function should only be called for generic classes.
|
||||
ASSERT(type_class.NumTypeParameters() > 0 &&
|
||||
|
@ -5110,7 +5116,7 @@ LocationSummary* TestRangeInstr::MakeLocationSummary(Zone* zone,
|
|||
bool opt) const {
|
||||
#if defined(TARGET_ARCH_IA32) || defined(TARGET_ARCH_X64) || \
|
||||
defined(TARGET_ARCH_ARM)
|
||||
const bool needs_temp = true;
|
||||
const bool needs_temp = (lower() != 0);
|
||||
#else
|
||||
const bool needs_temp = false;
|
||||
#endif
|
||||
|
@ -5136,6 +5142,9 @@ Condition TestRangeInstr::EmitComparisonCode(FlowGraphCompiler* compiler,
|
|||
}
|
||||
|
||||
Register in = locs()->in(0).reg();
|
||||
if (lower == 0) {
|
||||
__ CompareImmediate(in, upper);
|
||||
} else {
|
||||
#if defined(TARGET_ARCH_IA32) || defined(TARGET_ARCH_X64) || \
|
||||
defined(TARGET_ARCH_ARM)
|
||||
Register temp = locs()->temp(0).reg();
|
||||
|
@ -5144,6 +5153,7 @@ Condition TestRangeInstr::EmitComparisonCode(FlowGraphCompiler* compiler,
|
|||
#endif
|
||||
__ AddImmediate(temp, in, -lower);
|
||||
__ CompareImmediate(temp, upper - lower);
|
||||
}
|
||||
ASSERT((kind() == Token::kIS) || (kind() == Token::kISNOT));
|
||||
return kind() == Token::kIS ? UNSIGNED_LESS_EQUAL : UNSIGNED_GREATER;
|
||||
}
|
||||
|
|
|
@ -2882,6 +2882,8 @@ bool FlowGraphAllocator::UnallocatedIsSorted() {
|
|||
LiveRange* a = unallocated_[i];
|
||||
LiveRange* b = unallocated_[i - 1];
|
||||
if (!ShouldBeAllocatedBefore(a, b)) {
|
||||
a->Print();
|
||||
b->Print();
|
||||
UNREACHABLE();
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -2927,7 +2927,10 @@ void LoadClassIdInstr::InferRange(uword* lower, uword* upper) {
|
|||
if (cid != kDynamicCid) {
|
||||
*lower = *upper = cid;
|
||||
} else if (CompilerState::Current().is_aot()) {
|
||||
*upper = IsolateGroup::Current()->class_table()->NumCids();
|
||||
IsolateGroup* isolate_group = IsolateGroup::Current();
|
||||
*upper = isolate_group->has_dynamically_extendable_classes()
|
||||
? kClassIdTagMax
|
||||
: isolate_group->class_table()->NumCids();
|
||||
|
||||
HierarchyInfo* hi = Thread::Current()->hierarchy_info();
|
||||
if (hi != nullptr) {
|
||||
|
@ -2935,6 +2938,7 @@ void LoadClassIdInstr::InferRange(uword* lower, uword* upper) {
|
|||
if (type.IsType() && !type.IsFutureOrType() &&
|
||||
!Instance::NullIsAssignableTo(type)) {
|
||||
const auto& type_class = Class::Handle(type.type_class());
|
||||
if (!type_class.has_dynamically_extendable_subtypes()) {
|
||||
const auto& ranges =
|
||||
hi->SubtypeRangesForClass(type_class, /*include_abstract=*/false,
|
||||
/*exclude_null=*/true);
|
||||
|
@ -2945,6 +2949,7 @@ void LoadClassIdInstr::InferRange(uword* lower, uword* upper) {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LoadClassIdInstr::InferRange(RangeAnalysis* analysis, Range* range) {
|
||||
|
|
|
@ -74,6 +74,9 @@ bool CHA::HasSubclasses(const Class& cls) {
|
|||
// Class Object has subclasses, although we do not keep track of them.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (cls.has_dynamically_extendable_subtypes()) return true;
|
||||
|
||||
Thread* thread = Thread::Current();
|
||||
SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock());
|
||||
const GrowableObjectArray& direct_subclasses =
|
||||
|
@ -91,6 +94,7 @@ bool CHA::ConcreteSubclasses(const Class& cls,
|
|||
GrowableArray<intptr_t>* class_ids) {
|
||||
if (cls.InVMIsolateHeap()) return false;
|
||||
if (cls.IsObjectClass()) return false;
|
||||
if (cls.has_dynamically_extendable_subtypes()) return false;
|
||||
|
||||
if (!cls.is_abstract()) {
|
||||
class_ids->Add(cls.id());
|
||||
|
@ -120,6 +124,7 @@ bool CHA::IsImplemented(const Class& cls) {
|
|||
// TODO(fschneider): Enable tracking of CHA dependent code for VM heap
|
||||
// classes.
|
||||
if (cls.InVMIsolateHeap()) return true;
|
||||
if (cls.has_dynamically_extendable_subtypes()) return true;
|
||||
|
||||
return cls.is_implemented();
|
||||
}
|
||||
|
@ -127,7 +132,8 @@ bool CHA::IsImplemented(const Class& cls) {
|
|||
bool CHA::HasSingleConcreteImplementation(const Class& interface,
|
||||
intptr_t* implementation_cid) {
|
||||
intptr_t cid = interface.implementor_cid();
|
||||
if ((cid == kIllegalCid) || (cid == kDynamicCid)) {
|
||||
if ((cid == kIllegalCid) || (cid == kDynamicCid) ||
|
||||
interface.has_dynamically_extendable_subtypes()) {
|
||||
// No implementations / multiple implementations.
|
||||
*implementation_cid = kDynamicCid;
|
||||
return false;
|
||||
|
|
|
@ -114,7 +114,8 @@ void JitCallSpecializer::VisitInstanceCall(InstanceCallInstr* instr) {
|
|||
has_one_target = PolymorphicInstanceCallInstr::ComputeRuntimeType(
|
||||
targets) != Type::null();
|
||||
} else {
|
||||
has_one_target = !target.is_polymorphic_target();
|
||||
has_one_target =
|
||||
!target.is_polymorphic_target() && !target.IsDynamicallyOverridden();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -717,6 +717,13 @@ class IsolateGroup : public IntrusiveDListEntry<IsolateGroup> {
|
|||
isolate_group_flags_ =
|
||||
AllClassesFinalizedBit::update(value, isolate_group_flags_);
|
||||
}
|
||||
bool has_dynamically_extendable_classes() const {
|
||||
return HasDynamicallyExtendableClassesBit::decode(isolate_group_flags_);
|
||||
}
|
||||
void set_has_dynamically_extendable_classes(bool value) {
|
||||
isolate_group_flags_ =
|
||||
HasDynamicallyExtendableClassesBit::update(value, isolate_group_flags_);
|
||||
}
|
||||
|
||||
bool remapping_cids() const {
|
||||
return RemappingCidsBit::decode(isolate_group_flags_);
|
||||
|
@ -780,7 +787,8 @@ class IsolateGroup : public IntrusiveDListEntry<IsolateGroup> {
|
|||
V(UseOsr) \
|
||||
V(SnapshotIsDontNeedSafe) \
|
||||
V(BranchCoverage) \
|
||||
V(Coverage)
|
||||
V(Coverage) \
|
||||
V(HasDynamicallyExtendableClasses)
|
||||
|
||||
// Isolate group specific flags.
|
||||
enum FlagBits {
|
||||
|
|
|
@ -1374,6 +1374,10 @@ void KernelLoader::LoadClass(const Library& library,
|
|||
if (HasPragma::decode(pragma_bits)) {
|
||||
out_class->set_has_pragma(true);
|
||||
}
|
||||
if (DynModuleExtendablePragma::decode(pragma_bits)) {
|
||||
out_class->set_is_dynamically_extendable(true);
|
||||
IG->set_has_dynamically_extendable_classes(true);
|
||||
}
|
||||
class_helper.SetJustRead(ClassHelper::kAnnotations);
|
||||
class_helper.ReadUntilExcluding(ClassHelper::kTypeParameters);
|
||||
intptr_t type_parameter_counts =
|
||||
|
@ -1605,6 +1609,8 @@ void KernelLoader::FinishClassLoading(const Class& klass,
|
|||
signature.set_result_type(T.ReceiverType(klass));
|
||||
function.set_has_pragma(HasPragma::decode(pragma_bits));
|
||||
function.set_is_visible(!InvisibleFunctionPragma::decode(pragma_bits));
|
||||
function.SetIsDynamicallyOverridden(
|
||||
DynModuleCanBeOverriddenPragma::decode(pragma_bits));
|
||||
|
||||
FunctionNodeHelper function_node_helper(&helper_);
|
||||
function_node_helper.ReadUntilExcluding(
|
||||
|
@ -1783,6 +1789,15 @@ void KernelLoader::ReadVMAnnotations(intptr_t annotation_count,
|
|||
}
|
||||
*pragma_bits = SharedPragma::update(true, *pragma_bits);
|
||||
}
|
||||
if (constant_reader.IsStringConstant(name_index,
|
||||
"dyn-module:extendable")) {
|
||||
*pragma_bits = DynModuleExtendablePragma::update(true, *pragma_bits);
|
||||
}
|
||||
if (constant_reader.IsStringConstant(name_index,
|
||||
"dyn-module:can-be-overridden")) {
|
||||
*pragma_bits =
|
||||
DynModuleCanBeOverriddenPragma::update(true, *pragma_bits);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
helper_.SkipExpression();
|
||||
|
@ -1848,6 +1863,8 @@ void KernelLoader::LoadProcedure(const Library& library,
|
|||
procedure_helper.IsMemberSignature() ||
|
||||
is_synthetic);
|
||||
function.set_is_visible(!InvisibleFunctionPragma::decode(pragma_bits));
|
||||
function.SetIsDynamicallyOverridden(
|
||||
DynModuleCanBeOverriddenPragma::decode(pragma_bits));
|
||||
if (register_function) {
|
||||
functions_.Add(&function);
|
||||
} else {
|
||||
|
|
|
@ -236,6 +236,10 @@ class KernelLoader : public ValueObject {
|
|||
using FfiNativePragma =
|
||||
BitField<uint32_t, bool, DeeplyImmutablePragma::kNextBit, 1>;
|
||||
using SharedPragma = BitField<uint32_t, bool, FfiNativePragma::kNextBit, 1>;
|
||||
using DynModuleExtendablePragma =
|
||||
BitField<uint32_t, bool, SharedPragma::kNextBit, 1>;
|
||||
using DynModuleCanBeOverriddenPragma =
|
||||
BitField<uint32_t, bool, DynModuleExtendablePragma::kNextBit, 1>;
|
||||
|
||||
void FinishTopLevelClassLoading(const Class& toplevel_class,
|
||||
const Library& library,
|
||||
|
|
|
@ -3156,6 +3156,17 @@ void Class::set_can_be_future(bool value) const {
|
|||
set_state_bits(CanBeFutureBit::update(value, state_bits()));
|
||||
}
|
||||
|
||||
void Class::set_is_dynamically_extendable(bool value) const {
|
||||
ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
|
||||
set_state_bits(IsDynamicallyExtendableBit::update(value, state_bits()));
|
||||
}
|
||||
|
||||
void Class::set_has_dynamically_extendable_subtypes(bool value) const {
|
||||
ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
|
||||
set_state_bits(
|
||||
HasDynamicallyExtendableSubtypesBit::update(value, state_bits()));
|
||||
}
|
||||
|
||||
// Initialize class fields of type Array with empty array.
|
||||
void Class::InitEmptyFields() const {
|
||||
if (Object::empty_array().ptr() == Array::null()) {
|
||||
|
@ -11378,6 +11389,13 @@ bool Function::NeedsMonomorphicCheckedEntry(Zone* zone) const {
|
|||
return true;
|
||||
}
|
||||
|
||||
// Any method from the class with a dynamically loaded subtype
|
||||
// can be called via switchable call (when cid range check fails
|
||||
// during conditional table dispatch).
|
||||
if (Class::Handle(zone, Owner()).has_dynamically_extendable_subtypes()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Only if there are dynamic callers and if we didn't create a dyn:* forwarder
|
||||
// for it do we need the monomorphic checked entry.
|
||||
return HasDynamicCallers(zone) &&
|
||||
|
@ -27103,8 +27121,11 @@ EntryPointPragma FindEntryPointPragma(IsolateGroup* IG,
|
|||
continue;
|
||||
}
|
||||
*reusable_field_handle = IG->object_store()->pragma_name();
|
||||
if (Instance::Cast(*pragma).GetField(*reusable_field_handle) !=
|
||||
Symbols::vm_entry_point().ptr()) {
|
||||
const auto pragma_name =
|
||||
Instance::Cast(*pragma).GetField(*reusable_field_handle);
|
||||
if ((pragma_name != Symbols::vm_entry_point().ptr()) &&
|
||||
(pragma_name != Symbols::dyn_module_callable().ptr()) &&
|
||||
(pragma_name != Symbols::dyn_module_extendable().ptr())) {
|
||||
continue;
|
||||
}
|
||||
*reusable_field_handle = IG->object_store()->pragma_options();
|
||||
|
|
|
@ -2068,6 +2068,11 @@ class Class : public Object {
|
|||
// It means that variable of static type based on this class may hold
|
||||
// a Future instance.
|
||||
kCanBeFutureBit,
|
||||
// This class can be extended, implemented or mixed-in by
|
||||
// a dynamically loaded class.
|
||||
kIsDynamicallyExtendableBit,
|
||||
// This class has a dynamically extendable subtype.
|
||||
kHasDynamicallyExtendableSubtypesBit,
|
||||
};
|
||||
class ConstBit : public BitField<uint32_t, bool, kConstBit, 1> {};
|
||||
class ImplementedBit : public BitField<uint32_t, bool, kImplementedBit, 1> {};
|
||||
|
@ -2106,6 +2111,13 @@ class Class : public Object {
|
|||
class IsFutureSubtypeBit
|
||||
: public BitField<uint32_t, bool, kIsFutureSubtypeBit, 1> {};
|
||||
class CanBeFutureBit : public BitField<uint32_t, bool, kCanBeFutureBit, 1> {};
|
||||
class IsDynamicallyExtendableBit
|
||||
: public BitField<uint32_t, bool, kIsDynamicallyExtendableBit, 1> {};
|
||||
class HasDynamicallyExtendableSubtypesBit
|
||||
: public BitField<uint32_t,
|
||||
bool,
|
||||
kHasDynamicallyExtendableSubtypesBit,
|
||||
1> {};
|
||||
|
||||
void set_name(const String& value) const;
|
||||
void set_user_name(const String& value) const;
|
||||
|
@ -2175,6 +2187,16 @@ class Class : public Object {
|
|||
void set_can_be_future(bool value) const;
|
||||
bool can_be_future() const { return CanBeFutureBit::decode(state_bits()); }
|
||||
|
||||
void set_is_dynamically_extendable(bool value) const;
|
||||
bool is_dynamically_extendable() const {
|
||||
return IsDynamicallyExtendableBit::decode(state_bits());
|
||||
}
|
||||
|
||||
void set_has_dynamically_extendable_subtypes(bool value) const;
|
||||
bool has_dynamically_extendable_subtypes() const {
|
||||
return HasDynamicallyExtendableSubtypesBit::decode(state_bits());
|
||||
}
|
||||
|
||||
private:
|
||||
void set_functions(const Array& value) const;
|
||||
void set_fields(const Array& value) const;
|
||||
|
@ -4063,11 +4085,14 @@ class Function : public Object {
|
|||
// a hoisted instruction.
|
||||
// 'ProhibitsBoundsCheckGeneralization' is true if this function deoptimized
|
||||
// before on a generalized bounds check.
|
||||
// IsDynamicallyOverridden: This function can be overridden in a dynamically
|
||||
// loaded class.
|
||||
#define STATE_BITS_LIST(V) \
|
||||
V(WasCompiled) \
|
||||
V(WasExecutedBit) \
|
||||
V(ProhibitsInstructionHoisting) \
|
||||
V(ProhibitsBoundsCheckGeneralization)
|
||||
V(ProhibitsBoundsCheckGeneralization) \
|
||||
V(IsDynamicallyOverridden)
|
||||
|
||||
enum StateBits {
|
||||
#define DECLARE_FLAG_POS(Name) k##Name##Pos,
|
||||
|
|
|
@ -514,6 +514,8 @@ class ObjectPointerVisitor;
|
|||
V(current_position, ":current_position") \
|
||||
V(dynamic_assert_assignable_stc_check, \
|
||||
":dynamic_assert_assignable_stc_check") \
|
||||
V(dyn_module_callable, "dyn-module:callable") \
|
||||
V(dyn_module_extendable, "dyn-module:extendable") \
|
||||
V(end, "end") \
|
||||
V(executable, "executable") \
|
||||
V(from, "from") \
|
||||
|
|
Loading…
Reference in a new issue