INLINE DOCUMENTATION INTERFACING SCHEME TO ASSEMBLY CODE BY LARRY

BESTEKTEKST SMARTLOOP INLINER 14032017 FONTERRA SMARTLOOP INLINER BESTEKTEKST 52
BLUETOOTH® INLINE FLOW METER TASK FORCE TIPS MODEL
CLAIRE BECCY SMITH 124 MAINLINE ROAD LONDON GREATER LONDON

D EUTSCHER ROLLSPORT UND INLINEVERBAND EV SPORTKOMMISSION ROLLKUNSTLAUF SCHIEDSVEREINBARUNG
INLINE DOCUMENTATION INTERFACING SCHEME TO ASSEMBLY CODE BY LARRY
INLINE ENGINEERING – A STUDY IN CREATIVE CORPORATE FINANCING

-----------------------------INLINE DOCUMENTATION-------------------------------
	Interfacing Scheme to assembly code, by Larry Bartholdi
--------------------------------------------------------------------------------

	Typically, scheme users (as all high-level language users) are allowed
little if no knowledge of the whats and the hows of their system. The reason
is somewhat ideological (opposing 'hackers' to 'theoreticians'), but is also
a safety concern. Using a lower level allows greater freedom but meaner bugs,
and the software engineer should always consider the high-level approach first.

Nevertheless, PC Scheme/Geneva allows full access to lower levels through
a special list structure, that can be executed. The lists that can be executed
are of two kinds: PCS-CODE-BLOCKS (PCB) and PCS-INLINE-BLOCKS (PIB).

A PCB is defined thus: (list 'PCS-CODE-BLOCK #symbols #codebytes
			(list <symbol> ...) (list <codebyte> ...))

It is supposed to be executed by the PCS virtual machine (VM).
The symbols are accessible from the code through the mov_constant instruction,
and the code bytes are of course the instructions to be executed.
If this sounds too abstract, try ``(compile '(+ a 1))'', and you will see:

	(PCS-CODE-BLOCK 1 10 (A) (2 4 1 6 8 0 80 4 8 59))

This is a code block containing 1 symbol (A), and 10 code bytes, reading

	2 4 1		: load-immediate 1
	6 8 0		: lookup-value of A
	80 4 8		: add up the values
	59		: return

The complete instruction set can be found in sources.asm\assembly.ash.
Generally speaking, these instructions are executed by a 64-register
tagged virtual machine. Scheme code is translated in a highly optimised
VM code block, so little can be gained by hand-coding inner loops.
If you don't trust me, give a look to recur.s. It contains a good scheme
routine and an optimal VM machine. The performances match to about 10%,
while the development times differred by a factor of 10 !
Note also that VM instructions might differ from one version of PCS to
another, so you might have to rewrite your code if you use PCB directly...

Of greater interest are PIBs. They are defined as:
	(list 'PCS-INLINE-BLOCK #bytes (list <byte> ...))

The byte list corresponds to 8086 instructions. As a PIB is executed,
control is passed to the first byte of the list. The assembly subtask
must then return with a far return (#hCB). All registers may be trashed except
SS and SP (and of course CS and IP!).

A trivial example is then: (try it!)

	(%EXECUTE '(PCS-INLINE-BLOCK 1 (#hCB))) ---> undefined value

What is now needed is parameter passing. This is provided by a special
construct,
	(inline-lambda (args ...) P[IC]B).

Inline-lambda takes an argument list (or a number of arguments) and executes
the block it was passed with the arguments spreaded over VM registers R1-R61.
The assembly or VM code can then access the registers, modify them, etc.
and return a value in register R1.

Again a simple example:

	((inline-lambda (n) '(pcs-inline-block 1 (#hCB))) 1234)

would return 1234, because the value passed in R1 (1234) is not changed.

To code less trivial programs, some definitions, macros and aliases are
needed. They can be found in sources.asm\inline.ash.
inline.ash defines equates for VM registers R0-R63
(R0 is nil, and R63 is reserved), structures to access these registers,
and helper functions.

A VM register is defined as a base and a displacement into VM's paginated
memory. The bases are 8-bit values used as indexes in a segment table. 
To examine the object pointed to by a register, one would typically use:

	mov	bx, [reg1.page]		; get the register's page
	call	ldpage C, bx		; get the corresponding segment in bx
	mov	es, ax
	mov	bx, [reg1.disp]		; get the displacement part of the reg.

and the data can be found at [es:bx].

The objects can be of various types: fake objects like short integers
(signed 16-bit) which are just their displacement value, or true
objects composed of a tag (1 byte), a length (2 bytes) and some data.
The length includes the tag and the length bytes. Many objects contain
pointers, which are just another representation for a register, except
the register is 32-bit with disp first, while the pointer is 24-bit
with page first.
The tag-length-data motto is violated by two objects: lists and floating-point
numbers, whose size is fixed. A pair is just two pointers, while a flonum
is a tag followed by the 64-bit data. My...

You can recognize or allocate a new Scheme object specifying its type (tag):
	LISTTYPE	=	0
	FIXTYPE		=	2
	FLOTYPE		=	4
	BIGTYPE		=	6
	SYMBTYPE	=	8
	STRTYPE		=	10
	VECTTYPE	=	12
	CONTTYPE	=	14
	CLOSTYPE	=	16
	FREETYPE	=	18
	CODETYPE	=	20
	I86TYPE		=	22
	PORTTYPE	=	24
	CHARTYPE	=	26
	ENVTYPE		=	28
Any other value would cause unpredictable result... See INLINE.ASH for more
details on internal structure of VM objects.

Reg0-reg63 are aliases; an assembly code block is given control with DS:SI
pointing to register R0, so SI must be preserved as long as the
routine wants to access registers. DI is also used and points to a dispatch
table allowing dynamic linking to the most important PCS routines,
here documented by example:

	call	ldpage C, PAGE
			returns a segment descriptor in ax

	call	alloc_big_block C, REGPTR, TYPE, SIZE
			allocates a big memory block (> 1/128 of total memory)
			REGPTR is a register's address that will point to the
			new object.
	call	alloc_block C, REGPTR, TYPE, SIZE
			allocates a variable-length object.
	call	alloc_flonum C, REGPTR, FLOVALUE
			allocates and stores a 64-bit FP value
	call	alloc_int C, REGPTR, BIGNUM_buffer
			ditto - where a BIGNUM buffer is a size (1 word)
			in words, a sign (1 byte) 0 or 1, and the data, LSB first.
	call	alloc_list_cell C, REGPTR
			finds storage for a pair
	call	alloc_string C, STRING
			ditto - the string is null-terminated

	call	cons C, REGPTR1, REGPTR2, REGPTR3
			allocates a pair, sets its car to REGTR2 and its cdr to
			REGPTR3, and makes REGPTR1 point to the pair.

	call	free C, memory block
			the standard C function

	call	getch C
			guess what
	call	get_max_cols C
			returns the screen's width
	call	get_max_rows C
			returns the screen's depth

	call	int2long C, REGPTR
			reads a number from the register REGPTR and returns
			it in DX:AX (32-bit signed). No checks are done.

	call	is_graph_mode C
			returns 1 if graphics mode, 0 if alphanumerical

	call	long2int C, REGPTR, 32VALUE
			sets register REGPTR to the 32-bit signed value 32VALUE

	call	malloc C, SIZE
			the standard C function
	call	nosound C
			turns off the speaker
	call	sound C, FREQUENCY
			turns on the speaker
	call	zcuroff C
			turns off the cursor
	call	zcuron C
			turns on the cursor
	call	zprintf C, FORMAT, ...
			like C's printf
	call	zputc C, CHAR
			write a character to the screen
	call	zscroll C, LINE, COL, NLINES, NCOLS, ATTRIBUTE
			scrolls the screen area up 1 line
	call	zscroll_d C, (ditto)
			scrolls the screen area down 1 line

Among the tricks to know is that an assembly block may be placed at anytime
in memory, so all local variables must be accessed pc-relative, with the
code:

	call	$+3
	pop	bp
	sub	bp, $-1
	mov	ax, [bp+mydata]
...
mydata	dw	?

An example of this is given in inline\snow.asm.

Besides providing useful equates, inline.ash defines two macros,

	startinline	name, argcount
and
	endinline

These construction blocks delimit an assembly subroutine.
The trivial example would be coded:
;---------------------------------------------- start of TRIVIAL.ASM
IDEAL
INCLUDE "inline.ash"
StartInline	TRIVIAL, -1	; -1 means any argument count (mu-lambda)
	ret
EndInline
END
;---------------------------------------------- end of TRIVIAL.ASM
and assembled with
	TASM	trivial
	TLINK /t trivial, trivial.bin
now type PCS
	(load "trivial.bin")
	(trivial 1 2 3)
	----> (1 2 3)
	(exit)


You'll find some other examples in the inline\ subdirectory.

Note how to share tasks between Scheme and assembly code in 
PEEK.ASM and PEEK.S : Typically, type checking, link to the debugger 
and user interface should ALWAYS be written in pure Scheme.
You should only use assembly code to do things you wouldn't be able
to do in scheme (in our exemple, direct memory and port access).
The compiled version, PEEK.BIN and PEEK.FSL, is located in PCS
system directory but since they make an attempt against PCS decency,
they are not spontaneously loaded; use (load "peek.fsl") if needed.



Please send any comments to [email protected],
and, most of all, good luck !

20 nov 1992 (lb)
	

	

(*) Borland products (TASM, ...) are trademarks of Borland International Inc.
	


inline-supplementary-material-1
inline-supplementary-material-3
inline-supplementary-material-5


Tags: assembly code,, use assembly, assembly, documentation, interfacing, larry, inline, scheme