SECD virtual machine for pure_LISP
[ Index ] [ LISP reference
] [ LISP tutorial ] [ SECD
collection ] [ SECD reference ]
This document describes an modified SECD virtual machine, used for implementation
of the pure_LISP language.
Use also the Tutorial - it provides some info
about installation and first steps in the package.
This document is not yet finished, so you may use the next additional
sources of info for my SECD machine:
- file History.txt describing the evolution
of my SECD architecture.
- pascal sources in subdirectory meta, especially file secd_cod.pp,
defining the main parts of the machine (at the begining of this file
there is detailed comments).
1. Data types, registers, syntax of the command codes.
Registers
hard_SECK may be treated as 4 registers machine:
- S is the stack.
- E is the environment.
- C is the code (the executed program).
- K is the kernel state.
For efficiency it is represented by 4 meta_registers:
- SK - kernel stack
- EK - kernel environment
- CK - kernel code
- U - the copy of the interrupted user process.
hard_SECK include the boolean userMode. While the kernel is working,
userMode is false and if some user process is invoked, userMode becomes true.
2. Main codes (single process commands).
These codes may be devided into 2 groups:
1. Control codes - they may change E and C
registers or may intrrupt user process in some cases.
For proper handling of tail recursion there is the only code for the control
restoration - #rtn, represented by the nil value of the
C register.
There is also a pre-code CleanX, used by codes #rtn, #rec
and #trrec.
Post-code StartRec(p) begins a process of recovering for wating
recipe:
Non-regular control codes:
- CleanX - pre-code, substitutes evaluated recipe with
their value:
while top
of S is an evaluated recipe [nil.x]
do ([nil.x].s) e c --> (x.s) e c ;
if top of S is a working recipe then #intr_2 ;
- StartRec(p) - post-code, begins evaluation of a waiting
recipe:
([c'.e'].s)
e nil --> ([p.nil].s) e' c'
- #rtn - restores the control.
S must have the form (x R.s) where x is allready
evaluated value, R is working recipe of the form [p.q]
or the previous state of the environment E:
if R is recipe [p.q] then begin
CleanX;
if S is ([c'.e'] [p.q].s)
then StartRec(0)
else begin
if x is a node (l.r) then begin
replace [p.q]
with (l.r);
(x (l.r).s) e
nil --> ((l.r).s) e nil
end else begin
replace [p.q]
with [nil.x] ;
(x [nil.x].s)
e nil --> (x.s) e nil
end;
if q is node then #intr_3 else
#prim_p ;
end;
end
else (x e' c'.s) e nil --> (x.s) e' c'
Regular control codes:
- 1 #ld_x_y
s e c
--> ((GetNth(GetNth(e,x),y).s) e c
Loads a value from the environment E.
- 2 #ldc
s e (x.c)
--> (x.s) e c
Loads a constant.
- 3 #ldf
s e (f.c)
--> ((f.e).s) e c
Loads a function.
- 4 #delay
s e (c'.c)
--> ([c'.e].s) e c
Delay evaluation of the control list c'.
- 5 #case_k
if i in
[0..k-1] then c_i:=ci else c_i:=ck
(i.s) e (c0 ... ck.c) --> (c_i.s) e c
Starts conditional evaluation - loads the control list ci.
- 6 #dum
s e c
--> s (nil.e) c
Creates new dummy environment for letrec block.
- 7 #stop
Terminates the SECK machine (allowed only in kernel mode).
- 8 #call
if c=nil
then (c'.s) e c --> s e c'
else (c'.s) e c --> (e c.s) e c'
Calls the control list c'.
This code is used to finish the control functions if, case
and fork.
- 14 #pop
(x.s)
e c --> s e c
Drop the top element of S.
- 15 #prim_p
This code has 3 cases:
- p in [0..63] - starts the primitive code p
- p in [64..127] - starts Interrupt(p)
- p in [128..255] - executes TRApp(p-128)
2. Primitive codes - they change only the S register.
3. Special codes (user calls, interrupts, runtime error handling).
These codes changes all registers. They are used to swap control between
kernel and user processes.
- 44 #user0
(s' e' c'.s) e c sk ek ck u --> s' e' c' s e c nil
userMode:=true
This code invokes the user process (s' e' c') and is used by 3 argument
strict_LISP function (user0 S E C)
- 45 #user1
((s'.(e'.c')).s) e c sk ek ck u --> s' e' c' s e c nil
userMode:=true
CPCnt:=0
This code invokes the user process (s' e' c') and is used by 1 argument
strict_LISP function (user1 SEC)
- 46 #intr_i
(i s e.c) --> iSEC
s e c SK EK CK U --> (iSEC.SK) EK CK SK EK CK iSEC
userMode:=false
This code interrupts current user process, save it togheder with
int number i to structure iSEC. iSEC is saved in register U (note that
composite register K holds the full state of the kernel at the moment
of interrupt begining. This copy will be used if runtime error occures
during the interrupt handling). Kernel is loaded into S E C registers
and iSEC is passed to it.
- 47 #prev_i
s e c --> (S1/E1/C1 s) e c
Loads some of swaped registers.
Interrupts:
- Interrupts 0..63 are caused by some SEC machine commands:
- #intr_0 is started by code #fork
- #intr_1 is started when current user process reaches
the timesharing limit.
- #intr_2 is started when CleanX pre-code (started from
#rtn, #rec and #trrec) find working recipe at top of S.
- #intr_3 is started by #rtn when some lazy evaluation
is completed, but there is a queue of processes, waiting for
the result of this evaluation.
S = (q p s) where q is a queue, p is the primitive code
which must be executed after the recipe substitution.
- Interrupts 64..127 are started by #intr code and are treated as
soft_SEC primitives:
- #intr_64 implements the pure_LISP function (fopen fmod fn)
- #intr_65 - function (fclose f)
- #intr_66 - function (eof f)
- #intr_67 - function (connect IP port)
- #intr_68 - function (listen maxconn port)
- #intr_96 - function (send msg io)
- #intr_97 - function (recv io)
- #intr_98 - function (sendc i stream)
- #intr_99 - function (recvc stream)
- #intr_100 - function (fail n)
- Interrupts 128..255 are caused by runtime errors.
4. Kernel codes (used for multitasking, i/o, ipc, synchronization).
Kernel primitive codes:
Input/output codes:
- 64 #openf
(m fn s) e c --> (ps s) e c
open file fn with mode m (0-input 1-output), result ps is an integer,
representing a stream pointer to the file descriptor.
- 65 #putlex
(ps lex s) e c --> (0/1 s) e c
try to send lexeme lex to stream with descriptor ps. result is
0 if the try finished, 1 if operation is not finished.
- 66 #putc
(ps i s) e c --> (0/1 s) e c
same as putlex, but sends ascii code i to stream.
- 67 #settag
(i [x.y] a s) e c --> ([i|x.y] s) e c
changes subtag of the node [x.y].
- 68 #setcar
(b [x.y] a s) e c --> ([b.y] s) e c
changes first element (car) of the node [x.y].
- 69 #setcdr
(b [x.y] a s) e c --> ([x.b] s) e c
changes second element (cdr) of the node [x.y].
- 70 #opens
(m socket s) e c --> (ps s) e c
open stream, representing one direction of the TCP connection socket.
m is the direction (0-input 1-output), result ps is an integer,
representing a stream pointer.
- 71 #tcpconn
(P H s) e c --> (socket s) e c
Start TCP connection to host with IP address H on port P. Result
socket is an integer, representing pointer to Linux socket, traing to
establish the connection.
- 72 #tcplsn
(P M s) e c --> (socket s) e c
Start TCP listener on port P, allowing maximum M connections.
Result socket is an integer, representing pointer to Linux socket,
waiting for the client connections.
- 80 #getlex
(ps s) e c --> ((b.lex) s) e c
try to receive lexeme from stream with descriptor ps. b is 0
if the try finished, 1 if operation is not finished. lex is received
lexeme if the try finished.
- 81 #getc
(ps s) e c --> ((b.i) s) e c
same as getlex, but receives ascii code i.
- 82 #scont
(ps s) e c --> ((b.r) s) e c
continue get/put operation for stream ps. If operation finished,
b is 0 and r is the result, else b is 1.
- 83 #seof
(ps s) e c --> (eof(ps) s) e c
Tests stream ps for eof. If ps is a TCP connection test is for
closed/broken connection.
- 84 #close
(ps s) e c --> (ps_nil s) e c
Closes stream ps. Returns null stream pointer.
- 85 #pause
(n s) e c --> (n s) e c
pause SECK machine for n milliseconds. This code is used to leave
resources for other programs in Linux if there is no work for the kernel
and user processes.
- 86 #pintr
(n s) e c --> #intr_n(SECK)
This code is same as #intr_i, but gets its interrupt number
from S.
- 87 #gettag
([t|x.y] s) e c --> (t s) e c
Returns subtag of the node [x.y]. Possible subtags:
- 0 - node / pipe without msg handler
- 1 - recipe (thunk)
- 2 - pipe with msg handler
- 3 - stream
- 4 - TCP socket, waiting to be connected (connwait)
- 88 #getconn
(socket s) e c --> ((b.new_socket) s) e c
try to receive new connection from socket (created by #tcplsn or
#tcpconn).
b is 0 if the try finished, 1 if operation is not finished.
new_socket is a ready TCP connection if the try succeed.
(c) Skelet, Sep 2002 in terms of GNU GPL
Mail me at home or work