mirror of
https://github.com/ZuneDev/dotnet-runtime.git
synced 2026-07-27 13:22:01 -07:00
1 parent
d47f040521
commit
3a71efaf36
12 files changed
-2837
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -1,283 +0,0 @@
|
||||
;; Licensed to the.NET Foundation under one or more agreements.
|
||||
;; The.NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
;; OS provided macros
|
||||
#include <ksarm.h>
|
||||
;; generated by the build from AsmOffsets.cpp
|
||||
#include "AsmOffsets.inc"
|
||||
|
||||
;;
|
||||
;; CONSTANTS -- INTEGER
|
||||
;;
|
||||
TSF_Attached equ 0x01
|
||||
TSF_SuppressGcStress equ 0x08
|
||||
TSF_DoNotTriggerGc equ 0x10
|
||||
TSF_SuppressGcStress__OR__TSF_DoNotTriggerGC equ 0x18
|
||||
|
||||
;; GC type flags
|
||||
GC_ALLOC_FINALIZE equ 1
|
||||
GC_ALLOC_ALIGN8_BIAS equ 4
|
||||
GC_ALLOC_ALIGN8 equ 8
|
||||
|
||||
;; GC minimal sized object. We use this to switch between 4 and 8 byte alignment in the GC heap (see AllocFast.asm).
|
||||
SIZEOF__MinObject equ 12
|
||||
ASSERT (SIZEOF__MinObject :MOD: 8) == 4
|
||||
|
||||
;; Note: these must match the defs in PInvokeTransitionFrameFlags
|
||||
PTFF_SAVE_R4 equ 0x00000001
|
||||
PTFF_SAVE_R5 equ 0x00000002
|
||||
PTFF_SAVE_R6 equ 0x00000004
|
||||
PTFF_SAVE_R7 equ 0x00000008
|
||||
PTFF_SAVE_R8 equ 0x00000010
|
||||
PTFF_SAVE_R9 equ 0x00000020
|
||||
PTFF_SAVE_R10 equ 0x00000040
|
||||
PTFF_SAVE_ALL_PRESERVED equ 0x00000077 ;; NOTE: FP is not included in this set!
|
||||
PTFF_SAVE_SP equ 0x00000100
|
||||
PTFF_SAVE_R0 equ 0x00000200 ;; R0 is saved if it contains a GC ref and we're in hijack handler
|
||||
PTFF_SAVE_ALL_SCRATCH equ 0x00003e00 ;; R0-R3,LR (R12 is trashed by the helpers anyway, but LR is relevant for loop hijacking
|
||||
PTFF_R0_IS_GCREF equ 0x00004000 ;; iff PTFF_SAVE_R0: set -> r0 is Object, clear -> r0 is scalar
|
||||
PTFF_R0_IS_BYREF equ 0x00008000 ;; iff PTFF_SAVE_R0: set -> r0 is ByRef, clear -> r0 is Object or scalar
|
||||
PTFF_THREAD_ABORT equ 0x00010000 ;; indicates that ThreadAbortException should be thrown when returning from the transition
|
||||
|
||||
;; These must match the TrapThreadsFlags enum
|
||||
TrapThreadsFlags_None equ 0
|
||||
TrapThreadsFlags_AbortInProgress equ 1
|
||||
TrapThreadsFlags_TrapThreads equ 2
|
||||
|
||||
;; This must match HwExceptionCode.STATUS_REDHAWK_THREAD_ABORT
|
||||
STATUS_REDHAWK_THREAD_ABORT equ 0x43
|
||||
|
||||
;;
|
||||
;; Rename fields of nested structs
|
||||
;;
|
||||
OFFSETOF__Thread__m_alloc_context__alloc_ptr equ OFFSETOF__Thread__m_rgbAllocContextBuffer + OFFSETOF__gc_alloc_context__alloc_ptr
|
||||
OFFSETOF__Thread__m_alloc_context__alloc_limit equ OFFSETOF__Thread__m_rgbAllocContextBuffer + OFFSETOF__gc_alloc_context__alloc_limit
|
||||
|
||||
|
||||
__tls_array equ 0x2C ;; offsetof(TEB, ThreadLocalStoragePointer)
|
||||
|
||||
;;
|
||||
;; MACROS
|
||||
;;
|
||||
|
||||
GBLS __SECTIONREL_tls_CurrentThread
|
||||
__SECTIONREL_tls_CurrentThread SETS "SECTIONREL_tls_CurrentThread"
|
||||
|
||||
MACRO
|
||||
INLINE_GETTHREAD $destReg, $trashReg
|
||||
EXTERN _tls_index
|
||||
|
||||
ldr $destReg, =_tls_index
|
||||
ldr $destReg, [$destReg]
|
||||
mrc p15, 0, $trashReg, c13, c0, 2
|
||||
ldr $trashReg, [$trashReg, #__tls_array]
|
||||
ldr $destReg, [$trashReg, $destReg, lsl #2]
|
||||
ldr $trashReg, $__SECTIONREL_tls_CurrentThread
|
||||
add $destReg, $trashReg
|
||||
MEND
|
||||
|
||||
;; INLINE_GETTHREAD_CONSTANT_POOL macro has to be used after the last function in the .asm file that used
|
||||
;; INLINE_GETTHREAD. Optionally, it can be also used after any function that used INLINE_GETTHREAD
|
||||
;; to improve density, or to reduce distance between the constant pool and its use.
|
||||
MACRO
|
||||
INLINE_GETTHREAD_CONSTANT_POOL
|
||||
EXTERN tls_CurrentThread
|
||||
|
||||
$__SECTIONREL_tls_CurrentThread
|
||||
DCD tls_CurrentThread
|
||||
RELOC 15 ;; SECREL
|
||||
|
||||
__SECTIONREL_tls_CurrentThread SETS "$__SECTIONREL_tls_CurrentThread":CC:"_"
|
||||
|
||||
MEND
|
||||
|
||||
MACRO
|
||||
INLINE_THREAD_UNHIJACK $threadReg, $trashReg1, $trashReg2
|
||||
;;
|
||||
;; Thread::Unhijack()
|
||||
;;
|
||||
ldr $trashReg1, [$threadReg, #OFFSETOF__Thread__m_pvHijackedReturnAddress]
|
||||
cbz $trashReg1, %ft0
|
||||
|
||||
ldr $trashReg2, [$threadReg, #OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation]
|
||||
str $trashReg1, [$trashReg2]
|
||||
mov $trashReg1, #0
|
||||
str $trashReg1, [$threadReg, #OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation]
|
||||
str $trashReg1, [$threadReg, #OFFSETOF__Thread__m_pvHijackedReturnAddress]
|
||||
0
|
||||
MEND
|
||||
|
||||
DEFAULT_FRAME_SAVE_FLAGS equ PTFF_SAVE_ALL_PRESERVED + PTFF_SAVE_SP
|
||||
|
||||
;;
|
||||
;; Macro used from unmanaged helpers called from managed code where the helper does not transition immediately
|
||||
;; into pre-emptive mode but may cause a GC and thus requires the stack is crawlable. This is typically the
|
||||
;; case for helpers that meddle in GC state (e.g. allocation helpers) where the code must remain in
|
||||
;; cooperative mode since it handles object references and internal GC state directly but a garbage collection
|
||||
;; may be inevitable. In these cases we need to be able to transition to pre-meptive mode deep within the
|
||||
;; unmanaged code but still be able to initialize the stack iterator at the first stack frame which may hold
|
||||
;; interesting GC references. In all our helper cases this corresponds to the most recent managed frame (e.g.
|
||||
;; the helper's caller).
|
||||
;;
|
||||
;; This macro builds a frame describing the current state of managed code.
|
||||
;;
|
||||
;; INVARIANTS
|
||||
;; - The macro assumes it defines the method prolog, it should typically be the first code in a method and
|
||||
;; certainly appear before any attempt to alter the stack pointer.
|
||||
;; - This macro uses trashReg (after its initial value has been saved in the frame) and upon exit trashReg
|
||||
;; will contain the address of transition frame.
|
||||
;;
|
||||
MACRO
|
||||
PUSH_COOP_PINVOKE_FRAME $trashReg
|
||||
|
||||
PROLOG_STACK_ALLOC 4 ; Save space for caller's SP
|
||||
PROLOG_PUSH {r4-r6,r8-r10} ; Save preserved registers
|
||||
PROLOG_STACK_ALLOC 8 ; Save space for flags and Thread*
|
||||
PROLOG_PUSH {r7} ; Save caller's FP
|
||||
PROLOG_PUSH {r11,lr} ; Save caller's frame-chain pointer and PC
|
||||
|
||||
; Compute SP value at entry to this method and save it in the last slot of the frame (slot #11).
|
||||
add $trashReg, sp, #(12 * 4)
|
||||
str $trashReg, [sp, #(11 * 4)]
|
||||
|
||||
; Record the bitmask of saved registers in the frame (slot #4).
|
||||
mov $trashReg, #DEFAULT_FRAME_SAVE_FLAGS
|
||||
str $trashReg, [sp, #(4 * 4)]
|
||||
|
||||
mov $trashReg, sp
|
||||
MEND
|
||||
|
||||
;; Pop the frame and restore register state preserved by PUSH_COOP_PINVOKE_FRAME
|
||||
MACRO
|
||||
POP_COOP_PINVOKE_FRAME
|
||||
EPILOG_POP {r11,lr} ; Restore caller's frame-chain pointer and PC (return address)
|
||||
EPILOG_POP {r7} ; Restore caller's FP
|
||||
EPILOG_STACK_FREE 8 ; Discard flags and Thread*
|
||||
EPILOG_POP {r4-r6,r8-r10} ; Restore preserved registers
|
||||
EPILOG_STACK_FREE 4 ; Discard caller's SP
|
||||
MEND
|
||||
|
||||
|
||||
; Macro used to assign an alternate name to a symbol containing characters normally disallowed in a symbol
|
||||
; name (e.g. C++ decorated names).
|
||||
MACRO
|
||||
SETALIAS $name, $symbol
|
||||
GBLS $name
|
||||
$name SETS "|$symbol|"
|
||||
MEND
|
||||
|
||||
|
||||
;
|
||||
; Helper macro: create a global label for the given name,
|
||||
; decorate it, and export it for external consumption.
|
||||
;
|
||||
|
||||
MACRO
|
||||
__ExportLabelName $FuncName
|
||||
|
||||
LCLS Name
|
||||
Name SETS "|$FuncName|"
|
||||
EXPORT $Name
|
||||
$Name
|
||||
MEND
|
||||
|
||||
;
|
||||
; Macro for indicating an alternate entry point into a function.
|
||||
;
|
||||
|
||||
MACRO
|
||||
LABELED_RETURN_ADDRESS $ReturnAddressName
|
||||
|
||||
; export the return address name, but do not perturb the code by forcing alignment
|
||||
__ExportLabelName $ReturnAddressName
|
||||
|
||||
; flush any pending literal pool stuff
|
||||
ROUT
|
||||
|
||||
MEND
|
||||
|
||||
MACRO
|
||||
EXPORT_POINTER_TO_ADDRESS $Name
|
||||
|
||||
1
|
||||
|
||||
AREA |.rdata|, ALIGN=4, DATA, READONLY
|
||||
|
||||
$Name
|
||||
|
||||
DCD %BT1
|
||||
|
||||
EXPORT $Name
|
||||
|
||||
TEXTAREA
|
||||
|
||||
ROUT
|
||||
|
||||
MEND
|
||||
|
||||
;-----------------------------------------------------------------------------
|
||||
; Macro used to check (in debug builds only) whether the stack is 64-bit aligned (a requirement before calling
|
||||
; out into C++/OS code). Invoke this directly after your prolog (if the stack frame size is fixed) or directly
|
||||
; before a call (if you have a frame pointer and a dynamic stack). A breakpoint will be invoked if the stack
|
||||
; is misaligned.
|
||||
;
|
||||
MACRO
|
||||
CHECK_STACK_ALIGNMENT
|
||||
|
||||
#ifdef _DEBUG
|
||||
push {r0}
|
||||
add r0, sp, #4
|
||||
tst r0, #7
|
||||
pop {r0}
|
||||
beq %F0
|
||||
EMIT_BREAKPOINT
|
||||
0
|
||||
#endif
|
||||
MEND
|
||||
|
||||
;; Loads a 32bit constant into destination register
|
||||
MACRO
|
||||
MOV32 $destReg, $constant
|
||||
|
||||
movw $destReg, #(($constant) & 0xFFFF)
|
||||
movt $destReg, #(($constant) >> 16)
|
||||
MEND
|
||||
|
||||
;;
|
||||
;; CONSTANTS -- SYMBOLS
|
||||
;;
|
||||
|
||||
SETALIAS G_LOWEST_ADDRESS, g_lowest_address
|
||||
SETALIAS G_HIGHEST_ADDRESS, g_highest_address
|
||||
SETALIAS G_EPHEMERAL_LOW, g_ephemeral_low
|
||||
SETALIAS G_EPHEMERAL_HIGH, g_ephemeral_high
|
||||
SETALIAS G_CARD_TABLE, g_card_table
|
||||
SETALIAS G_FREE_OBJECT_EETYPE, ?g_pFreeObjectEEType@@3PAVEEType@@A
|
||||
#ifdef FEATURE_GC_STRESS
|
||||
SETALIAS THREAD__HIJACKFORGCSTRESS, ?HijackForGcStress@Thread@@SAXPAUPAL_LIMITED_CONTEXT@@@Z
|
||||
#endif ;; FEATURE_GC_STRESS
|
||||
;;
|
||||
;; IMPORTS
|
||||
;;
|
||||
EXTERN RhpGcAlloc
|
||||
EXTERN RhDebugBreak
|
||||
EXTERN RhpWaitForGC2
|
||||
EXTERN RhExceptionHandling_FailedAllocation
|
||||
|
||||
|
||||
EXTERN $G_LOWEST_ADDRESS
|
||||
EXTERN $G_HIGHEST_ADDRESS
|
||||
EXTERN $G_EPHEMERAL_LOW
|
||||
EXTERN $G_EPHEMERAL_HIGH
|
||||
EXTERN $G_CARD_TABLE
|
||||
EXTERN RhpTrapThreads
|
||||
EXTERN $G_FREE_OBJECT_EETYPE
|
||||
|
||||
EXTERN RhThrowHwEx
|
||||
EXTERN RhThrowEx
|
||||
EXTERN RhRethrow
|
||||
|
||||
#ifdef FEATURE_GC_STRESS
|
||||
EXTERN RhpStressGc
|
||||
EXTERN $THREAD__HIJACKFORGCSTRESS
|
||||
#endif ;; FEATURE_GC_STRESS
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,361 +0,0 @@
|
||||
;; Licensed to the .NET Foundation under one or more agreements.
|
||||
;; The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
#include "AsmMacros.h"
|
||||
|
||||
TEXTAREA
|
||||
|
||||
;; ARM64TODO: do same fix here as on Arm64?
|
||||
SETALIAS g_fGcStressStarted, ?g_GCShadow@@3PAEA
|
||||
|
||||
EXTERN $g_fGcStressStarted
|
||||
|
||||
PROBE_SAVE_FLAGS_EVERYTHING equ DEFAULT_FRAME_SAVE_FLAGS + PTFF_SAVE_ALL_SCRATCH
|
||||
PROBE_SAVE_FLAGS_R0_IS_GCREF equ DEFAULT_FRAME_SAVE_FLAGS + PTFF_SAVE_R0 + PTFF_R0_IS_GCREF
|
||||
|
||||
|
||||
;; Build a map of symbols representing offsets into a transition frame (see PInvokeTransitionFrame in
|
||||
;; rhbinder.h and keep these two in sync.
|
||||
map 0
|
||||
m_ChainPointer field 4 ; r11 - OS frame chain used for quick stackwalks
|
||||
m_RIP field 4 ; lr
|
||||
m_FramePointer field 4 ; r7
|
||||
m_pThread field 4
|
||||
m_Flags field 4 ; bitmask of saved registers
|
||||
m_PreservedRegs field (4 * 6) ; r4-r6,r8-r10
|
||||
m_CallersSP field 4 ; sp at routine entry
|
||||
m_SavedR0 field 4 ; r0
|
||||
m_VolatileRegs field (4 * 4) ; r1-r3,lr
|
||||
m_ReturnVfpRegs field (8 * 4) ; d0-d3, not really part of the struct
|
||||
m_SavedAPSR field 4 ; saved condition codes
|
||||
PROBE_FRAME_SIZE field 0
|
||||
|
||||
;; Support for setting up a transition frame when performing a GC probe. In many respects this is very
|
||||
;; similar to the logic in PUSH_COOP_PINVOKE_FRAME in AsmMacros.h. In most cases setting up the
|
||||
;; transition frame comprises the entirety of the caller's prolog (and initial non-prolog code) and
|
||||
;; similarly for the epilog. Those cases can be dealt with using PROLOG_PROBE_FRAME and EPILOG_PROBE_FRAME
|
||||
;; defined below. For the special cases where additional work has to be done in the prolog we also provide
|
||||
;; the lower level macros ALLOC_PROBE_FRAME, FREE_PROBE_FRAME and INIT_PROBE_FRAME that allow more control
|
||||
;; to be asserted.
|
||||
;;
|
||||
;; Note that we currently employ a significant simplification of frame setup: we always allocate a
|
||||
;; maximally-sized PInvokeTransitionFrame and save all of the registers. Depending on the caller this can
|
||||
;; lead to upto five additional register saves (r0-r3,r12) or 20 bytes of stack space. I have done no
|
||||
;; analysis to see whether any of the worst cases occur on performance sensitive paths and whether the
|
||||
;; additional saves will show any measurable degradation.
|
||||
|
||||
;; Perform the parts of setting up a probe frame that can occur during the prolog (and indeed this macro
|
||||
;; can only be called from within the prolog).
|
||||
MACRO
|
||||
ALLOC_PROBE_FRAME
|
||||
|
||||
PROLOG_STACK_ALLOC 4 ; Space for saved APSR
|
||||
PROLOG_VPUSH {d0-d3} ; Save floating point return registers
|
||||
PROLOG_PUSH {r0-r3,lr} ; Save volatile registers
|
||||
PROLOG_STACK_ALLOC 4 ; Space for caller's SP
|
||||
PROLOG_PUSH {r4-r6,r8-r10} ; Save non-volatile registers
|
||||
PROLOG_STACK_ALLOC 8 ; Space for flags and Thread*
|
||||
PROLOG_PUSH {r7} ; Save caller's frame pointer
|
||||
PROLOG_PUSH {r11,lr} ; Save frame-chain pointer and return address
|
||||
MEND
|
||||
|
||||
;; Undo the effects of an ALLOC_PROBE_FRAME. This may only be called within an epilog. Note that all
|
||||
;; registers are restored (apart for sp and pc), even volatiles.
|
||||
MACRO
|
||||
FREE_PROBE_FRAME
|
||||
|
||||
EPILOG_POP {r11,lr} ; Restore frame-chain pointer and return address
|
||||
EPILOG_POP {r7} ; Restore caller's frame pointer
|
||||
EPILOG_STACK_FREE 8 ; Discard flags and Thread*
|
||||
EPILOG_POP {r4-r6,r8-r10} ; Restore non-volatile registers
|
||||
EPILOG_STACK_FREE 4 ; Discard caller's SP
|
||||
EPILOG_POP {r0-r3,lr} ; Restore volatile registers
|
||||
EPILOG_VPOP {d0-d3} ; Restore floating point return registers
|
||||
EPILOG_STACK_FREE 4 ; Space for saved APSR
|
||||
MEND
|
||||
|
||||
;; Complete the setup of a probe frame allocated with ALLOC_PROBE_FRAME with the initialization that can
|
||||
;; occur only outside the prolog (includes linking the frame to the current Thread). This macro assumes SP
|
||||
;; is invariant outside of the prolog.
|
||||
;;
|
||||
;; $threadReg : register containing the Thread* (this will be preserved)
|
||||
;; $trashReg : register that can be trashed by this macro
|
||||
;; $BITMASK : value to initialize m_Flags field with (register or #constant)
|
||||
;; $frameSize : total size of the method's stack frame (including probe frame size)
|
||||
MACRO
|
||||
INIT_PROBE_FRAME $threadReg, $trashReg, $BITMASK, $frameSize
|
||||
|
||||
str $threadReg, [sp, #m_pThread] ; Thread *
|
||||
mov $trashReg, $BITMASK ; Bitmask of preserved registers
|
||||
str $trashReg, [sp, #m_Flags]
|
||||
add $trashReg, sp, #$frameSize
|
||||
str $trashReg, [sp, #m_CallersSP]
|
||||
MEND
|
||||
|
||||
;; Simple macro to use when setting up the probe frame can comprise the entire prolog. Call this macro
|
||||
;; first in the method (no further prolog instructions can be added after this).
|
||||
;;
|
||||
;; $threadReg : register containing the Thread* (this will be preserved). If defaulted (specify |) then
|
||||
;; the current thread will be calculated inline into r2 ($trashReg must not equal r2 in
|
||||
;; this case)
|
||||
;; $trashReg : register that can be trashed by this macro
|
||||
;; $BITMASK : value to initialize m_Flags field with (register or #constant)
|
||||
MACRO
|
||||
PROLOG_PROBE_FRAME $threadReg, $trashReg, $BITMASK
|
||||
|
||||
; Local string tracking the name of the register in which the Thread* is kept. Defaults to the value
|
||||
; of $threadReg.
|
||||
LCLS __PPF_ThreadReg
|
||||
__PPF_ThreadReg SETS "$threadReg"
|
||||
|
||||
; Define the method prolog, allocating enough stack space for the PInvokeTransitionFrame and saving
|
||||
; incoming register values into it.
|
||||
ALLOC_PROBE_FRAME
|
||||
|
||||
; If the caller didn't provide a value for $threadReg then generate code to fetch the Thread* into r2.
|
||||
; Record that r2 holds the Thread* in our local variable.
|
||||
IF "$threadReg" == ""
|
||||
ASSERT "$trashReg" != "r2"
|
||||
__PPF_ThreadReg SETS "r2"
|
||||
INLINE_GETTHREAD $__PPF_ThreadReg, $trashReg
|
||||
ENDIF
|
||||
|
||||
; Perform the rest of the PInvokeTransitionFrame initialization.
|
||||
INIT_PROBE_FRAME $__PPF_ThreadReg, $trashReg, $BITMASK, PROBE_FRAME_SIZE
|
||||
str sp, [$__PPF_ThreadReg, #OFFSETOF__Thread__m_pDeferredTransitionFrame]
|
||||
MEND
|
||||
|
||||
; Simple macro to use when PROLOG_PROBE_FRAME was used to set up and initialize the prolog and
|
||||
; PInvokeTransitionFrame. This will define the epilog including a return via the restored LR.
|
||||
MACRO
|
||||
EPILOG_PROBE_FRAME
|
||||
|
||||
FREE_PROBE_FRAME
|
||||
EPILOG_RETURN
|
||||
MEND
|
||||
|
||||
|
||||
;;
|
||||
;; The prolog for all GC suspension hijacks (normal and stress). Fixes up the hijacked return address, and
|
||||
;; clears the hijack state.
|
||||
;;
|
||||
;; Register state on entry:
|
||||
;; All registers correct for return to the original return address.
|
||||
;;
|
||||
;; Register state on exit:
|
||||
;; r2: thread pointer
|
||||
;; r3: trashed
|
||||
;; r12: trashed
|
||||
;;
|
||||
MACRO
|
||||
FixupHijackedCallstack
|
||||
|
||||
;; r2 <- GetThread(), TRASHES r3
|
||||
INLINE_GETTHREAD r2, r3
|
||||
|
||||
;;
|
||||
;; Fix the stack by restoring the original return address
|
||||
;;
|
||||
ldr lr, [r2, #OFFSETOF__Thread__m_pvHijackedReturnAddress]
|
||||
|
||||
;; Clear hijack state
|
||||
mov r12, #0
|
||||
str r12, [r2, #OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation]
|
||||
str r12, [r2, #OFFSETOF__Thread__m_pvHijackedReturnAddress]
|
||||
|
||||
MEND
|
||||
|
||||
|
||||
MACRO
|
||||
HijackTargetFakeProlog
|
||||
|
||||
;; This is a fake entrypoint for the method that 'tricks' the OS into calling our personality routine.
|
||||
;; The code here should never be executed, and the unwind info is bogus, but we don't mind since the
|
||||
;; stack is broken by the hijack anyway until after we fix it below.
|
||||
PROLOG_PUSH {lr}
|
||||
nop ; We also need a nop here to simulate the implied bl instruction. Without
|
||||
; this, an OS-applied -2 will back up into the method prolog and the unwind
|
||||
; will not be applied as desired.
|
||||
|
||||
MEND
|
||||
|
||||
|
||||
;;
|
||||
;;
|
||||
;;
|
||||
;; GC Probe Hijack targets
|
||||
;;
|
||||
;;
|
||||
EXTERN RhpPInvokeExceptionGuard
|
||||
|
||||
|
||||
NESTED_ENTRY RhpGcProbeHijackWrapper, .text, RhpPInvokeExceptionGuard
|
||||
|
||||
HijackTargetFakeProlog
|
||||
|
||||
LABELED_RETURN_ADDRESS RhpGcProbeHijack
|
||||
|
||||
FixupHijackedCallstack
|
||||
|
||||
ldr r3, =RhpTrapThreads
|
||||
ldr r3, [r3]
|
||||
tst r3, #TrapThreadsFlags_TrapThreads
|
||||
bne %0
|
||||
bx lr
|
||||
0
|
||||
mov r12, #(DEFAULT_FRAME_SAVE_FLAGS + PTFF_SAVE_R0)
|
||||
b RhpWaitForGC
|
||||
NESTED_END RhpGcProbeHijackWrapper
|
||||
|
||||
#ifdef FEATURE_GC_STRESS
|
||||
;;
|
||||
;;
|
||||
;; GC Stress Hijack targets
|
||||
;;
|
||||
;;
|
||||
LEAF_ENTRY RhpGcStressHijack
|
||||
FixupHijackedCallstack
|
||||
mov r12, #DEFAULT_FRAME_SAVE_FLAGS + PTFF_SAVE_R0
|
||||
b RhpGcStressProbe
|
||||
LEAF_END RhpGcStressHijack
|
||||
|
||||
;;
|
||||
;; Worker for our GC stress probes. Do not call directly!!
|
||||
;; Instead, go through RhpGcStressHijack.
|
||||
;; This worker performs the GC Stress work and returns to the original return address.
|
||||
;;
|
||||
;; Register state on entry:
|
||||
;; r0: hijacked function return value
|
||||
;; r1: hijacked function return value
|
||||
;; r2: thread pointer
|
||||
;; r12: register bitmask
|
||||
;;
|
||||
;; Register state on exit:
|
||||
;; Scratch registers, except for r0, have been trashed
|
||||
;; All other registers restored as they were when the hijack was first reached.
|
||||
;;
|
||||
NESTED_ENTRY RhpGcStressProbe
|
||||
PROLOG_PROBE_FRAME r2, r3, r12
|
||||
|
||||
bl RhpStressGc
|
||||
|
||||
EPILOG_PROBE_FRAME
|
||||
NESTED_END RhpGcStressProbe
|
||||
#endif ;; FEATURE_GC_STRESS
|
||||
|
||||
EXTERN RhpThrowHwEx
|
||||
|
||||
NESTED_ENTRY RhpWaitForGC
|
||||
PROLOG_PROBE_FRAME r2, r3, r12
|
||||
|
||||
ldr r0, [r2, #OFFSETOF__Thread__m_pDeferredTransitionFrame]
|
||||
bl RhpWaitForGC2
|
||||
|
||||
ldr r2, [sp, #OFFSETOF__PInvokeTransitionFrame__m_Flags]
|
||||
tst r2, #PTFF_THREAD_ABORT
|
||||
bne %1
|
||||
|
||||
EPILOG_PROBE_FRAME
|
||||
|
||||
1
|
||||
FREE_PROBE_FRAME
|
||||
EPILOG_NOP mov r0, #STATUS_REDHAWK_THREAD_ABORT
|
||||
EPILOG_NOP mov r1, lr ;; return address as exception PC
|
||||
EPILOG_BRANCH RhpThrowHwEx
|
||||
|
||||
NESTED_END RhpWaitForGC
|
||||
|
||||
LEAF_ENTRY RhpGcPoll
|
||||
ldr r0, =RhpTrapThreads
|
||||
ldr r0, [r0]
|
||||
tst r0, #TrapThreadsFlags_TrapThreads
|
||||
bne RhpGcPollRare
|
||||
bx lr
|
||||
LEAF_END RhpGcPoll
|
||||
|
||||
NESTED_ENTRY RhpGcPollRare
|
||||
PROLOG_PROBE_FRAME |, r3, #PROBE_SAVE_FLAGS_EVERYTHING
|
||||
|
||||
; Unhijack this thread, if necessary.
|
||||
INLINE_THREAD_UNHIJACK r2, r0, r1 ;; trashes r0, r1
|
||||
|
||||
ldr r0, [r2, #OFFSETOF__Thread__m_pDeferredTransitionFrame]
|
||||
bl RhpWaitForGC2
|
||||
|
||||
EPILOG_PROBE_FRAME
|
||||
NESTED_END RhpGcPollRare
|
||||
|
||||
LEAF_ENTRY RhpGcPollStress
|
||||
;
|
||||
; loop hijacking is used instead
|
||||
;
|
||||
__debugbreak
|
||||
|
||||
LEAF_END RhpGcPollStress
|
||||
|
||||
|
||||
#ifdef FEATURE_GC_STRESS
|
||||
NESTED_ENTRY RhpHijackForGcStress
|
||||
PROLOG_PUSH {r0,r1} ; Save return value
|
||||
PROLOG_VPUSH {d0-d3} ; Save VFP return value
|
||||
|
||||
;;
|
||||
;; Setup a PAL_LIMITED_CONTEXT that looks like what you'd get if you had suspended this thread at the
|
||||
;; IP after the call to this helper.
|
||||
;;
|
||||
;; This is very likely overkill since the calculation of the return address should only need SP and
|
||||
;; LR, but this is test code, so I'm not too worried about efficiency.
|
||||
;;
|
||||
;; Setup a PAL_LIMITED_CONTEXT on the stack {
|
||||
;; we'll need to reserve the size of the D registers in the context
|
||||
;; compute in the funny way below to include any padding between LR and D
|
||||
DREG_SZ equ (SIZEOF__PAL_LIMITED_CONTEXT - (OFFSETOF__PAL_LIMITED_CONTEXT__LR + 4))
|
||||
|
||||
PROLOG_STACK_ALLOC DREG_SZ ;; Reserve space for d8-d15
|
||||
PROLOG_PUSH {r0,lr} ;; Reserve space for SP and store LR
|
||||
PROLOG_PUSH {r0,r4-r11,lr}
|
||||
;; } end PAL_LIMITED_CONTEXT
|
||||
|
||||
;; Compute and save SP at callsite.
|
||||
add r0, sp, #(SIZEOF__PAL_LIMITED_CONTEXT + 0x20 + 8) ;; +0x20 for vpush {d0-d3}, +8 for push {r0,r1}
|
||||
str r0, [sp, #OFFSETOF__PAL_LIMITED_CONTEXT__SP]
|
||||
|
||||
mov r0, sp ; Address of PAL_LIMITED_CONTEXT
|
||||
bl $THREAD__HIJACKFORGCSTRESS
|
||||
|
||||
;; epilog
|
||||
EPILOG_POP {r0,r4-r11,lr}
|
||||
EPILOG_STACK_FREE DREG_SZ + 8 ; Discard saved SP and LR and space for d8-d15
|
||||
EPILOG_VPOP {d0-d3} ; Restore VFP return value
|
||||
EPILOG_POP {r0,r1} ; Restore return value
|
||||
bx lr
|
||||
NESTED_END RhpHijackForGcStress
|
||||
#endif ;; FEATURE_GC_STRESS
|
||||
|
||||
#ifdef FEATURE_GC_STRESS
|
||||
|
||||
;;
|
||||
;; INVARIANT: Don't trash the argument registers, the binder codegen depends on this.
|
||||
;;
|
||||
LEAF_ENTRY RhpSuppressGcStress
|
||||
|
||||
push {r0-r2}
|
||||
INLINE_GETTHREAD r0, r1
|
||||
|
||||
Retry
|
||||
ldrex r1, [r0, #OFFSETOF__Thread__m_ThreadStateFlags]
|
||||
orr r1, #TSF_SuppressGcStress
|
||||
strex r2, r1, [r0, #OFFSETOF__Thread__m_ThreadStateFlags]
|
||||
cbz r2, Success
|
||||
b Retry
|
||||
|
||||
Success
|
||||
pop {r0-r2}
|
||||
bx lr
|
||||
|
||||
LEAF_END RhpSuppressGcStress
|
||||
#endif ;; FEATURE_GC_STRESS
|
||||
|
||||
INLINE_GETTHREAD_CONSTANT_POOL
|
||||
|
||||
end
|
||||
@@ -1,83 +0,0 @@
|
||||
;; Licensed to the .NET Foundation under one or more agreements.
|
||||
;; The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
|
||||
#include "kxarm.h"
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; DATA SECTIONS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
__tls_array equ 0x2C ;; offsetof(TEB, ThreadLocalStoragePointer)
|
||||
|
||||
POINTER_SIZE equ 0x04
|
||||
|
||||
;; TLS variables
|
||||
AREA |.tls$|, DATA
|
||||
ThunkParamSlot % 0x4
|
||||
|
||||
TEXTAREA
|
||||
|
||||
EXTERN _tls_index
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Interop Thunks Helpers ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;;
|
||||
;; RhCommonStub
|
||||
;;
|
||||
NESTED_ENTRY RhCommonStub
|
||||
;; Custom calling convention:
|
||||
;; red zone has pointer to the current thunk's data block (data contains 2 pointer values: context + target pointers)
|
||||
;; Copy red zone value into r12 so that the PROLOG_PUSH doesn't destroy it
|
||||
PROLOG_NOP ldr r12, [sp, #-4]
|
||||
PROLOG_PUSH {r0-r3}
|
||||
|
||||
;; Save context data into the ThunkParamSlot thread-local variable
|
||||
;; A pointer to the delegate and function pointer for open static delegate should have been saved in the thunk's context cell during thunk allocation
|
||||
ldr r3, =_tls_index
|
||||
ldr r2, [r3]
|
||||
mrc p15, #0, r3, c13, c0, #2
|
||||
ldr r3, [r3, #__tls_array]
|
||||
ldr r2, [r3, r2, lsl #2] ;; r2 <- our TLS base
|
||||
|
||||
;; r2 = base address of TLS data
|
||||
;; r12 = address of context cell in thunk's data
|
||||
|
||||
;; store thunk address in thread static
|
||||
ldr r1, [r12]
|
||||
ldr r3, =ThunkParamSlot
|
||||
str r1, [r2, r3] ;; ThunkParamSlot <- context slot data
|
||||
|
||||
;; Now load the target address and jump to it.
|
||||
ldr r12, [r12, #POINTER_SIZE]
|
||||
EPILOG_POP {r0-r3}
|
||||
bx r12
|
||||
NESTED_END RhCommonStub
|
||||
|
||||
|
||||
;;
|
||||
;; IntPtr RhGetCommonStubAddress()
|
||||
;;
|
||||
LEAF_ENTRY RhGetCommonStubAddress
|
||||
ldr r0, =RhCommonStub
|
||||
bx lr
|
||||
LEAF_END RhGetCommonStubAddress
|
||||
|
||||
|
||||
;;
|
||||
;; IntPtr RhGetCurrentThunkContext()
|
||||
;;
|
||||
LEAF_ENTRY RhGetCurrentThunkContext
|
||||
|
||||
ldr r3, =_tls_index
|
||||
ldr r2, [r3]
|
||||
mrc p15, #0, r3, c13, c0, #2
|
||||
ldr r3, [r3, #__tls_array]
|
||||
ldr r2, [r3, r2, lsl #2] ;; r2 <- our TLS base
|
||||
|
||||
ldr r3, =ThunkParamSlot
|
||||
ldr r0, [r2, r3] ;; r0 <- ThunkParamSlot
|
||||
|
||||
bx lr
|
||||
LEAF_END RhGetCurrentThunkContext
|
||||
|
||||
END
|
||||
@@ -1,10 +0,0 @@
|
||||
;; Licensed to the .NET Foundation under one or more agreements.
|
||||
;; The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
#include "AsmMacros.h"
|
||||
|
||||
TEXTAREA
|
||||
|
||||
LEAF_END
|
||||
|
||||
end
|
||||
@@ -1,8 +0,0 @@
|
||||
;; Licensed to the .NET Foundation under one or more agreements.
|
||||
;; The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
#include "AsmMacros.h"
|
||||
|
||||
TEXTAREA
|
||||
|
||||
INLINE_GETTHREAD_CONSTANT_POOL
|
||||
@@ -1,141 +0,0 @@
|
||||
;; Licensed to the .NET Foundation under one or more agreements.
|
||||
;; The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
#include "AsmMacros.h"
|
||||
|
||||
TEXTAREA
|
||||
|
||||
|
||||
#ifdef FEATURE_CACHED_INTERFACE_DISPATCH
|
||||
|
||||
EXTERN RhpCidResolve
|
||||
EXTERN RhpUniversalTransition_DebugStepTailCall
|
||||
|
||||
;; Macro that generates code to check a single cache entry.
|
||||
MACRO
|
||||
CHECK_CACHE_ENTRY $entry
|
||||
;; Check a single entry in the cache.
|
||||
;; R1 : Instance MethodTable*
|
||||
;; R2: Cache data structure
|
||||
;; R12 : Trashed. On successful check, set to the target address to jump to.
|
||||
|
||||
ldr r12, [r2, #(OFFSETOF__InterfaceDispatchCache__m_rgEntries + ($entry * 8))]
|
||||
cmp r1, r12
|
||||
bne %ft0
|
||||
ldr r12, [r2, #(OFFSETOF__InterfaceDispatchCache__m_rgEntries + ($entry * 8) + 4)]
|
||||
b %fa99
|
||||
0
|
||||
MEND
|
||||
|
||||
|
||||
;; Macro that generates a stub consuming a cache with the given number of entries.
|
||||
GBLS StubName
|
||||
|
||||
MACRO
|
||||
DEFINE_INTERFACE_DISPATCH_STUB $entries
|
||||
|
||||
StubName SETS "RhpInterfaceDispatch$entries"
|
||||
|
||||
NESTED_ENTRY $StubName
|
||||
;; On input we have the indirection cell data structure in r12. But we need more scratch registers and
|
||||
;; we may A/V on a null this. Both of these suggest we need a real prolog and epilog.
|
||||
PROLOG_PUSH {r1-r2}
|
||||
|
||||
;; r12 currently holds the indirection cell address. We need to get the cache structure instead.
|
||||
ldr r2, [r12, #OFFSETOF__InterfaceDispatchCell__m_pCache]
|
||||
|
||||
;; Load the MethodTable from the object instance in r0.
|
||||
ldr r1, [r0]
|
||||
|
||||
GBLA CurrentEntry
|
||||
CurrentEntry SETA 0
|
||||
WHILE CurrentEntry < $entries
|
||||
CHECK_CACHE_ENTRY CurrentEntry
|
||||
CurrentEntry SETA CurrentEntry + 1
|
||||
WEND
|
||||
|
||||
;; Point r12 to the indirection cell using the back pointer in the cache block
|
||||
ldr r12, [r2, #OFFSETOF__InterfaceDispatchCache__m_pCell]
|
||||
|
||||
EPILOG_POP {r1-r2}
|
||||
EPILOG_BRANCH RhpInterfaceDispatchSlow
|
||||
|
||||
;; Common epilog for cache hits. Have to out of line it here due to limitation on the number of
|
||||
;; epilogs imposed by the unwind code macros.
|
||||
99
|
||||
;; R2 contains address of the cache block. We store it in the red zone in case the target we jump
|
||||
;; to needs it.
|
||||
;; R12 contains the target address to jump to
|
||||
EPILOG_POP r1
|
||||
;; The red zone is only 8 bytes long, so we have to store r2 into it between the pops.
|
||||
EPILOG_NOP str r2, [sp, #-4]
|
||||
EPILOG_POP r2
|
||||
EPILOG_BRANCH_REG r12
|
||||
|
||||
NESTED_END $StubName
|
||||
|
||||
MEND
|
||||
|
||||
;; Define all the stub routines we currently need.
|
||||
DEFINE_INTERFACE_DISPATCH_STUB 1
|
||||
DEFINE_INTERFACE_DISPATCH_STUB 2
|
||||
DEFINE_INTERFACE_DISPATCH_STUB 4
|
||||
DEFINE_INTERFACE_DISPATCH_STUB 8
|
||||
DEFINE_INTERFACE_DISPATCH_STUB 16
|
||||
DEFINE_INTERFACE_DISPATCH_STUB 32
|
||||
DEFINE_INTERFACE_DISPATCH_STUB 64
|
||||
|
||||
|
||||
;; Initial dispatch on an interface when we don't have a cache yet.
|
||||
LEAF_ENTRY RhpInitialInterfaceDispatch
|
||||
|
||||
;; The stub that jumped here pushed r12, which contains the interface dispatch cell
|
||||
;; we need to pop it here
|
||||
pop { r12 }
|
||||
|
||||
;; Simply tail call the slow dispatch helper.
|
||||
b RhpInterfaceDispatchSlow
|
||||
|
||||
LEAF_END RhpInitialInterfaceDispatch
|
||||
|
||||
LEAF_ENTRY RhpVTableOffsetDispatch
|
||||
;; On input we have the indirection cell data structure in r12. But we need more scratch registers and
|
||||
;; we may A/V on a null this. Both of these suggest we need a real prolog and epilog.
|
||||
PROLOG_PUSH {r1}
|
||||
|
||||
;; r12 currently holds the indirection cell address. We need to update it to point to the vtable
|
||||
;; offset instead.
|
||||
ldr r12, [r12, #OFFSETOF__InterfaceDispatchCell__m_pCache]
|
||||
|
||||
;; Load the MethodTable from the object instance in r0.
|
||||
ldr r1, [r0]
|
||||
|
||||
;; add the vtable offset to the MethodTable pointer
|
||||
add r12, r1, r12
|
||||
|
||||
;; Load the target address of the vtable into r12
|
||||
ldr r12, [r12]
|
||||
|
||||
EPILOG_POP {r1}
|
||||
EPILOG_BRANCH_REG r12
|
||||
LEAF_END RhpVTableOffsetDispatch
|
||||
|
||||
;; Cache miss case, call the runtime to resolve the target and update the cache.
|
||||
LEAF_ENTRY RhpInterfaceDispatchSlow
|
||||
ALTERNATE_ENTRY RhpInitialDynamicInterfaceDispatch
|
||||
;; r12 has the interface dispatch cell address in it.
|
||||
;; The calling convention of the universal thunk is that the parameter
|
||||
;; for the universal thunk target is to be placed in sp-8
|
||||
;; and the universal thunk target address is to be placed in sp-4
|
||||
str r12, [sp, #-8]
|
||||
ldr r12, =RhpCidResolve
|
||||
str r12, [sp, #-4]
|
||||
|
||||
;; jump to universal transition thunk
|
||||
b RhpUniversalTransition_DebugStepTailCall
|
||||
LEAF_END RhpInterfaceDispatchSlow
|
||||
|
||||
|
||||
#endif // FEATURE_CACHED_INTERFACE_DISPATCH
|
||||
|
||||
end
|
||||
@@ -1,273 +0,0 @@
|
||||
;; Licensed to the .NET Foundation under one or more agreements.
|
||||
;; The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
#include "kxarm.h"
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; STUBS & DATA SECTIONS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
THUNK_CODESIZE equ 0x10 ;; 4-byte mov, 2-byte add, 4-byte str, 4-byte ldr, 2-byte branch
|
||||
THUNK_DATASIZE equ 0x08 ;; 2 dwords
|
||||
|
||||
THUNK_POOL_NUM_THUNKS_PER_PAGE equ 0xFA ;; 250 thunks per page
|
||||
|
||||
PAGE_SIZE equ 0x1000 ;; 4K
|
||||
POINTER_SIZE equ 0x04
|
||||
|
||||
MACRO
|
||||
NAMED_READONLY_DATA_SECTION $name, $areaAlias
|
||||
AREA $areaAlias,DATA,READONLY
|
||||
RO$name % 4
|
||||
MEND
|
||||
|
||||
MACRO
|
||||
NAMED_READWRITE_DATA_SECTION $name, $areaAlias
|
||||
AREA $areaAlias,DATA
|
||||
RW$name % 4
|
||||
MEND
|
||||
|
||||
MACRO
|
||||
LOAD_DATA_ADDRESS $groupIndex, $index
|
||||
ALIGN 0x10 ;; make sure we align to 16-byte boundary for CFG table
|
||||
|
||||
;; set r12 to beginning of data page : r12 <- pc - (THUNK_CODESIZE * current thunk's index - sizeof(mov+add instructions)) + PAGE_SIZE
|
||||
;; fix offset of the data : r12 <- r12 + (THUNK_DATASIZE * current thunk's index)
|
||||
mov.w r12, PAGE_SIZE + ($groupIndex * THUNK_DATASIZE * 10 + THUNK_DATASIZE * $index) - (8 + $groupIndex * THUNK_CODESIZE * 10 + THUNK_CODESIZE * $index)
|
||||
add.n r12, r12, pc
|
||||
MEND
|
||||
|
||||
MACRO
|
||||
JUMP_TO_COMMON $groupIndex, $index
|
||||
;; start : r12 points to the current thunks first data cell in the data page
|
||||
;; put r12 into the red zone : r12 isn't changed
|
||||
;; set r12 to beginning of data page : r12 <- r12 - (THUNK_DATASIZE * current thunk's index)
|
||||
;; fix offset to point to last DWROD in page : r12 <- r11 + PAGE_SIZE - POINTER_SIZE
|
||||
;; jump to the location pointed at by the last dword in the data page
|
||||
str.w r12, [sp, #-4]
|
||||
ldr.w r12, [r12, #(PAGE_SIZE - POINTER_SIZE - ($groupIndex * THUNK_DATASIZE * 10 + THUNK_DATASIZE * $index))]
|
||||
bx.n r12
|
||||
MEND
|
||||
|
||||
MACRO
|
||||
TenThunks $groupIndex
|
||||
;; Each thunk will load the address of its corresponding data (from the page that immediately follows)
|
||||
;; and call a common stub. The address of the common stub is setup by the caller (last dword
|
||||
;; in the thunks data section) depending on the 'kind' of thunks needed (interop, fat function pointers, etc...)
|
||||
|
||||
;; Each data block used by a thunk consists of two dword values:
|
||||
;; - Context: some value given to the thunk as context (passed in eax). Example for fat-fptrs: context = generic dictionary
|
||||
;; - Target : target code that the thunk eventually jumps to.
|
||||
|
||||
LOAD_DATA_ADDRESS $groupIndex,0
|
||||
JUMP_TO_COMMON $groupIndex,0
|
||||
|
||||
LOAD_DATA_ADDRESS $groupIndex,1
|
||||
JUMP_TO_COMMON $groupIndex,1
|
||||
|
||||
LOAD_DATA_ADDRESS $groupIndex,2
|
||||
JUMP_TO_COMMON $groupIndex,2
|
||||
|
||||
LOAD_DATA_ADDRESS $groupIndex,3
|
||||
JUMP_TO_COMMON $groupIndex,3
|
||||
|
||||
LOAD_DATA_ADDRESS $groupIndex,4
|
||||
JUMP_TO_COMMON $groupIndex,4
|
||||
|
||||
LOAD_DATA_ADDRESS $groupIndex,5
|
||||
JUMP_TO_COMMON $groupIndex,5
|
||||
|
||||
LOAD_DATA_ADDRESS $groupIndex,6
|
||||
JUMP_TO_COMMON $groupIndex,6
|
||||
|
||||
LOAD_DATA_ADDRESS $groupIndex,7
|
||||
JUMP_TO_COMMON $groupIndex,7
|
||||
|
||||
LOAD_DATA_ADDRESS $groupIndex,8
|
||||
JUMP_TO_COMMON $groupIndex,8
|
||||
|
||||
LOAD_DATA_ADDRESS $groupIndex,9
|
||||
JUMP_TO_COMMON $groupIndex,9
|
||||
MEND
|
||||
|
||||
MACRO
|
||||
THUNKS_PAGE_BLOCK
|
||||
|
||||
TenThunks 0
|
||||
TenThunks 1
|
||||
TenThunks 2
|
||||
TenThunks 3
|
||||
TenThunks 4
|
||||
TenThunks 5
|
||||
TenThunks 6
|
||||
TenThunks 7
|
||||
TenThunks 8
|
||||
TenThunks 9
|
||||
TenThunks 10
|
||||
TenThunks 11
|
||||
TenThunks 12
|
||||
TenThunks 13
|
||||
TenThunks 14
|
||||
TenThunks 15
|
||||
TenThunks 16
|
||||
TenThunks 17
|
||||
TenThunks 18
|
||||
TenThunks 19
|
||||
TenThunks 20
|
||||
TenThunks 21
|
||||
TenThunks 22
|
||||
TenThunks 23
|
||||
TenThunks 24
|
||||
MEND
|
||||
|
||||
;;
|
||||
;; The first thunks section should be 64K aligned because it can get
|
||||
;; mapped multiple times in memory, and mapping works on allocation
|
||||
;; granularity boundaries (we don't want to map more than what we need)
|
||||
;;
|
||||
;; The easiest way to do so is by having the thunks section at the
|
||||
;; first 64K aligned virtual address in the binary. We provide a section
|
||||
;; layout file to the linker to tell it how to layout the thunks sections
|
||||
;; that we care about. (ndp\rh\src\runtime\DLLs\app\mrt100_app_sectionlayout.txt)
|
||||
;;
|
||||
;; The PE spec says images cannot have gaps between sections (other
|
||||
;; than what is required by the section alignment value in the header),
|
||||
;; therefore we need a couple of padding data sections (otherwise the
|
||||
;; OS will not load the image).
|
||||
;;
|
||||
|
||||
NAMED_READONLY_DATA_SECTION PaddingFor64KAlignment0, "|.pad0|"
|
||||
NAMED_READONLY_DATA_SECTION PaddingFor64KAlignment1, "|.pad1|"
|
||||
NAMED_READONLY_DATA_SECTION PaddingFor64KAlignment2, "|.pad2|"
|
||||
NAMED_READONLY_DATA_SECTION PaddingFor64KAlignment3, "|.pad3|"
|
||||
NAMED_READONLY_DATA_SECTION PaddingFor64KAlignment4, "|.pad4|"
|
||||
NAMED_READONLY_DATA_SECTION PaddingFor64KAlignment5, "|.pad5|"
|
||||
NAMED_READONLY_DATA_SECTION PaddingFor64KAlignment6, "|.pad6|"
|
||||
NAMED_READONLY_DATA_SECTION PaddingFor64KAlignment7, "|.pad7|"
|
||||
NAMED_READONLY_DATA_SECTION PaddingFor64KAlignment8, "|.pad8|"
|
||||
NAMED_READONLY_DATA_SECTION PaddingFor64KAlignment9, "|.pad9|"
|
||||
NAMED_READONLY_DATA_SECTION PaddingFor64KAlignment10, "|.pad10|"
|
||||
NAMED_READONLY_DATA_SECTION PaddingFor64KAlignment11, "|.pad11|"
|
||||
NAMED_READONLY_DATA_SECTION PaddingFor64KAlignment12, "|.pad12|"
|
||||
NAMED_READONLY_DATA_SECTION PaddingFor64KAlignment13, "|.pad13|"
|
||||
NAMED_READONLY_DATA_SECTION PaddingFor64KAlignment14, "|.pad14|"
|
||||
|
||||
;;
|
||||
;; Thunk Stubs
|
||||
;; NOTE: Keep number of blocks in sync with macro/constant named 'NUM_THUNK_BLOCKS' in:
|
||||
;; - ndp\FxCore\src\System.Private.CoreLib\System\Runtime\InteropServices\ThunkPool.cs
|
||||
;; - ndp\rh\src\tools\rhbind\zapimage.h
|
||||
;;
|
||||
LEAF_ENTRY ThunkPool, "|.tks0|"
|
||||
THUNKS_PAGE_BLOCK
|
||||
LEAF_END ThunkPool
|
||||
|
||||
NAMED_READWRITE_DATA_SECTION ThunkData0, "|.tkd0|"
|
||||
|
||||
LEAF_ENTRY ThunkPool1, "|.tks1|"
|
||||
THUNKS_PAGE_BLOCK
|
||||
LEAF_END ThunkPool1
|
||||
|
||||
NAMED_READWRITE_DATA_SECTION ThunkData1, "|.tkd1|"
|
||||
|
||||
LEAF_ENTRY ThunkPool2, "|.tks2|"
|
||||
THUNKS_PAGE_BLOCK
|
||||
LEAF_END ThunkPool2
|
||||
|
||||
NAMED_READWRITE_DATA_SECTION ThunkData2, "|.tkd2|"
|
||||
|
||||
LEAF_ENTRY ThunkPool3, "|.tks3|"
|
||||
THUNKS_PAGE_BLOCK
|
||||
LEAF_END ThunkPool3
|
||||
|
||||
NAMED_READWRITE_DATA_SECTION ThunkData3, "|.tkd3|"
|
||||
|
||||
LEAF_ENTRY ThunkPool4, "|.tks4|"
|
||||
THUNKS_PAGE_BLOCK
|
||||
LEAF_END ThunkPool4
|
||||
|
||||
NAMED_READWRITE_DATA_SECTION ThunkData4, "|.tkd4|"
|
||||
|
||||
LEAF_ENTRY ThunkPool5, "|.tks5|"
|
||||
THUNKS_PAGE_BLOCK
|
||||
LEAF_END ThunkPool5
|
||||
|
||||
NAMED_READWRITE_DATA_SECTION ThunkData5, "|.tkd5|"
|
||||
|
||||
LEAF_ENTRY ThunkPool6, "|.tks6|"
|
||||
THUNKS_PAGE_BLOCK
|
||||
LEAF_END ThunkPool6
|
||||
|
||||
NAMED_READWRITE_DATA_SECTION ThunkData6, "|.tkd6|"
|
||||
|
||||
LEAF_ENTRY ThunkPool7, "|.tks7|"
|
||||
THUNKS_PAGE_BLOCK
|
||||
LEAF_END ThunkPool7
|
||||
|
||||
NAMED_READWRITE_DATA_SECTION ThunkData7, "|.tkd7|"
|
||||
|
||||
|
||||
;;
|
||||
;; IntPtr RhpGetThunksBase()
|
||||
;;
|
||||
LEAF_ENTRY RhpGetThunksBase
|
||||
;; Return the address of the first thunk pool to the caller (this is really the base address)
|
||||
ldr r0, =ThunkPool
|
||||
sub r0, r0, #1
|
||||
bx lr
|
||||
LEAF_END RhpGetThunksBase
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; General Helpers ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;;
|
||||
;; int RhpGetNumThunksPerBlock()
|
||||
;;
|
||||
LEAF_ENTRY RhpGetNumThunksPerBlock
|
||||
mov r0, THUNK_POOL_NUM_THUNKS_PER_PAGE
|
||||
bx lr
|
||||
LEAF_END RhpGetNumThunksPerBlock
|
||||
|
||||
;;
|
||||
;; int RhpGetThunkSize()
|
||||
;;
|
||||
LEAF_ENTRY RhpGetThunkSize
|
||||
mov r0, THUNK_CODESIZE
|
||||
bx lr
|
||||
LEAF_END RhpGetThunkSize
|
||||
|
||||
;;
|
||||
;; int RhpGetNumThunkBlocksPerMapping()
|
||||
;;
|
||||
LEAF_ENTRY RhpGetNumThunkBlocksPerMapping
|
||||
mov r0, 8
|
||||
bx lr
|
||||
LEAF_END RhpGetNumThunkBlocksPerMapping
|
||||
|
||||
;;
|
||||
;; int RhpGetThunkBlockSize
|
||||
;;
|
||||
LEAF_ENTRY RhpGetThunkBlockSize
|
||||
mov r0, PAGE_SIZE * 2
|
||||
bx lr
|
||||
LEAF_END RhpGetThunkBlockSize
|
||||
|
||||
;;
|
||||
;; IntPtr RhpGetThunkDataBlockAddress(IntPtr thunkStubAddress)
|
||||
;;
|
||||
LEAF_ENTRY RhpGetThunkDataBlockAddress
|
||||
mov r12, PAGE_SIZE - 1
|
||||
bic r0, r0, r12
|
||||
add r0, PAGE_SIZE
|
||||
bx lr
|
||||
LEAF_END RhpGetThunkDataBlockAddress
|
||||
|
||||
;;
|
||||
;; IntPtr RhpGetThunkStubsBlockAddress(IntPtr thunkDataAddress)
|
||||
;;
|
||||
LEAF_ENTRY RhpGetThunkStubsBlockAddress
|
||||
mov r12, PAGE_SIZE - 1
|
||||
bic r0, r0, r12
|
||||
sub r0, PAGE_SIZE
|
||||
bx lr
|
||||
LEAF_END RhpGetThunkStubsBlockAddress
|
||||
|
||||
END
|
||||
@@ -1,157 +0,0 @@
|
||||
;; Licensed to the .NET Foundation under one or more agreements.
|
||||
;; The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
#include "AsmMacros.h"
|
||||
|
||||
#ifdef _DEBUG
|
||||
#define TRASH_SAVED_ARGUMENT_REGISTERS
|
||||
#endif
|
||||
|
||||
#ifdef TRASH_SAVED_ARGUMENT_REGISTERS
|
||||
EXTERN RhpIntegerTrashValues
|
||||
EXTERN RhpFpTrashValues
|
||||
#endif ;; TRASH_SAVED_ARGUMENT_REGISTERS
|
||||
|
||||
#define COUNT_ARG_REGISTERS (4)
|
||||
#define INTEGER_REGISTER_SIZE (4)
|
||||
#define ARGUMENT_REGISTERS_SIZE (COUNT_ARG_REGISTERS * INTEGER_REGISTER_SIZE)
|
||||
|
||||
;; Largest return block is 4 doubles
|
||||
#define RETURN_BLOCK_SIZE (32)
|
||||
|
||||
#define COUNT_FLOAT_ARG_REGISTERS (8)
|
||||
#define FLOAT_REGISTER_SIZE (8)
|
||||
#define FLOAT_ARG_REGISTERS_SIZE (COUNT_FLOAT_ARG_REGISTERS * FLOAT_REGISTER_SIZE)
|
||||
|
||||
#define PUSHED_LR_SIZE (4)
|
||||
#define PUSHED_R11_SIZE (4)
|
||||
|
||||
;;
|
||||
;; From CallerSP to ChildSP, the stack frame is composed of the following adjacent regions:
|
||||
;;
|
||||
;; ARGUMENT_REGISTERS_SIZE
|
||||
;; RETURN_BLOCK_SIZE
|
||||
;; FLOAT_ARG_REGISTERS_SIZE
|
||||
;; PUSHED_LR
|
||||
;; PUSHED_R11
|
||||
;;
|
||||
|
||||
#define DISTANCE_FROM_CHILDSP_TO_RETURN_BLOCK (PUSHED_R11_SIZE + PUSHED_LR_SIZE + FLOAT_ARG_REGISTERS_SIZE)
|
||||
|
||||
TEXTAREA
|
||||
|
||||
;;
|
||||
;; RhpUniversalTransition
|
||||
;;
|
||||
;; At input to this function, r0-3, d0-7 and the stack may contain any number of arguments.
|
||||
;;
|
||||
;; In addition, there are 2 extra arguments passed in the RED ZONE (8 byte negative space
|
||||
;; off of sp).
|
||||
;; sp-4 will contain the managed function that is to be called by this transition function
|
||||
;; sp-8 will contain the pointer sized extra argument to the managed function
|
||||
;;
|
||||
;; When invoking the callee:
|
||||
;;
|
||||
;; r0 shall contain a pointer to the TransitionBlock
|
||||
;; r1 shall contain the value that was in sp-8 at entry to this function
|
||||
;;
|
||||
;; Frame layout is:
|
||||
;;
|
||||
;; {StackPassedArgs} ChildSP+078 CallerSP+000
|
||||
;; {IntArgRegs (r0-r3) (0x10 bytes)} ChildSP+068 CallerSP-010
|
||||
;; {ReturnBlock (0x20 bytes)} ChildSP+048 CallerSP-030
|
||||
;; -- The base address of the Return block is the TransitionBlock pointer, the floating point args are
|
||||
;; in the neg space of the TransitionBlock pointer. Note that the callee has knowledge of the exact
|
||||
;; layout of all pieces of the frame that lie at or above the pushed floating point registers.
|
||||
;; {FpArgRegs (d0-d7) (0x40 bytes)} ChildSP+008 CallerSP-070
|
||||
;; {PushedLR} ChildSP+004 CallerSP-074
|
||||
;; {PushedR11} ChildSP+000 CallerSP-078
|
||||
;;
|
||||
;; NOTE: If the frame layout ever changes, the C++ UniversalTransitionStackFrame structure
|
||||
;; must be updated as well.
|
||||
;;
|
||||
;; NOTE: The callee receives a pointer to the base of the ReturnBlock, and the callee has
|
||||
;; knowledge of the exact layout of all pieces of the frame that lie at or above the pushed
|
||||
;; FpArgRegs.
|
||||
;;
|
||||
;; NOTE: The stack walker guarantees that conservative GC reporting will be applied to
|
||||
;; everything between the base of the ReturnBlock and the top of the StackPassedArgs.
|
||||
;;
|
||||
|
||||
MACRO
|
||||
UNIVERSAL_TRANSITION $FunctionName
|
||||
|
||||
NESTED_ENTRY Rhp$FunctionName
|
||||
;; Save argument registers (including floating point) and the return address.
|
||||
;; NOTE: While we do that, capture the two arguments in the red zone into r12 and r3.
|
||||
PROLOG_NOP ldr r12, [sp, #-4] ; Capture first argument from red zone into r12
|
||||
PROLOG_PUSH {r3} ; Push r3
|
||||
PROLOG_NOP ldr r3, [sp, #-4] ; Capture second argument from red zone into r3
|
||||
PROLOG_PUSH {r0-r2} ; Push the rest of the registers
|
||||
PROLOG_STACK_ALLOC RETURN_BLOCK_SIZE ; Save space a buffer to be used to hold return buffer data.
|
||||
PROLOG_VPUSH {d0-d7} ; Capture the floating point argument registers
|
||||
PROLOG_PUSH {r11,lr} ; Save caller's frame chain pointer and PC
|
||||
|
||||
;; Setup the arguments to the transition thunk.
|
||||
mov r1, r3
|
||||
|
||||
#ifdef TRASH_SAVED_ARGUMENT_REGISTERS
|
||||
|
||||
;; Before calling out, trash all of the argument registers except the ones (r0, r1) that
|
||||
;; hold outgoing arguments. All of these registers have been saved to the transition
|
||||
;; frame, and the code at the call target is required to use only the transition frame
|
||||
;; copies when dispatching this call to the eventual callee.
|
||||
|
||||
ldr r3, =RhpFpTrashValues
|
||||
vldr d0, [r3, #(0 * 8)]
|
||||
vldr d1, [r3, #(1 * 8)]
|
||||
vldr d2, [r3, #(2 * 8)]
|
||||
vldr d3, [r3, #(3 * 8)]
|
||||
vldr d4, [r3, #(4 * 8)]
|
||||
vldr d5, [r3, #(5 * 8)]
|
||||
vldr d6, [r3, #(6 * 8)]
|
||||
vldr d7, [r3, #(7 * 8)]
|
||||
|
||||
ldr r3, =RhpIntegerTrashValues
|
||||
ldr r2, [r3, #(2 * 4)]
|
||||
ldr r3, [r3, #(3 * 4)]
|
||||
|
||||
#endif // TRASH_SAVED_ARGUMENT_REGISTERS
|
||||
|
||||
;; Make the ReturnFromUniversalTransition alternate entry 4 byte aligned
|
||||
ALIGN 4
|
||||
add r0, sp, #DISTANCE_FROM_CHILDSP_TO_RETURN_BLOCK ;; First parameter to target function is a pointer to the return block
|
||||
blx r12
|
||||
|
||||
EXPORT_POINTER_TO_ADDRESS PointerToReturnFrom$FunctionName
|
||||
|
||||
; We cannot make the label public as that tricks DIA stackwalker into thinking
|
||||
; it's the beginning of a method. For this reason we export an auxiliary variable
|
||||
; holding the address instead.
|
||||
|
||||
;; Move the result (the target address) to r12 so it doesn't get overridden when we restore the
|
||||
;; argument registers. Additionally make sure the thumb2 bit is set.
|
||||
orr r12, r0, #1
|
||||
|
||||
;; Restore caller's frame chain pointer and PC.
|
||||
EPILOG_POP {r11,lr}
|
||||
|
||||
;; Restore the argument registers.
|
||||
EPILOG_VPOP {d0-d7}
|
||||
EPILOG_STACK_FREE RETURN_BLOCK_SIZE ; pop return block conservatively reported area
|
||||
EPILOG_POP {r0-r3}
|
||||
|
||||
;; Tailcall to the target address.
|
||||
EPILOG_BRANCH_REG r12
|
||||
|
||||
NESTED_END Rhp$FunctionName
|
||||
|
||||
MEND
|
||||
|
||||
; To enable proper step-in behavior in the debugger, we need to have two instances
|
||||
; of the thunk. For the first one, the debugger steps into the call in the function,
|
||||
; for the other, it steps over it.
|
||||
UNIVERSAL_TRANSITION UniversalTransition
|
||||
UNIVERSAL_TRANSITION UniversalTransition_DebugStepTailCall
|
||||
|
||||
END
|
||||
@@ -1,421 +0,0 @@
|
||||
;; Licensed to the .NET Foundation under one or more agreements.
|
||||
;; The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
;;
|
||||
;; Define the helpers used to implement the write barrier required when writing an object reference into a
|
||||
;; location residing on the GC heap. Such write barriers allow the GC to optimize which objects in
|
||||
;; non-ephemeral generations need to be scanned for references to ephemeral objects during an ephemeral
|
||||
;; collection.
|
||||
;;
|
||||
|
||||
#include "AsmMacros.h"
|
||||
|
||||
TEXTAREA
|
||||
|
||||
;; Macro used to copy contents of newly updated GC heap locations to a shadow copy of the heap. This is used
|
||||
;; during garbage collections to verify that object references where never written to the heap without using a
|
||||
;; write barrier. Note that we're potentially racing to update the shadow heap while other threads are writing
|
||||
;; new references to the real heap. Since this can't be solved perfectly without critical sections around the
|
||||
;; entire update process, we instead update the shadow location and then re-check the real location (as two
|
||||
;; ordered operations) and if there is a disparity we'll re-write the shadow location with a special value
|
||||
;; (INVALIDGCVALUE) which disables the check for that location. Since the shadow heap is only validated at GC
|
||||
;; time and these write barrier operations are atomic wrt to GCs this is sufficient to guarantee that the
|
||||
;; shadow heap contains only valid copies of real heap values or INVALIDGCVALUE.
|
||||
#ifdef WRITE_BARRIER_CHECK
|
||||
|
||||
SETALIAS g_GCShadow, ?g_GCShadow@@3PAEA
|
||||
SETALIAS g_GCShadowEnd, ?g_GCShadowEnd@@3PAEA
|
||||
|
||||
EXTERN $g_GCShadow
|
||||
EXTERN $g_GCShadowEnd
|
||||
|
||||
MACRO
|
||||
;; On entry:
|
||||
;; $DESTREG: location to be updated
|
||||
;; $REFREG: objectref to be stored
|
||||
;;
|
||||
;; On exit:
|
||||
;; r12: trashed
|
||||
;; other registers are preserved
|
||||
;;
|
||||
UPDATE_GC_SHADOW $DESTREG, $REFREG
|
||||
|
||||
;; If g_GCShadow is 0, don't perform the check.
|
||||
ldr r12, =$g_GCShadow
|
||||
ldr r12, [r12]
|
||||
cmp r12, 0
|
||||
beq %ft1
|
||||
|
||||
;; Save $DESTREG since we're about to modify it (and we need the original value both within the macro and
|
||||
;; once we exit the macro). Note that this is naughty since we're altering the stack pointer outside of
|
||||
;; the prolog inside a method without a frame. But given that this is only debug code and generally we
|
||||
;; shouldn't be walking the stack at this point it seems preferable to recoding the all the barrier
|
||||
;; variants to set up frames. The compiler knows exactly which registers are trashed in the simple write
|
||||
;; barrier case, so we don't have any more scratch registers to play with (and doing so would only make
|
||||
;; things harder if at a later stage we want to allow multiple barrier versions based on the input
|
||||
;; registers).
|
||||
push $DESTREG
|
||||
|
||||
;; Transform $DESTREG into the equivalent address in the shadow heap.
|
||||
ldr r12, =$G_LOWEST_ADDRESS
|
||||
ldr r12, [r12]
|
||||
subs $DESTREG, r12
|
||||
blo %ft0
|
||||
|
||||
ldr r12, =$g_GCShadow
|
||||
ldr r12, [r12]
|
||||
add $DESTREG, r12
|
||||
|
||||
ldr r12, =$g_GCShadowEnd
|
||||
ldr r12, [r12]
|
||||
cmp $DESTREG, r12
|
||||
bhs %ft0
|
||||
|
||||
;; Update the shadow heap.
|
||||
str $REFREG, [$DESTREG]
|
||||
|
||||
;; The following read must be strongly ordered wrt to the write we've just performed in order to
|
||||
;; prevent race conditions.
|
||||
dmb
|
||||
|
||||
;; Now check that the real heap location still contains the value we just wrote into the shadow heap.
|
||||
ldr r12, [sp]
|
||||
ldr r12, [r12]
|
||||
cmp r12, $REFREG
|
||||
beq %ft0
|
||||
|
||||
;; Someone went and updated the real heap. We need to invalidate the shadow location since we can't
|
||||
;; guarantee whose shadow update won.
|
||||
movw r12, #0xcccd
|
||||
movt r12, #0xcccc
|
||||
str r12, [$DESTREG]
|
||||
|
||||
0
|
||||
;; Restore original $DESTREG value from the stack.
|
||||
pop $DESTREG
|
||||
|
||||
1
|
||||
MEND
|
||||
|
||||
#else // WRITE_BARRIER_CHECK
|
||||
|
||||
MACRO
|
||||
UPDATE_GC_SHADOW $DESTREG, $REFREG
|
||||
MEND
|
||||
|
||||
#endif // WRITE_BARRIER_CHECK
|
||||
|
||||
;; There are several different helpers used depending on which register holds the object reference. Since all
|
||||
;; the helpers have identical structure we use a macro to define this structure. Two arguments are taken, the
|
||||
;; name of the register that points to the location to be updated and the name of the register that holds the
|
||||
;; object reference (this should be in upper case as it's used in the definition of the name of the helper).
|
||||
|
||||
;; Define a sub-macro first that expands to the majority of the barrier implementation. This is used below for
|
||||
;; some interlocked helpers that need an inline barrier.
|
||||
MACRO
|
||||
;; On entry:
|
||||
;; $DESTREG: location to be updated
|
||||
;; $REFREG: objectref to be stored
|
||||
;;
|
||||
;; On exit:
|
||||
;; $DESTREG, r12: trashed
|
||||
;; other registers are preserved
|
||||
;;
|
||||
INSERT_CHECKED_WRITE_BARRIER_CORE $DESTREG, $REFREG
|
||||
|
||||
;; The location being updated might not even lie in the GC heap (a handle or stack location for
|
||||
;; instance), in which case no write barrier is required.
|
||||
ldr r12, =$G_LOWEST_ADDRESS
|
||||
ldr r12, [r12]
|
||||
cmp $DESTREG, r12
|
||||
blo %ft0
|
||||
ldr r12, =$G_HIGHEST_ADDRESS
|
||||
ldr r12, [r12]
|
||||
cmp $DESTREG, r12
|
||||
bhs %ft0
|
||||
|
||||
;; Update the shadow copy of the heap with the same value just written to the same heap. (A no-op unless
|
||||
;; we're in a debug build and write barrier checking has been enabled).
|
||||
UPDATE_GC_SHADOW $DESTREG, $REFREG
|
||||
|
||||
;; If the reference is to an object that's not in an ephemeral generation we have no need to track it
|
||||
;; (since the object won't be collected or moved by an ephemeral collection).
|
||||
ldr r12, =$G_EPHEMERAL_LOW
|
||||
ldr r12, [r12]
|
||||
cmp $REFREG, r12
|
||||
blo %ft0
|
||||
ldr r12, =$G_EPHEMERAL_HIGH
|
||||
ldr r12, [r12]
|
||||
cmp $REFREG, r12
|
||||
bhs %ft0
|
||||
|
||||
;; All tests pass, so update the card table.
|
||||
ldr r12, =$G_CARD_TABLE
|
||||
ldr r12, [r12]
|
||||
add r12, r12, $DESTREG, lsr #10
|
||||
|
||||
;; Check that this card hasn't already been written. Avoiding useless writes is a big win on
|
||||
;; multi-proc systems since it avoids cache thrashing.
|
||||
ldrb $DESTREG, [r12]
|
||||
cmp $DESTREG, #0xFF
|
||||
beq %ft0
|
||||
mov $DESTREG, #0xFF
|
||||
strb $DESTREG, [r12]
|
||||
|
||||
0
|
||||
|
||||
MEND
|
||||
|
||||
;; There are several different helpers used depending on which register holds the object reference. Since all
|
||||
;; the helpers have identical structure we use a macro to define this structure. Two arguments are taken, the
|
||||
;; name of the register that points to the location to be updated and the name of the register that holds the
|
||||
;; object reference (this should be in upper case as it's used in the definition of the name of the helper).
|
||||
|
||||
;; Define a sub-macro first that expands to the majority of the barrier implementation. This is used below for
|
||||
;; some interlocked helpers that need an inline barrier.
|
||||
MACRO
|
||||
;; On entry:
|
||||
;; $DESTREG: location to be updated
|
||||
;; $REFREG: objectref to be stored
|
||||
;;
|
||||
;; On exit:
|
||||
;; $DESTREG, r12: trashed
|
||||
;; other registers are preserved
|
||||
;;
|
||||
INSERT_UNCHECKED_WRITE_BARRIER_CORE $DESTREG, $REFREG
|
||||
|
||||
;; Update the shadow copy of the heap with the same value just written to the same heap. (A no-op unless
|
||||
;; we're in a debug build and write barrier checking has been enabled).
|
||||
UPDATE_GC_SHADOW $DESTREG, $REFREG
|
||||
|
||||
;; If the reference is to an object that's not in an ephemeral generation we have no need to track it
|
||||
;; (since the object won't be collected or moved by an ephemeral collection).
|
||||
ldr r12, =$G_EPHEMERAL_LOW
|
||||
ldr r12, [r12]
|
||||
cmp $REFREG, r12
|
||||
blo %ft0
|
||||
ldr r12, =$G_EPHEMERAL_HIGH
|
||||
ldr r12, [r12]
|
||||
cmp $REFREG, r12
|
||||
bhs %ft0
|
||||
|
||||
;; All tests pass, so update the card table.
|
||||
ldr r12, =$G_CARD_TABLE
|
||||
ldr r12, [r12]
|
||||
add r12, r12, $DESTREG, lsr #10
|
||||
|
||||
;; Check that this card hasn't already been written. Avoiding useless writes is a big win on
|
||||
;; multi-proc systems since it avoids cache thrashing.
|
||||
ldrb $DESTREG, [r12]
|
||||
cmp $DESTREG, #0xFF
|
||||
beq %ft0
|
||||
mov $DESTREG, #0xFF
|
||||
strb $DESTREG, [r12]
|
||||
|
||||
0
|
||||
|
||||
MEND
|
||||
|
||||
MACRO
|
||||
;; Define a helper with a name of the form RhpCheckedAssignRefR0 etc. The location to be updated is in
|
||||
;; $DESTREG. The object reference that will be assigned into that location is in one of the other
|
||||
;; general registers determined by the value of $REFREG. R12 is used as a scratch register.
|
||||
|
||||
;; WARNING: Code in EHHelpers.cpp makes assumptions about write barrier code, in particular:
|
||||
;; - Function "InWriteBarrierHelper" assumes an AV due to passed in null pointer will happen at WriteBarrierFunctionAvLocation
|
||||
;; - Function "UnwindSimpleHelperToCaller" assumes no registers were pushed and LR contains the return address
|
||||
|
||||
DEFINE_CHECKED_WRITE_BARRIER $DESTREG, $REFREG
|
||||
|
||||
gbls WriteBarrierFunction
|
||||
gbls WriteBarrierFunctionAvLocation
|
||||
WriteBarrierFunction SETS "RhpCheckedAssignRef":cc:"$REFREG"
|
||||
WriteBarrierFunctionAvLocation SETS "RhpCheckedAssignRefAvLocation":cc:"$REFREG"
|
||||
|
||||
EXPORT $WriteBarrierFunction
|
||||
$WriteBarrierFunction
|
||||
|
||||
;; Export the canonical write barrier under unqualified name as well
|
||||
IF "$REFREG" == "R1"
|
||||
ALTERNATE_ENTRY RhpCheckedAssignRef
|
||||
ENDIF
|
||||
|
||||
;; Use the GC write barrier as a convenient place to implement the managed memory model for ARM. The
|
||||
;; intent is that writes to the target object ($REFREG) will be visible across all CPUs before the
|
||||
;; write to the destination ($DESTREG). This covers most of the common scenarios where the programmer
|
||||
;; might assume strongly ordered accessess, namely where the preceding writes are used to initialize
|
||||
;; the object and the final write, made by this barrier in the instruction following the DMB,
|
||||
;; publishes that object for other threads/cpus to see.
|
||||
;;
|
||||
;; Note that none of this is relevant for single cpu machines. We may choose to implement a
|
||||
;; uniprocessor specific version of this barrier if uni-proc becomes a significant scenario again.
|
||||
dmb
|
||||
|
||||
;; Write the reference into the location. Note that we rely on the fact that no GC can occur between
|
||||
;; here and the card table update we may perform below.
|
||||
ALTERNATE_ENTRY $WriteBarrierFunctionAvLocation
|
||||
IF "$REFREG" == "R1"
|
||||
ALTERNATE_ENTRY RhpCheckedAssignRefAVLocation
|
||||
ENDIF
|
||||
str $REFREG, [$DESTREG]
|
||||
|
||||
INSERT_CHECKED_WRITE_BARRIER_CORE $DESTREG, $REFREG
|
||||
|
||||
bx lr
|
||||
|
||||
MEND
|
||||
|
||||
|
||||
MACRO
|
||||
;; Define a helper with a name of the form RhpAssignRefR0 etc. The location to be updated is in
|
||||
;; $DESTREG. The object reference that will be assigned into that location is in one of the other
|
||||
;; general registers determined by the value of $REFREG. R12 is used as a scratch register.
|
||||
|
||||
;; WARNING: Code in EHHelpers.cpp makes assumptions about write barrier code, in particular:
|
||||
;; - Function "InWriteBarrierHelper" assumes an AV due to passed in null pointer will happen at WriteBarrierFunctionAvLocation
|
||||
;; - Function "UnwindSimpleHelperToCaller" assumes no registers were pushed and LR contains the return address
|
||||
|
||||
DEFINE_UNCHECKED_WRITE_BARRIER $DESTREG, $REFREG
|
||||
|
||||
gbls WriteBarrierFunction
|
||||
gbls WriteBarrierFunctionAvLocation
|
||||
WriteBarrierFunction SETS "RhpAssignRef":cc:"$REFREG"
|
||||
WriteBarrierFunctionAvLocation SETS "RhpAssignRefAvLocation":cc:"$REFREG"
|
||||
|
||||
;; Export the canonical write barrier under unqualified name as well
|
||||
IF "$REFREG" == "R1"
|
||||
ALTERNATE_ENTRY RhpAssignRef
|
||||
ENDIF
|
||||
|
||||
EXPORT $WriteBarrierFunction
|
||||
$WriteBarrierFunction
|
||||
|
||||
;; Use the GC write barrier as a convenient place to implement the managed memory model for ARM. The
|
||||
;; intent is that writes to the target object ($REFREG) will be visible across all CPUs before the
|
||||
;; write to the destination ($DESTREG). This covers most of the common scenarios where the programmer
|
||||
;; might assume strongly ordered accessess, namely where the preceding writes are used to initialize
|
||||
;; the object and the final write, made by this barrier in the instruction following the DMB,
|
||||
;; publishes that object for other threads/cpus to see.
|
||||
;;
|
||||
;; Note that none of this is relevant for single cpu machines. We may choose to implement a
|
||||
;; uniprocessor specific version of this barrier if uni-proc becomes a significant scenario again.
|
||||
dmb
|
||||
|
||||
;; Write the reference into the location. Note that we rely on the fact that no GC can occur between
|
||||
;; here and the card table update we may perform below.
|
||||
ALTERNATE_ENTRY $WriteBarrierFunctionAvLocation
|
||||
IF "$REFREG" == "R1"
|
||||
ALTERNATE_ENTRY RhpAssignRefAVLocation
|
||||
ENDIF
|
||||
str $REFREG, [$DESTREG]
|
||||
|
||||
INSERT_UNCHECKED_WRITE_BARRIER_CORE $DESTREG, $REFREG
|
||||
|
||||
bx lr
|
||||
|
||||
MEND
|
||||
|
||||
;; One day we might have write barriers for all the possible argument registers but for now we have
|
||||
;; just one write barrier that assumes the input register is R1.
|
||||
DEFINE_CHECKED_WRITE_BARRIER R0, R1
|
||||
|
||||
DEFINE_UNCHECKED_WRITE_BARRIER R0, R1
|
||||
|
||||
;; Interlocked operation helpers where the location is an objectref, thus requiring a GC write barrier upon
|
||||
;; successful updates.
|
||||
|
||||
;; WARNING: Code in EHHelpers.cpp makes assumptions about write barrier code, in particular:
|
||||
;; - Function "InWriteBarrierHelper" assumes an AV due to passed in null pointer will happen at RhpCheckedLockCmpXchgAVLocation
|
||||
;; - Function "UnwindSimpleHelperToCaller" assumes no registers were pushed and LR contains the return address
|
||||
|
||||
;; Interlocked compare exchange on objectref.
|
||||
;;
|
||||
;; On entry:
|
||||
;; r0: pointer to objectref
|
||||
;; r1: exchange value
|
||||
;; r2: comparand
|
||||
;;
|
||||
;; On exit:
|
||||
;; r0: original value of objectref
|
||||
;; r1,r2,r3,r12: trashed
|
||||
;;
|
||||
LEAF_ENTRY RhpCheckedLockCmpXchg
|
||||
|
||||
;; To implement our chosen memory model for ARM we insert a memory barrier at GC write barriers. This
|
||||
;; barrier must occur before the object reference update, so we have to do it unconditionally even
|
||||
;; though the update may fail below.
|
||||
dmb
|
||||
|
||||
CX_Retry
|
||||
;; Check location value is what we expect.
|
||||
ALTERNATE_ENTRY RhpCheckedLockCmpXchgAVLocation
|
||||
ldrex r3, [r0]
|
||||
cmp r2, r3
|
||||
bne CX_NoUpdate
|
||||
|
||||
;; Current value matches comparand, attempt to update with the new value.
|
||||
strex r3, r1, [r0]
|
||||
cmp r3, #0
|
||||
bne CX_Retry ; Retry the operation if another write beat us there
|
||||
|
||||
;; We've successfully updated the value of the objectref so now we need a GC write barrier.
|
||||
;; The following barrier code takes the destination in r0 and the value in r1 so the arguments are
|
||||
;; already correctly set up.
|
||||
|
||||
INSERT_CHECKED_WRITE_BARRIER_CORE r0, r1
|
||||
|
||||
;; The original value was equal to the comparand which is still in r2 so we can return that.
|
||||
mov r0, r2
|
||||
bx lr
|
||||
|
||||
CX_NoUpdate
|
||||
;; Location value didn't match comparand, return that value.
|
||||
mov r0, r3
|
||||
bx lr
|
||||
|
||||
LEAF_END RhpCheckedLockCmpXchg
|
||||
|
||||
;; Interlocked exchange on objectref.
|
||||
;;
|
||||
;; On entry:
|
||||
;; r0: pointer to objectref
|
||||
;; r1: exchange value
|
||||
;;
|
||||
;; On exit:
|
||||
;; r0: original value of objectref
|
||||
;; r1,r2,r3,r12: trashed
|
||||
;;
|
||||
|
||||
;; WARNING: Code in EHHelpers.cpp makes assumptions about write barrier code, in particular:
|
||||
;; - Function "InWriteBarrierHelper" assumes an AV due to passed in null pointer will happen within at RhpCheckedXchgAVLocation
|
||||
;; - Function "UnwindSimpleHelperToCaller" assumes no registers were pushed and LR contains the return address
|
||||
|
||||
LEAF_ENTRY RhpCheckedXchg
|
||||
|
||||
;; To implement our chosen memory model for ARM we insert a memory barrier at GC write barriers. This
|
||||
;; barrier must occur before the object reference update.
|
||||
dmb
|
||||
|
||||
X_Retry
|
||||
ALTERNATE_ENTRY RhpCheckedXchgAVLocation
|
||||
;; Read the original contents of the location.
|
||||
ldrex r2, [r0]
|
||||
|
||||
;; Attempt to update with the new value.
|
||||
strex r3, r1, [r0]
|
||||
cmp r3, #0
|
||||
bne X_Retry ; Retry the operation if another write beat us there
|
||||
|
||||
;; We've successfully updated the value of the objectref so now we need a GC write barrier.
|
||||
;; The following barrier code takes the destination in r0 and the value in r1 so the arguments are
|
||||
;; already correctly set up.
|
||||
|
||||
INSERT_CHECKED_WRITE_BARRIER_CORE r0, r1
|
||||
|
||||
;; The original value is currently in r2. We need to return it in r0.
|
||||
mov r0, r2
|
||||
bx lr
|
||||
|
||||
LEAF_END RhpCheckedXchg
|
||||
|
||||
end
|
||||
@@ -125,20 +125,6 @@ EXTERN_C void __faststorefence();
|
||||
#pragma intrinsic(__faststorefence)
|
||||
#define PalMemoryBarrier() __faststorefence()
|
||||
|
||||
#elif defined(HOST_ARM)
|
||||
|
||||
EXTERN_C void __yield(void);
|
||||
#pragma intrinsic(__yield)
|
||||
EXTERN_C void __dmb(unsigned int _Type);
|
||||
#pragma intrinsic(__dmb)
|
||||
FORCEINLINE void PalYieldProcessor()
|
||||
{
|
||||
__dmb(_ARM_BARRIER_ISHST);
|
||||
__yield();
|
||||
}
|
||||
|
||||
#define PalMemoryBarrier() __dmb(_ARM_BARRIER_ISH)
|
||||
|
||||
#elif defined(HOST_ARM64)
|
||||
|
||||
EXTERN_C void __yield(void);
|
||||
|
||||
Reference in new issue
Block a user