-*- Mode: Text -*-

Taken from SSDN section 11.7, "Calling a FEF".



The categories of CALL insns are:

CALL-0 thru CALL-6:
  Call a function with the specified number of args, return one result.

CALL-N:
  Similar to CALL-0 et al but takes number of args from top of stack.

COMPLEX-CALL (AUX):
  Takes both function and a call-info word from stack.  Four forms for the
  different call destinations: COMPLEX-CALL-TO-INDS, COMPLEX-CALL-TO-PUSH,
  COMPLEX-CALL-TO-RETURN, COMPLEX-CALL-TO-TAIL-REC.

APPLY (AUX)
  AKA LEXPR-FUNCALL, takes function from the stack and lexpr-funcalls it with
  one argument, returning one result.  Four forms as with COMPLEX-CALL.

LEXPR-FUNCALL-WITH-MAPPING-TABLE (AUX):
  Takes function and a self-mapping-table from stack and lexpr-funcalls the
  function with one argument and self-mapping-table provided, returning one
  result.  Four forms as with COMPLEX-CALL.



Outline of calling path, generalized to all CALL insns.

* Find initial values:
  * new function
  * arg-ptr
  * call-info
  * number of args

* If self-mapping-table-provided bit in call-info is set,
  * get new self-mapping-table.

* Dispatch on DTP to appropriate handler.
  In E2 function is in MD (M-FEF?), arg-ptr is in M-G (M-ARGUMENT-POINTER),
  call-info is in M-F (M-CALL-INFO), and number of args is in M-H.

  "Most functional objects will eventually return here, having led to a
  compiled function (DTP-FUNCTION) object; this dispatch pushes its own
  address and falls through on DTP-FUNCTIONs."

  PF says:

    "Here" means to this step in the process, ie, about to calculate the LC
    offset.  "Pushes its own address" is a reference to the type of dispatch
    microinsn, basically meaning it's (mostly) a call instead of a branch.

  The address that is being pushed in this case is a micro-pc on the micro-PDL
  which is not visible to the macroinsn level.  Since the E3 doesn't have true
  atomic microinsns it doesn't have a micro-pc (or micro-PDL).  We can however
  simulate this with a magic 'micro-pc' value which can be looked up in a
  table or the like, and these values can be stored on a fake micro-PDL.

  Continuing:

    Lots of things were functional objects on the Explorer.  DTP-FUNCTION,
    obviously.  DTP-SYMBOL would fetch the function cell of the symbol and
    come back with a DTP-FUNCTION.  DTP-INSTANCE would do a method-table
    lookup and come back.  DTP-ARRAY would turn into an AREF and *not* come
    back (allowed for old historical reasons).  DTP-LIST would fake a call to
    SYS:APPLY-LAMBDA to jump into the interpreter, and come back with that
    DTP-FUNCTION (I think)..  DTP-LEXICAL-CLOSURE would pull the function out
    of the closure and come back.  And so on; see "Calling Anything Else".

    In other words, the simple common case, DTP-FUNCTION, falls through to the
    next step, while most other things jump out of the common path to go find
    a DTP-FUNCTION.

* Calculate location-counter offset from prev function.

  This goes into the call frame, one of the five state words, where it can be
  used to restart the LC in the caller when returning.

  In today's typical processor, a CALL instruction saves the current PC.  On
  the Explorer, the "current PC" is saved as the current function plus the
  offset within it, which facilitates moving functions around during GC.  The
  hardware LC register itself was a full address, the function/offset form was
  just for storage.

* Read header word of FEF.
  And do what?

* Check for PDL-buffer overflow.

  This only means the PDL-cache that is in-processor.  Overflowing in this
  sense means that some of the PDL-cache has to be written out to make space
  for more.  If there is an actual overflow of the PDL array itself the
  overflow would cause a trap where the debugger would offer an option to
  extend the array.  Either way this is essentially transparent to the CALL
  insn aside from the check since either the situatoin is cleaned up or the
  call is aborted.  

  This check will probably be unnecessary in the E3 ucode because we probably
  won't be implementing an 'in-processor' PDL-cache (because referencing the
  actual PDL-array in Explorer memory would be just as fast as referencing a
  PDL-cache which mirrors the PDL-array, and because mirroring the PDL-array
  in a PDL-cache would take up unnecessary cycles).

* If lexical-closure bit in call-info is set,
  * get environment object from stack and save it.

* If metering is enabled,
  * record function entry.

* If call-type is not %FEF-CALL-LONG,
  * if lexpr-funcall (ie, APPLY),
    * if part of lexpr-list matches &REST in function,
      * make the lexpr-list the &REST arg;
      * else spread the last arg if needed.

* If call-dest is D-RETURN,
  * copy return fields from previous function's call-info
* If call-dest is D-TAIL-REC (D-REALLY-TAIL-RECURSIVE)
  * copy return fields from previous function's call-info,
    copy all state from previous function,
    copy args over previous function's call frame.
* If call-dest is D-INDS (D-IGNORE) or D-PDL (D-STACK) then do nothing.

* If too few arguments,
  * push NILs for unsupplied optional arguments.

* If too many arguments,
  * trap.

  PF sez:

    Calls always know how many arguments are supplied, functions always know
    how many they want, and if there's a mismatch we trap and end up in the
    debugger (where we might do anything from aborting to adding args to
    dropping args to modifying args).

    Trapping invloves switching to the debugger stack group, saving the
    micro-pc so we can look it up in a list that gives us an atom that
    indicates the type of condition to signal.

* Set up stack-list for any &REST argument.
  All args are pushed with cdr-code CDR-NEXT, thus &REST argument handling
  must change the last argument to CDR-NIL.

* Advance insn stream.
  Don't understand "Call and return macroinstructions do not use the
  mechanisms of the main macroinstruction loop, but instead include those
  mechanisms in the code, so that the next step can be hidden under the memory
  reference here."  What is next step that is to be hidden?  Where is the
  memory reference?

* Push the five state words of the previous function:
  * old arg-ptr (DTP-FIX)
  * old local-ptr (DTP-FIX)
  * old location-counter offset (DTP-FIX)
  * previous function (CDR-ERROR)
  * old call-info (CDR-ERROR)

* Handle locals.
  * push NILs for locals
  * store any lexical-closure environment object into its reserved local
  * put any &REST arg in local 0
  * set up new local-ptr
  * leave number of supplied optionals calculated in step argument-handling on
    stack

  (If there are non-NIL defaults then first insn will probably be
  DISPATCH-type to use the number of supplied optionals to decide which to
  initialize.  We might later use this to make a heuristic optimization.)

* Start execution of function.
  * get first macroinsn and decode
  * run macroinsn...


Local Variables:
fill-column: 78
mode: auto-fill
End:
