mirror of
https://gitlab.com/embeddable-common-lisp/ecl.git
synced 2026-01-18 15:22:03 -08:00
299 lines
9.2 KiB
C
299 lines
9.2 KiB
C
/* -*- mode: c; c-basic-offset: 8 -*- */
|
|
/*
|
|
gfun.c -- Dispatch for generic functions.
|
|
*/
|
|
/*
|
|
Copyright (c) 1990, Giuseppe Attardi.
|
|
|
|
ECL is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Library General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2 of the License, or (at your option) any later version.
|
|
|
|
See file '../Copyright' for full details.
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <ecl/ecl.h>
|
|
#include <ecl/ecl-inl.h>
|
|
#include <ecl/internal.h>
|
|
#include <ecl/cache.h>
|
|
|
|
static cl_object generic_function_dispatch_vararg(cl_narg, ...);
|
|
|
|
cl_object
|
|
FEnot_funcallable_fixed()
|
|
{
|
|
cl_env_ptr env = ecl_process_env();
|
|
cl_object fun = env->function;
|
|
FEerror("Not a funcallable instance ~A.", 1, fun);
|
|
@(return);
|
|
}
|
|
|
|
cl_object
|
|
FEnot_funcallable_vararg(cl_narg narg, ...)
|
|
{
|
|
return FEnot_funcallable_fixed();
|
|
}
|
|
|
|
static cl_object
|
|
user_function_dispatch(cl_narg narg, ...)
|
|
{
|
|
int i;
|
|
cl_object output;
|
|
cl_env_ptr env = ecl_process_env();
|
|
cl_object fun = env->function;
|
|
struct ecl_stack_frame frame_aux;
|
|
const cl_object frame = ecl_stack_frame_open(env, (cl_object)&frame_aux, narg);
|
|
ecl_va_list args; ecl_va_start(args, narg, narg, 0);
|
|
for (i = 0; i < narg; i++) {
|
|
ECL_STACK_FRAME_SET(frame, i, ecl_va_arg(args));
|
|
}
|
|
fun = fun->instance.slots[fun->instance.length - 1];
|
|
output = ecl_apply_from_stack_frame(frame, fun);
|
|
ecl_stack_frame_close(frame);
|
|
return output;
|
|
}
|
|
|
|
static void
|
|
reshape_instance(cl_object x, int delta)
|
|
{
|
|
cl_fixnum size = x->instance.length + delta;
|
|
cl_object aux = ecl_allocate_instance(ECL_CLASS_OF(x), size);
|
|
/* Except for the different size, this must match si_copy_instance */
|
|
aux->instance.sig = x->instance.sig;
|
|
memcpy(aux->instance.slots, x->instance.slots,
|
|
(delta < 0 ? aux->instance.length : x->instance.length) *
|
|
sizeof(cl_object));
|
|
x->instance = aux->instance;
|
|
}
|
|
|
|
cl_object
|
|
clos_set_funcallable_instance_function(cl_object x, cl_object function_or_t)
|
|
{
|
|
if (ecl_unlikely(!ECL_INSTANCEP(x)))
|
|
FEwrong_type_nth_arg(@[clos::set-funcallable-instance-function],
|
|
1, x, @[ext::instance]);
|
|
if (x->instance.isgf == ECL_USER_DISPATCH) {
|
|
reshape_instance(x, -1);
|
|
x->instance.isgf = ECL_NOT_FUNCALLABLE;
|
|
}
|
|
if (function_or_t == ECL_T) {
|
|
x->instance.isgf = ECL_STANDARD_DISPATCH;
|
|
x->instance.entry = generic_function_dispatch_vararg;
|
|
} else if (function_or_t == @'standard-generic-function') {
|
|
x->instance.isgf = ECL_RESTRICTED_DISPATCH;
|
|
x->instance.entry = generic_function_dispatch_vararg;
|
|
} else if (function_or_t == ECL_NIL) {
|
|
x->instance.isgf = ECL_NOT_FUNCALLABLE;
|
|
x->instance.entry = FEnot_funcallable_vararg;
|
|
} else if (function_or_t == @'clos::standard-reader-method') {
|
|
/* WARNING: We assume that f(a,...) behaves as f(a,b) */
|
|
x->instance.isgf = ECL_READER_DISPATCH;
|
|
x->instance.entry = (cl_objectfn)ecl_slot_reader_dispatch;
|
|
} else if (function_or_t == @'clos::standard-writer-method') {
|
|
/* WARNING: We assume that f(a,...) behaves as f(a,b) */
|
|
x->instance.isgf = ECL_WRITER_DISPATCH;
|
|
x->instance.entry = (cl_objectfn)ecl_slot_writer_dispatch;
|
|
} else if (Null(cl_functionp(function_or_t))) {
|
|
FEwrong_type_argument(@'function', function_or_t);
|
|
} else {
|
|
reshape_instance(x, +1);
|
|
x->instance.slots[x->instance.length - 1] = function_or_t;
|
|
x->instance.isgf = ECL_USER_DISPATCH;
|
|
x->instance.entry = user_function_dispatch;
|
|
}
|
|
@(return x)
|
|
}
|
|
|
|
cl_object
|
|
si_generic_function_p(cl_object x)
|
|
{
|
|
@(return ((ECL_INSTANCEP(x) && (x->instance.isgf))? ECL_T : ECL_NIL))
|
|
}
|
|
|
|
static cl_object
|
|
fill_spec_vector(cl_object vector, cl_object frame, cl_object gf)
|
|
{
|
|
cl_object *args = frame->frame.base;
|
|
cl_index narg = frame->frame.size;
|
|
cl_object spec_how_list = GFUN_SPEC(gf);
|
|
cl_object *argtype = vector->vector.self.t;
|
|
int spec_no = 1;
|
|
argtype[0] = gf;
|
|
loop_for_on_unsafe(spec_how_list) {
|
|
cl_object spec_how = ECL_CONS_CAR(spec_how_list);
|
|
cl_object spec_type = ECL_CONS_CAR(spec_how);
|
|
int spec_position = ecl_fixnum(ECL_CONS_CDR(spec_how));
|
|
unlikely_if (spec_position >= narg)
|
|
FEwrong_num_arguments(gf);
|
|
unlikely_if (spec_no >= vector->vector.dim)
|
|
ecl_internal_error("Too many arguments to fill_spec_vector()");
|
|
argtype[spec_no++] =
|
|
(!ECL_LISTP(spec_type) ||
|
|
Null(ecl_memql(args[spec_position], spec_type))) ?
|
|
cl_class_of(args[spec_position]) :
|
|
args[spec_position];
|
|
} end_loop_for_on_unsafe(spec_how_list);
|
|
vector->vector.fillp = spec_no;
|
|
return vector;
|
|
}
|
|
|
|
static cl_object
|
|
frame_to_list(cl_object frame)
|
|
{
|
|
cl_object arglist, *p;
|
|
for (p = frame->frame.base + frame->frame.size, arglist = ECL_NIL;
|
|
p != frame->frame.base; ) {
|
|
arglist = CONS(*(--p), arglist);
|
|
}
|
|
return arglist;
|
|
}
|
|
|
|
static cl_object
|
|
frame_to_classes(cl_object frame)
|
|
{
|
|
cl_object arglist, *p;
|
|
for (p = frame->frame.base + frame->frame.size, arglist = ECL_NIL;
|
|
p != frame->frame.base; ) {
|
|
arglist = CONS(cl_class_of(*(--p)), arglist);
|
|
}
|
|
return arglist;
|
|
}
|
|
|
|
static cl_object
|
|
generic_compute_applicable_method(cl_env_ptr env, cl_object frame, cl_object gf)
|
|
{
|
|
/* method not cached */
|
|
cl_object memoize;
|
|
cl_object methods = _ecl_funcall3(@'clos::compute-applicable-methods-using-classes',
|
|
gf, frame_to_classes(frame));
|
|
unlikely_if (Null(memoize = env->values[1])) {
|
|
cl_object arglist = frame_to_list(frame);
|
|
methods = _ecl_funcall3(@'compute-applicable-methods',
|
|
gf, arglist);
|
|
unlikely_if (methods == ECL_NIL) {
|
|
cl_object func = _ecl_funcall3(@'no-applicable-method',
|
|
gf, arglist);
|
|
frame->frame.base[0] = OBJNULL;
|
|
env->values[1] = ECL_NIL;
|
|
return func;
|
|
}
|
|
}
|
|
methods = clos_compute_effective_method_function(gf, GFUN_COMB(gf), methods);
|
|
env->values[1] = ECL_T;
|
|
return methods;
|
|
}
|
|
|
|
static cl_object
|
|
restricted_compute_applicable_method(cl_env_ptr env, cl_object frame, cl_object gf)
|
|
{
|
|
/* method not cached */
|
|
cl_object arglist = frame_to_list(frame);
|
|
cl_object methods = clos_std_compute_applicable_methods(gf, arglist);
|
|
unlikely_if (methods == ECL_NIL) {
|
|
cl_object func = _ecl_funcall3(@'no-applicable-method', gf, arglist);
|
|
frame->frame.base[0] = OBJNULL;
|
|
env->values[1] = ECL_NIL;
|
|
return func;
|
|
}
|
|
methods = clos_std_compute_effective_method(gf, GFUN_COMB(gf), methods);
|
|
env->values[1] = ECL_T;
|
|
return methods;
|
|
}
|
|
|
|
static cl_object
|
|
compute_applicable_method(cl_env_ptr env, cl_object frame, cl_object gf)
|
|
{
|
|
if (gf->instance.isgf == ECL_RESTRICTED_DISPATCH)
|
|
return restricted_compute_applicable_method(env, frame, gf);
|
|
else
|
|
return generic_compute_applicable_method(env, frame, gf);
|
|
}
|
|
|
|
cl_object
|
|
_ecl_standard_dispatch(cl_object frame, cl_object gf)
|
|
{
|
|
cl_object func, vector;
|
|
const cl_env_ptr env = frame->frame.env;
|
|
ecl_cache_ptr cache = env->method_cache;
|
|
ecl_cache_record_ptr e;
|
|
/*
|
|
* We have to copy the frame because it might be stored in cl_env.values
|
|
* which will be wiped out by the next function call. However this only
|
|
* happens when we cannot reuse the values in the C stack.
|
|
*/
|
|
#if !defined(ECL_USE_VARARG_AS_POINTER)
|
|
struct ecl_stack_frame frame_aux;
|
|
if (frame->frame.stack == (void*)0x1) {
|
|
const cl_object new_frame = (cl_object)&frame_aux;
|
|
ECL_STACK_FRAME_COPY(new_frame, frame);
|
|
frame = new_frame;
|
|
}
|
|
#endif
|
|
|
|
vector = fill_spec_vector(cache->keys, frame, gf);
|
|
e = ecl_search_cache(cache);
|
|
if (e->key != OBJNULL) {
|
|
func = e->value;
|
|
} else {
|
|
/* The keys and the cache may change while we
|
|
* compute the applicable methods. We must save
|
|
* the keys and recompute the cache location if
|
|
* it was filled. */
|
|
func = compute_applicable_method(env, frame, gf);
|
|
if (env->values[1] != ECL_NIL) {
|
|
cl_object keys = cl_copy_seq(vector);
|
|
if (e->key != OBJNULL) {
|
|
e = ecl_search_cache(cache);
|
|
}
|
|
e->key = keys;
|
|
e->value = func;
|
|
}
|
|
}
|
|
func = _ecl_funcall3(func, frame, ECL_NIL);
|
|
/* Only need to close the copy */
|
|
#if !defined(ECL_USE_VARARG_AS_POINTER)
|
|
if (frame == (cl_object)&frame_aux)
|
|
ecl_stack_frame_close(frame);
|
|
#endif
|
|
return func;
|
|
}
|
|
|
|
static cl_object
|
|
generic_function_dispatch_vararg(cl_narg narg, ...)
|
|
{
|
|
cl_object output;
|
|
ECL_STACK_FRAME_VARARGS_BEGIN(narg, narg, frame) {
|
|
output = _ecl_standard_dispatch(frame, frame->frame.env->function);
|
|
} ECL_STACK_FRAME_VARARGS_END(frame);
|
|
return output;
|
|
}
|
|
|
|
|
|
cl_object
|
|
si_clear_gfun_hash(cl_object what)
|
|
{
|
|
/*
|
|
* This function clears the generic function call hashes selectively.
|
|
* what = ECL_T means clear the hash completely
|
|
* what = generic function, means cleans only these entries
|
|
* If we work on a multithreaded environment, we simply enqueue these
|
|
* operations and wait for the destination thread to update its own hash.
|
|
*/
|
|
cl_env_ptr the_env = ecl_process_env();
|
|
#ifdef ECL_THREADS
|
|
cl_object list;
|
|
for (list = mp_all_processes(); !Null(list); list = ECL_CONS_CDR(list)) {
|
|
cl_object process = ECL_CONS_CAR(list);
|
|
struct cl_env_struct *env = process->process.env;
|
|
if (the_env != env) {
|
|
ecl_cache_remove_one(env->method_cache, what);
|
|
ecl_cache_remove_one(env->slot_cache, what);
|
|
}
|
|
}
|
|
#endif
|
|
ecl_cache_remove_one(the_env->method_cache, what);
|
|
ecl_cache_remove_one(the_env->slot_cache, what);
|
|
ecl_return0(the_env);
|
|
}
|