Module: dfmc-optimization Author: Keith Playford and Paul Haahr Synopsis: Inline function bodies 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 //// Simple inlining pass // define compilation-pass try-inlining, // visit: computations, // optimization: low, // after: analyze-calls, // before: single-value-propagation, // triggered-by: analyze-calls, // trigger: analyze-calls, // trigger: single-value-propagation; define generic try-inlining (c :: ); define generic try-inlining-call (c :: , function); define generic function-used-once? (function) => (used-once? :: ); define method try-inlining (c :: ) #f end method try-inlining; define method inline-method-value (ref :: ) reference-value(ref) end method; define method inline-method-value (ref :: ) #f end method; define method inline-method-value (ref :: ) let generator = ref.generator; if (instance?(generator, )) computation-closure-method(generator) end if end method; define function call-inline-effective-function (c :: ) let funct = inline-method-value(c.function); if (funct & call-iep?(c)) iep(function(funct)) else funct end if; end function; define method try-inlining (c :: ) try-inlining-call(c, call-inline-effective-function(c)) end method try-inlining; define method try-inlining-call (c :: , function) // nodes must be inlined, but the rules are different // Calls where the function is something other than an IEP // probably shouldn't ever be inlinined. #f end method try-inlining-call; //// INLINING POLICY define variable *inlining?* = #t; define method try-inlining-call (c :: , code :: <&iep>) // TODO: size heuristic // TODO: inline letrec-bound functions if (*inlining?*) let fun = code.function; // format-out("INLINING %=\nTOP? %= REFUSEDONCE? %= USEDONCE? %= \n", // c, lambda-top-level?(fun), // function-reference-used-once?(c.function), // function-used-once?(code)); if (lambda-top-level?(fun)) if (method-inlineable?(fun) // & ~member?(fun, call-inlining-stack(c)) & call-inlining-depth(c) < $max-inlining-depth) // when (lambda-inlineable?(fun)) // debug-message("AUTO INLINING %= INTO %=\n", fun, c.environment.lambda); // end when; inline-call-copied(c, code); end if; elseif (function-reference-used-once?(c.function) // the temporary & function-used-once?(code) // the value & ~inner-environment?(environment(c), environment(code))) inline-call(c, code) elseif (lambda-inlineable?(fun) == #t) // currently only bind-exit returns with-dfm-copier-environment (environment(code)) inline-call-copied(c, code) end with-dfm-copier-environment; // #f else #f end if; end if; end method try-inlining-call; /* TODO: OBSOLETE? define method function-called-more-than-twice? (ref :: , f :: <&lambda-or-code>) => (called-more-than-twice? :: ); local method caller-count (refd-object :: ) => (res :: ) let count :: = 0; for (user in users(refd-object)) if (instance?(user, )) count := count + 1; end if; end for; count end method; let f = f.function; (caller-count(ref) + caller-count(f.iep)) > 2 end method; */ define method function-used-once? (f :: <&lambda>) => (used-once? :: ); // format-out("%= USERS %= IUSERS %=\n", // f, f.users, f.iep.users); (size(users(f)) + size(users(f.iep))) = 1 end method function-used-once?; define method function-used-once? (f :: <&iep>) => (used-once? :: ) function-used-once?(f.function) end method function-used-once?; define function function-reference-used-once? (ref :: ) => (used-once? :: ); local method user-count (code :: ) => (res :: ) let count :: = 0; for (user in users(code)) unless (instance?(user, )) count := count + 1; end unless; end for; count end method; user-count(ref) = 1 end function; //// Inlining tools // Currently, only ieps may be inlined, because the xep code is // not (yet) represented in DFM code. xep->iep conversion is // handled by upgrading the calls in analyze-calls. define method inline-call (c :: , f :: <&lambda>) #f end method inline-call; // At it's heart, let-conversion should just be inlining plus removal // of the original lambda expression. Since inlining will normally // involve making a copy of the function's flow graph and splicing // it into place, an optimisation where the original code is no // longer needed is to re-use that. define function replace-call-computation! (env :: , call :: , first :: , last :: , ref :: false-or()) // format-out("MATCHING %= TO %=\n", temporary(call), ref); let (first, new-last, ref) = match-values-with-temporary (env, temporary(call), first, last, ref); unless (new-last == last) re-optimize(new-last); end unless; replace-computation!(call, first, new-last, ref); end function; define method maybe-update-inlined-next-methods (c :: , f :: <&lambda>, mapped-body) when (^next?(f)) assert(instance?(c, ) | instance?(c, ), "calling method %= inappropriately %=", f, c); let nmcs = collecting () walk-lambda-computations (method (c) when (primitive-call-to?(c, #"primitive-next-methods-parameter")) collect(c) end when end method, mapped-body); end collecting; for (nmc in nmcs) // REPLACE NEXT-METHODS QUERY IN BODY WITH NEXT-METHODS FROM CALL replace-computation-with-temporary!(nmc, next-methods(c)); end for; end when; end method; define method do-inline-call (c :: , f :: <&iep>, mapped :: ) let f = function(f); let target-env = lambda-environment(c.environment); let (mapped-body) = move-code-into!(f, target-env, mapped); let (first, last, return-c) = extract-lambda-body-extent(mapped-body, target-env); if (mapped == identity) f.body := #f; end if; redirect-arguments!(c, f, mapped); let return-t = return-c.computation-value; re-optimize-users(c.temporary); // format-out("RE-OPT %=\n", users(c.temporary)); let (first, last, tmp) = if (~c.temporary | instance?(c.temporary, )) // is the call's temporary expecting different values than the // fn's return temp is delivering? if (c.temporary & instance?(c.temporary, ) & (required-values(c.temporary) ~= required-values(return-t) | rest-values?(c.temporary) ~= rest-values?(return-t))) let (adjust, adjust-temp) = make-with-temporary (target-env, if (rest-values?(c.temporary)) else end, value: return-t, number-of-required-values: required-values(c.temporary), temporary-class: ); adjust-temp.required-values := required-values(c.temporary); adjust-temp.rest-values? := rest-values?(c.temporary); re-optimize(adjust); join-2x1-t!(first, last, adjust, adjust-temp); else values(first, last, return-t); end if; else let (extract-c, extract-t) = make-with-temporary (target-env, , temporary-class: temporary-class(c.temporary), value: return-t); re-optimize(extract-c); join-2x1-t!(first, last, extract-c, extract-t); end if; replace-call-computation!(target-env, c, first, last, tmp); delete-computation!(return-c); maybe-update-inlined-next-methods(c, f, mapped-body); #t end method; define method inline-call (c :: , f :: <&iep>) // format-out("MOVE INLINING %= %=\n", f.function, c); // print-method-out(c.environment.lambda); // format-out("--- OF ---\n"); // print-method-out(f.function); if (*colorize-dispatch*) color-dispatch(c, #"inlining") end; re-optimize-users(c.function); // MAYBE DELETE MAKE/INIT-CLOSURE IF PRESENT let f-body = f.body; let inherited-location = if (~computation-source-location(f-body)) parent-source-location() else #f end; walk-lambda-computations (method (c :: ) item-status(c) := $queueable-item-absent; computation-source-location(c) := computation-source-location(c) | inherited-location end, f-body); do-inline-call(c, f, identity); end method inline-call; define method inline-call-copied (c :: , f :: <&iep>) let code = f.function; // format-out("COPY INLINING %= %=\n", code, c); // print-method-out(c.environment.lambda); // format-out("--- OF ---\n"); ensure-method-dfm(code); // print-method-out(code); if (code.body) if (*colorize-dispatch*) color-dispatch(c, #"inlining") end; dynamic-bind (*inlining-depth* = call-inlining-depth(c) + 1) let copier = current-dfm-copier(estimated-copier-table-size(code)); do-inline-call(c, f, curry(deep-copy, copier)); // print-method-out(c.environment.lambda); end dynamic-bind; else lambda-inlineable?(code) := #f; debug-message("LOST %='s BODY FOR INLINING", code); end if; end method; define method extract-lambda-body-extent (body, env) => (first, last, return-c) let bind-c = body; let return-c = bind-return(bind-c); check-type(bind-c, ); check-type(return-c, ); if (bind-c.next-computation == return-c) let nop = make-in-environment(env, ); values(nop, nop, return-c) else let first = bind-c.next-computation; let last = return-c.previous-computation; first.previous-computation := #f; last.next-computation := #f; values(first, last, return-c) end if end method; define program-warning slot condition-method, required-init-keyword: meth:; slot condition-other-methods, required-init-keyword: other-methods:; format-string "Multiple applicable copy-down methods for %s, picking one at random"; format-arguments meth; end; define program-warning slot condition-method, required-init-keyword: meth:; format-string "Domain not fully known for copy-down method %s"; format-arguments meth; end; define serious-program-warning slot condition-method, required-init-keyword: meth:; format-string "Failed to find an applicable copy-down method for %s"; format-arguments meth; end; define method find-copy-downable-methods (m :: <©-down-method>) => (sorted, others) let gf = m.^method-generic-function; let sig = m.^function-signature; let req-size = sig.^signature-number-required; let req = copy-sequence(sig.^signature-required, end: req-size); let req-te* = map(curry (as, ), req); let methods-known = ^generic-function-methods-known(gf); unless (all-applicable-methods-guaranteed-known?(gf, req-te*)) note(, meth: m, source-location: m.model-source-location); end; // 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. let methods = choose(method (them :: <&method>) them == m | ~guaranteed-method-precedes?(them, m, req-te*) end method, methods-known); guaranteed-sorted-applicable-methods(methods, req-te*); end method; // markt, copy-down inlining, first cut (still generates a type-check warning) define compiler-sideways method copy-down-body (m :: <©-down-method>) => () let (sorted-applicable-methods, others) = find-copy-downable-methods(m); local method real-method (a) ~instance?(a, <©-down-method>) & a end; let meth = any?(real-method, sorted-applicable-methods) | begin let others = choose(real-method, others); if (others.size == 1) others.first else let lib = model-library(m); let kludge = any?(method (a) model-library(a) == lib & a end, others); if (kludge) note(, meth: m, other-methods: others, source-location: m.model-source-location); kludge end; end; end; if (~meth) note(, meth: m, source-location: m.model-source-location); else let callee = meth.^iep ; let f = callee.function ; // this required to set up copy source method ready for splicing: ensure-method-dfm (f) ; debug-assert(f.body, "No body to copy-down?"); f.lambda-copied-down? := #t; //format-out ("copy-down inlining body of %s for %s\n", f, m) ; really-run-compilation-passes (f); let target-env = m.environment.lambda-environment ; let mapper = curry (deep-copy, make ()); ensure-optimization-queue (m); while (queue-pop(m.optimization-queue)) end; // EMPTY BOGUS SCHTUFF let stale-temporaries = collecting () for-temporary (tmp in target-env) unless (member?(tmp, m.parameters)) collect(tmp) end unless; end for-temporary; end collecting; for (tmp in stale-temporaries) remove-temporary!(target-env, tmp); end for; let mapped-body = move-code-into! (f, target-env, mapper) ; let (first, last, return-c) = extract-lambda-body-extent (mapped-body, target-env) ; redirect-args! (m.parameters, f.parameters, mapper) ; for (param in mapper(f.parameters)) remove-temporary!(target-env, param); end for; // markt: fix for keyword default vector. Shame not to share these, // but that is more complex to get right for the linker. if (instance?(f, <&keyword-method>)) m.keyword-specifiers := mapper (f.keyword-specifiers) ; end if; let bind-comp = m.body ; let return-t = return-c.computation-value ; return-c.previous-computation := #f ; join-2x1! (first, last, return-c); bind-comp.next-computation := #f ; join-2x2! (bind-comp, bind-comp, first, last) ; re-optimize (return-c) ; m.body-spec := f.body-spec ; run-optimizations (m); #f end end; // markt, slightly more general version for copy-down define function redirect-args! (args, params, mapped :: ) => () for (parameter in params, argument in args) replace-temporary-in-users! (parameter.mapped, argument) end end; define method redirect-arguments! (c :: , f :: <&lambda>, mapped :: ) => () redirect-args! (c.arguments, f.parameters, mapped) end method redirect-arguments!; // old version: //define method redirect-arguments! // (c :: , f :: <&lambda>, mapped :: ) => () // for (parameter in f.parameters, argument in c.arguments) // replace-temporary-in-users!(mapped(parameter), argument); // end for; //end method redirect-arguments!; define method move-code-into! (f :: <&lambda>, env :: , mapped :: ) => (mapped-body) // THESE SHOULD BE IN ENVIRONMENT.DYLAN let f-env = f.environment; when (f-env) let mapped-env = mapped(f-env); for (entry in mapped-env.entries) env.entries := add!(env.entries, entry); end for; // TODO: MANAGE THESE // for (loop in mapped-env.loops) // env.loops := add!(env.loops, loop); // end for; for (inner in mapped-env.inners) env.inners := add!(env.inners, inner); inner.outer := env; end for; for-temporary (tmp in lambda-environment(mapped-env)) add-temporary!(env, tmp); tmp.frame-offset := next-frame-offset(env); tmp.environment := env; end for-temporary; end; let mapped-body = mapped(f.body); let lambda = env.lambda; // Make sure the new items are added to the optimization queue in the // correct order. We save the current entries, collect the new ones, and // then add the new ones to the old so they end up in the correct order. let old-q = lambda.optimization-queue; lambda.optimization-queue := make(, code: mapped-body); walk-lambda-computations (method (c :: ) c.environment := env; re-optimize-into!(c, lambda); end, mapped-body); let mapped-q = lambda.optimization-queue; lambda.optimization-queue := old-q; iterate xfer (qhead = queue-pop(mapped-q)) when (qhead) add-to-queue!(old-q, qhead); xfer(queue-pop(mapped-q)) end when; end iterate; mapped-body end method;