From ea1360fbafd4f6adf6f0ecfb8f4b248ba8618943 Mon Sep 17 00:00:00 2001 From: Alexander Markov Date: Mon, 8 Jun 2026 07:17:10 -0700 Subject: [PATCH] [modular_aot] Fixes for code generation of TypeCast * Use exact code pattern for calling TTS which is expected by the VM at runtime. * Always set instantiator/function type arguments registers for TTS. * List all registers potentially clobbered by TTS. Issue: https://github.com/dart-lang/sdk/issues/61635 Change-Id: I011bcfc8b755864271877cc36d365ba2b5f10648 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/509480 Reviewed-by: Slava Egorov Commit-Queue: Alexander Markov --- .../lib/back_end/arm64/code_generator.dart | 15 ++++++++--- .../lib/back_end/arm64/constraints.dart | 13 ++++++--- .../back_end/arm64/stub_code_generator.dart | 1 + .../lib/back_end/object_pool.dart | 19 +++++++++++++ .../lib/snapshot/snapshot.dart | 8 ++++++ .../testcases/lowering_test.dart.expect | 6 ++--- .../register_allocator_test.dart.expect | 27 ++++++++++--------- 7 files changed, 66 insertions(+), 23 deletions(-) diff --git a/pkg/native_compiler/lib/back_end/arm64/code_generator.dart b/pkg/native_compiler/lib/back_end/arm64/code_generator.dart index 3386c0a1a24..a1eba2da258 100644 --- a/pkg/native_compiler/lib/back_end/arm64/code_generator.dart +++ b/pkg/native_compiler/lib/back_end/arm64/code_generator.dart @@ -19,6 +19,7 @@ import 'package:native_compiler/back_end/assembler.dart'; import 'package:native_compiler/back_end/code_generator.dart'; import 'package:native_compiler/back_end/locations.dart'; import 'package:native_compiler/back_end/object_pool.dart'; +import 'package:native_compiler/runtime/names.dart'; import 'package:native_compiler/runtime/type_utils.dart'; import 'package:native_compiler/runtime/vm_defs.dart'; @@ -1042,7 +1043,7 @@ final class Arm64CodeGenerator extends CodeGenerator { _asm.loadFromPool(TypeTestingStub.dstTypeReg, dartType); } _asm.ldr( - tempReg, + TypeTestingStub.entryPointReg, _asm.fieldAddress( TypeTestingStub.dstTypeReg, vmOffsets.AbstractType_type_test_stub_entry_point_offset, @@ -1061,8 +1062,16 @@ final class Arm64CodeGenerator extends CodeGenerator { hasFunctionTypeArgs: hasFunctionTypeArgs, ), ); - _asm.loadFromPool(TypeTestingStub.subtypeTestCacheReg, stc); - _asm.blr(tempReg); + if (instr.inputCount == 1) { + _asm.mov(TypeTestingStub.instantiatorTypeArgumentsReg, nullReg); + _asm.mov(TypeTestingStub.functionTypeArgumentsReg, nullReg); + } + // VM expects exact code pattern for type testing stub calling sequence. + _asm.loadFromPool( + TypeTestingStub.subtypeTestCacheReg, + SubtypeTestCacheWithName(stc, Name('', null)), + ); + _asm.blr(TypeTestingStub.entryPointReg); } _asm.bind(done); diff --git a/pkg/native_compiler/lib/back_end/arm64/constraints.dart b/pkg/native_compiler/lib/back_end/arm64/constraints.dart index c924dcc0922..546b24a98b3 100644 --- a/pkg/native_compiler/lib/back_end/arm64/constraints.dart +++ b/pkg/native_compiler/lib/back_end/arm64/constraints.dart @@ -241,10 +241,15 @@ final class Arm64Constraints extends Constraints { TypeTestingStub.functionTypeArgumentsReg, ], ], - const [ - TypeTestingStub.dstTypeReg, - TypeTestingStub.subtypeTestCacheReg, - TypeTestingStub.scratchReg, + // Type testing stub can call runtime without preserving registers. + [ + for (final r in allocatableRegisters) + if (r != TypeTestingStub.instanceReg && + ((instr.inputCount == 1) || + (r != TypeTestingStub.instantiatorTypeArgumentsReg && + r != TypeTestingStub.functionTypeArgumentsReg))) + r, + ...allocatableFPRegisters, ], ); } diff --git a/pkg/native_compiler/lib/back_end/arm64/stub_code_generator.dart b/pkg/native_compiler/lib/back_end/arm64/stub_code_generator.dart index 6fab115a6c5..5a327b8b05d 100644 --- a/pkg/native_compiler/lib/back_end/arm64/stub_code_generator.dart +++ b/pkg/native_compiler/lib/back_end/arm64/stub_code_generator.dart @@ -136,6 +136,7 @@ final class TypeTestingStub { static const Register subtypeTestCacheReg = R3; static const Register scratchReg = R4; static const Register subtypeTestCacheResultReg = R7; + static const Register entryPointReg = R9; } final class InstantiateTypeArgumentsStub { diff --git a/pkg/native_compiler/lib/back_end/object_pool.dart b/pkg/native_compiler/lib/back_end/object_pool.dart index 9ee4f4924bf..83733c040bb 100644 --- a/pkg/native_compiler/lib/back_end/object_pool.dart +++ b/pkg/native_compiler/lib/back_end/object_pool.dart @@ -132,3 +132,22 @@ final class SubtypeTestCache { // Use identity hashCode and == as separate subtype test caches are // used for each type check. } + +/// A pair (subtype test cache, name). VM decodes type testing stub +/// calling sequence and reads name from object pool immediately +/// after subtype test cache when throwing type errors. +final class SubtypeTestCacheWithName extends PairSpecializedEntry { + final SubtypeTestCache stc; + final Name name; + + SubtypeTestCacheWithName(this.stc, this.name); + + @override + int get hashCode => finalizeHash(combineHash(stc.hashCode, name.hashCode)); + + @override + bool operator ==(Object other) => + other is SubtypeTestCacheWithName && + this.stc == other.stc && + this.name == other.name; +} diff --git a/pkg/native_compiler/lib/snapshot/snapshot.dart b/pkg/native_compiler/lib/snapshot/snapshot.dart index 4b531443e97..9d0eb9fcea8 100644 --- a/pkg/native_compiler/lib/snapshot/snapshot.dart +++ b/pkg/native_compiler/lib/snapshot/snapshot.dart @@ -1631,6 +1631,9 @@ final class ObjectPoolSerializationCluster extends SerializationCluster { entry.selector, ); serializer.push(icData); + case SubtypeTestCacheWithName(): + serializer.push(entry.stc); + serializer.push(entry.name); case ReservedEntry(): break; } @@ -1673,6 +1676,11 @@ final class ObjectPoolSerializationCluster extends SerializationCluster { case DynamicCallEntry(): serializer.writeUint(ObjectPoolEntryKind.dynamicCall.index); serializer.writeRefId(icDatas[entry]); + case SubtypeTestCacheWithName(): + serializer.writeUint(ObjectPoolEntryKind.objectRef.index); + serializer.writeRefId(entry.stc); + serializer.writeUint(ObjectPoolEntryKind.objectRef.index); + serializer.writeRefId(entry.name); case ReservedEntry(): } } else if (entry is UnboxedIntConstant) { diff --git a/pkg/native_compiler/testcases/lowering_test.dart.expect b/pkg/native_compiler/testcases/lowering_test.dart.expect index 792a57f6907..43d00741413 100644 --- a/pkg/native_compiler/testcases/lowering_test.dart.expect +++ b/pkg/native_compiler/testcases/lowering_test.dart.expect @@ -30,9 +30,9 @@ B0 = EntryBlock() v16 = TypeTest(v3, v12, v1, List) # RA: R7 <- (R0, R2, R1) temps: [R8, R3, R4] ParallelMove output(R7 -> vloc:R7) DirectCall print(v16) # RA: R0 <- (R7) temps: [R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R19, R20, R23, R25, V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30] - ParallelMove output(param[0] -> R5, param[2] -> R1, stack[0] -> R2) - ParallelMove input(vloc:R1 -> R0, vloc:R2 -> R2, vloc:R5 -> R1) - v19 = TypeCast(v3, v37, v1, Map) # RA: R0 <- (R0, R2, R1) temps: [R8, R3, R4] + ParallelMove output(param[0] -> R0, param[2] -> R1, stack[0] -> R2) + ParallelMove input(vloc:R1 -> R0, vloc:R2 -> R2, vloc:R0 -> R1) + v19 = TypeCast(v3, v37, v1, Map) # RA: R0 <- (R0, R2, R1) temps: [R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R19, R20, R23, R25, V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30] ParallelMove output(R0 -> vloc:R0) DirectCall print(v19) # RA: R0 <- (R0) temps: [R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R19, R20, R23, R25, V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30] v21 = AllocateClosure() # RA: R0 <- () temps: [R2, R3, R4] diff --git a/pkg/native_compiler/testcases/register_allocator_test.dart.expect b/pkg/native_compiler/testcases/register_allocator_test.dart.expect index 4dcc645b06a..9075e956d7a 100644 --- a/pkg/native_compiler/testcases/register_allocator_test.dart.expect +++ b/pkg/native_compiler/testcases/register_allocator_test.dart.expect @@ -401,15 +401,15 @@ B6 = TargetBlock() idom:B0 ParallelMove output(param[0] -> R0) v17 = InterfaceCall getter StreamController._varData(v1) # RA: R0 <- (R0) temps: [R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R19, R20, R23, R25, V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30] ParallelMove output(R0 -> vloc:R0, param[0] -> R1) - v25 = LoadInstanceField(StreamController.#typeArguments, v1) # RA: R5 <- (R1) - ParallelMove output(R5 -> stack[0]) - ParallelMove input(vloc:R0 -> R0, vloc:R5 -> R2, NullConstant(null) -> R1) - v18 = TypeCast(v17, v25, v13, _StreamControllerAddStreamState) # RA: R0 <- (R0, R2, R1) temps: [R8, R3, R4] + v25 = LoadInstanceField(StreamController.#typeArguments, v1) # RA: R1 <- (R1) + ParallelMove output(R1 -> stack[0]) + ParallelMove input(vloc:R0 -> R0, vloc:R1 -> R2, NullConstant(null) -> R1) + v18 = TypeCast(v17, v25, v13, _StreamControllerAddStreamState) # RA: R0 <- (R0, R2, R1) temps: [R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R19, R20, R23, R25, V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30] ParallelMove output(R0 -> vloc:R0) v21 = InterfaceCall getter _StreamControllerAddStreamState._varData(v18) # RA: R0 <- (R0) temps: [R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R19, R20, R23, R25, V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30] ParallelMove output(R0 -> vloc:R0, stack[0] -> R2) ParallelMove input(vloc:R0 -> R0, vloc:R2 -> R2, NullConstant(null) -> R1) - v22 = TypeCast(v21, v25, v13, _PendingEvents?) # RA: R0 <- (R0, R2, R1) temps: [R8, R3, R4] + v22 = TypeCast(v21, v25, v13, _PendingEvents?) # RA: R0 <- (R0, R2, R1) temps: [R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R19, R20, R23, R25, V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30] ParallelMove output(R0 -> vloc:R0) ParallelMove input(vloc:R0 -> R0) Return(v22) # RA: (R0) @@ -420,7 +420,7 @@ B7 = TargetBlock() idom:B0 ParallelMove output(R0 -> vloc:R0, param[0] -> R1) v26 = LoadInstanceField(StreamController.#typeArguments, v1) # RA: R2 <- (R1) ParallelMove input(vloc:R0 -> R0, vloc:R2 -> R2, NullConstant(null) -> R1) - v14 = TypeCast(v12, v26, v13, _PendingEvents?) # RA: R0 <- (R0, R2, R1) temps: [R8, R3, R4] + v14 = TypeCast(v12, v26, v13, _PendingEvents?) # RA: R0 <- (R0, R2, R1) temps: [R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R19, R20, R23, R25, V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30] ParallelMove output(R0 -> vloc:R0) ParallelMove input(vloc:R0 -> R0) Return(v14) # RA: (R0) @@ -1198,7 +1198,7 @@ B577 = TargetBlock() idom:B570 v588 = InterfaceCall List.[](v585, v788) # RA: R0 <- (-, R1) temps: [R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R19, R20, R23, R25, V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30] ParallelMove output(R0 -> vloc:R0) ParallelMove input(vloc:R0 -> R0) - v589 = TypeCast(v588, num) # RA: R0 <- (R0) temps: [R8, R3, R4] + v589 = TypeCast(v588, num) # RA: R0 <- (R0) temps: [R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R19, R20, R23, R25, V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30] ParallelMove output(R0 -> vloc:R1, stack[20] -> V0) v789 = BoxDouble(v564) # RA: R4 <- (V0) temps: [R0, R3, R2] v590 = InterfaceCall double./(v789, v589) # RA: R0 <- (R4, R1) temps: [R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R19, R20, R23, R25, V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30] @@ -1230,7 +1230,7 @@ B609 = TargetBlock() idom:B597 v618 = InterfaceCall List.[](v585, v794) # RA: R0 <- (-, R4) temps: [R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R19, R20, R23, R25, V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30] ParallelMove output(R0 -> vloc:R0) ParallelMove input(vloc:R0 -> R0) - v619 = TypeCast(v618, num) # RA: R0 <- (R0) temps: [R8, R3, R4] + v619 = TypeCast(v618, num) # RA: R0 <- (R0) temps: [R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R19, R20, R23, R25, V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30] ParallelMove output(R0 -> vloc:R2, stack[20] -> V0) v795 = BoxDouble(v564) # RA: R4 <- (V0) temps: [R1, R3, R0] v620 = InterfaceCall double.*(v795, v619) # RA: R0 <- (R4, R2) temps: [R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R19, R20, R23, R25, V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30] @@ -1675,12 +1675,13 @@ B0 = EntryBlock() v2 = Parameter(b) # RA: param[1] <- () ParallelMove output(param[1] -> vloc:R1, param[0] -> vloc:R0) ParallelMove input(vloc:R0 -> R0) - v4 = TypeCast(v1, Comparable) # RA: R0 <- (R0) temps: [R8, R3, R4] - ParallelMove output(R0 -> vloc:R2) + v4 = TypeCast(v1, Comparable) # RA: R0 <- (R0) temps: [R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R19, R20, R23, R25, V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30] + ParallelMove output(R0 -> vloc:R0, param[1] -> R1) + ParallelMove spill(R0 -> stack[0]) ParallelMove input(vloc:R1 -> R0) - v6 = TypeCast(v2, Comparable) # RA: R0 <- (R0) temps: [R8, R3, R4] - ParallelMove output(R0 -> vloc:R0) - v7 = DirectCall Comparable.compare(v4, v6) # RA: R0 <- (R2, R0) temps: [R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R19, R20, R23, R25, V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30] + v6 = TypeCast(v2, Comparable) # RA: R0 <- (R0) temps: [R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R19, R20, R23, R25, V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30] + ParallelMove output(R0 -> vloc:R0, stack[0] -> R1) + v7 = DirectCall Comparable.compare(v4, v6) # RA: R0 <- (R1, R0) temps: [R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R19, R20, R23, R25, V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30] ParallelMove output(R0 -> vloc:R0) ParallelMove input(vloc:R0 -> R0) Return(v7) # RA: (R0)