language: infix-dylan module: dispatch-engine-internal Copyright: Original Code is Copyright (c) 1995-2004 Functional Objects, Inc. All rights reserved. License: Functional Objects Library Public License Version 1.0 Dual-license: GNU Lesser General Public License Warranty: Distributed WITHOUT WARRANTY OF ANY KIND define variable *call-site-caches-enabled?* = #t; define function call-site-caches-enabled? () => (res) *call-site-caches-enabled?* end function; define function call-site-caches-enabled?-setter (well?) *call-site-caches-enabled?* := well? end function; define inline function call-site-caches-possible? (ds :: ) => (well? :: ) *call-site-caches-enabled?* // & (~*missed-dispatch-in-progress?* // | *missed-dispatch-in-progress?* == %ds-gf(ds)) // & (*missed-dispatch-depth* < 2) end function; define variable *partial-dispatch?* = #t; define open generic partial-dispatch?-setter (value, x); define method partial-dispatch? (x :: ) => (well?) *partial-dispatch?* end method; define method partial-dispatch?-setter (value, x :: ) *partial-dispatch?* := value; end method; define inline function partial-dispatch-possible? (ds :: ) => (well? :: ) call-site-caches-possible?(ds) & *partial-dispatch?* end function; define variable *sharing-partial-dispatch-cache-headers?* = #f; define inline function sharing-partial-dispatch-cache-headers? () => (well?) *sharing-partial-dispatch-cache-headers?* end function; define inline function sharing-partial-dispatch-cache-headers?-setter (well?) *sharing-partial-dispatch-cache-headers?* := well?; end function; define variable *partial-dispatch-megamorphic-punt?* = #f; define inline function partial-dispatch-megamorphic-punt? () => (well?) *partial-dispatch-megamorphic-punt?* end function; define inline function partial-dispatch-megamorphic-punt?-setter (well?) *partial-dispatch-megamorphic-punt?* := well?; end function; //define sealed generic profiling-call-site-cache-header-engine-node-count-1 // (x :: ) => (v :: ); //define sealed generic profiling-call-site-cache-header-engine-node-count-2 // (x :: ) => (v :: ); //define sealed generic profiling-call-site-cache-header-engine-node-count-1-setter // (v :: , x :: ); //define sealed generic profiling-call-site-cache-header-engine-node-count-2-setter // (v :: , x :: ); define inline-only function stchen-checkedmask (stchen :: ) => (m :: ) %load-byte(stchen$v-checkedmask, stchen$s-checkedmask, properties(stchen)) end function; define function install-cache-header-engine-node-next (old :: , next :: type-union(, ), gf :: ) => () // Make sure all side effects to date are to date... synchronize-side-effects(); // Then store. cache-header-engine-node-next(old) := next; // primitive-enable-cache-header-engine-node(old, gf); end function; define constant $cache-header-engine-node-users-increment :: = 4; define inline-only function make-new-gf-cache-info-users (current-size :: ) => (v :: ) make(, size: current-size + $cache-header-engine-node-users-increment, fill: #f) end function; define function track-cache-header-engine-node (e :: , c :: ) => () let v :: = gf-cache-info-users(c); let n :: = size(v); local method loop (i :: ) if (i == n) let nv = %make-simple-vector(n + $cache-header-engine-node-users-increment, #f); for (i :: from 0 below n) vector-element(nv, i) := vector-element(v, i) end; vector-element(nv, n) := e; gf-cache-info-users(c) := nv; else let e2 = vector-element(v, i); if (e2 == #f) vector-element(v, i) := e; elseif (e2 ~== e) loop(i + 1); end if; end if; end method; loop(0) end function; define function compute-headed-methods-under-domain (ds :: , dom :: ) => () let headed-methods :: = pair(#f, #()); let ptr :: = headed-methods; let gf :: = %ds-gf(ds); let scu :: = $empty-subjunctive-class-universe; for (m :: in generic-function-methods(gf)) if (~domain-disjoint?(dom, m, scu, gf)) let nxt :: = pair(m, #()); tail(ptr) := nxt; ptr := nxt; end if; end for; %ds-headed-methods(ds) := headed-methods; let nreq :: = signature-number-required(function-signature(gf)); let v :: = %make-simple-vector(nreq, ); for (i from 0 below nreq) v[i] := domain-type(dom, i) end; %ds-argtypes(ds) := v; end function; define function compute-typecheckable-argument-mask (gf :: , headed-methods :: ) => (bitz :: ); if (~*call-site-caches-enabled?* | ~generic-function-sealed?(gf)) 0 else let meths :: = tail(headed-methods); let nreq :: = min(%gf-number-required(gf), $simple-typechecked-cache-arguments-limit); if (meths == #()) 0 else iterate loop (argnum :: = 0, bitz :: = 0) if (argnum == nreq) bitz else let m :: = head(meths); let next-l :: = tail(meths); let this-spec :: = %method-specializer(m, argnum); if (this-spec == ) loop(argnum + 1, bitz) else iterate scan (l :: = next-l) if (l == #()) loop(argnum + 1, logior(ash(1, argnum), bitz)) else let m :: = head(l); let next-l :: = tail(l); if (same-specializer?(this-spec, %method-specializer(m, argnum))) scan(next-l) else loop(argnum + 1, bitz) end if end if end iterate; end if end if end iterate end if end if end function; //// Cache Info Creation and Upgrading define function upgrade-gf-cache-info! (new :: , ds :: , users :: false-or()) => (); gf-cache-info-users(new) := users | make-new-gf-cache-info-users(0); %ds-cache(ds) := new; end function; define function upgrade-to-basic-gf-cache-info (old, ds :: ) => (cache-info :: ) case old == #f | old == 0 => let new :: = system-allocate-simple-instance(); upgrade-gf-cache-info!(new, ds, #f); %gf-cache(%ds-gf(ds)) := new; instance?(old, ) => upgrade-to-simple-typechecked-gf-cache-info(old, ds); instance?(old, ) => old; otherwise => error("Bogus gf cache info %=", old); end case end function; define function upgrade-simple-typechecked-gf-cache-info! (new :: , ds :: , argmask :: false-or(), entries :: false-or(), users :: false-or()) => (); upgrade-gf-cache-info!(new, ds, users); let m :: = argmask | compute-typecheckable-argument-mask(%ds-gf(ds), %ds-headed-methods(ds)); simple-typechecked-gf-cache-info-argmask(new) := m; simple-typechecked-gf-cache-info-entries(new) := entries | make(, size: 1 + compress-mask(m, m), // What a crappy way to do logcount. fill: #f); end function; define function upgrade-to-simple-typechecked-gf-cache-info (old, ds :: ) => (cache-info :: ) if (instance?(old, )) old else let new :: = system-allocate-simple-instance(); select (old by instance?) , singleton(#f) => upgrade-simple-typechecked-gf-cache-info!(new, ds, old, #f, #f); end select; %gf-cache(%ds-gf(ds)) := new end if end function; // define function upgrade-partial-dispatch-gf-cache-info! // (new :: , ds :: , // caches :: false-or(), // argmask :: false-or(), // entries :: false-or(), // users :: false-or()) // => (); // upgrade-simple-typechecked-gf-cache-info!(new, ds, argmask, entries, users); // partial-dispatch-gf-cache-info-caches(new) := caches | #(); // end function; // define function upgrade-to-partial-dispatch-gf-cache-info (old-cache-info, ds :: ) // => (cache-info :: ) // if (instance?(old-cache-info, )) // old-cache-info // else // let new :: // = system-allocate-simple-instance(); // select (old-cache-info by instance?) // => // let old :: = old-cache-info; // upgrade-partial-dispatch-gf-cache-info!(new, ds, #f, // simple-typechecked-gf-cache-info-argmask(old), // simple-typechecked-gf-cache-info-entries(old), // gf-cache-info-users(old)); // , singleton(#f) => // upgrade-partial-dispatch-gf-cache-info!(new, ds, #f, old-cache-info, #f, #f); // end select; // %gf-cache(%ds-gf(ds)) := new // end if // end function; define function compute-argument-precheck-mask (ds :: , cache) => (); let m :: = case cache == #f => // By default, do first the argument positions which have no varying specializers. let gf :: = %ds-gf(ds); %gf-cache(gf) := compute-typecheckable-argument-mask(gf, %ds-headed-methods(ds)); instance?(cache, ) => // We've computed it already - maybe at compile time. cache; instance?(cache, ) => let cache :: = cache; simple-typechecked-gf-cache-info-argmask(cache); otherwise => 0; end case; if (m ~== 0) %ds-args-to-check-first(ds) := list(m) end; end function; //// Call site cache define function handle-simple-call-site-cache-head-methods-computed (ds :: , cache, old :: ) => (root-engine); let cache :: = upgrade-to-basic-gf-cache-info(cache, ds); track-cache-header-engine-node(old, cache); let oengine = cache-header-engine-node-next(old) | $absent-engine-node; let nengine = walk-existing-dispatch-engine(ds, oengine, walk-existing-dispatch-engine); if (oengine ~== nengine) install-cache-header-engine-node-next(old, nengine, %ds-gf(ds)); subst-engine-node(nengine, oengine, ds); end if; nengine end function; define function handle-simple-call-site-cache-head (ds :: , cache, old :: ) => (root-engine); compute-headed-methods(ds); handle-simple-call-site-cache-head-methods-computed(ds, cache, old); end function; //// Profiling Header define function handle-profiling-call-site-cache-head (ds :: , cache, old :: ) => (root-engine); compute-headed-methods(ds); handle-simple-call-site-cache-head-methods-computed(ds, cache, old) end function; //// Partial Dispatch define function find-shareable-partial-dispatch-cache-header (old :: , cache :: ) => (res :: false-or()) if (sharing-partial-dispatch-cache-headers?()) block (return) for (user in gf-cache-info-users(cache)) if (instance?(user, )) let user :: = user; if (user == old) return(old) else block (punt) for (i :: from 0 below domain-number-required(user)) unless (same-specializer?(domain-type(user, i), domain-type(old, i))) punt(#f); end unless; end for; return(user); end block; end if; end if; end for; end block; else #f end if end function; define function handle-partial-dispatch-cache-head (ds :: , cache, old :: ) => (root-engine); let enabled? = partial-dispatch-possible?(ds); if (enabled?) compute-headed-methods-under-domain(ds, old); else compute-headed-methods(ds); end if; let cache :: = upgrade-to-basic-gf-cache-info(cache, ds); // let cache :: = upgrade-to-partial-dispatch-gf-cache-info(cache, ds); track-cache-header-engine-node(old, cache); let oengine = cache-header-engine-node-next(old) | $absent-engine-node; let other = oengine == $absent-engine-node & enabled? & find-shareable-partial-dispatch-cache-header(old, cache); if (other & other ~== old) install-cache-header-engine-node-next(old, other, %ds-gf(ds)); other else let nengine = walk-existing-dispatch-engine(ds, oengine, walk-existing-dispatch-engine); if (oengine ~== nengine) let current-next = cache-header-engine-node-next(old) | $absent-engine-node; if (current-next ~== oengine) // has a punt happened that replugged next? current-next else install-cache-header-engine-node-next(old, nengine, %ds-gf(ds)); subst-engine-node(nengine, oengine, ds); nengine end if else nengine end if; end if end function; //// Unknown handler define patchable-constant handle-unknown-cache-head (ds :: , cache, old :: ) => (); (ds, cache, old) begin dispwarn(make(, format-string: "Unhandled g.f. cache for %= - %=", format-arguments: vector(%ds-gf(ds), old)), ds); cache-header-punt(ds, cache, old) end end patchable-constant; define function find-or-create-common-cache-header (gf :: ) => (res :: ) let d = discriminator(gf); if (instance?(d, )) d else let new :: = bootstrap-typed-allocate-engine-node(, engine-node$k-cache-header, 0); primitive-initialize-engine-node(new); cache-header-engine-node-parent(new) := gf; install-cache-header-engine-node-next(new, d, gf); discriminator(gf) := new; new end if end function; define function cache-header-punt (ds :: , cache, e :: ) => () let cache :: = upgrade-to-basic-gf-cache-info(cache, ds); track-cache-header-engine-node(e, cache); let root = find-or-create-common-cache-header(%ds-gf(ds)); install-cache-header-engine-node-next(e, root, %ds-gf(ds)) end function; //define function make-simple-typechecked-gf-cache-info (m :: ) // => (c :: ); // let c :: // = system-allocate-simple-instance(); // gf-cache-info-users(c) := make(, // size: $cache-header-engine-node-users-increment, // fill: #f); // simple-typechecked-gf-cache-info-argmask(c) := m; // simple-typechecked-gf-cache-info-entries(c) // := make(, // size: 1 + compress-mask(m, m), // What a crappy way to do logcount. // fill: #f); // c //end function; define function handle-simple-typechecked-cache-head (ds :: , cache, old :: ) => (); compute-headed-methods(ds); let cache :: = upgrade-to-simple-typechecked-gf-cache-info(cache, ds); let checkedmask :: = stchen-checkedmask(old); let argmask :: = simple-typechecked-gf-cache-info-argmask(cache); let idx :: = compress-mask(argmask, checkedmask); let entries :: = simple-typechecked-gf-cache-info-entries(cache); let nentries :: = size(entries); if (idx + 1 == nentries) track-cache-header-engine-node(old, cache) end; let gf :: = %ds-gf(ds); install-cache-header-engine-node-next (old, element(entries, idx) | (begin if (~entries[nentries - 1]) ensure-engine-for-simple-typechecked-gf-cache(checkedmask, ds); if (~entries[nentries - 1]) error("gf caching is hosed") end; end; find-or-install-simple-typechecked-cache (idx, argmask, checkedmask, entries, head(tail(%ds-headed-methods(ds))), gf) end), gf) end function; define function find-or-install-simple-typechecked-cache (idx :: , argmask :: , checkedmask :: , cachev :: , proto :: , gf :: ) element(cachev, idx) | (element(cachev, idx) := if (argmask == checkedmask) error("Find-or-install-simple-typechecked-cache didn't find all-checked state!") else // Iterate over all the masks which have one more bit set than checkedmask, // in case there is already discrimination engine defined that we can share // with. If we find one, we use that as the target of a typecheck-discriminator. // If not, pick one, and recurse. let (target, argnum :: ) = begin local method loop (m :: , bitnum :: , firstargnum) if (m == 0) let firstargnum :: = firstargnum | error("I'm in trouble!"); let ncheckedmask = logior(ash(1, firstargnum), checkedmask); let nidx :: = compress-mask(argmask, ncheckedmask); values(find-or-install-simple-typechecked-cache (nidx, argmask, ncheckedmask, cachev, proto, gf), firstargnum) elseif (logbit?(0, m) & ~logbit?(bitnum, checkedmask)) let e? = element(cachev, compress-mask (argmask, logior(ash(1, bitnum), checkedmask))); if (e?) values(e?, bitnum) else loop(ash(m, -1), bitnum + 1, firstargnum | bitnum) end if else loop(ash(m, -1), bitnum + 1, firstargnum) end if end method; loop(argmask, 0, #f) end; make-typecheck-discriminator(argnum, gf, %method-specializer(proto, argnum), target) end if) end function; define function ensure-engine-for-simple-typechecked-gf-cache (checkedmask :: , ds :: ) let cache :: = %ds-cache(ds); let argmask :: = simple-typechecked-gf-cache-info-argmask(cache); let cachevec :: = simple-typechecked-gf-cache-info-entries(cache); let nentries :: = size(cachevec); %ds-args-to-check-first(ds) := if (argmask == checkedmask) list(argmask) else list(checkedmask, logand(argmask, lognot(checkedmask))) end if; compute-dispatch-from-root(ds, %ds-gf(ds)); local method lose (desc, datum) let gf :: = %ds-gf(ds); let default = discriminator(gf); for (i from 0 below nentries) if (~cachevec[i]) cachevec[i] := default end; end; dispwarn(make(, format-string: "For dispatch on %= with args %=, encountered %s %= unexpectedly.", format-arguments: vector(gf, reconstruct-args-from-mepargs(gf, %ds-args(ds)), desc, datum)), ds); end method; let masque-so-far :: = 0; local method walk (ds :: , x, callback :: ) let msf :: = masque-so-far; cachevec[compress-mask(argmask, msf)] := x; // The weird checking order here is to force us to keep iterating until we get // past multiple discriminators which check on the same argument. if (instance?(x, )) let x :: = x; let argnum :: = discriminator-argnum(x); if (argnum < $simple-typechecked-cache-arguments-limit & logbit?(argnum, argmask)) if (~logbit?(argnum, msf)) masque-so-far := logior(msf, ash(1, argnum)) end; walk-existing-dispatch-engine(ds, x, callback) elseif (msf ~== argmask) lose("discriminator on arg", argnum); x else x end if else if (msf ~== argmask) lose("non-discriminator", x) end; x end if end method; walk(ds, discriminator(%ds-gf(ds)), walk) end function; define constant $profile-count-low-slot-offset = 5; define constant $profile-count-high-slot-offset = 6; define inline-only function %profile-count-low (di :: ) => (low :: ) primitive-wrap-machine-word (primitive-cast-pointer-as-raw (primitive-initialized-slot-value(di, integer-as-raw($profile-count-low-slot-offset)))) end function %profile-count-low; define inline-only function %profile-count-low-setter (new-low :: , di :: ) primitive-slot-value(di, integer-as-raw($profile-count-low-slot-offset)) := primitive-cast-raw-as-pointer(primitive-unwrap-machine-word(new-low)); end function %profile-count-low-setter; define inline-only function %profile-count-high (di :: ) => (high :: ) primitive-wrap-machine-word (primitive-cast-pointer-as-raw (primitive-initialized-slot-value(di, integer-as-raw($profile-count-high-slot-offset)))) end function %profile-count-high; define inline-only function %profile-count-high-setter (new-high :: , di :: ) primitive-slot-value(di, integer-as-raw($profile-count-high-slot-offset)) := primitive-cast-raw-as-pointer(primitive-unwrap-machine-word(new-high)); end function %profile-count-high-setter; define variable *profile-all-terminal-engine-nodes?* = #f; define function profile-all-terminal-engine-nodes? () => (well?) *profile-all-terminal-engine-nodes?* end function; define function profile-all-terminal-engine-nodes?-setter (well?) *profile-all-terminal-engine-nodes?* := well? end function;