Archive for January, 2008

Primitive procedures

Sunday, January 20th, 2008

With regular procedures marked with a proc prefix, we will mark primitive procedures (or primitives, for short) with a pproc prefix. The software state machine will now be supplied with the initial environment containing bindings of primitive names with their definitions, which is currently the name of the primitive. The initial environment is defined with the interaction-environment procedure.

interaction-environment =
  (lambda ()
    (quote(
      (car          pproc car)
      (cdr          pproc cdr)
      (cons         pproc cons)
      (eq?          pproc eq?)
      (read         pproc read)
      (eval         pproc eval)
      (display      pproc display)
      (readfile     pproc readfile)
      (writefile    pproc writefile)
      (byte         pproc byte)
      (set-car!     pproc set-car!)
      (set-cdr!     pproc set-cdr!)
      (exit         pproc exit)
    ))
  )

With this initial environment, the procedure can be queried for primitiveness in apply

  ((pproc? fn)  (apply-pproc (car(cdr fn)) args))

with an appropriate predicate

pproc? =
  (lambda (x)
    (eq? (car x) (quote pproc))
  )

The primitives can be moved out of the body of apply

apply-pproc =
  (lambda (fn args)
    (cond
      ((eq? fn (quote car))          (car (car args)))
      ((eq? fn (quote cdr))          (cdr (car args)))
      ((eq? fn (quote cons))         (cons (car args) (car(cdr args))))
      ((eq? fn (quote eq?))          (eq? (car args) (car(cdr args))))
      ((eq? fn (quote read))         (read))
      ((eq? fn (quote eval))         (eval (car args) (car(cdr args))))
      ((eq? fn (quote display))      (display (car args)))
      ((eq? fn (quote readfile))     (readfile (car args)))
      ((eq? fn (quote writefile))    (writefile (car args) (car(cdr args))))
      ((eq? fn (quote byte))         (byte (car args)))
      ((eq? fn (quote set-car!))     (set-car! (car args) (car(cdr args))))
      ((eq? fn (quote set-cdr!))     (set-cdr! (car args) (car(cdr args))))
      ((eq? fn (quote exit))         (exit))
      (#t                            (quote ()))
    )
  )

Primitives are implemented in some other program environment. The most basic (and therefore, most efficient) is the machine code environment. One universal interface would be to pass a procedure specifier and a list of evaluated arguments to a dispatch routine. So the last option in apply-pproc might be

      (#t                            (execute-machine-code fn args))

The first version of execute-machine-code is xeq-1f, which calls a primitive procedure using the Visual C++ fastcall convention. The dispatcher uses fn to select the primitive procedure to execute. A pointer to the value of args is passed to the machine language code in register ECX. Control is transferred to the primitive procedure with a CALL instruction, which pushes a return address onto the stack. No other items are pushed onto the stack. Control returns via a simple RET instruction, which pulls the return address off the top of the stack.

The remaining requirements of the interface are universal to the three main calling conventions: cdecl, stdcall, and fastcall. A pointer to the result is returned in EAX. The caller will not assume that ECX or EDX will retain their values. However, the primitive procedure (the callee) must retain the values of the EBP, EBX, ESI, and EDI registers, saving them and restoring them if necessary. Before calling, the direction flag, DF, will be in cleared condition for ascending (rather than Descending) addresses in string instructions. DF must be in cleared condition on return.

The first version of xeq-1f uses a byte atom as the value of fn. It indexes an address table, also known as the dispatch table. We can alter the primitive function versions with set!. For example, if the address of the car function is at position 1 in the dispatch table, and cdr function is at position 2, we can change their definitions…

(set! car
  (cons
    (quote pproc)
    (cons
      (byte (quote (#t #f #f #f  #f #f #f #f)))
      (quote ())
    )
  )
)

(set! cdr
  (cons
    (quote pproc)
    (cons
      (byte (quote (#f #t #f #f  #f #f #f #f)))
      (quote ())
    )
  )
)

Sequenced expressions

Monday, January 7th, 2008

The begin expression is used where a list of sequentially (left-to-right) executed expressions is not expected. It can be used anywhere a value expression is allowed.

The “stepping” procedure step-seq, used by begin, takes advantage of applicative order, which evaluates arguments fully before a function is applied to them.

step-seq =
  (lambda (val tail)
    (cond
      ((null? tail) val)
      (#t
        (step-seq
          (eval (car tail) env)
          (cdr tail)
        )
      )
    )
  )

In eval, the stepping function is called directly…

  (
    (eq? (car form) (quote begin))
    (step-seq
      (eval (car (cdr form)) env)
      (cdr (cdr form))
    )
  )