module: dfmc-management 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 //// Useful timing macros. define macro timing-compilation-phase { timing-compilation-phase (?phase:expression of ?ld:expression, ?keys:*) ?:body end } => { do-timing-compilation-phase(?ld, ?phase, method () ?body end, ?keys) } end macro; // TODO: All this because floating point printing is dodgy right now. define function integer-amount-to-string (amount :: , microamount :: ) => (float :: ) format-to-string("%d.%s", amount, integer-to-string(microamount, size: 6)) end function; define function float-amount-to-string (amount :: ) => (float :: ) let whole-amount :: = floor(amount); let microamount :: = round(1000000.0 * (amount - max(whole-amount, 0))); integer-amount-to-string(whole-amount, microamount); end function; define class () slot snap-time :: , required-init-keyword: time:; slot snap-time-string :: , required-init-keyword: time-string:; slot snap-space :: , required-init-keyword: space:; slot snap-space-string :: , required-init-keyword: space-string:; constant slot snap-phase :: , required-init-keyword: phase:; end class; define variable *dfmc-profile-allocation?* = #f; define function do-timing-compilation-phase (ld :: , phase, body :: , #key progress? = #f, accumulate?, #all-keys) progress? & progress-line("%s of %s timing begun.", phase, ld); let (elapsed-seconds, elapsed-microseconds, allocated-space) = values(#f, #f, #f); profiling(cpu-time-seconds, cpu-time-microseconds, allocation) if (*dfmc-profile-allocation?*) profiling(allocation-stats = list(description: format-to-string ("%s of %s", phase, library-description-emit-name(ld)))) body(); results end; else body(); end if; results elapsed-seconds := cpu-time-seconds; elapsed-microseconds := cpu-time-microseconds; allocated-space := allocation / 1000000.0; end; let elapsed-time = elapsed-seconds + elapsed-microseconds / 1000000.0; let snap = accumulate? & compilation-timing-property?(ld, method(snap :: ) snap.snap-phase = phase; end method); if (snap) let elapsed-time = snap.snap-time + elapsed-time; let allocated-space = snap.snap-space + allocated-space; snap.snap-time := elapsed-time; snap.snap-time-string := float-amount-to-string(elapsed-time); snap.snap-space := allocated-space; snap.snap-space-string := float-amount-to-string(allocated-space); else snap := make(, time: elapsed-time, time-string: integer-amount-to-string (elapsed-seconds, elapsed-microseconds), space: allocated-space, space-string: float-amount-to-string(allocated-space), phase: phase); record-compilation-timing-property(ld, snap); end if; progress? & if (accumulate?) progress-line ("%s of %s took %s seconds.", phase, ld, integer-amount-to-string (elapsed-seconds, elapsed-microseconds)); else progress-line ("%s of %s took %s seconds.", phase, ld, snap-time-string(snap)); end if; values(); end function; define function uniquify-timing-properties (props :: ) => (new-props :: ) let seen = make(); let rprops = reverse(props); collecting () for (prop in rprops) let name = prop.snap-phase; let symbol = as(, name); if (~element(seen, symbol, default: #f)) collect-first(prop); element(seen, symbol) := #t; end; end; end; end function; define function compilation-source-line-count (ld :: ) => (count :: ) collecting (as ) for (cr in compilation-context-records(ld)) let count = compilation-record-source-line-count(cr); // A false count means the source record was skipped entirely, // typically because its module: could not be found. if (count) collect(count) end; end; end; end function; /* // This only makes sense when comparing emulated with emulated or // native with native. In units of floops per second, where a floop // is the inner for loop. define constant *lum-floop-mark* = 0.225; define variable *platform-floop-mark* = #f; define function platform-floop-mark () *platform-floop-mark* | begin progress-line("Computing platform floop mark..."); let (secs, msecs) = timing () for (i from 0 below 50000) make(, size: 1024); end; end; let time = secs + msecs / 1000000.0; let rating = 1 / time; progress-line("Floop mark is: %=", rating); *platform-floop-mark* := rating end; end function; */ define function dump-timings-for (ld :: ) => () let props = uniquify-timing-properties(compilation-timing-properties(ld)); progress-line("Time taken per phase:"); progress-line (" Elapsed Time (secs) Amount Allocated (Mb)"); progress-line(""); collecting (total-time :: , total-space :: ) for (prop in props) collect-into(total-time, snap-time(prop)); collect-into(total-space, snap-space(prop)); end; let total-time = collected(total-time); let total-space = collected(total-space); local method table-line (name, percent-time, time, percent-space, space) progress-line(" %-25s - %3d%% %14s %3d%% %14s", name, percent-time, time, percent-space, space); end method; for (prop in props) let name = snap-phase(prop); let time = snap-time(prop); let time-string = snap-time-string(prop); let percentage-time = round((time / total-time) * 100); let space = snap-space(prop); let space-string = snap-space-string(prop); let percentage-space = round((space / total-space) * 100); table-line(name, percentage-time, time-string, percentage-space, space-string); end; progress-line(" --"); table-line("Sum", 100, float-amount-to-string(total-time), 100, float-amount-to-string(total-space)); let lines = compilation-source-line-count(ld); progress-line(" --"); let lpm = (lines / total-time) * 60.0; let bps = float-amount-to-string(total-space / total-time); progress-line(" Compiled %d lines at a rate of %d lines per minute, %s Mb per second.", lines, lpm, bps); let dood = instance?(ld, ) & library-description-dood(ld); let dood-size = if (dood) dood-size(dood) else 0 end; when (dood-size) progress-line(" --"); progress-line(" Database %d bytes.", dood-size); end when; /* let floop = platform-floop-mark(); let floop% = round((floop / *lum-floop-mark*) * 100.0); progress-line(" Normalized rate is %= lines per minute " "(machine rated at %=%% of lum).", lpm * *lum-floop-mark* / floop, floop%); */ end; retract-compilation-timings(ld); end function; // pretty print integers, right justified in supplied field length // and decorated with commas -- this should really live in a shared // IO library define function display-integer(number :: , field :: ) => (mangled-number :: ) let number-characters :: = "0123456789"; let result :: = make(, size: field); iterate process-integer (num :: = number, exponent :: = 1) let (quotient :: , remainder :: ) = truncate/(num, 10); let digit :: = number-characters[remainder]; if (quotient = 0) let index = field - (exponent + truncate/(exponent - 1, 3)); if (index < 0) error("field %d not big enough for integer %d", field, number); end; result[index] := digit; index + 1 else let index = process-integer(quotient, exponent + 1); if (modulo(exponent, 3) = 0) result[index] := ','; result[index + 1] := digit; index + 2 else result[index] := digit; index + 1 end; end if; end iterate; result; end function; define function print-gc-statistics (ld :: ) => () progress-line(""); progress-line("GC statistics for compilation of %s:", library-description-emit-name(ld)); progress-line (" Live Condemned Not Condemned"); progress-line(""); let stats :: = garbage-collection-stats(); for (i :: from 0 below stats.size by 3) progress-line("%s %s %s", display-integer(stats[i], 15), display-integer(stats[i + 1], 15), display-integer(stats[i + 2], 15)); end for; end function; //// Top level parsing. define constant $empty-compilation-record-vector = as(, #[]); // TODO: want to switch over to limited compilation-record vectors // but they must be consistently treated that way throughout compiler define function compute-source-compilation-records (ld :: , sr* :: ) => (new-cr* :: , // TODO: removed-cr* :: ) // TODO: let cr* = ld.library-description-compilation-records; if (empty?(sr*)) // force-parse case... values($empty-compilation-record-vector, cr*) elseif ((sr*.size == cr*.size) & every?(method(sr, cr) sr == cr.compilation-record-source-record end, sr*, cr*)) values(cr*, $empty-compilation-record-vector) else let table = ld.library-description-record-table; block () // Temporarily use sequence numbers as "removed" flags. for (cr in table) cr.compilation-record-sequence-number := #t; end; local method find-or-make-cr (sr) let cr = element(table, sr, default: #f); if (cr) if (cr.compilation-record-sequence-number == #f) error("Duplicate use of %s", sr); end; cr.compilation-record-sequence-number := #f; cr else table[sr] := make(, library: ld, source-record: sr); end if; end method; let new-cr* = map-as(, find-or-make-cr, sr*); let removed-cr* = #(); for (cr in table) if (cr.compilation-record-sequence-number) removed-cr* := pair(cr, removed-cr*); end; end for; values(// If nothing changed, reuse the original... if ((size(new-cr*) == size(cr*)) & every?(\==, new-cr*, cr*)) cr* else new-cr* end, removed-cr*); cleanup for (index from 0 below size(cr*)) cr*[index].compilation-record-sequence-number := index; end; end block; end if; end function; // This function must be called within a with-library-context if there is any // possibility of retracting. define sideways method install-library-description-sources (ld :: , sr* :: ) debug-out(#"Driver", "Computing changes in %s", ld.library-description-project); let (new-cr*, removed-cr*) = compute-source-compilation-records(ld, sr*); debug-out(#"Driver", if (new-cr* == ld.compilation-context-records) " -> No changes!\n" else " -> New-cr*: %s, old-cr*: %s, Removed-cr*: %s\n" end, new-cr*, ld.compilation-context-records, removed-cr*); unless (new-cr* == ld.compilation-context-records) detach-interactive-namespaces(ld); block (continue) block (retract) if (ld.library-description-stripped?) retract() end; if (empty?(sr*)) retract() end; // KLUDGE: if library doesn't have a definition, retract in order // to force reparsing of previously ignored records in case we // get a definition. (This would happen automatically if we handled // library redefinition, but we don't). if (~ld.library-description-defined?) retract() end; with-dependent-retraction dynamic-bind (*cross-module-access-abort* = retract) progress-line(" Retracting changed sources"); // Retract records we'll be removing do(retract-compilation-record, removed-cr*); // Retract any dependencies on changed sequence positions let n = size(new-cr*); for (i from 0 below n) let cr1 = new-cr*[i]; for (j from i + 1 below n, while: cr1.compilation-record-top-level-forms) let cr2 = new-cr*[j]; if (cr2.compilation-record-top-level-forms & (cr2.compilation-record-sequence-number < cr1.compilation-record-sequence-number)) retract-compilation-record-order(cr1, cr2); end; end for; end for; // Might be adding new source records, so defs no longer complete. ld.compiled-to-definitions? := #f; end dynamic-bind; end with-dependent-retraction; continue(); end block /* retract */; progress-line("Retracting library parsing"); retract-library-parsing(ld); end block /* continue */; ld.compilation-context-records := new-cr*; for (index from 0 below size(new-cr*)) new-cr*[index].compilation-record-sequence-number := index; end; end; // If compilation records computation and installation gets interrupted, // we may end up with some extra cr's in the compilation records table. // This is harmless and gets cleaned up at the next call. But that // is why we remove removed-cr*'s even if nothing has changed in the // sources.. let table = ld.library-description-record-table; for (cr in removed-cr*) remove-key!(table, cr.compilation-record-source-record); end; end method; define function ensure-library-definitions-installed (ld :: , #key library-only? = #f) debug-assert(~ld.compilation-definitions-inconsistent?, "Inconsistent definitions should have already been handled!"); unless (ld.compiled-to-definitions? | (library-only? & ld.library-description-defined?)) debug-out(#"driver", if (library-only?) "Parsing library definition for %s\n" else "Parsing unparsed definitions in %s\n" end, ld.library-description-project); debug-assert(~instance?(ld, ) | ld.interactive-namespaces-detached?, "Retracted defs with attached interactive namespaces!"); // TODO: This is using compiled-to-definitions? as a temp flag... Should // instead have a sequence associated with with-dependent-retraction, // so that stuff that gets retracted gets directly remembered for // reprocessing... block () until (ld.compiled-to-definitions? | (library-only? & ld.library-description-defined?)) ld.compiled-to-definitions? := #t; for (cr in ld.compilation-context-records, until: library-only? & ld.library-description-defined?) update-compilation-record-definitions(cr, library-only?: library-only?); end; end until; cleanup ld.compiled-to-definitions? := #f; end; unless (library-only? & ld.library-description-defined?) mark-definitions-installed(ld); note-definitions-updated(ld); debug-out(#"Driver", "Top-level %s parsing complete.\n", ld); // Some caches are not incremental, have to clear them on every change. retract-non-incremental-caches(ld); // Because we want to be able to compile lazily from definitions, // we force the installation of any models that can be reached // directly, rather than via name lookup, now so that they get // claimed appropriately. if (compiling-dylan-library?() & ld.library-description-defined?) install-dylan-boot-constants(ld) end if; end; end unless; end; // This is called at the end of parsing and installing definitions. define method mark-definitions-installed (ld :: ) if (*interactive-compilation-layer*) debug-assert(ld == *interactive-compilation-layer*); ld.compiled-to-definitions? := #t; else mark-project-definitions-installed(ld) end if; end method; define variable *compute-interfaces* = #f; define function mark-project-definitions-installed (ld :: ) ld.library-description-change-count := next-library-change-count(ld); if (*compute-interfaces*) let new-spec = library-interface-spec(ld.language-definition); unless (ld.library-description-interface-spec = new-spec) ld.library-description-interface-spec := new-spec; ld.library-description-interface-version := ld.library-description-change-count; end; end; // Reset since this is always relative to change-count anyway, and this // way we lessen the chance of overflow. ld.library-description-models-change-count := 0; debug-assert(every?(compilation-record-definitions-installed?, ld.library-description-compilation-records), "cr %s not installed!", choose(complement(compilation-record-definitions-installed?), ld.library-description-compilation-records)); ld.compiled-to-definitions? := #t; end function; // Must agree with same in boot.dylan define constant $library-build-count-wildcard = -1; define constant $library-build-count-wildcard-body-double = 17; define constant $library-build-count-only-wildcard = -2; define constant $library-build-count-only-wildcard-body-double = 31; define function next-library-change-count (ld :: ) => (count :: ) let (msecs, days) = current-timestamp(); let next-count = logxor(msecs, days); // avoid stumbling on wildcard if (next-count = $library-build-count-wildcard) $library-build-count-wildcard-body-double elseif (next-count = $library-build-count-only-wildcard) $library-build-count-only-wildcard-body-double else next-count end if; end function; // Some caches are not incremental, so have to clear them on every change.. define method retract-non-incremental-caches (ld :: ) end; define method retract-non-incremental-caches (ld :: ) remove-all-keys!(ld.library-type-cache); remove-all-keys!(ld.library-type-estimate-disjoint?-cache); remove-all-keys!(ld.library-type-estimate-cons-cache); remove-all-keys!(ld.library-type-estimate-dispatch-cache); retract-library-copiers(ld); // Clear out cache slots in imported bindings retract-library-imported-bindings(ld); next-method(); end method; define method retract-non-incremental-caches (ld :: ) remove-all-keys!(ld.library-description-dylan-value-cache); next-method(); end method; define function update-library-version (ld :: , mj, mn) debug-out(#"driver", "Checking major/minor version changes"); if (ld.library-description-major-version ~== mj | ld.library-description-minor-version ~== mn) // force database update with new version info. ld.compiled-to-definitions? := #f; ld.library-description-major-version := mj; ld.library-description-minor-version := mn; end; debug-out(#"driver", if (ld.library-description-major-version ~== mj | ld.library-description-minor-version ~== mn) " -> Changed, was: (%s, %s), is (%s, %s)\n" else " -> No change.\n" end, ld.library-description-major-version, ld.library-description-minor-version, mj, mn); end function; define method compute-library-definitions (ld :: ) with-dependent-retraction ensure-library-definitions-installed(ld, library-only?: #f); end with-dependent-retraction; end; // External entry point define method install-project-sources (ld :: , sr* :: , major-version, minor-version) with-program-conditions debug-out(#"driver", "Install sources %s for %s\n", sr*, ld.library-description-project); with-library-context (ld) with-stage-progress ("Updating sources for", $installing-stage) timing-compilation-phase ("Updating sources" of ld) ensure-definitions-consistent(ld, verify?: #f); update-library-version(ld, major-version, minor-version); install-library-description-sources(ld, sr*); if (ld.library-description-defined?) // Retract library definition if invalidated. verify-library-definition(ld); end; ensure-library-defined(ld); end timing-compilation-phase; end with-stage-progress; end with-library-context; end with-program-conditions; end method; // External entry point // It is the responsiblity of the caller to make sure all used libraries // have been parsed. define method parse-project-sources (ld :: ) let parsed? = #f; with-program-conditions debug-out(#"driver", "parse-project-sources for %s\n", ld); with-library-context (ld) ensure-definitions-consistent(ld); ensure-library-defined(ld); verify-used-libraries(ld); unless (ld.compiled-to-definitions?) with-stage-progress ("Updating definitions for", $parsing-stage) timing-compilation-phase ("Updating definitions" of ld) parsed? := #t; compute-library-definitions(ld); end; end; end; if (library-references-retracted-models?(ld)) progress-line("Retracting obsoleted models"); retract-library-compilation(ld) end; end; debug-out(#"driver", "DONE parse-project-sources for %s\n", ld); end with-program-conditions; parsed? end method; define function ensure-definitions-consistent (ld :: , #key verify? = #t) if (compilation-definitions-inconsistent?(ld)) progress-line("Retracting aborted parse"); retract-library-parsing(ld); elseif (verify?) verify-library-definition(ld); end; end function; define thread variable *demand-load-library-only?* = #t; define function ensure-library-defined (ld :: ) unless (ld.library-description-defined?) with-dependent-retraction debug-out(#"driver", "No library-definition in %s, parsing for it\n", ld.library-description-project); ensure-library-definitions-installed (ld, library-only?: *demand-load-library-only?*); debug-assert(ld.library-description-defined? | // maybe completely compiled, and still no def... ld.compiled-to-definitions?); debug-out(#"driver", "Library definition %s parsed\n", ld); end; end; end function; // HACK: need better solution like having a shared-symbols // table that can be consulted to lazily load mapped model properties define sideways method install-dylan-shared-symbols (ld :: ) with-library-context (ld) local method touch-symbol-properties (name) let defn = dylan-definition(name); let symbols = form-shared-symbols(defn); symbols end method; list(touch-symbol-properties(#"%shared-dylan-symbols"), touch-symbol-properties(#"%shared-streams-symbols")); end with-library-context; end method; define sideways method record-all-booted-model-properties (ld :: ) for (constant in booted-constant-definitions()) with-dependent ($compilation of constant) let binding = form-variable-binding(constant); debug-assert(binding, "NO BINDING FOR BOOTED FORM %s", constant); let (model-object, computed?) = untracked-binding-model-object-if-computed(binding); when (computed?) let properties = lookup-owned-model-properties-in(ld, model-object); record-booted-model-properties(ld, model-object, properties); end when; end with-dependent; end for; end method; define sideways method install-dylan-boot-constants (ld :: , #key force?) debug-assert(~*interactive-compilation-layer*); if (compiled-to-definitions?(ld)) with-library-context (ld) let started? = ld.compilation-from-definitions-started?; for (constant in booted-constant-definitions()) if (force?) form-models-installed?(constant) := #f; end if; with-dependent ($compilation of constant) maybe-compute-and-install-form-model-objects(constant); end; end; install-dylan-shared-symbols(ld); // By special dispensation, boot constants installation is not // considered as having started compilation... Otherwise we get an // infinite loop on retractions. ld.compilation-from-definitions-started? := started?; end; end; end; //// Top-level form generation. define function update-compilation-record-definitions (cr :: , #key library-only? = #f) let name = cr.compilation-record-source-record.source-record-name; source-record-progress-text("Parsing %s.dylan", name); debug-assert(~instance?(cr, ) | (*interactive-compilation-layer* & ~cr.compilation-record-definitions-installed? & ~cr.compilation-record-top-level-forms), "Bad setup for interactive cr update"); if (cr.compilation-record-definitions-installed?) debug-assert(if (cr.compilation-record-module) module-defined?(cr.compilation-record-module) else empty?(cr.compilation-record-top-level-forms) end, "Undefined module in installed cr?"); debug-out(#"internal", " %s unchanged.\n", cr); source-record-progress-report(); else if (cr.compilation-record-top-level-forms) let module = cr.compilation-record-module; unless (module & module-defined?(module)) // The module undefined case means module got deleted. // The module = #f case means module was undefined last time parsed // this record, so just keep trying - retracting it is practically // a noop (just remove the warning from last time), and all we really // do is lookup the module again. retract-compilation-record(cr); end; end; with-form-creation let sr = cr.compilation-record-source-record; if (cr.compilation-record-top-level-forms) debug-out(#"driver", " Reinstalling some forms in: %s.\n", sr); else let name = cr.compilation-record-source-record.source-record-name; progress-line(" Reading and installing: %s.dylan", name); compute-source-record-top-level-forms(cr); source-record-progress-report(); end; unless (library-only? & current-library-defined?()) let install-finished? = #f; block () // TODO: Set this first so form processing can clear it to request // reprocessing. Find some other way to do this... cr.compilation-record-definitions-installed? := #t; // This may add derived forms. install-top-level-forms(cr.compilation-record-top-level-forms); install-finished? := #t; cleanup unless (install-finished?) cr.compilation-record-definitions-installed? := #f; end; end block; // TODO: put derived forms in a separate slot so can retract them // (when explicit def is found later) without copying the whole // top-level-forms vector. See retract-form-top-level-processing. cr.compilation-record-top-level-forms := as(, cr.compilation-record-top-level-forms); end; end with-form-creation; end; end function update-compilation-record-definitions; define method compute-source-record-top-level-forms (cr :: ) with-dependent ($top-level-processing of cr) let module = lookup-compilation-record-module(cr, warn?: #f); compilation-record-module(cr) := module; if (~module) // Default to dylan-user while we look for namespace definitions compilation-record-module(cr) := dylan-user-module(); elseif (compiling-dylan-library?() & instance?(module, )) // Kludge: the dylan-user module in the dylan library is useless, // since it doesn't use 'dylan'. Normally, the dylan library has no // files in the dylan-user module, so it doesn't matter. This kludge // is to support whole-program compilation wherein we artificially // introduce random source records into the dylan library. let internal = lookup-module(#"internal", default: #f); when (internal) compilation-record-module(cr) := internal end; end; // Demand load everything if we may need the modules in order to parse // this file. dynamic-bind (*demand-load-library-only?* = if (module) #t else #f end) let sr = compilation-record-source-record(cr); let cr-name = source-record-module-name(sr); with-input-from-source-record (stream = sr) local method read (state, record-forms) let (fragment, new-state) = read-top-level-fragment(stream, cr, state); let fragment-forms = if (fragment) top-level-convert-forms(cr, fragment) else #() end; let record-forms = concatenate!(record-forms, fragment-forms); // If we've come across a namespace definition and we're in // an undefined module context, switch to the module if it's // now become defined. if (~module & ~empty?(fragment-forms) & instance? (first(fragment-forms), )) let new-module = lookup-module(cr-name, default: #f); if (new-module) compilation-record-module(cr) := new-module; module := new-module; end; // Continue reading. read(new-state, record-forms); elseif (~module) // The first things we see in an undefined module context // must all be namespace defining forms. Empty the source // record, and return. compilation-record-module(cr) := #f; // Force a warning. lookup-compilation-record-module(cr); values(#f, #()); elseif (fragment) // Continue reading. read(new-state, record-forms); else // Stop. values(new-state, record-forms); end; end method; // TODO: The db is inconsistent here for two reasons: the main // is that library/macro definitions get installed as they are // parsed, before they get recorded in cr-top-level-forms, so // there is no way to retract them! Second is that parsing // introduces // dependencies of the cr on syntax of bindings, but we assume // that cr's with no cr-top-level-forms have no dependencies, so // if parsing is aborted those dependencies never get retracted. // TRT is to (1) update cr-top-level-forms as parse them, before // install and (2) add a cr-inconsistent flag and retract cr if // aborted, instead of invalidating the whole library. with-inconsistent-definitions (current-library-description()) let (final-state, record-forms) = read(#f, #()); compilation-record-top-level-forms(cr) := record-forms; compilation-record-source-line-count(cr) := (final-state & source-lines-read(final-state)) | 0; end with-inconsistent-definitions; end with-input-from-source-record; end dynamic-bind; end with-dependent; end method; define sideways method install-top-level-forms (forms :: ) => () for (form in forms) unless (form-top-level-installed?(form)) // TODO: make this finer-grained. with-inconsistent-definitions (current-library-description()) install-top-level-form(form); end with-inconsistent-definitions; end; end; end method;