// 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 "vm/compiler/runtime_api.h" #include "vm/flags.h" #include "vm/globals.h" // For `StubCodeCompiler::GenerateAllocateUnhandledExceptionStub` #include "vm/compiler/backend/il.h" #define SHOULD_NOT_INCLUDE_RUNTIME #include "vm/compiler/stub_code_compiler.h" #include "vm/code_descriptors.h" #include "vm/compiler/api/type_check_mode.h" #include "vm/compiler/assembler/assembler.h" #include "vm/compiler/backend/locations.h" #include "vm/stack_frame.h" #define __ assembler-> namespace dart { namespace compiler { intptr_t StubCodeCompiler::WordOffsetFromFpToCpuRegister( Register cpu_register) { ASSERT(RegisterSet::Contains(kDartAvailableCpuRegs, cpu_register)); intptr_t slots_from_fp = target::frame_layout.param_end_from_fp + 1; for (intptr_t i = 0; i < kNumberOfCpuRegisters; i++) { Register reg = static_cast(i); if (reg == cpu_register) break; if (RegisterSet::Contains(kDartAvailableCpuRegs, reg)) { slots_from_fp++; } } return slots_from_fp; } void StubCodeCompiler::GenerateInitStaticFieldStub() { __ EnterStubFrame(); __ PushObject(NullObject()); // Make room for result. __ PushRegister(InitStaticFieldABI::kFieldReg); __ CallRuntime(kInitStaticFieldRuntimeEntry, /*argument_count=*/1); __ Drop(1); __ PopRegister(InitStaticFieldABI::kResultReg); __ LeaveStubFrame(); __ Ret(); } void StubCodeCompiler::GenerateCheckIsolateFieldAccessStub() { const Register kFieldReg = InitStaticFieldABI::kFieldReg; const Register kScratchReg = InitLateStaticFieldInternalRegs::kScratchReg; __ EnterStubFrame(); Label throw_since_no_isolate_is_present; if (!FLAG_experimental_shared_data) { // Should not be invoked __ Breakpoint(); } // This stub is also called from mutator thread running without an // isolate and attempts to load value from isolate static field. __ LoadIsolate(kScratchReg); __ BranchIfZero(kScratchReg, &throw_since_no_isolate_is_present); __ LeaveStubFrame(); __ Ret(); #if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64) // We are jumping over LeaveStubFrame so restore LR state to match one // at the jump point. __ set_lr_state(compiler::LRState::OnEntry().EnterFrame()); #endif // defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64) // Throw FieldAccessError __ Bind(&throw_since_no_isolate_is_present); __ PushObject(NullObject()); // Make room for (unused) result. __ PushRegister(kFieldReg); __ CallRuntime(kStaticFieldAccessedWithoutIsolateErrorRuntimeEntry, /*argument_count=*/1); __ Breakpoint(); } void StubCodeCompiler::GenerateInitLateStaticFieldStub(bool is_final, bool is_shared) { const Register kResultReg = InitStaticFieldABI::kResultReg; const Register kFieldReg = InitStaticFieldABI::kFieldReg; const Register kAddressReg = InitLateStaticFieldInternalRegs::kAddressReg; const Register kScratchReg = InitLateStaticFieldInternalRegs::kScratchReg; __ EnterStubFrame(); if (is_shared) { // Since initialization of shared fields has to be guarded by // a mutex, do the initialization in the runtime. __ PushObject(NullObject()); // Make room for the result __ PushRegister(kFieldReg); __ CallRuntime(kInitializeSharedFieldRuntimeEntry, /*argument_count=*/1); __ PopRegister(kFieldReg); __ PopRegister(kResultReg); __ LeaveStubFrame(); __ Ret(); return; } Label throw_since_no_isolate_is_present; if (FLAG_experimental_shared_data) { ASSERT(!is_shared); // This stub is also called from mutator thread running without an // isolate and attempts to load value from isolate static field. __ LoadIsolate(kScratchReg); __ BranchIfZero(kScratchReg, &throw_since_no_isolate_is_present); } __ Comment("Calling initializer function"); __ PushRegister(kFieldReg); __ LoadCompressedFieldFromOffset( FUNCTION_REG, kFieldReg, target::Field::initializer_function_offset()); if (!FLAG_precompiled_mode) { __ LoadCompressedFieldFromOffset(CODE_REG, FUNCTION_REG, target::Function::code_offset()); // Load a GC-safe value for the arguments descriptor (unused but tagged). __ LoadImmediate(ARGS_DESC_REG, 0); } if (FLAG_target_thread_sanitizer) { __ TsanFuncEntry(); } __ Call(FieldAddress(FUNCTION_REG, target::Function::entry_point_offset())); if (FLAG_target_thread_sanitizer) { __ TsanFuncExit(); } __ MoveRegister(kResultReg, CallingConventions::kReturnReg); __ PopRegister(kFieldReg); __ LoadStaticFieldAddress(kAddressReg, kFieldReg, kScratchReg, is_shared); Label throw_exception; if (is_final) { __ Comment("Checking that initializer did not set late final field"); if (is_shared) { __ LoadAcquireFromOffset(kScratchReg, kAddressReg, 0); } else { __ LoadFromOffset(kScratchReg, kAddressReg, 0); } __ CompareObject(kScratchReg, SentinelObject()); __ BranchIf(NOT_EQUAL, &throw_exception); } if (is_shared) { __ StoreReleaseToOffset(kResultReg, kAddressReg, 0); } else { __ StoreToOffset(kResultReg, kAddressReg, 0); } __ LeaveStubFrame(); __ Ret(); if (is_final) { #if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64) // We are jumping over LeaveStubFrame so restore LR state to match one // at the jump point. __ set_lr_state(compiler::LRState::OnEntry().EnterFrame()); #endif // defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64) __ Bind(&throw_exception); __ PushObject(NullObject()); // Make room for (unused) result. __ PushRegister(kFieldReg); __ CallRuntime(kLateFieldAssignedDuringInitializationErrorRuntimeEntry, /*argument_count=*/1); __ Breakpoint(); } if (FLAG_experimental_shared_data) { ASSERT(!is_shared); #if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64) // We are jumping over LeaveStubFrame so restore LR state to match one // at the jump point. __ set_lr_state(compiler::LRState::OnEntry().EnterFrame()); #endif // defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64) // Throw FieldAccessError __ Bind(&throw_since_no_isolate_is_present); __ PushObject(NullObject()); // Make room for (unused) result. __ PushRegister(kFieldReg); __ CallRuntime(kStaticFieldAccessedWithoutIsolateErrorRuntimeEntry, /*argument_count=*/1); __ Breakpoint(); } } void StubCodeCompiler::GenerateInitLateStaticFieldStub() { GenerateInitLateStaticFieldStub(/*is_final=*/false, /*is_shared=*/false); } void StubCodeCompiler::GenerateInitLateFinalStaticFieldStub() { GenerateInitLateStaticFieldStub(/*is_final=*/true, /*is_shared=*/false); } void StubCodeCompiler::GenerateInitSharedLateStaticFieldStub() { GenerateInitLateStaticFieldStub(/*is_final=*/false, /*is_shared=*/true); } void StubCodeCompiler::GenerateInitInstanceFieldStub() { __ EnterStubFrame(); __ PushObject(NullObject()); // Make room for result. __ PushRegistersInOrder( {InitInstanceFieldABI::kInstanceReg, InitInstanceFieldABI::kFieldReg}); __ CallRuntime(kInitInstanceFieldRuntimeEntry, /*argument_count=*/2); __ Drop(2); __ PopRegister(InitInstanceFieldABI::kResultReg); __ LeaveStubFrame(); __ Ret(); } void StubCodeCompiler::GenerateInitLateInstanceFieldStub(bool is_final) { const Register kInstanceReg = InitInstanceFieldABI::kInstanceReg; const Register kFieldReg = InitInstanceFieldABI::kFieldReg; const Register kAddressReg = InitLateInstanceFieldInternalRegs::kAddressReg; const Register kScratchReg = InitLateInstanceFieldInternalRegs::kScratchReg; __ EnterStubFrame(); // Save kFieldReg and kInstanceReg for later. // Call initializer function. __ PushRegistersInOrder({kFieldReg, kInstanceReg, kInstanceReg}); static_assert( InitInstanceFieldABI::kResultReg == CallingConventions::kReturnReg, "Result is a return value from initializer"); __ LoadCompressedFieldFromOffset( FUNCTION_REG, InitInstanceFieldABI::kFieldReg, target::Field::initializer_function_offset()); if (!FLAG_precompiled_mode) { __ LoadCompressedFieldFromOffset(CODE_REG, FUNCTION_REG, target::Function::code_offset()); #if defined(DART_BYTECODE_INTERPRETER) // InterpretCall stub needs arguments descriptor for all function calls. __ LoadObject(ARGS_DESC_REG, ArgumentsDescriptorBoxed(/*type_args_len=*/0, /*num_arguments=*/1)); #else // Load a GC-safe value for the arguments descriptor (unused but tagged). __ LoadImmediate(ARGS_DESC_REG, 0); #endif // defined(DART_BYTECODE_INTERPRETER) } if (FLAG_target_thread_sanitizer) { __ TsanFuncEntry(); } __ Call(FieldAddress(FUNCTION_REG, target::Function::entry_point_offset())); if (FLAG_target_thread_sanitizer) { __ TsanFuncExit(); } __ Drop(1); // Drop argument. __ PopRegisterPair(kInstanceReg, kFieldReg); __ LoadCompressedFieldFromOffset( kScratchReg, kFieldReg, target::Field::host_offset_or_field_id_offset()); #if defined(DART_COMPRESSED_POINTERS) // TODO(compressed-pointers): Variant of LoadFieldAddressForRegOffset that // ignores upper bits? __ SmiUntag(kScratchReg); __ SmiTag(kScratchReg); #endif __ LoadCompressedFieldAddressForRegOffset(kAddressReg, kInstanceReg, kScratchReg); Label throw_exception; if (is_final) { __ LoadCompressed(kScratchReg, Address(kAddressReg, 0)); __ CompareObject(kScratchReg, SentinelObject()); __ BranchIf(NOT_EQUAL, &throw_exception); } #if defined(TARGET_ARCH_IA32) // On IA32 StoreIntoObject clobbers value register, so scratch // register is used in StoreIntoObject to preserve kResultReg. __ MoveRegister(kScratchReg, InitInstanceFieldABI::kResultReg); __ StoreIntoObject(kInstanceReg, Address(kAddressReg, 0), kScratchReg); #else __ StoreCompressedIntoObject(kInstanceReg, Address(kAddressReg, 0), InitInstanceFieldABI::kResultReg); #endif // defined(TARGET_ARCH_IA32) __ LeaveStubFrame(); __ Ret(); if (is_final) { #if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64) // We are jumping over LeaveStubFrame so restore LR state to match one // at the jump point. __ set_lr_state(compiler::LRState::OnEntry().EnterFrame()); #endif // defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64) __ Bind(&throw_exception); __ PushObject(NullObject()); // Make room for (unused) result. __ PushRegister(kFieldReg); __ CallRuntime(kLateFieldAssignedDuringInitializationErrorRuntimeEntry, /*argument_count=*/1); __ Breakpoint(); } } void StubCodeCompiler::GenerateInitLateInstanceFieldStub() { GenerateInitLateInstanceFieldStub(/*is_final=*/false); } void StubCodeCompiler::GenerateInitLateFinalInstanceFieldStub() { GenerateInitLateInstanceFieldStub(/*is_final=*/true); } void StubCodeCompiler::GenerateThrowStub() { __ EnterStubFrame(); __ PushObject(NullObject()); // Make room for (unused) result. __ PushRegister(ThrowABI::kExceptionReg); __ CallRuntime(kThrowRuntimeEntry, /*argument_count=*/1); __ Breakpoint(); } void StubCodeCompiler::GenerateReThrowStub() { __ EnterStubFrame(); __ PushObject(NullObject()); // Make room for (unused) result. __ PushRegistersInOrder( {ReThrowABI::kExceptionReg, ReThrowABI::kStackTraceReg}); __ PushImmediate(Smi::RawValue(0)); // Do not bypass debugger. __ CallRuntime(kReThrowRuntimeEntry, /*argument_count=*/3); __ Breakpoint(); } void StubCodeCompiler::GenerateAssertSubtypeStub() { __ EnterStubFrame(); __ PushRegistersInOrder({AssertSubtypeABI::kInstantiatorTypeArgumentsReg, AssertSubtypeABI::kFunctionTypeArgumentsReg, AssertSubtypeABI::kSubTypeReg, AssertSubtypeABI::kSuperTypeReg, AssertSubtypeABI::kDstNameReg}); __ CallRuntime(kSubtypeCheckRuntimeEntry, /*argument_count=*/5); __ Drop(5); // Drop unused result as well as arguments. __ LeaveStubFrame(); __ Ret(); } void StubCodeCompiler::GenerateAssertAssignableStub() { #if !defined(TARGET_ARCH_IA32) __ Breakpoint(); #else __ EnterStubFrame(); __ PushObject(Object::null_object()); // Make room for the result. __ pushl(Address( EBP, target::kWordSize * AssertAssignableStubABI::kInstanceSlotFromFp)); __ pushl(Address( EBP, target::kWordSize * AssertAssignableStubABI::kDstTypeSlotFromFp)); __ pushl(Address( EBP, target::kWordSize * AssertAssignableStubABI::kInstantiatorTAVSlotFromFp)); __ pushl(Address(EBP, target::kWordSize * AssertAssignableStubABI::kFunctionTAVSlotFromFp)); __ PushRegistersInOrder({AssertAssignableStubABI::kDstNameReg, AssertAssignableStubABI::kSubtypeTestReg}); __ PushObject(Smi::ZoneHandle(Smi::New(kTypeCheckFromInline))); __ CallRuntime(kTypeCheckRuntimeEntry, /*argument_count=*/7); __ Drop(8); __ LeaveStubFrame(); __ Ret(); #endif } // Instantiate type arguments from instantiator and function type args. // Inputs: // - InstantiationABI::kUninstantiatedTypeArgumentsReg: tav to instantiate // - InstantiationABI::kInstantiatorTypeArgumentsReg: instantiator tav // - InstantiationABI::kFunctionTypeArgumentsReg: function tav // Outputs: // - InstantiationABI::kResultTypeArgumentsReg: instantiated tav // Clobbers: // - InstantiationABI::kScratchReg void StubCodeCompiler::GenerateInstantiateTypeArgumentsStub() { // We only need the offset of the current entry up until we either call // the runtime or until we retrieve the instantiated type arguments out of it // to put in the result register, so we use the result register to store it. const Register kEntryReg = InstantiationABI::kResultTypeArgumentsReg; // The registers that need spilling prior to traversing a hash-based cache. const RegisterSet saved_registers(InstantiateTAVInternalRegs::kSavedRegisters, /*fpu_register_mask=*/0); static_assert(((1 << InstantiationABI::kInstantiatorTypeArgumentsReg) & InstantiateTAVInternalRegs::kSavedRegisters) == 0, "Must handle possibility of inst tav reg being spilled"); static_assert(((1 << InstantiationABI::kFunctionTypeArgumentsReg) & InstantiateTAVInternalRegs::kSavedRegisters) == 0, "Must handle possibility of function tav reg being spilled"); // Takes labels for the cache hit/miss cases (to allow for restoring spilled // registers). auto check_entry = [&](compiler::Label* found, compiler::Label* not_found) { __ Comment("Check cache entry"); // Use load-acquire to get the entry. static_assert(TypeArguments::Cache::kSentinelIndex == TypeArguments::Cache::kInstantiatorTypeArgsIndex, "sentinel is not same index as instantiator type args"); __ LoadAcquireCompressedFromOffset( InstantiationABI::kScratchReg, kEntryReg, TypeArguments::Cache::kInstantiatorTypeArgsIndex * target::kCompressedWordSize); // Test for an unoccupied entry by checking for the Smi sentinel. __ BranchIfSmi(InstantiationABI::kScratchReg, not_found); // Otherwise it must be occupied and contain TypeArguments objects. compiler::Label next; __ CompareRegisters(InstantiationABI::kScratchReg, InstantiationABI::kInstantiatorTypeArgumentsReg); __ BranchIf(NOT_EQUAL, &next, compiler::Assembler::kNearJump); __ LoadCompressed( InstantiationABI::kScratchReg, compiler::Address(kEntryReg, TypeArguments::Cache::kFunctionTypeArgsIndex * target::kCompressedWordSize)); __ CompareRegisters(InstantiationABI::kScratchReg, InstantiationABI::kFunctionTypeArgumentsReg); __ BranchIf(EQUAL, found); __ Bind(&next); }; // Lookup cache before calling runtime. __ LoadAcquireCompressedFromOffset( InstantiationABI::kScratchReg, InstantiationABI::kUninstantiatedTypeArgumentsReg, target::TypeArguments::instantiations_offset() - kHeapObjectTag); // Go ahead and load the backing array data address into kEntryReg. __ LoadFieldAddressForOffset(kEntryReg, InstantiationABI::kScratchReg, target::Array::data_offset()); compiler::Label linear_cache_loop, hash_cache_search, cache_hit, call_runtime; // There is a maximum size for linear caches that is smaller than the size // of any hash-based cache, so we check the size of the backing array to // determine if this is a linear or hash-based cache. __ LoadFromSlot(InstantiationABI::kScratchReg, InstantiationABI::kScratchReg, Slot::Array_length()); __ CompareImmediate( InstantiationABI::kScratchReg, target::ToRawSmi(TypeArguments::Cache::kMaxLinearCacheSize)); #if defined(TARGET_ARCH_IA32) // We just don't have enough registers to do hash-based cache searching in a // way that doesn't overly complicate the generation code, so just go to // runtime. __ BranchIf(GREATER, &call_runtime); #else __ BranchIf(GREATER, &hash_cache_search); #endif __ Comment("Check linear cache"); // Move kEntryReg to the start of the first entry. __ AddImmediate(kEntryReg, TypeArguments::Cache::kHeaderSize * target::kCompressedWordSize); __ Bind(&linear_cache_loop); check_entry(&cache_hit, &call_runtime); __ AddImmediate(kEntryReg, TypeArguments::Cache::kEntrySize * target::kCompressedWordSize); __ Jump(&linear_cache_loop, compiler::Assembler::kNearJump); #if !defined(TARGET_ARCH_IA32) __ Bind(&hash_cache_search); __ Comment("Check hash-based cache"); compiler::Label pop_before_success, pop_before_failure; if (!saved_registers.IsEmpty()) { __ Comment("Spills due to register pressure"); __ PushRegisters(saved_registers); } __ Comment("Calculate address of first entry"); __ AddImmediate( InstantiateTAVInternalRegs::kEntryStartReg, kEntryReg, TypeArguments::Cache::kHeaderSize * target::kCompressedWordSize); __ Comment("Calculate probe mask"); __ LoadAcquireCompressedFromOffset( InstantiationABI::kScratchReg, kEntryReg, TypeArguments::Cache::kMetadataIndex * target::kCompressedWordSize); __ LsrImmediate( InstantiationABI::kScratchReg, TypeArguments::Cache::EntryCountLog2Bits::shift() + kSmiTagShift); __ LoadImmediate(InstantiateTAVInternalRegs::kProbeMaskReg, 1); __ LslRegister(InstantiateTAVInternalRegs::kProbeMaskReg, InstantiationABI::kScratchReg); __ AddImmediate(InstantiateTAVInternalRegs::kProbeMaskReg, -1); // Can use kEntryReg as scratch now until we're entering the loop. // Retrieve the hash from the TAV. If the retrieved hash is 0, jumps to // not_found, otherwise falls through. auto retrieve_hash = [&](Register dst, Register src) { Label is_not_null, done; __ CompareObject(src, NullObject()); __ BranchIf(NOT_EQUAL, &is_not_null, compiler::Assembler::kNearJump); __ LoadImmediate(dst, TypeArguments::kAllDynamicHash); __ Jump(&done, compiler::Assembler::kNearJump); __ Bind(&is_not_null); __ LoadFromSlot(dst, src, Slot::TypeArguments_hash()); __ SmiUntag(dst); // If the retrieved hash is 0, then it hasn't been computed yet. __ BranchIfZero(dst, &pop_before_failure); __ Bind(&done); }; __ Comment("Calculate initial probe from type argument vector hashes"); retrieve_hash(InstantiateTAVInternalRegs::kCurrentEntryIndexReg, InstantiationABI::kInstantiatorTypeArgumentsReg); retrieve_hash(InstantiationABI::kScratchReg, InstantiationABI::kFunctionTypeArgumentsReg); __ CombineHashes(InstantiateTAVInternalRegs::kCurrentEntryIndexReg, InstantiationABI::kScratchReg); __ FinalizeHash(InstantiateTAVInternalRegs::kCurrentEntryIndexReg, InstantiationABI::kScratchReg); // Use the probe mask to get a valid entry index. __ AndRegisters(InstantiateTAVInternalRegs::kCurrentEntryIndexReg, InstantiateTAVInternalRegs::kProbeMaskReg); // Start off the probing distance at zero (will increment prior to use). __ LoadImmediate(InstantiateTAVInternalRegs::kProbeDistanceReg, 0); compiler::Label loop; __ Bind(&loop); __ Comment("Loop over hash cache entries"); // Convert the current entry index into the entry address. __ MoveRegister(kEntryReg, InstantiateTAVInternalRegs::kCurrentEntryIndexReg); __ MulImmediate(kEntryReg, TypeArguments::Cache::kEntrySize * target::kCompressedWordSize); __ AddRegisters(kEntryReg, InstantiateTAVInternalRegs::kEntryStartReg); check_entry(&pop_before_success, &pop_before_failure); // Increment the probing distance and then add it to the current entry // index, then mask the result with the probe mask. __ AddImmediate(InstantiateTAVInternalRegs::kProbeDistanceReg, 1); __ AddRegisters(InstantiateTAVInternalRegs::kCurrentEntryIndexReg, InstantiateTAVInternalRegs::kProbeDistanceReg); __ AndRegisters(InstantiateTAVInternalRegs::kCurrentEntryIndexReg, InstantiateTAVInternalRegs::kProbeMaskReg); __ Jump(&loop); __ Bind(&pop_before_failure); if (!saved_registers.IsEmpty()) { __ Comment("Restore spilled registers on cache miss"); __ PopRegisters(saved_registers); } #endif // Instantiate non-null type arguments. // A runtime call to instantiate the type arguments is required. __ Bind(&call_runtime); __ Comment("Cache miss"); __ EnterStubFrame(); #if !defined(DART_ASSEMBLER_HAS_NULL_REG) __ PushObject(Object::null_object()); // Make room for the result. #endif #if defined(TARGET_ARCH_ARM) static_assert((InstantiationABI::kUninstantiatedTypeArgumentsReg > InstantiationABI::kInstantiatorTypeArgumentsReg) && (InstantiationABI::kInstantiatorTypeArgumentsReg > InstantiationABI::kFunctionTypeArgumentsReg), "Should be ordered to push arguments with one instruction"); #endif __ PushRegistersInOrder({ #if defined(DART_ASSEMBLER_HAS_NULL_REG) NULL_REG, #endif InstantiationABI::kUninstantiatedTypeArgumentsReg, InstantiationABI::kInstantiatorTypeArgumentsReg, InstantiationABI::kFunctionTypeArgumentsReg, }); __ CallRuntime(kInstantiateTypeArgumentsRuntimeEntry, 3); __ Drop(3); // Drop 2 type vectors, and uninstantiated type. __ PopRegister(InstantiationABI::kResultTypeArgumentsReg); __ LeaveStubFrame(); __ Ret(); #if !defined(TARGET_ARCH_IA32) __ Bind(&pop_before_success); if (!saved_registers.IsEmpty()) { __ Comment("Restore spilled registers on cache hit"); __ PopRegisters(saved_registers); } #endif __ Bind(&cache_hit); __ Comment("Cache hit"); __ LoadCompressed( InstantiationABI::kResultTypeArgumentsReg, compiler::Address(kEntryReg, TypeArguments::Cache::kInstantiatedTypeArgsIndex * target::kCompressedWordSize)); __ Ret(); } void StubCodeCompiler:: GenerateInstantiateTypeArgumentsMayShareInstantiatorTAStub() { const Register kScratch1Reg = InstantiationABI::kResultTypeArgumentsReg; const Register kScratch2Reg = InstantiationABI::kScratchReg; // Return the instantiator type arguments if its nullability is compatible for // sharing, otherwise proceed to instantiation cache lookup. compiler::Label cache_lookup; __ LoadCompressedSmi( kScratch1Reg, compiler::FieldAddress(InstantiationABI::kUninstantiatedTypeArgumentsReg, target::TypeArguments::nullability_offset())); __ LoadCompressedSmi( kScratch2Reg, compiler::FieldAddress(InstantiationABI::kInstantiatorTypeArgumentsReg, target::TypeArguments::nullability_offset())); __ AndRegisters(kScratch2Reg, kScratch1Reg); __ CompareRegisters(kScratch2Reg, kScratch1Reg); __ BranchIf(NOT_EQUAL, &cache_lookup, compiler::Assembler::kNearJump); __ MoveRegister(InstantiationABI::kResultTypeArgumentsReg, InstantiationABI::kInstantiatorTypeArgumentsReg); __ Ret(); __ Bind(&cache_lookup); GenerateInstantiateTypeArgumentsStub(); } void StubCodeCompiler:: GenerateInstantiateTypeArgumentsMayShareFunctionTAStub() { const Register kScratch1Reg = InstantiationABI::kResultTypeArgumentsReg; const Register kScratch2Reg = InstantiationABI::kScratchReg; // Return the function type arguments if its nullability is compatible for // sharing, otherwise proceed to instantiation cache lookup. compiler::Label cache_lookup; __ LoadCompressedSmi( kScratch1Reg, compiler::FieldAddress(InstantiationABI::kUninstantiatedTypeArgumentsReg, target::TypeArguments::nullability_offset())); __ LoadCompressedSmi( kScratch2Reg, compiler::FieldAddress(InstantiationABI::kFunctionTypeArgumentsReg, target::TypeArguments::nullability_offset())); __ AndRegisters(kScratch2Reg, kScratch1Reg); __ CompareRegisters(kScratch2Reg, kScratch1Reg); __ BranchIf(NOT_EQUAL, &cache_lookup, compiler::Assembler::kNearJump); __ MoveRegister(InstantiationABI::kResultTypeArgumentsReg, InstantiationABI::kFunctionTypeArgumentsReg); __ Ret(); __ Bind(&cache_lookup); GenerateInstantiateTypeArgumentsStub(); } static void BuildInstantiateTypeRuntimeCall(Assembler* assembler) { __ EnterStubFrame(); __ PushObject(Object::null_object()); __ PushRegistersInOrder({InstantiateTypeABI::kTypeReg, InstantiateTypeABI::kInstantiatorTypeArgumentsReg, InstantiateTypeABI::kFunctionTypeArgumentsReg}); __ CallRuntime(kInstantiateTypeRuntimeEntry, /*argument_count=*/3); __ Drop(3); __ PopRegister(InstantiateTypeABI::kResultTypeReg); __ LeaveStubFrame(); __ Ret(); } static void BuildInstantiateTypeParameterStub(Assembler* assembler, Nullability nullability, bool is_function_parameter) { Label runtime_call, return_dynamic, type_parameter_value_is_not_type; if (is_function_parameter) { __ CompareObject(InstantiateTypeABI::kFunctionTypeArgumentsReg, TypeArguments::null_object()); __ BranchIf(EQUAL, &return_dynamic); __ LoadFieldFromOffset( InstantiateTypeABI::kResultTypeReg, InstantiateTypeABI::kTypeReg, target::TypeParameter::index_offset(), kUnsignedTwoBytes); __ LoadIndexedCompressed(InstantiateTypeABI::kResultTypeReg, InstantiateTypeABI::kFunctionTypeArgumentsReg, target::TypeArguments::types_offset(), InstantiateTypeABI::kResultTypeReg); } else { __ CompareObject(InstantiateTypeABI::kInstantiatorTypeArgumentsReg, TypeArguments::null_object()); __ BranchIf(EQUAL, &return_dynamic); __ LoadFieldFromOffset( InstantiateTypeABI::kResultTypeReg, InstantiateTypeABI::kTypeReg, target::TypeParameter::index_offset(), kUnsignedTwoBytes); __ LoadIndexedCompressed(InstantiateTypeABI::kResultTypeReg, InstantiateTypeABI::kInstantiatorTypeArgumentsReg, target::TypeArguments::types_offset(), InstantiateTypeABI::kResultTypeReg); } switch (nullability) { case Nullability::kNonNullable: __ Ret(); break; case Nullability::kNullable: __ CompareAbstractTypeNullabilityWith( InstantiateTypeABI::kResultTypeReg, static_cast(Nullability::kNullable), InstantiateTypeABI::kScratchReg); __ BranchIf(NOT_EQUAL, &runtime_call); __ Ret(); break; } // The TAV was null, so the value of the type parameter is "dynamic". __ Bind(&return_dynamic); __ LoadObject(InstantiateTypeABI::kResultTypeReg, Type::dynamic_type()); __ Ret(); __ Bind(&runtime_call); BuildInstantiateTypeRuntimeCall(assembler); } void StubCodeCompiler:: GenerateInstantiateTypeNonNullableClassTypeParameterStub() { BuildInstantiateTypeParameterStub(assembler, Nullability::kNonNullable, /*is_function_parameter=*/false); } void StubCodeCompiler::GenerateInstantiateTypeNullableClassTypeParameterStub() { BuildInstantiateTypeParameterStub(assembler, Nullability::kNullable, /*is_function_parameter=*/false); } void StubCodeCompiler:: GenerateInstantiateTypeNonNullableFunctionTypeParameterStub() { BuildInstantiateTypeParameterStub(assembler, Nullability::kNonNullable, /*is_function_parameter=*/true); } void StubCodeCompiler:: GenerateInstantiateTypeNullableFunctionTypeParameterStub() { BuildInstantiateTypeParameterStub(assembler, Nullability::kNullable, /*is_function_parameter=*/true); } void StubCodeCompiler::GenerateInstantiateTypeStub() { BuildInstantiateTypeRuntimeCall(assembler); } void StubCodeCompiler::GenerateInstanceOfStub() { __ EnterStubFrame(); __ PushObject(NullObject()); // Make room for the result. __ PushRegistersInOrder({TypeTestABI::kInstanceReg, TypeTestABI::kDstTypeReg, TypeTestABI::kInstantiatorTypeArgumentsReg, TypeTestABI::kFunctionTypeArgumentsReg, TypeTestABI::kSubtypeTestCacheReg}); __ CallRuntime(kInstanceofRuntimeEntry, /*argument_count=*/5); __ Drop(5); __ PopRegister(TypeTestABI::kInstanceOfResultReg); __ LeaveStubFrame(); __ Ret(); } // For use in GenerateTypeIsTopTypeForSubtyping and // GenerateNullIsAssignableToType. static void EnsureIsSomeKindOfType(Assembler* assembler, Register type_reg, Register scratch_reg) { #if defined(DEBUG) compiler::Label is_type_param_or_type_or_function_type; __ LoadClassIdMayBeSmi(scratch_reg, type_reg); __ CompareImmediate(scratch_reg, kTypeParameterCid); __ BranchIf(EQUAL, &is_type_param_or_type_or_function_type, compiler::Assembler::kNearJump); __ CompareImmediate(scratch_reg, kTypeCid); __ BranchIf(EQUAL, &is_type_param_or_type_or_function_type, compiler::Assembler::kNearJump); __ CompareImmediate(scratch_reg, kFunctionTypeCid); __ BranchIf(EQUAL, &is_type_param_or_type_or_function_type, compiler::Assembler::kNearJump); __ CompareImmediate(scratch_reg, kRecordTypeCid); __ BranchIf(EQUAL, &is_type_param_or_type_or_function_type, compiler::Assembler::kNearJump); __ Stop("not a type, function type, record type or type parameter"); __ Bind(&is_type_param_or_type_or_function_type); #endif } // Version of AbstractType::IsTopTypeForSubtyping() used when the type is not // known at compile time. Must be kept in sync. // // Inputs: // - TypeTestABI::kDstTypeReg: Destination type. // // Non-preserved scratch registers: // - TypeTestABI::kScratchReg (only on non-IA32 architectures) // // Outputs: // - TypeTestABI::kSubtypeTestCacheReg: 0 if the value is guaranteed assignable, // non-zero otherwise. // // All registers other than outputs and non-preserved scratches are preserved. void StubCodeCompiler::GenerateTypeIsTopTypeForSubtypingStub() { // The only case where the original value of kSubtypeTestCacheReg is needed // after the stub call is on IA32, where it's currently preserved on the stack // before calling the stub (as it's also CODE_REG on that architecture), so we // both use it as a scratch and clobber it for the return value. const Register scratch1_reg = TypeTestABI::kSubtypeTestCacheReg; // We reuse the first scratch register as the output register because we're // always guaranteed to have a type in it (starting with kDstType), and all // non-Smi ObjectPtrs are non-zero values. const Register output_reg = scratch1_reg; #if defined(TARGET_ARCH_IA32) // The remaining scratch registers are preserved and restored before exit on // IA32. Because we have few registers to choose from (which are all used in // TypeTestABI), use specific TestTypeABI registers. const Register scratch2_reg = TypeTestABI::kFunctionTypeArgumentsReg; // Preserve non-output scratch registers. __ PushRegister(scratch2_reg); #else const Register scratch2_reg = TypeTestABI::kScratchReg; #endif static_assert(scratch1_reg != scratch2_reg, "both scratch registers are the same"); compiler::Label check_top_type, is_top_type, done; // Initialize scratch1_reg with the type to check (which also sets the // output register to a non-zero value). scratch1_reg (and thus the output // register) will always have a type in it from here on out. __ MoveRegister(scratch1_reg, TypeTestABI::kDstTypeReg); __ Bind(&check_top_type); // scratch1_reg: Current type to check. EnsureIsSomeKindOfType(assembler, scratch1_reg, scratch2_reg); compiler::Label is_type_ref; __ CompareClassId(scratch1_reg, kTypeCid, scratch2_reg); // Type parameters can't be top types themselves, though a particular // instantiation may result in a top type. // Function types cannot be top types. __ BranchIf(NOT_EQUAL, &done); __ LoadTypeClassId(scratch2_reg, scratch1_reg); __ CompareImmediate(scratch2_reg, kDynamicCid); __ BranchIf(EQUAL, &is_top_type, compiler::Assembler::kNearJump); __ CompareImmediate(scratch2_reg, kVoidCid); __ BranchIf(EQUAL, &is_top_type, compiler::Assembler::kNearJump); compiler::Label unwrap_future_or; __ CompareImmediate(scratch2_reg, kFutureOrCid); __ BranchIf(EQUAL, &unwrap_future_or, compiler::Assembler::kNearJump); __ CompareImmediate(scratch2_reg, kInstanceCid); __ BranchIf(NOT_EQUAL, &done, compiler::Assembler::kNearJump); // Instance type isn't a top type if non-nullable. __ CompareAbstractTypeNullabilityWith( scratch1_reg, static_cast(Nullability::kNonNullable), scratch2_reg); __ BranchIf(EQUAL, &done, compiler::Assembler::kNearJump); __ Bind(&is_top_type); __ LoadImmediate(output_reg, 0); __ Bind(&done); #if defined(TARGET_ARCH_IA32) // Restore preserved scratch registers. __ PopRegister(scratch2_reg); #endif __ Ret(); // An uncommon case, so off the main trunk of the function. __ Bind(&unwrap_future_or); __ LoadCompressedField( scratch2_reg, compiler::FieldAddress(scratch1_reg, compiler::target::Type::arguments_offset())); __ CompareObject(scratch2_reg, Object::null_object()); // If the arguments are null, then unwrapping gives dynamic, a top type. __ BranchIf(EQUAL, &is_top_type, compiler::Assembler::kNearJump); __ LoadCompressedField( scratch1_reg, compiler::FieldAddress( scratch2_reg, compiler::target::TypeArguments::type_at_offset(0))); __ Jump(&check_top_type, compiler::Assembler::kNearJump); } // Version of Instance::NullIsAssignableTo(other, inst_tav, fun_tav) used when // the destination type was not known at compile time. Must be kept in sync. // // Inputs: // - TypeTestABI::kInstanceReg: Object to check for assignability. // - TypeTestABI::kDstTypeReg: Destination type. // - TypeTestABI::kInstantiatorTypeArgumentsReg: Instantiator TAV. // - TypeTestABI::kFunctionTypeArgumentsReg: Function TAV. // // Non-preserved non-output scratch registers: // - TypeTestABI::kScratchReg (only on non-IA32 architectures) // // Outputs: // - TypeTestABI::kSubtypeTestCacheReg: 0 if the value is guaranteed assignable, // non-zero otherwise. // // All registers other than outputs and non-preserved scratches are preserved. void StubCodeCompiler::GenerateNullIsAssignableToTypeStub() { // The only case where the original value of kSubtypeTestCacheReg is needed // after the stub call is on IA32, where it's currently preserved on the stack // before calling the stub (as it's also CODE_REG on that architecture), so we // both use it as a scratch to hold the current type to inspect and also // clobber it for the return value. const Register kCurrentTypeReg = TypeTestABI::kSubtypeTestCacheReg; // We reuse the first scratch register as the output register because we're // always guaranteed to have a type in it (starting with the contents of // kDstTypeReg), and all non-Smi ObjectPtrs are non-zero values. const Register kOutputReg = kCurrentTypeReg; #if defined(TARGET_ARCH_IA32) // The remaining scratch registers are preserved and restored before exit on // IA32. Because we have few registers to choose from (which are all used in // TypeTestABI), use specific TestTypeABI registers. const Register kScratchReg = TypeTestABI::kFunctionTypeArgumentsReg; // Preserve non-output scratch registers. __ PushRegister(kScratchReg); #else const Register kScratchReg = TypeTestABI::kScratchReg; #endif static_assert(kCurrentTypeReg != kScratchReg, "code assumes distinct scratch registers"); compiler::Label is_assignable, done; // Initialize the first scratch register (and thus the output register) with // the destination type. We do this before the check to ensure the output // register has a non-zero value if kInstanceReg is not null. __ MoveRegister(kCurrentTypeReg, TypeTestABI::kDstTypeReg); __ CompareObject(TypeTestABI::kInstanceReg, Object::null_object()); compiler::Label check_null_assignable; // Skip checking the type if not null. __ BranchIf(NOT_EQUAL, &done); __ Bind(&check_null_assignable); // scratch1_reg: Current type to check. EnsureIsSomeKindOfType(assembler, kCurrentTypeReg, kScratchReg); compiler::Label is_not_type; __ CompareClassId(kCurrentTypeReg, kTypeCid, kScratchReg); __ BranchIf(NOT_EQUAL, &is_not_type, compiler::Assembler::kNearJump); __ CompareAbstractTypeNullabilityWith( kCurrentTypeReg, static_cast(Nullability::kNonNullable), kScratchReg); __ BranchIf(NOT_EQUAL, &is_assignable); // FutureOr is a special case because it may have the non-nullable bit set, // but FutureOr functions as the union of T and Future, so it must be // unwrapped to see if T is nullable. __ LoadTypeClassId(kScratchReg, kCurrentTypeReg); __ CompareImmediate(kScratchReg, kFutureOrCid); __ BranchIf(NOT_EQUAL, &done); __ LoadCompressedField( kScratchReg, compiler::FieldAddress(kCurrentTypeReg, compiler::target::Type::arguments_offset())); __ CompareObject(kScratchReg, Object::null_object()); // If the arguments are null, then unwrapping gives the dynamic type, // which can take null. __ BranchIf(EQUAL, &is_assignable); __ LoadCompressedField( kCurrentTypeReg, compiler::FieldAddress( kScratchReg, compiler::target::TypeArguments::type_at_offset(0))); __ Jump(&check_null_assignable, compiler::Assembler::kNearJump); __ Bind(&is_not_type); // Null is assignable to a type parameter only if it is nullable or if the // instantiation is nullable. __ CompareAbstractTypeNullabilityWith( kCurrentTypeReg, static_cast(Nullability::kNonNullable), kScratchReg); __ BranchIf(NOT_EQUAL, &is_assignable); // Don't set kScratchReg in here as on IA32, that's the function TAV reg. auto handle_case = [&](Register tav) { // We can reuse kCurrentTypeReg to hold the index because we no longer // need the type parameter afterwards. auto const kIndexReg = kCurrentTypeReg; // If the TAV is null, resolving gives the (nullable) dynamic type. __ CompareObject(tav, NullObject()); __ BranchIf(EQUAL, &is_assignable, Assembler::kNearJump); // Resolve the type parameter to its instantiated type and loop. __ LoadFieldFromOffset(kIndexReg, kCurrentTypeReg, target::TypeParameter::index_offset(), kUnsignedTwoBytes); __ LoadIndexedCompressed(kCurrentTypeReg, tav, target::TypeArguments::types_offset(), kIndexReg); __ Jump(&check_null_assignable); }; Label function_type_param; __ LoadFromSlot(kScratchReg, TypeTestABI::kDstTypeReg, Slot::AbstractType_flags()); __ BranchIfBit(kScratchReg, target::UntaggedTypeParameter::kIsFunctionTypeParameterBit, NOT_ZERO, &function_type_param, Assembler::kNearJump); handle_case(TypeTestABI::kInstantiatorTypeArgumentsReg); __ Bind(&function_type_param); #if defined(TARGET_ARCH_IA32) // Function TAV is on top of stack because we're using that register as // kScratchReg. __ LoadFromStack(TypeTestABI::kFunctionTypeArgumentsReg, 0); #endif handle_case(TypeTestABI::kFunctionTypeArgumentsReg); __ Bind(&is_assignable); __ LoadImmediate(kOutputReg, 0); __ Bind(&done); #if defined(TARGET_ARCH_IA32) // Restore preserved scratch registers. __ PopRegister(kScratchReg); #endif __ Ret(); } #if !defined(TARGET_ARCH_IA32) // The TypeTestStubs are used to test whether a given value is of a given // type. All variants have the same calling convention: // // Inputs (from TypeTestABI struct): // - kSubtypeTestCacheReg: RawSubtypeTestCache // - kInstanceReg: instance to test against. // - kInstantiatorTypeArgumentsReg : instantiator type arguments (if needed). // - kFunctionTypeArgumentsReg : function type arguments (if needed). // // See GenerateSubtypeNTestCacheStub for registers that may need saving by the // caller. // // Output (from TypeTestABI struct): // - kResultReg: checked instance. // // Throws if the check is unsuccessful. // // Note of warning: The caller will not populate CODE_REG and we have therefore // no access to the pool. void StubCodeCompiler::GenerateDefaultTypeTestStub() { __ LoadFromOffset(CODE_REG, THR, target::Thread::slow_type_test_stub_offset()); __ Jump(FieldAddress(CODE_REG, target::Code::entry_point_offset())); } // Used instead of DefaultTypeTestStub when null is assignable. void StubCodeCompiler::GenerateDefaultNullableTypeTestStub() { Label done; // Fast case for 'null'. __ CompareObject(TypeTestABI::kInstanceReg, NullObject()); __ BranchIf(EQUAL, &done); __ LoadFromOffset(CODE_REG, THR, target::Thread::slow_type_test_stub_offset()); __ Jump(FieldAddress(CODE_REG, target::Code::entry_point_offset())); __ Bind(&done); __ Ret(); } void StubCodeCompiler::GenerateTopTypeTypeTestStub() { __ Ret(); } void StubCodeCompiler::GenerateUnreachableTypeTestStub() { __ Breakpoint(); } static void BuildTypeParameterTypeTestStub(Assembler* assembler, bool allow_null) { Label done; if (allow_null) { __ CompareObject(TypeTestABI::kInstanceReg, NullObject()); __ BranchIf(EQUAL, &done, Assembler::kNearJump); } auto handle_case = [&](Register tav) { // If the TAV is null, then resolving the type parameter gives the dynamic // type, which is a top type. __ CompareObject(tav, NullObject()); __ BranchIf(EQUAL, &done, Assembler::kNearJump); // Resolve the type parameter to its instantiated type and tail call the // instantiated type's TTS. __ LoadFieldFromOffset(TypeTestABI::kScratchReg, TypeTestABI::kDstTypeReg, target::TypeParameter::index_offset(), kUnsignedTwoBytes); __ LoadIndexedCompressed(TypeTestABI::kScratchReg, tav, target::TypeArguments::types_offset(), TypeTestABI::kScratchReg); __ Jump(FieldAddress( TypeTestABI::kScratchReg, target::AbstractType::type_test_stub_entry_point_offset())); }; Label function_type_param; __ LoadFromSlot(TypeTestABI::kScratchReg, TypeTestABI::kDstTypeReg, Slot::AbstractType_flags()); __ BranchIfBit(TypeTestABI::kScratchReg, target::UntaggedTypeParameter::kIsFunctionTypeParameterBit, NOT_ZERO, &function_type_param, Assembler::kNearJump); handle_case(TypeTestABI::kInstantiatorTypeArgumentsReg); __ Bind(&function_type_param); handle_case(TypeTestABI::kFunctionTypeArgumentsReg); __ Bind(&done); __ Ret(); } void StubCodeCompiler::GenerateNullableTypeParameterTypeTestStub() { BuildTypeParameterTypeTestStub(assembler, /*allow_null=*/true); } void StubCodeCompiler::GenerateTypeParameterTypeTestStub() { BuildTypeParameterTypeTestStub(assembler, /*allow_null=*/false); } static void InvokeTypeCheckFromTypeTestStub(Assembler* assembler, TypeCheckMode mode) { __ PushObject(NullObject()); // Make room for result. __ PushRegistersInOrder({TypeTestABI::kInstanceReg, TypeTestABI::kDstTypeReg, TypeTestABI::kInstantiatorTypeArgumentsReg, TypeTestABI::kFunctionTypeArgumentsReg}); __ PushObject(NullObject()); __ PushRegister(TypeTestABI::kSubtypeTestCacheReg); __ PushImmediate(target::ToRawSmi(mode)); __ CallRuntime(kTypeCheckRuntimeEntry, 7); __ Drop(1); // mode __ PopRegister(TypeTestABI::kSubtypeTestCacheReg); __ Drop(1); // dst_name __ PopRegister(TypeTestABI::kFunctionTypeArgumentsReg); __ PopRegister(TypeTestABI::kInstantiatorTypeArgumentsReg); __ PopRegister(TypeTestABI::kDstTypeReg); __ PopRegister(TypeTestABI::kInstanceReg); __ Drop(1); // Discard return value. } void StubCodeCompiler::GenerateLazySpecializeTypeTestStub() { __ LoadFromOffset(CODE_REG, THR, target::Thread::lazy_specialize_type_test_stub_offset()); __ EnterStubFrame(); InvokeTypeCheckFromTypeTestStub(assembler, kTypeCheckFromLazySpecializeStub); __ LeaveStubFrame(); __ Ret(); } // Used instead of LazySpecializeTypeTestStub when null is assignable. void StubCodeCompiler::GenerateLazySpecializeNullableTypeTestStub() { Label done; __ CompareObject(TypeTestABI::kInstanceReg, NullObject()); __ BranchIf(EQUAL, &done); __ LoadFromOffset(CODE_REG, THR, target::Thread::lazy_specialize_type_test_stub_offset()); __ EnterStubFrame(); InvokeTypeCheckFromTypeTestStub(assembler, kTypeCheckFromLazySpecializeStub); __ LeaveStubFrame(); __ Bind(&done); __ Ret(); } void StubCodeCompiler::GenerateSlowTypeTestStub() { Label done, call_runtime; if (!FLAG_precompiled_mode) { __ LoadFromOffset(CODE_REG, THR, target::Thread::slow_type_test_stub_offset()); } __ EnterStubFrame(); // If the subtype-cache is null, it needs to be lazily-created by the runtime. __ CompareObject(TypeTestABI::kSubtypeTestCacheReg, NullObject()); __ BranchIf(EQUAL, &call_runtime); // Use the number of inputs used by the STC to determine which stub to call. Label call_2, call_3, call_4, call_6; __ Comment("Check number of STC inputs"); __ LoadFromSlot(TypeTestABI::kScratchReg, TypeTestABI::kSubtypeTestCacheReg, Slot::SubtypeTestCache_num_inputs()); __ CompareImmediate(TypeTestABI::kScratchReg, 2); __ BranchIf(EQUAL, &call_2, Assembler::kNearJump); __ CompareImmediate(TypeTestABI::kScratchReg, 3); __ BranchIf(EQUAL, &call_3, Assembler::kNearJump); __ CompareImmediate(TypeTestABI::kScratchReg, 4); __ BranchIf(EQUAL, &call_4, Assembler::kNearJump); __ CompareImmediate(TypeTestABI::kScratchReg, 6); __ BranchIf(EQUAL, &call_6, Assembler::kNearJump); // Fall through to the all inputs case. { __ Comment("Call 7 input STC check"); __ Call(StubCodeSubtype7TestCache()); __ CompareObject(TypeTestABI::kSubtypeTestCacheResultReg, CastHandle(TrueObject())); __ BranchIf(EQUAL, &done); // Cache said: yes. __ Jump(&call_runtime, Assembler::kNearJump); } __ Bind(&call_6); { __ Comment("Call 6 input STC check"); __ Call(StubCodeSubtype6TestCache()); __ CompareObject(TypeTestABI::kSubtypeTestCacheResultReg, CastHandle(TrueObject())); __ BranchIf(EQUAL, &done); // Cache said: yes. __ Jump(&call_runtime, Assembler::kNearJump); } __ Bind(&call_4); { __ Comment("Call 4 input STC check"); __ Call(StubCodeSubtype4TestCache()); __ CompareObject(TypeTestABI::kSubtypeTestCacheResultReg, CastHandle(TrueObject())); __ BranchIf(EQUAL, &done); // Cache said: yes. __ Jump(&call_runtime, Assembler::kNearJump); } __ Bind(&call_3); { __ Comment("Call 3 input STC check"); __ Call(StubCodeSubtype3TestCache()); __ CompareObject(TypeTestABI::kSubtypeTestCacheResultReg, CastHandle(TrueObject())); __ BranchIf(EQUAL, &done); // Cache said: yes. __ Jump(&call_runtime, Assembler::kNearJump); } __ Bind(&call_2); { __ Comment("Call 2 input STC check"); __ Call(StubCodeSubtype2TestCache()); __ CompareObject(TypeTestABI::kSubtypeTestCacheResultReg, CastHandle(TrueObject())); __ BranchIf(EQUAL, &done); // Cache said: yes. // Fall through to runtime_call } __ Bind(&call_runtime); __ Comment("Call runtime"); InvokeTypeCheckFromTypeTestStub(assembler, kTypeCheckFromSlowStub); __ Bind(&done); __ Comment("Done"); __ LeaveStubFrame(); __ Ret(); } #else // Type testing stubs are not implemented on IA32. #define GENERATE_BREAKPOINT_STUB(Name) \ void StubCodeCompiler::Generate##Name##Stub() { \ __ Breakpoint(); \ } VM_TYPE_TESTING_STUB_CODE_LIST(GENERATE_BREAKPOINT_STUB) #undef GENERATE_BREAKPOINT_STUB #endif // !defined(TARGET_ARCH_IA32) // Called for inline allocation of closure. // Input (preserved): // AllocateClosureABI::kFunctionReg: closure function. // AllocateClosureABI::kLengthAndFlagsReg: encoded length_and_flags. // AllocateClosureABI::kContextReg: closure context. // Output: // AllocateClosureABI::kResultReg: new allocated Closure object. // Clobbered: // AllocateClosureABI::kScratchReg void StubCodeCompiler::GenerateAllocateClosureStub(intptr_t num_elements) { const intptr_t instance_size = target::RoundedAllocationSize( target::Closure::InstanceSize(num_elements)); const Register result_reg = AllocateClosureABI::kResultReg; const Register scratch_reg = AllocateClosureABI::kScratchReg; __ EnsureHasClassIdInDEBUG(kFunctionCid, AllocateClosureABI::kFunctionReg, scratch_reg); if (!FLAG_use_slow_path && FLAG_inline_alloc) { Label slow_case; __ Comment("Inline allocation of uninitialized closure"); #if defined(DEBUG) // Need to account for the debug checks added by StoreToSlotNoBarrier. const auto distance = Assembler::kFarJump; #else const auto distance = Assembler::kNearJump; #endif __ TryAllocateObject(kClosureCid, instance_size, &slow_case, distance, result_reg, scratch_reg); __ Comment("Inline initialization of allocated closure"); // Put null in the scratch register for initializing most boxed fields. // We initialize the fields in offset order below. // Since the TryAllocateObject above did not go to the slow path, we're // guaranteed an object in new space here, and thus no barriers are needed. __ LoadObject(scratch_reg, NullObject()); __ StoreToSlotNoBarrier(AllocateClosureABI::kFunctionReg, result_reg, Slot::Closure_function()); __ StoreToSlotNoBarrier(AllocateClosureABI::kLengthAndFlagsReg, result_reg, Slot::Closure_length_and_flags()); for (intptr_t i = 0; i < num_elements - 1; ++i) { __ StoreCompressedIntoObjectNoBarrier( result_reg, FieldAddress(result_reg, target::Closure::element_offset(i)), scratch_reg); } __ StoreCompressedIntoObjectNoBarrier( result_reg, FieldAddress(result_reg, target::Closure::element_offset(num_elements - 1)), AllocateClosureABI::kContextReg); if (num_elements >= 2) { Label initialized; __ BranchIfBit( AllocateClosureABI::kLengthAndFlagsReg, UntaggedClosure::kHasDelayedTypeArgumentsBit + kSmiTagShift, ZERO, &initialized); __ LoadObject(scratch_reg, EmptyTypeArguments()); __ StoreCompressedIntoObjectNoBarrier( result_reg, FieldAddress(result_reg, target::Closure::element_offset( UntaggedClosure::kDelayedTypeArgumentsIndex)), scratch_reg); __ Bind(&initialized); } __ LoadImmediate(scratch_reg, target::ToRawSmi(0)); __ StoreToSlotNoBarrier(scratch_reg, result_reg, Slot::Closure_hash()); #if defined(DART_PRECOMPILER) && !defined(TARGET_ARCH_IA32) if (FLAG_precompiled_mode) { // Set the closure entry point in precompiled mode, either to the function // entry point in bare instructions mode or to 0 otherwise (to catch // misuse). __ LoadFromSlot(scratch_reg, AllocateClosureABI::kFunctionReg, Slot::Function_entry_point()); __ StoreToSlotNoBarrier(scratch_reg, result_reg, Slot::Closure_entry_point()); } #endif // result_reg: new object. __ Ret(); __ Bind(&slow_case); } __ Comment("Closure allocation via runtime"); __ EnterStubFrame(); __ PushObject(NullObject()); // Space on the stack for the return value. __ PushRegistersInOrder({AllocateClosureABI::kFunctionReg, AllocateClosureABI::kLengthAndFlagsReg, AllocateClosureABI::kContextReg}); __ CallRuntime(kAllocateClosureRuntimeEntry, 3); __ PopRegister(AllocateClosureABI::kContextReg); __ PopRegister(AllocateClosureABI::kLengthAndFlagsReg); __ PopRegister(AllocateClosureABI::kFunctionReg); __ PopRegister(AllocateClosureABI::kResultReg); ASSERT(target::WillAllocateNewOrRememberedObject(instance_size)); EnsureIsNewOrRemembered(); __ LeaveStubFrame(); // AllocateClosureABI::kResultReg: new object __ Ret(); if (FLAG_use_slow_path || !FLAG_inline_alloc) { // Make sure AllocateClosureN stubs have different code as // precompiler chokes on distinct stub Code objects with the same // (de-duplicated) instructions. __ LoadImmediate(scratch_reg, num_elements); } } void StubCodeCompiler::GenerateAllocateClosure1Stub() { GenerateAllocateClosureStub(1); } void StubCodeCompiler::GenerateAllocateClosure2Stub() { GenerateAllocateClosureStub(2); } void StubCodeCompiler::GenerateAllocateClosure3Stub() { GenerateAllocateClosureStub(3); } void StubCodeCompiler::GenerateAllocateClosure4Stub() { GenerateAllocateClosureStub(4); } // Generates allocation stub for _GrowableList class. // This stub exists solely for performance reasons: default allocation // stub is slower as it doesn't use specialized inline allocation. void StubCodeCompiler::GenerateAllocateGrowableArrayStub() { #if defined(TARGET_ARCH_IA32) // This stub is not used on IA32 because IA32 version of // StubCodeCompiler::GenerateAllocationStubForClass uses inline // allocation. Also, AllocateObjectSlow stub is not generated on IA32. __ Breakpoint(); #else const intptr_t instance_size = target::RoundedAllocationSize( target::GrowableObjectArray::InstanceSize()); if (!FLAG_use_slow_path && FLAG_inline_alloc) { Label slow_case; __ Comment("Inline allocation of GrowableList"); __ TryAllocateObject(kGrowableObjectArrayCid, instance_size, &slow_case, Assembler::kNearJump, AllocateObjectABI::kResultReg, /*temp=*/AllocateObjectABI::kTagsReg); __ StoreIntoObjectNoBarrier( AllocateObjectABI::kResultReg, FieldAddress(AllocateObjectABI::kResultReg, target::GrowableObjectArray::type_arguments_offset()), AllocateObjectABI::kTypeArgumentsReg); __ Ret(); __ Bind(&slow_case); } const uword tags = target::MakeTagWordForNewSpaceObject( kGrowableObjectArrayCid, instance_size); __ LoadImmediate(AllocateObjectABI::kTagsReg, tags); __ Jump( Address(THR, target::Thread::allocate_object_slow_entry_point_offset())); #endif // defined(TARGET_ARCH_IA32) } void StubCodeCompiler::GenerateAllocateRecordStub() { const Register result_reg = AllocateRecordABI::kResultReg; const Register shape_reg = AllocateRecordABI::kShapeReg; const Register temp_reg = AllocateRecordABI::kTemp1Reg; const Register new_top_reg = AllocateRecordABI::kTemp2Reg; if (!FLAG_use_slow_path && FLAG_inline_alloc) { Label slow_case; // Check for allocation tracing. NOT_IN_PRODUCT(__ MaybeTraceAllocation(kRecordCid, &slow_case, temp_reg)); // Extract number of fields from the shape. __ AndImmediate( temp_reg, shape_reg, compiler::target::RecordShape::kNumFieldsMask << kSmiTagShift); // Compute the rounded instance size. const intptr_t fixed_size_plus_alignment_padding = (target::Record::field_offset(0) + target::ObjectAlignment::kObjectAlignment - 1); __ AddScaled(temp_reg, kNoRegister, temp_reg, TIMES_COMPRESSED_HALF_WORD_SIZE, fixed_size_plus_alignment_padding); __ AndImmediate(temp_reg, -target::ObjectAlignment::kObjectAlignment); // Now allocate the object. __ LoadFromOffset(result_reg, THR, target::Thread::top_offset()); __ MoveRegister(new_top_reg, temp_reg); __ AddRegisters(new_top_reg, result_reg); // Check if the allocation fits into the remaining space. __ CompareWithMemoryValue(new_top_reg, Address(THR, target::Thread::end_offset())); __ BranchIf(UNSIGNED_GREATER_EQUAL, &slow_case); __ CheckAllocationCanary(result_reg); // Successfully allocated the object, now update top to point to // next object start and initialize the object. __ StoreToOffset(new_top_reg, THR, target::Thread::top_offset()); __ AddImmediate(result_reg, kHeapObjectTag); // Calculate the size tag. { Label size_tag_overflow, done; __ CompareImmediate(temp_reg, target::UntaggedObject::kSizeTagMaxSizeTag); __ BranchIf(UNSIGNED_GREATER, &size_tag_overflow, Assembler::kNearJump); __ LslImmediate(temp_reg, target::UntaggedObject::kSizeTagPos - target::ObjectAlignment::kObjectAlignmentLog2); __ Jump(&done, Assembler::kNearJump); __ Bind(&size_tag_overflow); // Set overflow size tag value. __ LoadImmediate(temp_reg, 0); __ Bind(&done); uword tags = target::MakeTagWordForNewSpaceObject(kRecordCid, 0); __ OrImmediate(temp_reg, tags); __ InitializeHeader(temp_reg, result_reg); } __ StoreCompressedIntoObjectNoBarrier( result_reg, FieldAddress(result_reg, target::Record::shape_offset()), shape_reg); // Initialize the remaining words of the object. { const Register field_reg = shape_reg; #if defined(TARGET_ARCH_ARM64) || defined(TARGET_ARCH_RISCV32) || \ defined(TARGET_ARCH_RISCV64) const Register null_reg = NULL_REG; #else const Register null_reg = temp_reg; __ LoadObject(null_reg, NullObject()); #endif Label loop, done; __ AddImmediate(field_reg, result_reg, target::Record::field_offset(0)); __ CompareRegisters(field_reg, new_top_reg); __ BranchIf(UNSIGNED_GREATER_EQUAL, &done, Assembler::kNearJump); __ Bind(&loop); for (intptr_t offset = 0; offset < target::kObjectAlignment; offset += target::kCompressedWordSize) { __ StoreCompressedIntoObjectNoBarrier( result_reg, FieldAddress(field_reg, offset), null_reg); } // Safe to only check every kObjectAlignment bytes instead of each word. ASSERT(kAllocationRedZoneSize >= target::kObjectAlignment); __ AddImmediate(field_reg, target::kObjectAlignment); __ CompareRegisters(field_reg, new_top_reg); __ BranchIf(UNSIGNED_LESS, &loop, Assembler::kNearJump); __ Bind(&done); } __ WriteAllocationCanary(new_top_reg); // Fix overshoot. __ Ret(); __ Bind(&slow_case); } __ EnterStubFrame(); __ PushObject(NullObject()); // Space on the stack for the return value. __ PushRegister(shape_reg); __ CallRuntime(kAllocateRecordRuntimeEntry, 1); __ Drop(1); __ PopRegister(AllocateRecordABI::kResultReg); EnsureIsNewOrRemembered(); __ LeaveStubFrame(); __ Ret(); } void StubCodeCompiler::GenerateAllocateSmallRecordStub(intptr_t num_fields, bool has_named_fields) { ASSERT(num_fields == 2 || num_fields == 3); const Register result_reg = AllocateSmallRecordABI::kResultReg; const Register shape_reg = AllocateSmallRecordABI::kShapeReg; const Register value0_reg = AllocateSmallRecordABI::kValue0Reg; const Register value1_reg = AllocateSmallRecordABI::kValue1Reg; const Register value2_reg = AllocateSmallRecordABI::kValue2Reg; const Register temp_reg = AllocateSmallRecordABI::kTempReg; Label slow_case; if ((num_fields > 2) && (value2_reg == kNoRegister)) { // Not implemented. __ Breakpoint(); return; } #if defined(DEBUG) // Need to account for the debug checks added by // StoreCompressedIntoObjectNoBarrier. const auto distance = Assembler::kFarJump; #else const auto distance = Assembler::kNearJump; #endif __ TryAllocateObject(kRecordCid, target::Record::InstanceSize(num_fields), &slow_case, distance, result_reg, temp_reg); if (!has_named_fields) { __ LoadImmediate( shape_reg, Smi::RawValue(RecordShape::ForUnnamed(num_fields).AsInt())); } __ StoreCompressedIntoObjectNoBarrier( result_reg, FieldAddress(result_reg, target::Record::shape_offset()), shape_reg); __ StoreCompressedIntoObjectNoBarrier( result_reg, FieldAddress(result_reg, target::Record::field_offset(0)), value0_reg); __ StoreCompressedIntoObjectNoBarrier( result_reg, FieldAddress(result_reg, target::Record::field_offset(1)), value1_reg); if (num_fields > 2) { __ StoreCompressedIntoObjectNoBarrier( result_reg, FieldAddress(result_reg, target::Record::field_offset(2)), value2_reg); } __ Ret(); __ Bind(&slow_case); __ EnterStubFrame(); __ PushObject(NullObject()); // Space on the stack for the return value. if (has_named_fields) { __ PushRegister(shape_reg); } else { __ PushImmediate( Smi::RawValue(RecordShape::ForUnnamed(num_fields).AsInt())); } __ PushRegistersInOrder({value0_reg, value1_reg}); if (num_fields > 2) { __ PushRegister(value2_reg); } else { __ PushObject(NullObject()); } __ CallRuntime(kAllocateSmallRecordRuntimeEntry, 4); __ Drop(4); __ PopRegister(result_reg); EnsureIsNewOrRemembered(); __ LeaveStubFrame(); __ Ret(); } void StubCodeCompiler::GenerateAllocateRecord2Stub() { GenerateAllocateSmallRecordStub(2, /*has_named_fields=*/false); } void StubCodeCompiler::GenerateAllocateRecord2NamedStub() { GenerateAllocateSmallRecordStub(2, /*has_named_fields=*/true); } void StubCodeCompiler::GenerateAllocateRecord3Stub() { GenerateAllocateSmallRecordStub(3, /*has_named_fields=*/false); } void StubCodeCompiler::GenerateAllocateRecord3NamedStub() { GenerateAllocateSmallRecordStub(3, /*has_named_fields=*/true); } // The UnhandledException class lives in the VM isolate, so it cannot cache // an allocation stub for itself. Instead, we cache it in the stub code list. void StubCodeCompiler::GenerateAllocateUnhandledExceptionStub() { Thread* thread = Thread::Current(); auto class_table = thread->isolate_group()->class_table(); ASSERT(class_table->HasValidClassAt(kUnhandledExceptionCid)); const auto& cls = Class::ZoneHandle(thread->zone(), class_table->At(kUnhandledExceptionCid)); ASSERT(!cls.IsNull()); GenerateAllocationStubForClass(nullptr, cls, Code::Handle(Code::null()), Code::Handle(Code::null())); } #define TYPED_DATA_ALLOCATION_STUB(clazz) \ void StubCodeCompiler::GenerateAllocate##clazz##Stub() { \ GenerateAllocateTypedDataArrayStub(kTypedData##clazz##Cid); \ } CLASS_LIST_TYPED_DATA(TYPED_DATA_ALLOCATION_STUB) #undef TYPED_DATA_ALLOCATION_STUB void StubCodeCompiler::GenerateLateInitializationError(bool with_fpu_regs) { auto perform_runtime_call = [&]() { __ PushRegister(LateInitializationErrorABI::kFieldReg); __ CallRuntime(kLateFieldNotInitializedErrorRuntimeEntry, /*argument_count=*/1); }; GenerateSharedStubGeneric( /*save_fpu_registers=*/with_fpu_regs, with_fpu_regs ? target::Thread:: late_initialization_error_shared_with_fpu_regs_stub_offset() : target::Thread:: late_initialization_error_shared_without_fpu_regs_stub_offset(), /*allow_return=*/false, perform_runtime_call); } void StubCodeCompiler:: GenerateLateInitializationErrorSharedWithoutFPURegsStub() { GenerateLateInitializationError(/*with_fpu_regs=*/false); } void StubCodeCompiler::GenerateLateInitializationErrorSharedWithFPURegsStub() { GenerateLateInitializationError(/*with_fpu_regs=*/true); } void StubCodeCompiler::GenerateNullErrorSharedWithoutFPURegsStub() { GenerateSharedStub( /*save_fpu_registers=*/false, &kNullErrorRuntimeEntry, target::Thread::null_error_shared_without_fpu_regs_stub_offset(), /*allow_return=*/false); } void StubCodeCompiler::GenerateNullErrorSharedWithFPURegsStub() { GenerateSharedStub( /*save_fpu_registers=*/true, &kNullErrorRuntimeEntry, target::Thread::null_error_shared_with_fpu_regs_stub_offset(), /*allow_return=*/false); } void StubCodeCompiler::GenerateNullArgErrorSharedWithoutFPURegsStub() { GenerateSharedStub( /*save_fpu_registers=*/false, &kArgumentNullErrorRuntimeEntry, target::Thread::null_arg_error_shared_without_fpu_regs_stub_offset(), /*allow_return=*/false); } void StubCodeCompiler::GenerateNullArgErrorSharedWithFPURegsStub() { GenerateSharedStub( /*save_fpu_registers=*/true, &kArgumentNullErrorRuntimeEntry, target::Thread::null_arg_error_shared_with_fpu_regs_stub_offset(), /*allow_return=*/false); } void StubCodeCompiler::GenerateNullCastErrorSharedWithoutFPURegsStub() { GenerateSharedStub( /*save_fpu_registers=*/false, &kNullCastErrorRuntimeEntry, target::Thread::null_cast_error_shared_without_fpu_regs_stub_offset(), /*allow_return=*/false); } void StubCodeCompiler::GenerateNullCastErrorSharedWithFPURegsStub() { GenerateSharedStub( /*save_fpu_registers=*/true, &kNullCastErrorRuntimeEntry, target::Thread::null_cast_error_shared_with_fpu_regs_stub_offset(), /*allow_return=*/false); } void StubCodeCompiler::GenerateStackOverflowSharedWithoutFPURegsStub() { GenerateSharedStub( /*save_fpu_registers=*/false, &kInterruptOrStackOverflowRuntimeEntry, target::Thread::stack_overflow_shared_without_fpu_regs_stub_offset(), /*allow_return=*/true); } void StubCodeCompiler::GenerateStackOverflowSharedWithFPURegsStub() { GenerateSharedStub( /*save_fpu_registers=*/true, &kInterruptOrStackOverflowRuntimeEntry, target::Thread::stack_overflow_shared_with_fpu_regs_stub_offset(), /*allow_return=*/true); } void StubCodeCompiler::GenerateRangeErrorSharedWithoutFPURegsStub() { GenerateRangeError(/*with_fpu_regs=*/false); } void StubCodeCompiler::GenerateRangeErrorSharedWithFPURegsStub() { GenerateRangeError(/*with_fpu_regs=*/true); } void StubCodeCompiler::GenerateWriteErrorSharedWithoutFPURegsStub() { GenerateWriteError(/*with_fpu_regs=*/false); } void StubCodeCompiler::GenerateWriteErrorSharedWithFPURegsStub() { GenerateWriteError(/*with_fpu_regs=*/true); } void StubCodeCompiler::GenerateFieldAccessError(bool with_fpu_regs) { auto perform_runtime_call = [&]() { __ PushRegister(FieldAccessErrorABI::kFieldReg); __ CallRuntime(kStaticFieldAccessedWithoutIsolateErrorRuntimeEntry, /*argument_count=*/1); }; GenerateSharedStubGeneric( /*save_fpu_registers=*/with_fpu_regs, with_fpu_regs ? target::Thread:: field_access_error_shared_with_fpu_regs_stub_offset() : target::Thread:: field_access_error_shared_without_fpu_regs_stub_offset(), /*allow_return=*/false, perform_runtime_call); } void StubCodeCompiler::GenerateFieldAccessErrorSharedWithoutFPURegsStub() { GenerateFieldAccessError(/*with_fpu_regs=*/false); } void StubCodeCompiler::GenerateFieldAccessErrorSharedWithFPURegsStub() { GenerateFieldAccessError(/*with_fpu_regs=*/true); } void StubCodeCompiler::GenerateFrameAwaitingMaterializationStub() { __ Breakpoint(); // Marker stub. } void StubCodeCompiler::GenerateAsynchronousGapMarkerStub() { __ Breakpoint(); // Marker stub. } void StubCodeCompiler::GenerateUnknownDartCodeStub() { // Enter frame to include caller into the backtrace. __ EnterStubFrame(); __ Breakpoint(); // Marker stub. } void StubCodeCompiler::GenerateNotLoadedStub() { __ EnterStubFrame(); __ CallRuntime(kNotLoadedRuntimeEntry, 0); __ Breakpoint(); } #define EMIT_BOX_ALLOCATION(Name) \ void StubCodeCompiler::GenerateAllocate##Name##Stub() { \ Label call_runtime; \ if (!FLAG_use_slow_path && FLAG_inline_alloc) { \ __ TryAllocate(compiler::Name##Class(), &call_runtime, \ Assembler::kNearJump, AllocateBoxABI::kResultReg, \ AllocateBoxABI::kTempReg); \ __ Ret(); \ } \ __ Bind(&call_runtime); \ __ EnterStubFrame(); \ __ PushObject(NullObject()); /* Make room for result. */ \ __ CallRuntime(kAllocate##Name##RuntimeEntry, 0); \ __ PopRegister(AllocateBoxABI::kResultReg); \ __ LeaveStubFrame(); \ __ Ret(); \ } EMIT_BOX_ALLOCATION(Mint) EMIT_BOX_ALLOCATION(Double) EMIT_BOX_ALLOCATION(Float32x4) EMIT_BOX_ALLOCATION(Float64x2) EMIT_BOX_ALLOCATION(Int32x4) #undef EMIT_BOX_ALLOCATION static void GenerateBoxFpuValueStub(Assembler* assembler, const dart::Class& cls, const RuntimeEntry& runtime_entry, void (Assembler::*store_value)(FpuRegister, Register, int32_t)) { Label call_runtime; if (!FLAG_use_slow_path && FLAG_inline_alloc) { __ TryAllocate(cls, &call_runtime, compiler::Assembler::kFarJump, BoxDoubleStubABI::kResultReg, BoxDoubleStubABI::kTempReg); (assembler->*store_value)( BoxDoubleStubABI::kValueReg, BoxDoubleStubABI::kResultReg, compiler::target::Double::value_offset() - kHeapObjectTag); __ Ret(); } __ Bind(&call_runtime); __ EnterStubFrame(); __ PushObject(NullObject()); /* Make room for result. */ (assembler->*store_value)(BoxDoubleStubABI::kValueReg, THR, target::Thread::unboxed_runtime_arg_offset()); __ CallRuntime(runtime_entry, 0); __ PopRegister(BoxDoubleStubABI::kResultReg); __ LeaveStubFrame(); __ Ret(); } void StubCodeCompiler::GenerateBoxDoubleStub() { GenerateBoxFpuValueStub(assembler, compiler::DoubleClass(), kBoxDoubleRuntimeEntry, &Assembler::StoreUnboxedDouble); } void StubCodeCompiler::GenerateBoxFloat32x4Stub() { #if !defined(TARGET_ARCH_RISCV32) && !defined(TARGET_ARCH_RISCV64) GenerateBoxFpuValueStub(assembler, compiler::Float32x4Class(), kBoxFloat32x4RuntimeEntry, &Assembler::StoreUnboxedSimd128); #else __ Stop("Not supported on RISC-V."); #endif } void StubCodeCompiler::GenerateBoxFloat64x2Stub() { #if !defined(TARGET_ARCH_RISCV32) && !defined(TARGET_ARCH_RISCV64) GenerateBoxFpuValueStub(assembler, compiler::Float64x2Class(), kBoxFloat64x2RuntimeEntry, &Assembler::StoreUnboxedSimd128); #else __ Stop("Not supported on RISC-V."); #endif } void StubCodeCompiler::GenerateDoubleToIntegerStub() { __ EnterStubFrame(); __ StoreUnboxedDouble(DoubleToIntegerStubABI::kInputReg, THR, target::Thread::unboxed_runtime_arg_offset()); __ PushObject(NullObject()); /* Make room for result. */ __ PushRegister(DoubleToIntegerStubABI::kRecognizedKindReg); __ CallRuntime(kDoubleToIntegerRuntimeEntry, 1); __ Drop(1); __ PopRegister(DoubleToIntegerStubABI::kResultReg); __ LeaveStubFrame(); __ Ret(); } void StubCodeCompiler::GenerateCheckedStoreIntoSharedStub() { __ EnterStubFrame(); __ PushObject(NullObject()); // Make room for result. __ PushRegistersInOrder({CheckedStoreIntoSharedStubABI::kFieldReg, CheckedStoreIntoSharedStubABI::kValueReg}); __ CallRuntime(kCheckedStoreIntoSharedRuntimeEntry, /*argument_count=*/2); __ Drop(2); __ PopRegister(CheckedStoreIntoSharedStubABI::kResultReg); __ LeaveStubFrame(); __ Ret(); } void StubCodeCompiler::GenerateEnsureDeeplyImmutableStub() { __ EnterStubFrame(); __ PushRegister(EnsureDeeplyImmutableStubABI::kValueReg); __ CallRuntime(kEnsureDeeplyImmutableRuntimeEntry, /*argument_count=*/1); __ Drop(1); __ LeaveStubFrame(); __ Ret(); } static intptr_t SuspendStateFpOffset() { return compiler::target::frame_layout.FrameSlotForVariableIndex( SuspendState::kSuspendStateVarIndex) * compiler::target::kWordSize; } static void CallDartCoreLibraryFunction( Assembler* assembler, intptr_t entry_point_offset_in_thread, intptr_t function_offset_in_object_store, bool uses_args_desc = false) { if (FLAG_target_thread_sanitizer) { __ TsanFuncEntry(); } if (FLAG_precompiled_mode) { __ Call(Address(THR, entry_point_offset_in_thread)); } else { __ LoadIsolateGroup(FUNCTION_REG); __ LoadFromOffset(FUNCTION_REG, FUNCTION_REG, target::IsolateGroup::object_store_offset()); __ LoadFromOffset(FUNCTION_REG, FUNCTION_REG, function_offset_in_object_store); __ LoadCompressedFieldFromOffset(CODE_REG, FUNCTION_REG, target::Function::code_offset()); if (!uses_args_desc) { // Load a GC-safe value for the arguments descriptor (unused but tagged). __ LoadImmediate(ARGS_DESC_REG, 0); } __ Call(FieldAddress(FUNCTION_REG, target::Function::entry_point_offset())); } if (FLAG_target_thread_sanitizer) { __ TsanFuncExit(); } } // Helper to generate allocation of _SuspendState instance. // Initializes tags, frame_capacity and frame_size. // Other fields are not initialized. // // Input: // frame_size_reg: size of the frame payload in bytes. // Output: // result_reg: allocated instance. // Clobbers: // result_reg, temp_reg. static void GenerateAllocateSuspendState(Assembler* assembler, Label* slow_case, Register result_reg, Register frame_size_reg, Register temp_reg) { if (FLAG_use_slow_path || !FLAG_inline_alloc) { __ Jump(slow_case); return; } // Check for allocation tracing. NOT_IN_PRODUCT( __ MaybeTraceAllocation(kSuspendStateCid, slow_case, temp_reg)); // Compute the rounded instance size. const intptr_t fixed_size_plus_alignment_padding = (target::SuspendState::HeaderSize() + target::SuspendState::FrameSizeGrowthGap() * target::kWordSize + target::ObjectAlignment::kObjectAlignment - 1); __ AddImmediate(temp_reg, frame_size_reg, fixed_size_plus_alignment_padding); __ AndImmediate(temp_reg, -target::ObjectAlignment::kObjectAlignment); // Now allocate the object. __ LoadFromOffset(result_reg, THR, target::Thread::top_offset()); __ AddRegisters(temp_reg, result_reg); // Check if the allocation fits into the remaining space. __ CompareWithMemoryValue(temp_reg, Address(THR, target::Thread::end_offset())); __ BranchIf(UNSIGNED_GREATER_EQUAL, slow_case); __ CheckAllocationCanary(result_reg); // Successfully allocated the object, now update top to point to // next object start and initialize the object. __ StoreToOffset(temp_reg, THR, target::Thread::top_offset()); __ SubRegisters(temp_reg, result_reg); __ AddImmediate(result_reg, kHeapObjectTag); if (!FLAG_precompiled_mode) { // Use rounded object size to calculate and save frame capacity. __ AddImmediate(temp_reg, temp_reg, -target::SuspendState::payload_offset()); __ StoreFieldToOffset(temp_reg, result_reg, target::SuspendState::frame_capacity_offset()); // Restore rounded object size. __ AddImmediate(temp_reg, temp_reg, target::SuspendState::payload_offset()); } // Calculate the size tag. { Label size_tag_overflow, done; __ CompareImmediate(temp_reg, target::UntaggedObject::kSizeTagMaxSizeTag); __ BranchIf(UNSIGNED_GREATER, &size_tag_overflow, Assembler::kNearJump); __ LslImmediate(temp_reg, target::UntaggedObject::kSizeTagPos - target::ObjectAlignment::kObjectAlignmentLog2); __ Jump(&done, Assembler::kNearJump); __ Bind(&size_tag_overflow); // Set overflow size tag value. __ LoadImmediate(temp_reg, 0); __ Bind(&done); uword tags = target::MakeTagWordForNewSpaceObject(kSuspendStateCid, 0); __ OrImmediate(temp_reg, tags); __ InitializeHeader(temp_reg, result_reg); } __ StoreFieldToOffset(frame_size_reg, result_reg, target::SuspendState::frame_size_offset()); } void StubCodeCompiler::GenerateSuspendStub( bool call_suspend_function, bool pass_type_arguments, intptr_t suspend_entry_point_offset_in_thread, intptr_t suspend_function_offset_in_object_store) { const Register kArgument = SuspendStubABI::kArgumentReg; const Register kTypeArgs = SuspendStubABI::kTypeArgsReg; const Register kTemp = SuspendStubABI::kTempReg; const Register kFrameSize = SuspendStubABI::kFrameSizeReg; const Register kSuspendState = SuspendStubABI::kSuspendStateReg; const Register kFunctionData = SuspendStubABI::kFunctionDataReg; const Register kSrcFrame = SuspendStubABI::kSrcFrameReg; const Register kDstFrame = SuspendStubABI::kDstFrameReg; Label alloc_slow_case, alloc_done, init_done, resize_suspend_state, remember_object, call_dart; #if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64) SPILLS_LR_TO_FRAME({}); // Simulate entering the caller (Dart) frame. #endif __ LoadFromOffset(kSuspendState, FPREG, SuspendStateFpOffset()); __ AddImmediate( kFrameSize, FPREG, -target::frame_layout.last_param_from_entry_sp * target::kWordSize); __ SubRegisters(kFrameSize, SPREG); __ EnterStubFrame(); if (pass_type_arguments) { __ PushRegister(kTypeArgs); } __ CompareClassId(kSuspendState, kSuspendStateCid, kTemp); if (FLAG_precompiled_mode) { __ BranchIf(EQUAL, &init_done); } else { Label alloc_suspend_state; __ BranchIf(NOT_EQUAL, &alloc_suspend_state); __ CompareWithMemoryValue( kFrameSize, FieldAddress(kSuspendState, target::SuspendState::frame_capacity_offset())); __ BranchIf(UNSIGNED_GREATER, &resize_suspend_state); __ StoreFieldToOffset(kFrameSize, kSuspendState, target::SuspendState::frame_size_offset()); __ Jump(&init_done); __ Bind(&alloc_suspend_state); } __ Comment("Allocate SuspendState"); __ MoveRegister(kFunctionData, kSuspendState); GenerateAllocateSuspendState(assembler, &alloc_slow_case, kSuspendState, kFrameSize, kTemp); __ StoreCompressedIntoObjectNoBarrier( kSuspendState, FieldAddress(kSuspendState, target::SuspendState::function_data_offset()), kFunctionData); { #if defined(TARGET_ARCH_ARM64) || defined(TARGET_ARCH_RISCV32) || \ defined(TARGET_ARCH_RISCV64) const Register kNullReg = NULL_REG; #else const Register kNullReg = kTemp; __ LoadObject(kNullReg, NullObject()); #endif __ StoreCompressedIntoObjectNoBarrier( kSuspendState, FieldAddress(kSuspendState, target::SuspendState::then_callback_offset()), kNullReg); __ StoreCompressedIntoObjectNoBarrier( kSuspendState, FieldAddress(kSuspendState, target::SuspendState::error_callback_offset()), kNullReg); } __ Bind(&alloc_done); __ Comment("Save SuspendState to frame"); __ LoadFromOffset(kTemp, FPREG, kSavedCallerFpSlotFromFp * target::kWordSize); __ StoreToOffset(kSuspendState, kTemp, SuspendStateFpOffset()); __ Bind(&init_done); __ Comment("Copy frame to SuspendState"); #ifdef DEBUG { // Verify that SuspendState.frame_size == kFrameSize. Label okay; __ LoadFieldFromOffset(kTemp, kSuspendState, target::SuspendState::frame_size_offset()); __ CompareRegisters(kTemp, kFrameSize); __ BranchIf(EQUAL, &okay); __ Breakpoint(); __ Bind(&okay); } #endif if (kSrcFrame == THR) { __ PushRegister(THR); } __ AddImmediate(kSrcFrame, FPREG, kCallerSpSlotFromFp * target::kWordSize); __ AddImmediate(kDstFrame, kSuspendState, target::SuspendState::payload_offset() - kHeapObjectTag); __ CopyMemoryWords(kSrcFrame, kDstFrame, kFrameSize, kTemp); if (kSrcFrame == THR) { __ PopRegister(THR); } __ LoadFromOffset(kTemp, FPREG, kSavedCallerPcSlotFromFp * target::kWordSize); __ StoreFieldToOffset(kTemp, kSuspendState, target::SuspendState::pc_offset()); #ifdef DEBUG { // Verify that kSuspendState matches :suspend_state in the copied stack // frame. Label okay; __ LoadFieldFromOffset(kTemp, kSuspendState, target::SuspendState::frame_size_offset()); __ AddRegisters(kTemp, kSuspendState); __ LoadFieldFromOffset( kTemp, kTemp, target::SuspendState::payload_offset() + SuspendStateFpOffset()); __ CompareRegisters(kTemp, kSuspendState); __ BranchIf(EQUAL, &okay); __ Breakpoint(); __ Bind(&okay); } #endif if (call_suspend_function) { // Push arguments for suspend Dart function early to preserve them // across write barrier. __ PushRegistersInOrder({kSuspendState, kArgument}); } // Write barrier. __ AndImmediate(kTemp, kSuspendState, target::Page::kPageMask); __ LoadFromOffset(kTemp, kTemp, target::Page::original_top_offset()); __ CompareRegisters(kSuspendState, kTemp); __ BranchIf(UNSIGNED_LESS, &remember_object); // Assumption: SuspendStates are always on non-image pages. // TODO(rmacnak): Also check original_end if we bound TLABs to smaller than a // heap page. __ Bind(&call_dart); if (call_suspend_function) { __ Comment("Call suspend Dart function"); if (pass_type_arguments) { __ LoadObject(ARGS_DESC_REG, ArgumentsDescriptorBoxed(/*type_args_len=*/1, /*num_arguments=*/2)); } CallDartCoreLibraryFunction(assembler, suspend_entry_point_offset_in_thread, suspend_function_offset_in_object_store, /*uses_args_desc=*/pass_type_arguments); } else { // SuspendStub returns either the result of Dart callback, // or SuspendStub argument (if Dart callback is not used). // The latter is used by yield/yield* in sync* functions // to indicate that iteration should be continued. __ MoveRegister(CallingConventions::kReturnReg, kArgument); } __ LeaveStubFrame(); if (FLAG_target_thread_sanitizer) { __ TsanFuncExit(); } #if !defined(TARGET_ARCH_X64) && !defined(TARGET_ARCH_IA32) // Drop caller frame on all architectures except x86 (X64/IA32) which // needs to maintain call/return balance to avoid performance regressions. __ LeaveDartFrame(); #elif defined(TARGET_ARCH_X64) // Restore PP in JIT mode on x64 as epilogue following SuspendStub call // will only unwind frame and return. if (!FLAG_precompiled_mode) { __ LoadFromOffset( PP, FPREG, target::frame_layout.saved_caller_pp_from_fp * target::kWordSize); } #endif __ Ret(); #if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64) // Slow path is executed with Dart and stub frames still on the stack. SPILLS_LR_TO_FRAME({}); SPILLS_LR_TO_FRAME({}); #endif __ Bind(&alloc_slow_case); __ Comment("SuspendState Allocation slow case"); // Save argument and frame size. __ PushRegistersInOrder({kArgument, kFrameSize}); __ PushObject(NullObject()); // Make space on stack for the return value. __ SmiTag(kFrameSize); // Pass frame size and function data to runtime entry. __ PushRegistersInOrder({kFrameSize, kFunctionData}); __ CallRuntime(kAllocateSuspendStateRuntimeEntry, 2); __ Drop(2); // Drop arguments __ PopRegister(kSuspendState); // Get result. __ PopRegister(kFrameSize); // Restore frame size. __ PopRegister(kArgument); // Restore argument. __ Jump(&alloc_done); __ Bind(&resize_suspend_state); __ Comment("Resize SuspendState"); // Save argument and frame size. __ PushRegistersInOrder({kArgument, kFrameSize}); __ PushObject(NullObject()); // Make space on stack for the return value. __ SmiTag(kFrameSize); // Pass frame size and old suspend state to runtime entry. __ PushRegistersInOrder({kFrameSize, kSuspendState}); // It's okay to call runtime for resizing SuspendState objects // as it can only happen in the unoptimized code if expression // stack grows between suspends, or once after OSR transition. __ CallRuntime(kAllocateSuspendStateRuntimeEntry, 2); __ Drop(2); // Drop arguments __ PopRegister(kSuspendState); // Get result. __ PopRegister(kFrameSize); // Restore frame size. __ PopRegister(kArgument); // Restore argument. __ Jump(&alloc_done); __ Bind(&remember_object); __ Comment("Old gen SuspendState slow case"); if (!call_suspend_function) { // Save kArgument which contains the return value // if suspend function is not called. __ PushRegister(kArgument); } { #if defined(TARGET_ARCH_IA32) LeafRuntimeScope rt(assembler, /*frame_size=*/2 * target::kWordSize, /*preserve_registers=*/false); __ movl(Address(ESP, 1 * target::kWordSize), THR); __ movl(Address(ESP, 0 * target::kWordSize), kSuspendState); #else LeafRuntimeScope rt(assembler, /*frame_size=*/0, /*preserve_registers=*/false); __ MoveRegister(CallingConventions::ArgumentRegisters[0], kSuspendState); __ MoveRegister(CallingConventions::ArgumentRegisters[1], THR); #endif rt.Call(kEnsureRememberedAndMarkingDeferredRuntimeEntry, 2); } if (!call_suspend_function) { __ PopRegister(kArgument); } __ Jump(&call_dart); } void StubCodeCompiler::GenerateAwaitStub() { GenerateSuspendStub( /*call_suspend_function=*/true, /*pass_type_arguments=*/false, target::Thread::suspend_state_await_entry_point_offset(), target::ObjectStore::suspend_state_await_offset()); } void StubCodeCompiler::GenerateAwaitWithTypeCheckStub() { GenerateSuspendStub( /*call_suspend_function=*/true, /*pass_type_arguments=*/true, target::Thread::suspend_state_await_with_type_check_entry_point_offset(), target::ObjectStore::suspend_state_await_with_type_check_offset()); } void StubCodeCompiler::GenerateYieldAsyncStarStub() { GenerateSuspendStub( /*call_suspend_function=*/true, /*pass_type_arguments=*/false, target::Thread::suspend_state_yield_async_star_entry_point_offset(), target::ObjectStore::suspend_state_yield_async_star_offset()); } void StubCodeCompiler::GenerateSuspendSyncStarAtStartStub() { GenerateSuspendStub( /*call_suspend_function=*/true, /*pass_type_arguments=*/false, target::Thread:: suspend_state_suspend_sync_star_at_start_entry_point_offset(), target::ObjectStore::suspend_state_suspend_sync_star_at_start_offset()); } void StubCodeCompiler::GenerateSuspendSyncStarAtYieldStub() { GenerateSuspendStub( /*call_suspend_function=*/false, /*pass_type_arguments=*/false, -1, -1); } void StubCodeCompiler::GenerateInitSuspendableFunctionStub( intptr_t init_entry_point_offset_in_thread, intptr_t init_function_offset_in_object_store) { const Register kTypeArgs = InitSuspendableFunctionStubABI::kTypeArgsReg; __ EnterStubFrame(); __ LoadObject(ARGS_DESC_REG, ArgumentsDescriptorBoxed(/*type_args_len=*/1, /*num_arguments=*/0)); __ PushRegister(kTypeArgs); CallDartCoreLibraryFunction(assembler, init_entry_point_offset_in_thread, init_function_offset_in_object_store, /*uses_args_desc=*/true); __ LeaveStubFrame(); // Set :suspend_state in the caller frame. __ StoreToOffset(CallingConventions::kReturnReg, FPREG, SuspendStateFpOffset()); __ Ret(); } void StubCodeCompiler::GenerateInitAsyncStub() { GenerateInitSuspendableFunctionStub( target::Thread::suspend_state_init_async_entry_point_offset(), target::ObjectStore::suspend_state_init_async_offset()); } void StubCodeCompiler::GenerateInitAsyncStarStub() { GenerateInitSuspendableFunctionStub( target::Thread::suspend_state_init_async_star_entry_point_offset(), target::ObjectStore::suspend_state_init_async_star_offset()); } void StubCodeCompiler::GenerateInitSyncStarStub() { GenerateInitSuspendableFunctionStub( target::Thread::suspend_state_init_sync_star_entry_point_offset(), target::ObjectStore::suspend_state_init_sync_star_offset()); } void StubCodeCompiler::GenerateResumeStub() { const Register kSuspendState = ResumeStubABI::kSuspendStateReg; const Register kTemp = ResumeStubABI::kTempReg; const Register kFrameSize = ResumeStubABI::kFrameSizeReg; const Register kSrcFrame = ResumeStubABI::kSrcFrameReg; const Register kDstFrame = ResumeStubABI::kDstFrameReg; const Register kResumePc = ResumeStubABI::kResumePcReg; const Register kException = ResumeStubABI::kExceptionReg; const Register kStackTrace = ResumeStubABI::kStackTraceReg; Label call_runtime; // Top of the stack on entry: // ... [SuspendState] [value] [exception] [stackTrace] [ReturnAddress] __ EnterDartFrame(0); if (FLAG_target_thread_sanitizer) { __ TsanFuncEntry(); } const intptr_t param_offset = target::frame_layout.param_end_from_fp * target::kWordSize; __ LoadFromOffset(kSuspendState, FPREG, param_offset + 4 * target::kWordSize); #ifdef DEBUG { Label okay; __ CompareClassId(kSuspendState, kSuspendStateCid, kTemp); __ BranchIf(EQUAL, &okay); __ Breakpoint(); __ Bind(&okay); } { Label okay; __ LoadFieldFromOffset(kTemp, kSuspendState, target::SuspendState::pc_offset()); __ CompareImmediate(kTemp, 0); __ BranchIf(NOT_EQUAL, &okay); __ Breakpoint(); __ Bind(&okay); } #endif __ LoadFieldFromOffset(kFrameSize, kSuspendState, target::SuspendState::frame_size_offset()); #ifdef DEBUG { Label okay; __ MoveRegister(kTemp, kFrameSize); __ AddRegisters(kTemp, kSuspendState); __ LoadFieldFromOffset( kTemp, kTemp, target::SuspendState::payload_offset() + SuspendStateFpOffset()); __ CompareRegisters(kTemp, kSuspendState); __ BranchIf(EQUAL, &okay); __ Breakpoint(); __ Bind(&okay); } #endif if (!FLAG_precompiled_mode) { // Copy Code object (part of the fixed frame which is not copied below) // and restore pool pointer. __ MoveRegister(kTemp, kSuspendState); __ AddRegisters(kTemp, kFrameSize); __ LoadFromOffset( CODE_REG, kTemp, target::SuspendState::payload_offset() - kHeapObjectTag + target::frame_layout.code_from_fp * target::kWordSize); __ StoreToOffset(CODE_REG, FPREG, target::frame_layout.code_from_fp * target::kWordSize); #if !defined(TARGET_ARCH_IA32) __ LoadPoolPointer(PP); #endif } // Do not copy fixed frame between the first local and FP. __ AddImmediate(kFrameSize, (target::frame_layout.first_local_from_fp + 1) * target::kWordSize); __ SubRegisters(SPREG, kFrameSize); __ Comment("Copy frame from SuspendState"); intptr_t num_saved_regs = 0; if (kSrcFrame == THR) { __ PushRegister(THR); ++num_saved_regs; } if (kDstFrame == CODE_REG) { __ PushRegister(CODE_REG); ++num_saved_regs; } __ AddImmediate(kSrcFrame, kSuspendState, target::SuspendState::payload_offset() - kHeapObjectTag); __ AddImmediate(kDstFrame, SPREG, num_saved_regs * target::kWordSize); __ CopyMemoryWords(kSrcFrame, kDstFrame, kFrameSize, kTemp); if (kDstFrame == CODE_REG) { __ PopRegister(CODE_REG); } if (kSrcFrame == THR) { __ PopRegister(THR); } __ Comment("Transfer control"); __ LoadFieldFromOffset(kResumePc, kSuspendState, target::SuspendState::pc_offset()); __ StoreZero(FieldAddress(kSuspendState, target::SuspendState::pc_offset()), kTemp); #if defined(TARGET_ARCH_X64) || defined(TARGET_ARCH_IA32) // Adjust resume PC to skip extra epilogue generated on x86 // right after the call to suspend stub in order to maintain // call/return balance. __ AddImmediate(kResumePc, SuspendStubABI::kResumePcDistance); #endif static_assert((kException != CODE_REG) && (kException != PP), "should not interfere"); __ LoadFromOffset(kException, FPREG, param_offset + 2 * target::kWordSize); __ CompareObject(kException, NullObject()); __ BranchIf(NOT_EQUAL, &call_runtime); if (!FLAG_precompiled_mode) { // Check if Code is disabled. __ LoadFieldFromOffset(kTemp, CODE_REG, target::Code::instructions_offset()); __ CompareWithMemoryValue( kTemp, FieldAddress(CODE_REG, target::Code::active_instructions_offset())); __ BranchIf(NOT_EQUAL, &call_runtime); #if !defined(PRODUCT) // Check if there is a breakpoint at resumption. __ LoadIsolate(kTemp); Label skip_breakpoints_check; // Skip check if no isolate is available(running isolategroup-bound code) __ BranchIfZero(kTemp, &skip_breakpoints_check); __ LoadFromOffset(kTemp, kTemp, target::Isolate::has_resumption_breakpoints_offset(), kUnsignedByte); __ CompareImmediate(kTemp, 0); __ BranchIf(NOT_EQUAL, &call_runtime); __ Bind(&skip_breakpoints_check); #endif } __ LoadFromOffset(CallingConventions::kReturnReg, FPREG, param_offset + 3 * target::kWordSize); __ Jump(kResumePc); __ Comment("Call runtime to throw exception or deopt"); __ Bind(&call_runtime); __ LoadFromOffset(kStackTrace, FPREG, param_offset + 1 * target::kWordSize); static_assert((kStackTrace != CODE_REG) && (kStackTrace != PP), "should not interfere"); #if defined(DART_BYTECODE_INTERPRETER) Label resume_interpreter; __ CompareWithMemoryValue( kResumePc, compiler::Address(THR, compiler::target::Thread:: resume_interpreter_adjusted_entry_point_offset())); __ BranchIf(EQUAL, &resume_interpreter); #endif // defined(DART_BYTECODE_INTERPRETER) // Set return address as if suspended Dart function called // stub with kResumePc as a return address. __ SetReturnAddress(kResumePc); if (!FLAG_precompiled_mode) { __ LoadFromOffset(CODE_REG, THR, target::Thread::resume_stub_offset()); } #if !defined(TARGET_ARCH_IA32) __ set_constant_pool_allowed(false); #endif __ EnterStubFrame(); __ PushObject(NullObject()); // Make room for (unused) result. __ PushRegistersInOrder({kException, kStackTrace}); __ CallRuntime(kResumeFrameRuntimeEntry, /*argument_count=*/2); if (FLAG_precompiled_mode) { __ Breakpoint(); } else { __ LeaveStubFrame(); __ LoadFromOffset(CallingConventions::kReturnReg, FPREG, param_offset + 3 * target::kWordSize); // Lazy deoptimize. __ Ret(); } #if defined(DART_BYTECODE_INTERPRETER) #if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64) // This case is used when Dart frame is still on the stack. if (FLAG_precompiled_mode) { RESTORES_LR_FROM_FRAME({}); // Undo EnterStubFrame() from the case above. } SPILLS_RETURN_ADDRESS_FROM_LR_TO_REGISTER({}); // Undo SetReturnAddress(). #endif __ Comment("Resume interpreter"); __ Bind(&resume_interpreter); __ PushObject(NullObject()); // Make room for result. // Load the value to pass to the resumed bytecode. __ LoadFromOffset(kTemp, FPREG, param_offset + 3 * target::kWordSize); __ PushRegistersInOrder({kTemp, kException, kStackTrace}); __ CallRuntime(kResumeInterpreterRuntimeEntry, /*argument_count=*/3); __ Drop(3); // Drop arguments. __ PopRegister(CallingConventions::kReturnReg); // Get result. __ LeaveDartFrame(); __ Ret(); #endif // defined(DART_BYTECODE_INTERPRETER) } void StubCodeCompiler::GenerateReturnStub( intptr_t return_entry_point_offset_in_thread, intptr_t return_function_offset_in_object_store, intptr_t return_stub_offset_in_thread) { const Register kSuspendState = ReturnStubABI::kSuspendStateReg; #if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64) SPILLS_LR_TO_FRAME({}); // Simulate entering the caller (Dart) frame. #endif __ LoadFromOffset(kSuspendState, FPREG, SuspendStateFpOffset()); #ifdef DEBUG { Label okay; __ CompareObject(kSuspendState, NullObject()); __ BranchIf(NOT_EQUAL, &okay); __ Breakpoint(); __ Bind(&okay); } #endif if (FLAG_target_thread_sanitizer) { __ TsanFuncExit(); } __ LeaveDartFrame(); if (!FLAG_precompiled_mode) { __ LoadFromOffset(CODE_REG, THR, return_stub_offset_in_thread); } __ EnterStubFrame(); __ PushRegistersInOrder({kSuspendState, CallingConventions::kReturnReg}); CallDartCoreLibraryFunction(assembler, return_entry_point_offset_in_thread, return_function_offset_in_object_store); __ LeaveStubFrame(); __ Ret(); } void StubCodeCompiler::GenerateReturnAsyncStub() { GenerateReturnStub( target::Thread::suspend_state_return_async_entry_point_offset(), target::ObjectStore::suspend_state_return_async_offset(), target::Thread::return_async_stub_offset()); } void StubCodeCompiler::GenerateReturnAsyncNotFutureStub() { GenerateReturnStub( target::Thread:: suspend_state_return_async_not_future_entry_point_offset(), target::ObjectStore::suspend_state_return_async_not_future_offset(), target::Thread::return_async_not_future_stub_offset()); } void StubCodeCompiler::GenerateReturnAsyncStarStub() { GenerateReturnStub( target::Thread::suspend_state_return_async_star_entry_point_offset(), target::ObjectStore::suspend_state_return_async_star_offset(), target::Thread::return_async_star_stub_offset()); } void StubCodeCompiler::GenerateAsyncExceptionHandlerStub() { const Register kSuspendState = AsyncExceptionHandlerStubABI::kSuspendStateReg; ASSERT(kSuspendState != kExceptionObjectReg); ASSERT(kSuspendState != kStackTraceObjectReg); Label rethrow_exception; #if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64) SPILLS_LR_TO_FRAME({}); // Simulate entering the caller (Dart) frame. #endif __ LoadFromOffset(kSuspendState, FPREG, SuspendStateFpOffset()); // Check if suspend_state is initialized. Otherwise // exception was thrown from the prologue code and // should be synchronously propagated. __ CompareObject(kSuspendState, NullObject()); __ BranchIf(EQUAL, &rethrow_exception); if (FLAG_target_thread_sanitizer) { __ TsanFuncExit(); } __ LeaveDartFrame(); if (!FLAG_precompiled_mode) { __ LoadFromOffset(CODE_REG, THR, target::Thread::async_exception_handler_stub_offset()); } __ EnterStubFrame(); __ PushRegistersInOrder( {kSuspendState, kExceptionObjectReg, kStackTraceObjectReg}); CallDartCoreLibraryFunction( assembler, target::Thread::suspend_state_handle_exception_entry_point_offset(), target::ObjectStore::suspend_state_handle_exception_offset()); __ LeaveStubFrame(); __ Ret(); #if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64) // Rethrow case is used when Dart frame is still on the stack. SPILLS_LR_TO_FRAME({}); #endif __ Comment("Rethrow exception"); __ Bind(&rethrow_exception); if (FLAG_target_thread_sanitizer) { __ TsanFuncExit(); } __ LeaveDartFrame(); if (!FLAG_precompiled_mode) { __ LoadFromOffset(CODE_REG, THR, target::Thread::async_exception_handler_stub_offset()); } __ EnterStubFrame(); __ PushObject(NullObject()); // Make room for (unused) result. __ PushRegistersInOrder({kExceptionObjectReg, kStackTraceObjectReg}); __ PushImmediate(Smi::RawValue(0)); // Do not bypass debugger. __ CallRuntime(kReThrowRuntimeEntry, /*argument_count=*/3); __ Breakpoint(); } void StubCodeCompiler::GenerateCloneSuspendStateStub() { const Register kSource = CloneSuspendStateStubABI::kSourceReg; const Register kDestination = CloneSuspendStateStubABI::kDestinationReg; const Register kTemp = CloneSuspendStateStubABI::kTempReg; const Register kFrameSize = CloneSuspendStateStubABI::kFrameSizeReg; const Register kSrcFrame = CloneSuspendStateStubABI::kSrcFrameReg; const Register kDstFrame = CloneSuspendStateStubABI::kDstFrameReg; Label alloc_slow_case; #ifdef DEBUG { // Can only clone _SuspendState objects with copied frames. Label okay; __ LoadFieldFromOffset(kTemp, kSource, target::SuspendState::pc_offset()); __ CompareImmediate(kTemp, 0); __ BranchIf(NOT_EQUAL, &okay); __ Breakpoint(); __ Bind(&okay); } #endif __ LoadFieldFromOffset(kFrameSize, kSource, target::SuspendState::frame_size_offset()); GenerateAllocateSuspendState(assembler, &alloc_slow_case, kDestination, kFrameSize, kTemp); // Copy pc. __ LoadFieldFromOffset(kTemp, kSource, target::SuspendState::pc_offset()); __ StoreFieldToOffset(kTemp, kDestination, target::SuspendState::pc_offset()); // Copy function_data. __ LoadCompressedFieldFromOffset( kTemp, kSource, target::SuspendState::function_data_offset()); __ StoreCompressedIntoObjectNoBarrier( kDestination, FieldAddress(kDestination, target::SuspendState::function_data_offset()), kTemp); // Copy then_callback. __ LoadCompressedFieldFromOffset( kTemp, kSource, target::SuspendState::then_callback_offset()); __ StoreCompressedIntoObjectNoBarrier( kDestination, FieldAddress(kDestination, target::SuspendState::then_callback_offset()), kTemp); // Copy error_callback. __ LoadCompressedFieldFromOffset( kTemp, kSource, target::SuspendState::error_callback_offset()); __ StoreCompressedIntoObjectNoBarrier( kDestination, FieldAddress(kDestination, target::SuspendState::error_callback_offset()), kTemp); // Copy payload frame. if (kSrcFrame == THR) { __ PushRegister(THR); } const uword offset = target::SuspendState::payload_offset() - kHeapObjectTag; __ AddImmediate(kSrcFrame, kSource, offset); __ AddImmediate(kDstFrame, kDestination, offset); __ CopyMemoryWords(kSrcFrame, kDstFrame, kFrameSize, kTemp); if (kSrcFrame == THR) { __ PopRegister(THR); } // Update value of :suspend_state variable in the copied frame // for the new SuspendState. __ LoadFieldFromOffset(kTemp, kDestination, target::SuspendState::frame_size_offset()); __ AddRegisters(kTemp, kDestination); __ StoreFieldToOffset( kDestination, kTemp, target::SuspendState::payload_offset() + SuspendStateFpOffset()); __ MoveRegister(CallingConventions::kReturnReg, kDestination); EnsureIsNewOrRemembered(); __ Ret(); __ Bind(&alloc_slow_case); __ Comment("CloneSuspendState slow case"); __ EnterStubFrame(); __ PushObject(NullObject()); // Make space on stack for the return value. __ PushRegister(kSource); __ CallRuntime(kCloneSuspendStateRuntimeEntry, 1); __ Drop(1); // Drop argument __ PopRegister(CallingConventions::kReturnReg); // Get result. __ LeaveStubFrame(); __ Ret(); } void StubCodeCompiler::GenerateResumeInterpreterStub() { #if defined(TARGET_ARCH_X64) || defined(TARGET_ARCH_IA32) // On X64/IA32 execution is resumed at PC + kResumePcDistance. const intptr_t start = __ CodeSize(); for (intptr_t i = 0; i < SuspendStubABI::kResumePcDistance; ++i) { __ nop(); } RELEASE_ASSERT(__ CodeSize() - start == SuspendStubABI::kResumePcDistance); #endif #if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64) SPILLS_LR_TO_FRAME({}); // Simulate entering the caller (Dart) frame. #endif __ PushObject(NullObject()); // Make room for result. __ PushRegister(CallingConventions::kReturnReg); // Return value. __ PushObject(NullObject()); // Exception. __ PushObject(NullObject()); // Stack trace. __ CallRuntime(kResumeInterpreterRuntimeEntry, /*argument_count=*/3); __ Drop(3); // Drop arguments. __ PopRegister(CallingConventions::kReturnReg); // Get result. __ LeaveDartFrame(); __ Ret(); } void StubCodeCompiler::GenerateFfiAsyncCallbackSendStub() { __ EnterStubFrame(); __ PushObject(NullObject()); // Make space on stack for the return value. __ PushRegister(FfiAsyncCallbackSendStubABI::kArgsReg); __ CallRuntime(kFfiAsyncCallbackSendRuntimeEntry, 1); __ Drop(1); // Drop argument. __ PopRegister(CallingConventions::kReturnReg); // Get result. __ LeaveStubFrame(); __ Ret(); } #if !defined(TARGET_ARCH_IA32) static void GenerateSubtypeTestCacheLoopBody(Assembler* assembler, int n, Register null_reg, Register cache_entry_reg, Register instance_cid_or_sig_reg, Register instance_type_args_reg, Register parent_fun_type_args_reg, Register delayed_type_args_reg, Label* found, Label* not_found, Label* next_iteration) { __ Comment("Loop"); // LoadAcquireCompressed assumes the loaded value is a heap object and // extends it with the heap bits if compressed. However, the entry may be // a Smi. // // Instead, just use LoadAcquire to load the lower bits when compressed and // only compare the low bits of the loaded value using CompareObjectRegisters. __ LoadAcquireFromOffset( TypeTestABI::kScratchReg, cache_entry_reg, target::kCompressedWordSize * target::SubtypeTestCache::kInstanceCidOrSignature, kObjectBytes); __ CompareObjectRegisters(TypeTestABI::kScratchReg, null_reg); __ BranchIf(EQUAL, not_found, Assembler::kNearJump); __ CompareObjectRegisters(TypeTestABI::kScratchReg, instance_cid_or_sig_reg); if (n == 1) { __ BranchIf(EQUAL, found, Assembler::kNearJump); return; } __ BranchIf(NOT_EQUAL, next_iteration, Assembler::kNearJump); __ CompareWithMemoryValue( instance_type_args_reg, Address(cache_entry_reg, target::kCompressedWordSize * target::SubtypeTestCache::kInstanceTypeArguments), kObjectBytes); if (n == 2) { __ BranchIf(EQUAL, found, Assembler::kNearJump); return; } __ BranchIf(NOT_EQUAL, next_iteration, Assembler::kNearJump); __ CompareWithMemoryValue( TypeTestABI::kInstantiatorTypeArgumentsReg, Address(cache_entry_reg, target::kCompressedWordSize * target::SubtypeTestCache::kInstantiatorTypeArguments), kObjectBytes); if (n == 3) { __ BranchIf(EQUAL, found, Assembler::kNearJump); return; } __ BranchIf(NOT_EQUAL, next_iteration, Assembler::kNearJump); __ CompareWithMemoryValue( TypeTestABI::kFunctionTypeArgumentsReg, Address(cache_entry_reg, target::kCompressedWordSize * target::SubtypeTestCache::kFunctionTypeArguments), kObjectBytes); if (n == 4) { __ BranchIf(EQUAL, found, Assembler::kNearJump); return; } __ BranchIf(NOT_EQUAL, next_iteration, Assembler::kNearJump); __ CompareWithMemoryValue( parent_fun_type_args_reg, Address( cache_entry_reg, target::kCompressedWordSize * target::SubtypeTestCache::kInstanceParentFunctionTypeArguments), kObjectBytes); if (n == 5) { __ BranchIf(EQUAL, found, Assembler::kNearJump); return; } __ BranchIf(NOT_EQUAL, next_iteration, Assembler::kNearJump); __ CompareWithMemoryValue( delayed_type_args_reg, Address( cache_entry_reg, target::kCompressedWordSize * target::SubtypeTestCache::kInstanceDelayedFunctionTypeArguments), kObjectBytes); if (n == 6) { __ BranchIf(EQUAL, found, Assembler::kNearJump); return; } __ BranchIf(NOT_EQUAL, next_iteration, Assembler::kNearJump); __ CompareWithMemoryValue( TypeTestABI::kDstTypeReg, Address(cache_entry_reg, target::kCompressedWordSize * target::SubtypeTestCache::kDestinationType), kObjectBytes); __ BranchIf(EQUAL, found, Assembler::kNearJump); } // An object that uses RAII to load from and store to the stack when // appropriate, allowing the code within that scope to act as if the given // register is always provided. Either the Register value stored at [reg] must // be a valid register (not kNoRegister) or [depth] must be a valid stack depth // (not StackRegisterScope::kNoDepth). // // When the Register value stored at [reg] is a valid register, this scope // generates no assembly and does not change the value stored at [reg]. // // When [depth] is a valid stack depth, this scope object performs the // following actions: // // On construction: // * Generates assembly to load the value on the stack at [depth] into [alt]. // * Sets the Register value pointed to by [reg] to [alt]. // // On destruction: // * Generates assembly to store the value of [alt] into the stack at [depth]. // * Resets the Register value pointed to by [reg] to kNoRegister. class StackRegisterScope : ValueObject { public: StackRegisterScope(Assembler* assembler, Register* reg, intptr_t depth, Register alt = TMP) : assembler(assembler), reg_(reg), depth_(depth), alt_(alt) { if (depth_ != kNoDepth) { ASSERT(depth_ >= 0); ASSERT(*reg_ == kNoRegister); ASSERT(alt_ != kNoRegister); __ LoadFromStack(alt_, depth_); *reg_ = alt_; } else { ASSERT(*reg_ != kNoRegister); } } ~StackRegisterScope() { if (depth_ != kNoDepth) { __ StoreToStack(alt_, depth_); *reg_ = kNoRegister; } } static constexpr intptr_t kNoDepth = kIntptrMin; private: Assembler* const assembler; Register* const reg_; const intptr_t depth_; const Register alt_; }; // Same inputs as StubCodeCompiler::GenerateSubtypeTestCacheSearch with // the following additional requirements: // - catch_entry_reg: the address of the backing array for the cache. // - TypeTestABI::kScratchReg: the Smi value of the length field for the // backing array in cache_entry_reg // // Also expects that all the STC entry input registers have been filled. static void GenerateSubtypeTestCacheHashSearch( Assembler* assembler, int n, Register null_reg, Register cache_entry_reg, Register instance_cid_or_sig_reg, Register instance_type_args_reg, Register parent_fun_type_args_reg, Register delayed_type_args_reg, Register cache_entry_end_reg, Register cache_contents_size_reg, Register probe_distance_reg, const StubCodeCompiler::STCSearchExitGenerator& gen_found, const StubCodeCompiler::STCSearchExitGenerator& gen_not_found) { // Since the test entry size is a power of 2, we can use shr to divide. const intptr_t kTestEntryLengthLog2 = Utils::ShiftForPowerOfTwo(target::SubtypeTestCache::kTestEntryLength); // Before we finish calculating the initial probe entry, we'll need the // starting cache entry and the number of entries. We'll store these in // [cache_contents_size_reg] and [probe_distance_reg] (or their equivalent // stack slots), respectively. __ Comment("Hash cache traversal"); __ Comment("Calculating number of entries"); // The array length is a Smi so it needs to be untagged. __ SmiUntag(TypeTestABI::kScratchReg); __ LsrImmediate(TypeTestABI::kScratchReg, kTestEntryLengthLog2); if (probe_distance_reg != kNoRegister) { __ MoveRegister(probe_distance_reg, TypeTestABI::kScratchReg); } else { __ PushRegister(TypeTestABI::kScratchReg); } __ Comment("Calculating starting entry address"); __ AddImmediate(cache_entry_reg, target::Array::data_offset() - kHeapObjectTag); if (cache_contents_size_reg != kNoRegister) { __ MoveRegister(cache_contents_size_reg, cache_entry_reg); } else { __ PushRegister(cache_entry_reg); } __ Comment("Calculating end of entries address"); __ LslImmediate(TypeTestABI::kScratchReg, kTestEntryLengthLog2 + target::kCompressedWordSizeLog2); __ AddRegisters(TypeTestABI::kScratchReg, cache_entry_reg); if (cache_entry_end_reg != kNoRegister) { __ MoveRegister(cache_entry_end_reg, TypeTestABI::kScratchReg); } else { __ PushRegister(TypeTestABI::kScratchReg); } // At this point, the stack is in the following order, if the corresponding // value doesn't have a register assignment: // // // // --------- top of stack // // and after calculating the initial entry, we'll replace them as follows: // // <-cache array contents size> (note this is _negative_) // // ---------- top of stack // // So name them according to their later use. intptr_t kProbeDistanceDepth = StackRegisterScope::kNoDepth; intptr_t kHashStackElements = 0; if (probe_distance_reg == kNoRegister) { kProbeDistanceDepth = 0; kHashStackElements++; } intptr_t kCacheContentsSizeDepth = StackRegisterScope::kNoDepth; if (cache_contents_size_reg == kNoRegister) { kProbeDistanceDepth++; kHashStackElements++; kCacheContentsSizeDepth = 0; } intptr_t kCacheArrayEndDepth = StackRegisterScope::kNoDepth; if (cache_entry_end_reg == kNoRegister) { kProbeDistanceDepth++; kCacheContentsSizeDepth++; kHashStackElements++; kCacheArrayEndDepth = 0; } // After this point, any exits should go through one of these two labels, // which will pop the extra stack elements pushed above. Label found, not_found; // When retrieving hashes from objects below, note that a hash of 0 means // the hash hasn't been computed yet and we need to go to runtime. auto get_abstract_type_hash = [&](Register dst, Register src, const char* name) { ASSERT(dst != kNoRegister); ASSERT(src != kNoRegister); __ Comment("Loading %s type hash", name); __ LoadFromSlot(dst, src, Slot::AbstractType_hash()); __ SmiUntag(dst); __ CompareImmediate(dst, 0); __ BranchIf(EQUAL, ¬_found); }; auto get_type_arguments_hash = [&](Register dst, Register src, const char* name) { ASSERT(dst != kNoRegister); ASSERT(src != kNoRegister); Label done; __ Comment("Loading %s type arguments hash", name); // Preload the hash value for TypeArguments::null() so control can jump // to done if null. __ LoadImmediate(dst, TypeArguments::kAllDynamicHash); __ CompareRegisters(src, null_reg); __ BranchIf(EQUAL, &done, Assembler::kNearJump); __ LoadFromSlot(dst, src, Slot::TypeArguments_hash()); __ SmiUntag(dst); __ CompareImmediate(dst, 0); __ BranchIf(EQUAL, ¬_found); __ Bind(&done); }; __ Comment("Hash the entry inputs"); { Label done; // Assume a Smi tagged instance cid to avoid a branch in the common case. __ MoveRegister(cache_entry_reg, instance_cid_or_sig_reg); __ SmiUntag(cache_entry_reg); __ BranchIfSmi(instance_cid_or_sig_reg, &done, Assembler::kNearJump); get_abstract_type_hash(cache_entry_reg, instance_cid_or_sig_reg, "closure signature"); __ Bind(&done); } if (n >= 7) { get_abstract_type_hash(TypeTestABI::kScratchReg, TypeTestABI::kDstTypeReg, "destination"); __ CombineHashes(cache_entry_reg, TypeTestABI::kScratchReg); } if (n >= 6) { get_type_arguments_hash(TypeTestABI::kScratchReg, delayed_type_args_reg, "delayed"); __ CombineHashes(cache_entry_reg, TypeTestABI::kScratchReg); } if (n >= 5) { get_type_arguments_hash(TypeTestABI::kScratchReg, parent_fun_type_args_reg, "parent function"); __ CombineHashes(cache_entry_reg, TypeTestABI::kScratchReg); } if (n >= 4) { get_type_arguments_hash(TypeTestABI::kScratchReg, TypeTestABI::kFunctionTypeArgumentsReg, "function"); __ CombineHashes(cache_entry_reg, TypeTestABI::kScratchReg); } if (n >= 3) { get_type_arguments_hash(TypeTestABI::kScratchReg, TypeTestABI::kInstantiatorTypeArgumentsReg, "instantiator"); __ CombineHashes(cache_entry_reg, TypeTestABI::kScratchReg); } if (n >= 2) { get_type_arguments_hash(TypeTestABI::kScratchReg, instance_type_args_reg, "instance"); __ CombineHashes(cache_entry_reg, TypeTestABI::kScratchReg); } __ FinalizeHash(cache_entry_reg); // This requires the number of entries in a hash cache to be a power of 2. __ Comment("Converting hash to probe entry index"); { StackRegisterScope scope(assembler, &probe_distance_reg, kProbeDistanceDepth, TypeTestABI::kScratchReg); // The entry count is not needed after this point; create the mask in place. __ AddImmediate(probe_distance_reg, -1); __ AndRegisters(cache_entry_reg, probe_distance_reg); // Now set the register to the initial probe distance in words. __ Comment("Set initial probe distance"); __ LoadImmediate(probe_distance_reg, target::kCompressedWordSize * target::SubtypeTestCache::kTestEntryLength); } // Now cache_entry_reg is the starting probe entry index. __ Comment("Converting probe entry index to probe entry address"); { StackRegisterScope scope(assembler, &cache_contents_size_reg, kCacheContentsSizeDepth, TypeTestABI::kScratchReg); __ LslImmediate(cache_entry_reg, kTestEntryLengthLog2 + target::kCompressedWordSizeLog2); __ AddRegisters(cache_entry_reg, cache_contents_size_reg); // Now set the register to the negated size of the cache contents in words. __ Comment("Set negated cache contents size"); if (cache_entry_end_reg != kNoRegister) { __ SubRegisters(cache_contents_size_reg, cache_entry_end_reg); } else { __ LoadFromStack(TMP, kCacheArrayEndDepth); __ SubRegisters(cache_contents_size_reg, TMP); } } Label loop, next_iteration; __ Bind(&loop); GenerateSubtypeTestCacheLoopBody( assembler, n, null_reg, cache_entry_reg, instance_cid_or_sig_reg, instance_type_args_reg, parent_fun_type_args_reg, delayed_type_args_reg, &found, ¬_found, &next_iteration); __ Bind(&next_iteration); __ Comment("Move to next entry"); { StackRegisterScope scope(assembler, &probe_distance_reg, kProbeDistanceDepth, TypeTestABI::kScratchReg); __ AddRegisters(cache_entry_reg, probe_distance_reg); __ Comment("Adjust probe distance"); __ AddImmediate(probe_distance_reg, target::kCompressedWordSize * target::SubtypeTestCache::kTestEntryLength); } __ Comment("Check for leaving array"); // Make sure we haven't run off the array. if (cache_entry_end_reg != kNoRegister) { __ CompareRegisters(cache_entry_reg, cache_entry_end_reg); } else { __ CompareToStack(cache_entry_reg, kCacheArrayEndDepth); } __ BranchIf(LESS, &loop, Assembler::kNearJump); __ Comment("Wrap around to start of entries"); // Add the negated size of the cache contents. if (cache_contents_size_reg != kNoRegister) { __ AddRegisters(cache_entry_reg, cache_contents_size_reg); } else { __ LoadFromStack(TypeTestABI::kScratchReg, kCacheContentsSizeDepth); __ AddRegisters(cache_entry_reg, TypeTestABI::kScratchReg); } __ Jump(&loop, Assembler::kNearJump); __ Bind(&found); __ Comment("Hash found"); __ Drop(kHashStackElements); gen_found(assembler, n); __ Bind(¬_found); __ Comment("Hash not found"); __ Drop(kHashStackElements); gen_not_found(assembler, n); } // Same inputs as StubCodeCompiler::GenerateSubtypeTestCacheSearch with // the following additional requirement: // - catch_entry_reg: the address of the backing array for the cache. // // Also expects that all the STC entry input registers have been filled. static void GenerateSubtypeTestCacheLinearSearch( Assembler* assembler, int n, Register null_reg, Register cache_entry_reg, Register instance_cid_or_sig_reg, Register instance_type_args_reg, Register parent_fun_type_args_reg, Register delayed_type_args_reg, const StubCodeCompiler::STCSearchExitGenerator& gen_found, const StubCodeCompiler::STCSearchExitGenerator& gen_not_found) { __ Comment("Linear cache traversal"); __ AddImmediate(cache_entry_reg, target::Array::data_offset() - kHeapObjectTag); Label found, not_found, loop, next_iteration; __ Bind(&loop); GenerateSubtypeTestCacheLoopBody( assembler, n, null_reg, cache_entry_reg, instance_cid_or_sig_reg, instance_type_args_reg, parent_fun_type_args_reg, delayed_type_args_reg, &found, ¬_found, &next_iteration); __ Bind(&next_iteration); __ Comment("Next iteration"); __ AddImmediate( cache_entry_reg, target::kCompressedWordSize * target::SubtypeTestCache::kTestEntryLength); __ Jump(&loop, Assembler::kNearJump); __ Bind(&found); __ Comment("Linear found"); gen_found(assembler, n); __ Bind(¬_found); __ Comment("Linear not found"); gen_not_found(assembler, n); } void StubCodeCompiler::GenerateSubtypeTestCacheSearch( Assembler* assembler, int n, Register null_reg, Register cache_entry_reg, Register instance_cid_or_sig_reg, Register instance_type_args_reg, Register parent_fun_type_args_reg, Register delayed_type_args_reg, Register cache_entry_end_reg, Register cache_contents_size_reg, Register probe_distance_reg, const StubCodeCompiler::STCSearchExitGenerator& gen_found, const StubCodeCompiler::STCSearchExitGenerator& gen_not_found) { #if defined(DEBUG) RegisterSet input_regs; ASSERT(null_reg != kNoRegister); input_regs.AddRegister(null_reg); ASSERT(cache_entry_reg != kNoRegister); ASSERT(!input_regs.ContainsRegister(cache_entry_reg)); input_regs.AddRegister(cache_entry_reg); ASSERT(instance_cid_or_sig_reg != kNoRegister); ASSERT(!input_regs.ContainsRegister(instance_cid_or_sig_reg)); input_regs.AddRegister(instance_cid_or_sig_reg); if (n >= 2) { ASSERT(instance_type_args_reg != kNoRegister); ASSERT(!input_regs.ContainsRegister(instance_type_args_reg)); input_regs.AddRegister(instance_type_args_reg); } if (n >= 5) { ASSERT(parent_fun_type_args_reg != kNoRegister); ASSERT(!input_regs.ContainsRegister(parent_fun_type_args_reg)); input_regs.AddRegister(parent_fun_type_args_reg); } ASSERT(!input_regs.ContainsRegister(TypeTestABI::kInstanceReg)); if (n >= 6) { ASSERT(delayed_type_args_reg != kNoRegister); ASSERT(!input_regs.ContainsRegister(delayed_type_args_reg)); input_regs.AddRegister(delayed_type_args_reg); } if (cache_entry_end_reg != kNoRegister) { ASSERT(!input_regs.ContainsRegister(cache_entry_end_reg)); input_regs.AddRegister(cache_entry_end_reg); } if (cache_contents_size_reg != kNoRegister) { ASSERT(!input_regs.ContainsRegister(cache_contents_size_reg)); input_regs.AddRegister(cache_contents_size_reg); } if (probe_distance_reg != kNoRegister) { ASSERT(!input_regs.ContainsRegister(probe_distance_reg)); input_regs.AddRegister(probe_distance_reg); } // We can allow the use of the registers below only if we're not expecting // them as an inspected input. if (n >= 3) { ASSERT(!input_regs.ContainsRegister( TypeTestABI::kInstantiatorTypeArgumentsReg)); } if (n >= 4) { ASSERT( !input_regs.ContainsRegister(TypeTestABI::kFunctionTypeArgumentsReg)); } if (n >= 7) { ASSERT(!input_regs.ContainsRegister(TypeTestABI::kDstTypeReg)); } // We use this as a scratch, so it has to be distinct from the others. ASSERT(!input_regs.ContainsRegister(TypeTestABI::kScratchReg)); // Verify the STC we received has exactly as many inputs as this stub expects. Label search_stc; __ LoadFromSlot(TypeTestABI::kScratchReg, TypeTestABI::kSubtypeTestCacheReg, Slot::SubtypeTestCache_num_inputs()); __ CompareImmediate(TypeTestABI::kScratchReg, n); __ BranchIf(EQUAL, &search_stc, Assembler::kNearJump); __ Breakpoint(); __ Bind(&search_stc); #endif __ LoadAcquireCompressedFromOffset( cache_entry_reg, TypeTestABI::kSubtypeTestCacheReg, target::SubtypeTestCache::cache_offset() - kHeapObjectTag); // Fill in all the STC input registers. Label initialized, not_closure; if (n >= 3) { __ LoadClassIdMayBeSmi(instance_cid_or_sig_reg, TypeTestABI::kInstanceReg); } else { // If the type is fully instantiated, then it can be determined at compile // time whether Smi is a subtype of the type or not. Thus, this code should // never be called with a Smi instance. __ LoadClassId(instance_cid_or_sig_reg, TypeTestABI::kInstanceReg); } __ CompareImmediate(instance_cid_or_sig_reg, kClosureCid); __ BranchIf(NOT_EQUAL, ¬_closure, Assembler::kNearJump); // Closure handling. { __ Comment("Closure"); __ LoadCompressed(instance_cid_or_sig_reg, FieldAddress(TypeTestABI::kInstanceReg, target::Closure::function_offset())); __ LoadCompressed(instance_cid_or_sig_reg, FieldAddress(instance_cid_or_sig_reg, target::Function::signature_offset())); if (n >= 2) { __ LoadCompressedSmi( TypeTestABI::kScratchReg, FieldAddress(TypeTestABI::kInstanceReg, target::Closure::length_and_flags_offset())); Label load_function_type_arguments, load_delayed_type_arguments; ASSERT(instance_type_args_reg != TypeTestABI::kInstanceReg); ASSERT(instance_type_args_reg != TypeTestABI::kScratchReg); __ MoveRegister(instance_type_args_reg, null_reg); __ BranchIfBit( TypeTestABI::kScratchReg, UntaggedClosure::kHasInstantiatorTypeArgumentsBit + kSmiTagShift, ZERO, (n >= 5) ? &load_function_type_arguments : &initialized); __ ExtractBitField( instance_type_args_reg, TypeTestABI::kScratchReg, UntaggedClosure::InstantiatorTypeArgumentsIndexBits::shift() + kSmiTagShift, UntaggedClosure::InstantiatorTypeArgumentsIndexBits::bitsize()); __ LoadIndexedCompressed( instance_type_args_reg, TypeTestABI::kInstanceReg, target::Closure::element_offset(0), instance_type_args_reg); if (n >= 5) { __ Bind(&load_function_type_arguments); ASSERT(parent_fun_type_args_reg != TypeTestABI::kInstanceReg); ASSERT(parent_fun_type_args_reg != TypeTestABI::kScratchReg); __ MoveRegister(parent_fun_type_args_reg, null_reg); __ BranchIfBit( TypeTestABI::kScratchReg, UntaggedClosure::kHasFunctionTypeArgumentsBit + kSmiTagShift, ZERO, (n >= 6) ? &load_delayed_type_arguments : &initialized); __ ExtractBitField( parent_fun_type_args_reg, TypeTestABI::kScratchReg, UntaggedClosure::FunctionTypeArgumentsIndexBits::shift() + kSmiTagShift, UntaggedClosure::FunctionTypeArgumentsIndexBits::bitsize()); __ LoadIndexedCompressed( parent_fun_type_args_reg, TypeTestABI::kInstanceReg, target::Closure::element_offset(0), parent_fun_type_args_reg); } if (n >= 6) { __ Bind(&load_delayed_type_arguments); // On 32-bit ARM [delayed_type_args_reg] can be the same as // [TypeTestABI::kInstanceReg]. In such a case [delayed_type_args_reg] // should not be overwritten before the last use of [kInstanceReg]. if (delayed_type_args_reg == TypeTestABI::kInstanceReg) { Label no_delayed_type_arguments; __ BranchIfBit( TypeTestABI::kScratchReg, UntaggedClosure::kHasDelayedTypeArgumentsBit + kSmiTagShift, ZERO, &no_delayed_type_arguments); __ LoadCompressed( delayed_type_args_reg, FieldAddress(TypeTestABI::kInstanceReg, target::Closure::element_offset( UntaggedClosure::kDelayedTypeArgumentsIndex))); __ Jump(&initialized, Assembler::kNearJump); __ Bind(&no_delayed_type_arguments); __ MoveRegister(delayed_type_args_reg, null_reg); } else { ASSERT(delayed_type_args_reg != TypeTestABI::kInstanceReg); ASSERT(delayed_type_args_reg != TypeTestABI::kScratchReg); __ MoveRegister(delayed_type_args_reg, null_reg); __ BranchIfBit( TypeTestABI::kScratchReg, UntaggedClosure::kHasDelayedTypeArgumentsBit + kSmiTagShift, ZERO, &initialized); __ LoadCompressed( delayed_type_args_reg, FieldAddress(TypeTestABI::kInstanceReg, target::Closure::element_offset( UntaggedClosure::kDelayedTypeArgumentsIndex))); } } } __ Jump(&initialized, Assembler::kNearJump); } // Non-Closure handling. { __ Comment("Non-Closure"); __ Bind(¬_closure); if (n >= 2) { Label has_no_type_arguments; __ LoadClassById(TypeTestABI::kScratchReg, instance_cid_or_sig_reg); __ MoveRegister(instance_type_args_reg, null_reg); __ LoadFieldFromOffset( TypeTestABI::kScratchReg, TypeTestABI::kScratchReg, target::Class::host_type_arguments_field_offset_in_words_offset(), kFourBytes); __ CompareImmediate(TypeTestABI::kScratchReg, target::Class::kNoTypeArguments, kFourBytes); __ BranchIf(EQUAL, &has_no_type_arguments, Assembler::kNearJump); __ LoadIndexedCompressed(instance_type_args_reg, TypeTestABI::kInstanceReg, 0, TypeTestABI::kScratchReg); __ Bind(&has_no_type_arguments); __ Comment("No type arguments"); } __ SmiTag(instance_cid_or_sig_reg); if (n >= 5) { __ MoveRegister(parent_fun_type_args_reg, null_reg); } if (n >= 6) { __ MoveRegister(delayed_type_args_reg, null_reg); } } __ Bind(&initialized); // There is a maximum size for linear caches that is smaller than the size // of any hash-based cache, so we check the size of the backing array to // determine if this is a linear or hash-based cache. // // We load it into TypeTestABI::kScratchReg as the hash search code expects // it there. Label is_hash; __ LoadFromSlot(TypeTestABI::kScratchReg, cache_entry_reg, Slot::Array_length()); __ CompareImmediate(TypeTestABI::kScratchReg, target::ToRawSmi(SubtypeTestCache::kMaxLinearCacheSize)); __ BranchIf(GREATER, &is_hash); GenerateSubtypeTestCacheLinearSearch( assembler, n, null_reg, cache_entry_reg, instance_cid_or_sig_reg, instance_type_args_reg, parent_fun_type_args_reg, delayed_type_args_reg, gen_found, gen_not_found); __ Bind(&is_hash); GenerateSubtypeTestCacheHashSearch( assembler, n, null_reg, cache_entry_reg, instance_cid_or_sig_reg, instance_type_args_reg, parent_fun_type_args_reg, delayed_type_args_reg, cache_entry_end_reg, cache_contents_size_reg, probe_distance_reg, gen_found, gen_not_found); } #endif // See comment on [GenerateSubtypeNTestCacheStub]. void StubCodeCompiler::GenerateSubtype1TestCacheStub() { GenerateSubtypeNTestCacheStub(assembler, 1); } // See comment on [GenerateSubtypeNTestCacheStub]. void StubCodeCompiler::GenerateSubtype2TestCacheStub() { GenerateSubtypeNTestCacheStub(assembler, 2); } // See comment on [GenerateSubtypeNTestCacheStub]. void StubCodeCompiler::GenerateSubtype3TestCacheStub() { GenerateSubtypeNTestCacheStub(assembler, 3); } // See comment on [GenerateSubtypeNTestCacheStub]. void StubCodeCompiler::GenerateSubtype4TestCacheStub() { GenerateSubtypeNTestCacheStub(assembler, 4); } // See comment on [GenerateSubtypeNTestCacheStub]. void StubCodeCompiler::GenerateSubtype6TestCacheStub() { GenerateSubtypeNTestCacheStub(assembler, 6); } // See comment on [GenerateSubtypeNTestCacheStub]. void StubCodeCompiler::GenerateSubtype7TestCacheStub() { GenerateSubtypeNTestCacheStub(assembler, 7); } #ifndef DART_TARGET_SUPPORTS_PROBE_POINTS void StubCodeCompiler::GenerateAllocationProbePointStub() { __ Stop("allocation probes are not supported on this platform"); } #endif } // namespace compiler } // namespace dart