LESSON #2 : OS-Friendly Fibonacci routine
Overview
In the previous lesson, we learnt how to write the Fibonacci algorithm in ASM 68K. This is half the work because the program is not able - in current state - to outputs the value. The program calculates the correct numbers but the only way to see results is to use a Debugger - such as MonAm embedded in the Devpac IDE.
To outputs the values, our program needs to interact with his environment. There are some differents ways to do this. We could create a file and writes the results inside. It is a possibility. We could also outputs the results in the CLI window (Command Line Interface). We might eventually open a screen and print the values on it. Or why not outputs the value on a remote system using the Serial port (RS232).
Well. In all cases, we will need to use the underlying Operating System. Only exception is when you deliberately fully take over the machine and use directely the chipsets - we often call this "Bang" the hardware.
For our today lesson, we will use the Operating System, in a OS-Friendly way.
The plan
We will see how to add all the needed code to our Fibonacci program so that it becomes a dedicated AmigaOS binary, usable in the CLI/SHELL.
To accomplish that, here is what we need to do :
- Use command line Arguments so that the user can interact with the program (INPUT).
- Protect the program from Bad user inputs (PROCESS).
- Compute the user inputs and calculates the corresponding Fibonacci numbers (PROCESS).
- Print the computed Fibonacci numbers to the CLI (OUTPUT).
- Return some Error Codes in case of something wrong (FEEDBACK).
Hello World
As a first OS program, we can try a simple 'Hello World' and explain it step by step.
What is important to keep in mind when programming the Operating System is to read the API carefully for each function we need to use. For the AmigaOS 3.x, best is to use the Amiga Developer Documentation.
I personally use the Online documentation available on this website :
http://amigadev.elowar.com
AmigaOS Includes and Autodocs
Or eventually, you could use the official documentation. But beware, it is AmigaOS 4 documentation, so there might be big differences with the old AmigaOS 3.x documentation. Anyway, this gives good overview of how to code the OS in C Language, with many nice examples.
Official AmigaOS.net Wiki
We will read the documentation for 3 functions used in the Hello World program. First step on AmigaOS is to use the EXEC library which is always open. This is a library similar to a DLL file on Windows. It is actually stored in the KickStart (ROM) so that it is available as soon as the computer boots. This is the entry that allows you to deal with all others libraries of the system. The EXEC library contains 2 importants functions in our case : OpenLibrary() and CloseLibrary() described here :
AmigaOS EXEC OpenLibrary()
AmigaOS EXEC CloseLibrary()
To output a string in the CLI, we need to open the DOS library. It contains many useful functions to print unformatted strings or formatted strings (similar to the C language printf). We will use the simplest one : PutStr(). It only needs the address of a NULL-terminated string and display it in the CLI window.
AmigaOS DOS PutStr()
Source code :
01: IncDir Work:Devpac/Include20/Include
02: Include exec/exec_lib.i
03: Include dos/dos_lib.i
04: Main:
05: lea DOSName,a1 ; A1 = DOS name.
06: moveq.l #36,d0 ; D0 = DOS minimal version.
07: CALLEXEC OpenLibrary ; D0 = OpenLibrary(A1, D0).
08: move.l d0,_DOSBase ; Store Result.
09: beq.b .exit ; Exit if D0 = 0.
10: move.l #StrHello,d1 ; D1 = Message to print.
11: CALLDOS PutStr ; D0 = PutStr(D1).
12: move.l _DOSBase,a1 ; A1 = DOS Base.
13: CALLEXEC CloseLibrary ; CloseLibrary(A1).
14: .exit ;
15: moveq.l #0,d0 ; D0 = DOS Return Code.
16: rts ; Exit Program.
17: _DOSBase dc.l 0
18: DOSName dc.b "dos.library",0
19: StrHello dc.b "Hello World !",10,0
The program begins by including some files from your disk. I use 'IncDir' to tell the compiler where to find the files on my disk. You have to modify it to use correct path. The directive 'IncDir' could be removed if you configure it globally in the Devpac settings : Menu / Settings / Assembler / Options... / Include field.
Then we include the 2 files that enable us to talk to the EXEC library and the DOS library, in a standard way. This two includes, you may want to look inside. You will see that they are quite simple. They defines the relative addresses of all the functions in the library. The values are relative to the Library Base. The Library Base is what you obtain in D0 each time you call OpenLibrary(). Also, at bottom of files, there is one useful MACRO.
01: CALLDOS MACRO
02: move.l _DOSBase,a6
03: jsr _LVO\1(a6)
04: ENDM
This macro is important to understand. You can see that it use a '_DOSBase' which is pushed inside the Address Register 'A6'. This is how all OS functions works on AmigaOS. Before calling a function, we need to push the Library Base (could be DOS, Graphics, Intuition, ...) in A6 just before calling the function we want to use with the 'JSR' instruction. Every call to a OS function use 'JSR' instead of 'BSR'. Both are quite same, except that BSR is 16-bits so that if you want to jump in a sub-routine, this one must be not too far from current location in memory. When dealing with OS function, we use a 32-bits jump because the Library Bases locations are far in memory. About the '_DOSBase' in the macro, this means we need to declare it in our program. This is what we have at bottom of our Hello World program :
01: _DOSBase dc.l 0
This is a reserved space in our program. 'DC.L 0' means that we DeClare a LONG (4 bytes) and is initialized with 0. It is same as when declaring a variable in C Language 'int _DOSBase = 0;'. This area will be, then, filled by the 'move.l D0,_DOSBase' in our main program just after the OpenLibrary(). Doing this correctely allow us now to use the MACRO 'CALLDOS'.01: IncDir Work:Devpac/Include20/Include
02: Include exec/exec_lib.i
03: Include dos/dos_lib.i
04: Main:
05: lea DOSName,a1 ; A1 = DOS name.
06: moveq.l #36,d0 ; D0 = DOS minimal version.
07: CALLEXEC OpenLibrary ; D0 = OpenLibrary(A1, D0).
08: move.l d0,_DOSBase ; Store Result.
09: beq.b .exit ; Exit if D0 = 0.
10: move.l #StrHello,d1 ; D1 = Message to print.
11: CALLDOS PutStr ; D0 = PutStr(D1).
12: move.l _DOSBase,a1 ; A1 = DOS Base.
13: CALLEXEC CloseLibrary ; CloseLibrary(A1).
14: .exit ;
15: moveq.l #0,d0 ; D0 = DOS Return Code.
16: rts ; Exit Program.
17: _DOSBase dc.l 0
18: DOSName dc.b "dos.library",0
19: StrHello dc.b "Hello World !",10,0
The program begins by including some files from your disk. I use 'IncDir' to tell the compiler where to find the files on my disk. You have to modify it to use correct path. The directive 'IncDir' could be removed if you configure it globally in the Devpac settings : Menu / Settings / Assembler / Options... / Include field.
Then we include the 2 files that enable us to talk to the EXEC library and the DOS library, in a standard way. This two includes, you may want to look inside. You will see that they are quite simple. They defines the relative addresses of all the functions in the library. The values are relative to the Library Base. The Library Base is what you obtain in D0 each time you call OpenLibrary(). Also, at bottom of files, there is one useful MACRO.
01: CALLDOS MACRO
02: move.l _DOSBase,a6
03: jsr _LVO\1(a6)
04: ENDM
This macro is important to understand. You can see that it use a '_DOSBase' which is pushed inside the Address Register 'A6'. This is how all OS functions works on AmigaOS. Before calling a function, we need to push the Library Base (could be DOS, Graphics, Intuition, ...) in A6 just before calling the function we want to use with the 'JSR' instruction. Every call to a OS function use 'JSR' instead of 'BSR'. Both are quite same, except that BSR is 16-bits so that if you want to jump in a sub-routine, this one must be not too far from current location in memory. When dealing with OS function, we use a 32-bits jump because the Library Bases locations are far in memory. About the '_DOSBase' in the macro, this means we need to declare it in our program. This is what we have at bottom of our Hello World program :
01: _DOSBase dc.l 0
The full program
Below is a complete program that use our Fibonacci routine, with extended features.
What the program does :
- Use the AmigaOS EXEC library.
- Open the AmigaOS DOS library.
- Read 2 arguments from the command-line : FROM and TO.
- Check the 2 arguments. Must be positive numbers (n >= 0).
- Calculates all the Fibonacci numbers between FROM and TO.
- Outputs the result in the command-line window.
- Free the opened resources (library and arguments).
- Returns an consistent error code, understandable by the user or a DOS Script.
- Exit the program.
How is organized the code :
The source code shows how a ASM program 'could' or 'should' looks like. One proposal is :
- Comment Block on top
- CPU Target
- Include files (AmigaOS3.x API)
- Private definitions
- Main program
- Sub-Routines
- Data Block at bottom
The full source code :
It introduces some other DOS functions such as ReadArgs() / FreeArgs() and another Print function that supports formatted strings : VPrintf(). AmigaOS DOS ReadArgs() AmigaOS DOS FreeArgs() AmigaOS DOS VPrintf()
It also introduces some other new concepts such as the use of SECTIONs. These are blocks of memory that are relocated from DISK to MEMORY by the Operating System when executing the program. Generally, we place our code in a SECTION CODE and our data (defined with the DC.x / DS.x at bottom of file) in SECTION DATA. These directives allows the developer to choose in which memory type to program goes. It is useful in particular when core or data need to be located in the CHIP memory for use with the OCS/ECS/AGA chipsets.
At bottom of source, we use a 'MyLabel DS.L 2'. Do not mingle DC.L and DS.L. DC.L (DeClare) allocate 1 long, whereas DS.L (Declare Space) allocate 'n' longs.
There are some new instructions used in code such as LEA (Load an address in a Address Register), BTST (Test a given Bit), BMI (Branch if Minus)... I advise you to read the documentation on the official Motorola PDF or on the http://68k.hax.com website.
***********************************************************
**
** Program : Fibonacci
** Version : 1.01
** Descr. : Output Fibonacci 32bits numbers.
** Author : Flype
** Copyright : Free to use.
**
***********************************************************
**
** Arguments:
**
** FROM/N/A : Mandatory, First Number to compute.
** TO /N/A : Mandatory, Last Number to compute.
**
***********************************************************
**
** Usage:
**
** >Fibonacci ?
** >Fibonacci 1 47
** >Fibonacci FROM=1 TO=47
**
** >Version Fibonacci FULL
**
** >Echo $RC
** OK ( 0) => No error occured.
** WARN ( 5) => An overflow occured.
** ERROR (10) => One or more bad arguments.
** FAIL (20) => Failed to open DOS V36+ library.
**
***********************************************************
**
** Limitation:
**
** Range of valid numbers is FROM=1 TO=47.
** Starting from Fibonacci(48), the result is incorrect
** due to the 32bits limitation which produces overflows.
**
***********************************************************
**
** History:
**
** 1.01 - Initial release.
**
***********************************************************
MACHINE MC68000
***********************************************************
** INCLUDES
***********************************************************
IncDir Work:Devpac/Include20/Include
Include exec/exec_lib.i
Include dos/dos.i
Include dos/dos_lib.i
***********************************************************
** PRIVATES
***********************************************************
RDARG_FROM EQU 0 ; FROM argument.
RDARG_TO EQU 4 ; TO argument.
***********************************************************
** MAIN PROGRAM
** D6 = DOS Return Code.
***********************************************************
SECTION .fast,CODE
Main:
.openDOS
lea _DOSName,a1 ; A1 = DOS name.
moveq.l #36,d0 ; D0 = DOS minimal version.
CALLEXEC OpenLibrary ; D0 = OpenLibrary(A1, D0).
move.l d0,_DOSBase ; Store Result.
beq.b .exitNoDos ; Exit if D0 = 0.
;==========================================================
.readArgs
move.l #ArgsTmplt,d1 ; D1 = Arguments Template.
move.l #ArgsArray,d2 ; D2 = Arguments Array.
moveq.l #0,d3 ; D3 = Optional RDArgs struct.
CALLDOS ReadArgs ; D0 = ReadArgs(D1, D2, D3).
move.l d0,_RDArgs ; Store Result.
beq.b .exitBadArgs ; Exit if D0 = 0.
lea ArgsArray,a1 ; A1 = Arguments array.
move.l RDARG_FROM(a1),a0 ; A0 = FROM argument.
move.l (a0),d0 ; D0 = FROM number.
btst.l #31,d0 ; D0 < 0 ?
bne.b .exitBadArgs ; Exit if D0 < 0.
move.l RDARG_TO(a1),a0 ; A0 = TO argument.
move.l (a0),d2 ; D2 = TO number.
btst.l #31,d2 ; D2 < 0 ?
bne.b .exitBadArgs ; Exit if D2 < 0.
;==========================================================
.loop
cmp.l d0,d2 ; While (D0 < D2)
bmi.b .closeArgs ; (
bsr Fibonacci ; D1 = Fibonacci(D0)
bsr PrintResult ; PrintResult(D0, D1)
add.l #1,d0 ; D0 + 1
bra.b .loop ; )
;==========================================================
.exitNoDos
moveq.l #RETURN_FAIL,d6 ; D6 = DOS Return Code.
bra.b .exit ; Branch to Exit.
.exitBadArgs
moveq.l #RETURN_ERROR,d6 ; D6 = DOS Return Code.
move.l #StrBadArgs,d1 ; D1 = String.
moveq.l #0,d2 ; D2 = NULL.
CALLDOS VPrintf ; D0 = VPrintf(D1, D2).
bra.b .closeDOS ; Branch to CloseDOS.
.closeArgs
move.l _RDArgs,d1 ; D1 = Struct RDargs.
CALLDOS FreeArgs ; FreeArgs(D1).
.closeDOS
move.l _DOSBase,a1 ; A1 = DOS Base.
CALLEXEC CloseLibrary ; CloseLibrary(A1).
.exit
move.l d6,d0 ; D0 = DOS Return Code.
rts ; Exit Program.
***********************************************************
** Fibonacci()
** INPUT: D0 = Number to compute.
** OUTPUT: D1 = Fibonacci Number.
***********************************************************
Fibonacci:
movem.l d0/d2/d3,-(sp) ; Store registers.
moveq.l #RETURN_OK,d6 ; D6 = DOS Return Code.
clr.l d1 ; D1 = 0
tst.l d0 ; D0 = 0 ?
beq.b .exit ; Exit if D0 = 0.
moveq.l #1,d1 ; D1 = 1
moveq.l #0,d2 ; D2 = 0
moveq.l #1,d3 ; D3 = 1
.loop
cmp.l #1,d0 ; While (1 < D0)
ble.b .exit ; (
move.l d2,d1 ; D1 = D2
add.l d3,d1 ; D1 + D3
bvs .overflow ; Branch if Overflow detected
.continue
move.l d3,d2 ; D2 = D3
move.l d1,d3 ; D3 = D1
sub.l #1,d0 ; D0 - 1
bra.b .loop ; )
.overflow
moveq.l #RETURN_WARN,d6 ; D6 = DOS Return Code.
bra.b .continue ; Come back to the loop.
.exit
movem.l (sp)+,d0/d2/d3 ; Restore registers.
rts ; Exit Sub-Routine.
***********************************************************
** PrintResult()
** INPUT: D0 = Number to compute.
** INPUT: D1 = Fibonacci Number.
***********************************************************
PrintResult:
movem.l d0-d2/a0,-(sp) ; Store registers.
lea ResArg1,a0 ; A0 = 1st arg address.
move.l d0,(a0) ; (A0) = 1st arg value.
lea ResArg2,a0 ; A0 = 2nd arg address.
move.l d1,(a0) ; (A0) = 2nd arg value.
move.l #StrResult,d1 ; D1 = Result String.
move.l ResArgV,d2 ; D2 = Result Array.
CALLDOS VPrintf ; D0 = VPrintf(D1, D2).
movem.l (sp)+,d0-d2/a0 ; Restore registers.
rts ; Exit Sub-Routine.
***********************************************************
** DATA SECTION
***********************************************************
SECTION .fast,DATA
_RDArgs dc.l 0
_DOSBase dc.l 0
_DOSName DOSNAME
EVEN
ResArg1 dc.l 0
ResArg2 dc.l 0
ResArgV dc.l ResArg1,ResArg2
ArgsArray ds.l 2
ArgsTmplt dc.b "FROM/N/A,TO/N/A",0
StrResult dc.b "Fibonacci(%lu) = %lu",10,0
StrBadArgs dc.b "Invalid Arguments.",10,0
StrVersion dc.b "$VER: Fibonacci 1.01 (10.5.2016) Flype",10,0,0
***********************************************************
** END OF FILE
***********************************************************
End notes
As we can see, making a program in a OS-Friendly way requires many new code around our primary Fibonacci routine. That's a fact ! On the other hand, with the corresponding documentation, it is not so difficult. AmigaOS provide a clean and easy way to 'talk' to the system in assembly language.