Using the SML/NJ Compiler Testbed (version of January 9, 1995)

				Andrew Appel

The "open compiler" allows a compiler
testbed in which all the internal modules of the compiler are accessible
from the interactive system.  This makes it easy to debug the compiler,
and to test new versions of certain modules.

You must start with an open compiler, built using the -full option in
the installation script.  Call this heap image "sml-full".

To do simple things--examining the compiler's data structures, for example,
but not replacing modules--it suffices to use "sml-full" as an ordinary
interactive system.  For example, suppose one wants to see the
static environment representation of a compiled functor F in file
foo.sml.  First, 
	use "foo.sml";
then use the interactive system to examine data structures in the
top-level environment:
	val e1 = #get  Environment.topLevelEnvRef ()
	val se1 = #static e1
	val complainer = fn _ => fn s => fn _ => output(std_out,s)
        val n1 = SymPath.SPATH[Symbol.fctSymbol "F"];
	val v = ModuleUtil.lookFCT(se1,n1,complainer);

Then you can pattern-match within v to examine the pieces you want:

  val v =
   FCTvar
    {access=EXTERN -,
     binding=FCT
               {argument=SIG #,body={fctseq=#,str=#,strseq=#,tyseq=#},
                lambdaty=ARROWty #,paramName=SYMBOL #,parent=ERROR_STR,
                stamp=-},name=SYMBOL (74,"F")} : Modules.functorVar

Now, suppose you want to modify an existing module of the compiler,
or add a new module to the compiler.  You can use the Compilation Manager
in "batch" mode (expressly for bootstrapping the compiler), and this
works well, but requires a bootstrap step each time you want to 
test anything.  Furthermore, you lose the valuable advantage of
having a working open compiler to debug with.  (You could bootstrap
with -full, giving you an open compiler, but if your modification
broke something, then you'll have a nonworking open compiler, which isn't
too useful.)

The Compiler Testbed allows quicker turnaround.  In essence, you
load your new module in as a client program, apply the rest of the
compiler to it (as a functor), and this produces a new "use" command
that shares the pervasive and top-level environments of the underlying
compiler.   (Warning: don't use this method if you change the representation
of static environments, or if you change the interface to executable code.)

Older (1993 and 1994) versions of the testbed did indeed use functor
application.  But now it is possible to use the Compilation Manager (CM)
to good effect.  First, load CM into sml-full, producing a heap 
image sml-full-cm.  

Let X be the compiler module you want to replace.  I'll use "cps/expand.sml"
as an example.  Let Y be the <arch>VisComp module for the architecture
you're running on.  I'll use Alpha32VisComp, located in 
alpha32/alpha32vis.sml, as an example.  Make a list of all the files
in the dependency path between X and Y (including X and Y).  For example,

	cps/expandNEW.sml
	cps/cpscompNEW.sml
	build/viscomp.sml
	alpha32/alpha32gen.sml
	alpha32/alpha32vis.sml

Now, make the following new files:

testvc.sml, renaming module Y to TestVC:

	structure TestVC = Alpha32VisComp

testb.sml, a copy of the following code:

	structure TestBed =
	struct
	  val _ =
	    (#set TestVC.BareEnvironment.pervasiveEnvRef 
	        (#get TestVC.BareEnvironment.pervasiveEnvRef ());
	     #set TestVC.BareEnvironment.topLevelEnvRef 
	        (#get TestVC.BareEnvironment.topLevelEnvRef ()));
	  val use = TestVC.Interact.use_file
	end

test.cm, containing the files from X to Y, plus the last two:

	Group is
	build/control.sml
	cps/expandNEW.sml
	cps/cpscompNEW.sml
	build/viscomp.sml
	alpha32/alpha32gen.sml
	alpha32/alpha32vis.sml
	testvc.sml
	testb.sml

(I threw in build/control.sml here so that I could adjust code-generation
flags for my new expand module without affecting the underlying compiler.)

     CM.make("test.cm");	
     TestBed.use "foo.sml";

The TestBed.use function is just like the regular use function, but
uses the new Expand module.  Now, recompilations are easy:

     CM.make("test.cm");	
     TestBed.use "foo.sml";

The first compilation will compile everying listed in test.cm;
the recompilation will recompile only the what's necessary, as usual
with CM.

