; pexl_rtl_memory.asm

%define PEXL_RTL_MEMORY_ASM
%include "pexl_rtl.h.asm"

%ifdef OS_WINDOWS
	; For allocations
	; n.b. Win32 stdcall API clobbers ecx and edx !
	extern _HeapCreate@12
	extern _HeapAlloc@12
	extern _HeapReAlloc@16
	extern _HeapFree@12

%elifdef OS_POSIX
	; For allocations
	extern malloc ;@4
	extern realloc ;@8
	extern mprotect ;@12
	extern free ;@4
		
%else
%fatal "Unknown operating system or no OS_ constant specified!"
%endif


section .data
	
pxl_rtl_heap:  dd 0
pxl_rtl_xheap: dd 0	

section .text

; void rtl-init()
; Allocate rw and rwx heaps for malloc/xalloc
DECLARE_MANGLED pxl_rtl_init, 0
	push ebp
	mov ebp, esp
	
%ifdef OS_WINDOWS
	
	push 0                    ; dwMaximumSize = NULL
	push 0                    ; dwInitialSize = NULL (one page)
	push 0                    ; flags = 0
	call _HeapCreate@12
	mov dword [pxl_rtl_heap], eax
	
	push 0                    ; dwMaximumSize = NULL
	push 0                    ; dwInitialSize = NULL (one page)
	push 0x00040000           ; flags = HEAP_CREATE_ENABLE_EXECUTE
	call _HeapCreate@12
	mov dword [pxl_rtl_xheap], eax
		
%elifdef OS_POSIX
	; nothing to do
%endif
		
	leave ; mov esp, ebp && pop ebp
	ret 0 ; Done
	
; void* __stdcall xalloc(size_t len)
; @param   len  Amount of bytes to allocate
; @return       Pointer to executable memory at least $len bytes, or 0 on failure
DECLARE_MANGLED pxl_rtl_xalloc, 4
	push ebp
	mov ebp, esp
	
%ifdef OS_WINDOWS
	
	push dword [ebp+8]           ; $len (SIZE_T dwBytes)
	push 0                       ; DWORD dwFlags
	push dword [pxl_rtl_xheap]   ; HANDLE hHeap
	call _HeapAlloc@12

	; Return is in eax, leave it there
%elifdef OS_POSIX
	
	push dword [ebp+8]    ; $len
	call malloc
	add esp, 4            ; cdecl
	
	push eax              ; stash 1
	
	push 7                ; PROT_READ | PROT_WRITE | PROT_EXEC
	push dword [ebp+8]    ; $len
	push eax              ; ptr
	call mprotect
	add esp,12            ; cdecl
	
	pop eax               ; unstash 1
%endif
	
	leave
	ret 4                 ; one argument

; void __stdcall pxl_rtl_xfree(void* ptr)
; @param   ptr  Pointer to memory to free
DECLARE_MANGLED pxl_rtl_xfree, 4
	push ebp
	mov ebp, esp

%ifdef OS_WINDOWS
	
	push dword [ebp+8]
	push 0
	push dword [pxl_rtl_xheap]
	call _HeapFree@12
	
%elifdef OS_POSIX

	push dword [ebp+8]
	call free
	add esp, 4 ;cdecl
%endif
	
	leave
	ret 4                 ; one argument
	
; void* __stdcall malloc(size_t len)
; @param   len  Amount of bytes to allocate
; @return       Pointer to non-executable memory at least $len bytes, or 0 on failure
DECLARE_MANGLED pxl_rtl_malloc, 4
	push ebp
	mov ebp, esp
	
%ifdef OS_WINDOWS

	push dword [ebp+8]           ; $len (SIZE_T dwBytes)
	push 0                       ; DWORD dwFlags
	push dword [pxl_rtl_heap]    ; HANDLE hHeap
	call _HeapAlloc@12

%elifdef OS_POSIX
	
	push dword [ebp+8]
	call malloc
	add esp, 4 ;cdecl
%endif

	; Return is in eax, leave it there
	leave
	ret 4                 ; one argument

; void __stdcall pxl_rtl_realloc(void* ptr, size_t len)
;  Resizes a pointer and returns its new memory location.
; @param void* ptr
; @param len
DECLARE_MANGLED pxl_rtl_realloc, 8
	push ebp
	mov ebp, esp
	
%ifdef OS_WINDOWS

	push dword [ebp+12] ; len
	push dword [ebp+8]  ; ptr
	push 0              ; flags
	push dword [pxl_rtl_heap]
	call _HeapReAlloc@16
	
%elifdef OS_POSIX
	push dword [ebp+12] ; len
	push dword [ebp+8]  ; ptr
	call realloc
	add esp, 8 ;cdecl

%endif
	
	leave
	ret 8 ; two arguments
	
; void __stdcall pxl_rtl_free(void* ptr)
; @param   ptr  Pointer to memory to free
DECLARE_MANGLED pxl_rtl_free, 4
	push ebp
	mov ebp, esp

%ifdef OS_WINDOWS
	
	push dword [ebp+8]
	push 0
	push dword [pxl_rtl_heap]
	call _HeapFree@12
	
%elifdef OS_POSIX
	push dword [ebp+8]
	call free
	add esp, 4 ;cdecl

%endif
	
	leave
	ret 4                 ; one argument
	
	
; heapfn __stdcall bind1st(void* func, void* param1)
; @param   func    Pointer to function to generate a stub for
; @param   param1  Parameter to bind into the stub
; @return          Executable stub with bound parameter, or 0 on failure
DECLARE_MANGLED pxl_rtl_bind1st, 8
	push ebp
	mov ebp, esp
	
	; Allocate executable memory
	push 13
	call pxl_rtl_xalloc
	
	; Copy arguments to registers
	mov ebx, dword [ebp+8]
	mov ecx, dword [ebp+12]
	
	; Copy stub into memory, return is in eax
	push edx
	mov byte [eax], 91      ; 0x5B pop ebx
	mov byte [eax+1], 104   ; 0x68 XXXXXXXX push dword $param1
	mov edx, ecx
	mov byte [eax+2], dl
	mov edx, ecx
	sar edx, 8
	mov byte [eax+3], dl
	mov edx, ecx
	sar edx, 16
	mov byte [eax+4], dl
	mov edx, ecx
	shr edx, 24
	mov byte [eax+5], dl
	mov byte [eax+6], 83    ; 0x53 push ebx
	mov byte [eax+7], 104   ; 0x68 XXXXXXXX push dword $func
	mov byte [eax+8], bl
	mov edx, ebx
	sar edx, 8
	mov byte [eax+9], dl
	mov edx, ebx
	sar edx, 16
	mov byte [eax+10], dl
	shr ebx, 24
	mov byte [eax+11], bl
	mov byte [eax+12], -61  ; 0xC3 ret
	
	; Return was in eax, leave it there
	leave
	ret 8                   ; two arguments

	
; void memcpy(void* dest, void* source, uint numBytes)
;  On older atoms it would be faster to compare dwords and then the remainder
;   by bytes, but this isn't true in general (http://stackoverflow.com/a/12359290/1140105)
; @param dest
; @param source
; @param numBytes
; @return void
DECLARE_MANGLED pxl_rtl_memcpy, 12
	push ebp
	mov ebp, esp
	
	mov edi, dword [ebp+8]  ; dest
	mov esi, dword [ebp+12] ; src
	mov ecx, dword [ebp+16] ; count
	rep movsb	
	
	leave
	ret 12 ; three arguments
	
	
; int memcmp(void* left, void* right, uint numBytes)
;  This can be done faster comparing 64-bits at a time with PCMPEQD (MMX)
; @param void* left
; @param void* right
; @param uint  numBytes
; @return int            0 for equal, otherwise -1/1 for which is 'greater'
DECLARE_MANGLED pxl_rtl_memcmp, 12
	push ebp
	mov ebp, esp
	
	mov edi, dword [ebp+8]  ; left
	mov esi, dword [ebp+12] ; right
	mov ecx, dword [ebp+16] ; count
	
	repe cmpsb
	
	je .is_eq
	jl .is_less
	
	; cmov can't move from an immediate
.is_gt:
	mov eax, 1
	jmp .done
.is_eq:
	mov eax, 0
	jmp .done
.is_less:
	mov eax, -1
	;jmp .done	
	
.done:
	leave
	ret 12 ; three arguments
	
	