diff --git a/pkg/vm/lib/transformations/dynamic_interface_annotator.dart b/pkg/vm/lib/transformations/dynamic_interface_annotator.dart index 3eab1771669..64f25319355 100644 --- a/pkg/vm/lib/transformations/dynamic_interface_annotator.dart +++ b/pkg/vm/lib/transformations/dynamic_interface_annotator.dart @@ -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); diff --git a/pkg/vm/lib/transformations/type_flow/analysis.dart b/pkg/vm/lib/transformations/type_flow/analysis.dart index f23ec893cc6..4defc26d02d 100644 --- a/pkg/vm/lib/transformations/type_flow/analysis.dart +++ b/pkg/vm/lib/transformations/type_flow/analysis.dart @@ -760,6 +760,10 @@ final class _DispatchableInvocation extends _Invocation { ConeType receiver, Map 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( diff --git a/runtime/tests/vm/dart/dynamic_module_pragmas_il_test.dart b/runtime/tests/vm/dart/dynamic_module_pragmas_il_test.dart new file mode 100644 index 00000000000..9fc2e616ba4 --- /dev/null +++ b/runtime/tests/vm/dart/dynamic_module_pragmas_il_test.dart @@ -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), + ]), + ]); +} diff --git a/runtime/vm/class_finalizer.cc b/runtime/vm/class_finalizer.cc index 72749067b75..3fe567e5272 100644 --- a/runtime/vm/class_finalizer.cc +++ b/runtime/vm/class_finalizer.cc @@ -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) { diff --git a/runtime/vm/class_finalizer.h b/runtime/vm/class_finalizer.h index 351a11ddbe8..7f65e81091c 100644 --- a/runtime/vm/class_finalizer.h +++ b/runtime/vm/class_finalizer.h @@ -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 diff --git a/runtime/vm/compiler/aot/aot_call_specializer.cc b/runtime/vm/compiler/aot/aot_call_specializer.cc index 6453c19cb7d..88a07540fd8 100644 --- a/runtime/vm/compiler/aot/aot_call_specializer.cc +++ b/runtime/vm/compiler/aot/aot_call_specializer.cc @@ -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) { - // 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 diff --git a/runtime/vm/compiler/aot/aot_call_specializer.h b/runtime/vm/compiler/aot/aot_call_specializer.h index 64365ed164c..a9382e22d97 100644 --- a/runtime/vm/compiler/aot/aot_call_specializer.h +++ b/runtime/vm/compiler/aot/aot_call_specializer.h @@ -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, diff --git a/runtime/vm/compiler/aot/precompiler.cc b/runtime/vm/compiler/aot/precompiler.cc index 205560e151f..2ebbaa59a43 100644 --- a/runtime/vm/compiler/aot/precompiler.cc +++ b/runtime/vm/compiler/aot/precompiler.cc @@ -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); diff --git a/runtime/vm/compiler/backend/flow_graph.cc b/runtime/vm/compiler/backend/flow_graph.cc index 618cf8250ca..7c017c78a5a 100644 --- a/runtime/vm/compiler/backend/flow_graph.cc +++ b/runtime/vm/compiler/backend/flow_graph.cc @@ -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 diff --git a/runtime/vm/compiler/backend/il.cc b/runtime/vm/compiler/backend/il.cc index 94c22ce8b72..bf707da3df9 100644 --- a/runtime/vm/compiler/backend/il.cc +++ b/runtime/vm/compiler/backend/il.cc @@ -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; } diff --git a/runtime/vm/compiler/backend/linearscan.cc b/runtime/vm/compiler/backend/linearscan.cc index b936ebab274..14e61f35164 100644 --- a/runtime/vm/compiler/backend/linearscan.cc +++ b/runtime/vm/compiler/backend/linearscan.cc @@ -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; } diff --git a/runtime/vm/compiler/backend/range_analysis.cc b/runtime/vm/compiler/backend/range_analysis.cc index 3be8a131e29..3c4a0bf8f54 100644 --- a/runtime/vm/compiler/backend/range_analysis.cc +++ b/runtime/vm/compiler/backend/range_analysis.cc @@ -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; + } } } } diff --git a/runtime/vm/compiler/cha.cc b/runtime/vm/compiler/cha.cc index b5d8e854b2e..5e6498a3db0 100644 --- a/runtime/vm/compiler/cha.cc +++ b/runtime/vm/compiler/cha.cc @@ -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* 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; diff --git a/runtime/vm/compiler/jit/jit_call_specializer.cc b/runtime/vm/compiler/jit/jit_call_specializer.cc index b8b1cfba752..d4a07f504c5 100644 --- a/runtime/vm/compiler/jit/jit_call_specializer.cc +++ b/runtime/vm/compiler/jit/jit_call_specializer.cc @@ -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(); } } diff --git a/runtime/vm/isolate.h b/runtime/vm/isolate.h index 86830311ddd..5c4280744f8 100644 --- a/runtime/vm/isolate.h +++ b/runtime/vm/isolate.h @@ -717,6 +717,13 @@ class IsolateGroup : public IntrusiveDListEntry { 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 { V(UseOsr) \ V(SnapshotIsDontNeedSafe) \ V(BranchCoverage) \ - V(Coverage) + V(Coverage) \ + V(HasDynamicallyExtendableClasses) // Isolate group specific flags. enum FlagBits { diff --git a/runtime/vm/kernel_loader.cc b/runtime/vm/kernel_loader.cc index 5d5d72fc854..27b880e68cb 100644 --- a/runtime/vm/kernel_loader.cc +++ b/runtime/vm/kernel_loader.cc @@ -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 { diff --git a/runtime/vm/kernel_loader.h b/runtime/vm/kernel_loader.h index a351f9c7d5a..2bff1bc9343 100644 --- a/runtime/vm/kernel_loader.h +++ b/runtime/vm/kernel_loader.h @@ -236,6 +236,10 @@ class KernelLoader : public ValueObject { using FfiNativePragma = BitField; using SharedPragma = BitField; + using DynModuleExtendablePragma = + BitField; + using DynModuleCanBeOverriddenPragma = + BitField; void FinishTopLevelClassLoading(const Class& toplevel_class, const Library& library, diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc index 9098f94ee70..7822711946c 100644 --- a/runtime/vm/object.cc +++ b/runtime/vm/object.cc @@ -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(); diff --git a/runtime/vm/object.h b/runtime/vm/object.h index c7aed1d7672..bf50761b7a2 100644 --- a/runtime/vm/object.h +++ b/runtime/vm/object.h @@ -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 {}; class ImplementedBit : public BitField {}; @@ -2106,6 +2111,13 @@ class Class : public Object { class IsFutureSubtypeBit : public BitField {}; class CanBeFutureBit : public BitField {}; + class IsDynamicallyExtendableBit + : public BitField {}; + class HasDynamicallyExtendableSubtypesBit + : public BitField {}; 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, diff --git a/runtime/vm/symbols.h b/runtime/vm/symbols.h index bce66d2a677..e15f6ee8a0f 100644 --- a/runtime/vm/symbols.h +++ b/runtime/vm/symbols.h @@ -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") \