* This here is startup code for Amiga Aztec C programs, to make them pure.
* Assemble it with the command "as purify.a" and link the resulting purify.o
* file with your C program, in some manner like "ln yourprogram.o purify.o -lc",
* and your program can then be marked as pure, and made resident, IF it meets
* certain conditions:
* 
* 1) You must not use the large data model for accessing your global varialbes.
* 
* 2) You MUST NOT call the geta4() function from within any part of your program
* that is run outside of your main process.  Subtasks, interrupt handlers, and
* wedges installed with SetFunction may not use geta4, and thus may not access
* your global variables by any direct method.
* 
* 3) You must not initialize global variables using the addresses of other
* global variables.  For example, the following is not correct:
* 
*     char foo[100], bar[100];
*     char *foobar[2] = { foo, bar };		/* WRONG */
* 
* If you do this, then any references to foobar[0] will end up accessing
* DIFFERENT MEMORY than accessing foo directly will.  Instead, you have to do
* such initialization in the code, with statements like "foobar[0] = foo;
* foobar[1] = bar;".
*
* 4) You must not use detach.o; this applies to ALL pure programs, not just
* those that use this startup code.
* 
* 5) Do not use this when making a shared library or device driver.  ("Duh...")
* 
* This was originally based on resident.asm by Olaf Barthel, which is available
* on fish disk 396, but does not presently contain any of his code.  Barthel's
* version had serious errors in it, and will not work in many cases.  It was
* also a lot less efficient than the method I use.
*
* With the Aztec assembler, do not use the -N flag (one pass), because there's
* a bug, at least in 5.2a and older, that makes the ENTRY directive not work
* with that option.  It will need a few changes in syntax to work with other
* assemblers.  It might be possible to make versions for other C compilers, so
* I've labeled everything which I know to be Aztec specific with a comment
* containing the word "Aztec".
*
* The one possibly uncool bit of programming used here is that I used
* SysBase->ThisTask instead of FindTask(0), in several places.  If they ever
* make an Amiga that runs tasks on several CPUs at once, that might break.
* 
* This is by Paul Kienitz, 1 December 1991, placed in the public domain.


; We avoid pulling in include files with a few definitions here:

MEMB_FAST	equ	2		; exec/memory.i
MEMB_CLEAR	equ	16		; exec/memory.i
ThisTask	equ	276		; exec/execbase.i
AttnFlags	equ	296		; exec/execbase.i
AFB_68881	equ	4		; exec/execbase.i
AG_OpenLib	equ	$30000		; exec/alerts.i
AO_DOSLib	equ	$8007		; exec/alerts.i
pr_ReturnAddr	equ	176		; libraries/dosextens.i
pr_CLI		equ	172		; libraries/dosextens.i
pr_MsgPort	equ	92		; libraries/dosextens.i

_LVOTypeOfMem		equ	-534
_LVOAllocMem		equ	-198
_LVOOldOpenLibrary	equ	-408
_LVOAlert		equ	-108
_LVOSupervisor		equ	-30
_LVOFreeMem		equ	-210
_LVOCloseLibrary	equ	-414
_LVOWaitPort		equ	-384
_LVOGetMsg		equ	-372
_LVOReplyMsg		equ	-378
_LVOForbid		equ	-132


lcall		macro
		jsr	_LVO\1(a6)
		endm

AbsExecBase	equ	(4).w

		xdef	.begin,_geta4
		xref	__main,_SysBase,_DOSBase
		xref	__savsp					; Aztec
		xdef	entry,cleanup		; for debugging

; the use of the label ".begin" for the entry point is an Aztec-ism
		entry	.begin

.begin:
entry:
		far	data					; Aztec
		lea	__H1_org+32766,a4	; to access original globals

		lea	__H1_end,a1		; Aztec; end of initialized data
		lea	__H2_org,a2		; Aztec; start of BSS
		near	data					; Aztec

		cmp.l	a1,a2			; the same address?
		bne	bomb			; if not, refuse to run

		move.l	a0,d6			; save command arg-line pointer
		move.l	d0,d5			; save command arg length

		lea	__H1_org,a1		; Aztec; start of data hunk
		move.l	AbsExecBase,a6
		lcall	TypeOfMem		; what kind of ram is it in?
		bclr.l	#MEMB_FAST,d0		; do NOT force fast ram!
		bset.l	#MEMB_CLEAR,d0		; do force cleared memory

		move.l	d0,d1			; memory type requirements
		move.l	#(__H2_end-__H1_org),d0	; Aztec; size of all globals
		lcall	AllocMem		; space for a complete copy
		tst.l	d0
		beq	bomb			; if it failed, don't run
		move.l	d0,a2

		lea	__H1_org,a0		; Aztec; address of old globals
		move.l	a2,a1			; address of new globals
		move.l	#((__H1_end-__H1_org)/4)-1,d0	; Aztec; size to copy
copyit:		move.l	(a0)+,(a1)+		; copy all initialized data
		dbra	d0,copyit

* Here's where we get tricky.  We need to put the address of our dublicated
* global variable space someplace where it can be found by geta4(), and we need
* to make sure that we free the copied globals no matter how the program exits.
* What we do is save the pointer on the stack, and then make it appear that the
* final exit address that the program must return to is the address of our
* cleanup routine.  To do this, after pushing the pointer to the copied globals,
* we push the size of the remaining available stack (12 bytes less than the
* original size) and then the address of the cleanup code.  We set the variable
* __savsp to point to that cleanup code address, so that the Aztec exit()
* function will return to that point.  We set the pr_ReturnAddr field of our
* struct Process to point one longword above that, to make the dos.library
* Exit() function return to the cleanup code.  This is also used by geta4() to
* locate the saved pointer to the copied globals.

		move.l	a2,-(sp)		; where geta4() can find it
		move.l	8(sp),a0		; original size of the stack
		sub.w	#12,a0			; minus space we're using
		move.l	a0,-(sp)		; make stack look smaller
		move.l	ThisTask(a6),a0		; equivalent to FindTask(0)
		move.l	sp,pr_ReturnAddr(a0)	; for geta4() and dos Exit()
		pea	cleanup			; so everybody goes there
		move.l	sp,__savsp		; Aztec; exit() and stack check

* The stack now looks like this:
*
* 	Stack size in bytes		<-  old pr_ReturnAddr pointed here
* 	Return address for final exit	<-  __savsp would have pointed here
* 	Pointer to globals for geta4	<-  also used by cleanup
* 	New stack size, minus twelve	<-  new pr_ReturnAddr points here
* 	Fake return address: cleanup	<-  __savsp will point here
*
* Once __main is called, there will also be these:
*
* 	Command line arg address	<-  argument to __main
* 	Command line arg length		<-  argument to __main ...SP is now here
* 	Return address to this module	<-  falls through to cleanup

		bsr	_geta4			; use new globals
		move.l	a6,_SysBase		; set up base for C program
		lea	dosname,a1
		lcall	OldOpenLibrary		; open dos.library
		move.l	d0,_DOSBase		; set up another base
		bne	dosokay
		move.l	#AG_OpenLib!AO_DOSLib,d7	; recoverable - no dos
		lea	ThisTask(a6),a5		; make it show our task adr
		lcall	Alert			; frighten the user
		bra	bomb			; and die

dosokay:	btst.b	#AFB_68881,AttnFlags+1(a6)	; is there an FPU?
		beq	nofpu
		lea	resetfpu,a5
		lcall	Supervisor		; reset it in supervisor mode

nofpu:		movem.l	d5/d6,-(sp)		; pass arguments to _main()
		jsr	__main			; RUN THE PROGRAM!
		add.w	#12,sp			; pop extra longword to fake rts

cleanup:	move.l	d0,d7			; set aside the return value
		bsr	_geta4			; so we can check DOSBase
		move.l	4(sp),a1		; address of copied globals
		move.l	#(__H2_end-__H1_org),d0	; size of copied globals
		move.l	AbsExecBase,a6
		lcall	FreeMem			; get rid of the copy
		addq	#8,sp			; restore primordial stack
		move.l	ThisTask(a6),a1
		lea	4(sp),a0		; just to be paranoid...
		move.l	a0,pr_ReturnAddr(a1)	; restore old pr_ReturnAddr

		move.l	_DOSBase,a1
		move.l	a1,d0			; fake tst.l a1
		beq	dosneveropen		; was there ever a DOSBase?
		lcall	CloseLibrary		; if yes, close it

dosneveropen:	move.l	d7,d0			; restore the return value
		rts				; goodbye cruel world


bomb:		move.l	AbsExecBase,a6		; come here if we can't run
		move.l	ThisTask(a6),a0
		tst.l	pr_CLI(a0)		; are we a Workbench process?
		bne	noworkbench		; if yes, then we need to
						;   reply the startup message:
		lea	pr_MsgPort(a0),a0
		move.l	a0,a3			; remember our msgport address
		lcall	WaitPort		; here come de message
		move.l	a3,a0
		lcall	GetMsg			; got it
		move.l	d0,d3			; yes, remember it
		lcall	Forbid			; standard shutdown procedure
		move.l	a3,a1
		lcall	ReplyMsg		; tell WB to unload us

noworkbench:	moveq	#-9,d0			; the system filters out -1
		rts				; might return to cleanup


		mc68881		; Aztec; activate FPU instruction opcodes

resetfpu:	clr.l	-(sp)			; call this in supervisor mode
		frestore (sp)+			; reset the FPU
		rte				; return to user mode


* Here's our magic routine to set up a4 for accessing the copied globals.
* CALL ONLY FROM WITHIN THE SAME PROCESS.

_geta4:		move.l	AbsExecBase,a4
		move.l	ThisTask(a4),a4
		move.l	pr_ReturnAddr(a4),a4	; ptr to FAKE stack size
		move.l	4(a4),a4		; stored ptr to copied globals
		lea	32766(a4),a4		; add the offset
		rts

dosname:	dc.b	'dos.library',0		; for OldOpenLibrary

		dseg				; Aztec
; Assembler bug:  If you declare __H1_org public within the code segment,
; then the assembler refers to it with small data even if you have told
; it to use large data.  Hence the above dseg is necessary.  This does not seem
; to happen with ordinary symbols, just the __Hx_xxx ones.
		public	__H1_org,__H1_end,__H2_org,__H2_end	; Aztec

	ifd	SHIT
__main:		rts
	dseg
__savsp:	ds.l	1
_SysBase:	ds.l	1
_DOSBase:	ds.l	1
	endc
