(;; SCCS : @(#)SKEL.LSP 3.12 2/3/86
;;; File : $xerox/skel.lsp
;;; Author : Richard A. O'Keefe
;;; Purpose: Basic interpreter support
PROGN
;;; ------------------------------------------------------------------------
;;
;; WARNING: This material is CONFIDENTIAL and proprietary to Quintus
;; Computer Systems Inc. This notice is protection against inadvertent
;; disclosure and does not constitute publication or reflect any intent
;; to publish.
;;
;; Copyright (C) 1985, by Quintus Computer Systems, Inc.
;; All rights reserved.
;;
;; CAVEAT LECTOR: This software is under development. No warrantee is
;; made that it is any use for anything at all.
;;
;;; ------------------------------------------------------------------------
;;; This instruction is
;;;
;;; +--------+--------------------------+
;;; | Trap | address of proc/2 |
;;; +--------+--------------------------+
;;;
;;; when it is executed, the C register (in the Sun/Vax emulators, PR)
;;; points to a procdeure record, which has the format
;;;
;;; +-----------------------------------+
;;;C+ 0 | link to next predicate in chain |
;;; +--------+--------+-----------------+
;;;C+ 2 |FuncTag | Arity | symbol number |
;;; +--------+--------+-----------------+
;;;C+ 4 |AtomTag | 0 | module number |
;;; +--------+--------+-----------------+
;;;C+ 6 | arity again as an unboxed FIXP |
;;; +--------+--------------------------+
;;;C+ 8 | | pointer to first clause |
;;; +--------+--------------------------+
;;;C+10 | | pointer to last clause |
;;; +--------+--------+-----------------+
;;;C+12 | flag word one | flag word two |
;;; +-----------------+-----------------+
;;;
;;; the symbol number is the InterLisp atom number of the name of the
;;; predicate; C+2 is a tagged functor cell. The module number is the
;;; InterLisp atom number of the name of the module this predicate is
;;; defined in; C+4 is a tagged atom cell. Some of these fields are
;;; not used in the Prolog-1108 implementation; the layout is identical
;;; to the layout used on the Sun and Vax so that the amount of system-
;;; dependent code is minimised. If we need a scaled version of the
;;; arity, say arity*2, C+6 is a good place to put it, and then we can
;;; use word access (C+7) rather than byte access (C+5bytes) to get at
;;; it.
;;;
;;; The point of the trap instruction is to convert a call on
;;; foo(X1,...,Xn) arity=n, symbol=foo, module=whatever
;;; into a call on
;;; proc(foo(X1,...,Xn), C')
;;; where C' is a coded form of the address of the procedure record.
;;; It is not sufficient to pass just the term foo(X1,...,Xn), as there
;;; could be any number of procedures with that name, all in different
;;; modules. On the Sun and Vax, C' is the address represented as an
;;; integer. That works well because only 29-bit integers are needed.
;;; It will not work well on the Dandelion, as large integers have to
;;; be boxed. We'll see later that addresses of this form have to be
;;; passed around a lot in the interpreter, so we'll want another way
;;; of hacking them. So we shall represent C' as the adress itself
;;; with a slightly different "immed" tag, one which is clearly an
;;; immediate constant but is not a number.
(def.macro QP.functor (C)
(if (NEQ C 'C) then (SHOULDNT QP.functor))
`(get.cell ,C 1)
)
(def.both.mode trap (address)
(put.16 I (PROC.ARITY (get.24 C)))
(if (zero I) then
(put.Aval 1 (QP.functor C))
else
;; dump the A registers into memory
(put.Amem 1 (get.Aval 1))
(put.Amem 2 (get.Aval 2))
(put.Amem 3 (get.Aval 3))
(put.Amem 4 (get.Aval 4))
(put.Aval 1 (tag.struct H))
(put.cell H 0 (QP.functor C))
(increment.cell.pointer H)
(put.24 S (loc.Amem 1))
(until (zero I)
(put.32 T0 (get.cell S 0))
(increment.cell.pointer S)
(select.4 T0
(ref.tag.8
(put.24 R (untag.ref T0))
(put.32 T0 (get.cell R 0))
(reselect.when.bound T0 R)
(if (before H R) then ; variable is local
(put.32 T0 (tag.ref H))
(bind.local R T0)
))
(PROGN)
)
(put.cell H 0 (get.32 T0))
(increment.cell.pointer H)
(decrement.counter I)
)
)
(put.Aval 2 (tag.other (get.24 C)))
(put.24 C (address.operand))
(do.execute)
)
(def.read.mode ignore.me (extend)
;; "call" port, execute backtrack instruction in write mode
(continue.writing 0)
)
(def.write.mode ignore.me (extend)
;; "redo" port, execute backtrack instruction in read mode
(continue.reading 0)
)
(def.read.mode ignore.me.but.keep.me (extend)
;; "call" port, execute backtrack instruction in write mode
(continue.writing 0)
)
(def.write.mode ignore.me.but.keep.me (extend)
;; "redo" port, execute backtrack instruction in read mode
(continue.reading 0)
)
(def.read.mode retry.at (extend address)
;; "redo" port; we've been backtracked into
(continue.writing.at (next.address.operand))
)
(def.write.mode retry.at (extend address)
;; "call" port; we've been "ignored" into
(continue.reading.at (next.address.operand))
)
(def.read.mode fail.on.retry (extend)
;; "fail" port; we've been backtracked into
(put.24 B (get.addr B saved.B))
(put.24 HB (get.addr B saved.H)) ; uses *new* B
(fast.fail)
)
(def.write.mode fail.on.retry (extend)
;; "fail" port; we've been skipped into (NO clauses)
(fast.fail)
)
;; This file is obliged to explicitly take into account the fact that
;; cells are pairs of half-words. I get up to a very sneaky trick:
;; the most significant half-word is effectively all tag (except for
;; constants), and the right half-word is a half-word offset. These
;; macros do addressy things for half-words. The conditions on them
;; are not necessary for them to be well behaved; they are there to
;; ensure that I haven't used them anywhere I didn't mean to.
(def.macro get.half (Base Offset)
(if (OR (NEQ Base 'S) (NOT (SMALLP Offset))) then
(SHOULDNT 'get.half))
`(\GETBASE (get.24 ,Base) ,Offset)
)
(def.macro put.half (Base Offset Val)
(if (NOT (AND (MEMB Base '(H S C)) (SMALLP Offset))) then
(SHOULDNT 'put.half))
`(\PUTBASE (get.24 ,Base) ,Offset ,Val)
)
(def.macro add.half (Base Offset)
(if (OR (NEQ Base 'C) (NEQ Offset 'N)) then
(SHOULDNT 'add.half))
`(\ADDBASE (get.24 ,Base) (get.16 ,Offset))
)
(def.read.mode interpret.me (extend)
(set.top.of.stack) ; sets R
(put.cell R 0 (get.Aval 1)) ; save A1 (save registers)
(put.addr R 1 (get.24 B0)) ; \
(put.addr R 2 (get.24 E )) ; \
(put.addr R 3 (get.24 CP)) ; \
(put.addr R 4 (get.24 B )) ; (create.choice.point)
(put.addr R 5 (get.24 P )) ; <--- except this is different
(put.addr R 6 (get.24 TR)) ;
(put.addr R 7 (get.24 H )) ;
(put.24 B (add.cell R 8)) ; /
(put.24 HB (get.24 H)) ; /
(check.stack B) ; /
(interpret.me)
)
(def.write.mode interpret.me (extend)
(put.Aval 1 (get.cell B -8)); (restore.registers)
(put.24 HB (get.24 H))
(put.addr B saved.BP (get.24 P))
(interpret.me)
)
;; (interpret.me)
;; is the common part of the read and write modes of the
;; (interpret.me) instruction. P points to the backtrack instruction
;; of an interpreted clause (so the clause's address is P-1). We are
;; to copy this term to the heap, and unify it with A1.
(def.open interpret.me ()
(put.16 I (IDIFFERENCE (get.code P 6) 4)) ; number of cells to copy
(check.heap) ; ensure room for term
(put.Aval 2 (tag.struct H)) ; always a compound term
(put.24 S (add.code P 7)) ; S->first cell of term
(until (zero I)
(put.16 N (get.half S 0)) ; the super-tag
(if (ILESSP (get.16 N) symbol.tag.16) then ; pointer
(\PUTBASEPTR (get.24 H) 0 ; bottom 24 bits
(\ADDBASE (get.24 HB) (get.half S 1)))
(\PUTBASEBYTE (get.24 H) 0 ; top 8 bits
(LRSH (get.16 N) 8))
else
(put.half H 0 (get.16 N))
(put.half H 1 (get.half S 1))
)
(increment.cell.pointer H)
(increment.cell.pointer S)
(decrement.counter I)
)
(put.24 CurClause (add.code P -1)) ; save clause address
(put.32 T0 (get.Aval 1))
(put.32 T1 (get.Aval 2))
(unify.and.continue 3) ; **HACK**
)
;; (len.term)
;; calculates the size of the term represented by A1, and returns
;; that size in register N. If the size would exceed 32767 cells,
;; including the list cell header, it aborts.
;; N is 32767 - the size of the CLAUSE in cells
;; H is the stack top (as in unify.one, compare)
;; S is the stack base (as in unify.one, compare)
;; T0, R are the current term, as usual
(def.open len.term () (PROG ()
(put.16 N 32763) ; 2↑15-4 (4=size of header)
(check.heap) ; check that H stack can't overflow
(put.24 S (get.24 H))
(put.word H 2 0)
(put.32 T0 (get.Aval 1)) ; term to find size of
L (select.4 T0
(ref.tag.8
(put.24 R (untag.ref T0))
(put.32 T0 (get.cell R 0))
(reselect.when.bound T0 R)
NIL)
(struct.tag.8
(put.24 R (untag.struct T0))
(put.32 T0 (get.cell R 0))
(put.16 I (arity.of T0))
(if (ILEQ (get.16 N) (get.16 I)) then
(put.24 H (get.24 S))
(QP.OVERFLOW 20))
(put.16 N (IDIFFERENCE (IDIFFERENCE (get.16 N) (get.16 I)) 1))
(put.16 I (SUB1 (get.16 I)))
(if (NOT (zero I)) then
(increment.cell.pointer H 3)
(put.addr H 0 (add.cell R 2))
(put.word H 2 (get.16 I))
)
(put.32 T0 (get.cell R 1))
(reselect))
(list.tag.8
(put.24 R (untag.list T0))
(if (ILEQ (get.16 N) 1) then
(put.24 H (get.24 S))
(QP.OVERFLOW 20))
(put.16 N (IDIFFERENCE (get.16 N) 2))
(increment.cell.pointer H 3)
(put.addr H 0 (add.cell R 1))
(put.word H 2 1)
(put.32 T0 (get.cell R 0))
(reselect))
(PROGN)
)
(put.16 I (get.word H 2))
(if (zero I) then
(put.16 N (IDIFFERENCE 32767 (get.16 N)))
(RETURN))
(put.24 R (get.addr H 0))
(decrement.counter I)
(if (zero I) then
(decrement.cell.pointer H 3) ; pop completed frame
else
(put.addr H 0 (add.cell R 1))
(put.word H 2 (get.16 I))
)
(put.32 T0 (get.cell R 0))
(GO L)
))
;; (put.term)
;; is called with the term to be stored in A1, the size of the term
;; in N, and the address of a block to store it in in C. It manages
;; a stack of (next thing to copy, next place to put a copy, count of
;; arguments still to be copied) triples like unify.one and compare.
;; S points to the cell that the current term is to be copied into,
;; while N counts the number of cells which have been filled in.
;; This code is only correct when the term to be stored is always a
;; "struct". Since the present Prolog system only stores :-(H,B)
;; and $record(T) terms, we are safe for now. A tagged pointer to
;; the first data cell of the clause will be left in the "next clause"
;; field, but that is about to be smashed anyway, so who cares?
(def.subr QP.HI.CODE (N)
(SETQ N (QP.OP.CODE N)) ; given 0<N<255
(if (LESSP N 256) then (LLSH N 8) ; return N*256
else N ; extended opcodeds
) ; are returned as is
)
(def.open put.term () (PROG ()
(put.half C 0 (CONSTANT (QP.HI.CODE 'interpret.me)))
(put.half C 1 (CONSTANT (QP.HI.CODE 'fail.on.retry)))
(put.half C 6 0) ; high size is 0, NOT TAGGED!
(put.half C 7 (get.16 N)) ; size of CLAUSE in cells
(put.24 S (add.cell C 1)) ; can safely be clobbered
(put.24 C (add.cell C 4)) ; where the functor cell is to go
(put.16 N 0) ; (halfword) offset for arguments
(put.32 T0 (get.Aval 1)) ; the term to be stored
L (select.4 T0
(ref.tag.8
(if (EQ (\GET.HI.16 T0) ref.tag.16) then
;; this variable has already been bound to an offset
(put.cell S 0 (get.32 T0))
else
(put.24 R (untag.ref T0))
(put.32 T0 (get.cell R 0))
(if (is.unbound T0 R) then
;; this variable is not yet bound
;; The arithmetic expression *does* yield 0..2↑16-1!
(put.24 B0 (\VAG2 0 (IPLUS (LLSH
(IDIFFERENCE (\GET.HI.16 S) (\GET.HI.16 C)) 16)
(IDIFFERENCE (\GET.LO.16 S) (\GET.LO.16 C)) )) )
(put.32 T0 (tag.ref B0))
(put.cell S 0 (get.32 T0))
(bind.trail R T0)
else
(reselect)
)))
(struct.tag.8
(put.24 R (untag.struct T0))
(put.32 T0 (get.cell R 0))
(put.16 I (SUB1 (arity.of T0)))
(put.half S 0 struct.tag.16)
(put.half S 1 (get.16 N))
(put.24 S (add.half C N))
(put.cell S 0 (get.32 T0))
(if (NOT (zero I)) then
(increment.cell.pointer H 3)
(put.addr H 0 (add.cell R 2))
(put.addr H 1 (add.cell S 2))
(put.word H 2 (get.16 I))
)
(put.32 T0 (get.cell R 1))
(increment.cell.pointer S)
(put.16 N (IPLUS (get.16 N) (Twice (get.16 I) 4) ))
(reselect))
(list.tag.8
(put.half S 0 list.tag.16)
(put.half S 1 (get.16 N))
(put.24 S (add.half C N))
(put.16 N (IPLUS (get.16 N) 4))
(put.24 R (untag.list T0))
(put.32 T0 (get.cell R 0))
(increment.cell.pointer H 3)
(put.addr H 0 (add.cell R 1))
(put.addr H 1 (add.cell S 1))
(put.word H 2 1)
(reselect))
(PROGN
(if (IGEQ (super.tag.of T0) other.tag.16) then
(\ADDREF (untag.immed T0)))
(put.cell S 0 (get.32 T0)))
)
(put.16 I (get.word H 2))
(if (zero I) then (RETURN))
(put.24 R (get.addr H 0))
(put.24 S (get.addr H 1))
(decrement.counter I)
(if (zero I) then
(decrement.cell.pointer H 3) ; pop completed frame
else
(put.addr H 0 (add.cell R 1))
(put.addr H 1 (add.cell S 1))
(put.word H 2 (get.16 I))
)
(put.32 T0 (get.cell R 0))
(GO L)
))
(def.read.mode store.skeleton (extend)
(put.24 CurClause 0) ; in case len.term fails
(len.term) ; puts length in N
(LET ((CurClause (\ALLOCBLOCK (get.16 N)) ))
(\ADDREF CurClause) ; nail it down!
(put.24 C CurClause)
(put.term)
(put.24 CurClause CurClause)
)
(fast.fail)
)
(def.both.mode load.cur.clause (extend)
(put.32 T1 (get.32 T0))
(put.32 T0 (tag.stack CurClause))
(continue 0)
)
;; This is a temporary tool for debugging, to make sure that clauses
;; are being stored correctly.
(def.subr QP.CLAUSE (CLAUSE)
(PROG (N I B TAG)
L (SETQ N (\GETBASE CLAUSE 0))
(SETQ I (if (EQ (LRSH N 8) 2) then
(ELT QP.opcode (IPLUS 256 (LOGAND N 255)))
else (ELT QP.opcode (LRSH N 8)) ))
(SETQ N (\GETBASE CLAUSE 1))
(SETQ B (if (EQ (LRSH N 8) 2) then
(ELT QP.opcode (IPLUS 256 (LOGAND N 255)))
else (ELT QP.opcode (LRSH N 8)) ))
(SETQ N (\GETBASE CLAUSE 7))
(PRINTOUT T
"Clause @ " CLAUSE
" [" I "; " B " " (\GETBASEPTR CLAUSE 2)
"; prev " (\GETBASEPTR CLAUSE 4)
"; size " N
"]" T)
(while (IGREATERP N 4)
(SETQ N (SUB1 N))
(SETQ TAG (\GETBASEBYTE CLAUSE (TIMES N 4)))
(PRINTOUT T
N
": (" TAG
") " (\GETBASEBYTE CLAUSE (ADD1 (TIMES N 4)))
"," (\GETBASE CLAUSE (ADD1 (TIMES N 2)) )
" "
(if (EQ TAG symbol.tag.8) then
(\VAG2 0 (\GETBASE CLAUSE (ADD1 (TIMES N 2)) ))
elseif (IGEQ TAG immed.tag.8) then
(\GETBASEPTR CLAUSE (TIMES N 2))
else
"+"
) T)
)
(if (EQ B 'R.retry.at) then
(SETQ CLAUSE (\GETBASEPTR CLAUSE 2))
(GO L)
)
)
)
;; When a clause is retracted, and we finally remove it from the
;; dead clause chain, this function is called to notify the Lisp
;; garbage collector that the references it used to make are not
;; there any more, and to say that this clause can be reclaimed.
(def.subr QP.FREE.CLAUSE (CLAUSE)
(PROG (I)
(if (NEQ (\GETBASE CLAUSE 6) 0) then
(RETURN (SHOULDNT "Clause freed twice"))
)
(\PUTBASE CLAUSE 6 55) ;; 55 is a random number
(SETQ I (LLSH (\GETBASE CLAUSE 7) 1))
;; I is now the size of the clause in words
(while (IGREATERP I 8) ;; not in header
(SETQ I (DIFFERENCE I 2))
(if (IGEQ (\GETBASE CLAUSE I) other.tag.16) then
(\DELREF (\GETBASEPTR CLAUSE I))
)
)
;; Now release the clause itself.
(\DELREF CLAUSE)
)
)
) STOP