Hey all, I'm working on pulling dispatch of non-VM procedure applications into the VM -- so invoking primitives written in C can happen quickly from within the VM.
This is also a step along the path towards native compilation. Procedure dispatch right now has so many cases -- it is feasible to inline all of those cases into the VM, but once we generate native code, it would be ludicrous to do so at all call sites. One could trampoline out to an "apply" procedure, but that loses on tail recursion in a number of important cases. My approach is to start by listing all the different kinds of applicable objects, and how the VM would apply them. scm_tc7_program VM procedures - native support scm_tcs_closures Interpreted procedures - Currently calls out to the interpreter, but in the future we will only see these during the first phase of bootstrapping, when eval.c is compiling eval.scm. This one should probably be moved to be a SMOB, because there is no need for code outside of eval.c to know about closures, and closures will only be live during the boot process. scm_tc7_subr_2o SCM (*) () -- 0 arguments. scm_tc7_subr_1 SCM (*) (SCM) -- 1 argument. scm_tc7_subr_1o SCM (*) (SCM) -- 1 optional argument. scm_tc7_subr_2o SCM (*) (SCM, SCM) -- 2 required args. scm_tc7_subr_2o SCM (*) (SCM, SCM) -- 2 optional args. scm_tc7_subr_3 SCM (*) (SCM, SCM, SCM) -- 3 required args. scm_tc7_lsubr SCM (*) (SCM) -- list subrs All arguments are passed as a list. scm_tc7_lsubr_2 SCM (*) (SCM, SCM, SCM) A list subr with two required args. scm_tc7_dsubr double (*) (double) -- double subrs Pretty much a bad non-optimization, IMO. Converts its argument to a double, calls the function, then converts the result double to a SCM -- dispatching to a generic if the ->double conversion didn't work. scm_tc7_cxr c[da]+r Interprets the SUBR pointer as some kind of bit pattern for chasing car/cdr pairs. Only useful in the e.g. (map cddr ...) case, because otherwise cddr gets compiled better for the VM. And, in the future, map will do some inlining, so that even (map cddr ...) will compile nicely. scm_tc7_asubr SCM (*) (SCM, SCM) -- "accumulating" subrs. With 0 arguments, returns subr (SCM_UNDEFINED, SCM_UNDEFINED). With 1 argument, returns subr (arg1, SCM_UNDEFINED). Otherwise returns subr (subr (arg1, arg2), arg3), ... -- like the semantics of +. scm_tc7_rpsubr SCM (*) (SCM, SCM) -- predicate subrs. With 0 arguments, returns true. With 1 argument, returns subr (arg1, SCM_UNDEFINED). Otherwise, like an asubr, but returns SCM_BOOL_F if any subr invocation returns false. Otherwise returns SCM_BOOL_T. scm_tc7_smob Applicable smobs Some smobs are applicable. Actually there are a number of smob apply procedures that one might choose to implement: SCM (*apply) (); SCM (*apply_0) (SCM); SCM (*apply_1) (SCM, SCM); SCM (*apply_2) (SCM, SCM, SCM); SCM (*apply_3) (SCM, SCM, SCM, SCM); In the apply_3 case, the last argument is a rest arg list. scm_tc7_gsubr Generic subrs The is the base case in which you can have N required args, N optional args, and maybe rest args too. SCM_DEFINE produces gsubrs -- at least, it tries to. scm_c_define_gsubr actually returns e.g. scm_tc7_subr_1 for certain combinations of req, opt, and rest args; but we can change that. scm_tc7_pws Procedures with setters Gets the procedure, and applies that. This needs to be inlined into the VM to preserve tail recursion. scm_tcs_struct Applicable structs Currently, a struct needs one of two bits set for this to work: One case is if the procedure is a "pure generic", an instance of <generic> and not one of its subclasses, in which case we fetch or compute the effective method, and apply that. Actually it's worth mentioning here that for pure generics, the current GOOPS code returns an effective method that includes an #...@dispatch isym, to speed up dispatch in the interpreter. It actually mutates a cache inline to to the memoized effective method. Generics that are instances of subclasses of <generic> cannot currently be applied at all, due to limitations in Guile's implementation of the generics MOP. But now that we have compiled procedures, I think we can just require that the effective method of a generic be any kind of applicable object, but normally a closure, implementing the dispatch algorithm in Scheme. That should be sufficiently fast, and if we need VM ops for dispatch, well so be it. At least that way we move more to Scheme and handle tail recursion too. That would allow the generic function application MOP to be finished -- including advice, before/after/around methods, different dispatch algorithms, etc. The other case is if the struct is an "operator". I don't really understand what these are -- the comment in objects.h seems to suggest that operators were an optimization for applicable structs and the evaluator. But they do not appear to be used anywhere; I will be happy to remove them entirely. Pascal Constanza's R6RS enhancement request seems relevant: http://www.r6rs.org/formal-comments/comment-6.txt I think his applicable struct construct seems to be equivalent to GOOPS/objects.c's "entities". I prefer Constanza's name though. That's all the cases, afaict. I'll reply to my message with a plan of action. Andy -- http://wingolog.org/