############################################################################
#
#	Name:	empg.icn
#
#	Title:	Expression Measurement Program Generator
#
#	Author:	Ralph E. Griswold
#
#	Date:	March 8, 1990
#
############################################################################
#
#     This program reads Icon expressions, one per line, and writes out
#  and Icon program, which when run, times the expressions and reports
#  average evaluation time and storage allocation.
#
#     Lines beginning with a # are treated as comments and written to the
#  output program so as to be written as comments when the output program is
#  run.
#
#     Lines beginning with a : are passed to the output program to be
#  evaluated, but not timed.
#
#     Lines beginning with a $ are included at the end of the output
#  program as declarations.
#
#     All other lines are timed in loops.
#
#     An example of input is:
#
#	:T := table(0)
#	$record complex(r,i)
#	T[1]
#	complex(0.0,0.0)
#
#     The resulting output program evaluates the expressions on the last two
#  lines and reports their average time and storage allocation.
#
#     Loop overhead for timing is computed first. The default number of
#  iterations s 10000. A different number can be given on the command line
#  when empg is executed, as in
#
#	iconx empg 1000 <test.exp >test.icn
#
#  which takes expressions from test.exp, computes loop overhead using 1000
#  iterations, and writes the measurement program to test.icn.
#
#     The default number of iterations for timing expressions is 1000. A
#  different number can be given on the command line when the measurement
#  program is run, as in
#
#	icont test
#	iconx test 5000
#
#  which times the expressions in test.icn using 5000 iterations.
#
#     If a garbage collection occurs during timing, the average time is
#  likely to be significantly distorted and average allocation cannot be
#  computed.  In this case, the number of garbage collections is reported
#  instead.  To avoid misleading results as a consequence, measurement
#  programs should be run with Icon's region sizes set to as large values
#  as possible. To avoid residual effects of one timed expression on
#  another, expressions that allocate significant amounts of storage
#  should be measured in separate programs.
#
#     The number of iterations used to compute loop overhead im empg
#  and the number of iterations used to time expressions in measurement
#  programs should be chosen so that the effects of low clock resolution
#  are minimized.  In particular, systems with very fast CPUs but
#  low clock resolution (like 386 and 486 processors running under
#  MS-DOS) need large values.
#
############################################################################
#
#  Links: numbers (in measurement programs, not in empg.icn)
#
############################################################################

procedure main(argl)
   local i, decls, line, input
   i := integer(argl[1]) | 10000
   decls := []				# list for declarations
   write("link numbers")
   write("global _Count, _Coll, _Store, _Overhead, _Names")
   write("procedure main(argl)")
   write("   _Iter := argl[1] | 1000")
   write("   _Names := [\"static\",\"string\",\"block \"]")
   write("   write(\"iterations: \",_Iter)")
   write("   write(\"&version: \",&version)")
   write("   write(\"&host: \",&host)")
   write("   write(\"&dateline: \",&dateline)")
   write("   write(\"region sizes: \")")
   write("   _I := 1")
   write("   every _S := &regions do {")
   write("      write(\"   \",_Names[_I],\"   \",_S)")
   write("      _I +:= 1")
   write("      }")
   write("   _Count := ",i)
   write("   _Itime := &time")
   write("   every 1 to _Count do { &null }")
   write("   _Overhead := real(&time - _Itime) / _Count")
   write("   _Itime := &time")
   write("   every 1 to _Count do { &null & &null }")
   write("   _Overhead := real(&time - _Itime) / _Count - _Overhead")
   write("   _Count := _Iter")
   while line := read(input) do 
      case line[1] of {
         ":": {			# evaluate but do not time
            write("   ",line[2:0])
            write("   write(",image(line[2:0]),")")
            }
         "$": {			# line of declaration
            put(decls,line[2:0])
            write("   write(",image(line[2:0]),")")
            }
         "#":			# comment
            write("   write(",image(line),")")
         default: {		# time in a loop
            write("   write(",image(line),")")
            write("   _Prologue()")
            write("   _Itime := &time")
            write("   every 1 to _Count do {")
            write("      &null & ", line)
            write("      }")
            write("   _Epilogue(&time - _Itime)")
            }
      }
   write("end")
   write("procedure _Prologue()")
   write("   _Store := []")
   write("   _Coll := []")
   write("   collect()")
   write("   every put(_Store,&storage)")
   write("   every put(_Coll,&collections)")
   write("end")
   write("procedure _Epilogue(_Time)")
   write("   every put(_Store,&storage)")
   write("   every put(_Coll,&collections)")
   write("   write(fix(real(_Time) / _Count - _Overhead,1,8),\" ms.\")")
   write("   if _Coll[1] = _Coll[5] then {")
   write("      write(\"average allocation:\",)")
   write("         every _I := 1 to 3 do")
   write("            write(\"   \",_Names[_I],fix(real(_Store[_I + 3] - _Store[_I]),_Count,12))")
   write("      }")
   write("   else {")
   write("   write(\"garbage collections:\")")
   write("   write(\"   total \",right(_Coll[5] - _Coll[1],4))")
   write("   every _I := 6 to 8 do write(\"   \",_Names[_I - 5],right(_Coll[_I] - _Coll[_I - 4],4))")
   write("      }")
   write("   write()")
   write("end")
   every write(!decls)		# write out declarations
end
