AMOS Professional Extension Development

Original Notes

+Music.s contains instructions for how to make an extension. I've added my own notes below and clarified some of the existing information.

Defining Tokens

Return value and parameter lists

Don't forget the terminating -1! If you built it and forgot it, trying to start AMOS Pro will do nothing. Replace with you known good copy and reboot, then try again.

Return value

  • I - none
  • 0 - long
  • 2 - string*

Parameters

  • 0 - long
  • 2 - string*
String parameters

Strings are stored in AMOS with a word for string length (so strings can be up to 65535 characters in length), and then the string itself. I'm not sure if they're null terminated.

Your own routines

You can Rbsr and other branch related jumps to your own stuff, but you can't Rjsr to your routines! Only to built-in AMOS routines. Use Rbsr and its equivalent! The Compiler will let you know of this mistake very quickly.

Built-in Routines

`L_Ram*`

These call exec/AllocMem for you through AMOS.

  • L_RamChip == PUBLIC|CLEAR|CHIP
  • L_RamChip2 == PUBLIC|CHIP
  • L_RamFast == PUBLIC|CLEAR
  • L_RamFast2 == PUBLIC
snippet.asm
  MOVE.L #400,D0
  Rjsr L_RamFast
  BNE RamAllocated
  ; couldn't get RAM, D0 is 0
RamAllocated:
  ; D0 contains address of RAM

Free it with L_RamFree:

snippet.asm
  MOVE.L D0,A1
  MOVE.L #length,D0
  Rjsr L_RamFree

L_Demande and HiChaine

When you want to demand string space, you apply for it with L_Demande.

The requested length goes into D3. It has to be even. You get the base address in both A0 and A1.

This is an AMOS string, so the first two bytes are the size of the string as a word. Then the string comes after.

Then, you have to eventually hand HiChaine(A5) (be sure to preserve A5) the ending address of the string.

Make sure you don't try to allocate a zero width string with this! It will fail hard.

Writing out a routine in +CompExt.s with comments to understand this:

snippet.asm
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;	=COMP ERR$
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	Lib_Par	CompErr
; - - - - - - - - - - - - -
	Dload	a2 ; get the data area
	moveq	#0,d3 ; clean out d3
	move.w	LErreur-CD(a2),d3 ; this is the length of the error string as a word
	beq.s	.Vide ; if it's zero there's nothing to do
	Rjsr	L_Demande ; get string space, put the address in a0 and a1
	move.w	d3,d0 ; preserve existing length in d0
	move.l	a0,d3 ; move the address into d3
	move.w	d0,(a1)+ ; put the length into the start of the string
	subq.w	#1,d0 ; reduce the length by 1
	lea	Erreur-CD(a2),a0 ; load the error string address into a0
.Loop1	move.b	(a0)+,(a1)+ ; copy a bit from a0 to a1
	dbra	d0,.Loop1 ; decrement d0. if d0 is greater than 0, go to loop
	move.w	a1,d1; put the current target address into a1
	and.w	#$0001,d1 ; keep the lowest bit
	add.w	d1,a1 ; add the current address to a1
	move.l	a1,HiChaine(a5) ; update the high end of string memory
	moveq	#2,d2
	rts
.Vide	move.l	ChVide(a5),d3 ; return empty string
	moveq	#2,d2
	rts

Here's a better version:

snippet.asm
    ; D4 contains your string length
    MOVE.L D4,D3
    AND.W #$FFFE,D3
    ADDQ #2,D3 ; round up to the nearest 2
 
    Rjsr L_Demande ; string is in A0/A1
    LEA 2(A0,D3.W),A1
    MOVE.L A1,HiChaine(A5)
    MOVE.L A0,A1 ; put A1 back
    MOVE.W D4,(A0)+ ; put length of string at start of memory
.stringstuff
    MOVE.B (A2)+,(A0)+
    BNE .stringstuff ; copy until null terminator

You know your string code is working if you can do this:

snippet.basic
A$=My String Function$ ' returns "wow"
Print "cat" + A$ + "dog" ' "catwowdog"

Returning an empty string

Send ChVide(A5) to D3. “vide” in French is “empty” so that makes sense.

Compiler

AMOS Professional will let you be a little sloppy with the differences between Lib_Def, Lib_Par, Rbcc, and Rjsr. The compiler will not.

Ensure you have your compiler set up properly:

  • Show default screen
  • Don't put AMOS to back
  • Include the AMOS library

Save those settings and you can use apcmp file.amos inclib in your build script to build something for testing.

If your code is crashing, check for the following:

  • You are only using Rbcc for routines in your library.
  • You are ideally only Rbcc to Lib_Def routines, not Lib_Par routines.

If you do this, the compiler will be happy. I suggest having a test AMOS file you can compile and run that will exercise all the code that Rbccs within your own code and running it often, to keep you safe.

Tips and Tricks

  • Memory blocks and Peek/Deek/Leek are one of the few ways you have of debugging things. Don't worry about allocating RAM and not freeing it at this point. You'll likely be rebooting very soon anyway…
  • Bisecting your changes is a must! You're going to be rebooting your machine a lot.
  • Don't forget to put the address registers back on the stack!
    • Ensure you test closing AMOS after you do stuff. Some bad things only happen once AMOS itself is unloaded.
      • Look for A4-A6 and D6-D7 not being restored, or A3 not being completely unwound for functions.
  • When adding new tokens, double-check that you are putting the label in the right spot!
snippet.asm
; functions
dc.w L_Nul,L_ThisWillReturnAValue
 
; instructions
dc.w L_ThisReturnsGarbage,L_Nul
  • Keep a known good copy of your extension around! If your startup routine is suddenly failing, you'll need to reboot and restore the known good copy.
    • This is because the Library_Digest executable seems to load AMOS config and will OOM if the extension is broken? Or maybe this just needs a reboot. Or a rewrite in C.
  • Set up AMOS assigns in your build script.
  • Did you Dload A3 into code that takes function parameters? You need to preserve and restore A3, otherwise AMOS will crash.
  • Checking a memory area to see if something needs to be freed? Be sure to clear that memory to avoid double frees.
  • Extension titles are one line only. They stop rendering at any lower ASCII character.
  • Did I mention you're going to be rebooting a lot? Setting up an ASSIGN to your code, Leaving your code file Out on the desktop, and whatever else you can do to get back into coding is necessary.
    • Of course, if you're cross-compiling, this is less of an issue.
  • You don't seem to be able to INCLUDE a file that contains Lib_Def code.
  • If you discovered you had been unwinding stacks incorrectly or anything else that could screw AMOS up, just reboot.
  • Don't use A5 for stuff! Unless you put it back right away. That's where extension memory is located.

How-Tos

Add a new command to be used in the Editor

These use a bunch of macros I built for the BSD Socket extension:

  • Add a new function definition to the token table:
snippet.asm
C_Tk dc.w 1,0
;...
  ; if something that accepts parameters and returns a value
  AddTokenFunction MyNewFunction
  dc.b "my new functio","n"+$80,"00,0",-1
 
  ; if something that returns nothing and just has side effects
  AddTokenInstruction MyNewInstruction
  dc.b "my new instructio"."n"+$80,"I",-1
  • Add a new Lib_Par section to the bottom of the file
snippet.asm
  Lib_Par MyNewFunction
 
  PreserveStackFunction
  RestoreStackFunction
  Ret_Int
  • For functions with more than one parameter, peel off the parameters from A3 in reverse order:
snippet.asm
  ; takes four parameters
  Lib_Par MyNewFunction
 
  PreserveStackFunction
 
  MOVE.L D3,D4 ; last parameter
  MOVE.L (A3)+,D3 ; second-to-last
  MOVE.L (A3)+,D2 ; second-to-first
  MOVE.L (A3)+,D1 ; first
 
  RestoreStackFunction
  Ret_Int
  • If returning an integer, put it in D3:
snippet.asm
  Lib_Par MyNewFunction
 
  PreserveStackFunction
  MOVE.L (A0),D3 ; return value
  RestoreStackFunction
  Ret_Int