[ VM ] Ensure TypeArguments register is preserved when regenerating allocation stubs for parameterized classes
Fixes https://github.com/flutter/flutter/issues/88104
TEST=pkg/vm_service/test/regress_88104_test.dart
Change-Id: I87affc62189bc076cf6e46c47e76f4fb005f9068
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/243850
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
diff --git a/pkg/vm_service/test/regress_88104_test.dart b/pkg/vm_service/test/regress_88104_test.dart
new file mode 100644
index 0000000..b864dfd
--- /dev/null
+++ b/pkg/vm_service/test/regress_88104_test.dart
@@ -0,0 +1,48 @@
+// Copyright (c) 2022, 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.
+
+// Regression test for https://github.com/flutter/flutter/issues/88104.
+//
+// Ensures that the `TypeArguments` register is correctly preserved when
+// regenerating the allocation stub for generic classes after enabling
+// allocation tracing.
+import 'dart:async';
+import 'dart:developer';
+
+import 'package:vm_service/vm_service.dart';
+
+import 'common/service_test_common.dart';
+import 'common/test_helper.dart';
+
+class Foo<T> {}
+
+testMain() async {
+ debugger();
+ for (int i = 0; i < 10; ++i) {
+ Foo<int>();
+ await Future.delayed(const Duration(milliseconds: 10));
+ }
+}
+
+final tests = <IsolateTest>[
+ hasStoppedAtBreakpoint,
+ (VmService service, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+ final isolate = await service.getIsolate(isolateId);
+ final rootLibId = isolate.rootLib!.id!;
+ final rootLib = await service.getObject(isolateId, rootLibId) as Library;
+ final fooCls = rootLib.classes!.first;
+ await service.setTraceClassAllocation(isolateId, fooCls.id!, true);
+ },
+ resumeIsolate,
+ hasStoppedAtExit,
+];
+
+main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'regress_88104_test.dart',
+ testeeConcurrent: testMain,
+ pause_on_exit: true,
+ );
diff --git a/runtime/vm/compiler/stub_code_compiler_arm.cc b/runtime/vm/compiler/stub_code_compiler_arm.cc
index 5af92dc..fc6b0ea 100644
--- a/runtime/vm/compiler/stub_code_compiler_arm.cc
+++ b/runtime/vm/compiler/stub_code_compiler_arm.cc
@@ -737,6 +737,33 @@
__ Branch(FieldAddress(R0, target::Code::entry_point_offset()));
}
+// Called from object allocate instruction when the allocation stub for a
+// generic class has been disabled.
+void StubCodeCompiler::GenerateFixParameterizedAllocationStubTargetStub(
+ Assembler* assembler) {
+ // Load code pointer to this stub from the thread:
+ // The one that is passed in, is not correct - it points to the code object
+ // that needs to be replaced.
+ __ ldr(CODE_REG,
+ Address(THR, target::Thread::fix_allocation_stub_code_offset()));
+ __ EnterStubFrame();
+ // Preserve type arguments register.
+ __ Push(AllocateObjectABI::kTypeArgumentsReg);
+ // Setup space on stack for return value.
+ __ LoadImmediate(R0, 0);
+ __ Push(R0);
+ __ CallRuntime(kFixAllocationStubTargetRuntimeEntry, 0);
+ // Get Code object result.
+ __ Pop(R0);
+ // Restore type arguments register.
+ __ Push(AllocateObjectABI::kTypeArgumentsReg);
+ // Remove the stub frame.
+ __ LeaveStubFrame();
+ // Jump to the dart function.
+ __ mov(CODE_REG, Operand(R0));
+ __ Branch(FieldAddress(R0, target::Code::entry_point_offset()));
+}
+
// Input parameters:
// R2: smi-tagged argument count, may be zero.
// FP[target::frame_layout.param_end_from_fp + 1]: last argument.
diff --git a/runtime/vm/compiler/stub_code_compiler_arm64.cc b/runtime/vm/compiler/stub_code_compiler_arm64.cc
index ee14dd9..ab3c748 100644
--- a/runtime/vm/compiler/stub_code_compiler_arm64.cc
+++ b/runtime/vm/compiler/stub_code_compiler_arm64.cc
@@ -972,6 +972,32 @@
__ br(R0);
}
+// Called from object allocate instruction when the allocation stub for a
+// generic class has been disabled.
+void StubCodeCompiler::GenerateFixParameterizedAllocationStubTargetStub(
+ Assembler* assembler) {
+ // Load code pointer to this stub from the thread:
+ // The one that is passed in, is not correct - it points to the code object
+ // that needs to be replaced.
+ __ ldr(CODE_REG,
+ Address(THR, target::Thread::fix_allocation_stub_code_offset()));
+ __ EnterStubFrame();
+ // Preserve type arguments register.
+ __ Push(AllocateObjectABI::kTypeArgumentsReg);
+ // Setup space on stack for return value.
+ __ Push(ZR);
+ __ CallRuntime(kFixAllocationStubTargetRuntimeEntry, 0);
+ // Get Code object result.
+ __ Pop(CODE_REG);
+ // Restore type arguments register.
+ __ Pop(AllocateObjectABI::kTypeArgumentsReg);
+ // Remove the stub frame.
+ __ LeaveStubFrame();
+ // Jump to the dart function.
+ __ LoadFieldFromOffset(R0, CODE_REG, target::Code::entry_point_offset());
+ __ br(R0);
+}
+
// Input parameters:
// R2: smi-tagged argument count, may be zero.
// FP[target::frame_layout.param_end_from_fp + 1]: last argument.
diff --git a/runtime/vm/compiler/stub_code_compiler_ia32.cc b/runtime/vm/compiler/stub_code_compiler_ia32.cc
index ad1f072..3f0639f 100644
--- a/runtime/vm/compiler/stub_code_compiler_ia32.cc
+++ b/runtime/vm/compiler/stub_code_compiler_ia32.cc
@@ -558,6 +558,24 @@
__ int3();
}
+// Called from object allocate instruction when the allocation stub for a
+// generic class has been disabled.
+void StubCodeCompiler::GenerateFixParameterizedAllocationStubTargetStub(
+ Assembler* assembler) {
+ __ EnterStubFrame();
+ // Preserve type arguments register.
+ __ pushl(AllocateObjectABI::kTypeArgumentsReg);
+ __ pushl(Immediate(0)); // Setup space on stack for return value.
+ __ CallRuntime(kFixAllocationStubTargetRuntimeEntry, 0);
+ __ popl(EAX); // Get Code object.
+ // Restore type arguments register.
+ __ popl(AllocateObjectABI::kTypeArgumentsReg);
+ __ movl(EAX, FieldAddress(EAX, target::Code::entry_point_offset()));
+ __ LeaveFrame();
+ __ jmp(EAX);
+ __ int3();
+}
+
// Input parameters:
// EDX: smi-tagged argument count, may be zero.
// EBP[target::frame_layout.param_end_from_fp + 1]: last argument.
diff --git a/runtime/vm/compiler/stub_code_compiler_riscv.cc b/runtime/vm/compiler/stub_code_compiler_riscv.cc
index 0faaa88..b9fc91d 100644
--- a/runtime/vm/compiler/stub_code_compiler_riscv.cc
+++ b/runtime/vm/compiler/stub_code_compiler_riscv.cc
@@ -791,6 +791,32 @@
__ jr(TMP);
}
+// Called from object allocate instruction when the allocation stub for a
+// generic class has been disabled.
+void StubCodeCompiler::GenerateFixParameterizedAllocationStubTargetStub(
+ Assembler* assembler) {
+ // Load code pointer to this stub from the thread:
+ // The one that is passed in, is not correct - it points to the code object
+ // that needs to be replaced.
+ __ lx(CODE_REG,
+ Address(THR, target::Thread::fix_allocation_stub_code_offset()));
+ __ EnterStubFrame();
+ // Preserve type arguments register.
+ __ PushRegister(AllocateObjectABI::kTypeArgumentsReg);
+ // Setup space on stack for return value.
+ __ PushRegister(ZR);
+ __ CallRuntime(kFixAllocationStubTargetRuntimeEntry, 0);
+ // Get Code object result.
+ __ PopRegister(CODE_REG);
+ // Restore type arguments register.
+ __ PopRegister(AllocateObjectABI::kTypeArgumentsReg);
+ // Remove the stub frame.
+ __ LeaveStubFrame();
+ // Jump to the dart function.
+ __ LoadFieldFromOffset(TMP, CODE_REG, target::Code::entry_point_offset());
+ __ jr(TMP);
+}
+
// Input parameters:
// T2: smi-tagged argument count, may be zero.
// FP[target::frame_layout.param_end_from_fp + 1]: last argument.
diff --git a/runtime/vm/compiler/stub_code_compiler_x64.cc b/runtime/vm/compiler/stub_code_compiler_x64.cc
index 2b8bb2a..92e562e 100644
--- a/runtime/vm/compiler/stub_code_compiler_x64.cc
+++ b/runtime/vm/compiler/stub_code_compiler_x64.cc
@@ -877,6 +877,28 @@
__ int3();
}
+// Called from object allocate instruction when the allocation stub for a
+// generic class has been disabled.
+void StubCodeCompiler::GenerateFixParameterizedAllocationStubTargetStub(
+ Assembler* assembler) {
+ // Load code pointer to this stub from the thread:
+ // The one that is passed in, is not correct - it points to the code object
+ // that needs to be replaced.
+ __ movq(CODE_REG,
+ Address(THR, target::Thread::fix_allocation_stub_code_offset()));
+ __ EnterStubFrame();
+ // Setup space on stack for return value.
+ __ pushq(AllocateObjectABI::kTypeArgumentsReg);
+ __ pushq(Immediate(0));
+ __ CallRuntime(kFixAllocationStubTargetRuntimeEntry, 0);
+ __ popq(CODE_REG); // Get Code object.
+ __ popq(AllocateObjectABI::kTypeArgumentsReg);
+ __ movq(RAX, FieldAddress(CODE_REG, target::Code::entry_point_offset()));
+ __ LeaveStubFrame();
+ __ jmp(RAX);
+ __ int3();
+}
+
// Input parameters:
// R10: smi-tagged argument count, may be zero.
// RBP[target::frame_layout.param_end_from_fp + 1]: last argument.
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index c454796..86e583b 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -5398,7 +5398,7 @@
}
ASSERT(!existing_stub.IsDisabled());
// Change the stub so that the next caller will regenerate the stub.
- existing_stub.DisableStubCode();
+ existing_stub.DisableStubCode(NumTypeParameters() > 0);
// Disassociate the existing stub from class.
untag()->set_allocation_stub(Code::null());
#endif // defined(DART_PRECOMPILED_RUNTIME)
@@ -17641,11 +17641,13 @@
new_code.UncheckedEntryPointOffset());
}
-void Code::DisableStubCode() const {
+void Code::DisableStubCode(bool is_cls_parameterized) const {
GcSafepointOperationScope safepoint(Thread::Current());
ASSERT(IsAllocationStubCode());
ASSERT(instructions() == active_instructions());
- const Code& new_code = StubCode::FixAllocationStubTarget();
+ const Code& new_code = is_cls_parameterized
+ ? StubCode::FixParameterizedAllocationStubTarget()
+ : StubCode::FixAllocationStubTarget();
SetActiveInstructions(Instructions::Handle(new_code.instructions()),
new_code.UncheckedEntryPointOffset());
}
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index 19f2fd3..6f226c3 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -6857,7 +6857,7 @@
void DisableDartCode() const;
- void DisableStubCode() const;
+ void DisableStubCode(bool is_cls_parameterized) const;
void Enable() const {
if (!IsDisabled()) return;
diff --git a/runtime/vm/stub_code_list.h b/runtime/vm/stub_code_list.h
index f9110be..39e1f49 100644
--- a/runtime/vm/stub_code_list.h
+++ b/runtime/vm/stub_code_list.h
@@ -75,6 +75,7 @@
V(ICCallThroughCode) \
V(MegamorphicCall) \
V(FixAllocationStubTarget) \
+ V(FixParameterizedAllocationStubTarget) \
V(Deoptimize) \
V(DeoptimizeLazyFromReturn) \
V(DeoptimizeLazyFromThrow) \