include mdef.inc
include struct.inc

	name	profilog	; PROlog + proFIle + epiLOG
	.586p

public  "C",__P5_overhead
xref	"C",__ProfAlloc

NUM_ALLOC equ 10
SPARE_SLOTS equ NUM_ALLOC*100

P5_timing_info struc
	flag		db ?	; flags
	__fill1		db ?	;
	__fill2		dw ?	;
	lo_count	dd ?	; 64-bit invokation counter
	hi_count	dd ?	; ...
	semaphore	dd ?	; semaphore (union with dynamic link)
	dynamic		equ semaphore
	lo_cycle	dd ?	; 64-bit clock counter
	hi_cycle	dd ?	;
	caller		dd ?	; calling function's adddress
	call_ins	dd ?	; calling instruction (in caller)
	callee		dd ?	; called function's address
P5_timing_info ends

_DATA segment word public 'DATA'
__P5_overhead		label dword
__P5_overhead_lo	dd 0
__P5_overhead_hi	dd 0
free_pointer		dd 0
end_pointer		dd 0
_DATA ends

dgroup group _DATA

_TEXT segment word public 'CODE'

assume ds:dgroup

;
; Calling Sequence
; ================
;
; Caller:
;
;	call	Callee
;	test	esp,offset CallBlock
;   .. or ..
;	call	eax	; any indirect
;	test	ebp,offset CallBlock
;
;    Callee
;	push	offset FunctionBlock
;	call	__PON
;
;	... body
;
;	push	offset FunctionBlock
;	call	__POFF
;
; in the function body, the stack looks like this
;
; 0[esp]	saved eflags
; 4[esp]	saved edi
; 8[esp]	saved esi
;12[esp]	saved ebx
;16[esp]	saved ecx
;20[esp]	saved eax
;24[esp]	saved edx
;28[esp]	return address to __PON/__POFF's caller
;32[esp]	offset FunctionBlock
;36[esp]	return address to Function's caller
;
my_return_addr		equ	28
profile_block		equ	32
func_return_addr	equ	36

;
; The P5_timing_info blocks for indirect calls looks like this:
;
;						flag callee dynamic
;
; Statically generated compiler blocks:		0    &c1    0
;						0    &c2    0
;						'+'  &c3    pointer ---+
;								       |
; Dynamically allocated blocks			0    &c4    0 <--------+
;						0    &c5    0
;						0    &c6    0
;						0    &c7    0
;						0    &c8    0
;						0    &c9    0
;						'+'  &ca    pointer ---+
;							               |
; More Dynamically allocated blocks		0    &ca    0 <--------+
;						0    &cb    0
; Free blocks					0    0      0
;						0    0      0
;						0    0      0
;						0    0      0
; End of free blocks				'-'  0      0
;




public	__PON
__PON	proc near
	push    edx
	push    eax
	rdtsc				; read the timer as early as possible
	push    ecx
	push    ebx
	mov	ebx,eax			; save the time
	mov	ecx,edx			; ...
	sub	ebx,__P5_overhead_lo	; time -= overhead
	sbb	ecx,__P5_overhead_hi	; ...
	sub	__P5_overhead_lo,eax	; overhead -= start_time
	sbb	__P5_overhead_hi,edx	;    (end_time part done in epilog)
	push    esi
	push    edi
	pushfd
	mov	esi,profile_block[esp]	; get pointer to profiler block
	add     dword ptr lo_count[esi],1; bump its counter
	adc     dword ptr hi_count[esi],0; ...
	mov	eax,my_return_addr[esp]	; get callee.  It's the return address
	sub	eax,10			; ... adjusted for push L1; call __PON
	mov	callee[esi],eax		; save in profile block
	inc     dword ptr semaphore[esi]; indicate block in use
	jne     done			; if in recursive function, all done

	sub	lo_cycle[esi],ebx	; func_time += __POFF_time - __PON_time
	sbb	hi_cycle[esi],ecx	;    (__POFF_time done in __POFF)

	mov	edi,my_return_addr[esp]	; get return address
	cmp	word ptr [edi],0c4f7h	; is a test esp instruction?
	je	done			; if it is, this is a user inserted
					; call to __PON, so we're all done
	mov	edx,func_return_addr[esp]; get calling instruction (return addr)
	dec	edx			; back up one for proper source cue
	mov	eax,my_return_addr[esp]	; Get callee.  It's the return address
	sub	eax,10			;     adjusted for push L1; call __PON
	mov	edi,func_return_addr[esp]; get callee's return address
	cmp	word ptr [edi],0c4f7h	; is a test esp instruction?
	jne	ind			; if it is (then it's a direct call)
	mov	edi,2[edi]		; get pointer to "call" block
update:
	sub	lo_cycle[edi],ebx	; call_time += __POFF_time - __PON_time
	sbb	hi_cycle[edi],ecx	;    (__POFF_time done in __POFF)
	add	dword ptr lo_count[edi],1; bump up call counter
	add	dword ptr hi_count[edi],0; bump up call counter
	mov	call_ins[edi],edx	; remember the call instruction
	mov	callee[edi],eax		; remember the callee function
	jmp	done			; all done!
found:
	mov	call_ins[esi],edi	; save pointer in call_ins field for now
	jmp	update
ind:
	cmp	word ptr [edi],0c5f7h	; is a test ebp instruction?
	jne	done			; if not, all done
	jmp	done
	mov	edi,2[edi]		; get pointer to call block

again:
	cmp	callee[edi],eax		; for( ;; ) {
	je	found			; - found if block found for this callee
	cmp	callee[edi],0		; - found if unused block found
	je	found			; - ...
	cmp	byte ptr flag[edi],0	; - if at end of block group
	jne	next_group		; - move on to the next group
	add	edi,sizeof P5_timing_info;- move on to next block
	jmp	again			; }

next_group:
	cmp	dynamic[edi],-1		; if there is no next group
	je	allocate		; - allocate one
	mov	edi,dynamic[edi]	; else
	jmp	again			; - search through it

allocate:
	push	eax			; save registers that contain
	push	ebx			; ... the timer values
	push	ecx			; ...
alloc_again:
	mov	eax,free_pointer	; if we don't have a spare group of blocks
	cmp	eax,end_pointer		; ...
	je	out_of_memory		; dynamically allocate more

	add	eax,NUM_ALLOC*sizeof P5_timing_info; grab a block of infos
	mov	free_pointer,eax	; ...
	mov	byte ptr [eax-sizeof P5_timing_info].flag,'-'; mark end of block
	sub	eax,NUM_ALLOC*sizeof P5_timing_info ; back up to start of block
	mov	dynamic[edi],eax	; link the infos into the chain
	mov	flag[edi],'+'		; indicate this is a linked info
	mov	ebx,caller[edi]		; for each info in the new block
	xor	ecx,ecx			; - ...

@@:	mov	caller[eax+ecx],ebx	; ... set caller field
	add	ecx,sizeof P5_timing_info; ...
	cmp	ecx,NUM_ALLOC*sizeof P5_timing_info
	jl	@B	

	mov	edi,eax			; save pointer to first new block
	pop	ecx			; restore registers with timer values
	pop	ebx			; ...
	pop	eax			; ...
	jmp	found			; jump to found code with pointer in edi

out_of_memory:				; we're really out - dyna-alloc big wack of infos
	mov	eax,SPARE_SLOTS*sizeof P5_timing_info
	push	eax			; ask for a bunch of blocks
	call	__ProfAlloc		; user replacable malloc routine.
	add	esp,4
	test	eax,eax			; if NULL, we're screwed - punt
	je	really_out_of_memory	; ...
	mov	free_pointer,eax	; set up start/end free pointers
	mov	end_pointer,eax		; ...
	add	end_pointer,SPARE_SLOTS*sizeof P5_timing_info
@@:					; initialize the blocks
	mov	lo_count[eax],0
	mov	hi_count[eax],0
	mov	semaphore[eax],-1
	mov	lo_cycle[eax],0
	mov	hi_cycle[eax],0
	mov	caller[eax],0
	mov	call_ins[eax],0
	mov	callee[eax],0
	mov	flag[eax],0
	add	eax,sizeof P5_timing_info
	cmp	eax,end_pointer
	je	alloc_again
	jmp	@B

really_out_of_memory:
	pop	ecx
	pop	ebx
	pop	eax

done:	popfd
	pop     edi
	pop     esi
	pop     ebx
	pop     ecx
	rdtsc
	add	__P5_overhead_lo,eax	; overhead += end_time
	adc	__P5_overhead_hi,edx
	pop     eax
	pop     edx
	ret	4
__PON	endp

public	__POFF
__POFF	proc near
	push    edx
	push    eax

	rdtsc				; read the timer as early as possible
	push    ecx
	push    ebx
	mov	ebx,eax
	mov	ecx,edx
	sub	ebx,__P5_overhead_lo	; time -= overhead
	sbb	ecx,__P5_overhead_hi	; ...
	sub	__P5_overhead_lo,eax	; overhead -= start_time
	sbb	__P5_overhead_hi,edx	; ...
	push    esi
	push    edi
	pushfd

	mov	esi,profile_block[esp]	; get the profile block
	dec     dword ptr semaphore[esi]; indicate leaving recursive function
	jge     donex			; done if still nested

	add     lo_cycle[esi],ebx	; time += end_time
	adc     hi_cycle[esi],ecx	; ...

	mov	edi,my_return_addr[esp]	; get return address
	cmp	word ptr [edi],0c4f7h   ; is a test esp instruction?
	je	donex			; if so, this is a user inserted
					; call to __PON, so we're all done

	mov	edi,func_return_addr[esp];get callee's return address
	cmp	word ptr [edi],0c4f7h	; is a test esp instruction?
	jne	indx			; if it is,
	mov	edi,2[edi]		; get the call info
foundx:
	add	lo_cycle[edi],ebx	; call_time += end_time
	adc	hi_cycle[edi],ecx	; ...
	jmp	donex

indx:
	cmp	word ptr [edi],0c5f7h   ; is a test ebp instruction?
	jne	donex			; if it is,
	jmp	donex
	xor	edi,edi			; - fish the call block pointer out of
	xchg	edi,call_ins[esi]	; - call ins (where we stashed it)
	test	edi,edi			; ...
	jne	foundx			; update time

donex:	popfd
	pop     edi
	pop     esi
	pop     ebx
	pop     ecx
	rdtsc
	add	__P5_overhead_lo,eax
	adc	__P5_overhead_hi,edx
	pop     eax
	pop     edx
	ret	4
__POFF	endp

public	__PINIT
__PINIT proc near
	ret
__PINIT endp

_TEXT	ends

	end
