Module: dfmc-optimization Synopsis: Compile-time method selection Author: Keith Playford 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 // TODO: It's probably the right thing to do the detailed compatibility // checking even in loose mode and it's not going to be exploited. Take // care that the checking doesn't get switched off by the loose mode // switch. //// Debug switches. define thread variable *trace-dispatch* = #f; define thread variable *trace-dispatch-failure* = #f; // define thread variable *trace-selection-success* = #f; define thread variable *trace-selection-failure* = #f; define thread variable *trace-call-cache-success* = #f; define thread variable *trace-call-cache-failure* = #f; define thread variable *colorize-dispatch* = #t; define thread variable *warn-about-bogus-upgrades* = #f; define thread variable *colorize-bogus-upgrades* = #t; define constant = ; define macro format-when { format-when(?test:expression, ?args:*) } => { if (?test) format-out(?args) end } end macro; define macro note-when { note-when(?test:expression, ?args:*) } => { if (?test) note(?args) end } end macro; //// Typist interface. define constant = type-union(<&type>, ); /// /// guaranteed-joint? (, ) => (joint? :: ) /// /// Returns true only if all instances conforming to the type-estimate /// are guaranteed to be instances of the given Dylan type. /// /// Test case: /// /// define sealed abstract class () end; /// define open concrete class () end; /// define concrete class (, ) end; /// /// guaranteed-joint?(lookup-value(#""), lookup-value(#"")); should /// return #t. Note is trying especially hard to be uncooperative. /// can be open or sealed, as you wish. /// /// Given a generic function and type estimates for its required /// arguments, guaranteed-joint? is a conservative test used to determine /// whether the arguments will always lie within a sealed domain. If they /// do (or if the generic function itself is completely sealed) the set of /// potentially applicable methods can be determined using /// potentially-joint?. If there's just one potentially applicable method, /// and that method is always applicable (guaranteed-joint? again), we can /// do a simple compile-time dispatch to that lone method. /// define generic guaranteed-joint?(t1 :: , t2 :: ) => (res :: ); define method guaranteed-joint?(t1 :: <&type>, t2 :: ) => (res :: ) // Coerce first arg to a . type-estimate-subtype?(as(, t1), t2) end; define method guaranteed-joint?(t1 :: , t2 :: <&type>) => (res :: ) // Coerce second arg to a . type-estimate-subtype?(t1, as(, t2)) end; define method guaranteed-joint?(t1 :: <&type>, t2 :: <&type>) => (res :: ) // Coerce both args to s. type-estimate-subtype?(as(, t1), as(, t2)) end; define method guaranteed-joint?(t1 :: , t2 :: ) => (res :: ) // Both are s, so hand off. type-estimate-subtype?(t1, t2) end; define method guaranteed-joint?(t1 :: , t2 :: <&type>) => (res :: ) // Singleton type estimates can be decided quickly, w/o typist overhead. ^instance?(type-estimate-singleton(t1), t2) end; // Disambiguating methods define method guaranteed-joint?(t1 :: , t2 :: <&top-type>) => (res :: singleton(#t)) #t end; define method guaranteed-joint?(t1 :: , t2 :: <&bottom-type>) => (res :: singleton(#f)) #f end; define method guaranteed-joint?(t1 :: <&singleton>, t2 :: <&type>) => (res :: ) // Singleton model types can be decided quickly, w/o typist overhead. ^instance?(^singleton-object(t1), t2) end; // Disambiguating methods define method guaranteed-joint?(t1 :: <&singleton>, t2 :: <&top-type>) => (res :: singleton(#t)) #t end; define method guaranteed-joint?(t1 :: <&singleton>, t2 :: <&bottom-type>) => (res :: singleton(#f)) #f end; define method guaranteed-joint?(t1 :: , t2 :: <&top-type>) => (res :: singleton(#t)) // Top types can be quickly decided, w/o typist overhead. #t end; define method guaranteed-joint?(t1 :: <&type>, t2 :: <&top-type>) => (res :: singleton(#t)) // Top types can be quickly decided, w/o typist overhead. #t end; define method guaranteed-joint?(t1 :: , t2 :: <&bottom-type>) => (res :: singleton(#f)) // Bottom can be quickly decided, w/o typist overhead. #f end; define method guaranteed-joint?(t1 :: <&type>, t2 :: <&bottom-type>) => (res :: singleton(#f)) // Bottom can be quickly decided, w/o typist overhead. #f end; define method guaranteed-joint? (t1 :: , t2 :: <&bottom-type>) => (res :: singleton(#t)) // But bottom is joint with itself. // Believe it or not, this comes up in checking the "return" type of error! #t end; define method guaranteed-joint? (t1 :: <&bottom-type>, t2 :: ) => (res :: singleton(#t)) // But bottom is joint with itself. // Believe it or not, this comes up in checking the "return" type of error! #t end; define method guaranteed-joint? (t1 :: , t2 :: ) => (res :: singleton(#t)) // But bottom is joint with itself. // Believe it or not, this comes up in checking the "return" type of error! #t end; define method guaranteed-joint? (t1 :: <&bottom-type>, t2 :: <&bottom-type>) => (res :: singleton(#t)) // But bottom is joint with itself. // Believe it or not, this comes up in checking the "return" type of error! #t end; /// /// guaranteed-disjoint?(t1, t2) -- #t if any instance of t1 is guaranteed /// never to be an instance of t2. NB: If t1 is a subtype of t2, then EVERY /// instance of t1 is an instance of t2. If t2 is a subtype of t1, then SOME /// (indirect) instances of t1 are (direct) instances of t2. So t1 & t2 /// must be incomparable in order to be disjoint. /// /// Disjointness is a symmetric, non-transitive, anti-reflexive relation. /// /// DRM p.49 gives disjointness rules. /// define generic guaranteed-disjoint?(t1 :: , t2 :: ) => (res :: ); define method guaranteed-disjoint?(t1 :: <&type>, t2 :: ) => (res :: ) // Coerce first arg to a . type-estimate-disjoint?(as(, t1), t2) end; define method guaranteed-disjoint?(t1 :: , t2 :: <&type>) => (res :: ) // Coerce second arg to a . type-estimate-disjoint?(t1, as(, t2)) end; define method guaranteed-disjoint?(t1 :: <&type>, t2 :: <&type>) => (res :: ) // Coerce both args to a . type-estimate-disjoint?(as(, t1), as(, t2)) end; define method guaranteed-disjoint?(t1 :: , t2 :: ) => (res :: ) // Both are s, so hand off type-estimate-disjoint?(t1, t2) end; /// /// These are attempts to short-circuit the typist in frequent but quick cases. /// Metering indicates that the typist overhead was excessive for these simple, /// frequently-occurring cases. /// define method guaranteed-disjoint?(t1 :: , t2 :: <&type>) => (res :: ) // Singleton type estimates can be quickly decided here. ~^instance?(type-estimate-singleton(t1), t2) end; define method guaranteed-disjoint?(t1 :: <&type>, t2 :: ) => (res :: ) // Singleton type estimates can be quickly decided here. ~^instance?(type-estimate-singleton(t2), t1) end; define method guaranteed-disjoint?(t1 :: <&singleton>, t2 :: <&type>) => (res :: ) // Singleton model types can be quickly decided here. ~^instance?(^singleton-object(t1), t2) end; define method guaranteed-disjoint?(t1 :: <&type>, t2 :: <&singleton>) => (res :: ) // Singleton model types can be quickly decided here. ~^instance?(^singleton-object(t2), t1) end; define method guaranteed-disjoint?(c1 :: <&class>, c2 :: <&class>) => (res :: ) // Going through the typist would involve wrapping & then unwrapping the // , so just apply directly to the eventual oracle. ^classes-guaranteed-disjoint?(c1, c2) end; define method guaranteed-disjoint?(c1 :: <&class>, c2 :: ) => (res :: ) // Going through the typist would involve wrapping & then unwrapping the // , so just apply directly to the eventual oracle. ^classes-guaranteed-disjoint?(c1, type-estimate-class(c2)) end; define method guaranteed-disjoint?(c1 :: , c2 :: <&class>) => (res :: ) // Going through the typist would involve wrapping & then unwrapping the // , so just apply directly to the eventual oracle. ^classes-guaranteed-disjoint?(type-estimate-class(c1), c2) end; define method guaranteed-disjoint?(c1 :: <&class>, c2 :: ) => (res :: ) // Going through the typist would involve wrapping & then unwrapping the // , so just apply directly to the eventual oracle. ^classes-guaranteed-disjoint? (c1, type-estimate-concrete-class(c2) | type-estimate-class(c2)) end; define method guaranteed-disjoint?(c1 :: , c2 :: <&class>) => (res :: ) // Going through the typist would involve wrapping & then unwrapping the // , so just apply directly to the eventual oracle. ^classes-guaranteed-disjoint? (type-estimate-concrete-class(c1) | type-estimate-class(c1), c2) end; define method guaranteed-disjoint?(c1 :: , c2 :: <&class>) => (res :: ) // Going through the typist would involve wrapping & then unwrapping the // , so just apply directly to the eventual oracle. ~^instance?(type-estimate-singleton(c1), c2) end; define method guaranteed-disjoint?(c1 :: , c2 :: ) => (res :: ) // Going through the typist would involve wrapping & then unwrapping the // , so just apply directly to the eventual oracle. ^classes-guaranteed-disjoint?(type-estimate-class(c1),type-estimate-class(c2)) end; define method guaranteed-disjoint?(c1 :: , c2 :: ) => (res :: ) // Going through the typist would involve wrapping & then unwrapping the // , so just apply directly to the eventual oracle. type-estimate-disjoint?(c1, c2) end; // I just happen to know that will only occur on the right define method guaranteed-disjoint?(c1 :: , c2 :: <&top-type>) => (res :: singleton(#f)) #f end; define method guaranteed-disjoint?(c1 :: <&type>, c2 :: <&top-type>) => (res :: singleton(#f)) #f end; define method guaranteed-disjoint?(t1 :: , t2 :: <&bottom-type>) => (res :: singleton(#t)) #t end; define method guaranteed-disjoint?(t1 :: <&type>, t2 :: <&bottom-type>) => (res :: singleton(#t)) #t end; /// /// potentially-joint? (, <&type>) => () /// /// Returns true unless all instances conforming to the type-estimate /// are guaranteed not to be instances of the given Dylan type. /// define function potentially-joint? (type-estimate :: , type :: <&type>) => (res :: ) // Not provably DISjoint. ~guaranteed-disjoint?(type-estimate, type) end; /// /// effectively-disjoint?(t1, t2) -- #t if any instance of t1 is unlikely /// to be an instance of t2. This function is only used for warnings /// generation. No optimizations may be made based on this notion of /// disjointness. /// // The intuition we apply is that #f is often used as a kind of "null" // value for the types it appears with in a type-union. Hence, whenever // we see #f in a type-union, we remove it and compute disjointess with // the remainder of the type-union only. define method null-type? (t :: ) => (well? :: ) instance?(t, ) & type-estimate-singleton(t) == #f end method; define method denull-type-estimate (t :: ) => (t :: ) t end method; define method denull-type-estimate (t :: ) => (t :: ) if (type-estimate-singleton(t) == #f) make() else t end; end method; define method maybe-type-estimate-union (unionees :: ) => (t :: ) if (empty?(unionees.tail)) unionees.head else make(, unionees: unionees) end end method; define method denull-type-estimate (t :: ) => (t :: ) let unionees = type-estimate-unionees(t); if (any?(null-type?, unionees)) local method non-null? (t :: ) => (well? :: ) ~null-type?(t) end method; let new-unionees = choose(non-null?, unionees); maybe-type-estimate-union(new-unionees); else t end; end method; define method union-of-values? (t :: ) => (well? :: ) instance?(t, ) & every?(rcurry(instance?, ), type-estimate-unionees(t)) end method; define method flatten-union-of-values (t :: ) => (t :: ) t end method; define method flatten-union-of-values (union :: ) => (vals :: ) let unionees = type-estimate-unionees(union); let vals1 = unionees.first; let n-fixed = size(type-estimate-fixed-values(vals1)); let rest? = type-estimate-rest-values(vals1); let fixed-unionees-sequence = make(, size: n-fixed, fill: #()); let rest-unionees = #(); for (vals :: in unionees) for (i from 0, fixed-i :: in type-estimate-fixed-values(vals)) fixed-unionees-sequence[i] := add-new!(fixed-unionees-sequence[i], fixed-i); end; if (rest?) rest-unionees := add-new!(rest-unionees, type-estimate-rest-values(vals)); end; end; let result = make(, fixed: map(method (unionees :: ) maybe-type-estimate-union(unionees) end, fixed-unionees-sequence), rest: rest? & maybe-type-estimate-union(rest-unionees)); result end method; define method effectively-disjoint? (t1 :: , t2 :: ) => (well? :: ) if (null-type?(t1) | null-type?(t2)) // A bare null in a comparison stands or falls on its own merit. guaranteed-disjoint?(t1, t2) elseif (union-of-values?(t1) | union-of-values?(t2)) effectively-disjoint? (flatten-union-of-values(t1), flatten-union-of-values(t2)); else // When neither is just null on its own, we apply the denulling // heuristic. let denulled-t1 = denull-type-estimate(t1); let denulled-t2 = denull-type-estimate(t2); guaranteed-disjoint?(denulled-t1, denulled-t2) end; end method; define method effectively-disjoint? (t1 :: , t2 :: ) => (well? :: ) // Whether these multiple value types are guaranteed disjoint: // t1 disj t2 iff NO instance of one can be an instance of the other, ie., // ~ exists x: x :: t1 & x :: t2. local method arity (val :: ) => (fixed :: , rest? :: ) // Fixed arity (exactly n values): (n, #f) // Infinite arity (n or more values): (n, #t) values(size(type-estimate-fixed-values(val)), type-estimate-rest-values(val) ~== #f) end, method vref (val :: , i :: ) => (value :: , rest? :: ) // Get the ith value type, using #rest value if necessary case i < size(type-estimate-fixed-values(val)) // Wants a positional value => values(type-estimate-fixed-values(val)[i], #f); type-estimate-rest-values(val) // Out of fixed vals, but can give a rest val => values(type-estimate-rest-values(val), #t); otherwise // No #rest val, so i is out of range for fixed vals. => error("%d out of range for multiple values %s", i, val); end end, method disjoint-by-type? () => (disjoint? :: ) // OK, arities overlap: are any pair guaranteed type-disjoint? let (nfixed1, nrest1?) = arity(t1); let (nfixed2, nrest2?) = arity(t2); block (xit) for (i from 0) when ((i >= nfixed1 & ~nrest1?) | (i >= nfixed2 & ~nrest2?)) // Ran off the end of one of one w/o #rest, so fail. // If either arg has no #rest, this is the failure exit. xit(#f, #t) end; let (t1i, rest1?) = vref(t1, i); // Next value from t1 let (t2i, rest2?) = vref(t2, i); // Next value from t2 case // Provably disjoint at ith value position? effectively-disjoint?(t1i, t2i) => xit(#t, #t); // Into #rest of both? No point in looking further. rest1? & rest2? => xit(#f, #t); // Go try some more. otherwise => ; end end end end, method disjoint-by-arity? () => (disjoint? :: ) // See if arities could not possibly overlap. Cheap first test. let (fixed1, rest1) = arity(t1); let (fixed2, rest2) = arity(t2); // Obviously, if both are infinite, they overlap. 3 more cases: (~rest1 & ~rest2 & fixed1 ~= fixed2) // Both finite & different | (~rest1 & rest2 & fixed1 < fixed2) // 1 finite, below low of 2 | ( rest1 & ~rest2 & fixed1 > fixed2) // 2 finite, below low of 1 end; values(disjoint-by-arity?() | disjoint-by-type?(), #t) end; define method effectively-disjoint? (t1 :: <&type>, t2 :: <&type>) => (well? :: ) effectively-disjoint?(as(, t1), as(, t2)) end method; define method effectively-disjoint? (t1 :: , t2 :: <&type>) => (well? :: ) effectively-disjoint?(t1, as(, t2)) end method; define method effectively-disjoint? (t1 :: <&type>, t2 :: ) => (well? :: ) effectively-disjoint?(as(, t1), t2) end method; /// /// guaranteed-preceding-specializer? (type1, type2, type-estimate) => (boolean) /// /// Type1 and type2 are Dylan language types that have appeared as /// method specializers. Type-estimate is the inferred type of an /// argument to a generic function. The predicate returns #t if type1 /// is more specific than type2 for all instances conforming to /// type-estimate (according to the rules on page 94 of the September /// '95 DRM). /// /// So, for example, in: /// /// define class () end; /// define class () end; /// define class () end; /// define class (, ) end; /// /// define method nurgle (o :: ) end; /// define method nurgle (o :: ) end; /// define method nurgle (o :: ) end; /// define method nurgle (o :: ) end; /// /// given a class estimate of for an argument to nurgle, I should be /// able to determine the complete sorted applicable method set, using the /// above predicate to order the methods on and . And similarly if /// the type estimate for the argument is more complex, such as an /// intersection type estimate involving . Let's hear it for /// monotonicity! /// define generic guaranteed-preceding-specializer?(t1 :: , t2 :: , arg :: ) => (always-precedes? :: ); // *** Stub for later improvement. define method guaranteed-preceding-specializer? (type1 :: <&type>, type2 :: <&type>, arg-te :: ) => (res :: ) ^subtype?(type1, type2) end; define generic slot-fixed-offset-in (sd :: <&slot-descriptor>, te :: ) // Is this slot at fixed offset, when referenced from something of type te? // When this is called, we know that the slot accessor method is // applicable, so we only have to deal with type estimates that // apply to objects which could be of classes which inherit the slot. => (offset :: false-or()); define method slot-fixed-offset-in (sd :: <&slot-descriptor>, te :: ) => (offset :: false-or()) // Unwrapping trampoline let class = type-estimate-class(te); slot-offset-fixed-in-class?(sd, class) & ^slot-fixed-offset(sd, class) end; define method slot-fixed-offset-in (sd :: <&slot-descriptor>, te :: ) => (offset :: false-or()) let class = type-estimate-concrete-class(te); slot-fixed-offset-in(sd, as(, class)); end; define method slot-fixed-offset-in (sd :: <&slot-descriptor>, te :: ) => (offset :: false-or()) // Unwrapping trampoline let offset = #f; // do all unionees have the same offset? // TODO: this would be a lot simpler if unionees were a // or if "for" weren't so limited // or if intermediate results of sequence functions were // optimized away: reduce(.., map(.., )) block (return) for (te in type-estimate-unionees(te)) let te-offset = slot-fixed-offset-in(sd, te); if (te-offset) if (offset) unless (offset = te-offset) return(#f); end; else offset := te-offset; end; else return(#f); end; end; offset end end; define generic slot-guaranteed-initialized-in? (sd :: <&slot-descriptor>, te :: ) => (initialized? :: ); define method slot-guaranteed-initialized-in? (sd :: <&slot-descriptor>, te :: ) => (initialized? :: ) // Unwrapping trampoline slot-guaranteed-initialized-in-class?(sd, type-estimate-class(te)) end; define method slot-guaranteed-initialized-in? (sd :: <&slot-descriptor>, te :: ) => (initialized? :: ) // Unwrapping trampoline every?(curry(slot-guaranteed-initialized-in?, sd), type-estimate-unionees(te)) end; //// Extended entry point analysis. // At this point we know that the call is interface-compatible with the // generic function since this has to be true for upgrading to be // attempted. // If we can prove this generic call will have a known applicable method // chain of size one (or more some day), upgrade the call to a call to // that method. define variable *profile-all-calls?* = #f; define variable *partial-dispatch?* = #f; define inline function call-site-caches-ok? (c :: , f :: <&generic-function>) => (well? :: ) let f = lambda(environment(c)); if (lambda-initializer?(f)) #f // elseif (compiling-dylan-library?()) // #f else #t end if; end function; define method maybe-upgrade-call (c :: , f :: <&generic-function>) => (res :: ) // The dispatch state prevents analysis being done again on a second // pass over the code, either due to feedback or to the call // computation being copied by inlining. // TODO: If things become more precise after inlining, allow dispatch // to be selectively redone by recording the previous inputs? Except // that CPA won't work like this anyway. if (dispatch-state(c) == $dispatch-untried) let arg-te* :: = argument-type-estimates(c); // effectives is non-empty only if all methods are known: let effectives :: = estimate-effective-methods(f, arg-te*, c); dispatch-state(c) := $dispatch-tried; // Try method upgrading (~empty?(effectives) & maybe-upgrade-gf-to-method-call(c, f, arg-te*, effectives)) | (call-site-caches-ok?(c, f) & case *partial-dispatch?* => maybe-upgrade-gf-to-partial-dispatch(c, f, arg-te*); *profile-all-calls?* => upgrade-gf-to-profiling-call-site-cache(c, f, arg-te*); otherwise => maybe-upgrade-gf-to-call-site-cache(c, f, arg-te*); end case) | next-method() else next-method(); end; end; //define method maybe-upgrade-call // (c :: , f :: <&generic-function>) => (res :: ) // // The dispatch state prevents analysis being done again on a second // // pass over the code, either due to feedback or to the call // // computation being copied by inlining. // // TODO: If things become more precise after inlining, allow dispatch // // to be selectively redone by recording the previous inputs? Except // // that CPA won't work like this anyway. // if (dispatch-state(c) == $dispatch-untried) // let arg-te* :: = argument-type-estimates(c); // let effectives :: = estimate-effective-methods(f, arg-te*, c); // dispatch-state(c) := $dispatch-tried; // // Try method upgrading // if (*profile-all-calls?*) // upgrade-gf-to-profiling-call-site-cache(c, f, arg-te*) // else // (~empty?(effectives) & maybe-upgrade-gf-to-method-call(c, f, arg-te*, effectives)) // | // if (*partial-dispatch?*) // maybe-upgrade-gf-to-partial-dispatch(c, f, arg-te*) // else // maybe-upgrade-gf-to-call-site-cache(c, f, arg-te*) // end // | // next-method() // end if // else // next-method(); // end; //end; define function maybe-upgrade-gf-to-method-call (c :: , f :: <&generic-function>, arg-te* :: , effectives :: ) => (res :: ) // if we've got this far, all methods are known (== effectives) let effective = head(effectives); if (instance?(effective, <&accessor-method>)) let slot-descriptor = get-method-slot-descriptor(effective); if (instance?(slot-descriptor, <&any-instance-slot-descriptor>) & size(effectives) = 1) // TODO: HANDLE NEXT-METHOD // gsb: no, there is nothing to handle with next-method here. let arg-te = arg-te*[accessor-method-dispatch-arg(effective)]; let offset = slot-fixed-offset-in(slot-descriptor, arg-te); if (offset) if (*colorize-dispatch*) color-dispatch(c, #"slot-accessor-fixed-offset") end; incf-static-dispatch-count(); upgrade-call-to-slot-accessor(c, effective, slot-descriptor, offset, arg-te); #t else // the g.f. dispatcher does a better job than direct-calling // the accessor method, so leave it as a g.f. call if (*colorize-dispatch*) color-dispatch(c, #"lambda-call") end; #f end else // if 1st of several effective methods is a slot access, // neither color nor upgrade #f end elseif (any?(rcurry(instance?, <&accessor-method>), tail(effectives))) #f // ditto if any effectives are a slot access else // and if no effectives are slot accesses, upgrade to method call let method-call = upgrade-to-method-call! (c, effective, tail(effectives), ); re-optimize(method-call); // Is this the right place to do this? maybe-upgrade-call(method-call, effective); #t end end function; /* * define function maybe-upgrade-gf-to-method-call * (c :: , f :: <&generic-function>, * arg-te* :: , effectives :: ) * => (res :: ) * let effective = head(effectives); * if (instance?(effective, <&accessor-method>)) * let slot-descriptor = get-method-slot-descriptor(effective); * if (instance?(slot-descriptor, <&any-instance-slot-descriptor>) * & size(effectives) = 1) // TODO: HANDLE NEXT-METHOD * // gsb: no, there is nothing to handle with next-method here. * let arg-te = arg-te*[accessor-method-dispatch-arg(effective)]; * let offset = slot-fixed-offset-in(slot-descriptor, arg-te); * if (offset) * if (*colorize-dispatch*) * color-dispatch(c, #"slot-accessor-fixed-offset") * end; * upgrade-call-to-slot-accessor(c, effective, slot-descriptor, * offset, arg-te); * #t * else * // the g.f. dispatcher does a better job than direct-calling * // the accessor method, so leave it as a g.f. call * if (*colorize-dispatch*) * color-dispatch(c, #"failed-to-select-where-all-known") * end; * #f * end * else * // the g.f. dispatcher does a better job than direct-calling * // the accessor method, so leave it as a g.f. call * if (*colorize-dispatch*) * color-dispatch(c, #"failed-to-select-where-all-known") * end; * #f * end * elseif (any?(rcurry(instance?, <&accessor-method>), tail(effectives))) * if (*colorize-dispatch*) * color-dispatch(c, #"failed-to-select-where-all-known") * end; * #f * else * let method-call * = upgrade-to-method-call! * (c, effective, tail(effectives), ); * re-optimize(method-call); // Is this the right place to do this? * maybe-upgrade-call(method-call, effective); * #t * end * end function; * */ /// /// GF PARTIAL DISPATCH CALLS /// define function compute-partial-dispatch-cache-mask (g :: <&generic-function>, arg-te* :: , call :: , effectives :: ) => (mask :: , arg-te* :: false-or()) let gsig = ^function-signature(g); let nargs :: = ^signature-number-required(gsig); if ((~empty?(effectives) & empty?(tail(effectives))) | nargs = 0) values(0, #f) else let nargs :: = min(nargs, $partial-dispatch-arguments-limit); let obj :: <&class> = dylan-value(#""); let mask :: = 0; let ans :: = (collecting (as ) for (m :: = 1 then ash(m, 1), arg-te in arg-te*, lim :: from 0 below nargs) let t = as(<&type>, arg-te); unless (t == obj) mask := logior(mask, m); if (instance?(t, <&singleton>)) debug-out(#"partial-dispatch", ">>> Got singleton %= \n", ^singleton-object(t)); let object = ^singleton-object(t); let type = if (instance?(object, <&class>) | object == #f) t else ^object-class(object) end if; collect(type) else debug-out(#"partial-dispatch", ">>> Got type %= \n", t); collect(t) end if end unless; end for end collecting); values(mask, mask ~== 0 & ans) end if end function; define method maybe-upgrade-gf-to-partial-dispatch (c :: , f :: <&generic-function>, arg-te* :: ) => (res :: ) maybe-upgrade-gf-to-partial-dispatch-1(c, f, arg-te*, 0) end method; define method maybe-upgrade-gf-to-partial-dispatch (c :: , f :: <&generic-function>, arg-te* :: ) => (res :: ) maybe-upgrade-gf-to-partial-dispatch-1 (c, f, arg-te*, ^pdisp-type-mask(reference-value(engine-node(c)))) end method; define function maybe-upgrade-gf-to-partial-dispatch-1 (c :: , f :: <&generic-function>, arg-te* :: , premask :: ) => (res :: ) let candidates :: = if (all-applicable-methods-guaranteed-known?(f, arg-te*)) let methods-known = ^generic-function-methods-known(f); let (leading-sorted, others) = guaranteed-sorted-applicable-methods(methods-known, arg-te*); concatenate(leading-sorted, others) else #() end if; let (mask, success-types) = compute-partial-dispatch-cache-mask(f, arg-te*, c, candidates); local method collect-failure-types () collecting () for (i :: from 0, spec in arg-te*) unless (logbit?(i, mask)) collect(list(i, spec)) end end; end end method; if (mask == premask) // Already dealing with all possible arg positions. note-when(*trace-call-cache-failure*, , source-location: dfm-source-location(c), context-id: dfm-context-id(c), failed-parameters: collect-failure-types(), failed-generic: f.model-variable-name, failed-type-estimates: arg-te*); #f else // let trymask :: = logand(mask, lognot(premask)); format-when(*trace-call-cache-success*, "Call site cache upgrade from %d to %d on %=\n", premask, mask, c); let engine-call = upgrade-to-partial-dispatch-call! (c, f, mask, success-types, ); re-optimize(engine-call); maybe-upgrade-call(engine-call, f); #t end if end function; define method maybe-wrap-profiling-engine-node (g :: <&generic-function>, call-site-cache :: <&cache-header-engine-node>) => (res :: <&cache-header-engine-node>) if (*profile-all-calls?*) // accumulated in checking phase to allow for generics to change id let engine-node = ^make(<&profiling-call-site-cache-header-engine-node>, next: call-site-cache, function: g); ^cache-header-engine-node-parent(call-site-cache) := engine-node; engine-node else call-site-cache end if end method; define method maybe-set-profiling-engine-node-call (call-site-cache :: <&cache-header-engine-node>, call-c) end method; define method maybe-set-profiling-engine-node-call (call-site-cache :: <&profiling-call-site-cache-header-engine-node>, call-c) profiling-call-site-cache-header-engine-node-call(call-site-cache) := call-c; end method; define method upgrade-to-partial-dispatch-call! (call :: , g :: <&generic-function>, type-mask :: , types :: , new-call-class :: ) let env = environment(call); let function-ref = make-object-reference(g); let call-site-cache = ^make(<&partial-dispatch-cache-header-engine-node> , function: g, types: types, type-mask: type-mask); let engine-node-ref = make-object-reference(maybe-wrap-profiling-engine-node(g, call-site-cache)); let (first-c, last-c, new-arguments) = method-call-arguments(call, g); let (call-c, call-t) = make-with-temporary (env, new-call-class, temporary-class: call-temporary-class(call), function: function-ref, engine-node: engine-node-ref, arguments: new-arguments); maybe-set-profiling-engine-node-call(reference-value(engine-node-ref), call-c); let (first-c, last-c) = join-2x1!(first-c, last-c, call-c); compatibility-state(call-c) := compatibility-state(call); replace-call-computation! (env, call, first-c, last-c, call-t); call-c end method; define method upgrade-to-profiling-call-site-cache-call! (call :: , g :: <&generic-function>, types :: , new-call-class :: ) let env = environment(call); let function-ref = make-object-reference(g); let call-site-cache = ^make(<&simple-call-site-cache-header-engine-node>, function: g); let engine-node-ref = make-object-reference(maybe-wrap-profiling-engine-node(g, call-site-cache)); let (first-c, last-c, new-arguments) = method-call-arguments(call, g); let (call-c, call-t) = make-with-temporary (env, new-call-class, temporary-class: call-temporary-class(call), function: function-ref, engine-node: engine-node-ref, arguments: new-arguments); maybe-set-profiling-engine-node-call(reference-value(engine-node-ref), call-c); let (first-c, last-c) = join-2x1!(first-c, last-c, call-c); compatibility-state(call-c) := compatibility-state(call); replace-call-computation! (env, call, first-c, last-c, call-t); call-c end method; define method upgrade-gf-to-profiling-call-site-cache (c :: , f :: <&generic-function>, arg-te* :: ) let engine-call = upgrade-to-profiling-call-site-cache-call!(c, f, arg-te*, ); re-optimize(engine-call); maybe-upgrade-call(engine-call, f); #t end method; /// /// GF TYPECHECKED CALLS /// define function get-gf-typechecked-cache-mask (g :: <&generic-function>) => (m :: ) ^generic-function-cache-info(g) | (begin let ans = compute-gf-typechecked-cache-mask(g); if (^%gf-cache(g) == #f) ^%gf-cache(g) := head(ans) end; ^generic-function-cache-info(g) := ans end) end function; define function compute-gf-typechecked-cache-mask (g :: <&generic-function>) let meths :: = ^generic-function-methods(g); let gsig = ^function-signature(g); let nargs :: = ^signature-number-required(gsig); if (empty?(tail(meths)) | nargs < 2) // Don't do anything if there aren't at least two methods, or two arguments. #(0 . #f) elseif (^generic-function-sealed?(g)) let nargs :: = min(nargs, $simple-typechecked-cache-arguments-limit); let meth-1 :: <&method> = head(meths); let spec1 = ^function-specializers(meth-1); let obj :: <&class> = dylan-value(#""); let mask :: = 0; // Initialize the mask to those arg positions which don't have // in them already. for (m :: = 1 then ash(m, 1), i from 0 below nargs) if (~^type-equivalent?(spec1[i], obj)) mask := logior(mask, m) end end for; // Clear the bits for we need to force dispatch for whatever reason. // Early-out if the positions which could be cleared, already are. // @@@@ This should be done more generally... for (meth :: <&method> in meths, until: (logand(mask, 3) == 0)) if (instance?(meth, <&accessor-method>)) ^ensure-slots-initialized(^signature-required(^function-signature(meth)) [accessor-method-dispatch-arg(meth)]); let sd = ^method-slot-descriptor(meth); // (gts,98jan15) sd could be #f in the case of a duplicate slot. // warning will have been given, and duplicate slot removed from class' // direct slots. The method, added during parsing, lives on, though. if (sd & ~slot-offset-fixed-in-class?(sd, ^slot-owner(sd))) mask := logand(mask, lognot(ash(1, accessor-method-dispatch-arg(meth)))) end if end if end for; // Go over remaining methods, look for differing specializers. for (meth :: <&method> in tail(meths), until: (mask == 0)) let spec2 = ^function-specializers(meth); for (i :: from 0 below nargs, m :: = 1 then ash(m, 1)) if (logbit?(i, mask) & ~^type-equivalent?(spec1[i], spec2[i])) mask := logand(mask, lognot(m)) end if end for end for; format-when(*trace-call-cache-success* | *trace-call-cache-failure*, "Computed cache mask of %d for %=\n", mask, g); if (mask == 0) #(0 . #f) else pair(mask, spec1) end else #(0 . #f) end if end function; define method maybe-upgrade-gf-to-call-site-cache (c :: , f :: <&generic-function>, arg-te* :: ) => (res :: ) maybe-upgrade-gf-to-call-site-cache-1(c, f, arg-te*, 0) end method; define method maybe-upgrade-gf-to-call-site-cache (c :: , f :: <&generic-function>, arg-te* :: ) => (res :: ) let e :: <&simple-typechecked-cache-header-engine-node> = reference-value(engine-node(c)); maybe-upgrade-gf-to-call-site-cache-1(c, f, arg-te*, ^stchen-checkedmask(e)) end method; define function maybe-upgrade-gf-to-call-site-cache-1 (c :: , f :: <&generic-function>, arg-te* :: , premask :: ) => (res :: ) let stuff :: = get-gf-typechecked-cache-mask(f); let mask :: = head(stuff); if (mask == premask) // Already dealing with all possible arg positions. #f else let trymask :: = logand(mask, lognot(premask)); let specs :: = tail(stuff); local method frobnicate (m :: , i :: , resultm :: ) if (m == 0) if (resultm == premask) note-when(*trace-call-cache-failure*, , source-location: dfm-source-location(c), context-id: dfm-context-id(c), failed-parameters: collecting () for (i :: from 0 below best-function-number-required(f), spec in best-function-specializers(f)) if (logbit?(i, trymask)) collect(list(i, spec)) end end; end, failed-generic: f.model-variable-name, failed-type-estimates: arg-te*); #f else format-when(*trace-call-cache-success*, "Call site cache upgrade from %d to %d on %=\n", premask, resultm, c); let engine-call = upgrade-to-simple-typechecked-gf-cache! (c, f, resultm, ); re-optimize(engine-call); maybe-upgrade-call(engine-call, f); #t end if else frobnicate(ash(m, -1), i + 1, if (logbit?(0, m) & guaranteed-joint?(arg-te*[i], specs[i])) logior(resultm, ash(1, i)) else resultm end if) end if end method; frobnicate(trymask, 0, premask) end if end function; define method maybe-upgrade-call (c :: , f :: <&generic-function>) => (res :: ) let signature = f.^function-signature; let required-count = signature.^signature-number-required; if (dispatch-state(c) == $dispatch-untried) let arg-te* = required-argument-type-estimates(c, required-count); let effectives = estimate-effective-methods(f, arg-te*, c); dispatch-state(c) := $dispatch-tried; if (empty?(effectives)) next-method(); else let effective = effectives.head; if (instance?(effective, <&accessor-method>)) let slot-descriptor = get-method-slot-descriptor(effective); if (instance?(slot-descriptor, <&any-instance-slot-descriptor>) & size(effectives) = 1) // TODO: HANDLE NEXT-METHOD // gsb: no, there is nothing to handle with next-method here. let arg-te = arg-te*[accessor-method-dispatch-arg(effective)]; let offset = slot-fixed-offset-in(slot-descriptor, arg-te); if (offset) if (*colorize-dispatch*) color-dispatch(c, #"slot-accessor-fixed-offset") end if; incf-static-dispatch-count(); upgrade-call-to-slot-accessor(c, effective, slot-descriptor, offset, arg-te); #t else // the g.f. dispatcher does a better job than direct-calling // the accessor method, so leave it as a g.f. call if (*colorize-dispatch*) color-dispatch(c, #"lambda-call") end if; #f end if else // if 1st of several effective methods is a slot access, // neither color nor upgrade #f end if elseif (any?(rcurry(instance?, <&accessor-method>), effectives)) next-method(); else let method-call = upgrade-to-method-call! (c, effective, effectives.tail, ); maybe-upgrade-call(method-call, effective); #t; end; end; else next-method(); end; end method; define method maybe-upgrade-call (c :: , f :: <&generic-function>) => (res :: ) // The dispatch state prevents analysis being done again on a second // pass over the code, either due to feedback or to the call // computation being copied by inlining. // TODO: If things become more precise after inlining, allow dispatch // to be selectively redone by recording the previous inputs? Except // that CPA won't work like this anyway. let signature = f.^function-signature; let required-count = signature.^signature-number-required; if (dispatch-state(c) == $dispatch-untried & c.arguments.size - 1 >= required-count) let arg-te* = required-argument-type-estimates(c, required-count); let effectives = estimate-effective-methods(f, arg-te*, c); dispatch-state(c) := $dispatch-tried; if (empty?(effectives)) next-method(); else let effective = effectives.head; if (any?(rcurry(instance?, <&accessor-method>), effectives)) next-method(); else let method-apply = upgrade-to-method-call! (c, effective, effectives.tail, ); maybe-upgrade-call(method-apply, effective); #t; end; end; else next-method(); end; end method; // Suppose we can't statically resolve a dispatch, but one or more of the // arguments is a type-union. Suppose the call to each tuple in the // "CPA expansion" can be statically resolved to a slot access, and the offset // and slot kind are identical in each case. Then we can still statically // dispatch as the code will be identical in each case, even though the slots // are semantically different. Detecting this case may help us to implement // methods on limited collections without having to duplicate large chunks of // code. The following code identifies this situation. // This code is only temporary, as a different approach will probably be // required for Andy S's typist, and the following code duplicates too much of // maybe-upgrade-call. // Unfortunately calling estimate-effective-methods generates a whole bunch // of spurious typing errors when I do this, and so the call to // maybe-upgrade-union-call is currently disabled. // define method maybe-upgrade-union-call // (c :: , f :: <&generic-function>, arg-te* :: ) // => (res :: ) // // block (return) // let products = list(arg-te*); // // // Compute a partial CPA expansion. We are only interested in detecting // // slot accesses so only expand out at most the first two arguments. // for (i :: from 0 below min(arg-te*.size, 2)) // let arg-te = arg-te*[i]; // if (instance?(arg-te, )) // products := // reduce(method(l, te) // reduce(method(l, p) // let new-p = copy-sequence(p); // new-p[i] := te; // pair(new-p, l) // end, l, products) // end, #(), type-estimate-unionees(arg-te)); // end; // end; // // unless (products.size > 1) return (#f) end; // There were no unions... // // local method compute-offset-if-possible(new-arg-te*) // let effectives = estimate-effective-methods(f, new-arg-te*, c); // if (effectives.size ~= 1) return(#f) end; // let effective = head(effectives); // unless (instance?(effective, <&accessor-method>)) return (#f) end; // let slot-descriptor = get-method-slot-descriptor(effective); // unless (instance?(slot-descriptor, <&any-instance-slot-descriptor>)) // return (#f) // end; // let arg-te = arg-te*[accessor-method-dispatch-arg(effective)]; // let offset = slot-fixed-offset-in(slot-descriptor, arg-te); // unless (offset) return (#f) end; // values(offset, slot-descriptor, arg-te, effective) // end; // // let (offset,sd,arg-te,effective) = compute-offset-if-possible(products[0]); // let slot-type = ^slot-type(sd); // let initialized? = slot-guaranteed-initialized-in?(sd, arg-te); // // for (i :: from 1 below products.size) // let (offset-i, sd-i, arg-te-i) // = compute-offset-if-possible(products[i]); // unless ( (offset-i = offset) // & (sd-i.object-class = sd.object-class) // & (^slot-type(sd-i) = slot-type) // & (initialized? = // slot-guaranteed-initialized-in?(sd-i, arg-te-i))) // return (#f) // end // end; // // if (*colorize-dispatch*) // color-dispatch(c, #"slot-accessor-fixed-offset") // end; // // incf-static-dispatch-count(); // upgrade-call-to-slot-accessor(c, effective, sd, offset, arg-te); // #t; // end // end; define method upgrade-call-to-slot-accessor (c :: , m :: <&getter-method>, sd :: <&instance-slot-descriptor>, offset :: , te :: ) => () let env = environment(c); let args = arguments(c); let (sv-c, sv-t) = make-with-temporary (env, , guaranteed-initialized?: slot-guaranteed-initialized-in?(sd, te), slot-descriptor: sd, slot-offset: offset, instance: args[0]); re-optimize(sv-c); replace-call-computation!(env, c, sv-c, sv-c, sv-t); end; define method upgrade-call-to-slot-accessor (c :: , m :: <&setter-method>, sd :: <&instance-slot-descriptor>, offset :: , te :: ) => () let env = environment(c); let args = arguments(c); let new-value-t = args[0]; let (sv-c, sv-t) = make-with-temporary (env, , slot-descriptor: sd, slot-offset: offset, new-value: new-value-t, instance: args[1]); replace-call-computation!(env, c, sv-c, sv-c, new-value-t) end; define compiler-sideways method computation-repeated-byte? (c :: ) => (res :: ) repeated-representation-byte? (^slot-type(computation-slot-descriptor(c))) end method; define method convert-boxer-call (env :: , function :: <&primitive>, arguments :: ) make-with-temporary (env, , primitive: function, arguments: arguments); end method; define method convert-boxer-call (env :: , function :: <&function>, arguments :: ) let function-ref = make-object-reference(function); let (call-c, call-t) = make-with-temporary (env, , function: function-ref, arguments: arguments); re-optimize(call-c); values(call-c, call-t) end method; define method maybe-convert-unbox (env :: , ref :: , type :: <&type>) => (unboxer-c :: false-or(), unboxed-ref :: ) let (raw-rep-type?, boxer, unboxer) = raw-repeated-representation?(type); let (unboxer-c, unboxed-ref) = if (raw-rep-type?) convert-boxer-call(env, unboxer, vector(ref)) else values(#f, ref) end if; values(unboxer-c, unboxed-ref) end method; define method maybe-convert-box (env :: , ref :: , type :: <&type>) => (boxer-c :: false-or(), boxed-ref :: ) let (raw-rep-type?, boxer) = raw-repeated-representation?(type); let (boxer-c, boxed-ref) = if (raw-rep-type?) convert-boxer-call(env, boxer, vector(ref)) else values(#f, ref) end if; values(boxer-c, boxed-ref) end method; define method upgrade-call-to-slot-accessor (c :: , m :: <&repeated-getter-method>, sd :: <&repeated-slot-descriptor>, offset :: , te :: ) => () let env = environment(c); let args = arguments(c); let function = make-dylan-reference(#"interpret-integer-as-raw"); let (raw-index-c, raw-index-t) = make-with-temporary (env, , function: function, arguments: vector(args[1])); let (sv-c, sv-t) = make-with-temporary (env, , guaranteed-initialized?: slot-guaranteed-initialized-in?(sd, te), index-tagged?: #t, slot-descriptor: sd, slot-offset: offset, index: raw-index-t, instance: args[0]); let (first-c, last-c) = join-1x1!(raw-index-c, sv-c); let (boxer-c, boxed-t) = maybe-convert-box(env, sv-t, ^slot-type(sd)); let (first-c, last-c, typed-value-t) = if (instance?(type, <&limited-integer>)) // For limited integer slots we add an extra guarantee type because // the shift-n-tag analyser doesn't appear to be smart enough to // work for itself that the value is still in range. let (first-int-c, last-int-c, int-t) = join-2x1-t!(first-c, last-c, boxer-c, boxed-t); let (gt-c, gt-t) = make-with-temporary (env, , value: boxed-t, type: make-object-reference(type)); join-2x1-t!(first-int-c, last-int-c, gt-c, gt-t); else join-2x1-t!(first-c, last-c, boxer-c, boxed-t); end; re-optimize(sv-c); re-optimize(raw-index-c); replace-call-computation! (env, c, first-c, last-c, typed-value-t); end; define method upgrade-call-to-slot-accessor (c :: , m :: <&repeated-setter-method>, sd :: <&repeated-slot-descriptor>, offset :: , te :: ) => () let env = environment(c); let args = arguments(c); let new-value-t = args[0]; let function = make-dylan-reference(#"interpret-integer-as-raw"); let (raw-index-c, raw-index-t) = make-with-temporary (env, , function: function, arguments: vector(args[2])); let (unboxer-c, unboxed-t) = maybe-convert-unbox(env, new-value-t, ^slot-type(sd)); let (first-c, last-c, coerced-value-t) = join-1x1-t!(raw-index-c, unboxer-c, unboxed-t); let (sv-c, sv-t) = make-with-temporary (env, , slot-descriptor: sd, slot-offset: offset, index-tagged?: #t, new-value: coerced-value-t, index: raw-index-t, instance: args[1]); let (first-c, last-c) = join-2x1!(first-c, last-c, sv-c); re-optimize(raw-index-c); replace-call-computation! (env, c, first-c, last-c, new-value-t); end; define method argument-type-estimates (c :: ) => (estimates :: ) // gts-debug("errs", "arg-tes(funcall): c.args=%=.\n", c.arguments); map-as(, type-estimate, c.arguments); end; define method argument-type-estimates (c :: ) => (estimates :: ) next-method(); /* local method flatten-apply-args (args) if (size(args) <= 1) args else let flattened = copy-sequence(args, end: size(args) - 2); concatentate(flattened, last(args)); end if; end method; // gts-debug("errs", "arg-tes(apply): c.args=%=.\n", c.arguments); map-as(, type-estimate, flatten-apply-args(c.arguments)); */ end; define function required-argument-type-estimates (c :: , required-count :: ) => (estimates :: ) collecting (as ) for (i :: from 0 below required-count, arg in c.arguments) collect(type-estimate(arg)) end; end; end; // Compute a reference to the given function and destructively modify the // call to refer to it rather than to what it currently refers to. define method simplify-call-to-call-to-object! (call :: , f :: <&function>) => () let ref-temp = make-object-reference(f); replace-call-function!(call, ref-temp); end method; define method method-call-arguments (call :: , func :: <&lambda>) => (first-c :: false-or(), last-c :: false-or(), arguments :: ) congruent-style-call-arguments(call, func) end method; define method method-call-arguments (call :: , func :: <&generic-function>) => (first-c :: false-or(), last-c :: false-or(), arguments :: ) congruent-style-call-arguments(call, func) end method; define function congruent-style-call-arguments (call :: , func :: <&function>) => (first-c :: false-or(), last-c :: false-or(), arguments :: ) if (~call-congruent?(call) & best-function-optionals?(func)) let number-required = best-function-number-required(func); if (number-required = call.arguments.size) let new-arguments = make(, size: number-required + 1); for (i :: from 0 below number-required) new-arguments[i] := call.arguments[i]; end for; let rest-t = make-object-reference(#[]); new-arguments[number-required] := rest-t; add-user!(rest-t, call); values(#f, #f, new-arguments) else let (rest-c, rest-t) = generate-stack-vector (call.environment, copy-sequence(call.arguments, start: number-required)); let new-arguments = make(, size: number-required + 1); for (i :: from 0 below number-required) new-arguments[i] := call.arguments[i]; end for; new-arguments[number-required] := rest-t; values(rest-c, rest-c, new-arguments) end else values(#f, #f, arguments(call)) end if; end function; define method method-call-arguments (call :: , func :: <&lambda>) => (first-c :: false-or(), last-c :: false-or(), arguments :: ) values(#f, #f, arguments(call)) end method; define method method-call-arguments (call :: , func :: <&lambda>) => (first-c :: false-or(), last-c :: false-or(), arguments :: ) values(#f, #f, arguments(call)) end method; define method replace-call-function! (call :: , temp :: ) => () remove-user!(call.function, call); add-user!(temp, call); call.function := temp end method; define function incf-static-dispatch-count () let l = current-library-description(); let lib = language-definition(model-library(l)); let &lib = namespace-model(lib); ^library-number-static-dispatches(&lib) := ^library-number-static-dispatches(&lib) + 1; end function; define function incf-dynamic-dispatch-count () let l = current-library-description(); let lib = language-definition(model-library(l)); let &lib = namespace-model(lib); ^library-number-dynamic-dispatches(&lib) := ^library-number-dynamic-dispatches(&lib) + 1; end function; define method upgrade-to-method-call! (call :: , f :: <&function>, next-methods :: , new-call-class :: ) => (method-call :: ) let env = environment(call); let function-ref = make-object-reference(f); let next-methods-ref :: = make-value-reference(next-methods, ); let (first-c, last-c, new-arguments) = method-call-arguments(call, f); let (call-c, call-t) = make-with-temporary (env, new-call-class, temporary-class: call-temporary-class(call), function: function-ref, next-methods: next-methods-ref, arguments: new-arguments); let (first-c, last-c) = join-2x1!(first-c, last-c, call-c); incf-static-dispatch-count(); compatibility-state(call-c) := compatibility-state(call); replace-call-computation! (env, call, first-c, last-c, call-t); call-c end method; define method upgrade-to-simple-typechecked-gf-cache! (call :: , g :: <&generic-function>, mask :: , new-call-class :: ) let env = environment(call); let function-ref = make-object-reference(g); let engine-node-ref = make-object-reference (^make(<&simple-typechecked-cache-header-engine-node>, function: g, checkedmask: mask)); let (first-c, last-c, new-arguments) = method-call-arguments(call, g); let (call-c, call-t) = make-with-temporary (env, new-call-class, temporary-class: call-temporary-class(call), function: function-ref, engine-node: engine-node-ref, arguments: new-arguments); let (first-c, last-c) = join-2x1!(first-c, last-c, call-c); compatibility-state(call-c) := compatibility-state(call); replace-call-computation! (env, call, first-c, last-c, call-t); call-c end method; //// Call consistency checking. define program-warning slot condition-type-estimate, required-init-keyword: type-estimate:; format-string "Function value in call is not a function - inferred type is %s."; format-arguments type-estimate; end program-warning; // Check the call if it hasn't been checked already. define method maybe-check-function-call (c :: ) => (ok-to-analyse?) // TODO: CORRECTNESS: How come this function gets called in iep form at // all?? if (call-congruent?(c)) #t else // The compatibility state prevents a call from being check this way // more than once. let state = c.compatibility-state; select (state) $compatibility-checked-compatible => #t; $compatibility-checked-incompatible => #f; $compatibility-unchecked => let ok? = check-function-call(c); c.compatibility-state := if (ok?) $compatibility-checked-compatible else $compatibility-checked-incompatible end; ok?; end; end; end method; // Do a conservative check of as many things about this call as we // possibly can. It's conservative in the sense that it warns only // if there's guaranteed to be a problem. If we work out ways of // extending the language appropriately so that we don't get // swamped with information, a mode conservative the other way // would be very useful. define method check-function-call (c :: ) => (ok-to-analyse?) block (return) let function-temp = c.function; let function-object = constant-value(function-temp); if (instance?(function-object, <&code>)) // We must already have checked to have upgraded this far. return(#t) end; let function-te = type-estimate(function-temp); // If we've hit bottom, there's been an error already. gts,98mar25 // Just bail out. if (instance?(function-te, )) return(#f) end if; if (guaranteed-disjoint?(function-te, dylan-value(#""))) note(, source-location: dfm-source-location(c), context-id: dfm-context-id(c), type-estimate: function-te); return(#f); end; // TODO: We could actually use the type estimate to check the validity of // the arguments in the absence of knowing the function itself. if (~function-object) return(#f); end; // So we have the object, which we know to be a function because of the // disjointness test on its type above. If the arguments are compatible, // we win. // **** we have only tested disjointness, not inclusion, so // we don't know it's a function **** // TODO: Except that we don't for some reason - gets through // somehow! if (instance?(function-object, <&function>)) let arg-te* = argument-type-estimates(c); // gts-debug("errs", "check-funcall: c=%=, arg-te*=%=.\n", c, arg-te*); check-maybe-mv-call-compatibility(function-object, c, arg-te*); else #f end; end; end method; // TODO: I haven't put much effort into getting multiple values right, // since we're moving away from representing them as multiple value // calls. define method check-maybe-mv-call-compatibility (f :: <&function>, call :: , arg-te* :: ) => (compatible?) check-call-compatibility(f, call, arg-te*); end method; define serious-program-warning slot condition-function, required-init-keyword: function:; end serious-program-warning; // TODO: Gross hack. What should really be passed? We need something like // a /. define method initialize (c :: , #key) next-method(); let def = c.condition-function.model-definition; if (def) let names = form-variable-names(def); c.condition-function := if (names.size = 1) names.first else def end; end; end method; define program-warning () slot condition-known-keywords, required-init-keyword: known-keywords:; slot condition-supplied-keyword, required-init-keyword: supplied-keyword:; format-string "Unknown keyword in call to %s - %s supplied, %s recognized"; format-arguments function, supplied-keyword, known-keywords; end program-warning; define program-warning () slot condition-supplied-count, required-init-keyword: supplied-count:; slot condition-required-count, required-init-keyword: required-count:; format-arguments function, supplied-count, required-count; end program-warning; define program-warning () format-string "Too few arguments in call to %s - %s supplied, %s expected."; end program-warning; define program-warning () format-string "Too many arguments in call to %s - %s supplied, %s expected."; end program-warning; define program-warning () slot condition-keyword-supplied-count, required-init-keyword: keyword-supplied-count:; format-string "Unbalanced keyword arguments in call to %s."; format-arguments function, keyword-supplied-count; end program-warning; define program-warning () slot condition-supplied-keyword-type-estimates, required-init-keyword: supplied-keyword-type-estimates:; format-string "Non-symbol keyword arguments in call to %s - inferred types are %s."; format-arguments function, supplied-keyword-type-estimates; end program-warning; define program-warning () slot condition-required-type, required-init-keyword: required-type:; slot condition-supplied-type-estimate, required-init-keyword: supplied-type-estimate:; slot condition-arg-name, // gts required-init-keyword: arg:; // gts format-string "Invalid type for argument %s in call to %s: %s supplied, %s expected."; format-arguments arg, function, supplied-type-estimate, required-type; end program-warning; define program-warning () slot condition-required-types, required-init-keyword: required-types:; slot condition-supplied-type-estimate, required-init-keyword: supplied-type-estimate:; format-string "Invalid #rest values in multiple-value call to %s - " "#rest %s supplied, %s expected."; format-arguments function, supplied-type-estimate, required-types; end program-warning; // The result value of this function is used to decide whether a call // can be upgraded or not, so while the warnings generated err on one // side of caution (i.e. only warn when something is known for sure // to be wrong), the result must be conservative the other way (i.e. // only return true if we know for certain that the input types are // compatible). define inline function maybe-trim-sig-types (v :: , n :: ) => (res :: ) if (n = size(v)) v else copy-sequence(v, end: n) end if end function; // rest-type-estimate seems never sent (i.e. always defaults to #f). (gts, 10/97) define method check-call-compatibility (f :: <&function>, call :: , arg-te* :: , #key rest-type-estimate = #f) => (compatible?); // let signature = ^function-signature(f); let signature // Special kludge for raw slot accessors, whose generic gets created // with in all specializers, but really should be the raw... = (instance?(f, <&generic-function>) & ^generic-function-sealed?(f) & begin let methods = ^generic-function-methods-known(f); methods.size == 1 & methods.first.^function-signature end) | ^function-signature(f); block (return) let required-types = ^signature-required(signature); let required-count = ^signature-number-required(signature); let supplied-count = size(arg-te*); if (supplied-count < required-count & ~rest-type-estimate) // We warn on too few arguments, even in the multiple value // call case. note(, source-location: dfm-source-location(call), context-id: dfm-context-id(call), function: f, required-count: required-count, supplied-count: supplied-count); return(#f); end; let accepts-keys? = ^signature-key?(signature) | ^signature-all-keys?(signature); let accepts-rest? = ^signature-rest?(signature); let fixed-number-required? = ~(accepts-keys? | accepts-rest?); if (fixed-number-required? & supplied-count > required-count) note(, source-location: dfm-source-location(call), context-id: dfm-context-id(call), function: f, required-count: required-count, supplied-count: supplied-count); return(#f); end; if (accepts-keys? & ~rest-type-estimate) let fixed-count = if (instance?(call, )) required-count + 1 else required-count end; let key-arg-count = supplied-count - fixed-count; if (odd?(key-arg-count)) note(, source-location: dfm-source-location(call), context-id: dfm-context-id(call), function: f, keyword-supplied-count: key-arg-count); // break("Unbalanced!"); return(#f); end; let key-arg-te* = copy-sequence(arg-te*, start: fixed-count); let symbol-class = dylan-value(#""); collecting (non-symbol-te*) for (i :: from 0 below key-arg-count by 2) let key-te = key-arg-te*[i]; if (guaranteed-disjoint?(key-te, symbol-class)) collect-into(non-symbol-te*, key-te); end; end; let non-symbol-te* = collected(non-symbol-te*); if (~empty?(non-symbol-te*)) note(, source-location: dfm-source-location(call), context-id: dfm-context-id(call), function: f, supplied-keyword-type-estimates: non-symbol-te*); // break("Non-symbol!"); return(#f); end; end; end; let guaranteed-compatible? = #t; let guaranteed-incompatible? = #f; for (arg-te in arg-te*, required-type in required-types, i :: from 0 below required-count) if (~guaranteed-joint?(arg-te, required-type)) guaranteed-compatible? := #f; // if (guaranteed-disjoint?(arg-te, required-type)) if (effectively-disjoint?(arg-te, required-type)) guaranteed-incompatible? := #t; let required-specs = spec-argument-required-variable-specs(signature-spec(f)); note(, source-location: dfm-source-location(call), context-id: dfm-context-id(call), function: f, required-type: required-type, supplied-type-estimate: arg-te, arg: spec-variable-name(required-specs[i])); end if; end if; end for; if (guaranteed-incompatible?) return(#f); end if; if (rest-type-estimate) for (i :: from supplied-count below required-count) let required-type = required-types[i]; if (~guaranteed-joint?(rest-type-estimate, required-type)) guaranteed-compatible? := #f; // if (guaranteed-disjoint?(rest-type-estimate, required-type)) if (effectively-disjoint?(rest-type-estimate, required-type)) note(, source-location: dfm-source-location(call), context-id: dfm-context-id(call), function: f, required-types: copy-sequence(required-types, start: supplied-count), supplied-type-estimate: rest-type-estimate); return(#f); end; end; end; end; // TODO: CORRECTNESS: Remove this hack, its contol flags, and its // associated condition below. if (~guaranteed-compatible? & instance?(f, <&method>) & compiling-dylan-library?()) if (*warn-about-bogus-upgrades*) note(, source-location: dfm-source-location(call), context-id: dfm-context-id(call), function: f, required-types: maybe-trim-sig-types (required-types, required-count), supplied-type-estimates: arg-te*); end; if (*colorize-bogus-upgrades*) color-dispatch(call, #"bogus-upgrade"); end; // Fake it in the Dylan library. #t else guaranteed-compatible? end; end block; end method; define program-warning () slot condition-required-types, required-init-keyword: required-types:; slot condition-supplied-type-estimates, required-init-keyword: supplied-type-estimates:; format-string "Bogus call upgrade possible for call to %s - %s supplied and considered compatible, %s expected."; format-arguments function, supplied-type-estimates, required-types; end program-warning; // For bare methods we can check keyword compatibility too. define program-warning () slot condition-supplied-keywords, required-init-keyword: supplied-keywords:; slot condition-recognized-keywords, required-init-keyword: recognized-keywords:; format-string "Unrecognized keyword arguments in call to %s - " "%s unrecognized, %s allowed."; format-arguments function, supplied-keywords, recognized-keywords; end program-warning; define method check-call-compatibility (f :: <&method>, call :: , arg-te* :: , #key rest-type-estimate = #f) => (compatible?) let compatible? = next-method(); if (~compatible?) #f else // Do any extra checking possible for bare method calls. block (return) let signature = ^function-signature(f); if (~rest-type-estimate & ^signature-key?(signature) & ~ ^signature-all-keys?(signature)) let recognized-keys = ^signature-keys(signature); collecting (unrecognized-keys) let arg-temps = call.arguments; let arg-count = size(arg-temps); let key-start = ^signature-number-required(signature); for (i :: from key-start below arg-count by 2) // TODO: I'd like to do this using the type estimate, but it // doesn't currently have singleton precision. // TODO: Check the declared types of the keyword in the method // being called here too. let (arg-value-constant?, arg-value) = constant-value?(arg-temps[i]); if (arg-value-constant? & ^symbol?(arg-value) & ~member?(arg-value, recognized-keys)) collect-into(unrecognized-keys, arg-value); end; end; let unrecognized-keys = collected(unrecognized-keys); if (~empty?(unrecognized-keys)) note(, source-location: dfm-source-location(call), context-id: dfm-context-id(call), function: f, supplied-keywords: unrecognized-keys, recognized-keywords: recognized-keys); return(#f); end; end; end; #t; end block; end if; end method; // We do what checking we can for apply. We should really be able to // do pretty much everything with any positionally supplied arguments. // So, f(x, y, z) and apply(f, x, y, z, #[]) should be checked to // a similar level of accuracy, including checking non-symbol // keyword arguments and the like. // TODO: Do better. Reuse more code, but keep the more specific error // reporting. define program-warning () format-string "Too many arguments in application of %s - " "%s supplied positionally to apply, only %s expected."; end program-warning; define program-warning () slot condition-required-type, required-init-keyword: required-type:; slot condition-supplied-type-estimate, required-init-keyword: supplied-type-estimate:; slot condition-arg-name, // gts required-init-keyword: arg:; // gts format-string "Invalid type for argument %s in application of %s: " // gts "type %s supplied, but %s expected."; format-arguments arg, function, supplied-type-estimate, required-type; end program-warning; define method check-call-compatibility (f :: <&function>, call :: , arg-te* :: , #key rest-type-estimate = #f) => (compatible?) check-apply-compatibility (f, call, arg-te*, rest-type-estimate: rest-type-estimate); end method; define method check-call-compatibility (f :: <&method>, call :: , arg-te* :: , #key rest-type-estimate = #f) => (compatible?) check-apply-compatibility (f, call, arg-te*, rest-type-estimate: rest-type-estimate); end method; define method check-apply-compatibility (f :: <&function>, call :: , arg-te* :: , #key rest-type-estimate = #f) => (compatible?) let arg-te* = copy-sequence(arg-te*, end: arg-te*.size - 1); let signature = ^function-signature(f); // gts-debug("errs", "check-apply-compat: call=%=, f=%=, arg-te*=%=.\n", // call, f, arg-te*); block (return) let required-types = ^signature-required(signature); let required-count = ^signature-number-required(signature); let supplied-count = size(arg-te*); let accepts-keys? = ^signature-key?(signature) | ^signature-all-keys?(signature); let accepts-rest? = ^signature-rest?(signature); let fixed-number-required? = ~(accepts-keys? | accepts-rest?); if (fixed-number-required? & supplied-count > required-count) note(, source-location: dfm-source-location(call), context-id: dfm-context-id(call), function: f, required-count: required-count, supplied-count: supplied-count); return(#f); end; let guaranteed-compatible? = #t; let guaranteed-incompatible? = #f; let intersect-count = min(required-count, supplied-count); for (arg-te in arg-te*, required-type in required-types, i :: from 0 below required-count) if (~guaranteed-joint?(arg-te, required-type)) guaranteed-compatible? := #f; // if (guaranteed-disjoint?(arg-te, required-type)) if (effectively-disjoint?(arg-te, required-type)) guaranteed-incompatible? := #t; note(, source-location: dfm-source-location(call), context-id: dfm-context-id(call), function: f, required-type: required-type, supplied-type-estimate: arg-te, arg: best-function-specializers(f)[i]); end if; end if; end for; if (guaranteed-incompatible?) return (#f); end if; // TODO: CORRECTNESS: Remove this hack, its contol flags, and its // associated condition below. if (~guaranteed-compatible? & instance?(f, <&method>) & compiling-dylan-library?()) if (*warn-about-bogus-upgrades*) note(, source-location: dfm-source-location(call), context-id: dfm-context-id(call), function: f, required-types: copy-sequence(required-types, end: intersect-count), supplied-type-estimates: arg-te*); end; if (*colorize-bogus-upgrades*) color-dispatch(call, #"bogus-upgrade"); end; // Fake it in the Dylan library. #t else guaranteed-compatible? end; end; end method; define program-warning () slot condition-required-types, required-init-keyword: required-types:; slot condition-supplied-type-estimates, required-init-keyword: supplied-type-estimates:; format-string "Invalid argument types in application of %s - " "%s supplied positionally to apply, %s expected in the corresponding " "positions."; format-arguments function, supplied-type-estimates, required-types; end program-warning; //// Compile-time method selection. define program-warning () slot condition-supplied-type-estimates, required-init-keyword: supplied-type-estimates:; format-string "No applicable methods for call to %s - inferred argument types %s."; format-arguments function, supplied-type-estimates; end program-warning; // TODO: Write a proper optimization note framework to use here and // elsewhere. define performance-note end; define performance-note () slot condition-selected-method, required-init-keyword: selected-method:; slot condition-type-estimates, required-init-keyword: type-estimates:; format-string "Selected method %s on %s."; format-arguments selected-method, type-estimates; end performance-note; define performance-note () slot condition-failed-generic, required-init-keyword: failed-generic:; slot condition-failed-type-estimates, required-init-keyword: failed-type-estimates:; format-string "Selection failed for %s on %s."; format-arguments failed-generic, failed-type-estimates; end performance-note; define performance-note () slot condition-failed-generic, required-init-keyword: failed-generic:; slot condition-failed-type-estimates, required-init-keyword: failed-type-estimates:; format-string "Can't dispatch %s on %s."; format-arguments failed-generic, failed-type-estimates; end performance-note; define performance-note () slot condition-failed-generic, required-init-keyword: failed-generic:; slot condition-failed-type-estimates, required-init-keyword: failed-type-estimates:; slot condition-failed-parameters, required-init-keyword: failed-parameters:; format-string "Failed to eliminate type checking for args %= in call to %s with %s."; format-arguments failed-parameters, failed-generic, failed-type-estimates; end performance-note; define method color-dispatch (call :: , decision) let loc = dfm-source-location(call); if (loc) color-location (loc, decision) end; end method; define method color-location (loc :: , decision) // TODO: should verify that loc is within current form. let cr = loc.source-location-record; if (current-library-description?(cr.compilation-record-library)) let decisions = cr.compilation-record-dispatch-decisions; // dispatch decisions might already have been canonicalized into a vector // if the library has already been fully compiled previously, but now // we've re-entered it in order to lazily (re)compute a model [see bug // #1000342 for a test case]. In that case, just don't record the // decision at all. when (instance?(decisions, )) cr.compilation-record-dispatch-decisions := pair(vector(loc.source-location-start-offset, loc.source-location-end-offset, decision), decisions); end; end; end; define method estimate-effective-methods (f :: <&generic-function>, arg-te* :: , call-or-method :: type-union(, <&method>)) => (effectives :: ) estimate-effective-methods-from-scratch(f, arg-te*, call-or-method); /* let n-required = ^signature-number-required(^function-signature(f)); let arg-te* = if (size(arg-te*) == n-required) arg-te* else copy-sequence(arg-te*, end: n-required); end; let methods = type-estimate-dispatch-cache-lookup(f, arg-te*); if (methods) methods else let methods = estimate-effective-methods-from-scratch(f, arg-te*, call-or-method); add-type-estimate-dispatch-cache-entry(f, arg-te*, methods); methods end; */ end method; define method estimate-effective-methods-from-scratch (f :: <&generic-function>, arg-te* :: , call-or-method :: type-union(, <&method>)) => (effectives :: ) let (call, lambda) = if (instance?(call-or-method, )) values(call-or-method, #f) else values(#f, call-or-method) end if; if (all-applicable-methods-guaranteed-known?(f, arg-te*)) format-when(*trace-dispatch*, "Could dispatch: %= on %=\n", f, arg-te*); let methods-known = ^generic-function-methods-known(f); let methods = if (call) methods-known else // Lose all methods that are known statically always to be more // specific than ourselves, leaving only methods known to be // less specific and those that are potentially more or less // specific. choose(method (them :: <&method>) them == lambda | ~guaranteed-method-precedes?(them, lambda, arg-te*) end method, methods-known) end if; let (leading-sorted, others) = guaranteed-sorted-applicable-methods(methods, arg-te*); if (empty?(leading-sorted) & empty?(others)) note-when (call, , source-location: dfm-source-location(call), context-id: dfm-context-id(call), function: f, supplied-type-estimates: arg-te*); #() else let chain = guaranteed-complete-method-chain(leading-sorted, others, arg-te*); if (chain) if (call) maybe-note (, source-location: dfm-source-location(call), context-id: dfm-context-id(call), selected-method: chain.first.model-definition, type-estimates: arg-te*); if (*colorize-dispatch* & empty?(chain)) color-dispatch(call, #"failed-to-select-where-all-known") end; else // Prune out everything that precedes us in the next method // chain. Note that the method calling next-method might not // actually be on this list if more specific methods override // entirely (i.e. don't call next-method) because of the way // the chain is constructed. for (cursor :: = chain then cursor.tail, until: empty?(cursor) | cursor.head == lambda) finally cursor end; end if; chain else if (call & *colorize-dispatch*) color-dispatch(call, #"failed-to-select-where-all-known") end; note-when (call & *trace-selection-failure*, , source-location: dfm-source-location(call), context-id: dfm-context-id(call), failed-generic: f.model-variable-name, failed-type-estimates: arg-te*); #() end; end; else if (call & *colorize-dispatch*) color-dispatch(call, #"not-all-methods-known") end; note-when (call & *trace-dispatch-failure*, , source-location: dfm-source-location(call), context-id: dfm-context-id(call), failed-generic: f.model-variable-name, failed-type-estimates: arg-te*); #() end; end method; define method guaranteed-complete-method-chain (leading-sorted :: , others :: , arg-te* :: ) => (method-chain :: false-or()) block (return) collecting (chain) for (m in leading-sorted) let sig = ^function-signature(m); if (arguments-guaranteed-joint? (arg-te*, ^signature-required(sig), ^signature-number-required(sig))) collect-into(chain, m); if (~m.^next?) return(collected(chain)); end if; else return(#f); end; finally /* TODO: THIS IS BAD BAD BAD if (empty?(others)) // TODO: Might be an error because the last one calls next method? end; collected(chain); */ #f end; end; end; end method; // Return the set of methods potentially applicable to the argument types // in two sets. The first is a sorted sequence, where each method in that // sequence is guaranteed to be more specific than the next in that // sequence, and the last in the sequence is guaranteed to be more // specific than all the remaining methods. The second set consists // of the remaining potentially applicable methods that can't be // usefully ordered. define method guaranteed-sorted-applicable-methods (methods :: , arg-te* :: ) => (leading-sorted :: , others :: ) let leading-sorted = #(); let others = #(); local method merge-method (m, sorted) if (empty?(sorted)) local method after-m? (other-m) guaranteed-method-precedes?(m, other-m, arg-te*); end; block (return) for (other-m in others) unless (after-m?(other-m)) others := pair(m, others); return(#()); end unless; end for; list(m); end block; /* if (every?(after-m?, others)) list(m); else others := pair(m, others); #(); end; */ else let lead = sorted.first; select (guaranteed-method-relationship(m, lead, arg-te*)) $method1-precedes => pair(m, sorted); $method2-precedes => pair(lead, merge-method(m, sorted.tail)); $methods-unordered => // Unordered methods are no use to us, so abandon everything // from here on. others := pair(m, concatenate(sorted, others)); #(); end; end; end; for (m in methods) let sig = ^function-signature(m); if (arguments-potentially-joint? (arg-te*, ^signature-required(sig), ^signature-number-required(sig))) leading-sorted := merge-method(m, leading-sorted); end; end; values(leading-sorted, others) end method; // Determine whether there is some guaranteed ordering between the two // methods for the given argument type estimates. define constant $methods-unordered = #"unordered"; define constant $method1-precedes = #"method1"; define constant $method2-precedes = #"method2"; define method guaranteed-method-precedes? (m1 :: <&method>, m2 :: <&method>, arg-te* :: ) guaranteed-method-relationship(m1, m2, arg-te*) == $method1-precedes end method; // This is pretty much as per the DRM specification, but using the // conservative "guaranteed-xxx" predicates. define method guaranteed-method-relationship (m1 :: <&method>, m2 :: <&method>, arg-te* :: ) => (relationship) let specs1 = m1.^function-specializers; let specs2 = m2.^function-specializers; let precedes-somewhere? = #f; let follows-somewhere? = #f; for (arg-te in arg-te*, spec1 in specs1, spec2 in specs2) case spec1 == spec2 => ; // continue guaranteed-preceding-specializer?(spec1, spec2, arg-te) => unless (^subtype?(spec2, spec1)) precedes-somewhere? := #t end; guaranteed-preceding-specializer?(spec2, spec1, arg-te) => follows-somewhere? := #t; otherwise => ; // continue end; end; if (precedes-somewhere?) if (follows-somewhere?) $methods-unordered else $method1-precedes end else if (follows-somewhere?) $method2-precedes else $methods-unordered end; end; end method; // Do we know all the methods on this generic function for the given // argument type estimates. define method all-applicable-methods-guaranteed-known? (f :: <&generic-function>, arg-te* :: ) => (known?) if (^generic-function-sealed?(f)) let binding = model-variable-binding(f); let methods = binding-method-definitions(binding); ~any?(form-dynamic?, methods); else let domains = ^generic-function-domains-known(f); if (empty?(domains)) #f else local method domain-guaranteed-joint? (domain) let types = ^domain-types(domain); arguments-guaranteed-joint?(arg-te*, types, size(types)) & all-domain-methods-guaranteed-known?(f, domain, arg-te*) end; any?(domain-guaranteed-joint?, domains); end; end; end method; define method all-domain-methods-guaranteed-known? (f :: <&generic-function>, domain :: <&domain>, arg-te* :: ) => (known? :: ) let ld = model-library(domain); let affected-method-definitions = if (current-library-description?(ld)) binding-method-definitions(model-variable-binding(f)) else with-dependent-context ($compilation of model-creator(domain)) // We don't do cross-library dependency tracking right now. without-dependency-tracking binding-method-definitions(model-variable-binding(f)); end; end; end; // format-out(">>> Methods in %= for %=:\n", ld, f); /* for (md in affected-method-definitions) format-out(">>> %=\n", md); end; */ ~any?(form-dynamic?, affected-method-definitions) end method; // TODO: This test of domain inclusion is insufficient. If the generic is // individually sealed over both branches of a test union type, we // should win here. // domain // domain // sealed-over type-union(, )? // Coalescing domain declarations could be tricky to get right. It might // be easier to expand any type estimate unions here? Unions aren't the // only things, though. Limited integers have the same problem (although // they could be considered unions). In fact, there are examples of // classes alone. // sealed () // () // () // domain () // domain () // sealed-over ? // Easier if we canonicalize the type a union of known concrete subtypes // I guess. define method arguments-guaranteed-joint? (arg-te* :: , domain-type* :: , max :: ) => (joint?) block (return) for (arg-te in arg-te*, domain-type in domain-type*, i :: from 0 below max) unless (guaranteed-joint?(arg-te, domain-type)) return(#f) end unless; end for; #t end block; // every?(guaranteed-joint?, arg-te*, domain-type*); end method; define method arguments-potentially-joint? (arg-te* :: , domain-type* :: , max :: ) => (joint?) block (return) for (arg-te in arg-te*, domain-type in domain-type*, i :: from 0 below max) unless (potentially-joint?(arg-te, domain-type)) return(#f) end unless; end for; #t end block; // every?(potentially-joint?, arg-te*, domain-type*); end method; // eof