; Licensed to the .NET Foundation under one or more agreements. ; The .NET Foundation licenses this file to you under the MIT license. ; See the LICENSE file in the project root for more information. ; ==++== ; ; ; ==--== ; *********************************************************************** ; File: JitHelpers_Fast.asm, see jithelp.asm for history ; ; Notes: routinues which we believe to be on the hot path for managed ; code in most scenarios. ; *********************************************************************** include AsmMacros.inc include asmconstants.inc ; Min amount of stack space that a nested function should allocate. MIN_SIZE equ 28h EXTERN g_ephemeral_low:QWORD EXTERN g_ephemeral_high:QWORD EXTERN g_lowest_address:QWORD EXTERN g_highest_address:QWORD EXTERN g_card_table:QWORD ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP EXTERN g_sw_ww_table:QWORD EXTERN g_sw_ww_enabled_for_gc_heap:BYTE endif ifdef WRITE_BARRIER_CHECK ; Those global variables are always defined, but should be 0 for Server GC g_GCShadow TEXTEQU g_GCShadowEnd TEXTEQU EXTERN g_GCShadow:QWORD EXTERN g_GCShadowEnd:QWORD endif INVALIDGCVALUE equ 0CCCCCCCDh ifdef _DEBUG extern JIT_WriteBarrier_Debug:proc endif extern JIT_InternalThrow:proc extern JITutil_ChkCastInterface:proc extern JITutil_IsInstanceOfInterface:proc extern JITutil_ChkCastAny:proc extern JITutil_IsInstanceOfAny:proc ;EXTERN_C Object* JIT_IsInstanceOfClass(MethodTable* pMT, Object* pObject); LEAF_ENTRY JIT_IsInstanceOfClass, _TEXT ; move rdx into rax in case of a match or null mov rax, rdx ; check if the instance is null test rdx, rdx je IsNullInst ; check is the MethodTable for the instance matches pMT cmp rcx, qword ptr [rdx] jne JIT_IsInstanceOfClass2 IsNullInst: REPRET LEAF_END JIT_IsInstanceOfClass, _TEXT LEAF_ENTRY JIT_IsInstanceOfClass2, _TEXT ; check if the parent class matches. ; start by putting the MethodTable for the instance in rdx mov rdx, qword ptr [rdx] align 16 CheckParent: ; NULL parent MethodTable* indicates that we're at the top of the hierarchy ; unroll 0 mov rdx, qword ptr [rdx + OFFSETOF__MethodTable__m_pParentMethodTable] cmp rcx, rdx je IsInst test rdx, rdx je DoneWithLoop ; unroll 1 mov rdx, qword ptr [rdx + OFFSETOF__MethodTable__m_pParentMethodTable] cmp rcx, rdx je IsInst test rdx, rdx je DoneWithLoop ; unroll 2 mov rdx, qword ptr [rdx + OFFSETOF__MethodTable__m_pParentMethodTable] cmp rcx, rdx je IsInst test rdx, rdx je DoneWithLoop ; unroll 3 mov rdx, qword ptr [rdx + OFFSETOF__MethodTable__m_pParentMethodTable] cmp rcx, rdx je IsInst test rdx, rdx jne CheckParent align 16 DoneWithLoop: if METHODTABLE_EQUIVALENCE_FLAGS gt 0 ; check if the instance is a proxy or has type equivalence ; get the MethodTable of the original Object (stored earlier in rax) mov rdx, [rax] test dword ptr [rdx + OFFSETOF__MethodTable__m_dwFlags], METHODTABLE_EQUIVALENCE_FLAGS jne SlowPath endif ; METHODTABLE_EQUIVALENCE_FLAGS gt 0 ; we didn't find a match in the ParentMethodTable hierarchy ; and it isn't a proxy and doesn't have type equivalence, return NULL xor eax, eax ret if METHODTABLE_EQUIVALENCE_FLAGS gt 0 SlowPath: ; Set up the args to call JITutil_IsInstanceOfAny. Note that rcx already contains ; the MethodTable* mov rdx, rax ; rdx = Object* ; Call out to JITutil_IsInstanceOfAny to handle the proxy/equivalence case. jmp JITutil_IsInstanceOfAny endif ; METHODTABLE_EQUIVALENCE_FLAGS gt 0 ; if it is a null instance then rax is null ; if they match then rax contains the instance align 16 IsInst: REPRET LEAF_END JIT_IsInstanceOfClass2, _TEXT ; TODO: this is not necessary... we will be calling JIT_ChkCastClass2 all of the time ; now that the JIT inlines the null check and the exact MT comparison... Or are ; they only doing it on the IBC hot path??? Look into that. If it will turn out ; to be cold then put it down at the bottom. ;EXTERN_C Object* JIT_ChkCastClass(MethodTable* pMT, Object* pObject); LEAF_ENTRY JIT_ChkCastClass, _TEXT ; check if the instance is null test rdx, rdx je IsNullInst ; check if the MethodTable for the instance matches pMT cmp rcx, qword ptr [rdx] jne JIT_ChkCastClassSpecial IsNullInst: ; setup the return value for a match or null mov rax, rdx ret LEAF_END JIT_ChkCastClass, _TEXT LEAF_ENTRY JIT_ChkCastClassSpecial, _TEXT ; save off the instance in case it is a proxy, and to setup ; our return value for a match mov rax, rdx ; check if the parent class matches. ; start by putting the MethodTable for the instance in rdx mov rdx, qword ptr [rdx] align 16 CheckParent: ; NULL parent MethodTable* indicates that we're at the top of the hierarchy ; unroll 0 mov rdx, qword ptr [rdx + OFFSETOF__MethodTable__m_pParentMethodTable] cmp rcx, rdx je IsInst test rdx, rdx je DoneWithLoop ; unroll 1 mov rdx, qword ptr [rdx + OFFSETOF__MethodTable__m_pParentMethodTable] cmp rcx, rdx je IsInst test rdx, rdx je DoneWithLoop ; unroll 2 mov rdx, qword ptr [rdx + OFFSETOF__MethodTable__m_pParentMethodTable] cmp rcx, rdx je IsInst test rdx, rdx je DoneWithLoop ; unroll 3 mov rdx, qword ptr [rdx + OFFSETOF__MethodTable__m_pParentMethodTable] cmp rcx, rdx je IsInst test rdx, rdx jne CheckParent align 16 DoneWithLoop: ; Set up the args to call JITutil_ChkCastAny. Note that rcx already contains the MethodTable* mov rdx, rax ; rdx = Object* ; Call out to JITutil_ChkCastAny to handle the proxy case and throw a rich ; InvalidCastException in case of failure. jmp JITutil_ChkCastAny ; if it is a null instance then rax is null ; if they match then rax contains the instance align 16 IsInst: REPRET LEAF_END JIT_ChkCastClassSpecial, _TEXT FIX_INDIRECTION macro Reg ifdef FEATURE_PREJIT test Reg, 1 jz @F mov Reg, [Reg-1] @@: endif endm ; PERF TODO: consider prefetching the entire interface map into the cache ; For all bizarre castes this quickly fails and falls back onto the JITutil_IsInstanceOfAny ; helper, this means that all failure cases take the slow path as well. ; ; This can trash r10/r11 LEAF_ENTRY JIT_IsInstanceOfInterface, _TEXT test rdx, rdx jz IsNullInst ; get methodtable mov rax, [rdx] mov r11w, word ptr [rax + OFFSETOF__MethodTable__m_wNumInterfaces] test r11w, r11w jz DoBizarre ; fetch interface map ptr mov rax, [rax + OFFSETOF__MethodTable__m_pInterfaceMap] ; r11 holds number of interfaces ; rax is pointer to beginning of interface map list align 16 Top: ; rax -> InterfaceInfo_t* into the interface map, aligned to 4 entries ; use offsets of SIZEOF__InterfaceInfo_t to get at entry 1, 2, 3 in this ; block. If we make it through the full 4 without a hit we'll move to ; the next block of 4 and try again. ; unroll 0 ifdef FEATURE_PREJIT mov r10, [rax + OFFSETOF__InterfaceInfo_t__m_pMethodTable] FIX_INDIRECTION r10 cmp rcx, r10 else cmp rcx, [rax + OFFSETOF__InterfaceInfo_t__m_pMethodTable] endif je Found ; move to next entry in list dec r11w jz DoBizarre ; unroll 1 ifdef FEATURE_PREJIT mov r10, [rax + SIZEOF__InterfaceInfo_t + OFFSETOF__InterfaceInfo_t__m_pMethodTable] FIX_INDIRECTION r10 cmp rcx, r10 else cmp rcx, [rax + SIZEOF__InterfaceInfo_t + OFFSETOF__InterfaceInfo_t__m_pMethodTable] endif je Found ; move to next entry in list dec r11w jz DoBizarre ; unroll 2 ifdef FEATURE_PREJIT mov r10, [rax + 2 * SIZEOF__InterfaceInfo_t + OFFSETOF__InterfaceInfo_t__m_pMethodTable] FIX_INDIRECTION r10 cmp rcx, r10 else cmp rcx, [rax + 2 * SIZEOF__InterfaceInfo_t + OFFSETOF__InterfaceInfo_t__m_pMethodTable] endif je Found ; move to next entry in list dec r11w jz DoBizarre ; unroll 3 ifdef FEATURE_PREJIT mov r10, [rax + 3 * SIZEOF__InterfaceInfo_t + OFFSETOF__InterfaceInfo_t__m_pMethodTable] FIX_INDIRECTION r10 cmp rcx, r10 else cmp rcx, [rax + 3 * SIZEOF__InterfaceInfo_t + OFFSETOF__InterfaceInfo_t__m_pMethodTable] endif je Found ; move to next entry in list dec r11w jz DoBizarre ; if we didn't find the entry in this loop jump to the next 4 entries in the map add rax, 4 * SIZEOF__InterfaceInfo_t jmp Top DoBizarre: mov rax, [rdx] test dword ptr [rax + OFFSETOF__MethodTable__m_dwFlags], METHODTABLE_NONTRIVIALINTERFACECAST_FLAGS jnz NonTrivialCast xor rax,rax ret align 16 Found: IsNullInst: ; return the successful instance mov rax, rdx ret NonTrivialCast: jmp JITutil_IsInstanceOfInterface LEAF_END JIT_IsInstanceOfInterface, _TEXT ; For all bizarre castes this quickly fails and falls back onto the JITutil_ChkCastInterface ; helper, this means that all failure cases take the slow path as well. ; ; This can trash r10/r11 LEAF_ENTRY JIT_ChkCastInterface, _TEXT test rdx, rdx jz IsNullInst ; get methodtable mov rax, [rdx] mov r11w, word ptr [rax + OFFSETOF__MethodTable__m_wNumInterfaces] ; speculatively fetch interface map ptr mov rax, [rax + OFFSETOF__MethodTable__m_pInterfaceMap] test r11w, r11w jz DoBizarre ; r11 holds number of interfaces ; rax is pointer to beginning of interface map list align 16 Top: ; rax -> InterfaceInfo_t* into the interface map, aligned to 4 entries ; use offsets of SIZEOF__InterfaceInfo_t to get at entry 1, 2, 3 in this ; block. If we make it through the full 4 without a hit we'll move to ; the next block of 4 and try again. ; unroll 0 ifdef FEATURE_PREJIT mov r10, [rax + OFFSETOF__InterfaceInfo_t__m_pMethodTable] FIX_INDIRECTION r10 cmp rcx, r10 else cmp rcx, [rax + OFFSETOF__InterfaceInfo_t__m_pMethodTable] endif je Found ; move to next entry in list dec r11w jz DoBizarre ; unroll 1 ifdef FEATURE_PREJIT mov r10, [rax + SIZEOF__InterfaceInfo_t + OFFSETOF__InterfaceInfo_t__m_pMethodTable] FIX_INDIRECTION r10 cmp rcx, r10 else cmp rcx, [rax + SIZEOF__InterfaceInfo_t + OFFSETOF__InterfaceInfo_t__m_pMethodTable] endif je Found ; move to next entry in list dec r11w jz DoBizarre ; unroll 2 ifdef FEATURE_PREJIT mov r10, [rax + 2 * SIZEOF__InterfaceInfo_t + OFFSETOF__InterfaceInfo_t__m_pMethodTable] FIX_INDIRECTION r10 cmp rcx, r10 else cmp rcx, [rax + 2 * SIZEOF__InterfaceInfo_t + OFFSETOF__InterfaceInfo_t__m_pMethodTable] endif je Found ; move to next entry in list dec r11w jz DoBizarre ; unroll 3 ifdef FEATURE_PREJIT mov r10, [rax + 3 * SIZEOF__InterfaceInfo_t + OFFSETOF__InterfaceInfo_t__m_pMethodTable] FIX_INDIRECTION r10 cmp rcx, r10 else cmp rcx, [rax + 3 * SIZEOF__InterfaceInfo_t + OFFSETOF__InterfaceInfo_t__m_pMethodTable] endif je Found ; move to next entry in list dec r11w jz DoBizarre ; if we didn't find the entry in this loop jump to the next 4 entries in the map add rax, 4 * SIZEOF__InterfaceInfo_t jmp Top DoBizarre: jmp JITutil_ChkCastInterface align 16 Found: IsNullInst: ; return either NULL or the successful instance mov rax, rdx ret LEAF_END JIT_ChkCastInterface, _TEXT ; There is an even more optimized version of these helpers possible which takes ; advantage of knowledge of which way the ephemeral heap is growing to only do 1/2 ; that check (this is more significant in the JIT_WriteBarrier case). ; ; Additionally we can look into providing helpers which will take the src/dest from ; specific registers (like x86) which _could_ (??) make for easier register allocation ; for the JIT64, however it might lead to having to have some nasty code that treats ; these guys really special like... :(. ; ; Version that does the move, checks whether or not it's in the GC and whether or not ; it needs to have it's card updated ; ; void JIT_CheckedWriteBarrier(Object** dst, Object* src) LEAF_ENTRY JIT_CheckedWriteBarrier, _TEXT ; When WRITE_BARRIER_CHECK is defined _NotInHeap will write the reference ; but if it isn't then it will just return. ; ; See if this is in GCHeap cmp rcx, [g_lowest_address] jb NotInHeap cmp rcx, [g_highest_address] jnb NotInHeap jmp JIT_WriteBarrier NotInHeap: ; See comment above about possible AV mov [rcx], rdx ret LEAF_END_MARKED JIT_CheckedWriteBarrier, _TEXT ; Mark start of the code region that we patch at runtime LEAF_ENTRY JIT_PatchedCodeStart, _TEXT ret LEAF_END JIT_PatchedCodeStart, _TEXT ; This is used by the mechanism to hold either the JIT_WriteBarrier_PreGrow ; or JIT_WriteBarrier_PostGrow code (depending on the state of the GC). It _WILL_ ; change at runtime as the GC changes. Initially it should simply be a copy of the ; larger of the two functions (JIT_WriteBarrier_PostGrow) to ensure we have created ; enough space to copy that code in. LEAF_ENTRY JIT_WriteBarrier, _TEXT align 16 ifdef _DEBUG ; In debug builds, this just contains jump to the debug version of the write barrier by default jmp JIT_WriteBarrier_Debug endif ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP ; JIT_WriteBarrier_WriteWatch_PostGrow64 ; Regarding patchable constants: ; - 64-bit constants have to be loaded into a register ; - The constants have to be aligned to 8 bytes so that they can be patched easily ; - The constant loads have been located to minimize NOP padding required to align the constants ; - Using different registers for successive constant loads helps pipeline better. Should we decide to use a special ; non-volatile calling convention, this should be changed to use just one register. ; Do the move into the GC . It is correct to take an AV here, the EH code ; figures out that this came from a WriteBarrier and correctly maps it back ; to the managed method which called the WriteBarrier (see setup in ; InitializeExceptionHandling, vm\exceptionhandling.cpp). mov [rcx], rdx ; Update the write watch table if necessary mov rax, rcx mov r8, 0F0F0F0F0F0F0F0F0h shr rax, 0Ch ; SoftwareWriteWatch::AddressToTableByteIndexShift NOP_2_BYTE ; padding for alignment of constant mov r9, 0F0F0F0F0F0F0F0F0h add rax, r8 cmp byte ptr [rax], 0h jne CheckCardTable mov byte ptr [rax], 0FFh NOP_3_BYTE ; padding for alignment of constant ; Check the lower and upper ephemeral region bounds CheckCardTable: cmp rdx, r9 jb Exit NOP_3_BYTE ; padding for alignment of constant mov r8, 0F0F0F0F0F0F0F0F0h cmp rdx, r8 jae Exit nop ; padding for alignment of constant mov rax, 0F0F0F0F0F0F0F0F0h ; Touch the card table entry, if not already dirty. shr rcx, 0Bh cmp byte ptr [rcx + rax], 0FFh jne UpdateCardTable REPRET UpdateCardTable: mov byte ptr [rcx + rax], 0FFh ret align 16 Exit: REPRET else ; JIT_WriteBarrier_PostGrow64 ; Do the move into the GC . It is correct to take an AV here, the EH code ; figures out that this came from a WriteBarrier and correctly maps it back ; to the managed method which called the WriteBarrier (see setup in ; InitializeExceptionHandling, vm\exceptionhandling.cpp). mov [rcx], rdx NOP_3_BYTE ; padding for alignment of constant ; Can't compare a 64 bit immediate, so we have to move them into a ; register. Values of these immediates will be patched at runtime. ; By using two registers we can pipeline better. Should we decide to use ; a special non-volatile calling convention, this should be changed to ; just one. mov rax, 0F0F0F0F0F0F0F0F0h ; Check the lower and upper ephemeral region bounds cmp rdx, rax jb Exit nop ; padding for alignment of constant mov r8, 0F0F0F0F0F0F0F0F0h cmp rdx, r8 jae Exit nop ; padding for alignment of constant mov rax, 0F0F0F0F0F0F0F0F0h ; Touch the card table entry, if not already dirty. shr rcx, 0Bh cmp byte ptr [rcx + rax], 0FFh jne UpdateCardTable REPRET UpdateCardTable: mov byte ptr [rcx + rax], 0FFh ret align 16 Exit: REPRET endif ; make sure this guy is bigger than any of the other guys align 16 nop LEAF_END_MARKED JIT_WriteBarrier, _TEXT ifndef FEATURE_IMPLICIT_TLS LEAF_ENTRY GetThread, _TEXT ; the default implementation will just jump to one that returns null until ; MakeOptimizedTlsGetter is run which will overwrite this with the actual ; implementation. jmp short GetTLSDummy ; ; insert enough NOPS to be able to insert the largest optimized TLS getter ; that we might need, it is important that the TLS getter doesn't overwrite ; into the dummy getter. ; db (TLS_GETTER_MAX_SIZE_ASM - 2) DUP (0CCh) LEAF_END GetThread, _TEXT LEAF_ENTRY GetAppDomain, _TEXT ; the default implementation will just jump to one that returns null until ; MakeOptimizedTlsGetter is run which will overwrite this with the actual ; implementation. jmp short GetTLSDummy ; ; insert enough NOPS to be able to insert the largest optimized TLS getter ; that we might need, it is important that the TLS getter doesn't overwrite ; into the dummy getter. ; db (TLS_GETTER_MAX_SIZE_ASM - 2) DUP (0CCh) LEAF_END GetAppDomain, _TEXT LEAF_ENTRY GetTLSDummy, _TEXT xor rax, rax ret LEAF_END GetTLSDummy, _TEXT LEAF_ENTRY ClrFlsGetBlock, _TEXT ; the default implementation will just jump to one that returns null until ; MakeOptimizedTlsGetter is run which will overwrite this with the actual ; implementation. jmp short GetTLSDummy ; ; insert enough NOPS to be able to insert the largest optimized TLS getter ; that we might need, it is important that the TLS getter doesn't overwrite ; into the dummy getter. ; db (TLS_GETTER_MAX_SIZE_ASM - 2) DUP (0CCh) LEAF_END ClrFlsGetBlock, _TEXT endif ; Mark start of the code region that we patch at runtime LEAF_ENTRY JIT_PatchedCodeLast, _TEXT ret LEAF_END JIT_PatchedCodeLast, _TEXT ; JIT_ByRefWriteBarrier has weird symantics, see usage in StubLinkerX86.cpp ; ; Entry: ; RDI - address of ref-field (assigned to) ; RSI - address of the data (source) ; RCX is trashed ; RAX is trashed when FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP is defined ; Exit: ; RDI, RSI are incremented by SIZEOF(LPVOID) LEAF_ENTRY JIT_ByRefWriteBarrier, _TEXT mov rcx, [rsi] ; If !WRITE_BARRIER_CHECK do the write first, otherwise we might have to do some ShadowGC stuff ifndef WRITE_BARRIER_CHECK ; rcx is [rsi] mov [rdi], rcx endif ; When WRITE_BARRIER_CHECK is defined _NotInHeap will write the reference ; but if it isn't then it will just return. ; ; See if this is in GCHeap cmp rdi, [g_lowest_address] jb NotInHeap cmp rdi, [g_highest_address] jnb NotInHeap ifdef WRITE_BARRIER_CHECK ; we can only trash rcx in this function so in _DEBUG we need to save ; some scratch registers. push r10 push r11 push rax ; **ALSO update the shadow GC heap if that is enabled** ; Do not perform the work if g_GCShadow is 0 cmp g_GCShadow, 0 je NoShadow ; If we end up outside of the heap don't corrupt random memory mov r10, rdi sub r10, [g_lowest_address] jb NoShadow ; Check that our adjusted destination is somewhere in the shadow gc add r10, [g_GCShadow] cmp r10, [g_GCShadowEnd] ja NoShadow ; Write ref into real GC mov [rdi], rcx ; Write ref into shadow GC mov [r10], rcx ; Ensure that the write to the shadow heap occurs before the read from ; the GC heap so that race conditions are caught by INVALIDGCVALUE mfence ; Check that GC/ShadowGC values match mov r11, [rdi] mov rax, [r10] cmp rax, r11 je DoneShadow mov r11, INVALIDGCVALUE mov [r10], r11 jmp DoneShadow ; If we don't have a shadow GC we won't have done the write yet NoShadow: mov [rdi], rcx ; If we had a shadow GC then we already wrote to the real GC at the same time ; as the shadow GC so we want to jump over the real write immediately above. ; Additionally we know for sure that we are inside the heap and therefore don't ; need to replicate the above checks. DoneShadow: pop rax pop r11 pop r10 endif ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP ; Update the write watch table if necessary cmp byte ptr [g_sw_ww_enabled_for_gc_heap], 0h je CheckCardTable mov rax, rdi shr rax, 0Ch ; SoftwareWriteWatch::AddressToTableByteIndexShift add rax, qword ptr [g_sw_ww_table] cmp byte ptr [rax], 0h jne CheckCardTable mov byte ptr [rax], 0FFh endif ; See if we can just quick out CheckCardTable: cmp rcx, [g_ephemeral_low] jb Exit cmp rcx, [g_ephemeral_high] jnb Exit ; move current rdi value into rcx and then increment the pointers mov rcx, rdi add rsi, 8h add rdi, 8h ; Check if we need to update the card table ; Calc pCardByte shr rcx, 0Bh add rcx, [g_card_table] ; Check if this card is dirty cmp byte ptr [rcx], 0FFh jne UpdateCardTable REPRET UpdateCardTable: mov byte ptr [rcx], 0FFh ret align 16 NotInHeap: ; If WRITE_BARRIER_CHECK then we won't have already done the mov and should do it here ; If !WRITE_BARRIER_CHECK we want _NotInHeap and _Leave to be the same and have both ; 16 byte aligned. ifdef WRITE_BARRIER_CHECK ; rcx is [rsi] mov [rdi], rcx endif Exit: ; Increment the pointers before leaving add rdi, 8h add rsi, 8h ret LEAF_END JIT_ByRefWriteBarrier, _TEXT g_pObjectClass equ ?g_pObjectClass@@3PEAVMethodTable@@EA EXTERN g_pObjectClass:qword extern ArrayStoreCheck:proc extern ObjIsInstanceOfNoGC:proc ; TODO: put definition for this in asmconstants.h CanCast equ 1 ;__declspec(naked) void F_CALL_CONV JIT_Stelem_Ref(PtrArray* array, unsigned idx, Object* val) LEAF_ENTRY JIT_Stelem_Ref, _TEXT ; check for null PtrArray* test rcx, rcx je ThrowNullReferenceException ; we only want the lower 32-bits of edx, it might be dirty or edx, edx ; check that index is in bounds cmp edx, dword ptr [rcx + OFFSETOF__PtrArray__m_NumComponents] ; 8h -> array size offset jae ThrowIndexOutOfRangeException ; r10 = Array MT mov r10, [rcx] ; if we're assigning a null object* then we don't need a write barrier test r8, r8 jz AssigningNull ifdef CHECK_APP_DOMAIN_LEAKS ; get Array TypeHandle mov r9, [r10 + OFFSETOF__MethodTable__m_ElementType] ; 10h -> typehandle offset ; check for non-MT test r9, 2 jnz NoCheck ; Check VMflags of element type mov r9, [r9 + OFFSETOF__MethodTable__m_pEEClass] mov r9d, dword ptr [r9 + OFFSETOF__EEClass__m_wAuxFlags] test r9d, EEClassFlags jnz ArrayStoreCheck_Helper NoCheck: endif mov r9, [r10 + OFFSETOF__MethodTable__m_ElementType] ; 10h -> typehandle offset ; check for exact match cmp r9, [r8] jne NotExactMatch DoWrite: lea rcx, [rcx + 8*rdx + OFFSETOF__PtrArray__m_Array] mov rdx, r8 ; JIT_WriteBarrier(Object** dst, Object* src) jmp JIT_WriteBarrier AssigningNull: ; write barrier is not needed for assignment of NULL references mov [rcx + 8*rdx + OFFSETOF__PtrArray__m_Array], r8 ret NotExactMatch: cmp r9, [g_pObjectClass] je DoWrite jmp JIT_Stelem_Ref__ObjIsInstanceOfNoGC_Helper ThrowNullReferenceException: mov rcx, CORINFO_NullReferenceException_ASM jmp JIT_InternalThrow ThrowIndexOutOfRangeException: mov rcx, CORINFO_IndexOutOfRangeException_ASM jmp JIT_InternalThrow LEAF_END JIT_Stelem_Ref, _TEXT NESTED_ENTRY JIT_Stelem_Ref__ObjIsInstanceOfNoGC_Helper, _TEXT alloc_stack MIN_SIZE save_reg_postrsp rcx, MIN_SIZE + 8h save_reg_postrsp rdx, MIN_SIZE + 10h save_reg_postrsp r8, MIN_SIZE + 18h END_PROLOGUE ; need to get TypeHandle before setting rcx to be the Obj* because that trashes the PtrArray* mov rdx, r9 mov rcx, r8 ; TypeHandle::CastResult ObjIsInstanceOfNoGC(Object *pElement, TypeHandle toTypeHnd) call ObjIsInstanceOfNoGC mov rcx, [rsp + MIN_SIZE + 8h] mov rdx, [rsp + MIN_SIZE + 10h] mov r8, [rsp + MIN_SIZE + 18h] cmp eax, CanCast jne NeedCheck lea rcx, [rcx + 8*rdx + OFFSETOF__PtrArray__m_Array] mov rdx, r8 add rsp, MIN_SIZE ; JIT_WriteBarrier(Object** dst, Object* src) jmp JIT_WriteBarrier NeedCheck: add rsp, MIN_SIZE jmp JIT_Stelem_Ref__ArrayStoreCheck_Helper NESTED_END JIT_Stelem_Ref__ObjIsInstanceOfNoGC_Helper, _TEXT ; Need to save r8 to provide a stack address for the Object* NESTED_ENTRY JIT_Stelem_Ref__ArrayStoreCheck_Helper, _TEXT alloc_stack MIN_SIZE save_reg_postrsp rcx, MIN_SIZE + 8h save_reg_postrsp rdx, MIN_SIZE + 10h save_reg_postrsp r8, MIN_SIZE + 18h END_PROLOGUE lea rcx, [rsp + MIN_SIZE + 18h] lea rdx, [rsp + MIN_SIZE + 8h] ; HCIMPL2(FC_INNER_RET, ArrayStoreCheck, Object** pElement, PtrArray** pArray) call ArrayStoreCheck mov rcx, [rsp + MIN_SIZE + 8h] mov rdx, [rsp + MIN_SIZE + 10h] mov r8, [rsp + MIN_SIZE + 18h] lea rcx, [rcx + 8*rdx + OFFSETOF__PtrArray__m_Array] mov rdx, r8 add rsp, MIN_SIZE ; JIT_WriteBarrier(Object** dst, Object* src) jmp JIT_WriteBarrier NESTED_END JIT_Stelem_Ref__ArrayStoreCheck_Helper, _TEXT extern JIT_FailFast:proc extern s_gsCookie:qword OFFSETOF_GSCOOKIE equ 0h OFFSETOF_FRAME equ OFFSETOF_GSCOOKIE + \ 8h ; ; incoming: ; ; rsp -> return address ; : ; ; Stack Layout: ; ; rsp-> callee scratch ; + 8h callee scratch ; +10h callee scratch ; +18h callee scratch ; : ; stack arguments ; : ; r13-> gsCookie ; + 8h __VFN_table ; +10h m_Next ; +18h m_pGCLayout ; +20h m_padding ; +28h m_rdi ; +30h m_rsi ; +38h m_rbx ; +40h m_rbp ; +48h m_r12 ; +50h m_r13 ; +58h m_r14 ; +60h m_r15 ; +68h m_ReturnAddress ; r12 -> // Caller's SP ; ; r14 = GetThread(); ; r15 = GetThread()->GetFrame(); // For restoring/popping the frame ; NESTED_ENTRY TailCallHelperStub, _TEXT PUSH_CALLEE_SAVED_REGISTERS alloc_stack 48h ; m_padding, m_pGCLayout, m_Next, __VFN_table, gsCookie, outgoing shadow area set_frame r13, 20h END_PROLOGUE ; ; This part is never executed, but we keep it here for reference ; int 3 if 0 ne 0 ; Save the caller's SP mov r12, rsp + ... ; ; fully initialize the TailCallFrame ; call TCF_GETMETHODFRAMEVPTR mov [r13 + OFFSETOF_FRAME], rax mov rax, s_gsCookie mov [r13 + OFFSETOF_GSCOOKIE], rax ; ; link the TailCallFrame ; CALL_GETTHREAD mov r14, rax mov r15, [rax + OFFSETOF__Thread__m_pFrame] mov [r13 + OFFSETOF_FRAME + OFFSETOF__Frame__m_Next], r15 lea r10, [r13 + OFFSETOF_FRAME] mov [rax + OFFSETOF__Thread__m_pFrame], r10 endif ; the pretend call would be here ; with the return address pointing this this real epilog PATCH_LABEL JIT_TailCallHelperStub_ReturnAddress ; our epilog (which also unlinks the TailCallFrame) ifdef _DEBUG mov rcx, s_gsCookie cmp [r13 + OFFSETOF_GSCookie], rcx je GoodGSCookie call JIT_FailFast GoodGSCookie: endif ; _DEBUG ; ; unlink the TailCallFrame ; mov [r14 + OFFSETOF__Thread__m_pFrame], r15 ; ; epilog ; lea rsp, [r13 + 28h] POP_CALLEE_SAVED_REGISTERS ret NESTED_END TailCallHelperStub, _TEXT end