## compile-in-dependency-order-g.pkg -- makelib dependency graph walks.
# Compiled by:
#
src/app/makelib/makelib.sublib###########################################
# See overview comments at bottom of file.
###########################################
# RUNTIME INVOCATION CONTEXT
#
# Our main two entrypoints are
#
# compile_near_tome_after_dependencies
# make_dependency_order_compile_fns
#
# Both are invoked by both bootstrap and standard compiler:
#
#
src/app/makelib/mythryl-compiler-compiler/mythryl-compiler-compiler-g.pkg#
src/app/makelib/main/makelib-g.pkg#
#
#
# KNOWN GOTCHAS
#
# This generic currently maintains a
# lot of persistent state, which must be explicitly
# reset by our client before each new compile, and
# which (for now) precludes using multiple threads
# to run multiple compiles in parallel within a
# single process. XXX BUGGO FIXME
### "In software as elsewhere,
### good engineering is whatever
### gets the job done without
### calling attention to itself."
###
### -- Citadel 2.21 release notes, 1982
stipulate
#
package acf = anormcode_form; # anormcode_form is from
src/lib/compiler/back/top/anormcode/anormcode-form.pkg package ad = anchor_dictionary; # anchor_dictionary is from
src/app/makelib/paths/anchor-dictionary.pkg package bio = data_file__premicrothread; # data_file__premicrothread is from
src/lib/std/src/posix/data-file--premicrothread.pkg package bsx = browse_symbolmapstack; # browse_symbolmapstack is from
src/lib/compiler/front/typer-stuff/symbolmapstack/browse.pkg package cf = compiledfile; # compiledfile is from
src/lib/compiler/execution/compiledfile/compiledfile.pkg package coc = global_controls::compiler; # global_controls is from
src/lib/compiler/toplevel/main/global-controls.pkg package cor = core_hack; # core_hack is from
src/app/makelib/compile/core-hack.pkg package cps = compiler_state; # compiler_state is from
src/lib/compiler/toplevel/interact/compiler-state.pkg package cs = code_segment; # code_segment is from
src/lib/compiler/execution/code-segments/code-segment.pkg package cst = compile_statistics; # compile_statistics is from
src/lib/compiler/front/basics/stats/compile-statistics.pkg package ctl = global_controls; # global_controls is from
src/lib/compiler/toplevel/main/global-controls.pkg package cx = compilation_exception; # compilation_exception is from
src/lib/compiler/front/basics/map/compilation-exception.pkg package ds = deep_syntax; # deep_syntax is from
src/lib/compiler/front/typer-stuff/deep-syntax/deep-syntax.pkg package err = error_message; # error_message is from
src/lib/compiler/front/basics/errormsg/error-message.pkg package fil = file__premicrothread; # file__premicrothread is from
src/lib/std/src/posix/file--premicrothread.pkg package idg = indegrees_of_library_dependency_graph; # indegrees_of_library_dependency_graph is from
src/app/makelib/depend/indegrees-of-library-dependency-graph.pkg package im = inlining_mapstack; # inlining_mapstack is from
src/lib/compiler/toplevel/compiler-state/inlining-mapstack.pkg package imt = import_tree; # import_tree is from
src/lib/compiler/execution/main/import-tree.pkg package is = interprocess_signals; # interprocess_signals is from
src/lib/std/src/nj/interprocess-signals.pkg package lg = inter_library_dependency_graph; # inter_library_dependency_graph is from
src/app/makelib/depend/inter-library-dependency-graph.pkg package mcv = mythryl_compiler_version; # mythryl_compiler_version is from
src/lib/core/internal/mythryl-compiler-version.pkg package mld = makelib_defaults; # makelib_defaults is from
src/app/makelib/stuff/makelib-defaults.pkg package mtq = makelib_thread_boss; # makelib_thread_boss is from
src/app/makelib/concurrency/makelib-thread-boss.pkg package mmz = memoize; # memoize is from
src/lib/std/memoize.pkg package ms = makelib_state; # makelib_state is from
src/app/makelib/main/makelib-state.pkg package myp = mythryl_parser; # mythryl_parser is from
src/lib/compiler/front/parser/main/mythryl-parser.pkg package nor = null_or; # null_or is from
src/lib/std/src/null-or.pkg package ns = number_string; # number_string is from
src/lib/std/src/number-string.pkg package pcs = per_compile_stuff; # per_compile_stuff is from
src/lib/compiler/front/typer-stuff/main/per-compile-stuff.pkg package ph = picklehash; # picklehash is from
src/lib/compiler/front/basics/map/picklehash.pkg package phs = picklehash_set; # picklehash_set is from
src/app/makelib/stuff/picklehash-set.pkg package pkj = pickler_junk; # pickler_junk is from
src/lib/compiler/front/semantic/pickle/pickler-junk.pkg package pp = standard_prettyprinter; # standard_prettyprinter is from
src/lib/prettyprint/big/src/standard-prettyprinter.pkg package prs = prettyprint_raw_syntax; # prettyprint_raw_syntax is from
src/lib/compiler/front/typer/print/prettyprint-raw-syntax.pkg package psx = posixlib; # posixlib is from
src/lib/std/src/psx/posixlib.pkg package rm = rehash_module; # rehash_module is from
src/lib/compiler/front/semantic/pickle/rehash-module.pkg package raw = raw_syntax; # raw_syntax is from
src/lib/compiler/front/parser/raw-syntax/raw-syntax.pkg package s2m = collect_all_modtrees_in_symbolmapstack; # collect_all_modtrees_in_symbolmapstack is from
src/lib/compiler/front/typer-stuff/symbolmapstack/collect-all-modtrees-in-symbolmapstack.pkg package sci = sourcecode_info; # sourcecode_info is from
src/lib/compiler/front/basics/source/sourcecode-info.pkg package sg = intra_library_dependency_graph; # intra_library_dependency_graph is from
src/app/makelib/depend/intra-library-dependency-graph.pkg package spn = spawn__premicrothread; # spawn__premicrothread is from
src/lib/std/src/posix/spawn--premicrothread.pkg package sps = source_path_set; # source_path_set is from
src/app/makelib/paths/source-path-set.pkg package str = string; # string is from
src/lib/std/string.pkg package sym = symbol_map; # symbol_map is from
src/app/makelib/stuff/symbol-map.pkg package sys = symbol_set; # symbol_set is from
src/app/makelib/stuff/symbol-set.pkg package syx = symbolmapstack; # symbolmapstack is from
src/lib/compiler/front/typer-stuff/symbolmapstack/symbolmapstack.pkg package tlt = thawedlib_tome; # thawedlib_tome is from
src/app/makelib/compilable/thawedlib-tome.pkg package ts = timestamp; # timestamp is from
src/app/makelib/paths/timestamp.pkg package ttm = thawedlib_tome_map; # thawedlib_tome_map is from
src/app/makelib/compilable/thawedlib-tome-map.pkg package ucs = unparse_code_and_data_segments; # unparse_code_and_data_segments is from
src/lib/compiler/execution/code-segments/unparse-code-and-data-segments.pkg package upj = unpickler_junk; # unpickler_junk is from
src/lib/compiler/front/semantic/pickle/unpickler-junk.pkg package urs = unparse_raw_syntax; # unparse_raw_syntax is from
src/lib/compiler/front/typer/print/unparse-raw-syntax.pkg package vub = vector_of_one_byte_unts; # vector_of_one_byte_unts is from
src/lib/std/src/vector-of-one-byte-unts.pkg package wnx = winix__premicrothread; # winix__premicrothread is from
src/lib/std/winix--premicrothread.pkg package xns = exceptions; # exceptions is from
src/lib/std/exceptions.pkg #
Pp = pp::Pp;
# Per-package table of exported symbols (functions, types...)
# and of exported inlinable functions:
#
Tome_Exports
=
{ symbolmapstack: syx::Symbolmapstack,
inlining_mapstack: im::Picklehash_To_Anormcode_Mapstack
};
Fat_Tomes_Compile_Result # Used only internally, for clarity and brevity.
=
{ tome_exports_thunk: Void -> Tome_Exports,
#
picklehashes: phs::Set
};
herein
# Mythryl_Compiler is from
src/lib/compiler/toplevel/compiler/mythryl-compiler.api # Freezefile_Roster is from
src/app/makelib/freezefile/freezefile-roster-g.pkg # freezefile_roster_g is from
src/app/makelib/freezefile/freezefile-roster-g.pkg # file__premicrothread is from
src/lib/std/src/posix/file--premicrothread.pkg # This generic is invoked in two places:
#
#
src/app/makelib/mythryl-compiler-compiler/mythryl-compiler-compiler-g.pkg #
src/app/makelib/main/makelib-g.pkg #
# which is to say, in the definitions of both the
# bootstrap and production compilers.
#
# Compile-time arguments:
#
#
# read_eval_print_from_stream:
# #
# When we're invoked by
#
src/app/makelib/main/makelib-g.pkg # this is a simple wrapper for the global
# read_eval_print_from_stream_hook
# there, which is initialized to
# read_eval_print_from_stream
# which comes ultimately from
#
src/lib/compiler/toplevel/interact/read-eval-print-loop-g.pkg #
# When we're invoked by
#
src/app/makelib/mythryl-compiler-compiler/mythryl-compiler-compiler-g.pkg # we get its argument
# read_eval_print_from_stream_hook
# supplied to it by
#
src/lib/core/mythryl-compiler-compiler/mythryl-compiler-compiler-for-intel32-posix.pkg # (and kin) as
# mythryl_compiler::interact::read_eval_print_from_stream;
#
# I think that comes out the same, give or take
# indirection through the hook ref.
#
#
#
generic package compile_in_dependency_order_g (
# =============================
# # mythryl_compiler_for_intel32_posix is from
src/lib/compiler/toplevel/compiler/mythryl-compiler-for-intel32-posix.pkg # # mythryl_compiler_for_intel32_win32 is from
src/lib/compiler/toplevel/compiler/mythryl-compiler-for-intel32-win32.pkg # # mythryl_compiler_for_pwrpc32 is from
src/lib/compiler/toplevel/compiler/mythryl-compiler-for-pwrpc32.pkg # # mythryl_compiler_for_sparc32 is from
src/lib/compiler/toplevel/compiler/mythryl-compiler-for-sparc32.pkg # # Mythryl_Compiler is from
src/lib/compiler/toplevel/compiler/mythryl-compiler.api #
package myc: Mythryl_Compiler; # "myc" == "mythryl_compiler".
# # We use this to compile a "raw::Declaration" down to a .compiled file.
# # (This is a generic argument because it varies by target architecture,
# # but I suspect it should be a runtime OOP argument instead. Ditto ffr. -- 2011-09-19 CrT)
#
package ffr: Freezefile_Roster; # "ffr" == "freezefile_roster".
#
read_eval_print_from_stream: fil::Input_Stream -> Void;
)
:
Compile_In_Dependency_Order # Compile_In_Dependency_Order is from
src/app/makelib/compile/compile-in-dependency-order.api {
stipulate
package r2x = myc::translate_raw_syntax_to_execode;
herein
Thawedlib_Tome_Watcher
=
ms::Makelib_State
->
tlt::Thawedlib_Tome
->
Void;
Compiledfile_Sink
=
{ key: tlt::Thawedlib_Tome,
#
value: { compiledfile: cf::Compiledfile,
component_bytesizes: cf::Component_Bytesizes
}
}
->
Void;
#######################################################
# exports_picklehash_cache__local
#
# Mythryl package sealing restricts
# the visible exports of a package to
# just those present in a given API.
#
# To implement this, we frequently wind up taking
# a symbol table (representing the exports of a
# compiled .pkg file, or more precisely, of one
# package foo { ... };
# clause) and masking it to (logically) contain
# only the symbols in a given 'exports_mask' symbol set
# (representing the symbols from a given compiled
# api Foo { ... };
# clause).
#
# Doing so changes the symbol table's picklehash,
# forcing us to recompute this, which is a
# moderately expensive operation.
#
# To avoid repeating such computations pointlessly,
# we keep a cache of their results and re-use
# rather than re-computing them were possible:
########################################################
#
package psm # "psm" == "picklehash + symbolset map"
=
map_g ( # map_g def in
src/app/makelib/stuff/map-g.pkg #
package {
#
Key = (ph::Picklehash, sys::Set);
#
fun compare ((u, f), (u', f'))
=
case (ph::compare (u, u'))
#
EQUAL => sys::compare (f, f');
unequal => unequal;
esac;
}
);
exports_picklehash_cache__local # More icky thread-hostile global mutable state :( XXX BUGGO FIXME.
=
REF (psm::empty: psm::Map( ph::Picklehash ));
########################################################
# symbol_and_inlining_mapstacks_etc_map
#
# 'symbol_and_inlining_mapstacks_etc_map'
# is our core "state-of-the-compilation"
# datastructure. It maps
# Thawedlib_Tome
# records representing what we knew about a sourcefile
# before compiling it to
# Tome_Exports_Etc
# records representing what we learned about that
# a sourcefile by compiling it.
#
Tome_Exports_Etc
=
{ symbol_and_inlining_mapstacks: sg::Tome_Compile_Result, # See
src/app/makelib/depend/intra-library-dependency-graph.pkg compiledfile_timestamp: ts::Timestamp,
picklehash_set: phs::Set
};
#
# compiledfile_timestamp:
# If this is older than the source file
# last-modification time, then the compiledfile
# is outdated and we need to recompile.
#
# picklehash_set:
# This contains hashes of all the compiledfile
# components ("pickles"). If recompiling the
# sourcecode yields an identical picklehash set
# then the compiled code is identical (to extremely
# high probability!) and we do not need to recompile
# tomes dependent on this one.
#
symbol_and_inlining_mapstacks_etc_map__local # More icky thread-hostile global mutable state :-/ XXX BUGGO FIXME.
=
REF (ttm::empty: ttm::Map( Tome_Exports_Etc ));
#
fun clear_state ()
=
{ if (mld::debug.get ()) printf "compile-dependency-graph-walk-g: clear_state/TOP [makelib::debug]\n"; fi;
#
symbol_and_inlining_mapstacks_etc_map__local := ttm::empty;
#
exports_picklehash_cache__local := psm::empty;
};
# Tome_Exports_Etc def is above.
# exports_picklehash_cache__local is defined above.
#
fun symbol_and_inlining_mapstacks_are_current
(
symbol_and_inlining_mapstacks_etc: Tome_Exports_Etc,
provided_picklehashes,
thawedlib_tome
)
=
not (
ts::needs_update
{
source => tlt::sourcefile_timestamp_of thawedlib_tome, # Last-modified time of our foo.api / foo.pkg sourcefile.
target => symbol_and_inlining_mapstacks_etc.compiledfile_timestamp # Creation-time timestamp for corresponding compiledfile.
}
)
and
phs::equal (
provided_picklehashes,
symbol_and_inlining_mapstacks_etc.picklehash_set
);
#
fun make_symbol_and_inlining_mapstacks_etc
(
compiledfile: cf::Compiledfile,
compiledfile_timestamp: ts::Timestamp,
context_symbolmapstack: syx::Symbolmapstack
)
=
{ symbol_and_inlining_mapstacks => symbol_and_inlining_mapstacks: sg::Tome_Compile_Result,
compiledfile_timestamp => compiledfile_timestamp: ts::Timestamp,
picklehash_set => picklehash_set: phs::Set
}
where
fun symbolmapstack_thunk ()
=
{ # Here we stash the implicit parameters
#
# compiledfile
# context_symbolmapstack
#
# to produce a symbolmapstack_thunk which can later
# generate compilefile's symbol table when/if required:
modmap0 = ffr::get (); # Global roster of freezefiles.
context_stampmapstack
=
s2m::collect_all_modtrees_in_symbolmapstack' (context_symbolmapstack, modmap0);
######################
(cf::pickle_of_symbolmapstack compiledfile)
-> ############
{ picklehash, pickle };
upj::unpickle_symbolmapstack # This will fill in modtree entries per
src/lib/compiler/front/typer-stuff/modules/module-level-declarations.pkg (\\ _ = context_stampmapstack)
(picklehash, pickle);
};
#
fun inlining_mapstack_thunk ()
=
{ # Much as above, here we stash the implicit parameter
#
# compiledfile
#
# to produce an inlining_mapstack_thunk which can later
# generate compilefile's inlining table when/if required:
(cf::pickle_of_inlinables compiledfile)
-> ############
{ pickle, ... };
inlinables_list
=
if (vub::length pickle != 0) upj::unpickle_highcode pickle;
else NULL;
fi;
im::make_inlining_mapstack
(
cf::hash_of_pickled_exports compiledfile,
inlinables_list
);
};
my symbol_and_inlining_mapstacks: sg::Tome_Compile_Result
=
{ symbolmapstack_thunk => mmz::memoize symbolmapstack_thunk,
inlining_mapstack_thunk => mmz::memoize inlining_mapstack_thunk,
#
symbolmapstack_picklehash => cf::hash_of_symbolmapstack_pickle compiledfile,
inlining_mapstack_picklehash => cf::hash_of_pickled_inlinables compiledfile,
compiledfile_version => cf::get_compiledfile_version compiledfile
};
my picklehash_set: phs::Set
=
phs::add_list (phs::empty, cf::picklehash_list compiledfile);
end; # fun make_symbol_and_inlining_mapstacks_etc
#
fun picklehash_set (p1, p2)
=
phs::add (phs::singleton p1, p2);
######################################################
# A typical source file S makes direct
# and indirect reference to types, values
# and functions from many other sourcefiles,
# collectively termed its "dependencies".
#
# Before S can be compiled, all of its
# dependencies must be compiled, and the
# resulting symbol and inlining tables
# combined to produce the environment
# in which S can be compiled.
#
# Also, if a given dependency D is sealed
# against some API, we must filter D's
# exports to reveal only those symbols
# permitted by the API before combining
# with the exports from other dependencies.
#
# For efficiency, symbol and inlining tables
# are exported lazily as thunks which construct
# those tables only if and when actually needed.
#
# To avoid wasting time and space by evaluating
# any such thunk more than once, we also memo-ize
# these thunks as we combine them to produce
# the overall compilation environment for S.
#
# When we filter a symbol table we change its
# picklehash and must compute a new one. This
# is an expensive operation, likely to be
# repeated during a make, so we keep a cache,
# exports_picklehash_cache__local, and when
# possible re-use rather than re-compute.
#
# Below are two routines which handle this
# processing of dependency exports, one for the
# filtered case and one for the unfiltered:
#
# fun memoize_unfiltered_dependency_exports
# fun memoize___filtered_dependency_exports
#
# One of the two will be called for each
# dependency.
######################################################
#
fun memoize_unfiltered_dependency_exports (symbol_and_inlining_mapstacks: sg::Tome_Compile_Result)
=
{
symbol_and_inlining_mapstacks
->
{ symbolmapstack_thunk,
inlining_mapstack_thunk,
#
symbolmapstack_picklehash,
inlining_mapstack_picklehash,
#
compiledfile_version
};
symbolmapstack_thunk'
=
mmz::memoize symbolmapstack_thunk;
{ tome_exports_thunk
=>
\\ ()
=
{ symbolmapstack => symbolmapstack_thunk' (),
inlining_mapstack => inlining_mapstack_thunk ()
},
picklehashes
=>
picklehash_set
( symbolmapstack_picklehash,
inlining_mapstack_picklehash
)
};
};
#
fun memoize___filtered_dependency_exports (symbol_and_inlining_mapstacks, symbol_set)
=
{ fun required_filtering symbol_set symbolmapstack
=
# Even if a the exports from a dependency
# are sealed against an API, it may be that
# explicit filtering of the exports is not
# needed, because all exported symbols are
# allowed visibility by the API.
#
# Here we check for this case, so as to avoid
# wasted explicit filtering, returning NULL
# if no filtering is required, and otherwise
# THE filtering symbol set:
#
{
domain
=
sys::add_list
(
sys::empty,
bsx::catalog symbolmapstack
);
symbol_set'
=
sys::intersection
(
symbol_set,
domain
);
sys::equal (domain, symbol_set')
##
?? NULL
:: THE symbol_set';
};
symbol_and_inlining_mapstacks # unpack exports from compiled dependency.
->
{ symbolmapstack_thunk,
inlining_mapstack_thunk,
#
symbolmapstack_picklehash,
inlining_mapstack_picklehash,
#
compiledfile_version
};
# We cannot filter a symbol table
# (or even decide not to filter it)
# without constructing it explicitly,
# so at this point we must force the
# symbol table thunk:
#
symbolmapstack
=
symbolmapstack_thunk ();
# If in fact no filtering is required, we
# can essentially revert to the unfiltered
# case (above):
#
case (required_filtering symbol_set symbolmapstack)
#
NULL => # No exported symbol table filtering needed.
{ picklehashes
=>
picklehash_set
( symbolmapstack_picklehash,
inlining_mapstack_picklehash
),
tome_exports_thunk
=>
\\ ()
=
{ symbolmapstack,
inlining_mapstack => inlining_mapstack_thunk ()
}
};
THE symbol_set # Exported symbol table must be filtered per API.
=>
{ symbolmapstack' # Construct filtered version of exports symbol table.
=
syx::filter
( symbolmapstack,
sys::vals_list symbol_set
);
# If an appropriate filtered symbol table picklehash
# is already in our cache we can just re-use it,
# otherwise we must compute the new one from scratch:
#
key = (symbolmapstack_picklehash, symbol_set); # key for searching the cache.
#
symbolmapstack_picklehash'
=
case (psm::get (*exports_picklehash_cache__local, key))
#
# Found a cached exports record, just return it:
#
THE symbolmapstack_picklehash'
=> symbolmapstack_picklehash';
NULL =>
# No cached exports record, construct one:
#
{ # Filtering a symbol table changes
# its hash, so compute the new one:
#
symbolmapstack_picklehash'
=
rm::rehash_module
{
symbolmapstack => symbolmapstack',
original_picklehash => symbolmapstack_picklehash,
compiledfile_version
};
# exports_picklehash_cache__local is defined above
# Enter new exports picklehash into our cache:
#
exports_picklehash_cache__local
:=
psm::set (
*exports_picklehash_cache__local,
key,
symbolmapstack_picklehash'
);
symbolmapstack_picklehash';
};
esac;
# Construct
#
{ picklehashes
=>
picklehash_set (symbolmapstack_picklehash', inlining_mapstack_picklehash),
tome_exports_thunk
=>
\\ ()
=
{ symbolmapstack => symbolmapstack',
inlining_mapstack => inlining_mapstack_thunk ()
}
};
};
esac;
}; # fun memoize___filtered_dependency_exports
#
fun symbol_and_inlining_mapstacks_atop
(
{ symbolmapstack => symbolmapstack, inlining_mapstack => inlining_mapstack },
{ symbolmapstack => symbolmapstack', inlining_mapstack => inlining_mapstack' }
)
=
# Combine two symbol+inlining tablepairs,
# with the first pair logically atop the
# second (i.e., searched first):
#
{ symbolmapstack
=>
syx::consolidate_lazy (
syx::atop
(symbolmapstack, symbolmapstack')
),
inlining_mapstack
=>
im::atop
(inlining_mapstack, inlining_mapstack') # "Let's not do stale picklehashes here..."
};
empty_fat_tomes_compile_result
=
{ picklehashes
=>
phs::empty,
tome_exports_thunk
=>
\\ ()
=
{ symbolmapstack => syx::empty,
inlining_mapstack => im::empty
}
};
#
fun layer ( { tome_exports_thunk => sait_thunk, picklehashes => hashes },
{ tome_exports_thunk => sait_thunk', picklehashes => hashes' }
)
=
# Combine two sets of dependency exports.
#
# This is always an assymetric operation
# in which one shadows the other in case
# of conflicting symbol definitions.
#
# As usual, we do things lazily to avoid
# explicitly constructing the result
# unless or until provably necessary:
#
{ tome_exports_thunk
=>
{. symbol_and_inlining_mapstacks_atop (
sait_thunk (),
sait_thunk' ()
);
},
picklehashes
=>
phs::union (hashes, hashes')
};
exception ABORT;
#
# "I would rather not use an exception here, but short of
# a better implementation of concurrency I see no choice."
# -- Matthias Blume
#
# The problem is that at each node we sequentially wait for the
# children nodes. But the scheduler might (and probably will)
# let a child run that we are not currently waiting for, so an
# error there will not result in "wait" returning immediately
# as it should for clean error recovery.
# Using the exception avoids having to implement a
# "wait for any child -- whichever finishes first" kind of call:
#
fun wait_for_thread_to_finish_then_return_result_running_at_priority
#
(makelib_state: ms::Makelib_State)
#
priority
#
(compile_thread, THE symbol_and_inlining_mapstacks) # makelib_thread is from
src/app/makelib/concurrency/makelib-thread-boss.pkg =>
#
# We're given a symbol-plus-inlining-mapstack pair,
# plus a thread which represents a compile-in-progress,
# which will return another such tablepair when finished.
#
# Wait for the compile to complete, then
# return the combination of the two tablepairs:
#
case (mtq::wait_for_thread_to_finish_then_return_result_running_at_priority
#
makelib_state.makelib_session.makelib_thread_boss
priority
compile_thread
)
#
THE symbol_and_inlining_mapstacks' # Success, return combination of the two symbol tables.
=>
THE (layer (symbol_and_inlining_mapstacks',
symbol_and_inlining_mapstacks
) );
NULL => NULL; # Compile returned NULL, so we do too.
esac;
wait_for_thread_to_finish_then_return_result_running_at_priority makelib_state priority (compile_thread, NULL) # Ok, actually we were NOT given input symbol-plus-inlining tableplair.
=>
{ mtq::wait_for_thread_to_finish_then_return_result_running_at_priority
#
makelib_state.makelib_session.makelib_thread_boss
priority
compile_thread; # Wait for the compile to finish.
#
NULL; # NULL input, so NULL output.
};
end;
#
fun make_tome_compilers
{
maybe_drop_thawedlib_tome_from_linker_map, # A hook letting us notify the linker when we re/compile a file -- a dummy or
# # drop_thawedlib_tome_from_linker_map() from
src/app/makelib/compile/link-in-dependency-order-g.pkg #
set__compiledfile__for__thawedlib_tome, # A dummy or else compiledfile_cache::set__compiledfile__for__thawedlib_tome, which caches a copy in ram.
#
compile_priority_of_thawedlib_tome # Prioritizes a sourcefile compile by number of files depending on it. Defined below as fun compile_priority_of_thawedlib_tome
}
=
{ compile_tome_tin_after_dependencies => compile_tome_tin_after_dependencies: ms::Makelib_State -> sg::Tome_Tin -> Null_Or( sg::Tome_Compile_Result ),
compile_fat_tome_after_dependencies => compile_fat_tome_after_dependencies: ms::Makelib_State -> lg::Fat_Tome -> Null_Or( Fat_Tomes_Compile_Result )
}
where
# We have two levels of compile-dependency graphs,
# one which records which complete libraries have
# compile dependencies on which other complete
# libraries, and then one per library recording
# which individual sourcefiles have compile
# dependencies upon other individual sourcefiles.
#
# Here we walk an intra-library individual-sourcefile
# level dependency graph compiling sourcefiles in
# post-order, so that each sourcefile is compiled
# only after all the libraries it needs have been
# compiled (thus making available the relevant type
# declarations etc):
compiles_started
=
REF ttm::empty;
#
# We use 'compiles_started' to keep track
# of which .compiled files we've already compiled
# (or are in the process of compiling).
#
# We use thawedlib_tome records as keys, to
# represent the individual .compiled files.
#
# The values are memoized fates
# representing compiles already fired off.
######################################################################################33
# To process the mutually recursive
# compiledfile dependency-graph
# sumtypes defined in
#
#
src/app/makelib/depend/intra-library-dependency-graph.pkg #
# we here define a matching set of
# mutually recursive functions,
# one per type.
#
fun compile_masked_tome_after_dependencies
#
(makelib_state: ms::Makelib_State)
#
({ exports_mask, tome_tin }: sg::Masked_Tome)
=
# The only thing distinguishing a far tome
# (.api/.pkg file in another library) from a
# near tome (.api/.pkg file in current library)
# is the export_mask symbol-set, so the only
# work we can't delegate here is applying
# that symbol-set to the result:
#
case ( compile_tome_tin_after_dependencies makelib_state tome_tin,
exports_mask
)
#
(THE symbol_and_inlining_mapstacks, THE symbol_set) => THE (memoize___filtered_dependency_exports (symbol_and_inlining_mapstacks, symbol_set));
(THE symbol_and_inlining_mapstacks, NULL ) => THE (memoize_unfiltered_dependency_exports (symbol_and_inlining_mapstacks ));
(NULL, _ ) => NULL;
esac
# fun compile_tome_tin_after_dependencies:
#
# Since 'tome_tin' is a trivial wrapper around
# the two possibilities of
# TOME_IN_FROZENLIB # A this.pkg.compiled file which is stored in a foo.lib.frozen freezefile.
# TOME_IN_THAWEDLIB # A this.pkg.compiled file which is NOT stored in a foo.lib.frozen freezefile.
# our work here is just trivially delegating
# as appropriate:
#
also
fun compile_tome_tin_after_dependencies makelib_state (sg::TOME_IN_THAWEDLIB thawedlib_tome)
=> compile_thawedlib_tome_tin makelib_state thawedlib_tome; # Delegate the compile.
compile_tome_tin_after_dependencies makelib_state (sg::TOME_IN_FROZENLIB r) # We never recompile anything in a frozen
=> # library so we have nothing to do here.
THE r.symbol_and_inlining_mapstacks;
end
also
fun compile_thawedlib_tome_tin
#
makelib_state
#
(sg::THAWEDLIB_TOME_TIN tin_to_compile) # 'tin_to_compile' is what we're compiling: { thawedlib_tome, near_imports, far_imports }
=
# Here's where the buck stops:
# compiling a thawedilb tome in the
# current library.
#
{
timestamp_of_youngest_sourcefile_in_library # We compute this value in this file; it (only) gets used in
src/app/makelib/main/makelib-g.pkg =
makelib_state.timestamp_of_youngest_sourcefile_in_library;
compiledfile_name # "foo.pkg.compiled" or such -- name of diskfile in which to save compile result.
=
tlt::make_compiledfile_name tin_to_compile.thawedlib_tome;
temporary_compiledfile_name # To minimize risk of leaving mangled .compiled files on disk, we write to a temporary
= # filename at first, and rename to final filename only when file is complete.
sprintf "%s.%d.tmp"
#
compiledfile_name
#
(wnx::process::get_process_id ());
# Start a compile running for this sourcefile
# unless we have already done so:
#
case (ttm::get (*compiles_started, tin_to_compile.thawedlib_tome))
#
THE compile_dependencies_then_sourcefile_thread # We already started a compile of this sourcefile.
=>
# Wait for existing file to complete,
# then return its result:
#
nor::map
#
.symbol_and_inlining_mapstacks
#
(mtq::wait_for_thread_to_finish_then_return_result
#
makelib_state.makelib_session.makelib_thread_boss
#
compile_dependencies_then_sourcefile_thread
);
NULL => # We have not run a compile for this sourcefile.
{ # Fire off an asynchronous compile:
#
compile_dependencies_then_sourcefile_thread
=
mtq::make_makelib_thread
#
makelib_state.makelib_session.makelib_thread_boss
#
{. compile_dependencies_then_sourcefile () # <===== Here's the beef in the burger.
then
tlt::forget_raw_declaration_and_sourcecode_info
#
tin_to_compile.thawedlib_tome;
#
# "We have not processed this file before,
# so we should remove its parsetree afterward." -- Matthias Blume
};
# Remember that we have a compile
# running on this file:
#
compiles_started
:=
ttm::set
( *compiles_started,
tin_to_compile.thawedlib_tome,
compile_dependencies_then_sourcefile_thread
);
# thawedlib_tome_map is from
src/app/makelib/compilable/thawedlib-tome-map.pkg # Wait for compile to finish.
#
# We wait at minimal priority so that we don't get
# priority over threads that may have to clean up
# after errors:
#
nor::map
#
.symbol_and_inlining_mapstacks
#
(mtq::wait_for_thread_to_finish_then_return_result
#
makelib_state.makelib_session.makelib_thread_boss
#
compile_dependencies_then_sourcefile_thread
);
};
esac
where
#####################################################################
# Now we define
#
# fun compile_dependencies_then_sourcefile ()
#
# To do that, we first need about 800 lines of support code: *grin*
#####################################################################
#
fun print_codesegment_components_bytesizes stream (component_bytesizes: cf::Component_Bytesizes)
=
# Print the size-in-bytes of each of the
# four major components of an compiledfile --
# code, data, pickled symbol table and
# pickled inlining table:
#
{
sizes_report
=
cat ( "["
!
#1 (fold_backward
maybe_add_size_info
(["bytes]\n"], "") # (initial_results_list, separator)
[ ( .code_bytesize, "code" ),
( .data_bytesize, "data" ),
(.symbolmapstack_bytesize, "symbolmapstack"),
( .inlinables_bytesize, "inlinables" )
]
)
)
where
fun maybe_add_size_info ((selector, label), (results, separator))
=
case (selector component_bytesizes) # 'selector' is one of .code_bytesize / .data_bytesize / .symbolmapstack_bytesize / .inlinables_bytesize
#
0 => (results, separator); # Do not report zero-length segments.
#
n => ( ( label # 'label' is onde of "code" / "data" / "dictionary" / "inlinable".
! ": "
! int::to_string n # Number of bytes in segment.
! separator # ", " or "".
! " "
! results # list-of-strings result accumulator.
),
", "
);
esac;
end;
fil::write (stream, sizes_report);
fil::flush stream;
};
#
fun unparse_codesegment_components_bytesizes
(pp: pp::Prettyprinter)
(component_bytesizes: cf::Component_Bytesizes)
=
{
pp.txt "\n\nCode segment byte sizes:\n";
sizes_report
=
cat
(fold_backward
info
["\n"]
[ ( .code_bytesize, "code" ),
( .data_bytesize, "data" ),
(.symbolmapstack_bytesize, "symbolmapstack"),
( .inlinables_bytesize, "inlinables" )
]
)
where
fun info ((selector, label), result_so_far)
=
( int::to_string (selector component_bytesizes) # Number of bytes in segment. selector is one of .code_bytesize / .data_bytesize / .symbolmapstack_bytesize / .inlinables_bytesize
! " "
! label # "code"/"data"/"dictionary"/"inlinable".
! " bytes\n"
! result_so_far # list-of-strings result accumulator.
);
end;
pp.lit sizes_report;
};
#
fun announce_compile ()
=
# List the source and (sometimes object) file
# to keep the guy at the console awake.
#
# We don't do this if we're a subprocess
# because the primary process will treat any
# output from us as a sign of trouble and will
# respond by killing us and re-doing the compile
# itself:
#
{
fil::say {.
cat [
" compile-in-dependency-order-g.pkg: Compiling source file ",
(ns::pad_right ' ' 50 (ad::os_string' (tlt::sourcepath_of tin_to_compile.thawedlib_tome)))
# "\tto object file ", # Dropped these two for now because they mostly add clutter.
# compiledfile_name, # May want to restore these when cross-compiling, since the
# object file name is then less predictable. -- 2010-10-23 CrT
];
};
# This is mostly a test of the new note_in_ramlog call -- 2012-10-12 CrT
fil::note_in_ramlog {.
cat [
"compile-in-dependency-order-g.pkg: Compiling source file ",
(ad::os_string' (tlt::sourcepath_of tin_to_compile.thawedlib_tome))
];
};
};
#
fun announce_compiledfile_load (component_bytesizes: cf::Component_Bytesizes)
=
fil::say {. cat [" compile-in-dependency-order-g.pkg: Loading ", (ad::os_string' (tlt::sourcepath_of tin_to_compile.thawedlib_tome)), ".compiled"]; };
#
fun announce_compiledfile_receipt (component_bytesizes: cf::Component_Bytesizes)
=
{ fil::say {. cat [" compile-in-dependency-order-g.pkg: Receiving ", (tlt::describe_thawedlib_tome tin_to_compile.thawedlib_tome), "\n"]; };
#
print_codesegment_components_bytesizes fil::stdout component_bytesizes;
};
#
fun handle_compile_error ()
=
if makelib_state.makelib_session.keep_going_after_compile_errors NULL;
else raise exception ABORT;
fi;
#
fun parse_and_compile_one_file
(
symbolmapstack: syx::Symbolmapstack, # These first two args constitute the exports from
inlining_mapstack: im::Picklehash_To_Anormcode_Mapstack, # the apis and packages we (tin_to_compile) reference.
picklehashes,
crossmodule_inlining_aggressiveness # From (tlt::attributes_of tin_to_compile.thawedlib_tome): ctl::Localsetting = Null_Or( Null_Or(Int) );
)
# We also get from our enclosing
# 'compile_thawedlib_tome_tin' fn the critical args:
#
# makelib_state # Global compile configuration/policy/preferences stuff.
#
# tin_to_compile # The record for the sourcefile we're actually compiling.
# # Its structure is from sg::Thawedlib_Tome_Tin, viz:
# # {
# # thawedlib_tome: tlt::Thawedlib_Tome,
# # near_imports: List( Thawedlib_Tome_Tin ), # Referenced .api and .pkg files in the same library -- ie, built by same .lib file.
# # far_imports: List( Masked_Tome ) # Referenced .api and .pkg files in other libraries. A thawedlib may refer to both thawed and frozen libs.
# # }
=
#
{
# printf "parse_and_compile_one_file/TOP -- compile-in-dependency-order-g.pkg\n";
fun maybe_compile_and_run_mythryl_codestring
#
pre_or_post # Either "pre" or "post", for human narration.
#
(THE mythryl_source_code) # Ascii string containing literal Mythryl source code to compile and run.
=>
# This fun is a little hack to support the makelib tools
# pre_compile_code / postcompile_code facility, which
# allows the tool to specify some source code to be
# compiled immediately before ("pre") or after ("post")
# the main body of code to be compiled by the tool.
#
# It is used (for example) to implement the sourcefile directives
#
# #DO set_control "compiler::verbose_compile_log" "TRUE";
# #DO set_control "compiler::trap_int_overflow" "TRUE";
# #DO set_control "compiler::check_vector_index_bounds" "FALSE";
#
# mentioned (respectively) in
#
# http://mythryl.org/my-Pre-Compile_Code.html
# http://mythryl.org/my-Int_Overflow_Checking.html
# http://mythryl.org/my-Vector_Index_Bounds_Checking.html
#
# (For the details of that mechanism search for
# "pre_compile_code_strings" in this file.)
#
# Here we take care of the mechanics of actually
# compiling and running these code fragments:
{
# fil::say [ # This is too much verbosity to go to the console.
# " compile-in-dependency-order-g.pkg: ", # It would be worth writing to the compile log, however.
# case pre_or_post
# #
# "pre" => "Pre-compile user code: ";
# _ => "Post-compile user code: ";
# esac,
# mythryl_source_code,
# "\n"
# ];
# say is from
src/lib/std/src/io/say.pkg # safely is from
src/lib/std/safely.pkg was_interactive = *myp::print_interactive_prompts;
was_unparsing_result = *myp::unparse_result;
myp::print_interactive_prompts := FALSE; # Suppresses a print "\n"; in
src/lib/compiler/toplevel/interact/read-eval-print-loop-g.pkg myp::unparse_result := FALSE; # Suppresses printing of result of evaluated expression.
safely::do # This should be a supported, exported 'eval' function.
{
open_it => {. fil::open_string mythryl_source_code; },
close_it => fil::close_input,
cleanup => \\ _ = { myp::print_interactive_prompts := was_interactive; }
}
read_eval_print_from_stream; # Ultimately from
src/lib/compiler/toplevel/interact/read-eval-print-loop-g.pkg # unless someone has reset read_eval_print_from_stream_hook in
src/app/makelib/main/makelib-g.pkg myp::print_interactive_prompts := was_interactive; # Restore bloodybedamned global variables to original state.
myp::unparse_result := was_unparsing_result;
};
maybe_compile_and_run_mythryl_codestring _ NULL
=>
(); # No configuration code supplied, so nothing to do.
end;
# A helper fn to remove all PRE_COMPILE_CODE entries
# from a list of declarations and return the remaining
# declarations plus the extracted strings.
#
# PRE_COMPILE_CODE entries derive from "#DO ...;" statements,
# which the grammar in src/lib/compiler/front/parser/yacc/mythryl.grammar
# allows only at toplevel, so we don't have to walk the entire
# parsetree, we need only recursively rewrite the toplevel
# raw::SOURCE_CODE_REGION_FOR_DECLARATION and
# raw::SEQUENTIAL_DECLARATIONS nodes.
#
# The arg pattern is (input, output, output):
#
fun split ([], declarations, pre_compile_code_strings) # Done --
=> # return
( (reverse declarations), # declarations with PRE_COMPILE_CODEs removed,
(reverse pre_compile_code_strings) # plus the PRE_COMPILE_CODE strings.
);
split ( raw::SOURCE_CODE_REGION_FOR_DECLARATION (declaration, source_code_region) ! rest,
declarations,
pre_compile_code_strings
)
=>
{ (split_off_pre_compile_code declaration)
->
(declaration', pre_compile_code_strings');
split (rest, declaration' ! declarations, (reverse pre_compile_code_strings') @ pre_compile_code_strings);
};
split (raw::PRE_COMPILE_CODE string ! rest, declarations, pre_compile_code_strings) # Add 'string' to pre_compile_codes and continue.
=>
split (rest, declarations, string ! pre_compile_code_strings);
split (raw::SEQUENTIAL_DECLARATIONS subdecs ! rest, declarations, pre_compile_code_strings) # Recursively process the sub-SEQUENTIAL_DECLARATIONS.
=>
{ (split (subdecs, [], []))
->
(declarations', pre_compile_code_strings');
split ( rest,
declarations' @ declarations,
pre_compile_code_strings' @ pre_compile_code_strings
);
};
split (other ! rest, declarations, pre_compile_code_strings) # Add 'other' to result declarations and continue.
=>
split (rest, other ! declarations, pre_compile_code_strings);
end
# Wrapper fn which removes PRE_COMPILE_CODE entries from
# a raw::Declaration list, returning both the filtered list
# and also the removed strings:
#
also
fun split_off_pre_compile_code (raw::SEQUENTIAL_DECLARATIONS declarations)
=>
{ (split (declarations, [], []))
->
(declarations', pre_compile_code_strings);
( raw::SEQUENTIAL_DECLARATIONS declarations',
pre_compile_code_strings
);
};
split_off_pre_compile_code (raw::SOURCE_CODE_REGION_FOR_DECLARATION (declaration, source_code_region))
=>
{ (split_off_pre_compile_code declaration)
->
(declaration, pre_compile_code_strings);
( raw::SOURCE_CODE_REGION_FOR_DECLARATION (declaration, source_code_region),
pre_compile_code_strings
);
};
split_off_pre_compile_code (raw::PRE_COMPILE_CODE pre_compile_code_string)
=>
( raw::SEQUENTIAL_DECLARATIONS [], # Any no-op declaration will do here.
[ pre_compile_code_string ]
);
split_off_pre_compile_code other # This case can't happen -- parse_all_declarations_in_file () always returns
=> # raw::SEQUENTIAL_DECLARATIONS in
src/lib/compiler/front/parser/main/parse-mythryl.pkg
(other, []);
end;
#
fun write_compiledfile_to_disk compiledfile
=
# Given 'compiledfile' (the in-hand result
# of compiling one sourcefile), write it to
# disk to create the actual .compiled file # A Mythryl 'foo.pkg.compiled' file corresponds to a Linux 'foo.o' file.
# recording the result of the compile. # A Mythryl 'foo.lib.frozen' file corresponds to a Linux 'foo.a' or 'foo.so' file.
#
{ fun verbosely_write_compiledfile_to_stream
#
stream
=
{ component_bytesizes
=
cf::write_compiledfile
{
compiledfile, # Compiledfile to write.
stream, # Diskfile to write it to.
drop_symbol_and_inlining_mapstacks => FALSE, # We keep full symbol table info in foo.pkg.compiled files.
# We drop it only in foo.lib.frozen files -- see
src/app/makelib/freezefile/freezefile-g.pkg architecture => myc::target_architecture, # PWRPC32/SPARC32/INTEL32. Used last (cf::read_compiledfile) to avoid linking in compiled code for
# an inappropriate machine architecture.
compiler_version_id # Something like: [110, 58, 3, 0, 2]. First two go into .compiled file 'magic'
=> # to prevent mixing code from incompatible compiler versions.
mcv::mythryl_compiler_version.compiler_version_id
};
# print_codesegment_components_bytesizes component_bytesizes; # 2006-09-10 CrT: This is just clutter for now.
component_bytesizes;
};
#
fun cleanup _
=
wnx::file::remove_file # Remove any half-built .compiled file.
temporary_compiledfile_name # 'foo.pkg.compiled.12345.tmp'
except
_ = ();
maybe_drop_thawedlib_tome_from_linker_map # Notify 'maybe_drop_thawedlib_tome_from_linker_map'
# # that we're about to re/create the .compiled file
makelib_state # for our sourcefile. In practice it is a dummy or else
# #
tin_to_compile.thawedlib_tome; # drop_thawedlib_tome_from_linker_map
# from
#
src/app/makelib/compile/link-in-dependency-order-g.pkg #
# This lets the linker flush from cache any stale
# versions of that .compiled file, or whatever.
# thawedlib_tome was an arg to fun 'compile_thawedlib_tome_tin'
# originally supplied as an arg to make_dependency_order_compile_fns
#
# in
src/app/makelib/main/makelib-g.pkg # or
src/app/makelib/mythryl-compiler-compiler/mythryl-compiler-compiler-g.pkg #
# safely is from
src/lib/std/safely.pkg # autodir is from
src/app/makelib/stuff/autodir.pkg ( safely::do
{ open_it => {. autodir::open_binary_output temporary_compiledfile_name; },
close_it => bio::close_output,
cleanup
}
verbosely_write_compiledfile_to_stream
then
{ ts::set_last_file_modification_time
(
temporary_compiledfile_name,
#
tlt::sourcefile_timestamp_of tin_to_compile.thawedlib_tome
);
wnx::file::rename_file # Make .compiled file writes effectively atomic
{ # by renaming them to final filename only
from => temporary_compiledfile_name, # once they are completely written out.
to => compiledfile_name #
};
}
)
except
any_exception
=
{ fun ppb (pp:Pp) # "pps" == "prettyprint stream".
=
{ pp.newline();
pp.lit (xns::exception_message any_exception);
};
tlt::error
makelib_state
tin_to_compile.thawedlib_tome
err::WARNING
("failed to write " + temporary_compiledfile_name)
ppb;
{ code_bytesize => 0,
data_bytesize => 0,
symbolmapstack_bytesize => 0,
inlinables_bytesize => 0
};
};
}; # \\ write_compiledfile_to_disk
# XXX SUCKO DELETEME
unparse_generic
=
print_raw_syntax_tree_as_nada::print_declaration_as_nada;
# print_raw_syntax_tree_as_nada is from
src/lib/compiler/front/typer/print/print-raw-syntax-as-nada.pkg # Get the raw::Declaration parsetree for the file
# we're compiling. It may already be cached in ram.
# If not, thawedlib-tome will parse the sourcefile
# for us during this call:
#
# printf "parse_and_compile_one_file/AAA (above main 'case') -- compile-in-dependency-order-g.pkg\n";
case (tlt::find_raw_declaration_and_sourcecode_info
#
makelib_state # makelib_state was an arg to fun 'compile_thawedlib_tome_tin'
NULL # Or, to prettyprint every file parsed: (THE (symbolmapstack, unparse_generic))
tin_to_compile.thawedlib_tome # 'tin_to_compile' was an arg to fun 'compile_thawedlib_tome_tin'
)
#
NULL => handle_compile_error (); # Syntax errors, couldn't parse sourcefile.
#
THE ( raw_declaration: raw::Declaration,
sourcecode_info: sci::Sourcecode_Info
)
=>
{
(split_off_pre_compile_code raw_declaration) # Remove all raw::PRE_COMPILE_CODE instances from raw_declaration
-> # and return their string values separately. These are produced
(raw_declaration, pre_compile_code_strings); # by '#DO ... ;' sourcecode statements -- see src/lib/compiler/front/parser/yacc/mythryl.grammar
# Maybe replace 'xcore' symbol with
# '_Core' symbol throughout parsetree.
# This is an obscure internal kludge
# we use to set up the original
# pervasive dictionary: # explicit_core_symbol is set (only) in
# #
src/app/makelib/mythryl-compiler-compiler/process-mythryl-primordial-library.pkg raw_declaration
=
case ((tlt::attributes_of tin_to_compile.thawedlib_tome).explicit_core_symbol)
#
NULL => raw_declaration; # The usual case.
#
THE core_symbol => cor::substitute_symbol_in_raw_declaration
( raw_declaration, core_symbol );
esac;
top_level_pkg_etc_defs_jar # Set of packages, generics etc currently
= # defined at the interactive toplevel.
cps::get_top_level_pkg_etc_defs_jar ();
previous_controller_settings # Save all current controller settings,
= # so we can restore them when done.
map
(\\ controller = controller.save_controller_state ()) # Return value is a thunk which when run sets controller back to saved value.
#
(tlt::controllers_of tin_to_compile.thawedlib_tome); # 'controllers' is a hack to set controllers
# (essentially, unix commandline switches) to a new
# value for just the duration of this compile.
# It is more support for the makelib 'tools' code. (If our switches weren't global variables, this would be only half as ugly...)
previous_top_level_pkg_etc_defs # Ditto with defined packages, apis etc.
=
top_level_pkg_etc_defs_jar.get_mapstack_set ();
#
fun restore_previous_global_compiler_state _ # Restore original controller settings
= # and known packages/generics.
{ top_level_pkg_etc_defs_jar.set_mapstack_set # We use a safely::do to ensure that this gets
# # called if for any reason we bomb out of the
previous_top_level_pkg_etc_defs; # following work () fn, which is the heart of
# # parse_and_compile_one_file.
apply (\\ r = r ()) previous_controller_settings;
};
#
fun run_precompile_code_for_this_tome () # Evaluate all the #DO ... ; statements etc for file.
=
{
# Run any pre-compile code from .lib file.
# This is typically used to set compile switches:
#
maybe_compile_and_run_mythryl_codestring
#
"pre"
(tlt::pre_compile_code_of tin_to_compile.thawedlib_tome);
# Run any pre-compile code from source file. Again, this is
# typically used to set compile switches via something like
#
# set_control "mythryl_parser::show_interactive_result_types" "TRUE";
#
apply
(\\ pre_compile_code_string = maybe_compile_and_run_mythryl_codestring "pre" (THE (pre_compile_code_string + ";")))
pre_compile_code_strings;
};
#
fun show_compile_phase_runtimes_for filename # Should switch to using a #DO ... ; for this (now that we have them) and delete this fn. XXX SUCKO FIXME.
=
str::is_suffix "foo.pkg" filename;
# str::is_suffix "make-nextcode-closures-g.pkg" filename; # Ran this for awhile and was getting Heisenbug style
# heap corruption during compiler compiles, stuff like
# bin/mythryld: Fatal error: unexpected fault, signal = 11, code = 0x42862b1a
# and
# bin/mythryld: Fatal error -- bad rw_vector tag 0x1c, chunk = 0x46220004, tagword = 0x746573
# I need to write some serious heap-corruption debugging support at some point,
# but now is not the time. XXX BUGGO FIXME -- 2011-09-01 CrT
#
fun maybe_open_compile_logfile source_file_name
=
if (not (mld::make_compile_logs.get ()))
#
NULL;
else
pp = pp::make_standard_prettyprinter_into_file (cat [ source_file_name, ".compile.log" ]) [];
# pps = pp.pp;
if (not *coc::verbose_compile_log)
#
pp.newline();
pp.newline();
pp.lit "This is a concise compile log."; pp.newline();
pp.lit "To get a verbose compile log, put the line"; pp.newline();
pp.newline();
pp.lit " #DO set_control \"compiler::verbose_compile_log\" \"TRUE\";"; pp.newline();
pp.newline();
pp.lit "at the top of your sourcefile."; pp.newline();
pp.newline();
# pp.lit "Next is raw syntax tree for foo:";
# pp.newline();
# foo = printf_format_string_to_raw_syntax::make_anonymous_curried_function ("%d %6.2f %-15s\n", 12, 13);
# urs::unparse_expression
# (symbolmapstack, NULL)
# pp
# (raw::PRE_FIXITY_EXPRESSION foo, 1000);
# pp.lit "Done raw syntax tree for foo:";
# pp.newline();
pp::flush_prettyprinter pp;
else
pp.newline();
pp.newline();
pp.lit "This is a verbose compile log."; pp.newline();
pp.lit "To get a concise compile log, remove the line"; pp.newline();
pp.newline();
pp.lit " #DO set_control \"compiler::verbose_compile_log\" \"TRUE\";"; pp.newline();
pp.newline();
pp.lit "from your sourcefile (or set it to FALSE instead of TRUE)."; pp.newline();
pp.newline();
pp.lit "(Following printed by src/lib/compiler/toplevel/main/compile-in-dependency-order-g.pkg.)";
pp.newline();
pp.newline();
pp.newline();
pp.newline();
pp.lit "Raw syntax tree unparsed:";
pp.newline();
urs::unparse_declaration
#
(symbolmapstack, THE sourcecode_info)
pp
(raw_declaration, 1000);
pp.newline();
pp.newline();
pp.newline();
pp.lit "Raw syntax tree prettyprinted (source code region records mostly suppressed for brevity):";
pp.newline();
prs::prettyprint_declaration
#
(symbolmapstack, THE sourcecode_info)
pp
(raw_declaration, 1000);
# pp.newline();
# pp.lit "Above fiddledeedee \\";
# pp.newline();
# fun fiddledeedee arg1 arg2 arg3 = sfprintf::printf' "%d %6.2f %-15s\n" [ sfprintf::INT arg1, sfprintf::FLOAT arg2, sfprintf::STRING arg3 ];
# pp.newline();
# pp.lit "Below fiddledeedee \\";
# pp.newline();
# pp.newline();
# pp.lit "Starting raw syntax tree for foo:";
# pp.newline();
# foo = printf_format_string_to_raw_syntax::make_anonymous_curried_function ("%d %6.2f %-15s\n", 12, 13);
# urs::unparse_expression
# (symbolmapstack, NULL)
# pp
# (raw::PRE_FIXITY_EXPRESSION foo, 1000);
# pp.newline();
# pp.lit "Done raw syntax tree for foo.";
# pp.newline();
pp::flush_prettyprinter pp;
fi;
THE pp;
fi;
fun wrap_up_compile_logfile_if_open
#
logfile_prettyprinter_or_null
component_bytesizes
compiledfile_version
inline_expression
symbolmapstack_picklehash
inlinables_picklehash
code_and_data_segments
=
# Wrap up compile log (if any):
#
case logfile_prettyprinter_or_null
#
NULL => ();
#
THE pp =>
{
# pp = pp.pp;
if *coc::verbose_compile_log
#
pp.newline();
pp.newline();
pp.lit "(Following printed by src/app/makelib/compile/compile-in-dependency-order-g.pkg.)";
pp.newline();
unparse_codesegment_components_bytesizes pp component_bytesizes;
pp.newline();
pp.newline();
pp.lit "compiledfile_version: ";
pp.lit compiledfile_version;
pp.newline();
pp.newline();
pp.lit "Compiled code saved in: ";
pp.lit compiledfile_name;
pp.newline();
pp.newline();
pp.lit "inline_expression: ";
pp.lit case inline_expression
NULL => "NULL";
_ => "(non-NULL)";
esac;
pp.newline();
pp.newline();
pp.lit "Symbol table picklehash: ";
pp.lit (ph::to_hex symbolmapstack_picklehash);
pp.newline();
pp.lit "Inlinables picklehash: ";
pp.lit (ph::to_hex inlinables_picklehash);
ucs::unparse_code_and_data_segments pp code_and_data_segments;
fi;
pp.flush ();
pp.close ();
};
esac;
#
fun compile_one_preparsed_file ()
=
# Here at last we arrive at the beating heart of
#
# fun parse_and_compile_one_file
#
# and thus
#
# fun compile_thawedlib_tome_tin
#
# and ultimately
#
# fun make_tome_compilers
#
# itself:
#
{ err = err::errors sourcecode_info; # 'sourcecode_info' was extracted from 'thawedlib_tome', above.
#
fun raise_compile_exception_if_compile_errors_found (phase: String): Void
=
if (err::saw_errors err) raise exception cx::COMPILE (phase + " failed"); fi;
if *log::debugging printf "compile_one_preparsed_file/top... [compile-in-dependency-order-g.pkg]\n"; fi;
run_precompile_code_for_this_tome (); # Evaluate all the #DO ... ; statements etc for file.
sourcecode_info.saw_errors := FALSE; # Clear error flag -- could still be set from earlier run. (Damn all global state to hell...)
source_file_name = ad::os_string' (tlt::sourcepath_of tin_to_compile.thawedlib_tome);
# For which files should we show per-compile-phase CPU usage?
# This can be a LOT of spew, so we usually enable it only
# for a specific file of interest:
#
cst::say_begin := FALSE; # To reduce clutter, don't announce start of each phase.
cst::say_when_nonzero := FALSE; # To reduce clutter, don't show phases which take 0.00 seconds.
cst::say_end := (show_compile_phase_runtimes_for source_file_name);
logfile_prettyprinter_or_null = maybe_open_compile_logfile source_file_name;
# # mythryl_compiler_for_intel32_posix is from
src/lib/compiler/toplevel/compiler/mythryl-compiler-for-intel32-posix.pkg #
per_compile_stuff
=
(r2x::make_per_compile_stuff #
{ # What's the point of this? We pass 'sourcecode_info'
sourcecode_info, # separately anyhow, and 'deep_syntax_transform' is always
# # null. That leaves just the (here implicit) stamp generator.
deep_syntax_transform => \\ x = x, # Should be either expanded or eliminated. XXX BUGGO FIXME
#
prettyprinter_or_null # (Much) later: But it also contains global information
=> # repository variables like 'saw_errors'. And this may
*coc::verbose_compile_log # be where all the various icky thread-hostile global mutables
?? logfile_prettyprinter_or_null # should be moved.
:: NULL,
compiler_verbosity => pcs::print_everything
}
): pcs::Per_Compile_Stuff( ds::Declaration );
crossmodule_inlining_aggressiveness
=
(ctl::inline::get' crossmodule_inlining_aggressiveness): Null_Or(Int);
compiledfile_version # Somthing like: "version-$ROOT/src/app/makelib/(makelib-lib.lib):compilable/thawedlib-tome.pkg-1187780741.285"
=
tlt::get_compiledfile_version tin_to_compile.thawedlib_tome: String;
# Translate_Raw_Syntax_To_Execode_0 is from
src/lib/compiler/toplevel/main/translate-raw-syntax-to-execode.api # translate_raw_syntax_to_execode_g is from
src/lib/compiler/toplevel/main/translate-raw-syntax-to-execode-g.pkg # mythryl_compiler_for_intel32_posix is from
src/lib/compiler/toplevel/compiler/mythryl-compiler-for-intel32-posix.pkg ################################################################
# This is the central call of makelib,
# where we actually compile a raw
# syntax tree all the way down to
# executable binary code: We do this one other place:
src/lib/compiler/toplevel/interact/read-eval-print-loop-g.pkg #
if *log::debugging printf "calling translate_raw_syntax_to_execode... [compile-in-dependency-order-g.pkg]\n"; fi;
(r2x::translate_raw_syntax_to_execode
{
raw_declaration => raw_declaration: raw::Declaration, # Parsetree we're compiling.
sourcecode_info => sourcecode_info: sci::Sourcecode_Info, # File we're compiling.
#
symbolmapstack => symbolmapstack: syx::Symbolmapstack, # These two args constitute the exports
inlining_mapstack => inlining_mapstack: im::Picklehash_To_Anormcode_Mapstack, # from the compiledfiles upon which we depend.
#
per_compile_stuff => per_compile_stuff: pcs::Per_Compile_Stuff( ds::Declaration ),
#
compiledfile_version => compiledfile_version: String,
#
crossmodule_inlining_aggressiveness => crossmodule_inlining_aggressiveness: Null_Or( Int ),
#
handle_compile_errors => raise_compile_exception_if_compile_errors_found
}
)
-> # Unpack results of compile:
{ code_and_data_segments => code_and_data_segments: cs::Code_And_Data_Segments, # Compiled binary code plus interpreted bytecode to regenerate literals vector.
new_symbolmapstack => new_symbolmapstack: syx::Symbolmapstack, # 'symbolmapstack' above plus stuff from 'raw_declaration'. Not used.
export_picklehash => export_picklehash: Null_Or( ph::Picklehash ),
inline_expression => inline_expression: Null_Or( acf::Function ), # A-normal code for inlining into other modules.
import_trees => import_trees: List( imt::Import_Tree ), # import_tree: How to find our imports at linktime.
symbolmapstack_picklehash,
pickle => symbolmapstack_pickle,
...
};
if *log::debugging printf "called translate_raw_syntax_to_execode. [compile-in-dependency-order-g.pkg]\n"; fi;
#
#
################################################################
# The 'inline_expression' returned by the compiler
# contains anormcode-form ("A-normal" machine-independent
# code for exported functions worth inlining in
# other modules. (This is usually disabled at present.)
#
# This will become part of the exported state
# of the module, so we pickle it now for inclusion
# in the .compiled file:
#
(pkj::pickle_highcode_program inline_expression)
->
{ picklehash => inlinables_picklehash,
pickle => inlinables_pickle
};
# byte is from
src/lib/std/src/byte.pkg #
inlinables_pickle
=
case inline_expression
#
NULL => byte::string_to_bytes ""; # If inline_expression is NULL
# # change inlinables_pickle to an empty bytestring:
THE _ => inlinables_pickle; # else do nothing.
esac;
# Wrap compile results neatly. This
# essentially generates the in-ram
# representation of the .compiled diskfile
# which we are about to write:
#
compiledfile
=
cf::make_compiledfile # fun make_compiledfile x = COMPILEDFILE x;
{
import_trees,
export_picklehash,
compiledfile_version,
code_and_data_segments,
#
picklehash_list => phs::vals_list picklehashes, # picklehashes was an arg to fun 'parse_and_compile_one_file'
#
symbolmapstack => { pickle => symbolmapstack_pickle,
picklehash => symbolmapstack_picklehash
},
#
inlinables => { pickle => inlinables_pickle,
picklehash => inlinables_picklehash
}
};
# Another layer of wrapping of compile results.
#
# This one is mostly about computing picklehashes
# and setting up thunks for later access to the
# symbol and inlining tables.
#
# This is our actual return result from
# compile_one_preparsed_file ()
#
symbol_and_inlining_mapstacks_etc
=
make_symbol_and_inlining_mapstacks_etc
(
compiledfile,
tlt::sourcefile_timestamp_of tin_to_compile.thawedlib_tome,
symbolmapstack
);
maybe_compile_and_run_mythryl_codestring # Run any funky per-file post-compile code from .lib file.
"post"
(tlt::postcompile_code_of tin_to_compile.thawedlib_tome);
restore_previous_global_compiler_state ();
(write_compiledfile_to_disk compiledfile) # Write the actual foo.api.compiled or foo.pkg.compiled file.
->
component_bytesizes;
wrap_up_compile_logfile_if_open
logfile_prettyprinter_or_null
component_bytesizes
compiledfile_version
inline_expression
symbolmapstack_picklehash
inlinables_picklehash
code_and_data_segments;
# (Usually) cache .compiled file in memory:
#
set__compiledfile__for__thawedlib_tome # When set__compiledfile__for__thawedlib_tome is not a dummy,
{ # it is compiledfile_cache::set__compiledfile__for__thawedlib_tome,
key => tin_to_compile.thawedlib_tome, # which caches a copy of the .compiled file contents in memory,
# # on the presumption we may shortly want it.
value => { compiledfile,
component_bytesizes
}
};
THE symbol_and_inlining_mapstacks_etc;
}; # fun compile_one_preparsed_file
safely::do
{
open_it => \\ () = (),
close_it => \\ () = (),
cleanup => restore_previous_global_compiler_state
}
compile_one_preparsed_file;
}
except
(err::COMPILE_ERROR
| cx::COMPILE _)
=
handle_compile_error ();
esac; # At this point we handle only
# explicit compiler bugs and ordinary
# compilation errors because for those
# there will already have been
# explanatory messages. Everything
# else "falls through" and will be
# treated at top level.
}; # fun parse_and_compile_one_file
#
fun compile_dependencies_then_sourcefile ()
=
{
# In this file we compute the most recent edit
# of any sourcefile in the library. This information
# is (only) used at one point, in
src/app/makelib/main/makelib-g.pkg #
timestamp_of_youngest_sourcefile_in_library
:=
ts::max (
#
*timestamp_of_youngest_sourcefile_in_library, # This ref is ultimately from makelib_state.
#
tlt::sourcefile_timestamp_of tin_to_compile.thawedlib_tome
);
# In the hope of increasing compile-job parallelism,
# we try to compile first those sourcefiles on which
# many other files depend:
#
compile_priority = compile_priority_of_thawedlib_tome tin_to_compile.thawedlib_tome;
#####################################################
# Our thawedlib_tome isn't in our 'compiles_started'
# so we're going to have schedule a compile for it.
#
# But before we can compile it, we must make sure
# that everything it depends upon has been compiled,
# to ensure that all the type declarations etc that
# it needs will be available at compile time.
#####################################################
# Fire up compiles of all our far
# dependencies, which is to say,
# all .compiled files in other libraries
# from which we import something:
#
far_dependency_compile_threads
=
map'
tin_to_compile.far_imports # 'tin_to_compile' is from 'fun compile_thawedlib_tome_tin' argument.
#
(\\ (masked_tome: sg::Masked_Tome)
=
mtq::make_makelib_thread
#
makelib_state.makelib_session.makelib_thread_boss
#
{. compile_masked_tome_after_dependencies makelib_state masked_tome; }
);
# Similarly, fire up compiles of all
# our local dependencies, which is
# to say, all .api/.pkg files in this library
# from which we import something:
#
near_dependency_compile_threads
=
map'
tin_to_compile.near_imports
#
(\\ thawedlib_tome_tin
=
mtq::make_makelib_thread
#
makelib_state.makelib_session.makelib_thread_boss
#
{. compile_thawedlib_tome_tin_after_dependencies thawedlib_tome_tin; })
where
fun compile_thawedlib_tome_tin_after_dependencies (thawedlib_tome: sg::Thawedlib_Tome_Tin)
=
nor::map
memoize_unfiltered_dependency_exports
(compile_thawedlib_tome_tin makelib_state thawedlib_tome);
end;
# Wait for all the above compiles to complete,
# accumulating and combining their exports:
#
combined_symbol_and_inlining_mapstacks
=
fold_forward
#
(wait_for_thread_to_finish_then_return_result_running_at_priority # fn-to-apply
#
makelib_state
compile_priority
)
#
(fold_forward # initial val for outer fold_forward
(wait_for_thread_to_finish_then_return_result_running_at_priority # fn-to-apply
#
makelib_state
compile_priority
)
(THE empty_fat_tomes_compile_result) # initial val
far_dependency_compile_threads # list to process
)
#
near_dependency_compile_threads; # list to process
case combined_symbol_and_inlining_mapstacks
#
NULL => NULL; # We can't compile our sourcefile because
# one or more of the sourcefiles it depends upon
# failed to compile.
THE { tome_exports_thunk, picklehashes }
=>
{ # We have successfully compiled all imports
# needed by tin_to_compile. (Which might be none.)
#
# Now we need to find/make compiled-code
# for tin_to_compile's source-code.
#
# If we've compiled this sourcefile
# recently, we may have the needed
# compiled-code cached in memory.
#
# If not, we'll have to either load
# the compiled code from its .compiled file,
# if any, or else generate it by
# compiling the source code.
#
# We start by checking our in-memory
# compiled-code cache:
#
case (ttm::get (*symbol_and_inlining_mapstacks_etc_map__local, tin_to_compile.thawedlib_tome))
#
NULL => must_load_or_compile_compiledfile (); # No appropriate object code in our in-memory cache.
THE symbol_and_inlining_mapstacks # Found matching compiled code in ram.
=> # Use it unless the sourcefile has been
if (symbol_and_inlining_mapstacks_are_current # modified since the compiledfile was compiled.
(
symbol_and_inlining_mapstacks,
picklehashes,
tin_to_compile.thawedlib_tome
)
)
THE symbol_and_inlining_mapstacks; # Use cached object code.
else
must_load_or_compile_compiledfile (); # Don't use cached object code.
fi;
esac
where
fun must_load_or_compile_compiledfile ()
=
# Our in-memory cache doesn't contain
# usable compiled code for our sourcefile
# so we must either load a .compiled file
# (if one exists), or else actually
# compile the sourcefile:
#
case (load_else_compile_compiledfile ())
#
THE symbol_and_inlining_mapstacks
=>
# Cache then return our compiled code:
#
{ symbol_and_inlining_mapstacks_etc_map__local
:=
ttm::set
( *symbol_and_inlining_mapstacks_etc_map__local, # Map.
tin_to_compile.thawedlib_tome, # Key.
symbol_and_inlining_mapstacks # Val.
);
THE symbol_and_inlining_mapstacks;
};
NULL => NULL; # Sourcefile doesn't compile -- give up.
esac
where
#
fun load_else_compile_compiledfile () # Get compiled code for our sourcefile,
= # by just reading it off disk if we can,
# If anything goes wrong loading # by actually compiling it if we must.
# the .compiled file, we re/compile it.
#
# Compiling may mean compiling it
# in a subprocess, and if so, we
# must load the resulting .compiled.
#
# If the second load also goes wrong,
# we recompile locally to gather error
# messages and make everything look
# "normal", which is to say local
# within this process:
#
load_else_compile_compiledfile'
{
ok_to_try_compiling_in_subprocess => TRUE,
#
compile_it => parse_and_compile_file_after_removing_any_pre_existing_compiledfile
}
where
# As a general policy, we avoid actually
# constructing symbol and inlining tables
# until we're sure we need them.
#
# We now definitely need the tables constituting
# the combined exports from our dependencies,
# so we go ahead and build them explicitly:
#
(tome_exports_thunk ())
->
{ symbolmapstack, inlining_mapstack };
# Unpack some relevant information
# about the file to be compiled:
#
(tlt::attributes_of tin_to_compile.thawedlib_tome)
->
{ crossmodule_inlining_aggressiveness, extra_static_compile_dictionary, ... };
# If an 'extra_static_compile_dictionary' was
# supplied, fold it into our symbol table.
#
# This is an obscure special-case hack used (only) in
#
#
src/app/makelib/mythryl-compiler-compiler/process-mythryl-primordial-library.pkg #
# where it serves to supply modules flagged as "primitive" in
#
# src/lib/core/init/init.cmi
#
# with access to base_types_and_ops from
#
#
src/lib/compiler/front/semantic/symbolmapstack/base-types-and-ops.pkg #
# which contains various foundation-of-the-universe things
# like 'Bool' which must be predefined in order to bootstrap
# everything else:
#
symbolmapstack
=
case extra_static_compile_dictionary
#
NULL => symbolmapstack; # Normal case.
THE symbolmapstack' => syx::atop (symbolmapstack, symbolmapstack'); # Special case for "primitive" modules.
esac;
# We need compiled code for some "foo.api" or "foo.pkg" sourcefile.
# If we've already generated a matching "foo.pkg.compiled" file
# just read it into memory, otherwise compile "foo.pkg" to
# generate the required compiled code:
#
fun load_else_compile_compiledfile'
{
ok_to_try_compiling_in_subprocess, # TRUE unless we've already tried it and it didn't work.
compile_it # A fn to re/compile the file -- in practice "parse_and_compile_file_after_removing_any_pre_existing_compiledfile".
}
=
case (load_compiledfile ())
#
NULL =>
if (not *coc::compile_in_subprocesses
or not ok_to_try_compiling_in_subprocess)
#
if ok_to_try_compiling_in_subprocess announce_compile (); fi; # Announce only if we have not already done so.
compile_it ();
else
announce_compile ();
# If we had lots of compile parallelism it would make sense
# to put a throttle here to limit the number of parallel
# compile (sub-)processes, to avoid thrashing the system.
#
# In practice at present we max out at twelve subprocesses
# "make compiler" and on user programs probably much less so
# at present I'm not worrying about this.
#
# As eventual provision for such throttling I have provided in
# in Makelib_Thread_Boss
src/app/makelib/concurrency/makelib-thread-boss.pkg # the currently-unused fields
#
# cores_in_use: Ref( Int ),
# core_wait_queue: Ref( Thread_Vim( Void ) )
#
# NB: Currently the best way of getting a
# cores-available count appears to be:
#
# core_count = posixlib::sysconf "NPROCESSORS_ONLN";
#
case (spn::fork_process [ spn::REDIRECT_STDERR_TO_STDOUT_IN_CHILD TRUE ])
#
THE process # We are the parent process from the fork().
=>
{ pid = spn::process_id_of process;
#
# Here's a little bookkeeping just to track how many
# subprocesses we're running at a given time. This
# is just to amuse the developer at the console during
# development -- it isn't used in software:
#
ms = makelib_state;
ses = ms.makelib_session;
mtq = ses.makelib_thread_boss;
cores_in_use = mtq::get_cores_in_use mtq;
cores_in_use = cores_in_use + 1;
mtq::set_cores_in_use (mtq, cores_in_use);
# fil::say {. sprintf "cores_in_use now d=%d" cores_in_use; }; # Normally commented out to reduce console noise during compiles.
(spn::get_stdout_from_child_as_text_stream process)
->
pipe_input_stream;
run_child_and_abort_on_any_unexpected_output ()
where
fun run_child_and_abort_on_any_unexpected_output ()
=
{
case (mtq::read_line_from_unix_pipe
#
makelib_state.makelib_session.makelib_thread_boss
pipe_input_stream
)
THE "XYZZY-PLUGH-DONE\n" # Subprocess compile finished successfully.
=>
{ log::note {. "Read 'done' line from client. -- load_else_compile_compiledfile'/run_child_and_abort_on_any_unexpected_output in src/app/makelib/compile/compile-in-dependency-order-g.pkg"; };
cores_in_use = mtq::get_cores_in_use makelib_state.makelib_session.makelib_thread_boss;
cores_in_use = cores_in_use - 1;
mtq::set_cores_in_use (makelib_state.makelib_session.makelib_thread_boss, cores_in_use);
spn::reap process; # Prevent zombie processes from accumulating in process table.
();
};
THE line # We interpret any other output from subprocess as indicating a failed compile.
=>
{ # printf "%s\t\t(pid=%d)\n" (str::chomp line) pid; # Sometimes diagnostically useful, usually just noise.
spn::kill (process, is::SIGHUP); # Force subprocess to exit if it is hung.
spn::reap process; # Prevent zombie processes from accumulating in process table.
wnx::file::remove_file compiledfile_name except _ = (); # "foo.pkg.compiled"
wnx::file::remove_file temporary_compiledfile_name except _ = (); # foo.pkg.compiled.12345.tmp'
();
};
NULL => # We interpret exit without XYZZY-PLUGH-DONE as likewise indicating a failed compile.
{ log::note {. sprintf "EOF on client input pipe."; };
spn::kill (process, is::SIGHUP); # Force subprocess to exit if it is hung.
spn::reap process; # Prevent zombie processes from accumulating in process table.
wnx::file::remove_file compiledfile_name except _ = (); # "foo.pkg.compiled"
wnx::file::remove_file temporary_compiledfile_name except _ = (); # (...).12345.tmp'
();
};
esac;
};
end;
# Load into our process the compiledfile
# generated by our reaped subprocess, or
# if there were any errors, recompile from
# scratch in-process:
#
load_else_compile_compiledfile'
{
ok_to_try_compiling_in_subprocess => FALSE,
compile_it
};
};
NULL => # We are the child process from the fork().
{
makelib_state.makelib_session.we_are_a_subprocess
:=
TRUE; # Nothing actually reads this value at present.
compile_it ()
except
_ = { fil::say {. "Compiler subprocess caught exception, exiting."; };
wnx::process::exit_x wnx::process::failure; # Caller doesn't actually check exit status.
};
printf "XYZZY-PLUGH-DONE\n";
wnx::process::exit_x wnx::process::success;
};
esac;
fi;
#
THE (compiledfile, compiledfile_timestamp, component_bytesizes)
=>
{
symbol_and_inlining_mapstacks_etc
=
make_symbol_and_inlining_mapstacks_etc
(
compiledfile,
compiledfile_timestamp,
symbolmapstack
);
contents_and_sizes
=
{ compiledfile,
component_bytesizes
};
if (symbol_and_inlining_mapstacks_are_current
(
symbol_and_inlining_mapstacks_etc,
picklehashes,
tin_to_compile.thawedlib_tome
)
)
announce_compiledfile_load component_bytesizes;
#
set__compiledfile__for__thawedlib_tome
{
key => tin_to_compile.thawedlib_tome,
value => contents_and_sizes
};
THE symbol_and_inlining_mapstacks_etc;
else
compile_it ();
# Load our new .compiled file into our process
# via recursive call:
#
load_else_compile_compiledfile'
{
ok_to_try_compiling_in_subprocess => FALSE, # Insurance against looping if fork()ing off subprocesses isn't working for some reason.
compile_it
};
fi;
};
esac
where
fun load_compiledfile ()
=
######################################################################
# A function to read the foo.api.compiled or foo.pkg.compiled file
# corresponding to our foo.api or foo.pkg sourcefile, if such a
# .compiled file exists.
#
# On failure (usually because it doesn't exist) we return NULL.
#
# On success we return:
# THE ( compiledfile,
# compiledfile_timestamp,
# component_bytesizes # Size-in-bytes of code, data etc segments within .compiled file.
# )
#
######################################################################
# Return NULL immediately if .compiled file is unreadable.
# This isn't strictly necessary, but avoids
# generating background failed-to-open-file
# errors that can be distracting when debugging:
#
if (not (wnx::file::access (compiledfile_name, [ wnx::file::MAY_READ ] )))
#
NULL;
else
# Our .compiled file looks readable,
# so go ahead and try to read it:
#
THE (
safely::do
{
open_it => open_compiled_file,
close_it => bio::close_input,
cleanup => \\ _ = ()
}
read_compiled_file
)
except
_ = NULL;
fi
where
#
fun open_compiled_file ()
=
bio::open_for_read compiledfile_name;
#
fun read_compiled_file stream
=
{
my { compiledfile, component_bytesizes }
=
cf::read_compiledfile
{
architecture => myc::target_architecture, # PWRPC32/SPARC32/INTEL32.
stream,
compiler_version_id
=>
mcv::mythryl_compiler_version.compiler_version_id # Something like: [110, 58, 3, 0, 2].
}; # We'll get an error back if first two don't match version in file.
tlt::set_compiledfile_version
(
tin_to_compile.thawedlib_tome,
#
cf::get_compiledfile_version compiledfile
); # Version is (e.g.) "version-$ROOT/src/app/makelib/(makelib-lib.lib):compilable/thawedlib-tome.pkg-1187780741.285"
( compiledfile,
(ts::last_file_modification_time compiledfile_name),
component_bytesizes
);
};
# safely is from
src/lib/std/safely.pkg end; # fun load_compiledfile
end; # fun load_else_compile_compiledfile'
#
fun parse_and_compile_file_after_removing_any_pre_existing_compiledfile ()
=
{ source_path = tlt::sourcepath_of tin_to_compile.thawedlib_tome;
#
wnx::file::remove_file compiledfile_name # "foo.pkg.compiled"
except
_ = ();
timestamp_of_youngest_sourcefile_in_library # Computed in this file; used (only) in
src/app/makelib/main/makelib-g.pkg :=
ts::NO_TIMESTAMP;
# announce_compile ();
#
parse_and_compile_one_file
(
symbolmapstack, # Combined symbol tables of all apis and pkgs referenced by tin_to_compile.
inlining_mapstack, # Combined inlining tables of all apis and pkgs referenced by tin_to_compile.
picklehashes,
crossmodule_inlining_aggressiveness
);
}; # fun parse_and_compile_file_after_removing_any_pre_existing_compiledfile
end; # fun load_else_compile_compiledfile
end;
end;
}; # Dependencies compiled ok.
esac;
}; # fun compile_dependencies_then_sourcefile
end;
}; # fun compile_thawedlib_tome_tin
#
fun compile_fat_tome_after_dependencies (makelib_state: ms::Makelib_State) (fat_tome: lg::Fat_Tome)
= compile_masked_tome_after_dependencies makelib_state (fat_tome.masked_tome_thunk ());
end; # fun make_tome_compilers
# We have two levels of compile-dependency graphs,
# one which records dependencies between complete
# libraries and then one per library recording
# dependencies between individual sourcefiles.
#
# Here we walk the library-level dependency graph
# compiling libraries in post-order, so that each
# library is compiled only after all the libraries
# it needs have been compiled (making available the
# relevant type declarations etc):
#
# We get called from various places in:
#
#
src/app/makelib/main/makelib-g.pkg #
src/app/makelib/mythryl-compiler-compiler/mythryl-compiler-compiler-g.pkg #
fun make_dependency_order_compile_fns
{
root_library => (root_library as lg::LIBRARY { catalog, ... } ), # Root node for our dagwalk. ("dagwalk" == "directed-acyclic-graph walk".)
#
maybe_drop_thawedlib_tome_from_linker_map, # A dummy or drop_thawedlib_tome_from_linker_map()
# # from
src/app/makelib/compile/link-in-dependency-order-g.pkg #
set__compiledfile__for__thawedlib_tome # Dummy or compiledfile_cache::set__compiledfile__for__thawedlib_tome, which caches a copy in ram.
}
=>
{ compile_library_catalog_in_dependency_order,
#
compile_all_fat_tomes_in_library_in_dependency_order, # Called by freeze' in
src/app/makelib/main/makelib-g.pkg # # and by freeze in
src/app/makelib/mythryl-compiler-compiler/mythryl-compiler-compiler-g.pkg #
per_fat_tome_fns_to_compile_after_dependencies # For each far tome in library, a fn that will compile it after compiling its dependencies.
=> # This is (only) used to look up and compile the pervasive-package symbol "<Pervasive>"
sym::map compile_fat_tome_after_dependencies_during_bootstrap catalog # during bootstrap stuff in
src/app/makelib/main/makelib-g.pkg }
where
if (mld::debug.get ()) printf "compile-dependency-graph-walk-g: make_dependency_order_compile_fns/TOP [makelib::debug]\n"; fi;
# As a heuristic to try and save wall-clock
# time when doing parallel makes on multicore
# machines, we try to compile first those
# sourcefiles on which many other sourcefiles
# depend, since doing so is most likely to
# open up opportunities to do multiple compiles
# in parallel:
#
node_to_indegree__map
=
idg::compute__node_to_indegree__map_of root_library;
#
fun compile_priority_of_thawedlib_tome # Look up thawedlib_tome in node-to-indegree map.
# # If found in map, use indegree as priority;
(thawedlib_tome: tlt::Thawedlib_Tome) # otherwise default to zero priority.
=
the_else (
ttm::get
( node_to_indegree__map,
thawedlib_tome
),
0
);
compile_fat_tome_after_dependencies
=
mmz::memoize
{.
.compile_fat_tome_after_dependencies
#
(make_tome_compilers
{
maybe_drop_thawedlib_tome_from_linker_map,
set__compiledfile__for__thawedlib_tome,
compile_priority_of_thawedlib_tome
}
);
}: Void -> ms::Makelib_State -> lg::Fat_Tome -> Null_Or( Fat_Tomes_Compile_Result );
#
fun concurrently_compile_fat_tomes_in_dependency_order
(
makelib_state: ms::Makelib_State,
#
fat_tomes: List( lg::Fat_Tome ) # In practice, the list of tomes in a library, from either lib.catalog or all_tomes_in_library.
)
=
# We return the symbolmapstack-plus-inlining-mapstack pair
# which is the result of concurrently compiling everything
# on the input list and combining all the results.
#
{
fat_tome_compile_threads
=
map' fat_tomes make_thread_to_compile_fat_tome_after_dependencies
where
fun make_thread_to_compile_fat_tome_after_dependencies (fat_tome: lg::Fat_Tome)
=
mtq::make_makelib_thread
#
makelib_state.makelib_session.makelib_thread_boss
#
{. compile_fat_tome_after_dependencies () makelib_state fat_tome; };
end;
fat_tome_compile_results
=
fold_forward
#
(wait_for_thread_to_finish_then_return_result_running_at_priority makelib_state 0)
#
(THE empty_fat_tomes_compile_result)
#
fat_tome_compile_threads;
case fat_tome_compile_results
#
THE compile_result => THE (compile_result.tome_exports_thunk ());
#
NULL => NULL;
esac;
}
except
ABORT = NULL;
#
fun compile_library_catalog_in_dependency_order # Called by compile_library in
src/app/makelib/mythryl-compiler-compiler/mythryl-compiler-compiler-g.pkg # # and by compile_library in
src/app/makelib/main/makelib-g.pkg (makelib_state: ms::Makelib_State) # and by dagwalker_for_make_command in
src/app/makelib/main/makelib-g.pkg = # It returns all the info resulting from compiling a batch of libraries.
concurrently_compile_fat_tomes_in_dependency_order
(
makelib_state,
sym::vals_list catalog # 'catalog' is make_dependency_order_compile_fns(... library.catalog )
);
# symbol_map is from
src/app/makelib/stuff/symbol-map.pkg #
fun compile_all_fat_tomes_in_library_in_dependency_order # External entry point (after we return it from make_dependency_order_compile_fns).
# # Called by freeze' in
src/app/makelib/main/makelib-g.pkg (makelib_state: ms::Makelib_State) # and by freeze in
src/app/makelib/mythryl-compiler-compiler/mythryl-compiler-compiler-g.pkg =
not_null # Return TRUE iff all .api and .pkg files compiled successfully.
(concurrently_compile_fat_tomes_in_dependency_order
(
makelib_state,
all_fat_tomes_in_library
)
)
where
all_fat_tomes_in_library # All .api and .pkg files in library (including its sublibraries but of course not external libraries).
=
find_all_fat_tomes_in_library
{
libraries_to_do => [root_library],
libraries_done => sps::empty,
fat_tomes_found => []
}
where
# A little helper function for find_all_fat_tomes_in_library.
# It takes an entry from a library.sublibraries list
# and adds the library to our libraries_to_do list:
#
fun add_library (lt: lg::Library_Thunk, libraries_left)
=
lt.library_thunk () ! libraries_left;
# find_all_fat_tomes_in_library() constructs a list of all fat tomes
# (in essence, all .api and .pkg files) in a given library by
# processing the library plus its sublibraries, direct and indirect.
#
# First argument is the list of library graph nodes yet to process.
# Initially, this is just the root library of the .sublibraries tree.
#
# Second argument is the set of library graph nodes already processed,
# so we can avoid processing a given node more than once.
#
# Third argument is the accumulating result list of
# symbols exported via the library .exports lists. -- CrT
#
fun find_all_fat_tomes_in_library
{
libraries_to_do => [],
fat_tomes_found,
...
}
=>
# Done:
#
fat_tomes_found: List( lg::Fat_Tome );
find_all_fat_tomes_in_library
{
libraries_to_do => lg::LIBRARY lib ! libraries_to_do,
libraries_done,
fat_tomes_found
}
=>
if (sps::member (libraries_done, lib.libfile))
#
# Skip library -- we've already done it:
#
find_all_fat_tomes_in_library { libraries_to_do, libraries_done, fat_tomes_found };
else
# Add all .api and .pkg files in this library to our result:
#
fat_tomes_found
=
sym::fold_forward
(!)
fat_tomes_found
lib.catalog;
# Add all sublibraries of this lib to our left-to-do list:
#
libraries_to_do
=
fold_forward
add_library
libraries_to_do
lib.sublibraries;
# Remember we've processed this library:
#
libraries_done = sps::add (libraries_done, lib.libfile);
# Process remaining libraries_to_do recursively:
#
find_all_fat_tomes_in_library { libraries_to_do, libraries_done, fat_tomes_found };
fi;
find_all_fat_tomes_in_library
{
libraries_to_do => lg::BAD_LIBRARY ! libraries_to_do, # This sub/library had errors, but we continue processing to report any other errors this run.
libraries_done,
fat_tomes_found
}
=>
# Ignore bogus entry on libraries_to_do list:
#
find_all_fat_tomes_in_library { libraries_to_do, libraries_done, fat_tomes_found };
end;
end;
end; # fun compile_all_fat_tomes_in_library_in_dependency_order
#
fun compile_fat_tome_after_dependencies_during_bootstrap # This is (only) used to compile the pervasive-package symbol "<Pervasive>"
# # during bootstrap stuff in
src/app/makelib/main/makelib-g.pkg (fat_tome: lg::Fat_Tome)
#
(makelib_state: ms::Makelib_State)
=
case (compile_fat_tome_after_dependencies () makelib_state fat_tome except ABORT = NULL)
#
THE compile_result => THE (compile_result.tome_exports_thunk ());
#
NULL => NULL;
esac;
end;
make_dependency_order_compile_fns { root_library => lg::BAD_LIBRARY, ... }
=>
{ compile_library_catalog_in_dependency_order
=>
\\ _ = NULL,
compile_all_fat_tomes_in_library_in_dependency_order
=>
\\ _ = FALSE,
per_fat_tome_fns_to_compile_after_dependencies
=>
sym::empty
};
end; # make_dependency_order_compile_fns
#
fun compile_tome_tin_after_dependencies ()
=
compile_tome_tin_after_dependencies'
where
(make_tome_compilers
{
maybe_drop_thawedlib_tome_from_linker_map => \\ _ = \\ _ = (),
set__compiledfile__for__thawedlib_tome => \\ _ = (),
compile_priority_of_thawedlib_tome => \\ _ = 0
})
->
{ compile_tome_tin_after_dependencies, ... };
#
fun compile_tome_tin_after_dependencies'
#
(makelib_state: ms::Makelib_State)
#
(tome: sg::Tome_Tin)
=
compile_tome_tin_after_dependencies makelib_state tome
except
ABORT = NULL;
end;
#
fun drop_stale_entries_from_compiler_map () # Called (only) by drop_stale_entries_from_compiler_and_linker_maps() in
src/app/makelib/main/makelib-g.pkg =
symbol_and_inlining_mapstacks_etc_map__local
:=
ttm::keyed_filter
(tlt::is_known o #1)
*symbol_and_inlining_mapstacks_etc_map__local;
#
fun drop_all_entries_from_compiler_map () # Never invoked.
=
symbol_and_inlining_mapstacks_etc_map__local
:=
ttm::empty;
#
fun get_symbol_and_inlining_mapstacks thawedlib_tome
=
(the (ttm::get (*symbol_and_inlining_mapstacks_etc_map__local, thawedlib_tome))).symbol_and_inlining_mapstacks;
end; # stipulate
};
end;
# MOTIVATION
#
# If package A references type/fun/value in a package B.
# then we say package A "depends upon" package B.
#
# This is important during compiles, when we must
# have access to type information from B in order
# to compile A, and also during linking, when we
# must remember to link in B whenever we link A
# into a process or program.
#
# We represent the detailed dependency relationships
# between a set of packages using a dependency graph
# -- see
#
#
src/app/makelib/depend/intra-library-dependency-graph.pkg#
# We represent less detailed dependency relationships,
# accurate only to the granularity of libraries,
# using library dependency graphs. See
#
#
src/app/makelib/depend/inter-library-dependency-graph.pkg#
# We need to do two kinds of dagwalks over these graphs,
# compile dagwalks and link dagwalks.
#
# To achieve good separation of concerns, we implement
# the mechanics of doing these dagwalks separately
# from the code needing them done, and hide the
# implementation details behind an abstract api.
#
# Link dagwalks are implemented in
#
#
src/app/makelib/compile/link-in-dependency-order-g.pkg#
# Compile dagwalks are implemented here.
# DATA STRUCTURE
#
# 'symbol_and_inlining_mapstacks_etc_map__local':
#
# We use a 'symbol_and_inlining_mapstacks_etc_map__local' dictionary to remember which
# source code files we have already compiled, and to record
# for each such file the result of compiling it -- in
# particular, the resulting compiledfile and its creation date, and
# the interface information needed to compile other files
# dependent upon this file, namely the symbolmapstack of
# exported values and types, and the dictionary of
# inlinable functions.
#
# symbol_and_inlining_mapstacks_etc_map__local keys: Thawedlib_Tome records.
#
# The 'symbol_and_inlining_mapstacks_etc_map__local' dictionary keys are 'Thawedlib_Tome' records,
# which summarize what we know about a given compiledfile
# including its sourcefile and parsetree.
#
# In particular, the Thawedlib_Tome record includes
# a function make_compiledfile_name() which will generate and
# return the name of the corresponding .compiled file to generate
# presuming it is known to exist and be current. See
#
#
src/app/makelib/compilable/thawedlib-tome.pkg#
#
# symbol_and_inlining_mapstacks_etc_map__local values: Tome_Exports_Etc records.
#
# Each 'symbol_and_inlining_mapstacks_etc_map__local' dictionary value is an 'symbol_and_inlining_mapstacks'
# record (defined in this file -- see below).
#
# This record includes a 'compiledfile_timestamp' timestamp field which
# may be used to determine whether the .compiled file is
# currently valid, by checking to see if the sourcefile has
# been modified since the .compiled file was generated.
#
# The record also includes pickle hashes for the compiledfile:
# If recompiling the sourcefile results in a new .compiled file with
# the same picklehashes, then the source edit didn't introduce any
# interesting (to a compiler) changes (maybe just some new comments)
# and we don't need to run around recompiling all files which
# depend on thaat sourcefile. This can often avoid a lot of useless
# recompilations.
#
# Finally, the 'symbol_and_inlining_mapstacks' record also includes all
# the interface information produced by compiling the corresponding
# sourcefile -- which is to say, all the information needed
# to recompile files which depend upon that sourcefile.
# ALGORITHM
#
# Our basic algorithm is quite simple.
#
# In essence we do a post-order dagwalk of
# the dependency tree for the program, compiling
# each node after all of its children.
#
# Our dependency 'tree' is really a "DAG" (directed
# acyclic graph) because it has shared subtrees due
# to multiple libraries calling the same library fns,
# so we use datastructures such as our
#
# symbol_and_inlining_mapstacks_etc_map__local
#
# dictionary to ensure that we don't compile a
# given makefile or sourcefile more than once.
#
#
# In a bit more detail, the heart of the
# post-order dependency graph dagwalk is
#
# fun compile_dependencies_then_sourcefile
#
# Key points of interest:
#
#
# o Our 'dependency tree' is actually factored
# into one 'inter-library' dependency graph
# recording dependencies between complete
# libraries (a "library" being essentially
# the set of sourcefiles compiled by one
# .lib file) plus one 'intra-library' graph
# per library recording dependencies between
# individual source files.
#
# This factoring adds some complexity to the
# tree-traversal code, but does not change it
# in any essential way.
#
#
# o Before we compile a given sourcefile,
# we queue up compiles of all other sourcefiles
# that it depends upon, and wait for them to complete.
#
# Each of them do the same thing recursively, so
# we wind up compiling the dependency tree in a
# wave that starts at the leafs and ripples up
# to the root.
#
# This ensures that when we compile a given file,
# all needed info from other files is available.
#
# (The structure of our source language
# guarantees we can order our compiles
# in this fashion: We allow no cyclic
# dependencies between source files.
# To the occasional irritation of programmers!)
#
#
# o We use one 'compiles_started' map per library
# to ensure that we don't queue up multiple compiles
# of a sourcefile.
#
#
#
# COMPLICATING FACTORS
#
# As usual, most of the code complexity derives from attempts
# to improve speed and efficiency. In this case, they include:
#
# o To improve wallclock compile times, we support using
# multiple Unix processes to compile files in parallel
# during a build. These processes may be on the same
# machine (to take advantage of multi-processor boxes)
# or on other machines. (In the latter case, we assume
# use of a shared filesystem such as NFS.)
#
# o To minimize redundant work done, particularly parsing
# of source code files, we do much of the work lazily,
# using thunks and memos. As usual, this makes the code
# harder to understand and maintain. *wrygrin*
#
# o To buy efficiency in cases where we do need to parse
# a file multiple times, where possible we work from a
# custom abstraction of the source code called a
# 'module dependencies summary' which contains just
# the information we need from a source file. See
#
#
src/app/makelib/compilable/module-dependencies-summary.pkg#
#
#
# JUST LIKE UNIX 'make'
#
# We retain enough information on disk between
# runs (in particular, the .compiled files) that
# the above algorithm also provides us with
# 'make' functionality, in the sense that if
# we compile everything, edit one or more files,
# and then recompile, only the logically required
# recompiles will be done.
## (C) 1999 Lucent Technologies, Bell Laboratories
## Author: Matthias Blume (blume@kurims.kyoto-u.ac.jp)
## Subsequent changes by Jeff Prothero Copyright (c) 2010-2015,
## released per terms of SMLNJ-COPYRIGHT.