| // Copyright (c) 2020, 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. |
| |
| #include <functional> |
| |
| #include "platform/assert.h" |
| |
| #include "vm/class_finalizer.h" |
| #include "vm/compiler/backend/flow_graph_compiler.h" |
| #include "vm/compiler/backend/il_test_helper.h" |
| #include "vm/symbols.h" |
| #include "vm/type_testing_stubs.h" |
| #include "vm/unit_test.h" |
| |
| #if defined(TARGET_ARCH_ARM64) || defined(TARGET_ARCH_ARM) || \ |
| defined(TARGET_ARCH_X64) |
| |
| namespace dart { |
| |
| #define __ assembler-> |
| |
| static void GenerateInvokeTTSStub(compiler::Assembler* assembler) { |
| auto calculate_breadcrumb = [](const Register& reg) { |
| return 0x10 + 2 * (static_cast<intptr_t>(reg)); |
| }; |
| |
| __ EnterDartFrame(0); |
| |
| for (intptr_t i = 0; i < kNumberOfCpuRegisters; ++i) { |
| if (((1 << i) & kDartAvailableCpuRegs) == 0) continue; |
| if (((1 << i) & TypeTestABI::kAbiRegisters) != 0) continue; |
| if (((1 << i) & TTSInternalRegs::kInternalRegisters) != 0) continue; |
| const Register reg = static_cast<Register>(i); |
| __ LoadImmediate(reg, calculate_breadcrumb(reg)); |
| } |
| |
| // Load the arguments into the right TTS calling convention registers. |
| const intptr_t instance_offset = |
| (kCallerSpSlotFromFp + 3) * compiler::target::kWordSize; |
| const intptr_t inst_type_args_offset = |
| (kCallerSpSlotFromFp + 2) * compiler::target::kWordSize; |
| const intptr_t fun_type_args_offset = |
| (kCallerSpSlotFromFp + 1) * compiler::target::kWordSize; |
| const intptr_t dst_type_offset = |
| (kCallerSpSlotFromFp + 0) * compiler::target::kWordSize; |
| |
| __ LoadMemoryValue(TypeTestABI::kInstanceReg, FPREG, instance_offset); |
| __ LoadMemoryValue(TypeTestABI::kInstantiatorTypeArgumentsReg, FPREG, |
| inst_type_args_offset); |
| __ LoadMemoryValue(TypeTestABI::kFunctionTypeArgumentsReg, FPREG, |
| fun_type_args_offset); |
| __ LoadMemoryValue(TypeTestABI::kDstTypeReg, FPREG, dst_type_offset); |
| |
| const intptr_t subtype_test_cache_index = __ object_pool_builder().AddObject( |
| Object::null_object(), compiler::ObjectPoolBuilderEntry::kPatchable); |
| const intptr_t dst_name_index = __ object_pool_builder().AddObject( |
| Symbols::OptimizedOut(), compiler::ObjectPoolBuilderEntry::kPatchable); |
| ASSERT_EQUAL(subtype_test_cache_index + 1, dst_name_index); |
| ASSERT(__ constant_pool_allowed()); |
| |
| FlowGraphCompiler::GenerateIndirectTTSCall( |
| assembler, TypeTestABI::kDstTypeReg, subtype_test_cache_index); |
| |
| // We have the guarantee that TTS preserves all input registers, if the TTS |
| // handles the type test successfully. |
| // |
| // Let the test know which TTS abi registers were not preserved. |
| ASSERT(((1 << static_cast<intptr_t>(TypeTestABI::kInstanceReg)) & |
| TypeTestABI::kPreservedAbiRegisters) != 0); |
| // First we check the instance register, freeing it up in case there are no |
| // other safe registers to use since we need two registers: one to accumulate |
| // the register mask, another to load the array address when saving the mask. |
| __ LoadFromOffset(TypeTestABI::kScratchReg, FPREG, instance_offset); |
| compiler::Label instance_matches, done_with_instance; |
| __ CompareRegisters(TypeTestABI::kScratchReg, TypeTestABI::kInstanceReg); |
| __ BranchIf(EQUAL, &instance_matches, compiler::Assembler::kNearJump); |
| __ LoadImmediate(TypeTestABI::kScratchReg, |
| 1 << static_cast<intptr_t>(TypeTestABI::kInstanceReg)); |
| __ Jump(&done_with_instance, compiler::Assembler::kNearJump); |
| __ Bind(&instance_matches); |
| __ LoadImmediate(TypeTestABI::kScratchReg, 0); |
| __ Bind(&done_with_instance); |
| for (intptr_t i = 0; i < kNumberOfCpuRegisters; ++i) { |
| if (((1 << i) & TypeTestABI::kPreservedAbiRegisters) == 0) continue; |
| const Register reg = static_cast<Register>(i); |
| compiler::Label done; |
| switch (reg) { |
| case TypeTestABI::kInstanceReg: |
| // Skip the already handled instance register. |
| continue; |
| case TypeTestABI::kDstTypeReg: |
| __ LoadFromOffset(TypeTestABI::kInstanceReg, FPREG, dst_type_offset); |
| break; |
| case TypeTestABI::kFunctionTypeArgumentsReg: |
| __ LoadFromOffset(TypeTestABI::kInstanceReg, FPREG, |
| fun_type_args_offset); |
| break; |
| case TypeTestABI::kInstantiatorTypeArgumentsReg: |
| __ LoadFromOffset(TypeTestABI::kInstanceReg, FPREG, |
| inst_type_args_offset); |
| break; |
| default: |
| FATAL("Unexpected register %s", RegisterNames::RegisterName(reg)); |
| break; |
| } |
| __ CompareRegisters(reg, TypeTestABI::kInstanceReg); |
| __ BranchIf(EQUAL, &done, compiler::Assembler::kNearJump); |
| __ AddImmediate(TypeTestABI::kScratchReg, 1 << i); |
| __ Bind(&done); |
| } |
| __ SmiTag(TypeTestABI::kScratchReg); |
| __ LoadFromOffset(TypeTestABI::kInstanceReg, FPREG, |
| (kCallerSpSlotFromFp + 5) * compiler::target::kWordSize); |
| __ StoreFieldToOffset(TypeTestABI::kScratchReg, TypeTestABI::kInstanceReg, |
| compiler::target::Array::element_offset(0)); |
| |
| // Let the test know which non-TTS abi registers were not preserved. |
| __ LoadImmediate(TypeTestABI::kScratchReg, 0); |
| for (intptr_t i = 0; i < kNumberOfCpuRegisters; ++i) { |
| if (((1 << i) & kDartAvailableCpuRegs) == 0) continue; |
| if (((1 << i) & TypeTestABI::kAbiRegisters) != 0) continue; |
| const Register reg = static_cast<Register>(i); |
| compiler::Label done; |
| __ CompareImmediate(reg, calculate_breadcrumb(reg)); |
| __ BranchIf(EQUAL, &done, compiler::Assembler::kNearJump); |
| __ AddImmediate(TypeTestABI::kScratchReg, 1 << i); |
| __ Bind(&done); |
| } |
| __ SmiTag(TypeTestABI::kScratchReg); |
| __ LoadFromOffset(TypeTestABI::kInstanceReg, FPREG, |
| (kCallerSpSlotFromFp + 4) * compiler::target::kWordSize); |
| __ StoreFieldToOffset(TypeTestABI::kScratchReg, TypeTestABI::kInstanceReg, |
| compiler::target::Array::element_offset(0)); |
| |
| // Set the return from the stub to be null. |
| __ LoadObject(CallingConventions::kReturnReg, Object::null_object()); |
| __ LeaveDartFrame(); |
| __ Ret(); |
| } |
| |
| #undef __ |
| |
| static void FinalizeAndCanonicalize(AbstractType* type) { |
| *type = ClassFinalizer::FinalizeType(*type); |
| ASSERT(type->IsCanonical()); |
| } |
| |
| static void CanonicalizeTAV(TypeArguments* tav) { |
| *tav = tav->Canonicalize(Thread::Current(), nullptr); |
| } |
| |
| static void RunTTSTest( |
| const Object& instance, |
| const AbstractType& dst_type, |
| const TypeArguments& instantiator_tav, |
| const TypeArguments& function_tav, |
| std::function<void(const Object& result, const SubtypeTestCache& stc)> lazy, |
| std::function<void(const Object& result, |
| const SubtypeTestCache& stc, |
| const Smi& abi_regs_modified, |
| const Smi& rest_regs_modified)> nonlazy) { |
| THR_Print( |
| "TTS_Test(instance=%s, dst_type=%s, instantiator_tav=%s, " |
| "function_tav=%s)\n", |
| instance.ToCString(), dst_type.ToCString(), instantiator_tav.ToCString(), |
| function_tav.ToCString()); |
| ASSERT(instantiator_tav.IsNull() || instantiator_tav.IsCanonical()); |
| ASSERT(function_tav.IsNull() || function_tav.IsCanonical()); |
| auto thread = Thread::Current(); |
| |
| // Build a stub which will do calling conversion to call TTS stubs. |
| const auto& klass = |
| Class::Handle(thread->isolate_group()->class_table()->At(kInstanceCid)); |
| const auto& symbol = String::Handle( |
| Symbols::New(thread, OS::SCreate(thread->zone(), "TTSTest"))); |
| const auto& signature = FunctionType::ZoneHandle(FunctionType::New()); |
| const auto& function = Function::Handle(Function::New( |
| signature, symbol, UntaggedFunction::kRegularFunction, false, false, |
| false, false, false, klass, TokenPosition::kNoSource)); |
| compiler::ObjectPoolBuilder pool_builder; |
| const auto& invoke_tts = Code::Handle( |
| StubCode::Generate("InvokeTTS", &pool_builder, &GenerateInvokeTTSStub)); |
| const auto& pool = |
| ObjectPool::Handle(ObjectPool::NewFromBuilder(pool_builder)); |
| invoke_tts.set_object_pool(pool.ptr()); |
| invoke_tts.set_owner(function); |
| invoke_tts.set_exception_handlers( |
| ExceptionHandlers::Handle(ExceptionHandlers::New(0))); |
| |
| EXPECT_EQ(2, pool.Length()); |
| const intptr_t kSubtypeTestCacheIndex = 0; |
| |
| const auto& arguments_descriptor = |
| Array::Handle(ArgumentsDescriptor::NewBoxed(0, 6)); |
| const auto& arguments = Array::Handle(Array::New(6)); |
| const auto& abi_regs_modified_box = Array::Handle(Array::New(1)); |
| const auto& rest_regs_modified_box = Array::Handle(Array::New(1)); |
| arguments.SetAt(0, abi_regs_modified_box); |
| arguments.SetAt(1, rest_regs_modified_box); |
| arguments.SetAt(2, instance); |
| arguments.SetAt(3, instantiator_tav); |
| arguments.SetAt(4, function_tav); |
| arguments.SetAt(5, dst_type); |
| |
| // Ensure we have a) uninitialized TTS b) no/empty SubtypeTestCache. |
| auto& instantiated_dst_type = AbstractType::Handle(dst_type.ptr()); |
| if (dst_type.IsTypeParameter()) { |
| instantiated_dst_type = TypeParameter::Cast(dst_type).GetFromTypeArguments( |
| instantiator_tav, function_tav); |
| } |
| instantiated_dst_type.SetTypeTestingStub(StubCode::LazySpecializeTypeTest()); |
| EXPECT(instantiated_dst_type.type_test_stub() == |
| StubCode::LazySpecializeTypeTest().ptr()); |
| EXPECT(pool.ObjectAt(kSubtypeTestCacheIndex) == Object::null()); |
| |
| auto& result = Object::Handle(); |
| auto& result2 = Object::Handle(); |
| auto& abi_regs_modified = Smi::Handle(); |
| auto& rest_regs_modified = Smi::Handle(); |
| auto& tts = Code::Handle(); |
| auto& tts2 = Code::Handle(); |
| auto& stc = SubtypeTestCache::Handle(); |
| auto& stc2 = SubtypeTestCache::Handle(); |
| |
| // First invocation will a) specialize the TTS b) may create SubtypeTestCache |
| result = DartEntry::InvokeCode(invoke_tts, invoke_tts.EntryPoint(), |
| arguments_descriptor, arguments, thread); |
| stc ^= pool.ObjectAt(kSubtypeTestCacheIndex); |
| tts = instantiated_dst_type.type_test_stub(); |
| if (!result.IsError()) { |
| EXPECT(tts.ptr() != StubCode::LazySpecializeTypeTest().ptr()); |
| } |
| lazy(result, stc); |
| |
| // Second invocation will a) keep TTS b) keep optional SubtypeTestCache |
| result2 = DartEntry::InvokeCode(invoke_tts, invoke_tts.EntryPoint(), |
| arguments_descriptor, arguments, thread); |
| stc2 ^= pool.ObjectAt(kSubtypeTestCacheIndex); |
| tts2 = instantiated_dst_type.type_test_stub(); |
| abi_regs_modified ^= abi_regs_modified_box.At(0); |
| rest_regs_modified ^= rest_regs_modified_box.At(0); |
| EXPECT(result2.IsError() || !abi_regs_modified.IsNull()); |
| EXPECT(tts2.ptr() == tts.ptr()); |
| EXPECT(stc2.ptr() == stc.ptr()); |
| nonlazy(result2, stc2, abi_regs_modified, rest_regs_modified); |
| |
| // Third invocation will a) explicitly install TTS beforehand b) keep optional |
| // SubtypeTestCache |
| // (This is to simulate AOT where we don't use lazy specialization but |
| // precompile the TTS) |
| TypeTestingStubGenerator::SpecializeStubFor(thread, instantiated_dst_type); |
| tts = instantiated_dst_type.type_test_stub(); |
| |
| result2 = DartEntry::InvokeCode(invoke_tts, invoke_tts.EntryPoint(), |
| arguments_descriptor, arguments, thread); |
| stc2 ^= pool.ObjectAt(kSubtypeTestCacheIndex); |
| tts2 = instantiated_dst_type.type_test_stub(); |
| abi_regs_modified ^= abi_regs_modified_box.At(0); |
| rest_regs_modified ^= rest_regs_modified_box.At(0); |
| EXPECT(result2.IsError() || !abi_regs_modified.IsNull()); |
| EXPECT(tts2.ptr() == tts.ptr()); |
| EXPECT(stc2.ptr() == stc.ptr()); |
| nonlazy(result2, stc2, abi_regs_modified, rest_regs_modified); |
| } |
| |
| static void ReportModifiedRegisters(const Smi& modified_registers) { |
| const intptr_t reg_mask = Smi::Cast(modified_registers).Value(); |
| for (intptr_t i = 0; i < kNumberOfCpuRegisters; i++) { |
| if (((1 << i) & reg_mask) != 0) { |
| const Register reg = static_cast<Register>(i); |
| dart::Expect(__FILE__, __LINE__) |
| .Fail("%s was modified", RegisterNames::RegisterName(reg)); |
| } |
| } |
| } |
| |
| static void CommonTTSHandledChecks(const Object& result, |
| const SubtypeTestCache& stc) { |
| // Ensure the type test succeeded. |
| EXPECT(result.IsNull()); |
| // Ensure we didn't fall back to the subtype test cache. |
| EXPECT(stc.IsNull()); |
| } |
| |
| static void ExpectLazilyHandledViaTTS(const Object& result, |
| const SubtypeTestCache& stc) { |
| THR_Print("Testing lazy handled via TTS\n"); |
| CommonTTSHandledChecks(result, stc); |
| } |
| |
| static void ExpectHandledViaTTS(const Object& result, |
| const SubtypeTestCache& stc, |
| const Smi& abi_regs_modified, |
| const Smi& rest_regs_modified) { |
| THR_Print("Testing non-lazy handled via TTS\n"); |
| CommonTTSHandledChecks(result, stc); |
| // Ensure the TTS abi registers were preserved. |
| ReportModifiedRegisters(abi_regs_modified); |
| // Ensure the non-TTS abi registers were preserved. |
| ReportModifiedRegisters(rest_regs_modified); |
| } |
| |
| static void CommonSTCHandledChecks(const Object& result, |
| const SubtypeTestCache& stc) { |
| // Ensure the type test succeeded. |
| EXPECT(result.IsNull()); |
| // Ensure we did fall back to the subtype test cache. |
| EXPECT(!stc.IsNull()); |
| // Ensure the test is marked as succeeding in the STC. |
| EXPECT_EQ(1, stc.NumberOfChecks()); |
| SubtypeTestCacheTable entries(Array::Handle(stc.cache())); |
| EXPECT(entries[0].Get<SubtypeTestCache::kTestResult>() == |
| Object::bool_true().ptr()); |
| } |
| |
| static void ExpectLazilyHandledViaSTC(const Object& result, |
| const SubtypeTestCache& stc) { |
| THR_Print("Testing lazy handled via STC\n"); |
| CommonSTCHandledChecks(result, stc); |
| } |
| |
| static void ExpectHandledViaSTC(const Object& result, |
| const SubtypeTestCache& stc, |
| const Smi& abi_regs_modified, |
| const Smi& rest_regs_modified) { |
| THR_Print("Testing non-lazy handled via STC\n"); |
| CommonSTCHandledChecks(result, stc); |
| // Ensure the TTS/STC abi registers were preserved. |
| ReportModifiedRegisters(abi_regs_modified); |
| // Ensure the non-TTS abi registers were preserved. |
| ReportModifiedRegisters(rest_regs_modified); |
| } |
| |
| static void CommonTTSFailureChecks(const Object& result, |
| const SubtypeTestCache& stc) { |
| // Ensure we have not updated STC (which we shouldn't do in case the type test |
| // fails, i.e. an exception is thrown). |
| EXPECT(stc.IsNull()); |
| // Ensure we get a proper exception for the type test. |
| EXPECT(result.IsUnhandledException()); |
| const auto& error = |
| Instance::Handle(UnhandledException::Cast(result).exception()); |
| EXPECT(strstr(error.ToCString(), "_TypeError")); |
| } |
| |
| static void ExpectLazilyFailedViaTTS(const Object& result, |
| const SubtypeTestCache& stc) { |
| THR_Print("Testing lazy failure via TTS\n"); |
| CommonTTSFailureChecks(result, stc); |
| } |
| |
| static void ExpectFailedViaTTS(const Object& result, |
| const SubtypeTestCache& stc, |
| const Smi& abi_regs_modified, |
| const Smi& rest_regs_modified) { |
| THR_Print("Testing nonlazy failure via TTS\n"); |
| CommonTTSFailureChecks(result, stc); |
| // Registers only need to be preserved on success. |
| } |
| |
| static void CommonSTCFailureChecks(const Object& result, |
| const SubtypeTestCache& stc) { |
| // Ensure we have not updated STC (which we shouldn't do in case the type test |
| // fails, i.e. an exception is thrown). |
| EXPECT(stc.IsNull()); |
| // Ensure we get a proper exception for the type test. |
| EXPECT(result.IsUnhandledException()); |
| const auto& error = |
| Instance::Handle(UnhandledException::Cast(result).exception()); |
| EXPECT(strstr(error.ToCString(), "_TypeError")); |
| } |
| |
| static void ExpectLazilyFailedViaSTC(const Object& result, |
| const SubtypeTestCache& stc) { |
| THR_Print("Testing lazy failure via STC\n"); |
| CommonSTCFailureChecks(result, stc); |
| } |
| |
| static void ExpectFailedViaSTC(const Object& result, |
| const SubtypeTestCache& stc, |
| const Smi& abi_regs_modified, |
| const Smi& rest_regs_modified) { |
| THR_Print("Testing non-lazy failure via STC\n"); |
| CommonSTCFailureChecks(result, stc); |
| // Registers only need to be preserved on success. |
| } |
| |
| const char* kSubtypeRangeCheckScript = |
| R"( |
| class I<T, U> {} |
| class I2 {} |
| |
| class Base<T> {} |
| |
| class A extends Base<int> {} |
| class A1 extends A implements I2 {} |
| class A2<T> extends A implements I<int, T> {} |
| |
| class B extends Base<String> {} |
| class B1 extends B implements I2 {} |
| class B2<T> extends B implements I<T, String> {} |
| |
| genericFun<A, B>() {} |
| |
| createI() => I<int, String>(); |
| createI2() => I2(); |
| createBaseInt() => Base<int>(); |
| createBaseNull() => Base<Null>(); |
| createBaseNever() => Base<Never>(); |
| createA() => A(); |
| createA1() => A1(); |
| createA2() => A2<int>(); |
| createB() => B(); |
| createB1() => B1(); |
| createB2() => B2<int>(); |
| createBaseIStringDouble() => Base<I<String, double>>(); |
| createBaseA2Int() => Base<A2<int>>(); |
| createBaseA2A1() => Base<A2<A1>>(); |
| createBaseB2Int() => Base<B2<int>>(); |
| )"; |
| |
| ISOLATE_UNIT_TEST_CASE(TTS_SubtypeRangeCheck) { |
| const auto& root_library = |
| Library::Handle(LoadTestScript(kSubtypeRangeCheckScript)); |
| const auto& class_a = Class::Handle(GetClass(root_library, "A")); |
| const auto& class_base = Class::Handle(GetClass(root_library, "Base")); |
| const auto& class_i = Class::Handle(GetClass(root_library, "I")); |
| const auto& class_i2 = Class::Handle(GetClass(root_library, "I2")); |
| |
| const auto& obj_i = Object::Handle(Invoke(root_library, "createI")); |
| const auto& obj_i2 = Object::Handle(Invoke(root_library, "createI2")); |
| const auto& obj_base_int = |
| Object::Handle(Invoke(root_library, "createBaseInt")); |
| const auto& obj_base_null = |
| Object::Handle(Invoke(root_library, "createBaseNull")); |
| const auto& obj_base_never = |
| Object::Handle(Invoke(root_library, "createBaseNever")); |
| const auto& obj_a = Object::Handle(Invoke(root_library, "createA")); |
| const auto& obj_a1 = Object::Handle(Invoke(root_library, "createA1")); |
| const auto& obj_a2 = Object::Handle(Invoke(root_library, "createA2")); |
| const auto& obj_b = Object::Handle(Invoke(root_library, "createB")); |
| const auto& obj_b1 = Object::Handle(Invoke(root_library, "createB1")); |
| const auto& obj_b2 = Object::Handle(Invoke(root_library, "createB2")); |
| |
| const auto& type_dynamic = Type::Handle(Type::DynamicType()); |
| auto& type_object = Type::Handle(Type::ObjectType()); |
| type_object = type_object.ToNullability(Nullability::kNullable, Heap::kNew); |
| |
| const auto& tav_null = TypeArguments::Handle(TypeArguments::null()); |
| |
| auto& tav_object = TypeArguments::Handle(TypeArguments::New(1)); |
| tav_object.SetTypeAt(0, type_object); |
| CanonicalizeTAV(&tav_object); |
| |
| auto& tav_object_dynamic = TypeArguments::Handle(TypeArguments::New(2)); |
| tav_object_dynamic.SetTypeAt(0, type_object); |
| tav_object_dynamic.SetTypeAt(1, type_dynamic); |
| CanonicalizeTAV(&tav_object_dynamic); |
| |
| auto& tav_dynamic_t = TypeArguments::Handle(TypeArguments::New(2)); |
| tav_dynamic_t.SetTypeAt(0, type_dynamic); |
| tav_dynamic_t.SetTypeAt( |
| 1, TypeParameter::Handle(GetClassTypeParameter(class_base, 0))); |
| CanonicalizeTAV(&tav_dynamic_t); |
| |
| // We will generate specialized TTS for instantiated interface types |
| // where there are no type arguments or the type arguments are top |
| // types. |
| // |
| // obj as A // Subclass ranges |
| // obj as Base<Object?> // Subclass ranges with top-type tav |
| // obj as I2 // Subtype ranges |
| // obj as I<Object?, dynamic> // Subtype ranges with top-type tav |
| // |
| |
| // <...> as A |
| const auto& type_a = AbstractType::Handle(class_a.RareType()); |
| RunTTSTest(obj_i, type_a, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
| ExpectFailedViaTTS); |
| RunTTSTest(obj_i2, type_a, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
| ExpectFailedViaTTS); |
| RunTTSTest(obj_base_int, type_a, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
| ExpectFailedViaTTS); |
| RunTTSTest(obj_a, type_a, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
| ExpectHandledViaTTS); |
| RunTTSTest(obj_a1, type_a, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
| ExpectHandledViaTTS); |
| RunTTSTest(obj_a2, type_a, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
| ExpectHandledViaTTS); |
| RunTTSTest(obj_b, type_a, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
| ExpectFailedViaTTS); |
| RunTTSTest(obj_b1, type_a, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
| ExpectFailedViaTTS); |
| RunTTSTest(obj_b2, type_a, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
| ExpectFailedViaTTS); |
| |
| // <...> as Base<Object?> |
| auto& type_base = AbstractType::Handle(Type::New(class_base, tav_object)); |
| FinalizeAndCanonicalize(&type_base); |
| RunTTSTest(obj_i, type_base, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
| ExpectFailedViaTTS); |
| RunTTSTest(obj_i2, type_base, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
| ExpectFailedViaTTS); |
| RunTTSTest(obj_base_int, type_base, tav_null, tav_null, |
| ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
| RunTTSTest(obj_base_null, type_base, tav_null, tav_null, |
| ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
| RunTTSTest(obj_a, type_base, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
| ExpectHandledViaTTS); |
| RunTTSTest(obj_a1, type_base, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
| ExpectHandledViaTTS); |
| RunTTSTest(obj_a2, type_base, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
| ExpectHandledViaTTS); |
| RunTTSTest(obj_b, type_base, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
| ExpectHandledViaTTS); |
| RunTTSTest(obj_b1, type_base, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
| ExpectHandledViaTTS); |
| RunTTSTest(obj_b2, type_base, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
| ExpectHandledViaTTS); |
| |
| // Base<Null|Never> as Base<int?> |
| // This is a regression test verifying that we don't fall through into |
| // runtime for Null and Never. |
| auto& type_nullable_int = Type::Handle(Type::IntType()); |
| type_nullable_int = type_nullable_int.ToNullability( |
| TestCase::IsNNBD() ? Nullability::kNullable : Nullability::kLegacy, |
| Heap::kNew); |
| auto& tav_nullable_int = TypeArguments::Handle(TypeArguments::New(1)); |
| tav_nullable_int.SetTypeAt(0, type_nullable_int); |
| CanonicalizeTAV(&tav_nullable_int); |
| auto& type_base_nullable_int = |
| AbstractType::Handle(Type::New(class_base, tav_nullable_int)); |
| FinalizeAndCanonicalize(&type_base_nullable_int); |
| RunTTSTest(obj_base_null, type_base_nullable_int, tav_null, tav_null, |
| ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
| RunTTSTest(obj_base_never, type_base_nullable_int, tav_null, tav_null, |
| ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
| |
| if (TestCase::IsNNBD()) { |
| // Base<Null|Never> as Base<int> |
| auto& type_int = Type::Handle(Type::IntType()); |
| type_int = type_int.ToNullability(Nullability::kNonNullable, Heap::kNew); |
| auto& tav_int = TypeArguments::Handle(TypeArguments::New(1)); |
| tav_int.SetTypeAt(0, type_int); |
| CanonicalizeTAV(&tav_int); |
| auto& type_base_int = Type::Handle(Type::New(class_base, tav_int)); |
| type_base_int = |
| type_base_int.ToNullability(Nullability::kNonNullable, Heap::kNew); |
| FinalizeAndCanonicalize(&type_base_int); |
| if (IsolateGroup::Current()->null_safety()) { |
| RunTTSTest(obj_base_null, type_base_int, tav_null, tav_null, |
| ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| } |
| RunTTSTest(obj_base_never, type_base_int, tav_null, tav_null, |
| ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
| } |
| |
| // <...> as I2 |
| const auto& type_i2 = AbstractType::Handle(class_i2.RareType()); |
| RunTTSTest(obj_i, type_i2, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
| ExpectFailedViaTTS); |
| RunTTSTest(obj_i2, type_i2, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
| ExpectHandledViaTTS); |
| RunTTSTest(obj_base_int, type_i2, tav_null, tav_null, |
| ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| RunTTSTest(obj_a, type_i2, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
| ExpectFailedViaTTS); |
| RunTTSTest(obj_a1, type_i2, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
| ExpectHandledViaTTS); |
| RunTTSTest(obj_a2, type_i2, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
| ExpectFailedViaTTS); |
| RunTTSTest(obj_b, type_i2, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
| ExpectFailedViaTTS); |
| RunTTSTest(obj_b1, type_i2, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
| ExpectHandledViaTTS); |
| RunTTSTest(obj_b2, type_i2, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
| ExpectFailedViaTTS); |
| |
| // <...> as I<Object, dynamic> |
| auto& type_i_object_dynamic = |
| AbstractType::Handle(Type::New(class_i, tav_object_dynamic)); |
| FinalizeAndCanonicalize(&type_i_object_dynamic); |
| RunTTSTest(obj_i, type_i_object_dynamic, tav_null, tav_null, |
| ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
| RunTTSTest(obj_i2, type_i_object_dynamic, tav_null, tav_null, |
| ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| RunTTSTest(obj_base_int, type_i_object_dynamic, tav_null, tav_null, |
| ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| RunTTSTest(obj_a, type_i_object_dynamic, tav_null, tav_null, |
| ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| RunTTSTest(obj_a1, type_i_object_dynamic, tav_null, tav_null, |
| ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| RunTTSTest(obj_a2, type_i_object_dynamic, tav_null, tav_null, |
| ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
| RunTTSTest(obj_b, type_i_object_dynamic, tav_null, tav_null, |
| ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| RunTTSTest(obj_b1, type_i_object_dynamic, tav_null, tav_null, |
| ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| RunTTSTest(obj_b2, type_i_object_dynamic, tav_null, tav_null, |
| ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
| |
| // We do not generate TTS for uninstantiated types if we would need to use |
| // subtype range checks for the class of the interface type. |
| // |
| // obj as I<dynamic, T> |
| // |
| auto& type_dynamic_t = |
| AbstractType::Handle(Type::New(class_i, tav_dynamic_t)); |
| FinalizeAndCanonicalize(&type_dynamic_t); |
| RunTTSTest(obj_i, type_dynamic_t, tav_object, tav_null, |
| ExpectLazilyHandledViaSTC, ExpectHandledViaSTC); |
| RunTTSTest(obj_i2, type_dynamic_t, tav_object, tav_null, |
| ExpectLazilyFailedViaSTC, ExpectFailedViaSTC); |
| RunTTSTest(obj_base_int, type_dynamic_t, tav_object, tav_null, |
| ExpectLazilyFailedViaSTC, ExpectFailedViaSTC); |
| RunTTSTest(obj_a, type_dynamic_t, tav_object, tav_null, |
| ExpectLazilyFailedViaSTC, ExpectFailedViaSTC); |
| RunTTSTest(obj_a1, type_dynamic_t, tav_object, tav_null, |
| ExpectLazilyFailedViaSTC, ExpectFailedViaSTC); |
| RunTTSTest(obj_a2, type_dynamic_t, tav_object, tav_null, |
| ExpectLazilyHandledViaSTC, ExpectHandledViaSTC); |
| RunTTSTest(obj_b, type_dynamic_t, tav_object, tav_null, |
| ExpectLazilyFailedViaSTC, ExpectFailedViaSTC); |
| RunTTSTest(obj_b1, type_dynamic_t, tav_object, tav_null, |
| ExpectLazilyFailedViaSTC, ExpectFailedViaSTC); |
| RunTTSTest(obj_b2, type_dynamic_t, tav_object, tav_null, |
| ExpectLazilyHandledViaSTC, ExpectHandledViaSTC); |
| |
| // obj as Object (with null safety) |
| auto isolate_group = IsolateGroup::Current(); |
| if (isolate_group->null_safety()) { |
| auto& type_non_nullable_object = |
| Type::Handle(isolate_group->object_store()->non_nullable_object_type()); |
| RunTTSTest(obj_a, type_non_nullable_object, tav_null, tav_null, |
| ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
| RunTTSTest(Object::null_object(), type_non_nullable_object, tav_null, |
| tav_null, ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| } |
| } |
| |
| ISOLATE_UNIT_TEST_CASE(TTS_GenericSubtypeRangeCheck) { |
| const auto& root_library = |
| Library::Handle(LoadTestScript(kSubtypeRangeCheckScript)); |
| const auto& class_a1 = Class::Handle(GetClass(root_library, "A1")); |
| const auto& class_a2 = Class::Handle(GetClass(root_library, "A2")); |
| const auto& class_base = Class::Handle(GetClass(root_library, "Base")); |
| const auto& class_i = Class::Handle(GetClass(root_library, "I")); |
| const auto& fun_generic = |
| Function::Handle(GetFunction(root_library, "genericFun")); |
| |
| const auto& obj_i = Object::Handle(Invoke(root_library, "createI")); |
| const auto& obj_i2 = Object::Handle(Invoke(root_library, "createI2")); |
| const auto& obj_base_int = |
| Object::Handle(Invoke(root_library, "createBaseInt")); |
| const auto& obj_a = Object::Handle(Invoke(root_library, "createA")); |
| const auto& obj_a1 = Object::Handle(Invoke(root_library, "createA1")); |
| const auto& obj_a2 = Object::Handle(Invoke(root_library, "createA2")); |
| const auto& obj_b = Object::Handle(Invoke(root_library, "createB")); |
| const auto& obj_b1 = Object::Handle(Invoke(root_library, "createB1")); |
| const auto& obj_b2 = Object::Handle(Invoke(root_library, "createB2")); |
| const auto& obj_basea2int = |
| Object::Handle(Invoke(root_library, "createBaseA2Int")); |
| const auto& obj_basea2a1 = |
| Object::Handle(Invoke(root_library, "createBaseA2A1")); |
| const auto& obj_baseb2int = |
| Object::Handle(Invoke(root_library, "createBaseB2Int")); |
| const auto& obj_baseistringdouble = |
| Object::Handle(Invoke(root_library, "createBaseIStringDouble")); |
| |
| const auto& type_dynamic = Type::Handle(Type::DynamicType()); |
| auto& type_int = Type::Handle(Type::IntType()); |
| if (!TestCase::IsNNBD()) { |
| type_int = type_int.ToNullability(Nullability::kLegacy, Heap::kNew); |
| } |
| auto& type_string = Type::Handle(Type::StringType()); |
| if (!TestCase::IsNNBD()) { |
| type_string = type_string.ToNullability(Nullability::kLegacy, Heap::kNew); |
| } |
| auto& type_object = Type::Handle(Type::ObjectType()); |
| type_object = type_object.ToNullability( |
| TestCase::IsNNBD() ? Nullability::kNullable : Nullability::kLegacy, |
| Heap::kNew); |
| auto& type_a1 = Type::Handle(class_a1.DeclarationType()); |
| if (!TestCase::IsNNBD()) { |
| type_a1 = type_a1.ToNullability(Nullability::kLegacy, Heap::kNew); |
| } |
| FinalizeAndCanonicalize(&type_a1); |
| |
| const auto& tav_null = TypeArguments::Handle(TypeArguments::null()); |
| |
| auto& tav_object_dynamic = TypeArguments::Handle(TypeArguments::New(2)); |
| tav_object_dynamic.SetTypeAt(0, type_object); |
| tav_object_dynamic.SetTypeAt(1, type_dynamic); |
| CanonicalizeTAV(&tav_object_dynamic); |
| |
| auto& tav_dynamic_int = TypeArguments::Handle(TypeArguments::New(2)); |
| tav_dynamic_int.SetTypeAt(0, type_dynamic); |
| tav_dynamic_int.SetTypeAt(1, type_int); |
| CanonicalizeTAV(&tav_dynamic_int); |
| |
| auto& tav_dynamic_string = TypeArguments::Handle(TypeArguments::New(2)); |
| tav_dynamic_string.SetTypeAt(0, type_dynamic); |
| tav_dynamic_string.SetTypeAt(1, type_string); |
| CanonicalizeTAV(&tav_dynamic_string); |
| |
| auto& tav_int = TypeArguments::Handle(TypeArguments::New(1)); |
| tav_int.SetTypeAt(0, type_int); |
| CanonicalizeTAV(&tav_int); |
| |
| auto& type_i_object_dynamic = |
| AbstractType::Handle(Type::New(class_i, tav_object_dynamic)); |
| FinalizeAndCanonicalize(&type_i_object_dynamic); |
| const auto& tav_iod = TypeArguments::Handle(TypeArguments::New(1)); |
| tav_iod.SetTypeAt(0, type_i_object_dynamic); |
| |
| // We will generate specialized TTS for instantiated interface types |
| // where there are no type arguments or the type arguments are top |
| // types. |
| // |
| // obj as Base<I<Object, dynamic>> // Subclass ranges for Base, subtype |
| // // ranges tav arguments. |
| // obj as Base<T> // Subclass ranges for Base, type |
| // // equality for instantiator type arg T |
| // obj as Base<B> // Subclass ranges for Base, type |
| // // equality for function type arg B. |
| // |
| |
| // <...> as Base<I<Object, dynamic>> |
| auto& type_base_i_object_dynamic = |
| AbstractType::Handle(Type::New(class_base, tav_iod)); |
| FinalizeAndCanonicalize(&type_base_i_object_dynamic); |
| RunTTSTest(obj_baseb2int, type_base_i_object_dynamic, tav_null, tav_null, |
| ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
| RunTTSTest(obj_baseistringdouble, type_base_i_object_dynamic, tav_null, |
| tav_null, ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
| RunTTSTest(obj_a, type_base_i_object_dynamic, tav_null, tav_null, |
| ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| RunTTSTest(obj_a1, type_base_i_object_dynamic, tav_null, tav_null, |
| ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| RunTTSTest(obj_a2, type_base_i_object_dynamic, tav_null, tav_null, |
| ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| RunTTSTest(obj_b, type_base_i_object_dynamic, tav_null, tav_null, |
| ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| RunTTSTest(obj_b1, type_base_i_object_dynamic, tav_null, tav_null, |
| ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| RunTTSTest(obj_b2, type_base_i_object_dynamic, tav_null, tav_null, |
| ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| |
| // <...> as Base<T> with T instantiantiator type parameter (T == int) |
| const auto& tav_baset = TypeArguments::Handle(TypeArguments::New(1)); |
| tav_baset.SetTypeAt( |
| 0, TypeParameter::Handle(GetClassTypeParameter(class_base, 0))); |
| auto& type_base_t = AbstractType::Handle(Type::New(class_base, tav_baset)); |
| FinalizeAndCanonicalize(&type_base_t); |
| RunTTSTest(obj_base_int, type_base_t, tav_int, tav_null, |
| ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
| RunTTSTest(obj_baseistringdouble, type_base_t, tav_int, tav_null, |
| ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| |
| // <...> as Base<B> with B function type parameter |
| const auto& tav_baseb = TypeArguments::Handle(TypeArguments::New(1)); |
| tav_baseb.SetTypeAt( |
| 0, TypeParameter::Handle(GetFunctionTypeParameter(fun_generic, 1))); |
| auto& type_base_b = AbstractType::Handle(Type::New(class_base, tav_baseb)); |
| FinalizeAndCanonicalize(&type_base_b); |
| // With B == int |
| RunTTSTest(obj_base_int, type_base_b, tav_null, tav_dynamic_int, |
| ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
| RunTTSTest(obj_baseistringdouble, type_base_b, tav_null, tav_dynamic_int, |
| ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| // With B == dynamic (null vector) |
| RunTTSTest(obj_base_int, type_base_b, tav_null, tav_null, |
| ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
| RunTTSTest(obj_i2, type_base_b, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
| ExpectFailedViaTTS); |
| |
| // We do not generate TTS for uninstantiated types if we would need to use |
| // subtype range checks for the class of the interface type. |
| // |
| // obj as I<dynamic, String> // I is generic & implemented. |
| // obj as Base<A2<T>> // A2<T> is not instantiated. |
| // obj as Base<A2<A1>> // A2<A1> is not a rare type. |
| // |
| |
| // <...> as I<dynamic, String> |
| RELEASE_ASSERT(class_i.is_implemented()); |
| auto& type_i_dynamic_string = |
| Type::Handle(Type::New(class_i, tav_dynamic_string)); |
| type_i_dynamic_string = type_i_dynamic_string.ToNullability( |
| Nullability::kNonNullable, Heap::kNew); |
| FinalizeAndCanonicalize(&type_i_dynamic_string); |
| RunTTSTest(obj_i, type_i_dynamic_string, tav_null, tav_null, |
| ExpectLazilyHandledViaSTC, ExpectHandledViaSTC); |
| RunTTSTest(obj_base_int, type_i_dynamic_string, tav_null, tav_null, |
| ExpectLazilyFailedViaSTC, ExpectFailedViaSTC); |
| |
| // <...> as Base<A2<T>> |
| const auto& tav_t = TypeArguments::Handle(TypeArguments::New(1)); |
| tav_t.SetTypeAt(0, |
| TypeParameter::Handle(GetClassTypeParameter(class_base, 0))); |
| auto& type_a2_t = Type::Handle(Type::New(class_a2, tav_t)); |
| type_a2_t = type_a2_t.ToNullability(Nullability::kLegacy, Heap::kNew); |
| FinalizeAndCanonicalize(&type_a2_t); |
| const auto& tav_a2_t = TypeArguments::Handle(TypeArguments::New(1)); |
| tav_a2_t.SetTypeAt(0, type_a2_t); |
| auto& type_base_a2_t = Type::Handle(Type::New(class_base, tav_a2_t)); |
| type_base_a2_t = |
| type_base_a2_t.ToNullability(Nullability::kNonNullable, Heap::kNew); |
| FinalizeAndCanonicalize(&type_base_a2_t); |
| RunTTSTest(obj_basea2int, type_base_a2_t, tav_null, tav_null, |
| ExpectLazilyHandledViaSTC, ExpectHandledViaSTC); |
| RunTTSTest(obj_base_int, type_base_a2_t, tav_null, tav_null, |
| ExpectLazilyFailedViaSTC, ExpectFailedViaSTC); |
| |
| // <...> as Base<A2<A1>> |
| const auto& tav_a1 = TypeArguments::Handle(TypeArguments::New(1)); |
| tav_a1.SetTypeAt(0, type_a1); |
| auto& type_a2_a1 = Type::Handle(Type::New(class_a2, tav_a1)); |
| type_a2_a1 = type_a2_a1.ToNullability(Nullability::kLegacy, Heap::kNew); |
| FinalizeAndCanonicalize(&type_a2_a1); |
| const auto& tav_a2_a1 = TypeArguments::Handle(TypeArguments::New(1)); |
| tav_a2_a1.SetTypeAt(0, type_a2_a1); |
| auto& type_base_a2_a1 = Type::Handle(Type::New(class_base, tav_a2_a1)); |
| type_base_a2_a1 = |
| type_base_a2_a1.ToNullability(Nullability::kNonNullable, Heap::kNew); |
| FinalizeAndCanonicalize(&type_base_a2_a1); |
| RunTTSTest(obj_basea2a1, type_base_a2_a1, tav_null, tav_null, |
| ExpectLazilyHandledViaSTC, ExpectHandledViaSTC); |
| RunTTSTest(obj_basea2int, type_base_a2_a1, tav_null, tav_null, |
| ExpectLazilyFailedViaSTC, ExpectFailedViaSTC); |
| } |
| |
| ISOLATE_UNIT_TEST_CASE(TTS_Regress40964) { |
| const char* kScript = |
| R"( |
| class A<T> { |
| test(x) => x as B<T>; |
| } |
| class B<T> {} |
| class C<T> {} |
| |
| createACint() => A<C<int>>(); |
| createBCint() => B<C<int>>(); |
| createBCnum() => B<C<num>>(); |
| )"; |
| |
| const auto& root_library = Library::Handle(LoadTestScript(kScript)); |
| const auto& class_b = Class::Handle(GetClass(root_library, "B")); |
| |
| const auto& acint = Object::Handle(Invoke(root_library, "createACint")); |
| const auto& bcint = Object::Handle(Invoke(root_library, "createBCint")); |
| const auto& bcnum = Object::Handle(Invoke(root_library, "createBCnum")); |
| |
| // dst_type = B<T> |
| const auto& dst_tav = TypeArguments::Handle(TypeArguments::New(1)); |
| dst_tav.SetTypeAt(0, |
| TypeParameter::Handle(GetClassTypeParameter(class_b, 0))); |
| auto& dst_type = Type::Handle(Type::New(class_b, dst_tav)); |
| FinalizeAndCanonicalize(&dst_type); |
| const auto& cint_tav = |
| TypeArguments::Handle(Instance::Cast(acint).GetTypeArguments()); |
| const auto& function_tav = TypeArguments::Handle(); |
| |
| // a as B<T> -- a==B<C<int>, T==<C<int>> |
| RunTTSTest(bcint, dst_type, cint_tav, function_tav, ExpectLazilyHandledViaTTS, |
| ExpectHandledViaTTS); |
| |
| // a as B<T> -- a==B<C<num>, T==<C<int>> |
| RunTTSTest(bcnum, dst_type, cint_tav, function_tav, ExpectLazilyFailedViaTTS, |
| ExpectFailedViaTTS); |
| } |
| |
| ISOLATE_UNIT_TEST_CASE(TTS_TypeParameter) { |
| const char* kScript = |
| R"( |
| class A<T> { |
| T test(dynamic x) => x as T; |
| } |
| H genericFun<H>(dynamic x) => x as H; |
| |
| createAInt() => A<int>(); |
| createAString() => A<String>(); |
| )"; |
| |
| const auto& root_library = Library::Handle(LoadTestScript(kScript)); |
| const auto& class_a = Class::Handle(GetClass(root_library, "A")); |
| ClassFinalizer::FinalizeTypesInClass(class_a); |
| |
| const auto& fun_generic = |
| Function::Handle(GetFunction(root_library, "genericFun")); |
| |
| const auto& dst_type_t = |
| TypeParameter::Handle(GetClassTypeParameter(class_a, 0)); |
| |
| const auto& dst_type_h = |
| TypeParameter::Handle(GetFunctionTypeParameter(fun_generic, 0)); |
| |
| const auto& aint = Object::Handle(Invoke(root_library, "createAInt")); |
| const auto& astring = Object::Handle(Invoke(root_library, "createAString")); |
| |
| const auto& int_tav = |
| TypeArguments::Handle(Instance::Cast(aint).GetTypeArguments()); |
| const auto& string_tav = |
| TypeArguments::Handle(Instance::Cast(astring).GetTypeArguments()); |
| |
| const auto& int_instance = Integer::Handle(Integer::New(1)); |
| const auto& string_instance = String::Handle(String::New("foo")); |
| |
| THR_Print("Testing int instance, class parameter instantiated to int\n"); |
| RunTTSTest(int_instance, dst_type_t, int_tav, string_tav, |
| ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
| THR_Print("\nTesting string instance, class parameter instantiated to int\n"); |
| RunTTSTest(string_instance, dst_type_t, int_tav, string_tav, |
| ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| |
| THR_Print( |
| "\nTesting string instance, function parameter instantiated to string\n"); |
| RunTTSTest(string_instance, dst_type_h, int_tav, string_tav, |
| ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
| RunTTSTest(int_instance, dst_type_h, int_tav, string_tav, |
| ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| } |
| |
| // Check that we generate correct TTS for _Smi type. |
| ISOLATE_UNIT_TEST_CASE(TTS_Smi) { |
| const auto& root_library = Library::Handle(Library::CoreLibrary()); |
| const auto& smi_class = Class::Handle(GetClass(root_library, "_Smi")); |
| ClassFinalizer::FinalizeTypesInClass(smi_class); |
| |
| const auto& dst_type = AbstractType::Handle(smi_class.RareType()); |
| const auto& tav_null = TypeArguments::Handle(TypeArguments::null()); |
| |
| THR_Print("\nTesting that instance of _Smi is a subtype of _Smi\n"); |
| RunTTSTest(Smi::Handle(Smi::New(0)), dst_type, tav_null, tav_null, |
| ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
| } |
| |
| } // namespace dart |
| |
| #endif // defined(TARGET_ARCH_ARM64) || defined(TARGET_ARCH_ARM) || \ |
| // defined(TARGET_ARCH_X64) |