1
0
mirror of https://github.com/dart-lang/sdk synced 2024-07-05 17:30:16 +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:
Alexander Markov 2024-06-18 14:31:24 +00:00 committed by Commit Queue
parent 1e5f96514c
commit 0c9067c626
20 changed files with 527 additions and 35 deletions

View File

@ -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);

View File

@ -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(

View 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),
]),
]);
}

View File

@ -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) {

View File

@ -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

View File

@ -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_ = &it;
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) {
// 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);
if (!interface_target.IsDynamicallyOverridden()) {
// Target functions were removed by tree shaking. This call is dead code,
// or the receiver is always null.
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

View File

@ -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,

View File

@ -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);

View File

@ -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

View File

@ -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,14 +5142,18 @@ 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();
Register temp = locs()->temp(0).reg();
#else
Register temp = TMP;
Register temp = TMP;
#endif
__ AddImmediate(temp, in, -lower);
__ CompareImmediate(temp, upper - lower);
__ AddImmediate(temp, in, -lower);
__ CompareImmediate(temp, upper - lower);
}
ASSERT((kind() == Token::kIS) || (kind() == Token::kISNOT));
return kind() == Token::kIS ? UNSIGNED_LESS_EQUAL : UNSIGNED_GREATER;
}

View File

@ -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;
}

View File

@ -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,12 +2938,14 @@ void LoadClassIdInstr::InferRange(uword* lower, uword* upper) {
if (type.IsType() && !type.IsFutureOrType() &&
!Instance::NullIsAssignableTo(type)) {
const auto& type_class = Class::Handle(type.type_class());
const auto& ranges =
hi->SubtypeRangesForClass(type_class, /*include_abstract=*/false,
/*exclude_null=*/true);
if (ranges.length() > 0) {
*lower = ranges[0].cid_start;
*upper = ranges[ranges.length() - 1].cid_end;
if (!type_class.has_dynamically_extendable_subtypes()) {
const auto& ranges =
hi->SubtypeRangesForClass(type_class, /*include_abstract=*/false,
/*exclude_null=*/true);
if (ranges.length() > 0) {
*lower = ranges[0].cid_start;
*upper = ranges[ranges.length() - 1].cid_end;
}
}
}
}

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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,

View File

@ -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();

View File

@ -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,

View File

@ -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") \