GWBasic Machine Code


Call Statement


The GWBasic Interpreter is a high level language. It reads each line of the program that was loaded into it's memory, interprets it and executes it. This takes time. However, Machine Language instructions can be sequentially executed, one instruction at a time without the need for interpretation. Refer to the DEBUG tutorial examples for reference.

If we wish to take advantage of the speed of the processor's native language, we can either compile the entire GWBasic program into an executable, or run selected parts of our GWBasic program with machine language using the CALL statement.

This example demonstrates one method of loading a machine language program into memory and executing it using the GWBasic CALL statement.

This example was taken from my GWBasic Users Manual, appendix D. Several changes needed to be made for it to work.
The first DATA line of data was missing a comma, generating a Syntex Error
.I inserted the comman.
Insufficient Data in the DATA array generated an Out of DATA error. I reduced the number of READs by changing the FOR/NEXT loop count.
Additionally, I removed un-necessary nulls at the last of the machine code. This wasn't necessary, but it simplifies explaination of the example.
After these correction, the program ran as expected. Be aware of these corrections if you copy the example in the manual to run this program.
I further modified the program for easier discussion in this example session.






What is this example all about

This GWBasic Manual (appendix D) example has been modified considerably, but it does the same thing in the same way as the manual's example. Both the BASIC program and the Machine code can be copied from the example or from the text in this example. This is essentially what the BASIC program does:

The first line of code assignes the variables. This must be done prior to calling VARPTR because assigning variables, changes their location in memory. We need to know where our machine code is. Space is assigned for the INDEX variable, the machine language CODE bytes, and the byte COUNT of the machine language program in the DATA statements.

Next, define space for an integer array that will hold the machine code, and not change position prior to calling it.

Then  a call VARPAR (variable parameter) returns the ARRAY's memory location to a variable named mem% and is assigned as File Control Block #1. We are ready to copy the machine code into memory from the DATA statements.

FOR the INDEX count of zero to the byte COUNT of the machine code: READ the DATA bytes sequentially and store them into executable memory at location MEM%  plus the offset in the INDEX using the POKE statement.

The three variables that are to be used are defined, the first two being the values to be added with the machine language program. The third is assigned so it's location will be known by the program.

Again the location of the program is interrogated and given a name so that it may be CALLED.

Finally the CALL statement is invoked using the assigned name to do all the work. The output and input variables are included in the CALL statement within perens. The module will sum C1%, C2% and return the answer in C3%.

Finally the values in all three variables are printed out for us to see. The answer in C3% may be used later in a running program, just as if it had been calculate with our interpreter.

The machine language program runs much faster than if C1% and C2% were summed into C3% using the + operator in the expression C3%= C1% + C2% because the interpreter isn't needed to crunch the numbers. Its done with the machine code.

This example is trivial; however, this scheme may be used any place in a running program after it is initially set up for use. An entire library of functions may be written for use in a variety of programs and kept available in a dynamic link library. If such a library is to be created, it would be better to use another method of CALLing them. The CALL function can also be used to CALL programs by name. The CALL statement can link to this library of programs and they can be called much the same as a sub-routine. These linkable programs are sometimes called INCLUDES to high level languages.





This is the GWBasic program


10 '          prog TWOSUM
20 '
30 '
40 '
50 ' This is where you put your own stuff
60 '
70 '
80 '
90 '
100 '     program start
110      INDEX= 0: CODE= 0: COUNT= 22
120    DIM ARRAY% (COUNT)
130  MEM%= VARPTR (ARRAY% (1))
140 '
150 FOR INDEX= 0 TO COUNT-1
160   READ CODE
170    POKE MEM% +INDEX,CODE
180      NEXT INDEX
190 '
200     C1%= 2: C2%= 3: C3%= 0
210  TWOSUM= VARPTR (ARRAY% (1))
220    CALL TWOSUM(C1%,C2%,C3%)
230 '
240 '
250 CLS: PRINT
260      PRINT " Display the sum of two numbers "
270      PRINT "   "; C1%
280      PRINT "  +"; C2%
290      PRINT "  ="; C3%
300      PRINT
310 '
320       END  
330 '
340 '
350 '         These are the machine code data.
360 '    Variable COUNT must equal the number of data bytes
370 '
380 DATA &H55, &H8B, &HEC, &H8B, &H76, &H08, &H8B, &H04
390 DATA &H8B, &H76, &H0A, &H03, &H04, &H8B, &H7E, &H06
400 DATA &H89, &H05, &H5D, &HCA, &H06, &H00
410 '
420 '
430 '
440 '
450 END   ' development handy backup statement
460 SAVE"twosum",A








The Un-Assembled code in the DATA Statement's Array

This is the code as it would appear Un-Assembled in DEBUG with my version of DEBUG.EXE: You can re-create it by typing in the DATA bytes using the DEBUG Enter feature. A batch file can even be created to call DEBUG and do it for you. This would save a lot of keyboard hacking if you wished to create and debug your own module. The BATCH and the DEBUG section of this tutorial are a good reference for doing such a thing.

This is basically what this simple module does. It is a trivial program, but it does serve to illustrate one use of the CALL statement.
The return address to our GWBasic program is pushed onto the stack so that it can be popped back off when the module has been executed, followed by the processors RETF (return far instruction releasing six bytes). Then the eighth byte at our VARPAR location is SI ed and its contents is MOVed into the AX register. The same is done with the tenth byte, and the contents of that location is added to the contents of the AX register. Then the sum, in the AX register is put back into our VARPAR's sixth memory location which happens to be our two byte variable that we have labeled C3%. The RETF instruction restores the return address of our GWBasic program that we have POPped off the stack. Then our BWBasic program can continue, just as if it added the two variables the long way.



-
-u 0100
0AFA:0100 55            PUSH    BP                          Save Base Pointer
0AFA:0101 8BEC          MOV     BP,SP
0AFA:0103 8B7608        MOV     SI,[BP+08]     Get address of parameter b
0AFA:0106 8B04          MOV     AX,[SI]                Get value of b
0AFA:0108 8B760A        MOV     SI,[BP+0A]     Get address of parameter a
0AFA:010B 0304          ADD     AX,[SI]
                Add value of a to value of b
0AFA:010D 8B7E06        MOV     DI,[BP+06]          Get address of parameter c      
0AFA:0110 8905          MOV     [DI],AX                Store sum in parameter c
0AFA:0112 5D            POP     BP
                          Restore Base Pointer
0AFA:0113 CA0600        RETF    0006                      Return


The clock cycles necessary to perform these half dozen processor instruction is far less than the time necessary to perform the GWBasic instructions necessary to perform these same actions and return the results into a variable that we can access using the VARPAR memory location. Low level language programming is an art in itself. Its a welcome trade off to be able to use an interpreter to perform these operations at the expense of the interpretation time. Thats the whole idea of High Level Languages. Not only does the code provide documentation for the programs structure, but the source code is transportable between processors. Each processor can interpret the BASIC source code in terms of that processor's instruction set. The CALL statement permits us the best of both worlds.




Smuuary
Link to the program and copy-paste it into a text file so you can run it with the GWBasic Interpreter. Load up the machine code into DEBUG and play around with the processor operations you might wish to explore. Put then together and see what you can do with your GWBasic program.

Isn't it nice that all these programs are available in the set of programs that have been provided with the Window's system? It isn't even necessary to go out and purchase a bunch of applications to write programs using the Windows system.
Its all here. Its just a matter of becoming familiar with their operation.

In short.... play around and have fun.........  Happy Hacking......