SETL Optimizer
Stefan M. Freudenberger, Art Grand, Jacob T. Schwartz, Micha Sharir, Leonard Vanek. SETL optimizer. Given to Annie Liu by Jack Schwartz on 4 February 2009.
1 .=member intro 2 .title 'setl optimizer' 3$ 4$ 5$ ssssssss eeeeeeeeee tttttttttt ll 6$ ssssssssss eeeeeeeeee tttttttttt ll 7$ ss ss ee tt ll 8$ ss ee tt ll 9$ sssssssss eeeeee tt ll 10$ sssssssss eeeeee tt ll 11$ ss ee tt ll 12$ ss ss ee tt ll 13$ ssssssssss eeeeeeeeee tt llllllllll 14$ ssssssss eeeeeeeeee tt llllllllll 15$ 16$ 17$ oooooooo ppppppppp tttttttttt 18$ oooooooooo pppppppppp tttttttttt 19$ oo oo pp pp tt 20$ oo oo pp pp tt 21$ oo oo pppppppppp tt 22$ oo oo ppppppppp tt 23$ oo oo pp tt 24$ oo oo pp tt 25$ oooooooooo pp tt 26$ oooooooo pp tt 27$ 28$ 29$ t h e s e t l o p t i m i z e r 30$ 31$ 32$ stefan m. freudenberger 33$ art grand 34$ jacob t. schwartz 35$ micha sharir 36$ leonard vanek 37$ 38$ 39$ this software is part of the setl programming system 40$ address queries and comments to 41$ 42$ setl project 43$ department of computer science 44$ new york university 45$ courant institute of mathematical sciences 46$ 251 mercer street 47$ new york, ny 10012 48$ 49$ 50$ 51$ 52$ table of contents 53$ ----- -- -------- 54$ 55$ 1. intent of the optimiser 56$ 57$ 2. data structures 58$ 59$ 2a. internal form of the program 60$ 2b. the program 61$ maps on blocks, on instructions, instruction macros 62$ iteration macros 63$ 2c. code sequences for calls, entries,exits 64$ 2d. scopes and routines 65$ maps on scopes, scope types, procedure values, macros 66$ 2e. effects of separate compilation 67$ 2f. code for iterative set and tuple formers 68$ 2g. code for case statement and multiparameter retrievals 69$ 70$ 3. variable declarations 71$ 72$ 3a. the symbol table 73$ 3b. values of variables 74$ 3c. occurences 75$ 3d. q1-opcodes 76$ 3e. control graph and interval analysis, 77$ data flow maps, call graph 78$ 3f. types 79$ 3g. forms 80$ 3h. call paths 81$ 3i. various initial maps 82$ 3j. control parameters 83$ 84$ 4. global variable declarations 85$ 86$ 5. module specifications 87$ 88$ 5a. main program 89$ 5b. compiler interface, utilities 90$ 5c. call graph analysis 91$ 5d. interval analysis 92$ 5e. redundant expression elimination and code motion 93$ 5f. live analysis 94$ 5g. data flow analysis package 95$ 5h. data flow analysis - bfrom computation 96$ 5i. type finding 97$ 5k. automatic data structure selection 98$ 5l. conversion optimisation 99$ 5m. copy optimisation 100$ 5n. output to the code generator 101$ 102$ 6. data structure declarations for global variables 103$ 104$ 7. main program for the optimiser 105$ 106$ 8. interface to little-written portions 107$ 108$ 8a. read_q1, get_header 109$ 8b. get_forms 110$ 8c. get_symtab, cnvval 111$ 8d. get_code 112$ 8e. write_q1, set_ltl_maps, put_header 113$ 8f. put_forms 114$ 8g. put_symtab 115$ 8h. put_code 116$ 8i. bld_val, elmt_sym 117$ 8j. reset 118$ 119$ 9. routines for the preliminary pass 120$ 121$ ... opt_ini, readcc, 122$ first_pass, 123$ shortcut, get_temp, transclose, exp_name 124$ 9a. satisfy_members 125$ 9b. satisfy_procs 126$ 9c. bld_body 127$ 9d. bld_entry, bld_label, bld_use, bld_def, bld_call, 128$ bld_exitstop 129$ 130$ 10. interval analysis 131$ 132$ 10a. find_intervals, find_graph 133$ 10b. find_ints 134$ 10c. update 135$ 10d. dfst, .intof_lim, get_targ 136$ 137$ 11. available expressions analysis 138$ 139$ 11a. csx 140$ 11b. interproc_csx 141$ 11c. intraproc_csx 142$ 11d. move_eliminate, insert_exp 143$ 11e. csx_blockmaps 144$ 145$ 12. live analysis 146$ 147$ 12a. live 148$ 12b. interproc_live 149$ 12c. intraproc_live 150$ 12d. live_blockmaps 151$ 152$ 11f.bfrom_analysis 153$ 154$ 11g. find_bfrom, global_bfrom, local_bfrom 155$ 11h. comp_bfrom 156$ 11i. bfrom_blockmaps, .join 157$ 158$ 13. dataflow solver 159$ cgraph_analysis, interproc_fwd_analysis, 160$ interproc_fwd_eliminate, intraproc_fwd_eliminate, 161$ propagate_exposed, fwd_propagate_in, intraproc_fwd_analysis 162$ interproc_back_eliminate, intraproc_back_eliminate, 163$ intra_aux_eliminate, exit_info, back_propagatein, 164$ also auxiliary routines 165$ 166$ 14. type finder 167$ 168$ 14a. the type lattice, basic algorithm 169$ 14b. type_find 170$ 14c. type_forward, given_type, dfs 171$ 14d. type_backward 172$ 14e. type_final 173$ 14f. forward 174$ 14g. backward 175$ 14h. type_constant, constant_equality 176$ 14i. ntyp, knt_type 177$ 14j. pair_type 178$ 14k. trim, norm 179$ 14l. .con 180$ 14m. .dis 181$ 14n. .sub 182$ 14o. const_typ, sting_length, .is_pair, .is_map 183$ 14p. ads_type 184$ 185$ 15. auto_dstruct 186$ 187$ ... auto_data 188$ 15a. basegen 189$ 15b. genbases 190$ 15c. genabase 191$ 15d. basemerge 192$ 15e. equibase 193$ 15f. .lim 194$ 15g. baseadjust 195$ 15h. fancy_output 196$ 15i. real_repr, remarks 197$ 198$ 16. utilities for code manipulation 199$ 200$ add_block,add_sym,add_label,add_int,add_inst, 201$ insert, make_nl,ermesg,abort 202$ 203$ 17. dumps 204$ dmp, symdmp,formdmp,codedmp,intdmp, etc. 205$ 206$ 207$ 1 .=member ntent1 2$ 3$ 4$ 5$ i n t e n t o f t h e o p t i m i z e r 6$ -------------------------------------------------- 7$ 8$ most of the optimizer is concerned with gathering information about a 9$ program being optimized. the optimization algorithms are so complex 10$ that it is easy to loose track of how the optimizer actually improves 11$ the code. before getting started, we take time to discuss this. 12$ 13$ the optimizer makes code improvements in five areas: 14$ 15$ 1. it performs various classical optimizations such as code motion, 16$ redundant subexpression elimination, removal of dead and 17$ unreachable code, and constant propagation. these optimizations 18$ are reflected in changes to the q1 code. 19$ 20$ 2. it determines the types of undeclared and partially declared 21$ variables. this means both type determination in the tennenbaum 22$ sense, and automatic data structure selection. this information 23$ is used to fill in forms. 24$ 25$ 3. it determines which routines are recursive, and determines which 26$ temporaries and local variables can be made static (is_stk = om). 27$ 28$ 4. it determines when copies must be made as part of an instruction. 29$ 30$ 5. it makes various peephole optimizations to the code. 31$ 32$ most information generated by the optimizer is in the form of attrib- 33$ utes of variables. the optimizer performs 'name splitting' in order 34$ to insure that the attributes of a variable are the same throughout 35$ the program. 36$ 37$ name splitting is a process of dividing the occurrences of each 38$ variable into equivalence classes such that all occurrences in each 39$ class have the same attributes. each equivalence class is then made 40$ into a new variable. 41$ 42$ once we have performed name splitting, it is generally possible for 43$ more than one variable to share the same run time address. variables 44$ which can share storage are made into aliases by setting the 'alias' 45$ map. 46$ 47$ new names are also added whenever we create bases, labels, etc. 48$ this is done by the routine 'add_sym'. 49$ 50 51$ 52$ the following section describes all the data structures and modules of 53$ the optimizer. we begin with some comments on the data structures. 54$ 1 .=member ifrm2a 2 .title 'data structures' 3 4$ d a t a s t r u c t u r e s 5$ ------------------------------ 6 7$ first we give declarations and reprs for all the global data 8$ structures of the optimizer. 9 10 11$ internal form of the program 12$ ---------------------------- 13 14$ in this section we discuss the internal form of setl programs and give 15$ the data structures which define it. 16 17$ we begin by defining a few terms. 18$ 19$ 1. symbols 20$ 21$ each symbol corresponds to a resolved name in a setl source program 22$ or to a compiler generated temporary. symbols are represented as 23$ atoms which are elements of the base 'symbols'. 24$ 25$ 2. temporaries 26$ 27$ a temporary is a symbol generated by the compiler to serve as the 28$ output of an expression. the output of an expression is always a 29$ temporary; symbols appearing in the source program may only appear 30$ as outputs in assignment instructions. 31$ 32$ when the program is read in from the semantic pass, each 33$ instruction generates a unique temporary. temporaries are merged 34$ during redundant subexpression elimination. 35$ 36$ 3. forms 37$ 38$ a 'form' is a description of how a setl object is represented at 39$ run time. the form of a symbol indicates how values assigned to 40$ that symbol are to be represented. forms may be supplied by the 41$ user through the 'repr' statement, or may be generated 42$ automatically. 43$ 44$ forms are represented as atoms on which various maps are defined. 45$ 46$ note that the typefinder and the automatic data choice algorithm 47$ do not compute the forms of variables. instead they compute much 48$ simpler type descriptors known as 'types'. 'types' are converted 49$ to 'forms' later on. 50$ 51$ 4. the program 52$ 53$ the program is divided into routines, basic blocks, and 54$ instructions. each instruction consists of an opcode and a tuple 55$ of arguments. all the inputs and outputs of an instruction 56$ appear explicitly as arguments. 57$ 58$ the instructions in each block are arranged in a linked list. this 59$ is designed to give maximum flexibility in the insertion and 60$ deletion of code. if we were to use tuples, then the entire block 61$ might need to be copied if we were to insert or delete an instruc- 62$ tion. 63$ 64$ 5. occurrences 65$ 66$ an occurrence is a use or definition of a variable. occurrences 67$ are identified by pairs 68$ 69$ [ instruction, argument no. ] 70$ 71$ occurrences which are used as inputs are called 'ivariables', and 72$ occurrences which are outputs are called 'ovariables'. an 73$ occurrence may be both an i- and o-variable, for example, 74$ 'f' in 'f(x) := y'. 75 76$ plex bases and data abstraction 77$ ------------------------------ 78 79$ symbols, forms, etc. can all be thought of as abstract data types. we 80$ treat abstract objects as atoms which have various maps based on them. 81$ 82$ the objects of each abstract type form a base. very often we will 83$ have a set 's' which contains all the objects of a given type(say all 84$ symbols). 's' is clearly equivalent to the base of symbols. 85$ 86$ generally 's' will not be represented explicitly. instead we will 87$ keep a map which links all the elements of the base in order of 88$ creation. this is done for three reasons: 89$ 90$ 1. is allows us to maintain 'symbols' as a plex base and still be able 91$ to iterate over 's'. plex bases are generally much more efficient 92$ than normal bases. 93$ 94$ 2. it allows us to iterate over 's' in a standard order for dumping 95$ purposes. 96$ 97$ 3. we must be able to iterate over all symbols in their order of 98$ creation in order to build the tables which are passed to the code 99$ generator. 100$ 101$ a variety of macros for iterating over link maps are provided in the 102$ section 'iteration macros'. 103$ 1 .=member prog2b 2 .title 'the program' 3 4$ the program 5$ ----------- 6 7$ the program is divided into routines, basic blocks, and instructions. 8$ each instruction consists of an opcode and a tuple of arguments. all 9$ the inputs and outputs of an instruction appear explicitly as 10$ arguments. 11$ 12$ if an instruction has an output, it is always the first argument; 13$ if an instruction has label as an argument, it is always the last 14$ argument. 15$ 16$ each instruction has at most one argument which may require copying. 17$ the instruction's 'copy flag' indicates whether the argument must be 18$ copied conditionally, unconditionally, or not at all. the 19$ instruction's opcode determines which argument the copy flag refers 20$ to. 21$ 22$ an instruction may also have an argument whose share bit must be set. 23$ the instruction's 'share flag' indicates whether the bit must 24$ actually be set. 25$ 26$ the instructions in each block are threaded into a linked list. this 27$ is designed to allow maximum flexibility in code insertion and 28$ deletion. 29$ 30$ blocks are 'extended' in the sense that there may be more than one 31$ branch out of a block. the last instruction of a block is always a 32$ procedure exit, stop or goto. calls constitute a single instruction 33$ blocks, but are represented by a 3-instruction block (containing 34$ label, call, and go-to). since each block ends in a goto, we can 35$ generate code for blocks in any order. 36$ 37$ when the optimizer reads in the program, it reads blocks in the order 38$ in which they appear in the source. this order is fairly optimal 39$ since it makes the branches at the end of most blocks redundant. 40$ 41$ we keep track of this order using the first_block and next_block maps 42$ so that we can write the blocks out optimally. 43$ 44 45 46$ maps on blocks 47$ -------------- 48 49 macro block_maps; 50 routof, $ routine containing block 51 next_block, $ next block, see above 52 first_inst, $ first instruction of block 53 last_inst $ last instruction of a block 54 endm; 55 56 57$ maps on instructions 58$ -------------------- 59 60 macro inst_maps; 61 opcode, $ operation code 62 args, $ tuple of arguments 63 occs, $ tuple of occurrences 64 blockof, $ gives block containing instruction 65 stmtof, $ cummulative statement count of the instruction 66 copy_flag, $ indicates what copy action should be done 67 share_flag, $ indicates setting of a share bit 68 next_inst $ next instruction in block 69 endm; 70 71 72$ macros for accessing instructions 73$ --------------------------------- 74 75 macro arg1(i); args(i)(1) endm; 76 macro arg2(i); args(i)(2) endm; 77 macro arg3(i); args(i)(3) endm; 78 79 80$ the following macro yields the label of a basic block. 81 82 macro blk_label(b); arg1(first_inst(b)) endm; 83 84$ the values for copy flags are: 85 86 macro copy_actions; 87 copy_no, $ no copy required 88 copy_yes, $ always copy 89 copy_test $ test share bit before copy 90 endm; 91 92 93 .title 'iteration macros' 94 95$ iteration macros 96$ ---------------- 97 98$ the following macros are used for iterating over all 99$ blocks, forms, symbols, etc. 100 101 macro for_list(x, first, next); $ iterate over a list 102 init x := first; while x /= om step x := next(x); 103 endm; 104 105 macro for_block(b, scope); $ for blocks in a scope 106 for_list(b, first_block(scope), next_block) 107 endm; 108 109 macro for_inst(i, b); $ for instructions in a block 110 for_list(i, first_inst(b), next_inst) 111 endm; 112 113 macro for_form(f, scope); $ for forms in a scope 114 $ see scopes, below 115 for_list(f, first_form(scope), next_form) 116 endm; 117 118 macro for_sym(s, scope); $ for symbols in a scope 119 $ see scopes, below 120 for_list(s, first_sym(scope), next_sym) 121 endm; 122 123 1 .=member clls2c 2 .title 'code sequences for calls, entries, and exits' 3 4$ code sequences for calls, entries, and exits 5$ ------------------------------------------- 6 7$ in this section we discuss the representation of calls, entries and 8$ exits in the q1 instructions. 9 10$ note 11$ ---- 12 13$ the setl system uses two radically different linkage mechanisms 14$ depending on the 'back' option on the control card. 15$ 16$ back=0 is the default, and indicates that backtracking is illegal. 17$ this allows the relatively simple linkage mechanism described below. 18$ 19$ back=1 indicates that the program may contain backtracking. in this 20$ case we refuse to optimize it. we may choose to handle backtracking 21$ in a later version of the optimizer. 22 23$ calling sequences 24$ ----------------- 25 26$ a calling section consists of: 27$ 28$ 1. 'argin' assignments 29$ 30$ these are a series of instructions which bind the actual arguments 31$ to the formal parameters. there is one q1_argin instruction for 32$ each argument. these instructions have: 33$ 34$ arg1: name of actual argument 35$ arg2: name of procedure being called 36$ arg3: argument number 37$ 38$ for write-only arguments, arg1 points to the symbol 'om'. 39$ 40$ the run time semantics of an argin instruction is to push arg1 onto 41$ the invocation stack. however the optimizer thinks of it as an 42$ assignment to the formal parameter. in order to do this, it adds 43$ the formal parameter as the first argument of that instruction. 44$ 45$ 2. the actual call 46$ 47$ this consists of a q1_call instruction with: 48$ 49$ arg1: name of procedure 50$ arg2: number of actual arguments 51$ 52$ 3. free and argout instructions 53$ 54$ 'free' instructions remove the values of read-only arguments from 55$ the invocation stack. they are ignored by the optimizer. argout 56$ instructions assign the values of write and read-write parameters 57$ back to the arguments. 58$ 59$ the q1_free and q1_argout instructions have the same arguments as 60$ the q1_argin instruction. the optimizer adds a fourth argument to 61$ argout instructions as its last argument. 62$ 63$ note that if a procedure has a variable number of arguments, 64$ such as: 65$ 66$ procedurep(a, b(*)); 67$ 68$ then the extra arguments are gathered into a tuple. thus the call 69$ 70$ p(1, 2, 3); 71$ 72$ has two argin instructions. the first passes '1' and the second 73$ passes '[ 2, 3 ]'. the q1_call instruction indicates that the call 74$ had 3 arguments. this is the value returned by the 'na' operator. 75 76 77$ entry, exit, and stop blocks 78$ ---------------------------- 79 80$ each routine begins with an 'entry' block, and ends with an 'exit' 81$ block followed by a 'stop' block. the entry block has a single 82$ instruction with: 83$ 84$ opcode: q1_entry 85$ arg1: name of routine 86$ 87$ the exit block has one instruction with: 88$ 89$ opcode: q1_exit 90$ arg1: name of routine 91$ 92$ the stop block has one instruction with: 93$ 94$ opcode: q1_stop 95$ 96 1 .=member scor2d 2 .title 'scopes and routines' 3 4$ scopes and routines 5$ ------------------- 6 7$ setl programs are divided into separate namescopes. a namescope is 8$ either a directory member, a procedure, or a special system namescope. 9$ when the semantic pass writes out the q1 tables, it writes them one 10$ scope at a time. we must keep lists of the symbols, forms, and blocks 11$ for each scope. this is done by using a series of maps first_xxx 12$ which point to the start of each list and last_xxx which point to the 13$ end of the list. 14$ 15$ namescopes are simply identified by their names. the following maps 16$ are defined on scopes: 17 18 macro sc_maps; 19 scopes, $ tuple of scopes in lexical order 20 cont_scopes, $ tuple of containing scopes (inner-to-outer) 21 sc_stmt_ct, $ maps each scope to its statement count 22 sc_estmt_ct, $ maps each procedure scope to the statement 23 $ number of the q1_entry instruction 24 sc_type, $ string sc_xxx giving the type of a scope 25 sc_nprocs, $ number of procedures in a member 26 first_sym, $ first symbol in a scope 27 last_sym, $ last symbol in a scope 28 first_block, $ first block in a scope 29 last_block, $ last block in a scope 30 first_form, $ first form in a scope 31 last_form $ last form in a scope 32 endm; 33 34$ the scope types are: 35 36 macro sc_types; 37 sc_sys, $ system unit 38 sc_lib, $ library 39 sc_dir, $ directory 40 sc_prog, $ program 41 sc_mod, $ module 42 sc_proc, $ procedure 43 sc_end $ indicates end of file 44 endm; 45 46$ the following maps are defined on procedure names: 47$ note that procedure names are also symbols 48 49 macro rout_maps; 50 routs, $ set of all routines 51 rentry, $ entry block 52 rexit, $ exit block 53 rstop, $ stop block 54 rparams, $ tuple of formal parameter names 55 membof $ member in which routine is supplied 56 endm; 57$ 58$ there is more information on procedures contained in their 'value' 59$ entries. (see section 'values', below) this information is accessed 60$ by the following macros: 61$ 62$ rretn: the global variable used to return the value of a function 63$ call. 64$ 65$ rvary: indicates procedure with variable number of arguments 66$ 67$ rnargs: indicates number of arguments 68$ 69$ rptyps: types of parameters: rd, wr, or rw. 70$ 71 macro rretn(p); value(p)(1) endm; 72 macro rvary(p); value(p)(2) endm; 73 macro rnargs(p); value(p)(3) endm; 74 macro rptyps(p); value(p)(4) endm; 75 76 1 .=member secm2e 2$ the effects of separate compilation 3$ ----------------------------------- 4 5$ 6$ setl allows members to be compiled separately. when we run the opti- 7$ miser, there are four possibilities: 8$ 9$ 1. the input is a set of libraries. add a dummy main program which 10$ refers to all the libraries in its 'libraries' list, then go on 11$ to case 2. 12$ 13$ 2. the input consists of a program and a set of libraries. 14$ add a dummy directory which contains a description of the 15$ program, and go on to case 3. 16$ 17$ 3. the input contains a set of libraries, a directory, and a set 18$ of members which are described in the directory. find all 19$ the members which are described in the directory but not 20$ included in the input and build dummy bodies for their 21$ exported procedures. go on to case 4. 22$ 23$ 4. the input is a complete program. 24$ 1 .=member spcd2f 2 .title 'code generated for various special operations' 3 4$ code for iterative set and tuple formers 5$ ---------------------------------------- 6 7$ the run time library uses a stack for argument 8$ passage, recursion, etc. and also to implement 9$ iterative set and tuple formers. 10$ 11$ there are two q1 opcodes which manipulate the stack, and altogether 12$ there are three opcodes used in relation with iterative set and 13$ tuple formers: 14$ 15$ q1_push: push a1 onto the stack. 16$ 17$ q1_set1: build a set from the top a3 stack entries and 18$ store it in a1. 19$ 20$ q1_tup1: similar to q1_set1, but builds a tuple. 21$ 22$ note that in these last two operators the first input argument is the 23$ expression in the iterative set or tuple former. this aids in the 24$ type finder. 25$ 26$ q1_set1 and q1_tup1 are always used in conjunction with a q1_push 27$ instruction. their second argument is the same as the first argument 28$ of the corresponding push. 29$ 30$ the code for 's := {x : x in s1}' looks like: 31$ 32$ counter := 0; 33$ 34$ (forall x in s1) 35$ push(x); 36$ counter +:= 1; 37$ end forall; 38$ 39$ s := set1(x, counter); 40$ 41$ 'q1_push' is used only for iterative set and tuple formers. 42$ (procedures use q1_argin instructions to push parameters) 43$ the optimizer can view it as a simple assignment t2 := t2. 44$ (in connection with the computation of bfrom links, etc.) 45$ the code generator will view it as a stack push. 46$ 47$ the opcodes q1_set1 and q1_tup1 are used solely for enumerative 48$ set- and tuple-formers, while iterative set- and tuple-formers use 49$ the q1_set and q1_tup instructions, resp. 50$ 1 .=member cacd2g 2$ 3$ the code generated for "s := { x+1 : x in s1 }" is: 4$ 5$ asn t.1 0 $ initialize counter 6$ asn i.3 s1 $ iterate over copy os s1 7$ inext i.1 i.2 i.3 8$ goto l.1 9$ 10$ label l.1 $ loop body 11$ next i.1 i.2 i.3 $ advance in s1 12$ eq t.2 i.2 om $ check for iteration end 13$ if t.2 l.3 14$ asn x i.1 $ assign next element to x 15$ add t.3 x 1 $ compute 16$ push t.3 t.4 $ push set element 17$ add t.5 t.1 1 $ increment counter 18$ asn t.1 t.5 19$ goto l.2 20$ 21$ label l.2 $ step block 22$ goto l.1 23$ 24$ label l.3 $ term block 25$ asn i.1 om 26$ asn i.3 om 27$ set1 t.4 t.3 t.1 $ setformer 28$ asn s t.4 29$ 30 31 32$ code for case statement 33$ ----------------------- 34 35$ a case statement such as 'case x of' ... '(red):' ... 'end' 36$ is implemented by building a map from tag values to labels. 37$ the actual 'q1_case' instruction has: 38$ 39$ arg1: name of map 40$ arg2: name of expression, i.e. 'x' 41$ arg3: label for 'else' clause. 42$ 43$ note that the first argument is a constant. by examining 44$ its value, we can determine the labels of all the alternatives 45$ in the case statement. 46$ 47 48$ code for f(x1, ..., xn), etc. 49$ ----------------------------- 50 51$ multi-variate map retrieval and assignment statements compile into an 52$ enumerative tuple former for the domain element, which is then used to 53$ index the map. if the cardinality of the domain tuple is 1, then the 54$ semantic pass catches the obvious optimization to not create this 55$ tuple. 56 57 1 .=member smtb3a 2 3 4$ variable declarations 5$ --------------------- 6 7$ in this section we declare variables global to the entire optimizer. 8$ the globals can be divided into various logical groupings, such as 9$ the code and the symbol table. we provide a macro to list the 10$ variables in each group. 11 12 .title 'symbol table' 13 14$ the symbol table 15$ ---------------- 16 17$ the 'symbol table' is a collection of maps on symbols. 18$ these maps are: 19$ 20$ name: a string giving the name of the symbol. 21$ 22$ is_internal: flags internally generated name. 23$ 24$ value: the value of a symbol. see below. 25$ 26$ is_const: indicates constant 27$ 28$ scope: maps local variables into their procedures and 29$ global variables into their module, library, etc. 30$ 31$ form: gives the form of a symbol 32$ 33$ alias: this field is used for symbols which share storage with 34$ each other. these symbols are created by the compiler 35$ when it processes the 'const' statement and are created 36$ by the optimizer during the 'name splitting' phase. 37$ 38$ the alias field is used in conjunction with the is_store 39$ field. if two symbols s1 and s2 share storage, then one 40$ of them, say s1, will have: 41$ 42$ is_store = 1 indicating storage is required 43$ alias = om 44$ 45$ while s2 will have: 46$ 47$ is_store = om indicating no separate storage 48$ alias = s1 49$ 50$ the alias field is also used to link unsatisfied 51$ external procedures which effect the same global 52$ variables, etc. 53$ 54$ is_store: see above 55$ 56$ is_temp: flags temporary variables. 57$ (is_temp is a subset of is_internal) 58$ 59$ is_read: indicates read permission for constant or variable 60$ 61$ is_write: indicates write permission for a variable 62$ 63$ is_stk: flags stacked variables 64$ 65$ is_param: flags formal parameters 66$ 67$ is_repr: indicates that specific information is available about 68$ the internal representation of the object. this 69$ information is either supplied by the user, or 70$ determined by the optimizer. 71$ 72$ is_init: flags initialised variables: their alias field will 73$ point to the symbol table entry carrying the initial 74$ value of this symbol. 75$ 76$ is_seen: member, procedure, or label has been seen in the input. 77$ 78$ is_back: flags backtracked variables: we do not attempt to opti- 79$ mise programs with backtracking, yet this flag must be 80$ transmitted from the sem to cod. 81$ 82$ is_rec: flags recursive routines. by default, we assume that 83$ all routines are recursive. call graph analysis will 84$ tell us which routines are not recursive, and thus do 85$ not require (more expensive) recursive routine prologs 86$ and epilogs. 87$ 88$ next_sym: links the symbols of each scope in order of creation 89$ note that if a procedure has 'n' parameters then they 90$ are the first 'n' symbols in its scope. 91 92 93 macro symbol_table; 94 name, 95 is_internal, 96 value, 97 is_const, 98 scope, 99 form, 100 alias, 101 is_store, 102 is_temp, $ flags temporaries 103 is_read, $ indicates reads permission for a variable 104 is_write, $ indicates write permission for a variable 105 is_stk, $ flags local stacked variables 106 is_param, $ flags formal parameters 107 is_repr, $ indicates data structure info is available 108 is_init, $ flags initalised (global) variables 109 is_seen, $ flags members seen in the input 110 is_back, $ flags backtracked variables 111 is_rec, $ flags recursive routines 112 next_sym 113 endm; 114 1 .=member valu3b 2 .title 'value of variables' 3 4$ value of variables 5$ ------------------ 6 7$ the 'value' map is defined for constants(is_const = 1) and variables 8$ appearing in an 'init' statement. constants have: 9$ 10$ is_const: 1 11$ value: gives value of constant 12$ 13$ uninitialized variables have: 14$ 15$ is_const: om 16$ value: om 17$ 18$ we can distinguish between constants and initialized variables 19$ by noting that constants have their 'is_write' bit = om, whereas 20$ initialized variables have their 'is_write' bit = 1 (true). 21$ 22$ we can distinguish between two types of 'value' entries: 23$ 24$ 1. run time values 25$ 26$ these are values for constant denotations, etc. for example, the 27$ value of the symbol '1' is the integer 1, and the value of the 28$ symbol 'nl' is a null set. 29$ 30$ 2. compile time values 31$ 32$ symbols such as labels, module names, and procedures can be said to 33$ have values even though they can never appear in expressions. their 34$ values are of interest only to the compiler. 35$ 36$ a. labels 37$ 38$ the value of a label is the instruction which defines it. 39$ 40$ b. perform blocks 41$ 42$ the value of a perform block is a pair [ l1, l2 ] where l1 is 43$ the label for the perform block and l2 is the label for the 44$ point it returns to. perform blocks are compiled into the 45$ sequence 'go to l1; l2:'. 46$ 47$ c. procedures 48$ 49$ the value of a procedure is a (procedure descriptor) tuple whose 50$ components are: 51$ 52$ 1. the name of the variable used to return the procedure value 53$ 54$ 2. a flag indicating whether the procedure has a variable number 55$ of arguments. 56$ 57$ 3. the number of arguments (or minimum number if the procedure 58$ can have a variable number of arguments). 59$ 60$ 4. a tuple whose i-th component is either 'rd', 'wr', or 'rw', 61$ indicating the type of the i-th parameter. 62$ 63$ d. libraries, programs, and modules 64$ 65$ the value (descriptor) of a library, program, or module is a 66$ tuple [ libraries, reads, writes, imports, exports ] where 67$ 'libraries' is the set of libraries referenced, 'reads' is a 68$ list of globals read, 'writes' is a set of globalswritten, 69$ 'exports' is a set of procedures exported, and 'imports' is a 70$ set of procedures, exported from other modules which are called 71$ from the member. 72$ 73$ note that directories have no value. 74$ 75$ when the compiler processes a statement such as 76$ 77$ const s = { [ 1, 2 ], [ 2, 3 ] }; 78$ 79$ it generates three symbol table entries, one for 's' and one for each 80$ of its components. the value of 's' is then represented as a list of 81$ symbol table pointers to its elements. the symbol table entries for 82$ the components always appear before the symbol table entry for 's'. 83$ 84$ whenever a new composite value is added to a scope, we first have to 85$ find (or add) its components to the current scope, or an enclosing 86$ scope (i.e. a scope which is 'larger' than the current scope). this 87$ process is neccessarily recursive. 88$ 89$ during constant propagation, the optimizer may find that a variable 90$$$ ????? may be simplified 91$ 'x' has a constant value of 'v'. it must then do three things: 92$ 93$ 1. set is_const(x) = 1. 94$ 95$ 2. set value(x) = v. 96$ 97$ 3. if 'v' is a set or tuple then iterate over its elements 'e' making 98$ sure that there is a symbol table entry with value 'e' and the 99$ proper scope and type. the entry for 'e' must appear before the 100$ entry for 'x'. 101$ 102$ step (3) is done using a map called value_inv which sends a value, 103$$$ ????? may be simplified 104$ type, and scope into a symbol. 105 106 1 .=member occr3c 2 .title 'occurrences' 3 4$ occurrences 5$ ----------- 6 7$ an occurrence is a use or definition of a variable. it is identified 8$ as a triple 9$ 10$ [ instruction identifier, argument number, call path ]. 11$ 12$ but in our initial debugging we ignore the third argument and treat 13$ occurences as pairs. 14$ 15$ 'call paths' are discussed in the next section. 16$ 17$ occurrences which are inputs are called 'ivariables', and occurrences 18$ which are outputs are called 'ovariables'. an occurrence may be both 19$ an i- and o-variable, for example 'f' in 'f(x) := y'. 20$ 21$ o-variables always appear in the first argument position, while 22$ i-variables may appear anywhere. 23$ 24$ the set ops_ovar contains all opcodes whose first argument is an 25$ ovariable, and the set ops_ivar contains all opcodes whose first 26$ argument is an ivariable. these sets are not mutually exclusive, 27$ as seen from the 'f(x)' example above. 28 29 30$ the following macros are used for occurrences: 31$ 32$ oi_op: the opcode of their instruction 33$ oi_sym: the symbol for the occurrence 34$ oi_val: the symbols value 35$ oi_sib: the n-th argument of the instruction containing i 36 37 macro ocrs_maps; 38 instno, $ the instruction number of an occurrence 39 argno $ the argument number of an occurrence 40 endm; 41 42 macro path(oi); oi(3) endm; 43 44 macro oi_op(oi); opcode(instno(oi)) endm; 45 macro oi_sym(oi); args(instno(oi))(argno(oi)) endm; 46 macro oi_form(oi); form(oi_sym(oi)) endm; 47 macro oi_name(oi); name(oi_sym(oi)) endm; 48 macro oi_val(oi); value(oi_sym(oi)) endm; 49 macro oi_str(oi); (str instno(oi)+'/'+str argno(oi)) endm; 50 macro oi_rout(oi); routof(blockof(instno(oi))) endm; 51 52 macro oi_stmt(oi); 53 ( name(oi_rout(oi)) + '.' + 54 str(stmtof(instno(oi)) - sc_stmt_ct(oi_rout(oi)) + 1) ) 55 endm; 56 57$ in order to speed up iterations and test the types of occurrences, 58$ we provide the following sets: 59 60 macro oi_sets; 61 all_oi, $ set of all occurrences 62 all_o, $ set of all o-variables 63 all_i $ set of all i-variables 64 endm; 65 66 67 macro is_ovar(oi); (oi in all_o) endm; 68 macro is_ivar(oi); (oi in all_i) endm; 69 70 macro get_oi(i, j); occs(i)(j) endm; 71 macro get_ovar(oi); get_oi(instno(oi), 1) endm; 72 73 macro first_ivar(op); $ argument number of first i-variable 74 if op in ops_ivar then 1 else 2 end 75 endm; 76 77 macro get_ivars(oi; j); 78 [ get_oi(instno(oi), j) : 79 j in [ first_ivar(oi_op(oi))..#args(instno(oi)) ] ] 80 endm; 81 82 1 .=member opcd3d 2 .title 'q1 opcodes' 3 4$ the set opcodes defines all the operations in the internal program 5$ representation. 6 7 macro opcodes; 8$ 9$ binary operators 10$ 11 q1_add, $ + 12 q1_div, $ div 13 q1_exp, $ ** 14 q1_eq, $ = 15 q1_ge, $ >= 16 q1_lt, $ < smfh 1 q1_pos, $ > 0 (used only for arithmetic iterators) 17 q1_in, $ in 18 q1_incs, $ incs, subset 19 q1_less, $ less 20 q1_lessf, $ lessf 21 q1_max, $ max 22 q1_min, $ min 23 q1_mod, $ // 24 q1_mult, $ * 25 q1_ne, $ /= 26 q1_notin, $ notin 27 q1_npow, $ npow 28 q1_atan2, $ atan2 29 q1_slash, $ / 30 q1_sub, $ - 31 q1_with, $ with 32$ 33$ unary operators - of form a1 := op a2 except where noted 34$ 35 q1_abs, $ abs 36 q1_char, $ char 37 q1_ceil, $ ceiling 38 q1_floor, $ floor 39 q1_isint, $ is_integer 40 q1_isreal, $ is_real 41 q1_isstr, $ is_string 42 q1_isbool, $ is_boolean 43 q1_isatom, $ is_atom 44 q1_istup, $ is_tuple 45 q1_isset, $ is_set 46 q1_ismap, $ is_map 47 q1_arb, $ arb 48 q1_val, $ val 49 q1_dom, $ domain 50 q1_fix, $ fix 51 q1_float, $ float 52 q1_nelt, $ # 53 q1_not, $ not 54 q1_pow, $ pow 55 q1_rand, $ random 56 q1_sin, $ sin 57 q1_cos, $ cos 58 q1_tan, $ tan 59 q1_arcsin, $ asin 60 q1_arccos, $ acos 61 q1_arctan, $ atan 62 q1_tanh, $ tanh 63 q1_expf, $ exp 64 q1_log, $ log 65 q1_sqrt, $ sqrt 66 q1_range, $ range 67 q1_type, $ type 68 q1_umin, $ unary minus 69 q1_even, $ even 70 q1_odd, $ odd 71 q1_str, $ str 72 q1_sign, $ sign 73$ 74$ miscellaneous 75$ 76 q1_end, $ a1 := a2(a3..) 77 q1_subst, $ a1 := a2(a3..a4) 78 q1_newat, $ a1 := newat 79 q1_time, $ a1 := time 80 q1_date, $ a1 := date 81 q1_na, $ a1 := number of arguments of current routine 82 q1_set, $ enumerative set former 83 q1_set1, $ iterative set former 84 q1_tup, $ enumerative tuple former 85 q1_tup1, $ iterative tuple former 86 q1_from, $ a1 from a2; 87 q1_fromb, $ a1 fromb a2 88 q1_frome, $ a1 frome a2 89$ 90$ iterators 91$ 92 q1_next, $ a1 := next element of a3 93 q1_nextd, $ a1 := next element of domain a3 94 q1_inext, $ initialize next loop 95 q1_inextd, $ initialize nextd loop 96$ 97$ mappings 98$ 99 q1_of, $ a1 := a2(a3) 100 q1_ofa, $ a1 := a2<> 101 102 q1_sof, $ a1(a2) := a3 103 q1_sofa, $ a1<> := a3 104 q1_send, $ a1(a2..) := a3 105 q1_ssubst, $ a1(a2..a3) := a4 106$ 107$ assignments - all assign a2 to a1 108$ 109 q1_asn, $ a1 := a2 110 111$ argument passage - a1 is argument, a2 is routine, a3 is argument no. 112 113 q1_argin, $ assign argument to formal parameter 114 q1_argout, $ assignment back to argument 115 116 q1_push, $ push element for set former 117 q1_free, $ free stack space after call 118$ 119$ control statements 120$ 121 q1_call, $ call a1. a2 is number of arguments 122 q1_goto, $ goto a1 123 124 q1_if, $ if a1 then goto a2 125 q1_ifnot, $ if not a1 then goto a2 smfg 1 q1_bif, $ if a1 then goto a2 (a1 is boolean) smfg 2 q1_bifnot, $ if not a1 then goto a2 (a1 is boolean) smfg 3 q1_ifasrt, $ if getipp('assert=1/2') = 0 then goto a1 126 q1_case, $ t := a1(a2); if t = om then goto a3 127 q1_stop, $ stop 128 129 q1_entry, $ procedure entry point. a1 is the routine 130 q1_exit, $ routine exit. a1 is the routine name 131 132 q1_ok, $ ok 133 q1_lev, $ get ok level 134 q1_fail, $ fail 135 q1_succeed, $ succeed 136 137 q1_asrt, $ if not a1 then error; end; 138 q1_stmt, $ indicates start of new statement 139 q1_label, $ 'a1:' defines label a1 140 q1_tag, $ label for case tag 141 q1_debug, $ debugging request 142 q1_trace, $ trace request 143 q1_notrace, $ cancel trace request 144 q1_error, $ compile time error 145 q1_noop, $ indicates dead instruction 146$ 147$ auxiliary operations for external procedure simulation 148$ 149 q1_ifrand, $ if random go to a1 150 q1_use, $ most general use of a1 151 q1_def, $ most general definition of a1 smfg 4 q1_isom, $ argument is omega smfg 5 q1_notom, $ argument is not omega 152 q1_arbb, $ arb to simulate q1_fromb 153 q1_arbe, $ arb to simulate q1_frome 154 q1_lessb, $ less to simulate q1_fromb 155 q1_lesse, $ less to simulate q1_frome 156 q1_sargin, $ argin for system routine call 157 q1_sargout $ argout for system routine call 158 endm; 159 160 161 162 macro ops_classes; $ collection of operator classes 163 ops_typeback, $ - backwards type propagation is meaningful 164 ops_exps, $ - operators yielding valid expressions 165 ops_modify, $ - operators which modify program variables 166 ops_extract, $ - extractions 167 ops_retrieve, $ - retrievals 168 ops_update, $ - updates 169 ops_sin, $ - sinister assignments 170 ops_bin, $ - binary operators 171 ops_un, $ - unary operators 172 ops_goto, $ - all branches 173 ops_iter, $ - iterators smfe 1 ops_typepred, $ - type predicates 174 ops_ovar, $ - operators with o-variables 175 ops_ivar, $ - operators with arg1 an i-variable 176 ops_destuse1, $ - first i-variable is used destructively 177 ops_destuse3, $ - third i-variable is used destructively 178 ops_destuse4, $ - fourth i-variable is used destructively 179 ops_share1, $ - first argument becomes shared 180 ops_share2, $ - second argument becomes shared 181 ops_share3, $ - third argument becomes shared 182 ops_share4, $ - fourth argument becomes shared 183 ops_smap, $ - operators modifying maps leaving it s-maps 184 ops_local, $ - operators supporting local basing 185 ops_sparse, $ - operators supporting sparse representation 186 ops_nonewval, $ - operators which only transmit values 187 ops_fold, $ - constant folding possible 188 ops_arith $ - arithmetic operations 189 endm; 190 191 192 macro q1_vars; $ variables which define q1 193 symbol_table, $ - all symbol table maps 194 sym_om, $ - symtab pointer for omega 195 sym_sys, $ - symtab pointer for system scope 196 sym_dir, $ - symtab pointer for directory scope 197 sym_prog, $ - symtab pointer for program scope (member) 198 sym_main, $ - symtab pointer for main program (routine) 199 form_table, $ - all form table maps 200 std_form, $ - maps basic forms to their form table entry 201 system_routs, $ - set of all system-defined routines 202 sc_maps, $ - all maps defined for scopes 203 rout_maps, $ - all maps defined for routines 204 block_maps, $ - all maps defined for blocks 205 inst_maps $ - all maps defined for instructions 206 endm; 207 208 209 macro q1_consts; $ related constants 210 sc_types, $ - scope types: range values for sc_type 211 tup_sc_types, $ - ... 212 ft_types, $ - form types: range values for ft_type 213 tup_ft_types, $ - ... 214 ft_mapcs, $ - map types: range values for ft_mapc 215 tup_ft_mapcs, $ - ... 216 ft_predicates, $ - form table predicates is_fint, etc. 217 simple_type, $ - maps specific form types into classes 218 opcodes, $ - range values for opcode 219 tup_opcodes, $ - ... 220 copy_actions, $ - range values for copy_flag 221 tup_copy_actions, $ ... 222 ops_classes $ - collection of operator classes 223 endm; 224 225 1 .=member flow3e 2 .title 'control graph and interval analysis' 3 4$ control graph and interval analysis 5$ ----------------------------------- 6 7$ one of the first steps of the optimizer is the construction of the 8$ 'control graph'. this graph consists of two maps on basic blocks: 9$ 10$ after we build the control graph we divide the program into regions 11$ known as intervals. each interval corresponds to a loop in the 12$ program. 13$ 14$ each time we construct an interval we add a new basic block called 15$ the interval's 'target block'. when we move code out of an interval, 16$ we move it to the interval's target block. 17$ 18$ intervals are identified by their target block. 19$ 20$ note that we do not build the derived graphs explicitly. instead, 21$ every time we build an interval we add a series of edges from the 22$ interval's target block to the interval's successors. these edges are 23$ called virtual edges since they do not correspond to actual program 24$ paths. we also add edges from the interval's predecessors to its 25$ target block, which replace corresponding edges entering the 26$ interval's head, and we also add an edge linking the target block to 27$ the head. all these edges, however, are real and indicate actual 28$ execution branchings. 29$ 30$ the following maps and sets are used in connection with interval 31$ analysis. 32$ 33$ intof: maps each block into its interval 34$ 35$ int_nodes: maps each interval into a tuple containing the nodes of 36$ the interval in reverse postorder. iterating over 37$ int_nodes(i) is equivalent to iterating forward over the 38$ nodes in i. 39$ 40$ ints: maps each routine 'r' into a tuple containing all the 41$ intervals in 'r' in reverse preorder. iterating 42$ backwards (forward) over this tuple is equivalent to 43$ iterating over the intervals of 'r' from outermost to 44$ innermost (innermost to outermost). 45$ 46$ proper_ints: set of all proper intervals 47$ 48$ vedges: the set of all virtual edges 49 50 macro intv_maps; 51 intof, 52 int_nodes, 53 ints, 54 proper_ints, 55 vedges 56 endm; 57 58$ the following macro maps each interval into its head: 59 macro int_head(i); int_nodes(i)(1) endm; 60 61 62 .title 'data flow maps' 63 64$ data flow maps 65$ -------------- 66 67$ data flow analysis produces to maps on occurrences, called bfrom and 68$ ffrom, and a set of occurrences called bfrom_dead. before discussing 69$ these sets, we give the following definition: 70$ 71$ an x-clear path is a path through the program which is free of 72$ occurrences of 'x'. 73$ 74$ we now define the results of data flow: 75$ 76$ 1. bfrom: 77$ 78$ let 'i' be a use of 'x'. then bfrom{i} is the set of occurrences 79$ 'oi' of 'x' such that there is an x-clear path from oi to i. 80$ 81$ 2. ffrom: 82$ 83$ this is the inverse of bfrom. 84$ 85$ 3. bfrom_dead: 86$ 87$ this is the set of all occurrences 'oi' of 'x' such that there is 88$ an x-clear path from 'oi' to either a stop instruction (or an exit 89$ instruction in the case of local variables) or a redefinition of 90$ 'x'. 91 92 93$ the live analysis routine will compute another global map, known as 94$ 'liveat', which maps each basic block to the set of all variables live 95$ at its start. 96 97 98 .title 'the call graph' 99 100$ the call graph 101$ -------------- 102 103$ the call graph is represented by the following maps: 104$ 105$ cgraph: this is the actual call graph. it is a set of pairs 106$ [ p, q ] where p and q are procedures and p contains 107$ a call to q. 108$ 109$ callsin: maps each procedure to the set of all calls within it. 110$ 111$ callproc: maps each call block to the procedure it calls. 112$ 113$ the call graph analysis will analyze cgraph and produce the following 114$ objects: 115$ 116$ cg_sccs: a tuple of all strongly-connected components of the call 117$ graph arranged in their reverse postorder. 118$ 119$ scc_nodes: maps each s.c.c s to the tuple of all its nodes in their 120$ reverse postorder. 121$ 122$ scc_d: maps each s.c.c. s to the number of back-edge target 123$ nodes in s. 124$ 125$ note that a procedure p is (co-)recursive iff it belongs to a 126$ s.c.c. s for which scc_d(s) > 0. 127 128 macro call_maps; 129 cgraph, 130 callsin, 131 callproc, 132 cg_sccs, 133 scc_nodes, 134 scc_d 135 endm; 136 137 1 .=member typs3f 2 .title 'types' 3 4$ types 5$ ----- 6 7$ the typefinder produces a map 'typ' which sends each occurrence 8$ into a tuple with the following fields: 9$ 10$ 1. grosstyp 11$ 12$ a set of strings indicating all possible 'real types' an 13$ occurrence might take on. 14$ 15$ the 'real type' of an occurrence is a string such as 'int' 16$ or 'real' which we might get by applying the setl 'type' 17$ operator to the occurence. the possible real types are: 18$ 'int', 'real', 'string', 'atom', 'tuple', and 'set'. 19$ 20$ during automatic data structure choice we make use of additional 21$ descriptors whose grosstyps are 'base' and 'elmt'. 22$ 23$ 2. is_knt 24$ 25$ a flag which is 1 for known length tuples and om otherwise. 26$ 27$ 3. comptyp 28$ 29$ this field contains information on the component 30$ types of sets and tuples. it is interpreted in one of two 31$ ways depending on the value of 'is_knt'. 32$ 33$ is_knt = 1: comptyp is a tuple whose i-th component is a 34$ type descriptor for the i-th component type. 35$ 36$ is_knt = om: comptyp is the component type of the set or tuple. 37$ 38$ the type finder exports three predicates for making the above 39$ tests. these predicates are called is_null, is_pair, and is_map. 40$ like the is_om field they have values of yes, no, and maybe. 41$ 42$ at the end of the algorithm the type of each ovariable is a function 43$ of the instruction which defines it, while the type of each ivariable 44$ is a function of its use and the definitions and uses of all 45$ occurrences it is linked to. 46$ 47$ we may wind up with two occurrences 'o' and 'i' which are linked by 48$ bfrom but have different types. this means that the code generator 49$ must insert a conversion along the path from 'o' to 'i'. the type 50$ finde checks whether such a conversion would be legal, and gives an 51$ error message if it would not. 52$ 53$ the typefinder ignores reprs supplied by the user. 54$$$ ****** not consistent 55$ the user supplied reprs are validated later on by the 56$$$ ???? this not consistent with remark made elsewhere 57$ automatic data structure choice algorithm. 58$ 59$ note that union types are very often overestimates. for example, the 60$ union of 'set(int)' and 'tuple(string)' is 'set or tuple of int or 61$ string'. 62 63 64$ macros for fields of type descriptors 65$ ------------------------------------- 66 67$ the following macros are used to access parts of a type descriptor. 68$ note that the 'comptyp' macro allows for two possibilities: 69$ 70$ 1. the type descriptor is 'general'. return general. 71$ 2. otherwise return the second component of the type descriptor. 72 73 macro grosstyp(t); t(1) endm; 74 75 macro comptyp(t); 76 (if t = type_gen or t = type_zero then t else t(2) end) 77 endm; 78 79 macro is_knt(t); t(3) endm; 80 macro is_om(t); (t_om in grosstyp(t)) endm; 81 macro is_notom(t); (t_om notin grosstyp(t)) endm; 82 macro is_based(t); (t(4) = based) endm; 83 macro set_type(t); t(5) endm; 84 macro map_type(t); t(6) endm; 85 86$ domtyp and rangetyp can be used on map types to retrive the domain and 87$ range type descriptor, respectively. we access the component type (a 88$ known-length tuple), its component type (a tuple of types of the known 89$ length tuple), and then the first or second components are the desired 90$ types. 91 92 macro domtyp(t); t(2)(2)(1) endm; 93 macro rangetyp(t); t(2)(2)(2) endm; 94 95$ ctypn is used to retrieve the type descriptor of the i'th component of 96$ a known-length tuple. 97 98 macro ctypn(t, i); t(2)(i) endm; 99 100$ test if grosstype is primitive, i.e. int, real, string, or atom. 101 102 macro is_prim(g); (t_set notin g and t_tuple notin g) endm; 103 104 macro is_const_int(oi); $ test for integer constant 105 (is_const(oi_sym(oi)) = 1 and t_int in grosstyp(typ(oi))) 106 endm; 107 108 macro t_types; 109 t_om, 110 t_int, 111 t_real, 112 t_string, 113 t_atom, 114 t_elmt, 115 t_error, 116 t_tuple, 117 t_set, 118 t_map 119 endm; 120 121 macro based_modes; 122 locl, 123 remt, 124 sprse, 125 neutrl 126 endm; 127 128 macro typ_consts; $ all constants for the type finder 129 no, maybe, yes, max_depth, max_len, 130 t_types, bsctyps, gross_types, 131 type_int, type_real, type_string, type_boolean, 132 type_atom, type_zero, type_gen, type_om, type_notom, 133 type_tuple, type_pair, type_set, type_map, 134 int_real, int_real_str, int_real_str_atom, 135 str_tup, str_tup_set, set_tup, tup_set_map, 136 type_int_real, type_int_real_str, type_str_tup, 137 type_str_tup_set, 138 ft_usetmaps, localtp, remotetp, 139 based_modes, 140 fixed_typ 141 endm; 142 143 144 macro ads_maps; 145 oi_repr, 146 actual_bases, 147 elmt_mode, 148 bscope, 149 userbase 150 endm; 151 152 1 .=member frms3g 2 .title 'forms' 3 4$ forms 5$ ----- 6 7$ a 'form' is a complete description of how an object is represented 8$ at run time. 9$ 10$ forms are treated as atoms on which various maps are defined. if f1 11$ and f2 are forms then f1 = f2 if and only if f(f1) = f(f2) for all 'f' 12$ defined on forms. 13$ 14$ at the beginning of the typefinder phase we extract information from 15$ the 'form' map and build a map from 'occurrences' to 'types'. 16$ a 'type' is a somewhat simplified description of how objects are 17$ represented. 18$ 19$ during the name splitting phase we rebuild formtab. when we do this 20$$$ ???? expand this point 21$ we preserve its unique invertibility. 22$ 23$ the following maps are defined on forms: 24$ 25$ ft_type: a string f_xxx giving the basic type 26$ 27$ ft_mapc: a string ft_xxx indicating whether a map is stored as a 28$ map, smap, or mmap. 29$ 30$ ft_elmt: gives the form of the elements of sets, maps, and 31$ tuples. in the case of procedures, this gives the form 32$ of the procedure parameters. 33$ 34$ note that the form of the return-value of the procedure (if any) can 35$ be found by first retrieving the return value symbol as the first 36$ component in the value of the procedure, and then obtain its form. 37$ 38$ ft_dom: gives the form of the domain elements of a map. 39$ 40$ ft_im: gives f{x} for mmaps and f(x) for maps and smaps. 41$ 42$ ft_imset gives f{x} for maps: always 'sparse set(ft_im)'. 43$ 44$ ft_base: gives the form of the base 'b' in 'elmt b', 45$ local set(elmt b) and remote set(elmt b). 46$ 47$ ft_low: the lower limit for a short integer. 48$ 49$ ft_lim: this field gives various kinds of range information 50$ depending on the value of ft_type: 51$ 52$ mixed tuples: 53$ 54$ for mixed tuples, ft_lim gives the number of component 55$ types. 56$ 57$ homogeneous tuples: 58$ 59$ ft_lim indicates the minumum number of components we 60$ should allocate when we build the tuple. suppose 't' is 61$ a tuple which is usually indexed with values <= 5. then 62$ it is useful to allocate 't' with 5 components so that 63$ a large number of range checks can be suppressed. we 64$ indicate this by setting ft_lim = 5. 65$ 66$ procedures and user defined operators: 67$ 68$ ft_lim is one more than the number of arguments 69$ 70$ integers: 71$ 72$ if ft_lim is non-zero it indicates the maximum value 73$ which can be stored in the integer. 74$ 75$ ft_tup: this field is used only for remote maps. 76$ a 'remote map(elmt b) mode' is represented using a tuple 77$ whose type is 'tuple(mode)'. ft_tup gives the form of 78$ the tuple. 79$ 80$ ft_hashok: this is a flag indicating that the hash code of a set or 81$ tuple should be maintained at run time. 82$ 83$ ft_neltok: this is a flag indicating that the cardinality of a set 84$ or tuple should be maintained at run time. 85$ 86$ ft_pos: gives the position in the base element-block of a local 87$ object, relative to the origin of all similarly repred 88$ local objects of that base 89$ 90$ ft_num: maps each base and locally-repred form to the number of 91$ such repred objects 92$ 93$ ft_deref: pointer to the form after dereferencing it 94 95 96 macro form_table; 97 ft_type, 98 ft_mapc, 99 ft_elmt, 100 ft_dom, 101 ft_im, 102 ft_imset, 103 ft_base, 104 ft_low, 105 ft_lim, 106 ft_tup, 107 ft_hashok, 108 ft_neltok, 109 ft_pos, 110 ft_num, 111 ft_deref, $ dereferenced form 112 next_form, $ maps each form to the next form in formtab 113 basesymb $ maps each base form to its symtab entry 114 endm; 115$ 116$ the ft_type codes are: 117$ 118 macro ft_types; 119 f_gen, $ general 120 f_sint, $ short int 121 f_sstring, $ short string 122 f_atom, $ short atom 123 f_latom, $ long atom 124 f_elmt, $ element 125 f_uint, $ untyped int 126 f_ureal, $ untyped real 127 f_int, $ long or short integer 128 f_string, $ long or short chars 129 f_real, $ real 130 f_ituple, $ integer tuple 131 f_rtuple, $ real tuple 132 f_ptuple, $ packed tuple 133 f_tuple, $ std. tuple 134 f_mtuple, $ mixed tuple 135 f_uset, $ standard set 136 f_lset, $ local subset 137 f_rset, $ remote subset 138 f_umap, $ standard map 139 f_lmap, $ local map 140 f_rmap, $ remote map 141 f_limap, $ local integer map 142 f_lrmap, $ local real map 143 f_lpmap, $ local packed map 144 f_rimap, $ remote integer map 145 f_rrmap, $ remote real map 146 f_rpmap, $ remote packed map 147 f_base, $ base 148 f_pbase, $ plex base 149 f_uimap, $ unbased untyped integer map 150 f_urmap, $ unbased untyped real map 151 f_error, $ error 152 f_proc, $ procedure or operator 153 f_memb, $ member 154 f_lab $ label 155 endm; 156$ 157$ the various map cases are: 158$ 159 macro ft_mapcs; 160 ft_map, $ map 161 ft_smap, $ smap 162 ft_mmap $ mmap 163 endm; 164$ 165$ we provide the following predicates on forms: 166$ 167$ is_fint: true for typed and untyped integers 168$ is_freal: true for typed and untyped reals 169$ is_funt: true for untyped integers and reals 170$ is_fnum: true for typed and untyped integers and reals 171$ is_fstring: true for short and long strings 172$ is_fprim: true for primitive types 173$ is_ftup: true for tuples 174$ is_fset: true for sets, maps, and bases 175$ is_fmap: true for maps 176$ is_floc: true for local sets and maps 177$ is_frem: true for remote sets and maps 178$ is_fbased: true for based types, bases, and long atoms 179$ is_fimap: true for untyped integer maps 180$ is_frmap: true for untyped real maps 181$ is_fbase: true for bases and plex bases 182$ 183 macro is_fint(fm); (ft_type(fm) in ft_fint) endm; 184 macro is_freal(fm); (ft_type(fm) in ft_freal) endm; 185 macro is_funt(fm); (ft_type(fm) in ft_funt) endm; 186 macro is_fnum(fm); (ft_type(fm) in ft_fnum) endm; 187 macro is_fstring(fm); (ft_type(fm) in ft_fstring) endm; 188 macro is_fprim(fm); (ft_type(fm) in ft_fprim) endm; 189 macro is_ftup(fm); (ft_type(fm) in ft_ftup) endm; 190 macro is_fset(fm); (ft_type(fm) in ft_fset) endm; 191 macro is_fmap(fm); (ft_type(fm) in ft_fmap) endm; 192 macro is_floc(fm); (ft_type(fm) in ft_floc) endm; 193 macro is_frem(fm); (ft_type(fm) in ft_frem) endm; 194 macro is_fbased(fm); (ft_type(fm) in ft_fbased) endm; 195 macro is_fimap(fm); (ft_type(fm) in ft_fimap) endm; 196 macro is_frmap(fm); (ft_type(fm) in ft_frmap) endm; 197 macro is_fbase(fm); (ft_type(fm) in ft_fbase) endm; 198 199 200 macro ft_predicates; 201 ft_fint, ft_freal, ft_funt, ft_fnum, ft_fstring, ft_fprim, 202 ft_ftup, ft_fset, ft_fmap, ft_floc, ft_frem, ft_fbased, 203 ft_fimap, ft_frmap, ft_fbase 204 endm; 205 206 1 .=member imps3i 2 .title 'various initial maps' 3 4$ various initial maps 5$ -------------------- 6 7$ during the initial code scanning phase (phase 1 below), we 8$ build up varrious maps required by subsequent optimization 9$ phases. these maps are: 10 11 macro var_maps; 12 variables, $ set of all program variables 13 uservars, $ set of all variables appearing in the source smfe 2 itervars, $ set of all internal iterator variables 14 globalvars, $ set of all global variables 15 localvars, $ maps each routine to its local variables 16 occsof $ maps each variable to all its occurrences 17 endm; 18 19 macro exp_maps; 20 globalexps, $ set of all expressions which depend on 21 $ at least one global variable 22 localexps, $ maps each routine to the set of its strictly 23 $ local expressions 24 allexps, $ set of all expresions appearing in the program 25 opcexp, $ maps each expression to the opcode defining it 26 argsexp, $ maps each expression to the tuple of its input 27 $ arguments 28 dependon $ maps each variable to the set of all expres- 29 $ sions which explicitly depend on it 30 endm; 31 32 1 .=member cpar3j 2 .title 'control parameters' 3 4$ control parameters 5$ ------------------ 6 7$ the following variables are read from the control card: 8$ 9$ default value variable set meaning 10$ ------------- ------------ ------- 11$ 12$ rem=1/1 rem run time error mode 13$ db=/sfci dump_string first initial of each table to be dumped 14$ 15$ a: available expression module 16$ b: bfrom module 17$ c: code after preliminary pass, 18$ conversion analysis 19$ d: data structure module 20$ x: base merging phase 21$ y: base adjustment phase 22$ e: execution statistics 23$ f: form table 24$ i: intervals after find_ints 25$ s: symbol table 26$ t: type finder module 27$ 28$ full=0/1 extended debugging diagnostic 29$ summary=/all print final summary 30$ 31$ note that code motion can be performed only if the run time error mode 32$ is 0 or 1. any other value indicates that errors detected in code 33$ moved out of loops will cause a run time abort. 34 35 macro control_params; 36 q1_file, 37 ssm_file, $ cstmt_count -> setl source line 38 term_file, 39 prog_level, 40 debug_flag, $ perform error testing 41 at_flag, $ automatic titling smfk 1 lcp_flag, $ listing control: program parameters smfk 2 lcs_flag, $ listing control: program statistics 42 rem, $ run-time error mode 43 dump_string 44 endm; 45 46 macro utilities; 47 add_sym(rd), $ add symbol 48 del_sym(rd, rd, rd), $ delete symbol 49 add_var(rd), $ add variable 50 add_int(rd, rd), $ add integer constant 51 add_label(rd), $ add label 52 add_form(rd), $ add form 53 add_block(rd, rd, rd), $ add block to scope 54 add_inst(rd, rd, rd(*)), $ add instruction 55 insert_ins(rw, rd, rd(*)), $ insert instruction after i 56 insert_ins1(rw, rd, rd), $ insert with tuple of args 57 del_block(rd, rd, rd), $ delete block from routine 58 del_inst(rw, rd, rd) $ delete instruction from block 59 endm; 60 61 62 macro print_utils; 63 ermsg(rd), $ print error message 64 abort(rd), $ print error message and abort 65 prints(rd, rd), $ print sorted map 66 format_type(rd), $ format type descriptor into a string 67 format_repr(rd), $ format repr descriptor into a string 68 format_form(rd), $ format form table entry into string 69 format_inst(rd, rd) $ format instruction into source string 70 endm; 71 1 .=member decls4 2 3 directory setl_optimizer; 4 5 var 6 sc_maps, $ maps on scopes 7 block_maps, $ maps on blocks 8 inst_maps; $ maps on instructions 9 10 const 11 copy_actions, $ copy actions for instructions 12 base_copy_actions = { copy_actions }, 13 tup_copy_actions = [ copy_actions ], 14 sc_types, $ scope types 15 base_sc_types = { sc_types }, 16 tup_sc_types = [ sc_types ]; 17 18 var 19 system_routs, $ set of all system routines (like read, print, 20 $ etc.) called by the program being analysed. 21 rout_maps, $ maps on routines 22 symbol_table, $ q1 symbol table maps 23 sym_om, $ - symtab pointer for omega 24 sym_sys, $ - symtab pointer for system scope 25 sym_dir, $ - symtab pointer for directory scope 26 sym_prog, $ - symtab pointer for program scope (member) 27 sym_main; $ - symtab pointer for main program (routine) 28 29 var 30 all_modules; $ set of all modules and program declared 31 $ in a directory 32 33 const 34 opcodes, 35 base_opcodes = { opcodes }, 36 tup_opcodes = [ opcodes ], 37 38 ops_exps = $ operations yielding valid expressions 39 { q1_in, q1_notin, q1_incs, 40 q1_eq, q1_ne, q1_lt, q1_ge, smfh 2 q1_pos, 41 q1_add, q1_sub, q1_mult, q1_slash, 42 q1_div, q1_mod, q1_exp, q1_atan2, 43 q1_max, q1_min, q1_npow, 44 q1_not, q1_even, q1_odd, 45 q1_isint, q1_isreal, q1_isstr, q1_isbool, 46 q1_isatom, q1_istup, q1_isset, q1_ismap, 47 q1_dom, q1_range, q1_pow, q1_nelt, 48 q1_abs, q1_char, q1_ceil, q1_floor, 49 q1_fix, q1_float, q1_sin, q1_cos, 50 q1_tan, q1_arcsin, q1_arccos, q1_arctan, 51 q1_tanh, q1_expf, q1_log, q1_sqrt, 52 q1_sign, q1_type, q1_str, q1_val, 53 q1_umin, 54 q1_of, q1_ofa, q1_subst, q1_end, 55 q1_set, q1_tup }, 56 57 ops_modify = $ operations which can modify program variables 58 { q1_with, 59 q1_less, q1_lessb, q1_lesse, q1_lessf, 60 q1_arb, q1_arbb, q1_arbe, q1_rand, 61 q1_newat, q1_time, q1_date, q1_na, 62 q1_set1, q1_tup1, 63 q1_inext, q1_next, q1_inextd, q1_nextd, 64 q1_sof, q1_sofa, q1_ssubst, q1_send, 65 q1_def, 66 q1_asn, q1_argin, q1_argout, q1_sargout }, 67 68 ops_extract = $ extractions 69 { q1_from, q1_rand, 70 q1_arb, q1_arbb, q1_arbe, 71 q1_next, q1_nextd, q1_inext, q1_inextd }, 72 73 ops_retrieve = $ retrievals 74 { q1_of, q1_ofa, q1_end, q1_subst }, 75 76 ops_update = $ updates 77 { q1_add, q1_sub, q1_mult, 78 q1_with, q1_less, q1_lesse, q1_lessb, 79 q1_from, q1_fromb, q1_frome, q1_lessf }, 80 81 ops_sin = $ sinister assignments 82 { q1_sof, q1_sofa, q1_ssubst, q1_send }, 83 84 ops_bin = $ binary operations 85 { q1_in, q1_notin, q1_incs, 86 q1_eq, q1_ne, q1_lt, q1_ge, smfh 3 q1_pos, 87 q1_add, q1_sub, q1_mult, q1_slash, 88 q1_div, q1_mod, q1_exp, q1_atan2, 89 q1_max, q1_min, q1_npow, q1_with, 90 q1_less, q1_lessb, q1_lesse, q1_lessf }, 91 92 ops_un = $ unary operators 93 { q1_not, q1_even, q1_odd, 94 q1_isint, q1_isreal, q1_isstr, q1_isbool, 95 q1_isatom, q1_istup, q1_isset, q1_ismap, 96 q1_arb, q1_arbb, q1_arbe, 97 q1_dom, q1_range, q1_pow, q1_nelt, 98 q1_abs, q1_char, q1_ceil, q1_floor, 99 q1_fix, q1_float, q1_sin, q1_cos, 100 q1_tan, q1_arcsin, q1_arccos, q1_arctan, 101 q1_tanh, q1_expf, q1_log, q1_sqrt, 102 q1_sign, q1_type, q1_str, q1_val, 103 q1_umin, q1_rand }, 104 105 ops_goto = $ all branches 106 { q1_goto, q1_case, smfg 6 q1_if, q1_ifnot, q1_bif, q1_bifnot, smfg 7 q1_ifasrt, q1_ifrand }, 108 109 ops_ovar = $ operations with o-variables 110 { q1_in, q1_notin, q1_incs, 111 q1_eq, q1_ne, q1_lt, q1_ge, smfh 4 q1_pos, 112 q1_add, q1_sub, q1_mult, q1_slash, 113 q1_div, q1_mod, q1_exp, q1_atan2, 114 q1_max, q1_min, q1_npow, 115 q1_from, q1_fromb, q1_frome, q1_with, 116 q1_less, q1_lessb, q1_lesse, q1_lessf, 117 q1_not, q1_even, q1_odd, 118 q1_isint, q1_isreal, q1_isstr, q1_isbool, 119 q1_isatom, q1_istup, q1_isset, q1_ismap, 120 q1_arb, q1_arbb, q1_arbe, 121 q1_dom, q1_range, q1_pow, q1_nelt, 122 q1_abs, q1_char, q1_ceil, q1_floor, 123 q1_fix, q1_float, q1_sin, q1_cos, 124 q1_tan, q1_arcsin, q1_arccos, q1_arctan, 125 q1_tanh, q1_expf, q1_log, q1_sqrt, 126 q1_sign, q1_type, q1_str, q1_val, 127 q1_umin, q1_rand, 128 q1_newat, q1_time, q1_date, q1_na, 129 q1_set, q1_set1, q1_tup, q1_tup1, 130 q1_inext, q1_next, q1_inextd, q1_nextd, 131 q1_of, q1_ofa, q1_subst, q1_end, 132 q1_sof, q1_sofa, q1_ssubst, q1_send, 133 q1_def, 134 q1_asn, q1_argin, q1_argout, q1_sargout }, 135 136 ops_ivar = $ operations whose arg1's are i-variables 137 { q1_push, q1_free, q1_use, q1_sargin, smfg 8 q1_if, q1_ifnot, q1_bif, q1_bifnot, smfg 9 q1_case, q1_asrt, q1_isom, q1_notom }, 139 140 ops_iter = $ iterators 141 { q1_inext, q1_next, q1_inextd, q1_nextd }, smfe 3 smfe 4 ops_typepred = $ type predicates smfe 5 { q1_isint, q1_isreal, q1_isstr, q1_isbool, smfe 6 q1_isatom, q1_istup, q1_isset, q1_ismap }, 142 143 ops_typeback = $ operators for which backwards type propagation 144 $ is meaningful. 145 { q1_in, q1_notin, q1_incs, smfh 5 q1_lt, q1_ge, q1_pos, 147 q1_add, q1_sub, q1_mult, q1_slash, 148 q1_div, q1_mod, q1_exp, q1_atan2, 149 q1_max, q1_min, q1_npow, q1_with, 150 q1_less, q1_lessb, q1_lesse, q1_lessf, 151 q1_not, q1_even, q1_odd, smfe 7 q1_isint, q1_isreal, q1_isstr, q1_isbool, smfe 8 q1_isatom, q1_istup, q1_isset, q1_ismap, 152 q1_arb, q1_arbb, q1_arbe, 153 q1_dom, q1_range, q1_pow, q1_nelt, 154 q1_abs, q1_char, q1_ceil, q1_floor, 155 q1_fix, q1_float, q1_sin, q1_cos, 156 q1_tan, q1_arcsin, q1_arccos, q1_arctan, 157 q1_tanh, q1_expf, q1_log, q1_sqrt, smfe 9 q1_sign, q1_type, q1_str, q1_val, smfe 10 q1_umin, q1_rand, 159 q1_set, q1_set1, q1_tup, q1_tup1, 160 q1_inext, q1_next, q1_inextd, q1_nextd, 161 q1_of, q1_ofa, q1_subst, q1_end, 162 q1_sof, q1_sofa, q1_ssubst, q1_send, 163 q1_argin, q1_argout, q1_sargin, smfg 10 q1_if, q1_ifnot, q1_bif, q1_bifnot, smfg 11 q1_asn, q1_asrt, q1_isom, q1_notom }, 165 166 ops_destuse1 = $ ops whose first ivar is used destructively 167 { q1_add, q1_sub, q1_mult, q1_with, 168 q1_less, q1_lessb, q1_lesse, q1_lessf }, 169 170 ops_destuse3 = $ same for third ivar 171 { q1_next, q1_sof, q1_sofa, q1_send }, 172 173 ops_destuse4 = $ same for fourth ivar 174 { q1_ssubst }, 175 176 ops_share1 = $ ops that share their first argument 177 { q1_of, q1_push, q1_asn, q1_next, 178 q1_arb, q1_nextd, q1_ofa, 179 q1_argin, q1_argout }, 180 181 ops_share2 = $ second argument 182 { q1_argin, q1_asn }, 183 184 ops_share3 = $ third argument 185 { q1_with, q1_sof, q1_sofa }, 186 187 ops_share4 = $ forth argument 188 { q1_argout }, 189 190 ops_smap = $ ops modifying a map leaving it a smap 191 { q1_sof }, 192 193 ops_local = $ ops supporting local representation 194 { q1_in, q1_notin, q1_less, q1_lessf, 195 q1_with, q1_of, q1_ofa, q1_sof, 196 q1_sofa }, 197 198 ops_sparse = $ ops supporting sparse repr (no hashing) 199 { q1_npow, q1_arb, q1_from, q1_dom, 200 q1_range, q1_nelt, q1_pow, q1_rand, 201 q1_next, q1_inext, q1_nextd, q1_inextd, 202 q1_asn, q1_argin, q1_sargin, q1_sargout, 203 q1_argout }, 204 205 ops_nonewval = $ ops with ovar that only transmit values 206 { q1_from, q1_fromb, q1_frome, 207 q1_arb, q1_arbb, q1_arbe, 208 q1_of, q1_ofa, 209 q1_asn, q1_argin, q1_argout }, 210 211 ops_fold = $ operations which can be constant folded 214 { q1_in, q1_notin, q1_incs, 215 q1_eq, q1_ne, q1_lt, q1_ge, smfh 6 q1_pos, 216 q1_add, q1_sub, q1_mult, q1_slash, 217 q1_div, q1_mod, q1_exp, q1_atan2, 218 q1_max, q1_min, q1_npow, q1_with, 219 q1_less, q1_lessb, q1_lesse, q1_lessf, 220 q1_not, q1_even, q1_odd, 221 q1_isint, q1_isreal, q1_isstr, q1_isbool, 222 q1_isatom, q1_istup, q1_isset, q1_ismap, 223 q1_dom, q1_range, q1_pow, q1_nelt, 224 q1_abs, q1_char, q1_ceil, q1_floor, 225 q1_fix, q1_float, q1_sin, q1_cos, 226 q1_tan, q1_arcsin, q1_arccos, q1_arctan, 227 q1_tanh, q1_expf, q1_log, q1_sqrt, 228 q1_sign, q1_type, q1_str, q1_val, 229 q1_umin, 230 q1_of, q1_ofa, q1_subst, q1_end, 231 q1_sof, q1_sofa, q1_ssubst, q1_send, smfb 1 q1_asn, q1_argin, q1_argout }, 233 234 ops_arith = $ arithmetical operations 235 { q1_add, q1_sub, q1_mult, q1_slash, 236 q1_div, q1_mod, q1_exp, 237 q1_max, q1_min }; 238 239 240 var cessor, $ maps each block into its successors 241 pred; $ maps each block into its predecessors 242 243 var intv_maps; $ maps on intervals 244 245 var ocrs_maps, $ maps on occurrences 246 oi_sets; $ sets on occurrences 247 248 var bfrom, $ maps each occurrence to previous ones 249 ffrom, $ maps each occurrence to subsequent ones 250 bfrom_dead; $ set of occurrences that can be dead on a path 251 252 var call_maps; $ maps on call instructions 253 254 var fom_syms, xom_syms, 255 fom_ocrs, xom_ocrs; 256 257 var dead_labs; $ labels of blocks being deleted 258 var cut_blocks; $ blocks bypassed by short_cut 259 260 var typ; 261 262 const 263 no = om, 264 maybe = 1, 265 yes = 1; 266 267 const 268 max_depth = 06, $ max nesting depth for type descriptors 269 max_len = 06; $ maximum length for known-length tuples 270 271 const 272 t_om = 'om', smfc 1 t_int = 'integer', 274 t_real = 'real', 275 t_string = 'string', 276 t_atom = 'atom', 277 t_elmt = 'elmt', 278 t_error = 'error', 279 t_tuple = 'tuple', 280 t_set = 'set', 281 t_map = 'map', 282 gross_types = { t_types }; 283 284 const 285 bsctyps = { t_om, t_int, t_real, t_string, t_atom, 286 t_tuple, t_set }; 287 288 const 289 type_zero = [ {} ], 290 type_om = [ { t_om } ], 291 type_gen = [ bsctyps, type_zero, false ], 292 293 type_notom = [ { t_int, t_real, t_string, 294 t_atom, t_tuple, t_set }, 295 type_gen, false ], 296 297 type_int = [ { t_int } ], 298 type_real = [ { t_real } ], 299 type_string = [ { t_string } ], 300 type_boolean = [ { t_atom } ], 301 type_atom = [ { t_atom } ], 302 303 type_tuple = [ { t_tuple }, type_gen, false ], 304 type_pair = [ { t_tuple }, [ type_notom, 305 type_notom ], true ], 306 307 type_set = [ { t_set }, type_gen, false ], 308 type_map = [ { t_set }, type_pair, false ]; 309 310 const 311 int_real = { t_int, t_real }, 312 int_real_str = { t_int, t_real, t_string }, 313 int_real_str_atom = { t_int, t_real, t_string, t_atom }, 314 str_tup = { t_string, t_tuple }, 315 str_tup_set = { t_string, t_tuple, t_set }, 316 set_tup = { t_tuple, t_set }, 317 tup_set_map = { t_tuple, t_set, t_map }; 318 319 const 320 type_int_real = [ int_real ], 321 type_int_real_str = [ int_real_str ], 322 type_str_tup = [ str_tup, type_gen, false ], 323 type_str_tup_set = [ str_tup_set, type_gen, false ]; 324$ 325$ the following constant map sends opcodes with a fixed output type to 326$ to that type: 327$ 328 const 329 fixed_typ = 330 { [ q1_eq, type_boolean ], 331 [ q1_ne, type_boolean ], 332 [ q1_isint, type_boolean ], 333 [ q1_isreal, type_boolean ], 334 [ q1_isstr, type_boolean ], 335 [ q1_isbool, type_boolean ], 336 [ q1_isatom, type_boolean ], 337 [ q1_istup, type_boolean ], 338 [ q1_isset, type_boolean ], 339 [ q1_ismap, type_boolean ], 340 [ q1_type, type_string ], 341 [ q1_str, type_string ], 342 [ q1_date, type_string ], 343 [ q1_time, type_int ], 344 [ q1_na, type_int ], 345 [ q1_newat, type_atom ] }; 346 347 const 348 based_modes, 349 base_based_modes = { based_modes }; 350 351 var ads_maps; 352 353 var form_table; 354 355 const 356 ft_types, 357 base_ft_types = { ft_types }, 358 tup_ft_types = [ ft_types ], 359 ft_mapcs, 360 base_ft_mapcs = { ft_mapcs }, 361 tup_ft_mapcs = [ ft_mapcs ]; 362 363 364$ the variable 'std_form' maps the basic types 'f_xxx' into their 365$ forms. it is built when the q1 tables are read in. 366$$$ ???? logic here unclear, needs careful check 367 368 var std_form; 369 370$ the following constant map is used to obtain the 371$ type class of each of the above 'f_xxx' form types 372 const 373 simple_type = 374 { [ f_gen, 'gen' ], 375 [ f_sint, 'int' ], 376 [ f_sstring, 'string' ], 377 [ f_atom, 'atom' ], 378 [ f_latom, 'atom' ], 379 [ f_elmt, 'elmt' ], 380 [ f_uint, 'int' ], 381 [ f_ureal, 'real' ], 382 [ f_int, 'int' ], 383 [ f_string, 'string' ], 384 [ f_real, 'real' ], 385 [ f_ituple, 'tuple' ], 386 [ f_rtuple, 'tuple' ], 387 [ f_ptuple, 'tuple' ], 388 [ f_tuple, 'tuple' ], 389 [ f_mtuple, 'tuple' ], 390 [ f_uset, 'set' ], 391 [ f_lset, 'set' ], 392 [ f_rset, 'set' ], 393 [ f_umap, 'map' ], 394 [ f_lmap, 'map' ], 395 [ f_rmap, 'map' ], 396 [ f_lpmap, 'map' ], 397 [ f_limap, 'map' ], 398 [ f_lrmap, 'map' ], 399 [ f_rpmap, 'map' ], 400 [ f_rimap, 'map' ], 401 [ f_rrmap, 'map' ], 402 [ f_base, 'base' ], 403 [ f_pbase, 'base' ], 404 [ f_uimap, 'map' ], 405 [ f_urmap, 'map' ], 406 [ f_error, 'error' ], 407 [ f_proc, 'proc' ], 408 [ f_memb, 'memb' ], 409 [ f_lab, 'lab' ] }; 410 411 412 const 413 ft_fint = { f_sint, f_uint, f_int }, 414 ft_freal = { f_ureal, f_real }, 415 ft_funt = { f_uint, f_ureal }, 416 ft_fnum = { f_sint, f_uint, f_int, 417 f_ureal, f_real }, 418 ft_fstring = { f_sstring, f_string }, 419 ft_fprim = { f_sint, f_sstring, f_atom, f_uint, 420 f_ureal, f_int, f_string, f_real }, 421 ft_ftup = { f_tuple, f_ituple, f_rtuple, f_ptuple, 422 f_mtuple }, 423 ft_fset = { f_uset, f_lset, f_rset, 424 f_umap, f_uimap, f_urmap, 425 f_lmap, f_limap, f_lrmap, f_lpmap, 426 f_rmap, f_rimap, f_rrmap, f_rpmap, 427 f_base, f_pbase }, 428 ft_fmap = { f_umap, f_uimap, f_urmap, 429 f_lmap, f_limap, f_lrmap, f_lpmap, 430 f_rmap, f_rimap, f_rrmap, f_rpmap }, 431 ft_floc = { f_lset, 432 f_lmap, f_limap, f_lrmap, f_lpmap }, 433 ft_frem = { f_rset, 434 f_rmap, f_rimap, f_rrmap, f_rpmap }, 435 ft_fbased = { f_elmt, f_lset, f_rset, 436 f_lmap, f_limap, f_lrmap, f_lpmap, 437 f_rmap, f_rimap, f_rrmap, f_rpmap }, 438 ft_fimap = { f_limap, f_rimap, f_uimap }, 439 ft_frmap = { f_lrmap, f_rrmap, f_urmap }, 440 ft_fbase = { f_base, f_pbase }; 441 442 443 const 444 ft_usetmaps = 445 { f_uset, f_umap, f_uimap, f_urmap }; 446 447 const 448 localtp = 449 { [ f_uset, f_lset ], 450 [ f_umap, f_lmap ], 451 [ f_uimap, f_limap ], 452 [ f_urmap, f_lrmap ] }; 453 454 const 455 remotetp = 456 { [ f_uset, f_rset ], 457 [ f_umap, f_rmap ], 458 [ f_uimap, f_rimap ], 459 [ f_urmap, f_rrmap ] }; 460 461 var push_former; $ maps each push instruction to the set or 462 $ tuple former following it. 463 464 var 465 var_maps, $ maps on variables 466 exp_maps, $ maps on expressions 467 control_params; $ program parameters 468 var statistics; $ used to collect execution statistics 469$ 470$ the following global objects are used for program annotation purposes. 471$ we annotate each program analysed in the following manner: 472$ 473$ 1. for each routine p, we print a summary of all global variables 474$ referenced or defined, formatted as reads/writes lists. 475$ 476$ 2. for each routine p, we print a summary of all routines q which are 477$ called from p, formatted as an imports list. (this information is 478$ contained in the call graph.) 479$ 480$ 3. for each destructive use du of a global variable x in a routine p, 481$ we print warning message. 482$ 483 var 484 globals_du, $ routine -> destructive use occurrence smfk 3 globals_e, $ maps routines to globals exposed 485 globals_r, $ maps routines to globals used 486 globals_w, $ maps routines to globals defined 487 messages; $ cstmt_count -> severity -> message 488 489 490$ 491$ initialise the maps in the setl data structures to the null set. 492$ 493 init 494 $ initilise symbol table maps 495 name := {}, value := {}, scope := {}, 496 form := {}, alias := {}, 497 is_read := {}, is_write := {}, is_const := {}, 498 is_internal := {}, is_temp := {}, is_store := {}, 499 is_stk := {}, is_param := {}, is_repr := {}, 500 is_init := {}, is_seen := {}, is_back := {}, 501 is_rec := {}, next_sym := {}, 502 503 $ initialise form maps 504 ft_type := {}, ft_mapc := {}, ft_elmt := {}, 505 ft_dom := {}, ft_im := {}, ft_imset := {}, 506 ft_base := {}, 507 ft_low := {}, ft_lim := {}, ft_tup := {}, 508 ft_hashok := {}, ft_neltok := {}, ft_pos := {}, 509 ft_num := {}, ft_deref := {}, next_form := {}, 510 basesymb := {}, std_form := {}, 511 512 $ initialise scope maps 513 scopes := [], cont_scopes := {}, 514 sc_type := {}, sc_nprocs := {}, 515 sc_stmt_ct := {}, sc_estmt_ct := {}, 516 first_sym := {}, last_sym := {}, 517 first_form := {}, last_form := {}, 518 first_block := {}, last_block := {}, 519 all_modules := {}, 520 521 $ initialise routine maps 522 routs := {}, rentry := {}, rexit := {}, 523 rstop := {}, rparams := {}, system_routs := {}, 524 membof := {}, 525 526 $ initialise interval maps 527 ints := {}, proper_ints := {}, 528 529 $ initialise block maps 530 routof := {}, next_block := {}, first_inst := {}, 531 last_inst := {}, cessor := {}, pred := {}, 532 intof := {}, int_nodes := {}, vedges := {}, 533 cut_blocks := {}, dead_labs := {}, 534 535 $ initialise instruction maps 536 opcode := {}, args := {}, occs := {}, 537 blockof := {}, stmtof := {}, copy_flag := {}, 538 share_flag := {}, next_inst := {}, 539 540 $ initialise maps on call instructions 541 cgraph := {}, callsin := {}, callproc := {}, 542 cg_sccs := [], scc_nodes := {}, scc_d := {}, 543 544 $ initilise maps on occurrences 545 instno := {}, argno := {}, 546 all_oi := {}, all_o := {}, all_i := {}, 547 548 $ initialise maps on variables smfe 11 variables := {}, uservars := {}, itervars := {}, smfe 12 globalvars := {}, localvars := {}, occsof := {}, 551 552 $ initilise maps on expressions 553 globalexps := {}, localexps := {}, allexps := {}, 554 opcexp := {}, argsexp := {}, dependon := {}, 555 556 $ initialise maps for automatic documentation smfk 4 globals_du := {}, globals_e := {}, globals_r := {}, smfk 5 globals_w := {}, smfk 6 558 messages := {}, 560 statistics := [], 561 push_former := {}; 562 563 1 .=member mman5a 2 .title 'module specifications' 3 4$ overall design of the optimizer 5$ ------------------------------- 6 7$ in this section we describe the overall organization of the optimizer. 8$ the optimizer contains one top level routine for each section; these 9$ routines are called in sequence from the main program. 10 11$ in describing each section, we concentrate on the following points: 12 13$ 1. the general purpose of the section 14$ 2. the algorithm used 15$ 3. the principal routines contained in the section 16$ 4. the tables produced as output of the section 17$ 5. the diagnostics, warnings, etc. issued to the user. 18 19 20 .title 'main program' 21 22$ main program 23$ ------------ 24 25$ the main program simply calls successive phases of the optimizer 26$ till the program converges. 27 28 program setl_optimizer - main: 29 30 imports 31 opt_ini, $ initialize 32 cgraph_analysis, $ call graph analysis 33 find_intervals, $ interval analysis 34 live, $ live variable analysis 35 csx, $ common subexpr elimination+code motion 36 find_bfrom, $ bfrom computation smfb 2 find_region_constants, $ flow-constant loop analysis 37 type_find, $ type analysis 38 auto_data, $ automatic data structure selection 39 conv_optimize, $ conversion optimization 40 copy_optimize, $ copy optimization 41 opt_term; $ print tables, etc. 42 43 writes 44 statistics; 45 46 1 .=member mutl5b 2 .title 'utilities' 3 4$ utilities 5$ --------- 6 7 module setl_optimizer - util: 8 9 exports 10 print_utils, 11 utilities; 12 13 writes 14 oi_sets, $ sets on occurrences 15 var_maps, $ maps on variables 16 exp_maps, $ maps on expressions 17 ocrs_maps, $ maps defining occurrences 18 dead_labs, 19 q1_vars, $ variables defining q1 20 messages; $ cstmt_count -> severity -> message 21 22 reads 23 q1_consts, q1_vars, $ constants and variables defining q1 24 typ_consts, $ all constants for the type finder 25 cessor, 26 pred, 27 intv_maps; $ maps on intervals 28 29 30 .title 'compiler interface' 31 32$ compiler interface 33$ ------------------ 34 35$ the setl compiler is written in a low level language called 'little'. 36$ the semantic pass writes the q1 tables onto a binary file, which is 37$ then read in by the code generator. 38 39$ the interface module contains two main routines. the first reads in 40$ the file created by the semantic pass using the setl binary i/o, and 41$ converts the q1 tables to setl data structures. the second routine 42$ converts the setl data structures back to little data structures and 44$ writes them onto the q1 file. 45 46 module setl_optimizer - interface: 47 48 reads 49 q1_consts, $ constants used to define q1 50 control_params; $ program parameters 51 52 writes 53 all_modules, 54 ocrs_maps, $ maps defining occurrences 55 q1_vars; $ variables defining q1 56 57 exports 58 read_q1, 59 write_q1; 60 61 imports 62 can_conv(rd, rd), 63 utilities, 64 print_utils, 65 dmp(rd, rd(*)); 66 67 68 module setl_optimizer - dumps: 69 70 exports 71 dmp(rd, rd(*)), 72 print_summary(rd); $ print scope summary 73 74 imports 75 print_utils; 76 77 reads 78 q1_consts, $ constants used to define q1 79 q1_vars, $ variables defining q1 80 var_maps, $ maps on variables 81 all_modules, $ set of all modules smfk 7 globals_e, $ maps routines to globals exposed 82 globals_r, $ maps routines to globals used 83 globals_w, $ maps routines to globals defined 84 globals_du, $ destructively used global variables 85 call_maps, $ maps on the call graph 86 ocrs_maps, $ maps defining occurrences 87 cessor, smfk 8 ffrom, 88 intv_maps; $ maps on intervals 89 90 91 .title 'initialization' 92 93$ phase 1. initialization 94$ ----------------------- 95 96$ in this phase we read the control card, read the q1 tables generated 97$ by the semantic pass, and make a prepass over the program. 99 100$ the prepass does the following things: 101 102$ 1. modify the input so that it appears to be a complete program. 103$ 2. compute the 'oi_sets' such as all_oi. 104$ 3. fill in the fourth arguments of argin and argout instructions. 105$ 4. replace each 'from' instruction with an 'arb' followed by a 'less' 106$ 5. compute the 'var_maps', 'exp_maps' and some of the 'call_maps' 107$ (see section on 'various initial maps' for details). 108 109$ steps (3) and (4) are reversed before the program is written out for 110$ the code generator. 111 112 module setl_optimizer - optinit: 114 115 imports 116 print_utils, 117 read_q1, 118 dmp(rd, rd(*)), 119 utilities; 120 121 reads 122 q1_consts; $ constants used to define q1 123 124 writes 125 control_params, $ program parameters 126 q1_vars, $ variables defining q1 127 all_modules, 128 push_former, 129 cut_blocks, 130 var_maps, $ maps on variables 131 exp_maps, $ maps on expressions 132 ocrs_maps, $ maps defining occurrences 133 call_maps, $ maps on the call graph 134 oi_sets, $ sets on occurrences 135 messages, $ cstmt_count -> severity -> message 136 statistics; $ used to collect execution statistics 137$ note that these names are macros for groups of variables defined 138$ above. 139 140 exports 141 opt_ini; 142 143 1 .=member mcgr5c 2 .title 'call graph analysis' 3 4$ phase 2. call graph analysis 5$ ---------------------------- 6 7$ this phase analyses the call graph and builds up some auxiliary 8$ related maps. the relevant procedures are contained in the 9$ 'dataflow_solver' module. for details see the section 'data-flow 10$ analysis package' below. 11 1 .=member mint5d 2 .title 'interval analysis' 3 4$ phase 3. interval analysis 5$ -------------------------- 6 7$ interval analysis is performed using a variation of tarjans 8$ algorithm. the outputs of the algorithm are described in the 9$ previous section, 'control graph and interval analysis'. 10 11 13 module setl_optimizer - interval_analysis: 14 16 imports 17 dmp(rd, rd(*)), 18 print_utils, 19 utilities; 20 21 reads 22 control_params, $ program parameters 23 q1_consts; $ constants used to define q1 24 25 writes 26 q1_vars, $ variables defining q1 27 dead_labs, 28 cut_blocks, 29 intv_maps, $ maps on intervals 30 cessor, 31 pred, 32 statistics; $ used to collect execution statistics 33 34 exports 35 find_intervals; 36 38 1 .=member mcdm5e 2 .title 'redundant expression elimination' 3 4$ phase 4. redundant expression elimination and code motion 5$ --------------------------------------------------------- 6 7$ this phase finds redundant subexpressions using available 8$ expression determination. it also moves code out of loops and 9$ eliminates all redundant expression computations. 10 11$ the code motion procedure uses the interval structure to move code 12$ out of intervals. each interval is entered through a block known 13$ as the interval head; each interval head has a single predecessor 14$ block which is outside the interval. this is known as the 15$ target block of the interval. code moved out of an interval is 16$ moved to the interval's target block. this is essentialy a 17$ matter of concatenating the instruction to the target block. 18 19$ instructions are deleted either by changing their opcode to 20$ 'q1_noop', or, more efficiently, when the previous instruction 21$ is also available, by deleting them from the linked list of 22$ their block. 23 24 25 module setl_optimizer - availexp_analysis: 26 27 exports 28 csx; 29 30 imports 31 print_utils, 32 utilities, 33 .comp_syms(rd, rd), 34 interproc_fwd_analysis_syms 35 (rw, wr, rd, rd, rd, rd, rw, wr, rd), 36 intraproc_fwd_analysis_syms 37 (rd, rw, wr, rd, rd, rd, rd, rw, wr, rd); 38$ see the section 'data flow analysis package' below for details 39$ concerning these routines. 40 41 reads 42 control_params, $ program parameters 43 q1_consts, $ constants used to define q1 44 call_maps, $ maps on the call graph 45 intv_maps, $ maps on intervals 46 cessor, pred; 48 49 writes 50 fom_syms, xom_syms, 51 oi_sets, $ sets on occurrences 52 var_maps, $ maps on variables 53 exp_maps, $ maps on expressions 54 q1_vars, $ variables defining q1 55 messages, $ cstmt_count -> severity -> message 56 statistics; $ used to collect execution statistics 57 58 1 .=member mlva5f 2 .title 'live variable analysis' 3 4 module setl_optimizer - live_analysis: 5 6 exports 7 live; 8 9 imports 10 utilities, 11 print_utils, 12 .comp_syms(rd, rd), 13 interproc_back_analysis_syms(rw, wr, rd, rd, rd), 14 intraproc_back_analysis_syms(rd, rw, wr, rd, rd, rd); 15 16 reads 17 all; 18 19 writes 20 fom_syms, xom_syms, 21 messages, $ cstmt_count -> severity -> message 22 statistics; $ used to collect execution statistics 23 24 1 .=member dfap5g 2 .title 'data-flow analysis package' 3 4$ data-flow analysis package 5$ --------------------------- 6 7$ to facilitate several previously mentioned optimization phases, we 8$ provide a general purpose package of routines which solve data flow 9$ problems of the bitvectoring class under various situations (forward 10$ or backward problems, interprocedural or intraprocedural solution). 11$ this package also includes the call graph analysis routine mentioned 12$ earlier. 13$ 14$ nb. there are actually two modules which are almost identical. 15$ they differ in that the first module is used for problems where the 16$ the lattice elements are symbols (elmt syms), while the second 17$ module is used for problems where the lattice elements are oc- 18$ currences (elmt ocrs). 19$ 20 module setl_optimizer - dataflow_solver_syms: 21 22 imports 23 print_utils; 24 25 exports 26 .comp_syms(rd, rd), 27 cgraph_analysis, 28$ 29$ the following routines perform data flow analysis for the 30$ following respective cases: interprocedurally and forward, 31$ intraprocedurally and forward, interprocedurally and backwards, 32$ intraprocedurally and backwards. 33$ 34 interproc_fwd_analysis_syms 35 (rw, wr, rd, rd, rd, rd, rw, wr, rd), 36 intraproc_fwd_analysis_syms 37 (rd, rw, wr, rd, rd, rd, rd, rw, wr, rd), 38$ 39$ the parameters of the interprocedural analyser are as follows: 40$ f: maps each flow edge into a data-flow map 41$ soln: the solution map (soln: flow node --> bitvector data) 42$ id: identity map for analysis 43$ zero: initial (default) data-value for analysis 44$ meet_flag: true if meet analysis, false if join analysis 45$ move_code: true if code motion required, false otherwise 46$ exposed: exposed block 'events' (needed only for code motion) 47$ insert: maps each interval to 'events' to be inserted at 48$ its entry (meaningless if code motion not required) 49$ 50$ the intraprocedural analyser has the same parameters, with an 51$ additional first parameter, equal to the procedure to be analysed 52$ 53 interproc_back_analysis_syms 54 (rw, wr, rd, rd, rd), 55 intraproc_back_analysis_syms 56 (rd, rw, wr, rd, rd, rd); 57$ 58$ the parameters of the interprocedural analyser coincide with 59$ the first five parameters of the corresponding forward analyser. 60$ similar correspondence exists between the intraprocedural 61$ analysers. 62$ 63 reads 64 name, 65 fom_syms, xom_syms, 66 cessor, pred, 67 routof, sym_main, 68 rout_maps, 69 control_params; $ program parameters 70 71 writes 72 is_rec, $ flags recursive routines (symtab) 73 intv_maps, $ maps on intervals 74 call_maps; $ maps on the call graph 75 76 77 module setl_optimizer - dataflow_solver_ocrs: 78 79 exports 80 .comp_ocrs(rd, rd), 81 interproc_fwd_analysis_ocrs 82 (rw, wr, rd, rd, rd, rd, rw, wr, rd), 83 intraproc_fwd_analysis_ocrs 84 (rd, rw, wr, rd, rd, rd, rd, rw, wr, rd), 85 interproc_back_analysis_ocrs 86 (rw, wr, rd, rd, rd), 87 intraproc_back_analysis_ocrs 88 (rd, rw, wr, rd, rd, rd); 89 90 reads 91 fom_ocrs, xom_ocrs, 92 cessor, pred, 93 routof, sym_main, 94 rout_maps, 95 call_maps; $ maps on the call graph 96 97 writes 98 intv_maps; $ maps on intervals 99 100 1 .=member mflo5h 2 .title 'data flow analysis' 3 4$ phase 5. data flow analysis 5$ --------------------------- 6 7$ this phase builds bfrom, ffrom, and bfrom_dead. these maps are 8$ defined in the section 'data flow maps' above. 9 10 11 module setl_optimizer - bfrom_analysis: 12 13 exports 14 find_bfrom; 15 16 imports 17 print_utils, 18 .comp_ocrs(rd, rd), 19 interproc_fwd_analysis_ocrs 20 (rw, wr, rd, rd, rd, rd, rw, wr, rd), 21 intraproc_fwd_analysis_ocrs 22 (rd, rw, wr, rd, rd, rd, rd, rw, wr, rd); 23$ see description of 'data flow analysis package' above for 24$ detailed account of parameters of these routines. 25 26 reads 27 var_maps, $ maps on variables 28 q1_consts, $ constants used to define q1 29 q1_vars, $ variables defining q1 30 call_maps, $ maps on the call graph 31 ocrs_maps, $ maps defining occurrences 32 oi_sets, $ sets on occurrences 33 intv_maps, $ maps on intervals 34 cessor, pred, 36 control_params; $ program parameters 37 38 writes 39 fom_ocrs, xom_ocrs, 40 bfrom, ffrom, 42 bfrom_dead, smfk 9 globals_e, $ maps routines to globals exposed 43 globals_r, $ maps routines to globals used 44 globals_w, $ maps routines to globals defined 45 messages, $ cstmt_count -> severity -> message 46 statistics; $ used to collect execution statistics 47 48 smfb 3 smfb 4 smfb 5 module setl_optimizer - region_constants: smfb 6 smfb 8 exports smfb 9 find_region_constants; smfb 10 reads smfb 11 q1_consts, $ constants used to define q1 smfb 12 q1_vars, $ variables defining q1 smfb 13 var_maps, $ maps on variables smfb 14 ocrs_maps, $ maps defining occurrences smfb 15 oi_sets, $ sets on occurrences smfb 16 bfrom, $ occurrence -> preceding occurrences smfb 17 intv_maps, $ maps on intervals smfb 18 call_maps, $ maps on the call graph smfb 19 control_params; $ program parameters smfb 20 writes smfb 21 messages, $ cstmt_count -> severity -> message smfb 22 statistics; $ used to collect execution statistics smfb 23 smfb 24 1 .=member mtyp5i 2 .title 'type finding' 3 4$ phase 8 - type finding 5$ ---------------------- 6 7$ this phase is an extension of the type finder described in a ph.d. 8$ thesis by aaron tannenbaum. it calculates a map called 'typ' which 9$ sends each occurrence into a 'type descriptor'. the structure of 10$ type descriptors is described above in the section 'types'. 11$ 12$ essentially the type finder collects two pieces of information 13$ about each occuurrence: 14$ 15$ 1. its type, in the sense of the setl 'type' operator. 16$ 17$ 2. a flag indicating whether the occurrence is definitely omega, 18$ possibly omega, or definitely not omega. 19$ 20$ (1) and (2) are determined recursively for the components of sets and 21$ tuples. 22$ 23$ the type finder is driven by the bfrom and ffrom maps. it exports 24$ a main procedure and three predicates on types. these predicates 25$ use a three valued logic, and return values given by the constants 26$ yes, no, and maybe. 27 28 module setl_optimizer - typfind: 29 30 reads 31 q1_consts, $ constants used to define q1 32 q1_vars, $ variables defining q1 33 ocrs_maps, $ maps defining occurrences 34 oi_sets, $ sets on occurrences 35 typ_consts, $ all constants for the type finder 36 var_maps, $ maps on variables 37 exp_maps, $ maps on expressions 38 cessor, pred, 39 intv_maps, $ maps on intervals 40 call_maps, $ maps on the call graph 41 bfrom, ffrom, bfrom_dead, 42 control_params; $ program parameters 43 44 writes 45 typ, $ maps occurrences to their types smfg 12 bfrom, ffrom, bfrom_dead, 46 messages, $ cstmt_count -> severity -> message 47 statistics; $ used to collect execution statistics 48 49 imports 50 print_utils, 51 utilities; 52 53 exports 54 type_find, $ top level routine 55 .is_pair(tp), $ true for pairs 56 .is_map(tp); $ true for maps 57 1 .=member adac5k 2 .title 'automatic data structure selection' 3 4$ phase 10 - automatic data structure selection 5$ --------------------------------------------- 6 7$ the automatic data structure selection algorithm, which is described 8$ in detail in the member admn15 below, is driven by the data flow maps 9$ and the typ map, the result of the type finder module. 10$ 11$ to allow for the results of the automatic data structure selection, 12$ we ultimately take three types of action: 13$ 14$ 1. we add a new symbol table entry for each base. 15$ 2. we build a map 'basetyp' from bases to type descriptors. 16$ 3. we reset typ(oi) for each oi we decide to base. 17 18$ type descriptors for bases have: 19$ 20$ grosstyp: 'base' 21$ comptyp: type of elements 22$ 23$ type descriptors for elements have: 24$ 25$ grosstyp: 'elmt' 26$ comptyp: not used 27$ basenam: symbol table pointer for base 28$ 29$ type descriptors for primitive types (ie. integer, real, string, and 30$ atom) have a grosstype of t_int, t_real, etc., and a component type of 31$ type_zero, our error type. non-primitive types (ie. tuple, set, and 32$ map) have a grosstype of t_tuple, t_set, or t_map, and their component 33$ type is itself a type descriptor for the component type of the tuple, 34$ the element type of the set, etc. thus a type descriptor for a map 35$ from string to atom would have a component type of known-length tuple 36$ of length two, whose first component must be a string, and whose 37$ second component must be an atom. 38$ 39$ the automatic data-structure selection algorithm can take one of two 40$ choices with respect to user-supplied reprs. it is not clear which 41$ choice is correct: 42$ 43$ 1. it can begin by using the user supplied reprs to refine and 44$ validate the 'typ' map. 45$ 46$ 2. it can ignore the user-supplied reprs until it is done, then use 47$ them to refine and validate its results. 48$ 49 module setl_optimizer - auto_dstruct: 50 51 exports 52 auto_data; 54 imports 55 utilities, 56 print_utils, 57 .is_pair(tp), $ true for pairs 58 .is_map(tp); $ true for maps 60 reads 61 q1_consts, $ constants used to define q1 62 variables, $ set of all variables 63 typ_consts, $ all constants for the type finder 64 bfrom, ffrom, bfrom_dead, 65 ocrs_maps, $ maps defining occurrences 66 oi_sets, $ sets on occurrences 67 control_params; $ program parameters 69 writes 70 q1_vars, $ variables defining q1 71 typ, $ maps occurrences to their types 72 ads_maps, 73 statistics; $ used to collect execution statistics 74 75 1 .=member cnvo5l 2 .title 'conversion optimization' 3 4$ phase 11 - conversion optimization 5$ ---------------------------------- 6 7$ conversion optimization completes the work begun with the type finding 8$ and automatic data structure selection phases. 9$ 10$ at this point the 'typ' map contains as much information on the type 11$ and representation of each occurrence as we know how to collect. 12$ 13$ we must now split each variable 'x' into several variables x1, ..., xn 14$ such that each xi has the same 'typ' for all its occurrences. in the 15$ process it may be necessary to add assignments of the form 'xi = xj'. 16$ these assignments will actually be treated as conversions by the code 17$ generator. 18$ 19$ once we have done name splitting we are able to talk about the type of 20$ a variable 'xi'. we then convert the xi's type descriptor into a form 21$ and set form(xi). 22$ 23$ then we perform a data flow analysis to determine when and where 24$ should conversions from one split variable to another be performed. 25 26 27 module setl_optimizer - conversion_analysis: 28 29 exports 30 conv_optimize, 31 can_conv(rd, rd); 33 imports 34 utilities, 35 print_utils, 36 dmp(rd, rd(*)), 37 .comp_syms(rd, rd), 38 interproc_fwd_analysis_syms 39 (rw, wr, rd, rd, rd, rd, rw, wr, rd), 40 intraproc_fwd_analysis_syms 41 (rd, rw, wr, rd, rd, rd, rd, rw, wr, rd), 42 interproc_back_analysis_syms 43 (rw, wr, rd, rd, rd), 44 intraproc_back_analysis_syms 45 (rd, rw, wr, rd, rd, rd); 47 reads 48 q1_consts, $ constants used to define q1 49 var_maps, $ maps on variables 50 ocrs_maps, $ maps defining occurrences 51 intv_maps, $ maps on intervals 52 typ_consts, $ all constants for the type finder 53 control_params; $ program parameters 55 writes 56 fom_syms, xom_syms, 57 q1_vars, $ variables defining q1 58 basesymb, smfk 10 occsof, $ maps variables to their occurrences 59 variables, $ set of all variables 60 uservars, $ set of all user-defined variables 61 bfrom, ffrom, bfrom_dead, 62 typ, $ maps occurrences to their type 63 ads_maps, 64 oi_sets, $ sets on occurrences 65 messages, $ cstmt_count -> severity -> message 66 statistics; $ used to collect execution statistics 67 1 .=member mcpy5m 2 .title 'copy optimisation' 3 4$ phase 9 - copy optimisation 5$ --------------------------- 6 7$ copy optimisation is done using the technique described in 8$ newsletter 176. we associate a dummy share bit variable with 9$ each variable in the program. suppose that 'x' is a 10$ variable and 'sx' is its shadow variable. 11$ 12$ we assume that every operaton whch puts 'x' into a set or 13$ tuple causes 'x' to be shared, i.e. causes its share bit to 14$ be set to true. we treat this as a definition of 'sx'. 15$ 16$ we also assume that each time 'x' is used destructively it 17$ is conditionally copied. by this we mean that the library 18$ checks x's share bit. if the share bit is set, the library 19$ copies 'x' and resets it's share bit to false. 20$ 21$ the copy optimisation algorithm begins by indicating the 22$ uses and definitions of the share bits 'sx'. it then performs 23$ code motion, redundant subexpression elimination, dead code 24$ elimination and constant propagation on the shadow variables. 25$ 26$ at the end of the algorithm we have filled in estimates concerning 27$ uses and definitions of share bits. we then create two maps on 28$ instructions: 29$ 30$ copy_flag: 31$ 32$ if 'i' is an instruction, then copy_flag(i) has the 33$ following values: 34$ 35$ copy_pre: copy arg1(i) before executing 'i' 36$ 37$ copy_test: copy arg1(i) before executing 'i' iff its 38$ share bit is set. 39$ 40$ copy_no: don't copy arg1(i). 41$ 42$ note that arg1 is the only argument which is ever used 43$ destructively. 44$ 45$ share_flag: 46$ 47$ share_flag(i) is true if one of i's inputs must have 48$ its share bit set. it is always obvious from the opcode of 'i' 49$ which input we are talking about. 50 51 module setl_optimizer - copy_optimization: 52 53 exports 54 copy_optimize; 55 56 imports 57 utilities, 58 print_utils, 59 .comp_syms(rd, rd), 60 .comp_ocrs(rd, rd), 61 interproc_back_analysis_syms 62 (rw, wr, rd, rd, rd), 63 intraproc_back_analysis_syms 64 (rd, rw, wr, rd, rd, rd), 65 interproc_fwd_analysis_ocrs 66 (rw, wr, rd, rd, rd, rd, rw, wr, rd), 67 intraproc_fwd_analysis_ocrs 68 (rd, rw, wr, rd, rd, rd, rd, rw, wr, rd); 69 70 reads 71 q1_consts, $ constants used to define q1 72 typ, $ maps occurrences to their types 73 typ_consts, $ all constants for the type finder 74 bfrom, ffrom, $ data flow maps 75 var_maps, $ maps on variables 76 ocrs_maps, $ maps defining occurrences 77 oi_sets, $ sets on occurrences 78 cessor, $ 79 control_params; $ program parameters 80 81 writes 82 fom_syms, xom_syms, 83 fom_ocrs, xom_ocrs, 84 q1_vars, $ variables defining q1 85 globals_du, $ destructively used global variables 86 messages, $ cstmt_count -> severity -> message 87 statistics; $ used to collect execution statistics 88 89 1 .=member outp5n 2 .title 'interface with code generator' 3 4$ phase 13 - output to the code generator 5$ -------------------------------------------- 6 7$ this is the final phase of the optimizer. we do three things: 8 9$ 1. change all code sequences 'a := arb b; b less:= a;' to 'a from b' 10$ 2. remove the fourth argument of all argin and argout instructions. 11$ 3. write out the q1 tables. 12 13 module setl_optimizer - optend: 15 16 exports 17 opt_term; 19 imports 20 utilities, 21 print_utils, 22 dmp(rd, rd(*)), 23 print_summary(rd), $ print scope summary 24 write_q1; 26 writes 27 q1_vars, $ variables defining q1 28 statistics; $ used to collect execution statistics 30 reads 31 control_params, $ program parameters 32 exp_maps, $ maps on expressions 33 push_former, 34 dead_labs, 35 q1_consts, $ constants used to define q1 36 messages; $ cstmt_count -> severity -> message 37 38 1 .=member reprs6 2 .title 'reprs' 3 4$ data structures for global variables and procedures 5$ --------------- --- ------ --------- --- ---------- 6 7 repr 8$ 9$ there are two ways to represent booleans in setl, namely as setl 10$ booleans, or as integers in the range from 1 to 1. while the former 11$ is somewhat more elegant, the latter requires, when packed, only one 12$ bit of storage. note that the range 'integer 1 .. 1' is dual-valued, 13$ since this range implicitly includes om. in this mode, we represent 14$ true as 1, and false as om. 15$ 16 mode bool: integer 1..1; 17 18 mode index: integer 1..65535; 19 20 21$$-- plex base syms; $ symbol table 22 plex base forms; $ form table 23 plex base blocks; $ basic block table 24 plex base insts; $ instruction or code table 25$$-- plex base ocrs; $ symbol occurrences 26 27 base syms: atom; 28 mode symbol: elmt syms; 29 30 base ocrs: atom; 31 mode occurrence: elmt ocrs; 32 33 base df_base_ocrs: occurrence; 34 mode df_elmt_ocrs: remote set(elmt df_base_ocrs); 35 mode df_map_ocrs: tuple(df_elmt_ocrs, df_elmt_ocrs); 36 37 base df_base_syms: symbol; 38 mode df_elmt_syms: remote set(elmt df_base_syms); 39 mode df_map_syms: tuple(df_elmt_syms, df_elmt_syms); 40 41 base df_nodes: elmt blocks; 42 mode df_node: elmt df_nodes; 43 44 base df_edges: tuple(df_node, df_node); 45 mode df_edge: elmt df_edges; 46 47 base expressions: symbol; 48 mode expression: elmt expressions; 49$ 50$ there are several maps from scopes and routines defined. for greater 51$ space efficiency, we create bases for their domains, and base their 52$ respective ranges locally on these two bases. 53$ 54 base base_scopes: symbol; 55 56 base base_routs: elmt base_scopes; 57 mode routine: elmt base_routs; 58 59 base 60 base_sc_types, 61 base_ft_types, 62 base_ft_mapcs, 63 base_opcodes, 64 base_based_modes, 65 base_copy_actions: string; 66 67 base tent_bases: atom; 68 mode tent_base: elmt tent_bases; 69 70 base gross_types: string; 71 mode basic_type: elmt gross_types; 72 mode gross_type: remote set(basic_type); 73$ 74$ although there are no maps on types, or sets on types, we store all 75$ type descriptors in a base. this allows us to do fast type equality 76$ tests. 77$ 78 base types: tuple( 79 gross_type, 80 general, 81 boolean, 82 *, $$-- is_based 83 elmt base_based_modes, 84 elmt base_ft_mapcs 85 ); 86$ 87$ maps on symbols use the following data structures: 88$ (see section symbol table for a more detailed account of these maps) 89$ 90 name: local smap(symbol) string; 91 value: local smap(symbol) general; 92 scope: local smap(symbol) elmt base_scopes; 93 form: local smap(symbol) elmt forms; 94 alias: local smap(symbol) symbol; 95 is_read: packed local smap(symbol) bool; 96 is_write: packed local smap(symbol) bool; 97 is_const: packed local smap(symbol) bool; 98 is_internal: packed local smap(symbol) bool; 99 is_temp: packed local smap(symbol) bool; 100 is_store: packed local smap(symbol) bool; 101 is_stk: packed local smap(symbol) bool; 102 is_param: packed local smap(symbol) bool; 103 is_repr: packed local smap(symbol) bool; 104 is_init: packed local smap(symbol) bool; 105 is_seen: packed local smap(symbol) bool; 106 is_back: packed local smap(symbol) bool; 107 is_rec: packed local smap(symbol) bool; 108 next_sym: local smap(symbol) symbol; 109$ 110$ maps on scopes use the following data structures: 111$ (see section 'scopes and routines' for a more detailed account of 112$ these maps.) 113$ 114 scopes: tuple(elmt base_scopes); 115 cont_scopes: local smap(elmt base_scopes) 116 tuple(elmt base_scopes); 117 sc_type: local smap(elmt base_scopes) 118 elmt base_sc_types; 119 sc_nprocs: local smap(elmt base_scopes) integer; 120 sc_stmt_ct: local smap(elmt base_scopes) integer; 121 sc_estmt_ct: local smap(elmt base_scopes) integer; 122 first_sym: local smap(elmt base_scopes) symbol; 123 last_sym: local smap(elmt base_scopes) symbol; 124 first_block: local smap(routine) elmt blocks; 125 last_block: local smap(routine) elmt blocks; 126 first_form: local smap(elmt base_scopes) elmt forms; 127 last_form: local smap(elmt base_scopes) elmt forms; 128 sc_types: elmt base_sc_types; 129 tup_sc_types: tuple (elmt base_sc_types); 130 sym_om: symbol; 131 sym_sys: elmt base_scopes; 132 sym_dir: elmt base_scopes; 133 sym_prog: elmt base_scopes; 134 sym_main: routine; 135 all_modules: sparse set(elmt base_scopes); 136$ 137$ maps on routines use the following data structures: 138$ (see section 'scopes and routines' for a more detailed account of 139$ these maps.) 140$ 141 routs: sparse set(routine); 142 rentry: local smap(routine) elmt blocks; 143 rexit: local smap(routine) elmt blocks; 144 rstop: local smap(routine) elmt blocks; 145 rparams: local smap(routine) tuple(symbol); 146 membof: local smap(routine) elmt base_scopes; 147 system_routs: sparse set(symbol); 148$ 149$ maps on intervals use the following data structures: 150$ see section 'data flow maps' for more details. 151$ 152 ints: local smap(routine) tuple(elmt blocks); 153 proper_ints: local set(elmt blocks); 154$ 155$ there are several constants relating to forms. they use the following 156$ data structures: 157$ (see section forms for further details.) 158$ 159 ft_types: elmt base_ft_types; 160 tup_ft_types: tuple(elmt base_ft_types); 161 ft_mapcs: elmt base_ft_mapcs; 162 tup_ft_mapcs: tuple(elmt base_ft_mapcs); 163 std_form: local smap(elmt base_ft_types) 164 elmt forms; 165 ft_predicates: local set(elmt base_ft_types); 166$ 167$ maps on forms use the following data structures: 168$ (see the section on forms for further details) 169$ 170 ft_type: packed local smap(elmt forms) 171 elmt base_ft_types; 172 ft_mapc: packed local smap(elmt forms) 173 elmt base_ft_mapcs; 174 ft_elmt: local smap(elmt forms) *; 175 ft_dom: local smap(elmt forms) elmt forms; 176 ft_im: local smap(elmt forms) elmt forms; 177 ft_imset: local smap(elmt forms) elmt forms; 178 ft_base: local smap(elmt forms) elmt forms; 179 ft_low: local smap(elmt forms) integer; 180 ft_lim: local smap(elmt forms) integer; 181 ft_tup: local smap(elmt forms) elmt forms; 182 ft_hashok: packed local smap(elmt forms) bool; 183 ft_neltok: packed local smap(elmt forms) bool; 184 ft_pos: local smap(elmt forms) integer; 185 ft_num: local smap(elmt forms) 186$ nb. 'string' should be element of base of local types 187 smap(string) integer; 188 ft_deref: local smap(elmt forms) elmt forms; 189 next_form: local smap(elmt forms) elmt forms; 190 basesymb: sparse smap(elmt forms) symbol; 191$ 192$ maps on blocks use the following data structures: 193$ see section '...' for more details. 194$ 195 routof: local smap(elmt blocks) routine; 196 next_block: local smap(elmt blocks) elmt blocks; 197 first_inst: local smap(elmt blocks) elmt insts; 198 last_inst: local smap(elmt blocks) elmt insts; 199 cessor: local mmap(elmt blocks) elmt blocks; 200 pred: local mmap(elmt blocks) elmt blocks; 201 intof: local smap(elmt blocks) elmt blocks; 202 int_nodes: local smap(elmt blocks) 203 tuple(elmt blocks); 204 vedges: local mmap(elmt blocks) elmt blocks; 205 206 cut_blocks: sparse set(elmt blocks); 207 dead_labs: sparse set(symbol); 208$ 209$ there are various global collections defined on q1 opcodes. 210$ the section q1 opcodes for more details. 211$ 212 opcodes: elmt base_opcodes; 213 tup_opcodes: tuple(elmt base_opcodes); 214 copy_actions: elmt base_copy_actions; 215 tup_copy_actions: tuple(elmt base_copy_actions); 216 ops_classes: local set(elmt base_opcodes); 217$ 218$ maps on instructions use the following data structures: 219$ (see section 'the program' for more details) 220$ 221 opcode: packed local smap(elmt insts) 222 elmt base_opcodes; 223 args: local smap(elmt insts) tuple(symbol); 224 occs: local smap(elmt insts) 225 tuple(occurrence); 226 blockof: local smap(elmt insts) elmt blocks; 227 stmtof: packed local smap(elmt insts) index; 228 copy_flag: packed local smap(elmt insts) 229 elmt base_copy_actions; 230 share_flag: packed local smap(elmt insts) bool; 231 next_inst: local smap(elmt insts) elmt insts; 232$ 233$ maps on call instructions use the following data structures: 234$ (see the section on the call graph for further details) 235$ 236 cgraph: sparse mmap(routine) routine; 237 callsin: local mmap(routine) elmt blocks; 238 callproc: sparse smap(elmt blocks) routine; 239 cg_sccs: tuple(routine); 240 scc_nodes: local smap(routine) tuple(routine); 241 scc_d: local smap(routine) integer; 242$ 243$ sets of and maps on occurrences use the following data structures: 244$ (see section 'occurrences' for further details) 245$ 246 all_oi: local set(occurrence); 247 all_o: local set(occurrence); 248 all_i: local set(occurrence); 249 250 typ: remote smap(occurrence) elmt types; 251 252 t_types: basic_type; 253 bsctyps, 254 int_real, 255 int_real_str, 256 int_real_str_atom, 257 str_tup, 258 str_tup_set, 259 set_tup, 260 tup_set_map: gross_type; 261 type_zero, 262 type_om, 263 type_gen, 264 type_notom, 265 type_int, 266 type_real, 267 type_string, 268 type_boolean, 269 type_atom, 270 type_tuple, 271 type_pair, 272 type_set, 273 type_map, 274 type_int_real, 275 type_int_real_str, 276 type_str_tup, 277 type_str_tup_set: elmt types; 278 279 fixed_typ: local smap(elmt base_opcodes) 280 elmt types; 281 simple_type: local smap(elmt base_ft_types) string; 282 $$$ ??? string ??? 283 ft_usetmaps: local set(elmt base_ft_types); 284 localtp, remotetp: local smap(elmt base_ft_types) 285 elmt base_ft_types; 286$ 287$ maps for the automatic data structure choice phase 288$ (see section on types for declarations.) 289$ 290 oi_repr: remote smap(occurrence) elmt types; 291 userbase: remote smap(tent_base) symbol; 292 actual_bases: remote set(tent_base); 293 bscope: local smap(tent_base) elmt base_scopes; 294 elmt_mode: local smap(tent_base) elmt types; 295$ 296$ maps on variables use the following data structures: 297$ (see the section on various initial maps for further details) 298$ (this section follows call paths) 299$ 300 variables: local set(symbol); 301 uservars: local set(symbol); smfe 13 itervars: local set(symbol); 302 globalvars: sparse set(symbol); 303 localvars: local mmap{routine} 304 sparse set(symbol); 305 occsof: local mmap{symbol} 306 sparse set(occurrence); 307$ 308$ maps on expressions use the following data structures: 309$ (see the section on various initial maps for further details) 310$ (this section follows call paths) 311$ 312 globalexps: sparse set(expression); 313 localexps: local mmap{routine} 314 sparse set(expression); 315 allexps: local set(expression); 316 opcexp: local smap(expression) 317 elmt base_opcodes; 318 argsexp: local smap(expression) tuple(symbol); 319 dependon: sparse mmap{symbol} 320 sparse set(expression); 321$ 322$ maps on occurrences use the following data structures: 323$ (see the section on occurrences for further details) 324$ 325 instno: local smap(occurrence) elmt insts; 326 argno: packed local smap(occurrence) index; 327$ 328$ data flow maps use the following data structures: 329$ (see the section on data flow maps for further detail) 330$ 331 bfrom: local mmap{occurrence} 332 sparse set(occurrence); 333 ffrom: local mmap{occurrence} 334 sparse set(occurrence); 335 bfrom_dead: local set(occurrence); 336 337 xom_ocrs: df_elmt_ocrs; 338 fom_ocrs: df_map_ocrs; 339 xom_syms: df_elmt_syms; 340 fom_syms: df_map_syms; 341 342 push_former: sparse smap(elmt insts) elmt insts; 343$ 344$ maps used to collect information for automatic documentation use the 345$ following data structures: 346$ 347 globals_du: sparse mmap(routine) occurrence; smfk 11 globals_e: sparse mmap(routine) symbol; 348 globals_r, globals_w: sparse mmap(routine) symbol; 349 messages: mmap{integer} 350 mmap{string} 351 set(tuple(string)); 352 statistics: tuple(integer)(20); 353$ 354$ general system parameters use the following data structures: 355$ 356 q1_file: string; 357 ssm_file: string; 358 term_file: string; 359 debug_flag: boolean; 360 at_flag: boolean; smfk 12 lcp_flag, lcs_flag: boolean; 361 prog_level: string; 362 rem: integer; 363 dump_string: string; 364$ 365$ the following procedures are exported by module util 366$ 367 add_sym: procedure(elmt base_scopes) symbol; 368 del_sym: procedure( 369 symbol, 370 symbol, 371 elmt base_scopes 372 ); 373 add_form: procedure(elmt base_scopes) elmt forms; 374 add_block: procedure( 375 elmt blocks, 376 elmt base_scopes, 377 boolean ) 378 elmt blocks; 379 add_inst: procedure( 380 elmt blocks, 381 elmt base_opcodes, 382 tuple(symbol) ) 383 elmt insts; 384 del_inst: procedure( 385 elmt insts, 386 elmt insts, 387 elmt blocks 388 ); 389 insert_ins1, 390 insert_ins: procedure( 391 elmt insts, 392 elmt base_opcodes, 393 tuple(symbol) 394 ); 395 del_block: procedure( 396 elmt blocks, elmt blocks, 397 elmt base_scopes ); 398 add_label: procedure(elmt base_scopes) symbol; 399 add_var: procedure(elmt base_scopes) symbol; 400 add_int: procedure(elmt base_scopes, integer) 401 symbol; 402 ermsg, abort: procedure(string); 403 prints: procedure( 404 string, 405 tuple(tuple(string, general)) 406 ); 407 format_type: procedure(elmt types) string; 408 format_repr: procedure(elmt types) string; 409 format_form: procedure(elmt forms) string; 410 format_inst: procedure(elmt insts, tuple(symbol)) 411 string; 412$ 413$ the following procedure is exported by the module dumps: 414$ 415 dmp: procedure( 416 elmt base_scopes, 417 tuple(string) 418 ); 419 print_summary: procedure(sparse set(elmt base_scopes)); 420 read_q1, write_q1: procedure; 421 opt_ini, opt_term: procedure; 422$ 423$ the following procedures are exported by the modules 424$ interval_analysis, availexp_analysis, live_analysis, and 425$ bfrom_analysis, resp. they are declared here purely to enable us to 426$ use the ur check feature of sem. 427$ 428 find_intervals: procedure; 429 csx: procedure; 430 live: procedure; 431 find_bfrom: procedure; smfb 25 find_region_constants: procedure; 432$ 433$ the following procedures are exported by module dataflow_solver: 434$ 435$ nb. there are two copies of the dataflow solver around, one operating 436$ on the base of symbols (syms), the other on the base of occurrences 437$ (ocrs). this is due to the fact that setl does not have generic 438$ bases. 439$ 440 .comp_ocrs: operator(df_map_ocrs, df_map_ocrs) 441 df_map_ocrs; 442 cgraph_analysis: procedure; 443 444$ nb. the setl system currently does not provide for an efficient 445$ way to handle set operations between remote and sparse sets on 446$ a common base. while some of these operations could be done 447$ more efficiently by source transformation, we have taken the 448$ approach to ignore the potential sparseness of insert, etc, and 449$ for now represent them as bit vectors (df_elmt = remote sets) 450 451 interproc_fwd_analysis_ocrs: 452 procedure( 453 remote smap(df_edge) df_map_ocrs, 454 remote smap(df_node) df_elmt_ocrs, 455 df_map_ocrs, 456 df_elmt_ocrs, 457 boolean, 458 boolean, 459 remote mmap{df_node} df_elmt_ocrs, 460 remote mmap{df_node} df_elmt_ocrs, 461 remote mmap{df_node} df_elmt_ocrs 462 ); 463 intraproc_fwd_analysis_ocrs: 464 procedure( 465 routine, 466 remote smap(df_edge) df_map_ocrs, 467 remote smap(df_node) df_elmt_ocrs, 468 df_map_ocrs, 469 df_elmt_ocrs, 470 boolean, 471 boolean, 472 remote mmap{df_node} df_elmt_ocrs, 473 remote mmap{df_node} df_elmt_ocrs, 474 remote mmap{df_node} df_elmt_ocrs 475 ); 476 interproc_back_analysis_ocrs: 477 procedure( 478 remote smap(df_edge) df_map_ocrs, 479 remote smap(df_node) df_elmt_ocrs, 480 df_map_ocrs, 481 df_elmt_ocrs, 482 boolean 483 ); 484 intraproc_back_analysis_ocrs: 485 procedure( 486 routine, 487 remote smap(df_edge) df_map_ocrs, 488 remote smap(df_node) df_elmt_ocrs, 489 df_map_ocrs, 490 df_elmt_ocrs, 491 boolean 492 ); 493 .comp_syms: operator(df_map_syms, df_map_syms) 494 df_map_syms; 495 interproc_fwd_analysis_syms: 496 procedure( 497 remote smap(df_edge) df_map_syms, 498 remote smap(df_node) df_elmt_syms, 499 df_map_syms, 500 df_elmt_syms, 501 boolean, 502 boolean, 503 remote mmap{df_node} df_elmt_syms, 504 remote mmap{df_node} df_elmt_syms, 505 remote mmap{df_node} df_elmt_syms 506 ); 507 intraproc_fwd_analysis_syms: 508 procedure( 509 routine, 510 remote smap(df_edge) df_map_syms, 511 remote smap(df_node) df_elmt_syms, 512 df_map_syms, 513 df_elmt_syms, 514 boolean, 515 boolean, 516 remote mmap{df_node} df_elmt_syms, 517 remote mmap{df_node} df_elmt_syms, 518 remote mmap{df_node} df_elmt_syms 519 ); 520 interproc_back_analysis_syms: 521 procedure( 522 remote smap(df_edge) df_map_syms, 523 remote smap(df_node) df_elmt_syms, 524 df_map_syms, 525 df_elmt_syms, 526 boolean 527 ); 528 intraproc_back_analysis_syms: 529 procedure( 530 routine, 531 remote smap(df_edge) df_map_syms, 532 remote smap(df_node) df_elmt_syms, 533 df_map_syms, 534 df_elmt_syms, 535 boolean 536 ); 537$ 538$ the following routines are exported by module typfind 539$ 540 type_find: procedure; 541 .is_pair: operator(elmt types) boolean; 542 .is_map: operator(elmt types) boolean; 543$ 544$ the following procedure is exported by the module auto_dstruct: 545$ 546 auto_data: procedure; 547$ 548$ the following routines are exported by the module conversion_analysis: 549$ 550 conv_optimize: procedure; 551 can_conv: procedure(elmt forms, elmt forms) 552 boolean; 553$ 554$ the following procedure is exported by the module copy_optimization: 555$ 556 copy_optimize: procedure; 557 end repr; 558 559 560 end directory; 561 562 1 .=member maino7 2 3 4 program setl_optimizer - main; 5$ 6$ this is the main program of the optimizer. we begin by reading in the 7$ intermediate tables. we then perform inter- and intra-procedural 8$ analysis, iterating until the control graph of the program converges. 9$ we then perform type analysis, copy optimzation, automatic data 10$ structure selection, etc. 11$ 12 statistics with:= time; $ save initial time 13 14 opt_ini; $ initialize 15 16 cgraph_analysis; $ find the call graph 17 find_intervals; $ perform interval analysis 18 live; $ live variable analysis 19 csx; $ common subexpr elimination and code motion 20 find_bfrom; $ bfrom computation smfb 26 find_region_constants; $ find flow-constant loops 21 22 23 type_find; $ type analysis 24 auto_data; $ automatic data structure selection 25 conv_optimize; $ conversion optimisation 26 copy_optimize; $ copy optimisation 27 opt_term; $ print tables, etc. 28 29 30 end program setl_optimizer - main; 31 32 1 .=member intfa8 2 3 4 module setl_optimizer - interface; 5$ 6$ this module handles the interface between the optimizer and the 7$ rest of the compiler. it exports two procedures: 8$ 9$ read_q1: reads in the q1 code 10$ write_q1: writes it back out 11$ 12 13$ the q1 data structures 14$ ---------------------- 15 16$ 17$ the optimizer inttterfaces with the rest of the compiler via an 18$ intermediate-code file called 'q1', which is written out by the 19$ semantic pass in a form suitable to be read in by the setl binary 20$ i-o routine 'getb'. the optimizer will write out a modified q1 file 21$ having a similar format, using the binary i-o routine 'putb', and this 22$ file will be read in by the code generator. this q1 file is different 23$ from the standard q1 file used for direct communication between the 24$ semantic pass and the code generator. 25$ 26$ there are three 'main' data structures in the q1 representation each 27$ of these is an array divided into several fields: 28$ 29$ symtab: the symbol table 30$ formtab: the form table 31$ codetab: the actual code 32$ 33$ codetab contains an entry for each instruction. the codetab entry 34$ contains the opcode, copy flag, etc. plus a list of arguments. 35$ 36$ each basic block is assigned an index. this index is used to access an 37$ array called blocktab which in turn gives the codetab index for the 38$ start of the block. 39$ 40 41$ sequencing of information in the data to be read 42$ ------------------------------------------------ 43 44$ 45$ the q1 data structures are set up so that we do not have to keep the 46$ entire program in core during compilation. 47$ 48$ the little q1 tables are divided into 'segments'. there is one segment 49$ for each module, procedure, etc. in the program. each segment is 50$ written out as soon as we are done compiling it. when the symbols 51$ defined in a segment are no longer needed we throw away the table 52$ space used to store the segment, and re-use it for the next segment. 53$ 54$ each segment consists of a header followed by a slice of each of the 55$ arrays mentioned in the previous section. these slices are arranged 56$ in a standard order. 57$ 58$ each header consists of: 59$ 60$ 1. an integer code sc_xxx indicating whether the segment represents a 61$ module, library, procedure, etc. 62$ 63$ 2. a string giving the name of the current segment. 64$ 65$ 3. a symtab pointer to the segment name. (integer) 66$ this pointer is needed for code generation. note also that 67$ the segment name may not be unique, but the pointer would be. 68$ 69$ 4. the number of procedures in the current module, library, etc. 70$ this number is used to tell when we have read in the last 71$ procedure in a segment. 72$ 73$ 5. the statement count for the current member. 74$ 75$ each array slice consists of: 76$ 77$ 1. an integer 'org' giving the index in the full table of the 78$ 0-th entry to be read in. 79$ 80$ 2. an integer 'last' giving the index in the full table of the 81$ last entry to be read in. 82$ 83$ 3. a series of entries, each of which consists of a series of 84$ fields, each of which is a (setl) integer, real or character 85$ string. 86$ 87 88$ the following macro is used to iterate over an array slice, 89$ reading entries as it iterates. together with the next macro, 90$ they try to resemble somewhat a loop header and a loop ender 91$ for iteration over a tuple or map. 'index' is the index in full 92$ table of the table entry currently read in. 93 94 macro for_slice(index); 95 getb(q1_file, org, last); $ indices of 0'th and last 96 $ table entry 97 98 (forall index in [ org+1..last ]) $ iterate over entries 99 endm; 100 101 macro end_slice; 102 end forall 103 endm; 104$ 105$ the sequencing of information within each segment is as follow 106$ 107$ 1. segment type (integer code) 108$ 2. segment name (string) 109$ 3. scope name (integer index to symtab) 110$ 4. number of procedures (integer) 111$ 5. statement count (integer) 112$ 113$ 6. formtab org and last (integers) 114$ 7. formtab body (series of integer fields; see below) 115$ 116$ 8. symbtab org and last (integers) 117$ 9. symbtab body (series of various fields; see below) 118$ 119$ 10. blocktab org and last (integers) 120$ 11. blocktab body (integer pointers to codetab) 121$ 122$ 12. codetab org and last (integers) 123$ 13. codetab body (series of integer fields; see below) 124$ 125 126$ name and value 127$ -------------- 128 129$ 130$ some special fields in symtab, giving the name and value of 131$ symbols deserve special comment. 132$ 133$ 1. name 134$ 135$ the 'name' field of a symtab entry is a string giving the 136$ symbol name. internally generated names have a name field 137$ of ''. rather than generate explicit names entries, we simply 138$ call them 't$xxxx' where xxxx is their symtab index. 139$ 140$ 2. value 141$ 142$ constant symbols, initialized variables, procedures, members 143$ and labels all have values. the 'vptr' field of a symtab entry 144$ indicates whether the symbol does have a value. if so, then 145$ at the end of the series of fixed fields for that entry, there 146$ follow a series of value-entries, whose format depends on the 147$ form of the symbol, as follows: 148$ 149$ reals, integers and strings have one value-entry, giving their 150$ (setl) value. 151$ 152$ if 'c' is a constant of type 'elmt b' then c's val entry 153$ is a symtab pointer to the constant you would get by 154$ dereferencing 'c'. 155$ 156$ for all other symbols, the first value entry is 'vlen', giving 157$ the number of additional value entries to follow. 158$ 159$ if 'c' is an n-tuple or an n-element set then its value 160$ entries are n symtab pointers to its elements, 161$ which may in turn be constant sets or tuples. 162$ 163$ if 'c' is a procedure then its value entries are: 164$ 165$ a. a pointer to the variable it uses for value return 166$ b. a flag indicating whether it has a variable no. of args 167$ c. its number of arguments 168$ d. a series of symtab pointers to the entries for rd, wr, and rw. 169$ 170$ if 'c' is a label its val entry is a codetab pointer to 171$ the instruction defining it. 172$ 173$ if 'c' is a module, program, or library then its val 174$ entry consists of 5 lists of, respectively, libraries used, 175$ globals read, globals written, procedures imported, and 176$ procedures exported by the member. each list is an integer n, 177$ followed by 'n' symtab pointers. 178$ 179 180 181$ 182$ the optimizer represents type codes, opcodes, etc. as character 183$ strings, while the compiler represents them as integers. the 184$ following maps are used to send strings such as 'q1_add' into 185$ the integer codes used by the compiler. 186$ 187 var 188 sc_type_no, $ codes sc_xxx 189 ft_type_no, $ codes f_xxx 190 ft_mapc_no, $ codes ft_xxx 191 cflag_no, $ codes copy_xxx 192 opcode_no; $ codes q1_xxx 193$ note that these quantities are initialized in write_q1 194 195 196$ setl symbols are represented by blank atoms, little symbols are 197$ representeed by array indices. the maps below map each array 198$ index into the corresponding atom 199 200 var 201 stl_sym, $ maps (little) symbols to setl symbols (members 202 $ of plex base) 203 stl_form, $ maps (little) forms to setl forms (ditto) 204 stl_block, $ maps (little) block indices to setl blocks 205 $ (ditto) 206 stl_inst; $ maps (little) instruction indices to setl 207 $ instructions (ditto) 208 209$ the output interface builds the inverse mappings: 210 211 var 212 ltl_sym, $ maps setl symbols to (little) symbol indices 213 ltl_form, $ maps setl forms to (little) form indices 214 ltl_block, $ maps setl blocks to (little) block indices 215 ltl_inst; $ maps setl instructions to (little) instruction 216 $ indices 217 218 var 219 blocktab; $ basic block table 220 221 222 var 223 cur_memb, $ setl symbol for current member 224 cur_scope; $ setl symbol for current scope 225 226 var 227 orgind, $ maps each table to origin of current slice 228 lastind, $ maps each table to end of current slice 229 org_stack; $ stack of orgind values of superscopes 230 234 var 235 scp_ind, $ scope index of symbol table entry in its own 236 $ scope, if it exists 237 value_inv; $ maps each scope to the values defined in this 238 $ scope, to the symbol table entries that define 239 $ these values. 240 241 init 242 $ initialise little-to-setl maps 243 stl_sym := {}, stl_form := {}, stl_block := {}, 244 stl_inst := {}, 245 $ initialise setl-to-little maps 246 ltl_sym := {}, ltl_form := {}, ltl_block := {}, 247 ltl_inst := {}, value_inv := {}; 248 249 repr 250 stl_sym: smap(integer) symbol; 251 stl_form: smap(integer) elmt forms; 252 stl_block: smap(integer) elmt blocks; 253 stl_inst: smap(integer) elmt insts; 254 255 ft_type_no: smap(elmt base_ft_types) integer; 256 ft_mapc_no: smap(elmt base_ft_mapcs) integer; 257 cflag_no: smap(elmt base_copy_actions) integer; 258 opcode_no: smap(elmt base_opcodes) integer; 259 sc_type_no: smap(elmt base_sc_types) integer; 260 261 ltl_sym: smap(symbol) integer; 262 ltl_form: smap(elmt forms) integer; 263 ltl_block: smap(elmt blocks) integer; 264 ltl_inst: smap(elmt insts) integer; 265 266 value_inv: remote mmap{elmt base_scopes} 267 mmap{general} 268 sparse set(symbol); 270 blocktab: tuple(*); 271 cur_memb, cur_scope: elmt base_scopes; 272 scp_ind: integer; 273 orgind: smap(string) integer; 274 lastind: smap(string) integer; 275 org_stack: tuple(smap(string) integer); 276 277 get_header: procedure(boolean); 278 get_forms: procedure; 279 get_symtab: procedure; 280 cnvval: procedure( 281 symbol, 282 general ) 283 general; 284 get_code: procedure; 285 set_ltl_maps: procedure(elmt base_scopes); 286 put_header: procedure(elmt base_scopes); 287 put_trailer: procedure; 288 put_forms: procedure(elmt base_scopes); 289 put_symtab: procedure(elmt base_scopes); 290 put_code: procedure(elmt base_scopes); 291 bld_val: procedure(symbol) 292 tuple( 293 boolean, 294 integer, 295 tuple(general) 296 ); 297 elmt_sym: procedure( 298 general, 299 elmt forms, 300 symbol ) 301 integer; 302 reset: procedure( 303 elmt base_scopes, 304 elmt base_scopes 305 ); 306 end repr; 307 308 1 .=member gthd8a 2 3 4 procedure read_q1; 5$ 6$ this is the top level routine for reading the q1 tables generated 7$ by the semantic pass. we read one segment at a time until the 8$ routine for reading a header encounters an end of file. 9$ 10 repr 11 done: boolean; 12 end repr; 13 14 loop 15 doing get_header(done); $ read header 16 while not done 17 do 18 get_forms; $ read formtab 19 get_symtab; $ read symtab 20 get_code; $ read code 21 end loop; 22 23 $ delete the static variables global to the module 24 stl_sym := om; stl_form := om; stl_block := om; 25 stl_inst := om; 26 27 end procedure read_q1; 28 29 30 31 32 procedure get_header(wr done); 33$ 34$ this routine reads the header for a segment of q1. we set 'done' if 35$ we have reached the end of the input. 36$ 37 repr 38 i: integer; 39 tp: elmt base_sc_types; 40 nam: string; 41 nprocs: integer; 42 stmts: integer; 43 estmts: integer; 44 scp: symbol; 45 end repr; 46 47$ first read in the segment type and convert it to a string. 48 49 getb(q1_file, i); 50 tp := tup_sc_types(i); 51$ see introductory section scopes and routines 52$ for the initialisation of this tuple 53 54$ see the 'reprs' section for the plex-base repring of all these 55$ special string constants. 56 57$ the end of the q1 file is indicated by an empty segment whose type is 58$ 'sc_end'. if we have reached this segment we set 'done' and return. 59 if tp = sc_end then 60 done := true; 61 return; 62 else 63 done := false; 64 end if; 65$ 66$ read the remaining part of the header record. it consists of 67$ 1. the scope name as a string 68$ 2. the symbol table pointer for the scope name 69$ 3. the number of procedures in the current scope 70$ 4. the global statement count at the start of the scope 71$ 5. the global statement count of the q1_entry instruction (in proce- 72$ dure scopes only) 73$ 74 getb(q1_file, nam, i, nprocs, stmts, estmts); 75 76 case tp of 77 78 (sc_sys): 79 80 $ the system scope can be thought of as a standard prelude which 81 $ defines standard symbols such as om, true, etc. 82 $ 83 $ even though there is no symbol table entry for this scope, we 84 $ still have to associate the usual scope maps with this scope. 85 $ therefore we generate a new atom for it here. 86 87 sym_sys := cur_scope := scp := newat; 88 scp_ind := om; 89 90 (sc_lib): 91 92 $ the symbol table entry for a library is the first entry in it 93 $ own scope. we open the scope here by generating a new atom, 94 $ and define scp_ind to suppress the generation of a new atom 95 $ in the get_symtab routine. 96 97 cur_scope := scp := newat; 98 first_sym(cur_scope) := last_sym(cur_scope) := scp; 99 scope(scp) := cur_scope; 100 scp_ind := i; 101 102 (sc_dir): 103 104 $ like libraries, the symbol table entry for a directory is the 105 $ first entry in its own scope. again, we open the scope here, 106 $ and in addition define sym_dir to point to it. 107 108 sym_dir := cur_scope := scp := newat; 109 first_sym(cur_scope) := last_sym(cur_scope) := scp; 110 scope(scp) := cur_scope; 111 scp_ind := i; 112 113 (sc_prog): 114 115 $ if we have a directory, then the symbol table entry for the 116 $ program scope has appeared in the directory. otherwise, the 117 $ symbol table entry for the program scope is the first entry in 118 $ its own scope, and we open a new scope here. in addition, we 119 $ set sym_prog to point to the new scope. 120 121 if sym_dir = om then $ no directory: open new scope 122 sym_prog := cur_scope := scp := newat; 123 first_sym(cur_scope) := last_sym(cur_scope) := scp; 124 scope(scp) := cur_scope; 125 scp_ind := i; 126 else $ we previously have seen a directory: define cur_scope 127 scp := cur_scope := sym_prog; 128 scp_ind := om; 129 end if; 130 131 (sc_mod, sc_proc): 132 133 $ we can only have a module if we have a directory. if we have 134 $ a directory, then the symbol table entry for the module 135 $ appears in the directory scope, and stl_sym(i) points to it. 136 137 $ procedures always appear in some enclosing scope: the symbol 138 $ table entry for a procedure appearing in an exports list of a 139 $ library appears in the library's header, while a procedure 140 $ appearing in an exports or imports list of a directory appears 141 $ in the directory scope. procedures defined in a program or 142 $ module scope appear in the program's or module's header, resp. 143 144 scp := cur_scope := stl_sym(i); 145 scp_ind := om; 146 147 end case; 148 149 $ we keep a global tuple of scopes so that segments can be written 150 $ out in the order in which they were read. 151 scopes with:= cur_scope; 152 153 $ define the relevant scope maps 154 sc_type(cur_scope) := tp; 155 sc_nprocs(cur_scope) := nprocs; 156 sc_stmt_ct(cur_scope) := stmts; 157 sc_estmt_ct(cur_scope) := estmts; 158 159 $ mark the current scope as being part of the input. the remaining 160 $ symbol table fields either have been or will be set by get_symtab. 161 name(scp) := nam; 162 is_seen(scp) := 1; 163 164 if sc_type(scp) = sc_proc then 165 membof(scp) := cur_memb; 166 else 167 cur_memb := scp; 168 end if; 169$ 170$ note that the correspondence between the little scope index and the 171$ setl atom, needed in the output interface, cannot be saved here since 172$ symbols might be added or deleted during optimisation. 173$ 174 175 end procedure get_header; 176 177 1 .=member gtfm8b 2 3 4 procedure get_forms; 5$ 6$ this routine reads a segment of formtab. 7$ 8 repr 9 org: integer; 10 last: integer; 11 i: integer; 12 formtab_entry: tuple(*); 13 ft_type_: integer; 14 ft_mapc_: integer; 15 ft_elmt_: integer; 16 ft_dom_: integer; 17 ft_im_: integer; 18 ft_imset_: integer; 19 ft_base_: integer; 20 ft_deref_: integer; 21 ft_low_: integer; 22 ft_lim_: integer; 23 ft_pos_: integer; 24 ft_hashok_: boolean; 25 ft_neltok_: boolean; 26 ft_tup_: tuple(integer); 27 frm: elmt forms; 28 tp: elmt base_ft_types; 29 flim: integer; 30 cmpfrm: integer; 31 localtyps: tuple(string); 32 zzz: string; 33 yyy: integer; 34 end repr; 35$ 36$ read in formtab entries one at a time and build the setl formtab 37$ 38$ see the introductory section "form" for a description of the maps 39$ (fields) being read here. 40$ 41 for_slice(i) 42 getb(q1_file, formtab_entry); 43 [ ft_type_, ft_mapc_, ft_elmt_, ft_dom_, ft_im_, ft_imset_, 44 ft_base_, ft_deref_, ft_low_, ft_lim_, ft_pos_, 45 ft_hashok_, ft_neltok_, ft_tup_ ] := formtab_entry; 46 47 $ retain the relation between little and setl forms for later 48 stl_form(i) := frm := add_form(cur_scope); 49$ 50$ set various fields. note that in the little data structures 51$ ft_type is zero origined, so we must add one before accessing 52$ tup_ft_types. 53$ 54 ft_type(frm) := tp := tup_ft_types(ft_type_ + 1); 55 ft_deref(frm) := stl_form(ft_deref_); 56$ 57$ note the correspondence between the little 0 and the setl om in 58$ the following test. 59$ see section forms for the initialisation of tup_ft_types. 60$ 61 if ft_mapc_ /= 0 then 62 ft_mapc(frm) := tup_ft_mapcs(ft_mapc_); 63 end if; 64$ 65$ the system segment contains a standard entry for each standard type 66$ f_xxx. this entry will always be the first entry whose ft_type is 67$ f_xxx. we detect these entries and use them to fill in a map from 68$ strings f_xxx to standard forms. this map is called std_form. 69$ 70$ if this is the first form of type tp, save a pointer to it 71 if std_form(tp) = om then std_form(tp) := frm; end if; 72 73$ note that in what follows, we make use of the fact that the 74$ default (little) values for missing (irrlevant) fields are 0 75 76$ set element type, etc. 77 if tp = f_mtuple or tp = f_proc then 78 ft_lim(frm) := flim := ft_lim_; 79 ft_elmt(frm) := [ stl_form(cmpfrm) : cmpfrm in ft_tup_ ]; 80 81 else 82 if tp = f_sint or is_ftup(frm) or is_fbase(frm) then 83 if tp = f_sint then ft_low(frm) := ft_low_; end if; 84 ft_lim(frm) := flim := ft_lim_; 85 end if; 86 87 if is_floc(frm) then 88 ft_pos(frm) := ft_pos_; 89 90 elseif is_frem(frm) and is_fmap(frm) then 91 ft_tup(frm) := stl_form(ft_tup_(1)); 92 end if; 93 94 if is_fset(frm) or is_ftup(frm) then 95 ft_elmt(frm) := stl_form(ft_elmt_); 96 end if; 97 98 if is_fmap(frm) then 99 ft_dom(frm) := stl_form(ft_dom_); 100 ft_im(frm) := stl_form(ft_im_); 101 if ft_mapc(frm) = ft_map then 102 ft_imset(frm) := stl_form(ft_imset_); 103 end if; 104 end if; 105 106 if is_fbased(frm) then 107 ft_base(frm) := stl_form(ft_base_); 108 end if; 109 end if; 110 111 if ft_hashok_ then ft_hashok(frm) := 1; end if; 112 if ft_neltok_ then ft_neltok(frm) := 1; end if; 113 114$ see comment in section 3g 'forms' explaining use of 'ft_tup' 115$ and other maps (fields) appearing here 116 117$ compute the nasty 'ft_num' map for bases. 118 if is_fbase(frm) then 119 localtyps := 120 [ f_lset, f_lmap, f_lpmap, f_limap, f_lrmap ]; 121 ft_num(frm) := 122 { [zzz, ft_tup_(yyy)] : zzz = localtyps(yyy) }; 123 end if; 124 end_slice; 125 126 127 end procedure get_forms; 128 129 1 .=member gtst8c 2 3 4 procedure get_symtab; 5$ 6$ this routine reads a segment of the symbol table, and then builds the 7$ corresponding (setl) symbol table entries. 8$ 9 init 10 $ the following maps are needed to account for forward referen- 11 $ ces within the current segment 12 aliases := {}, $ maps symbols to their alias index 13 values := {}; $ maps members and procedures to their values 14 15 repr 16 org: integer; 17 last: integer; 18 aliases: sparse smap(symbol) integer; 19 values: sparse smap(symbol) general; 20 i: integer; 21 symtab_entry: tuple(*); 22 name_: string; 23 form_: integer; 24 alias_: integer; 25 is_repr_: boolean; 26 is_temp_: boolean; 27 is_stk_: boolean; 28 is_read_: boolean; 29 is_write_: boolean; 30 is_param_: boolean; 31 is_store_: boolean; 32 is_init_: boolean; 33 is_seen_: boolean; 34 is_back_: boolean; 35 is_rec_: boolean; 36 has_value_: boolean; 37 vlen_: integer; 38 value_: general; 39 sym: symbol; 40 alind: integer; 41 end repr; 42 43$ read symtab entries one at a time and build setl symbol table entries. 44 45$ see the introductory section 'the symbol table' for an account 46$ of the maps (fields) being read 47 48$ note that the 'scope' and 'next_sym' fields of a symbol table 49$ entry are set by the 'add_sym' utility while 'tempop' is set 50$ during the initial pass over the code, when temporary (expression) 51$ names are re-constructed; 'tempop' will map each such temporary 52$ to the instruction in which it is computed. see 'symbol table' 53 54 for_slice(i) 55 getb(q1_file, symtab_entry); 56 [ name_, form_, alias_, is_repr_, is_temp_, 57 is_stk_, is_read_, is_write_, is_param_, 58 is_store_, is_init_, is_seen_, is_back_, is_rec_, 59 has_value_, vlen_, value_ ] := symtab_entry; 60 61 if i = scp_ind then 62$ current symbol is the current scope (appearing in its own 63$ symtab slice). retrieve the already generated symbol for it. 64 sym := stl_sym(i) := cur_scope; 65 66 else 67 sym := add_sym(cur_scope); $ get setl symbol 68 stl_sym(i) := sym; 69 end if; 70 71 form(sym) := stl_form(form_); 72 if is_fbase(form(sym)) then basesymb(form(sym)) := sym; end if; 73 74 if name_ = '' then 75 name(sym) := 't.' + str i; is_internal(sym) := 1; 76 else 77 name(sym) := name_; is_internal(sym) := om; 78 end if; 79$ 80$ account for possible forward references in the value entry 81$ 82 if ft_type(form(sym)) = f_memb then 83 84 $ the value of a member is a quintuple, giving (in this 85 $ order) the libraries referenced, the globals read, the 86 $ globals written, the procedures imported, and the proce- 87 $ dures exported. since the imports and exports lists may 88 $ contain forward references, we cannot process them until 89 $ the entire segment has been read. 90 91 values(sym) := value_; 92 93 elseif ft_type(form(sym)) = f_proc then 94 95 $ the value of a procedure is a quadruple, giving (in this 96 $ order) the global for the return value, a flag indicating 97 $ whether the procedure has a variable number of arguments, 98 $ the number of formal parameters, and a sequence of rd's, 99 $ rw's, and wr's indicating how a parameter was declared. 100 $ since the return value is a forward reference, we cannot 101 $ process the value_ until the entire segment has been read 102 103 values(sym) := value_; 104 105 elseif alias_ /= 0 then 106 107 $ the alias of a symbol may be a forward reference. hence 108 $ we cannot fill in this field now, but have to wait until 109 $ the entire segment has been read. since the value of an 110 $ aliased symbol is the value of the alias, this may be a 111 $ forward reference as well. 112 113 aliases(sym) := alias_; 114 115 elseif has_value_ then 116 117 $ in all other case, we can build the value entry. 118 119 value(sym) := cnvval(sym, value_); 120 121 end if; 122 123 $ if we are currently processing the directory scope, then we 124 $ collect all module names so that we can determine later which 125 $ modules are missing in the input. 126 127 if cur_scope = sym_dir and ft_type(form(sym)) = f_memb then 128 129 $ note that the symbol table entry for a library appears as 130 $ the first symbol of its own scope, and not in the direc- 131 $ tory scope. consequently we collect here only the main 132 $ program, and all modules mentioned in the directory. 133 134 all_modules with:= sym; 135 136 $ if this is the program scope, we must set sym_prog, since 137 $ we cannot be sure that the main program is part of the 138 $ input. note that the first symbol in the directory scope 139 $ is the directory name. the second symbol with a member 140 $ form must be the main program. 141 142 if cur_scope /= sym and sym_prog = om then 143 sym_prog := sym; 144 membof(sym_main) := sym_prog; 145 end if; 146 end if; 147 148 $ when the semantic pass compiles the main program, it treats 149 $ it like a procedure with the reserved name '_main'. if this 150 $ is the system scope, we must find this symbol table entry for 151 $ the main program and set sym_main to point to it. 152 153 if cur_scope = sym_sys and name(sym) = '_main' then 154 sym_main := sym; 155 end if; 156 157 $ set all flags 158 if is_temp_ then is_temp(sym) := 1; end if; 159 if is_stk_ then is_stk(sym) := 1; end if; 160 if is_read_ then is_read(sym) := 1; end if; 161 if is_write_ then is_write(sym) := 1; end if; 162 if is_param_ then is_param(sym) := 1; end if; 163 if is_store_ then is_store(sym) := 1; end if; 164 if is_repr_ then is_repr(sym) := 1; end if; 165 if is_init_ then is_init(sym) := 1; end if; 166 if is_seen_ then is_seen(sym) := 1; end if; 167 if is_back_ then is_back(sym) := 1; end if; 168 if is_rec_ then is_rec(sym) := 1; end if; 169 170 if has_value_ and not is_write_ then 171 is_const(sym) := 1; 172 end if; 173 end_slice; 174$ 175$ we have finished to read the current segment, but we still must fill 176$ in all the forward references. 177$ 178 (forall value_ = values(sym)) 179 value(sym) := cnvval(sym, value_); 180 end forall; 181 182 (forall alind = aliases(sym)) 183 alias(sym) := stl_sym(alind); 184 if is_init(sym)=1 then continue forall; end if; 185 value(sym) := value(stl_sym(alind)); 186 end forall; 187 188 189 end procedure get_symtab; 190 191 192 193 194 procedure cnvval(sym, value_); 195$ 196$ this routine computes the value of a symbol table entry 197$ and returns a pair [value, flag] where 'flag' indicates 198$ whether the symbol has a value. 199$ 200 repr 201 frm: elmt forms; 202 tp: elmt base_ft_types; 203 tp1: string; 204 vl: general; 205 j: integer; 206 tup: tuple; 207 k: integer; 208 q: integer; 209 lenx: integer; 210 end repr; 211 212$ see the description of the value fields for details. 213 214$ elements of compound values are represented by pointers to their 215$ symbols; we retrieve these element values using the macro 216$ (note that the elements of a compound value v will always have 217$ been processed before v): 218 219 macro elmt_val(j); value(stl_sym(value_(j))) endm; 220 221 222 frm := form(sym); $ get form of symbol 223 tp := ft_type(frm); 224 tp1 := simple_type(tp); $ int, real, etc. 225$ this map sends (detailed) form indicators, such as "f-sint", "f-uint", 226$ "f-int" into gross form indicators such as "int". it is initialized 227$ in section 3g 'forms' 228 229 case tp1 of 230 231 ('int', 'real', 'string'): 232 233 vl := value_(1); 234 235 ('atom'): 236 237 $ recall that booleans are represented by the short atoms 0 and 238 $ maxsi, where maxsi is an implementation-dependend constant 239 $ giving to the maximum value for a short integer. 240 $ note that there can be no other constants with mode atom. 241 242 if is_boolean value_(1) then 243 vl := value_(1); 244 else 245 print('*** illegal type for constant in input: sym =', 246 sym, name(sym), 'form =', frm, 'value =', value_); 247 end if; 248 249 ('elmt'): $ element 250 251 vl := elmt_val(1); 252 253 ('tuple'): $ tuple 254 255 vl := [ elmt_val(j) : j in [ 1..#value_ ] ]; 256 257 ('set', 'map'): $ sets and maps 258 259 vl := { elmt_val(j) : j in [ 1..#value_] }; 260 261 ('proc'): $ procedures 262 263 $ the value of a procedure is a quadruple, giving (in this 264 $ order) the global for the return value, a flag indicating 265 $ whether the procedure has a variable number of arguments, the 266 $ number of formal parameters, and a sequence of rd's, rw's, 267 $ and wr's indicating how a parameter was declared. 268 $ note that the return value is a forward reference. 269 270 vl := []; 271 vl(1) := stl_sym(value_(1)); $ global for return value 272 if value_(2) = 1 then vl(2) := 1; end if; $ var # parameters 273 vl(3) := value_(3); $ number of formal parameters 274 vl(4) := [ stl_sym(value_(j)) : j in [ 4..#value_ ] ]; 275 276 ('memb'): $ members of directories 277 278 $ the value of a member is a quintuple, giving (in this order) 279 $ the libraries referenced, the globals read, the globals writ- 280 $ ten, the procedures imported, and the procedures exported. 281 $ note the the imports and exports lists may contain forward 282 $ references. 283 284 $ each list consists of an integer n, followed by n symbol table 285 $ pointers. 286 287 tup := []; $ tuple of sets of symbols 288 k := 1; 289 290 (forall q in [1..5]) 291 lenx := value_(k); $ length entries in q'th rights list 292 293 tup with:= { stl_sym(value_(k+j)) : j in [1..lenx] }; 294 k := k + lenx + 1; 295 end forall; 296 297 vl := tup; 298 299 ('lab'): $ labels and case tags 300 301 $ the value of a label should be the instruction that it points 302 $ to. however, this will not be determined until the get_code 303 $ procedure, and we need a valid map should we encounter a case 304 $ map. 305 306 vl := sym; 307 308 else 309 print('*** illegal type for constant in input: sym =', 310 sym, name(sym), 'form =', frm, 'value =', value_); 311 312 end case; 313 314 return vl; 315 316 end procedure cnvval; 317 318 1 .=member gtcd8d 2 3 4 procedure get_code; 5$ 6$ this routine reads segments of blocktab and codetab. 7$ 8 repr 9 org: integer; 10 last: integer; 11 i: integer; 12 nxt: smap(integer) integer; 13 codetab_entry: tuple(*); 14 opcode_: integer; 15 blockof_: integer; 16 next_: integer; 17 cflag_: integer; 18 sflag_: integer; 19 nargs_: integer; 20 args_: tuple(*); 21 inst: elmt insts; 22 opc: elmt base_opcodes; 23 blk: elmt blocks; 24 j: integer; 25 i1, i2: integer; 26 cstmt_count: integer; 27 argsi: tuple(symbol); 28 oi: occurrence; 29 occsi: tuple(occurrence); 30 end repr; 31 32$ we begin by reading in blocktab and mapping each little block number 33$ into a setl block (an element of the plex base of setl blocks). 34 35 blocktab := []; 36 37 getb(q1_file, org, last); 38 39 (forall i in [ org+1..last ]) 40 getb(q1_file, blocktab(i)); 41 end forall; 42 43$ currently, in an attempt to make things easier for the optimiser, the 44$ semantic pass performs basic block decomposition. this decision, 45$ which it may be well to review and change, was made at an earlier 46$ stage of our design. 47 48$ note that all blocktab slices have org = 0 (as only one such table can 49$ be nonempty in any stacking of scopes) 50 51 blk := om; 52 (forall i in [ 1..#blocktab ]) 53 stl_block(i) := blk := add_block(blk, cur_scope, true); 54 end forall; 55 56$ read codetab entries one at a time and build entries for the setl 57$ tables. we also build a temporary map 'nxt' sending the compiler- 58$ issued number of each instruction into the number of the next instruc- 59$ tion. once all instructions have been read and plex base atoms 60$ created for them, this map will be converted into a map between these 61$ atoms. 62 63 nxt := {}; 64 65 cstmt_count := sc_estmt_ct(cur_scope); 66 67 for_slice(i) 68 getb(q1_file, codetab_entry); 69 [ opcode_, blockof_, next_, cflag_, 70 sflag_, nargs_, args_ ] := codetab_entry; 71 72 if sc_type(cur_scope) /= sc_proc then continue; end; 73 74 $ create an instruction atom in its plex base and note the 75 $ correspondence between the instruction number and atom. 76 stl_inst(i) := inst := newat; 77 78 opcode(inst) := opc := tup_opcodes(opcode_); 79 blockof(inst) := blk := stl_block(blockof_); 80 81 if opc = q1_stmt then cstmt_count +:= 1; end if; 82 stmtof(inst) := cstmt_count; 83 84$ note that the next_ field is 0 for the last instruction in a block 85 if next_ = 0 then 86 last_inst(blk) := inst; 87 else 88 nxt(i) := next_; 89 end if; 90 91$ store arguments. see introductory section 'the program' for an 92$ account of the maps (fields) being read 93 94 argsi := []; occsi := []; 95 96 (forall j in [ 1..nargs_ ]) 97 argsi(j) := stl_sym(args_(j)); 98 99 oi := newat; $ create new occurrence 100 instno(oi) := inst; 101 argno(oi) := j; 102 occsi(j) := oi; 103 end forall; 104 105 args(inst) := argsi; 106 occs(inst) := occsi; 107 108$ if this is a label or case tag definition, establish its value, 109$ which is the present instruction 110 111 if opcode(inst) in { q1_label, q1_tag } then 112 value(arg1(inst)) := inst; 113 end if; 114 end_slice; 115 116$ build up the maps 'first_inst' and 'next_inst' 117 118 (forall i in [1..#blocktab]) 119 first_inst(stl_block(i)) := stl_inst(blocktab(i)); 120 end forall; 121 122 (forall i2 = nxt(i1)) 123 next_inst(stl_inst(i1)) := stl_inst(i2); 124 end forall; 125 126 127 end procedure get_code; 128 129 1 .=member pthd8e 2 3 4 procedure write_q1; 5$ 6$ this is the top level routine for writing the q1 tables generated 7$ by the semantic pass. 8$ 9 repr 10 tp: elmt base_ft_types; 11 mapc: elmt base_ft_mapcs; 12 cact: elmt base_copy_actions; 13 opc: elmt base_opcodes; 14 sctp: elmt base_sc_types; 15 i: integer; 16 sc: elmt base_scopes; 17 scx: elmt base_scopes; 18 sym, sym1: symbol; 19 vl: general; 20 end repr; 21 22 printa(term_file, ' - start to write q1 file'); 23 24$ initialize various tables and auxiliary variables. 25 26$ first we initialize a map 'orgind' mapping each q1 table to its origin 27$ index in the currently processed scope. for more comments on this 28$ mechanism see introduction to this module. 29 30 orgind := 31 { [ 'formtab', -1 ], 32 [ 'symtab', 0 ], 33 [ 'blocktab', 0 ], 34 [ 'codetab', 0 ] }; 35 36$ initialize 'lastind', mapping each table to its last index in the 37$ currently processed scope 38 lastind := {}; 39 40$ we also maintain a stack 'org_stack' for stacking up the orgind maps 41$ of superscopes of the currently processed scope 42 org_stack := []; 43 44$ initialize maps from codes to integers. see introduction to this 45$ module for description of the following tuples. 46 47 $ for ft_type_no, note the zero-origin of the little formtab 48 ft_type_no := { [ tp, i-1 ] : tp = tup_ft_types(i) }; 49 ft_mapc_no := { [ mapc, i ] : mapc = tup_ft_mapcs(i) }; 50 cflag_no := { [ cact, i-1 ] : cact = tup_copy_actions(i) }; 51 opcode_no := { [ opc, i ] : opc = tup_opcodes(i) }; 52 sc_type_no := { [ sctp, i ] : sctp = tup_sc_types(i) }; 53$ smfb 28$ compute value_inv which maps eahc scope to the values defined in this smfb 29$ scope, to the symbol table entry which defines the denotation. note smfb 30$ that we assume here that all run-time constants originate from denota- smfb 31$ tions. composite denotations refer to more primitive denotations smfb 32$ which are defined previously. hence if cont_scopes maps each scope to smfb 33$ a tuple of scopes which contain this scope (this map is build during smfb 34$ the first pass) then we can assert that smfb 35$ exists sc in cont_scopes | value_inv{sc}(vl) /= om; 61$ 62 (forall sc in scopes) 63 (for_sym(sym, sc)) 64 if (vl := value(sym)) /= om then 65 if ft_type(form(sym)) = f_lab then 66 value_inv{sc}{sym} with:= sym; 67 elseif alias(sym) = om and 68 not exists sym1 in value_inv{sc}{vl} | 69 form(sym) = form(sym1) then 70 value_inv{sc}{vl} with:= sym; 71 end if; 72 end if; 73 end; 74 end forall; 75$ 76$ next iterate over scopes, and write out the scopes which were seen in 77$ the input stream. thus we suppress scopes added during first pass. 78$ (such as the dummy procedures we added) 79$ 80 (forall sc = scopes(i) | is_seen(sc) /= om) 81 set_ltl_maps(sc); $ initialize ltl_xxx maps for sc 82 put_header(sc); $ write out the scope header 83 put_forms(sc); $ write formtab 84 put_symtab(sc); $ write symtab 85 put_code(sc); $ write blocktab and codetab 86 reset(sc, scopes(i+1)); $ reset the orgind map for next scope 87 end forall; 88 89$ finally write a dummy header to indicate end of file 90 put_trailer; 91 92 $ delete the static variables global to the module 93 orgind := om; lastind := om; org_stack := om; 94 ltl_sym := om; ltl_form := om; ltl_block := om; 95 ltl_inst := om; cflag_no := om; opcode_no := om; 96 ft_type_no := om; ft_mapc_no := om; sc_type_no := om; 97 value_inv := om; 98 99 end procedure write_q1; 100 101 102 103 104 procedure set_ltl_maps(sc); 105$ 106$ this procedure computes the various 'ltl_xxx' maps for the current 107$ scope 'sc'. the reason for doing it in advance is that there are 108$ various 'forward' cross references between the various tables, such as 109$ symtab aliasing, value of labels, scope symtab pointers, procedure 110$ return value symbols etc. 111$ 112 repr 113 $ representation of parameters 114 sc: elmt base_scopes; 115 116 $ representation of local variables 117 frmorg: integer; 118 symorg: integer; 119 blockorg: integer; 120 instorg: integer; 121 frm: elmt forms; 122 sym: symbol; 123 blk: elmt blocks; 124 inst: elmt insts; 125 end repr; 126 127 frmorg := orgind('formtab'); 128 symorg := orgind('symtab'); 129 blockorg := instorg := 0; 130 133 (for_form(frm, sc)) 134 ltl_form(frm) := (frmorg +:= 1); 135 end; 136 137 lastind('formtab') := frmorg; 138 139 (for_sym(sym, sc)) 140 ltl_sym(sym) := (symorg +:= 1); 141 end; 142 143 lastind('symtab') := symorg; 144 145 (for_block(blk, sc)) 146 ltl_block(blk) := (blockorg +:= 1); 147 (for_inst(inst, blk)) 148 ltl_inst(inst) := (instorg +:= 1); 152 end; $ end for_inst; 153 end; $ end for_block; 154 155 lastind('blocktab') := blockorg; 156 lastind('codetab') := instorg; 157 158 end procedure set_ltl_maps; 159 160 161 162 163 procedure put_header(sc); 164 165$ this routine writes the header for a segment of q1. 166 167$ note that any scope other than the system scope will have already 168$ appeared in a previous scope, so that its ltl_sym entry is 169$ already available 170 171 repr 172 sc: elmt base_scopes; 173 tp: elmt base_sc_types; 174 end repr; 175 176 putb(q1_file, 177 sc_type_no(tp := sc_type(sc)), 178 name(sc), 179 if tp = 'sc_sys' then 0 else ltl_sym(sc) end, 180 sc_nprocs(sc), 181 sc_stmt_ct(sc), 182 sc_estmt_ct(sc) 183 ); 184 185 186 end procedure put_header; 187 188 189 190 191 procedure put_trailer; 192$ 193$ this routine writes out a dummy header to indicate end of file. 194$ 195 putb(q1_file, sc_type_no(sc_end), '', 0, 0, 0, 0); 196 197 end procedure put_trailer; 198 199 1 .=member ptfm8f 2 3 4 procedure put_forms(sc); 5 6$ this routine writes a segment of formtab onto the q1 file. 7 8 repr 9 sc: elmt base_scopes; 10 frm: elmt forms; 11 i: integer; 12 tp: elmt base_ft_types; 13 mapc: elmt base_ft_mapcs; 14 ft_type_: integer; 15 ft_mapc_: integer; 16 mttab_: tuple(integer); 17 f: elmt forms; 18 ft_dom_: integer; 19 ft_im_: integer; 20 ft_imset_: integer; 21 ft_base_: integer; 22 ft_deref_: integer; 23 ft_tup_: integer; 24 ft_elmt_: integer; 25 frm1: elmt forms; 26 ft_low_: integer; 27 ft_lim_: integer; 28 flim: integer; 29 ft_pos_: integer; 30 fpos: integer; 31 ft_neltok_: boolean; 32 ft_hashok_: boolean; 33 ft_num_: tuple(integer); 34 localtyps: tuple(string); 35 fm: string; 36 outform: tuple(*); 37 end repr; 38 39$ iterate over formtab writing out entries one at a time. 40 41 putb(q1_file, orgind('formtab'), lastind('formtab')); 42 43 loop for_form(frm, sc) do 44 45$ get little index of form for use just below. 46 i := ltl_form(frm); 47 48 tp := ft_type(frm); 49 mapc := ft_mapc(frm); 50 51 ft_type_ := ft_type_no(tp); 52 ft_mapc_ := if mapc /= om then ft_mapc_no(mapc) else 0 end; 53 ft_deref_ := ltl_form(ft_deref(frm)); 54 55 if tp = f_mtuple or tp = f_proc then 56 mttab_ := [ ltl_form(f) : f in ft_elmt(frm) ]; 57 ft_dom_ := ft_im_ := ft_base_ := ft_tup_ := 0; 58 ft_elmt_ := ft_imset_ := 0; 59 60 else 61 ft_elmt_ := if ft_elmt(frm) /= om 62 then ltl_form(ft_elmt(frm)) else 0 end; 63 ft_dom_ := if (frm1 := ft_dom(frm)) /= om 64 then ltl_form(frm1) else 0 end; 65 ft_im_ := if (frm1 := ft_im(frm)) /= om 66 then ltl_form(frm1) else 0 end; 67 ft_imset_ := if (frm1 := ft_imset(frm)) /= om 68 then ltl_form(frm1) else 0 end; 69 ft_base_ := if (frm1 := ft_base(frm)) /= om 70 then ltl_form(frm1) else 0 end; 71 ft_tup_ := if (frm1 := ft_tup(frm)) /= om 72 then ltl_form(frm1) else 0 end; 73 end if; 74 75 ft_low_ := ft_low(frm) ? 0; 76 ft_lim_ := ft_lim(frm) ? 0; 77 ft_pos_ := ft_pos(frm) ? 0; 78 ft_neltok_ := ft_neltok(frm) /= om; 79 ft_hashok_ := ft_hashok(frm) /= om; 80 81 ft_num_ := []; 82 if is_fbase(frm) then 83 localtyps := 84 [ f_lset, f_lmap, f_lpmap, f_limap, f_lrmap ]; 85 (forall fm in localtyps) 86 ft_num_ with:= ft_num(frm)(fm); 87 end forall; 88 end if; 89 90 outform := 91 [ ft_type_, ft_mapc_, ft_elmt_, ft_dom_, ft_im_, 92 ft_imset_, ft_base_, ft_deref_, 93 ft_low_, ft_lim_, ft_pos_, ft_hashok_, ft_neltok_ ]; 94 95 if tp = f_mtuple or tp = f_proc then 96 outform with:= mttab_; 97 98 elseif is_frem(frm) and is_fmap(frm) then 99 outform with:= [ ft_tup_ ]; 100 101 elseif is_fbase(frm) then 102 outform with:= ft_num_; 103 104 else 105 outform with:= []; 106 end if; 107 108 putb(q1_file, outform); 109 end loop; 110 111 112 end procedure put_forms; 113 114 1 .=member ptst8g 2 3 4 procedure put_symtab(sc); 5$ 6$ this routine writes a segment of symtab onto the q1 file. 7$ 8 repr 9 sc: elmt base_scopes; 10 sym: symbol; 11 i: integer; 12 name_: string; 13 has_value_: boolean; 14 vlen_: integer; 15 val_: tuple(*); 16 alias_: integer; 17 sym1: symbol; 18 form_: integer; 19 is_temp_: boolean; 20 is_read_: boolean; 21 is_write_: boolean; 22 is_stk_: boolean; 23 is_param_: boolean; 24 is_store_: boolean; 25 is_repr_: boolean; 26 is_init_: boolean; 27 is_seen_: boolean; 28 is_back_: boolean; 29 is_rec_: boolean; 30 outsymb: tuple(*); 31 end repr; 32 33$ iterate over symtab, writing out one entry at a time. 34 35 putb(q1_file, orgind('symtab'), lastind('symtab')); 36 37 loop for_sym(sym, sc) do 38 39 i := ltl_sym(sym); 40 name_ := if is_internal(sym) = 1 then '' else name(sym) end; 41 [ has_value_, vlen_, val_ ] := bld_val(sym); 42 43 alias_ := if (sym1 := alias(sym)) /= om then 44 ltl_sym(sym1) else 0 end; 45 46$ note that the map 'is_internal' is relevant only to the optimizer. 47$ other parts of the compiler identify internal symbols as those having 48$ zero name field. therefore this map is ignored in this table dump. 49 50 form_ := ltl_form(form(sym)); 51 52$ set all flags 53 is_temp_ := is_temp(sym) /= om; 54 is_read_ := is_read(sym) /= om; 55 is_write_ := is_write(sym) /= om; 56 is_stk_ := is_stk(sym) /= om; 57 is_param_ := is_param(sym) /= om; 58 is_store_ := is_store(sym) /= om; 59 is_repr_ := true; 60 is_init_ := is_init(sym) /= om; 61 is_seen_ := is_seen(sym) /= om; 62 is_back_ := is_back(sym) /= om; 63 is_rec_ := is_rec(sym) /= om; 64 65 outsymb := 66 [ name_, form_, alias_, is_repr_, is_temp_, 67 is_stk_, is_read_, is_write_, is_param_, 68 is_store_, is_init_, is_seen_, is_back_, is_rec_, 69 has_value_ ]; 70 71 if has_value_ then $ the symbol has a value entry 72 outsymb with:= vlen_; 73 outsymb with:= val_; 74 end if; 75 76 putb(q1_file, outsymb); 77 end loop; 78 79 80 end procedure put_symtab; 81 82 1 .=member ptcd8h 2 3 4 procedure put_code(sc); 5 6$ this routine writes segments of blocktab and codetab. 7 8 repr 9 sc: elmt base_scopes; 10 block: elmt blocks; 12 inst: elmt insts; 13 i: integer; 14 opcode_: integer; 15 nargs_: integer; 16 blockof_: integer; 17 next_: integer; 18 inst1: elmt insts; 19 cflag_: integer; smfb 36 cf: elmt base_copy_actions; 21 sflag_: integer; 22 argsi: tuple(symbol); 23 args_: tuple(*); 24 j: integer; 25 outcode: tuple(*); 26 end repr; 27 28 putb(q1_file, orgind('blocktab'), lastind('blocktab')); 29 30 loop for_block(block, sc) do 33 putb(q1_file, ltl_inst(first_inst(block))); 34 end loop; 35 36 37$ finally build up codetab entries and write them out one at a time. 38 39 putb(q1_file, orgind('codetab'), lastind('codetab')); 40 41 loop for_block(block, sc) do loop for_inst(inst, block) do 42 i := ltl_inst(inst); 43 44 opcode_ := opcode_no(opcode(inst)); 45 nargs_ := # args(inst); 46 blockof_ := ltl_block(block); 47 48 next_ := if (inst1 := next_inst(inst)) /= om then 49 ltl_inst(inst1) else 0 end; 50 51 cflag_ := if (cf := copy_flag(inst)) /= om then 52 cflag_no(cf) else 0 end; 53 54 sflag_ := if share_flag(inst) /= om then 1 else 0 end; 55 56 argsi := args(inst); 57 args_ := [ ltl_sym(argsi(j)) : j in [ 1..nargs_ ] ]; 58 59 outcode := 60 [ opcode_, blockof_, next_, cflag_, sflag_, 61 nargs_, args_ ]; 62 63 putb(q1_file, outcode); 64 end loop; 65 end loop; 66 67 end procedure put_code; 68 69 1 .=member bldv8i 2 3 4 procedure bld_val(sym); 5$ 6$ this routine computes for a given symbol 'sym' three value-related 7$ entries: 8$ 9$ has_value_: a flag indicating whether the symbol has a value 10$ vlen_: the length of the value (see below) 11$ val_ : the value itself, as a series of subentries. 12$ 13$ this routine uses an auxiliary routine, elmt_sym, to find, given a 14$ value v, a form fm, and a scope sc, a symbol with value v and form fm 15$ in the containing scopes (inner-to-outer), or, if no such symbol 16$ exists, a symbol with value v and a form fm' so that fm' can be con- 17$ verted to fm. again, this search is done inner-to-outer through the 18$ containing scopes. note that such a symbol must exists unless there 19$ is a compiler bug. 20$ 21 repr 22 sym: symbol; 23 vl: general; 24 fm: elmt forms; 25 sc: elmt base_scopes; 26 tp: elmt base_ft_types; 27 tp1: string; 28 v: general; 29 i, j, n: integer; 30 val_: tuple(integer); 31 vli: sparse set(symbol); 32 sm: symbol; 33 end repr; 34 35 36 $ constants and initialised variables with omega as their value are 37 $ aliased to sym_om, and thus will be rebuild in the code generator 38 $ interface correctly, as we copy the value of aliased symbols from 39 $ the symbol they are alised to. 40 41 if value(sym) = om then return [ false, 0 ]; end if; 42 43 vl := value(sym); 44 fm := form(sym); 45 sc := scope(sym); 46 tp := ft_type(fm); 47 tp1 := simple_type(tp); $ int, real, etc. 48$ 'simple_type' maps each ft_type string to a string describing 49$ the 'basic' type of hat form, such as 'int', 'tuple', 'base' etc. 50 51 case tp1 of 52 53 ('int', 'real', 'string'): 54 55 return [ true, 1, [ vl ] ]; 56 57 ('atom'): 58 59 $ recall that booleans are represented by the short atoms 0 and 60 $ maxsi, where maxsi is an implementation-dependend constant 61 $ giving to the maximum value for a short integer. 62 $ note that there can be no other constants with mode atom. 63 64 if is_boolean vl then 65 return [ true, 1, [ vl ] ]; 66 else 67 print('*** illegal type for constant in output:', 68 'sym =', sym, name(sym), 'form =', fm, 'scope =', sc); 69 end if; 70 71 ('elmt'): $ element 72 73 return 74 [ true, 1, [ elmt_sym(vl, ft_elmt(ft_base(fm)), sym) ] ]; 75 76 ('tuple', 'set', 'map'): 77 78 return 79 [ true, 80 # vl, 81 if tp = f_mtuple then 82 [ elmt_sym(v, ft_elmt(fm)(j), sym) : v = vl(j) ] 83 else 84 [ elmt_sym(v, ft_elmt(fm), sym) : v in vl ] 85 end 86 ]; 87 88 ('proc'): $ procedures 89 90 $ the value of a procedure is a quadruple, giving (in this 91 $ order) the global for the return value, a flag indicating 92 $ whether the procedure has a variable number of arguments, the 93 $ number of formal parameters, and a sequence of rd's, rw's, 94 $ and wr's indicating how a parameter was declared. 95 96 return 97 [ true, 98 3 + # vl(4), $ 3 + length of sequence of rd's, ... 99 [ ltl_sym(vl(1)), $ global for the return value 100 if vl(2) = om then 0 else 1 end, $ variable #parameters 101 vl(3) ] + $ number of formal parameters 102 [ ltl_sym(vl(4)(i)) : i in [ 1..#vl(4) ] ] ]; 103$ note that here we use pointers to the little symbol table entries for 104$ the actual symbols rd/wr/rw; see the corresponding input routine 105$ cnvval. 106 107 ('memb'): $ members 108 109 $ the value of a member is a quintuple, giving (in this order) 110 $ the libraries referenced, the globals read, the globals writ- 111 $ ten, the procedures imported, and the procedures exported. 112 113 n := 0; val_ := []; 114 115 (forall i in [ 1..5 ]) 116 vli := vl(i); 117 118 n +:= (#vli + 1); 119 120 val_ with:= #vli; 121 (forall sm in vli) 122 val_ with:= ltl_sym(sm); 123 end forall; 124 end forall; 125 126 return [ true, n, val_ ]; 127 128 ('lab'): $ labels 129 smfi 1 return [ true, 1, [ ltl_inst(value(sym)) ] ]; 131 132 else 133 print('*** illegal type for constant in output:', 134 'sym =', sym, name(sym), 'form =', fm, 'scope =', sc); 135 end case; 136 137 end procedure bld_val; 138 139 140 141 142 procedure elmt_sym(vl, fm, sym); 143$ 144$ this routine returns the name of a setl symbol whose value is vl, 145$ whose type is fm, and whose scope is sc or a containing scope. 146$ 147 repr 148 vl: general; 149 fm: elmt forms; 150 sym: symbol; 151 sc, scx: elmt base_scopes; 152 s: symbol; 153 end repr; 154 155 sc := scope(sym); 156 157 if exists scx in cont_scopes(sc), s in value_inv{scx}{vl} | 158 form(s) = fm then 159 return ltl_sym(s); 160 161 elseif exists scx in cont_scopes(sc), s in value_inv{scx}{vl} | 162 can_conv(form(s), fm) then 163 return ltl_sym(s); 164 165 else 166 print('*** error in elmt_sym: scope = ' + str sc + 167 ', form = ' + str fm + 168 ', value = ' + str vl + ' ***' ); 169 170 print; 171 print('value_inv =', value_inv); 172 print; 173 end if; 174 175 176 end procedure elmt_sym; 177 178 1 .=member rset8j 2 3 4 procedure reset(sc, nxt); 5$ 6$ this procedure is called to reset the 'orgind' map when we are done 7$ writing out the table entries for a scope. 'sc' is the scope name, 8$ and 'nxt' is the next scope. 9$ 10$ the following cases may arise: 11$ 12$ 1. sc is not a procedure. in this case nxt is a subscope of sc; we 13$ stack the 'orgind' value for sc in 'org_stack', and take 'lastind' for 14$ sc to be the new 'orgind' value for nxt. 15$ 16$ 2. sc is a procedure and nxt is also a procedure. in this case we do 17$ nothing, as the current 'orgind' map is appropriate for nxt also, and 18$ there is no need to stack it. 19$ 20$ 3. sc is a procedure, and nxt is a library, the directory, or the 21$ main program. in this case we observe that the library and directory 22$ headers are static. in the case of libraries their headers are still 23$ needed since it contains the procedure symbol table entries for the 24$ exported procedures. since we like to keep these headers, we do 25$ nothing since the current orgind value is appropriate for nxt, too. 26$$-- for libraries, this is an overestimate: only the entries for ex- 27$$-- ported procedures and thei return values remain visible, but not 28$$-- their and 29$ 30$ 4. sc is a procedure, and nxt is a module header. in this case we pop 31$ org_stack to obtain the orgind map for nxt. note that the preceding 32$ scope must have been the main program or a module scope. 33$ 34 if sc_type(sc) = sc_proc then 35 if nxt = om or sc_type(nxt) = sc_mod then 36 orgind frome org_stack; 37 end if; 38 else $ keep entries till end of member 39 org_stack with:= orgind; 40 orgind := lastind; 41 end if; 42 43 end procedure reset; 44 45 46 end module setl_optimizer - interface; 47 48 1 .=member fpass9 2 3 4 module setl_optimizer - optinit; 5 6 7$ this module contains routines to initialize the optimizer. 8 9 var 10 expsym, $ maps expression opcode and args to the 11 $ expresion identifying temporary 12 expdepend, $ explicit dependency relation between 13 $ expressions 14 live_temps; $ live temporaries 15 16 init 17 expsym := {}, expdepend := {}, live_temps := {}; 18 19 repr 20 expsym: smap(tuple( 21 elmt base_opcodes, 22 tuple(symbol) )) 23 symbol; 24 expdepend: sparse mmap{symbol} 25 remote set(expression); 26 live_temps: sparse set(symbol); 27 28 readcc: procedure; 29 first_pass: procedure; 30 shortcut: procedure(symbol) symbol; 31 get_temp: procedure( 32 elmt base_opcodes, 33 tuple(symbol), 34 routine, 35 elmt insts ) 36 symbol; smfc 24 exp_name: procedure( smfc 25 elmt base_opcodes, smfc 26 tuple(symbol), smfc 27 elmt insts ) smfc 28 string; 39 transclose: procedure( 40 sparse set(symbol), 41 sparse mmap{symbol} 42 remote set(expression) ) 43 sparse mmap{symbol} 44 remote set(expression); 45 satisfy_members: procedure; 46 satisfy_procs: procedure; 47 bld_body: procedure( 48 routine, 49 sparse set(symbol), 50 sparse set(symbol), 51 sparse set(routine) 52 ); 53 bld_entry: procedure(routine); 54 bld_label: procedure(routine, symbol); 55 bld_use: procedure(routine, symbol); 56 bld_def: procedure(routine, symbol); 57 bld_call: procedure(routine, routine); 58 bld_exitstop: procedure(routine); 59 end repr; 60 61 62 procedure opt_ini; 63 64 readcc; $ read control card parameters 65 smfl 1 prog_level := 'opt(85023)'; 67 68 title('cims.setl.' + prog_level); 69 smfk 14 if lcp_flag then $ print phase heading smfk 15 print('parameters for this compilation:'); smfk 16 print; smfk 17 print('q1 file: q1 =', q1_file); smfk 18 print('source map file: ssm =', ssm_file); smfk 19 print('run-time error mode: rem =', rem); smfk 20 print('dumps requested: db =', dump_string); smfk 21 print; smfk 22 end if; 78 79 $ start log on terminal file 80 open(term_file, 'text-out'); 81 printa(term_file, ' start cims.setl.' + prog_level, date); 82 83 $ read the q1 file written by the semantic pass 84 open(q1_file,'binary'); read_q1; close(q1_file); 85 statistics with:= time; $ save initial time 86 87 first_pass; $ a priliminary pass through the code 88 89 $ dump tables 90 if 's' in dump_string then dmp(om, 'symtab'); end if; 91 if 'f' in dump_string then dmp(om, 'formtab'); end if; 92 if 'c' in dump_string then dmp(om, 'codetab'); end if; 93 94 statistics with:= time; $ save initial time 95 96 97 end procedure opt_ini; 98 99 100 101 102 procedure readcc; 103$ 104$ this routine reads the control card parameters relevant to the 105$ optimizer. 106$ 107$ control card parameters are read in using two procedures in the 108$ standard prelude: 109$ 110$ getipp(s): returns the value of an integer control card parameter 111$ getspp(s): returns the value of a string control card parameter 112$ 113$ 's' is a string of the form 'xxx=yyy/zzz' where: 114$ 115$ 'xxx' is the name of the parameter as it appears on the control card 116$ 'yyy' is the default if the parameter is not supplied 117$ 'zzz' is the default if only the parameter name is supplied. 118$ 119 q1_file := getspp('q1=q1/q1'); $ q1 file name 120 ssm_file := getspp('ssm=/'); $ optimiser source map 121 term_file := getspp('sterm=0/0'); $ terminal file name 122 rem := getipp('rem=1/1'); $ run-time error mode 123 debug_flag := getipp('odebug=1/1') = 1; $ perform debugging code 124 at_flag := getipp('at=0/1') = 1; $ automatic titling smfk 23 lcp_flag := getipp('lcp=0/1') = 1; $ list program parms smfk 24 lcs_flag := getipp('lcs=1/1') = 1; $ list program stats 125 dump_string := getspp('db=/sfci'); $ dump options 126 127 128 end procedure readcc; 129 130 131 132 133 procedure first_pass; 134$ 135$ this procedure does a preliminary pass over the program performing 136$ the following tasks: 137$ 138$ 1. it generates a dummy routine for each unsatisfied external 139$ procedure. 140$ 141$ 2. it iterates over the code making various local cleanups which 142$ simplify later algorithms. at the same time it builds various 143$ auxiliary sets and maps. 144$ 145 init smfh 8 varofexps := {}, push_of := {}, smfh 9 all_eq := {}, all_system_routs := {}; 148 149 repr 150 sc, scx: elmt base_scopes; 151 all_system_routs: sparse set(symbol); smfg 16 all_eq: sparse set(elmt insts); 152 sym: symbol; 153 r: routine; 154 params: tuple(symbol); 155 varofexps: sparse set(symbol); 156 expoftmp: sparse smap(symbol) symbol; 157 b: elmt blocks; 158 i, iprev: elmt insts; 159 opc: elmt base_opcodes; 160 oi1: occurrence; 161 a1, a2, a3: symbol; 162 p1: symbol; 163 formpar: symbol; 165 x, s: symbol; 166 y: occurrence; smfg 17 i1, i2: elmt insts; smfg 18 op1, op2: elmt base_opcodes; smfg 19 opc1, opc2: elmt base_opcodes; smfg 20 l: symbol; 168 opcarb, opcless: elmt base_opcodes; 169 j: integer; 170 a, aa, aivs: tuple(symbol); 171 aj, bj: symbol; 172 tsym: symbol; 173 opcrev: elmt base_opcodes; 174 lsin: integer; 175 presym: symbol; 181 rem_all_oi: remote set(occurrence); 182 oi: occurrence; 183 v: symbol; 184 push_of: sparse smap(symbol) elmt insts; 185 casemap, cas: general; 186 lab: symbol; 187 end repr; 188 189 190$ the cleanups performed here are as follows: 191$$$ ??????? art: supply description of cleanups 192$ the auxiliary sets and maps built are as follows: 193$ we begin by building the set of all procedures in the input. 194 routs := { sc in scopes | sc_type(sc) = sc_proc }; 195 $ note that this set includes main program, too. 196 197 (for_sym(sym, sym_sys)) 198 if ft_type(form(sym)) = f_proc and sym /= sym_main then 199 all_system_routs with:= sym; 200 end if; 201 if name(sym) = 'om' then sym_om := sym; end if; 202 end; 203 204$ check whether the input contains a directory and a main program. 205$ if not, build dummies. 206 207 satisfy_members; 208 209$ next fill in the bodies of all procedures mentioned in the directory 210$ but not supplied by the user. 211 212$ sometimes we will find two unsatisfied externals p1 and p2 which can 213$ have the same dummy body. rather than building separate bodies for 214$ each of them we will set alias(p1) = p2. when we iterate over the 215$ code we will substitute p2 for each occurrence of p1. 216 217 satisfy_procs; 218 219$ build 'rparams' sending each procedure into a tuple containing its 220$ formal parameters. if a procedure has 'n' formal parameters they are 221$ always its first 'n' symbols. 222 223 (forall r in routs) 224 params := []; 225 226 (for_sym(sym, r)) 227 if is_param(sym)=1 then 228 params with:= sym; 229 elseif ft_type(form(sym)) /= f_lab then 230 quit; 231 end if; 232 end; 233 rparams(r) := params; 234 end forall; 235 236$ next we make a pass over the code to build the 'oi_sets', 'exp_maps', 237$ and 'var_maps', and to accomplish various cleanups. 238 239$ the loop body consists of two parts. the first part is a case 240$$$ ???? reword this enigmatic paragraph 241$ statement which performs various special actions and putting 242$ the instruction's first occurrence into the appropriate sets. 243$ the second part adds the instructions remaining occurrences 244$ to all_oi and all_i. 245 246$ the loop builds the following sets and maps listed above: 247$ 248$ a. rentry: sends each routine into its entry block 249$ b. rexit: sends each routine into its exit block 250$ c. rstop: sends each routine into its stop block 251$ d. all_oi: set of all occurrences 252$ e. all_o: set of ovariables 253$ f. all_i: set of ivariables 254$ g. callsin: maps each routine to its call blocks 255$ h. callproc: maps each call block to the routine it calls 256$ i. cgraph: the call graph itself. 257$ j. globalvars: set of global variables 258$ k. localvars: maps each routine to its local variables 259$ variables: set of all program variables 260$ l. occsof: maps each variable to its occurrences 261$ m. globalexps: set of global expressions 262$ n. localexps: maps each routine to its local expressions 263$ o. expdepend: explicit dependency relation between expressions 264$ varofexps: user variables which are expression operands 265$ live_temps: all temporaries still needed after code modification 266$ system_routs: system routines call by the program 267$ allexps: set of all expressions 268$ opcexp: maps expressions to their defining opcode 269$ argsexp: maps expressions to tuple of their input args 270$$$ ??????? micha: it may be most efficient to build 271$$$ ??????? some block propagation maps right here 272$ 273$ let us comment on the way we handle expressions in the optimiser. the 274$ semantic pass generates a temporary variable as an output variable for 275$ for each expression computation, but maintains uniqeness of these 276$ names only within a basic block, so that the same expression, computed 277$ in different blocks, may be assigned (i.e. identified with) different 278$ target temporaries. however, for our redundant expression elimination 279$ phase, we want to maintain this unique representation of expressions 280$ by their target temporaries throughout the whole program. this 281$ requires renaming of temporaries, which is performed in this loop. 282$ 283$ this is done as follows: for each basic block we build a map 284$ 'expoftmp', mapping each temporary to its new expression-identifying 285$ temporary symbol. when we process an instruction i of the form 286$ 't := op(a1, a2...an)' which is known to yield an expression which is 287$ well defined and has no side effects (thus excluding expressions com- 288$ puted e.g. by 'q1_arb' or 'q1_rand' operators), we replace each 289$ temporary among the i-variables by its 'expoftmp' value (which must be 290$ already available) to obtain a new list (b1, b2...bn) of input argu- 291$ ments. then the pair tn = [ op, input args ] uniquely identifies the 292$ expression being computed by this instruction; we maintain a map 293$ 'expsym' which maps each such pair to the corresponding expression- 294$ identifying temporary symbol. using this map we get the required 295$ target variable for i and replace t, if necessary, by that variable. 296$ 297$ we also try to re-use temporaries generated by the semantic pass as 298$ the new expression names. any temporary that is not re-used is marked 299$ as a dead symbol and will be later removed from the symbol table. 300$ 301 (forall r in routs) 302 expoftmp := {}; 303 (for_block(b, r)) 304 iprev := om; $ keep previous instruction 305 306 (for_inst(i, b)) 307 opc := opcode(i); 308 oi1 := get_oi(i, 1); $ first occurrence 309 310 case opc of 311 312 (q1_stmt): 313 314 iprev := i; 315 continue; 316 317 (q1_argin, q1_argout, q1_free): 318 319 $ the second parameter of the current instruction, a2, 320 $ gives the name of the routine called. if this is a 321 $ call to an unsatisfied external procedure, we must 322 $ account for the possibility that satisfy_procs might 323 $ have determined that a2 belongs to a class of proce- 324 $ dures p1, p2, ..., pn, all with identical parameters, 325 $ globals accessed, etc., and decided not to supply a 326 $ dummy body for each pi, but rather to supply a dummy 327 $ body for some pj, and to set alias(pi) = pj for the 328 $ remaining pi's. if this is the case, we substitute pj 329 $ for pi in the instruction. 330 $ 331 $ furthermore, we recall that the data flow algorithms 332 $ require that the formal parameter name appears as an 333 $ explicit, assignment-like argument to the argin and 334 $ argout instructions. we add it at this time. 335 $ 336 $ if, on the other hand, this instruction is part of a 337 $ call to a setl systems routine such as read or print, 338 $ then we account for the fact that we do not know the 339 $ structure of these routines by replacing argin and 340 $ argout instructions by sargin and sargout instruc- 341 $ tions, and thus are able to distinguish the calling 342 $ sequences. note that we consider the first argument 343 $ of an sargin instruction to be an i-variable, while 344 $ the first argument of an sargout is an o-variable. 345 $ 346 $ note that the above remarks also hold for q1_free 347 $ instructions, since the code generator needs to know 348 $ the forms of the formal parameters to be able to emit 349 $ the proper stack pops. 350 351 a2 := arg2(i); 352 if a2 in all_system_routs then 353 system_routs with:= a2; 354 if opc = q1_argin then 355 opc := opcode(i) := q1_sargin; 356 elseif opc = q1_argout then 357 opc := opcode(i) := q1_sargout; 358 end if; 359 360 else 361 p1 := alias(a2); 362 if p1 /= om then arg2(i) := a2 := p1; end if; 363 364 formpar := rparams(a2)(value(arg3(i))); 365 if opc = q1_argin then 366 args(i) := [ formpar ] + args(i); 367 elseif opc = q1_argout then 368 args(i) with:= formpar; 369 end if; 370 smfc 30 if #args(i) /= #occs(i) then smfc 31 oi := newat; $ record additional occurrence smfc 32 instno(oi) := i; smfc 33 argno(oi) := #args(i); smfc 34 occs(i) with:= oi; smfc 35 end if; 375 end if; 376 377 (q1_call): 378 379 $ here the first argument, a1, is the procedure name. 380 $ as explained above, a1 may be an alias for some other 381 $ procedure. 382 383 a1 := arg1(i); 384 if a1 in all_system_routs then 385 system_routs with:= a1; 386 387 $ assure that this is not a call to host 388 if name(a1) = 'host' then 389 abort('attempt to optimise program with host'); 390 end if; 391 392 $ define the return value 393 i1 := i; 394 insert_ins(i1, q1_def, value(a1)(1)); 395 else 396 p1 := alias(a1); 397 if p1 /= om then arg1(i) := a1 := p1; end if; 398 smfg 21 cgraph with:= [ r, a1 ]; $ 'r' calls 'a1' smfg 22 callsin with:= [ r, b ]; $ call block in 'r' smfg 23 callproc(b) := a1; $ 'b' calls 'a1' smfg 24 smfg 25 $ this is a 'single-instruction' block, i.e. it smfg 26 $ consists of a label, a call, and a goto. smfg 27 $ assert opcode(first_inst(b)) = q1_label; smfg 28 $ assert next_inst(first_inst(b)) = i; smfg 29 $ assert next_inst(i) = last_inst(i); 400 end if; 401 402 (q1_entry): 403 404 rentry(r) := b; 405 406 (q1_exit): 407 408 rexit(r) := b; 409 410 (q1_stop): 411 412 rstop(r) := b; 413 414 (q1_sof, q1_sofa, q1_send, q1_ssubst, q1_next): 415 416 $ these are opcodes whose first argument is both an 417 $ i-variable and an o-variable. to simplify our task, 418 $ we add a last argument, equal to the first argument, 419 $ to account for the i-occurrence. 420 smfg 30 args(i) with:= arg1(i); 423 424 oi := newat; $ record additional occurrence 425 instno(oi) := i; 426 argno(oi) := #args(i); 427 occs(i) with:= oi; 428 429 (q1_from, q1_fromb, q1_frome): 430 431 $ the second argument for these opcodes is both an 432 $ i-variable and an o-variable. since 'x from s' is 433 $ semantically equivalent to 'x := arb s; s less:= x', 434 $ we merely transform the former to the latter. note 435 $ that neither from nor arb yield valid expressions. 436 $ this observation is important if we later want to 437 $ reverse the arbb and arbe sequences into fromb's and 438 $ frome's, resp. (this is done in the output interface 439 $ cleanup). 440 441 [ x, s ] := args(i); 442 443 opcode(i) := q1_noop; 444 args(i) := []; 445 occs(i) := []; 446 447 if opc = q1_from then 448 opcarb := q1_arb; 449 opcless := q1_less; 450 elseif opc = q1_frome then 451 opcarb := q1_arbe; 452 opcless := q1_lesse; 453 else 454 opcarb := q1_arbb; 455 opcless := q1_lessb; 456 end if; 457 458 if is_temp(s) /= om then is_temp(s) := om; end if; 459 assert is_temp(x) = om; 460 i1 := i; 461 insert_ins(i1, opcarb, x, s ); 462 insert_ins(i1, opcless, s, s, x); 463 opc := q1_noop; smfg 31 smfg 32 (q1_eq, q1_ne): smfg 33 smfg 34 if arg2(i) /= arg3(i) then smfg 35 if arg2(i) = sym_om or arg3(i) = sym_om then smfg 36 all_eq with:= i; smfg 37 end if; smfg 38 end if; 464 465 (q1_inext, q1_inextd): 466 467 $ precede the iterator variable a2 by a q1_def, so that 468 $ we don't have to worry about the two o-variables. 469 $ note that this variable has no influence on optimisa- 470 $ tion, except that it appears as the operand to a test 471 $ for omega which signals the end of the iteration. smfi 3 if #name(x := arg1(i)) > 2 and name(x)(1..2) = 't.' then smfi 4 if 'c' notin dump_string then smfi 5 name(x) := ''; smfi 6 end if; smfi 7 end if; smfe 15 smfe 16 itervars with:= arg2(i); smfk 25 smfk 26 if #name(x := arg3(i)) > 2 and name(x)(1..2) = 't.' smfk 27 and opcode(iprev) = q1_asn and x = arg1(iprev) smfk 28 and 'c' notin dump_string smfk 29 then smfk 30 name(x) := name(arg2(iprev)); smfk 31 end if; 472 473 i1 := iprev; 474 insert_ins(i1, q1_def, arg2(i)); 475 476 (q1_push): 477 x frome args(i); 478 y frome occs(i); 479 push_of(x) := i; smfi 8 smfi 9 if 'c' notin dump_string then smfi 10 if is_temp(x := arg1(i)) /= om smfi 11 and opcode(iprev) = q1_asn smfi 12 and arg1(iprev) = arg1(i) then smfi 13 name(x) := name(arg2(iprev)); smfi 14 end if; smfi 15 end if; 480 481 (q1_set1, q1_tup1): 482 push_former(push_of(arg1(i))) := i; smfi 16 smfi 17 if #name(x := arg1(i)) > 2 and name(x)(1..2) = 't.' then smfi 18 if 'c' notin dump_string then smfi 19 name(x) := format_inst(i, om); smfi 20 end if; smfi 21 end if; 483 484 (q1_case): 485 casemap := value(arg1(i)); 486 value(arg1(i)) := 487 { [ cas, shortcut(lab) ] : lab = casemap(cas) }; 488 489 (q1_goto): 490 arg1(i) := shortcut(arg1(i)); 491 last_inst(b) := i; 492 next_inst(i) := om; 493 smfg 39 (q1_if, q1_ifnot, q1_bif, q1_bifnot): 495 arg2(i) := shortcut(arg2(i)); smfg 40 smfg 41 (q1_ifasrt): smfg 42 arg1(i) := shortcut(arg1(i)); 496 497 (q1_error): 498 abort('optimisation terminated due to ' 499 'prior compilation error'); 500 501 (q1_ok, q1_fail, q1_succeed, q1_lev): 502 abort('attempt to optimise program with backtracking'); 503 504 end case; 505 506$ add occurrences in i to the various 'oi_sets'. 507 (forall j in [ first_ivar(opc)..#args(i) ]) 508 oi := get_oi(i, j); 509 510 all_oi with:= oi; 511 all_i with:= oi; 512 end forall; 513 514 if opc in ops_ovar then 515 all_oi with:= oi1; 516 all_o with:= oi1; 517 end if; 518 519$ update the various 'exp_maps' 520 521 a := args(i); $ get arguments 522 if opc in ops_exps then 523 (forall j in [ 2..#a ]) 524 aj := a(j); 525 if (bj := expoftmp(aj)) /= om then 526 a(j) := aj := bj; 527 else 528 if is_const(aj) = om and aj /= sym_om then 529 varofexps with:= aj; 530 end if; 531 end if; 532 end forall; 533 534 aivs := a(2..); $ get input arguments 535 536 $ get expression-identifying temporary 537 tsym := get_temp(opc, aivs, r, i); 538 539 $ replace the output temporary by the new one and note 540 $ that correspondence. 541 a(1) := expoftmp(a(1)) := tsym; 542 543 else 544 $ not an expression-producing instruction: in this case 545 $ just replace temporaries by their new expression- 546 $ identifying symbols. 547 (forall j in [ first_ivar(opc)..#a ]) 548 if (bj := expoftmp(a(j))) /= om then 549 a(j) := bj; 550 end if; 551 end forall; 552 553 $ if a sinister assignment, e.g. 'f(x) := y', replace it 554 $ by the pair 't := y' 'f(x) := t', where t is the 555 $ expression-identifying temporary for 'f(x)'. 556 557 if opc in ops_sin then 558 if a(1) /= (a1 := a(#a)) then 559 a(1) := a1; 560 varofexps with:= a1; 561 end if; 562 563 opcrev := $ the reverse opcode 564 case opc of 565 (q1_sof): q1_of, 566 (q1_sofa): q1_ofa, 567 (q1_send): q1_end, 568 (q1_ssubst): q1_subst 569 else om end; 570 lsin := if opc = q1_ssubst then 3 else 2 end; 571 aa := a(1..lsin); 572 tsym := get_temp(opcrev, aa, r, i); 573 if a(lsin+1) /= tsym then 574 insert_ins(iprev, q1_asn, tsym, a(lsin+1)); 575 a(lsin+1) := tsym; 576 end if; 577 end if; 578 579 if opc in ops_ovar then 580 live_temps with:= a(1); 581 end if; 582 end if; 583 584 $ restore argument list 585 args(i) := a; 586 iprev := i; 587 end; $ end for_inst; 588 end; $ end for_block; 589 end forall; 590$ 591$ next delete all dead temporaries from the symbol table 592$ 593 (forall sc in scopes) 594 presym := om; $ note previous symbol 595 (for_sym(sym, sc)) 596 if is_temp(sym)=1 and sym notin live_temps then 597 del_sym(sym, presym, sc); 598 sym := presym; 599 else 600 presym := sym; 601 end if; 602 end; $ end for_sym; 603 end forall; smfg 43$ smfg 44$ iterate over all equality tests with one constant operand to see smfg 45$ whether control flow information should be made explicit for the type smfg 46$ finder. smfg 47$ smfg 48 (forall i1 in all_eq) smfg 49 i2 := next_inst(i1); smfg 50 op1 := opcode(i1); smfg 51 op2 := opcode(i2); smfg 52 smfg 53 if op2 notin { q1_if, q1_ifnot, q1_bif, q1_bifnot } then smfg 54 continue forall; smfg 55 end if; smfg 56 if arg1(i1) /= arg1(i2) then continue forall; end if; smfg 57 smfg 58 if arg2(i1) = sym_om or arg3(i1) = sym_om then smfg 59 a1 := if arg2(i1) /= sym_om then arg2(i1) else arg3(i1) end; smfg 60 opc1 := q1_isom; opc2 := q1_notom; smfg 61 end if; smfg 62 smfg 63 if op1 = q1_eq and op2 in { q1_ifnot, q1_bifnot } smfg 64 or op1 = q1_ne and op2 in { q1_if, q1_bif } then smfg 65 [ opc1, opc2 ] := [ opc2, opc1 ]; smfg 66 end if; smfg 67 smfg 68 r := routof(blockof(i2)); smfg 69 smfg 70 b := add_block(om, r, true); smfg 71 l := add_label(r); smfg 72 i := add_inst(b, q1_label, l); smfg 73 value(l) := i; smfg 74 stmtof(i) := stmtof(i1); smfg 75 smfg 76 insert_ins(i, opc1, a1); smfg 77 insert_ins(i, q1_goto, arg2(i2)); smfg 78 smfg 79 arg2(i2) := l; smfg 80 smfg 81 insert_ins(i2, opc2, a1); smfg 82 smfg 83 end forall; 661$ 662$ next compute globalvars, localvars and occsof. 663$ 664 (forall oi in (rem_all_oi := all_oi)) 665 v := oi_sym(oi); 666 667 $ record only occurrences which are not constant, thus ignoring 668 $ constant variables, denotations, labels, procedures, etc. 669 670 if is_const(v) = om and v /= sym_om then 671 672 variables with:= v; 673 occsof{v} with:= oi; 674 675 $ in addition, separate the variables which appear in the 676 $ user's source program, thus ignoring temporaries, return 677 $ values, etc. 678 679 if is_internal(v) = om then smff 2 if '(' notin name(v) and '$' notin name(v) then 681 uservars with:= v; 682 end if; 683 end if; 684 end if; 685 end forall; 686 687 (forall sc in scopes) 688 (for_sym(v, sc)) 689 if v in variables then 690 if sc_type(sc) = sc_proc then 691 localvars{sc} with:= v; 692 else 693 globalvars with:= v; 694 end if; 695 end if; 696 697 $ mark all variables which occur only once in the input 698 699 if #occsof{v} = 1 and v in uservars then smfc 36 messages{stmtof(instno(occsof(v)))}{'i'} with:= 701 [ '"' + name(v) + '" appears only here.' ]; smfe 17 smfe 18 elseif occsof{v} = {} and v in uservars then smfe 19 messages{sc_estmt_ct(scope(v))}{'i'} with:= smfe 20 [ '"' + name(v) + '" is declared but not used.' ]; 702 end if; 703 end; $ end for_sym; 704 smfb 38 $ take the opportunity to build cont_scopes which maps each smfb 39 $ scope to a tuple of scopes which contain it. 706 707 scx := membof(sc) ? sc; $ scx is the first non-procedure scope 708 709 case sc_type(scx) of 710 (sc_sys): cont_scopes(sc) := [ sym_sys ]; 711 (sc_lib): cont_scopes(sc) := [ scx, sym_sys ]; 712 (sc_dir): cont_scopes(sc) := [ sym_dir, sym_sys ]; 713 (sc_prog): cont_scopes(sc) := [ scx, sym_dir, sym_sys ]; 714 (sc_mod): cont_scopes(sc) := [ scx, sym_dir, sym_sys ]; 715 end case; 716 717 if sc_type(sc) = sc_proc then 718 cont_scopes(sc) := [ sc ] + cont_scopes(sc); 719 end if; 720 end forall; 721$ 722$ finally, compute the 'dependon' relation as the transitive closure of 723$ 'expdepend', domain-restricted to 'varofexps'. 724$ 725 dependon := transclose(varofexps, expdepend); 726 727 $ delete the static variables global to the module 728 expsym := om; expdepend := om; live_temps := om; 729 730 731 end procedure first_pass; 732 733 734 735 736 procedure shortcut(lab); 737$ 738$ this routine returns the label of the first non-empty block that can 739$ be reached from the label lab. 740$ 741 repr 742 lab: symbol; 743 seenlabs: sparse set(symbol); 744 i1, i2: elmt insts; 745 end repr; 746 747 seenlabs := {}; 748 749 loop 750 doing 751 i1 := first_inst(blockof(value(lab))); 752 i2 := next_inst(i1); 753 $ nb. the first instruction of each basic block is either a 754 $ q1_label or a q1_tag instruction, and the last instruction 755 $ of each basic block is branch instruction. hence a block 756 $ whose second instruction is an unconditional branch 757 $ instruction is a trivial block, and can be deleted. if the 758 $ first instruction is a q1_tag instruction, the corresponding 759 $ case map must be changed. since this case should arise 760 $ rarely, we ignore it, and only short-cut q1_label/q1_goto- 761 $ blocks. 762 while 763 opcode(i1) = q1_label and opcode(i2) = q1_goto 764 do 765 if lab in seenlabs then 766 print(' *** warning: an infinite loop starting at', lab); 767 quit loop; 768 end if; 769 770 seenlabs with:= lab; 771 cut_blocks with:= blockof(i1); 772 lab := arg1(i2); 773 end loop; 774 775 return lab; 776 777 778 end procedure shortcut; 779 780 781 782 783 procedure get_temp(opc, aivs, r, i); 784 785 repr 786 opc: elmt base_opcodes; 787 aivs: tuple(symbol); 788 r: routine; 789 i: elmt insts; 790 tn: tuple(elmt base_opcodes, tuple(symbol)); 791 tsym: symbol; 792 scps: sparse set(elmt base_scopes); 793 scr, scexp: elmt base_scopes; 794 aj: symbol; 795 j: integer; 796 end repr; 797 798 799 tn := [ opc, aivs ]; $ expression-identifying pair 800 801 if (tsym := expsym(tn)) = om then 802$ first find scope of tsym, and whether it has to be analysed 803$ locally or globally. 804 scps := { scope(aj) : aj in aivs}; 805$$$ ???? note that here we also take into account constant arguments of 806$$$ ???? the expression. thus 'x + 2' where x is local to r will be 807$$$ ???? analyzed globally, as '2' is a global constant. the reason for 808$$$ ???? doing so is that cases like 'x + 375', where 'x' is global but 809$$$ ???? '375' is a constant local to r, will be handled incorrectly if 810$$$ ???? we ignore constant arguments; namely, this expression will be 811$$$ ???? regarded as a global expression. this is o.k. if and only if 812$$$ ???? we move '375' to the symbol table of the global scope of this 813$$$ ???? expression. eventually we will do this, but for the time being 814$$$ ???? we do not exclude constants. 815 816 scexp := 817 if r in scps then 818 r 819$ allow for the peculiar phenomenon that '_main' belongs 820$ to the system scope rather than to the containing program scope 821 elseif r = sym_main and sym_prog in scps then 822 sym_prog 823 elseif (scr := scope(r)) in scps then 824 scr 825 else 826 arb scps 827 end; 828 829 tsym := add_sym(scexp); 830$ 831$ define the relevant symbol table maps 832$ smfc 37 name(tsym) := exp_name(opc, aivs, i); 834 form(tsym) := std_form(f_gen); 835 is_temp(tsym) := 1; 836 is_internal(tsym) := 1; 837 is_read(tsym) := 1; 838 is_write(tsym) := 1; 839 is_store(tsym) := 1; 840 841 if exists aj in aivs | is_stk(aj) = 1 or is_param(aj) = 1 then 842 is_stk(tsym) := 1; 843 end if; 844$ 845$ update the relevant epression sets and maps 846$ 847 if exists aj in aivs | 848 aj in globalexps or 849 is_const(aj) = om and aj /= sym_om and scope(aj) /= r 850 then 851 globalexps with:= tsym; 852 else 853 localexps{r} with:= tsym; 854 end if; 855 856 (forall aj in aivs | is_const(aj) = om and aj /= sym_om) 857 expdepend{aj} with:= tsym; 858 end forall; 859 860 expsym(tn) := tsym; 861 opcexp(tsym) := opc; 862 argsexp(tsym) := aivs; 863 allexps with:= tsym; 864 live_temps with:= tsym; 865 866 else 867 $ we re-use the same temporary for this expression. this 868 $ implies that we have to change the temporary to an internal 869 $ variable since the storage allocation algorithm assumes that smfi 22 $ each temporary is used exactly once. 871 is_temp(tsym) := om; 872 end if; 873 874 return tsym; 875 876 877 end procedure get_temp; 878 879 880 881 882 procedure transclose(a, rel); 883 884$ compute the transitive closure of the relation rel, domain-restricted 885$ to the set a. 886 887$ although transclose is a general routine, its sole use in 888$ first_pass justifies the representations below. 889 890 repr 891 a: sparse set(symbol); 892 rel: sparse mmap{symbol} 893 remote set(expression); 894 tcl: sparse mmap{symbol} 895 remote set(expression); 896 897 x: symbol; 898 y: expression; 899 tclx, newx, delta: remote set(expression); 900 end repr; 901 902 tcl := {}; 903 (forall x in a) 904 tclx := newx := rel{x}; 905 (while newx /= {}) 906 y from newx; 907 delta := rel{y} - tclx; 908 newx +:= delta; 909 tclx +:= delta; 910 end while; 911 tcl{x} := tclx; 912 end forall; 913 914 return tcl; 915 916 end procedure transclose; 917 918 919 920 smfc 38 procedure exp_name(opc, aivs, i); 922$ 923 repr smfc 39 opc: elmt base_opcodes; smfc 40 aivs: tuple(symbol); 924 i: elmt insts; smfc 41 a: tuple(symbol); 925 end repr; 926 927 if 'c' in dump_string then return 't' + str i; end if; 928 smfc 42 if opc = opcode(i) then smfc 43 return format_inst(i, aivs); smfc 44 elseif opc in ops_retrieve and opcode(i) in ops_sin then smfc 45 a := aivs(2..); a(#aivs+1) := aivs(1); smfc 46 return format_inst(i, a); smfc 47 else smfc 48 return 't' + str i; smfc 49 end if; 931 932 933 end procedure exp_name; 934 935 1 .=member smem9a 2 3 4 procedure satisfy_members; 5$ 6$ this routine satisfies missing members, so that we get the program 7$ into a normal form. this simplifies the optimisation of separately 8$ compiled members considerably, and assures that we will always have a 9$ main program. 10$ 11$ a member is a library, a directory, a program, or a module. 12$ 13$ the semantic pass can compile program and module members separately, 14$ provided that the program has a directory specifying the rights of the 15$ individual modules w.r.t. read/write access to globals, and what pro- 16$ cedures are imported/exported by which member. libraries are by their 17$ definition independent to separate compilation since we always require 18$ all libraries accessed by any member to be present during the semantic 19$ pass. thus the following are some examples for valid separate compi- 20$ lations: 21$ 22$ 1. zero or more libraries 23$ 2. zero or more libraries, plus a directory 24$ 3. a directory, plus a main program 25$ 4. a directory, plus zero or more modules 26$ 27$ note that it does not make sense to speak about separate compilation 28$ for a simple setl program, as such a program consists of a program 29$ scope only. 30$ 31$ to simplify our algorithms, we normalise the input so that we have: 32$ 33$ zero or more libraries, 34$ followed by a directory, 35$ followed by a main program, 36$ followed by zero or more modules. 37$ 38$ this means that in case 1, above, we have to generate a new directory 39$ and a new main program, and in cases 2 and 4, a main program. 40$ 41 repr 42 j: integer 0..65536; 43 s: symbol; 44 sc: elmt base_scopes; 45 end repr; 46$ 47$ if the input supplied a directory, then simply return; otherwise, add 48$ a new scope for a dummy directory, and initialise it. 49$ 50 if sym_dir /= om then return; end if; 51 52 sym_dir := s := newat; $ add a new scope for the directory 53 54 $ initialise the relevant scope maps 55 sc_type(sym_dir) := sc_dir; 56 sc_nprocs(sym_dir) := 0; 57 sc_stmt_ct(sym_dir) := 0; 58 sc_estmt_ct(sym_dir) := 0; 59 60 last_sym(sym_dir) := first_sym(sym_dir) := sym_dir; 61 62 $ add the new scope to the tuple of all scopes smfi 23 if exists sc = scopes(j) | smfi 24 sc_type(sc) = sc_prog or sc_type(sc) = sc_mod then 64 $ insert the directory before the main program 65 scopes(j..j-1) := [ sym_dir ]; 66 else 67 $ input has only libraries 68 $ (if it had main program, we would have found its scope above.) 69 $ (if it had no main program, but zero or more modules, it would 70 $ have a directory.) 71 $ (note that this means that we will build a dummy main program 72 $ as well.) 73 scopes with:= sym_dir; 74 end if; 75 76 $ initialise the symbol table entry just created 77 $ (note that a direcory member has no value due to a current bug in 78 $ grm/prs/sem - implementation restriction...) 79 name(s) := '_directory'; 80 scope(s) := sym_dir; 81 form(s) := std_form(f_memb); 82$ 83$ if the input supplied a main program, we move it into the directory we 84$ just created, and for simplicity mark the directory as seen, thus 85$ eventually writing it out for the code generator. this is ok since 86$ we look at a simple program, which cannot be part of a separate com- 87$ pilation. if the input did not supply a main program, we create a 88$ dummy main program and have it reference all libraries. note that if 89$ we don't have a directory, we cannot have any modules either. this 90$ is a consequence of the definition of module programs. 91$ 92 if sym_prog /= om then $ we have a main program: move it 93 94 last_sym(sym_dir) := next_sym(sym_dir) := sym_prog; 95 first_sym(sym_prog) := next_sym(sym_prog); 96 next_sym(sym_prog) := om; 97 if first_sym(sym_prog) = om then last_sym(sym_prog) := om; end; 98 99 scope(sym_prog) := sym_dir; 100 all_modules with:= sym_prog; 101 102 is_seen(sym_dir) := 1; 103 104 else 105 sym_prog := s := newat; $ add a new program scope 106 last_sym(sym_dir) := next_sym(sym_dir) := sym_prog; 107 108 $ (the relevant scope maps will be initialised in satisfy_procs) 109 110 $ initialise the relevant symbol table maps 111 name(s) := '_program'; 112 scope(s) := sym_dir; 113 form(s) := std_form(f_memb); 114 is_const(s) := 1; 115 value(s) := [ { sc in scopes | sc_type(sc) = sc_lib }, 116 {}, {}, {}, {} ]; 117 end if; 118 119 120 end procedure satisfy_members; 121 122 1 .=member sprc9b 2 3 4 procedure satisfy_procs; 5$ 6$ this routine generates dummy routines for all unsatisfied external 7$ procedures. 8$ 9$ before we outline our algorithm, let us stress once more that a mem- 10$ ber can always import all routines exported by the libraries it refe- 11$ rences. 12$ 13$ we begin by assuming that all the modules are missing, then iterate 14$ over the list of supplied modules, removing them from missing_mods. 15$ this iteration is done by scanning forward through the scopes tuple 16$ which is created by the input interface and contains a list of all the 17$ modules seen in the input. 18$ 19$ then we determine the set of all missing procedures, namely the set of 20$ all procedures exported by a missing module. at the same time, we 21$ determine which globals each of these missing procedures could access 22$ and which other procedures they might call. (the latter set is, of 23$ course, the set of all procedures imported by the module). 24$ 25$ finally we fill in the dummy bodies of the missing procedures. we 26$ iterate over missing_procs building one dummy body at a time. each 27$ time we process a procedure p we look for all equivalent procedures q 28$ and make them aliases for p. 29$ 30 repr 31 l: sparse set(elmt base_scopes); 32 r, w: sparse set(symbol); 33 i, e: sparse set(routine); 34 missing_mods: sparse set(elmt base_scopes); 35 missing_procs: sparse set(routine); 36 lb, m, s: elmt base_scopes; 37 p, q: routine; 38 rds, wrts: sparse mmap{routine} 39 sparse set(symbol); 40 clls: sparse mmap{routine} 41 sparse set(routine); 42 end repr; 43 44 45 $ recall that all_modules is the set of all modules referenced by 46 $ the directory, and is build by get_symtab. 47 $ scopes is a tuple containing all the scopes seen in the input. 48 49 missing_mods := { m in all_modules | m notin scopes }; 50 51 if sym_prog notin all_modules then 52 $ the main program was generated by satisfy_members: we have to 53 $ build a body for it. 54 missing_mods with:= sym_prog; 55 end if; 56$ 57$ find the libraries used, the variables read and written, and the pro- 58$ cedures imported and exported. 59$ 60 missing_procs := {}; rds := {}; wrts := {}; clls := {}; 61 62 (forall m in missing_mods) 63 64 $ (the value of a member is a quintuple, giving (in this order) 65 $ the libraries referenced, the globals read, the globals writ- 66 $ ten, the procedures imported, and the procedures exported.) 67 [ l, r, w, i, e ] := value(m); 68 69 $ the missing procedures for this module are the procedures of 70 $ its exports list. 71 missing_procs +:= e; 72 73 $ open the scope for this module and initialise the relevant 74 $ scope maps. 75 if m = sym_prog then 76 sc_type(m) := sc_prog; 77 78 $ we can (conceptually) think that the main program scope 79 $ sym_prog exports the main program procedure sym_main to 80 $ the system scope. 81 e with:= sym_main; missing_procs with:= sym_main; 82 83 else 84 sc_type(m) := sc_mod; 85 end if; 86 87 $ initialise the remaining relevant scope maps 88 sc_nprocs(m) := #e; 89 sc_stmt_ct(m) := 0; 90 sc_estmt_ct(m) := 0; 91 scopes with:= m; 92 all_modules with:= m; 93 94 $ the imports list of the missing procedure is really the union 95 $ of its imports list and of all procedures exported by all the 96 $ libraries referenced. 97 i := i +/[ value(lb)(5) : lb in l ]; 98 99 (forall p in e) 100 $ initialise the routine scope 101 sc_type(p) := sc_proc; 102 sc_nprocs(p) := 0; smfi 25 sc_stmt_ct(p) := 1; smfi 26 sc_estmt_ct(p) := 1; 105 scopes with:= p; 106 routs with:= p; 107 membof(p) := m; 108 109 $ all procedures exported by this module are assumed to 110 $ read, write and call all the globals read, the globals 111 $ written, and procedures imported by this module. 112 rds{p} := r; wrts{p} := w; clls{p} := i; 113 end forall; 114 end forall; 115$ 116$ finally fill in the dummy bodies of the missing procedures. 117$ 118 (while missing_procs /= {}) 119 120 p from missing_procs; 121 122 bld_body(p, rds{p}, wrts{p}, clls{p}); 123 124 if p = sym_main then continue; end if; 125 126 loop while 127 (exists q in missing_procs | q /= sym_main and 128 rvary(p) = rvary(q) and $ varying # of arguments 129 rptyps(p) = rptyps(q) and $ same parameter types 130 scope(p) = scope(q) and $ same scope 131 form(p) = form(q) and $ same form 132 rds{p} = rds{q} and $ same variables read 133 wrts{p} = wrts{q} and $ same variables written 134 clls{p} = clls{q} $ same procedures called 135 ) 136 do 137 missing_procs less:= q; 138 alias(q) := p; 139 140 scopes := [ m in scopes | m /= q ]; 141 routs less:= q; 142 membof lessf:= q; 143 144 sc_type lessf:= q; 145 sc_nprocs lessf:= q; 146 sc_stmt_ct lessf:= q; 147 sc_estmt_ct lessf:= q; 148 end loop; 149 end while; 150 151 152 end procedure satisfy_procs; 153 154 1 .=member blbd9c 2 3 4 procedure bld_body(p, rds, wrts, clls); 5$ 6$ this routine generates a dummy procedure body for the unsatisfied 7$ external procedure p. rds is the set of global variables read, wrts 8$ the set of global variables written, and clls the set of precedures 9$ called by p. 10$ 11$ each dummy procedure body looks as follows. we start by adding new 12$ symbol table entries for the formal parameters of p, and then proceed 13$ to generate, after the standard code sequence for a routine prelude, 14$ random uses and definitions of each of the formal parameters, depen- 15$ ding on how they are declared (ie. rd, rw, or wr). next we generate 16$ code to use each variable in rds, followed by code to use and define 17$ each variable in wrts, followed by a call to each procedure called by 18$ p, ie. the procedures mentioned in clls. finally, after we added a 19$ random branch back to start of p, we define p's return value, and end 20$ p with the standard routine postlude. note that we must include a 21$ random jump to the routine's stop block. 22$ 23 repr 24 p: routine; 25 rds, wrts: sparse set(symbol); 26 clls: sparse set(routine); 27 l: symbol; 28 fms: tuple(elmt forms); 29 tps: tuple(symbol); 30 j: integer 0..65536; 31 sym: symbol; smfi 27 q: routine; 32 end repr; 33 34 $ build an entry block for the procedure 35 bld_entry(p); 36 37 $ add 'l:' and save a pointer to l. we will build a conditional 38 $ branch back to l at the end of the routine. 39 l := add_label(p); 40 bld_label(p, l); 41 42 $ generate symbols for the parameters and build dummy uses and defi- 43 $ nitions. 44 45 fms := ft_elmt(form(p)); $ forms of parameters 46 tps := rptyps(p); $ types of parameters 47 48 (forall j in [ 1..#tps ]) 49 sym := add_sym(p); $ build symbol table entry 50 51 is_param(sym) := 1; 52 form(sym) := fms(j); 53 54 case name(tps(j)) of 55 56 ('rd'): $ read-only parameter: generate use 57 58 is_read(sym) := 1; 59 bld_use(p, sym); 60 61 ('wr'): $ write-only parameter: generate def 62 63 is_write(sym) := 1; 64 bld_def(p, sym); 65 66 ('rw'): $ read-write parameter 67 68 is_read(sym) := 1; 69 is_write(sym) := 1; 70 71 bld_use(p, sym); 72 bld_def(p, sym); 73 74 end case; 75 end forall; 76 77 $ add uses of reads variables 78 (forall sym in rds) bld_use(p, sym); end forall; 79 80 $ add definitions and uses of writes variables 81 (forall sym in wrts) bld_use(p, sym); bld_def(p, sym); end forall; 82 83 $ add calls to imported procedures smfi 28 (forall q in clls) bld_call(p, q); end forall; 85 86 $ build a conditional branch to the top of the routine 87 add_inst(last_block(p), q1_ifrand, l); 88 89 $ each procedure p has a global variable associated with it which is 90 $ used to return the value of function calls. the name of this 91 $ is given by rretn(p). build an assignment to it 92 bld_def(p, rretn(p)); 93 94 $ build exit and stop blocks 95 bld_exitstop(p); smfk 32 assert opcode(first_inst(first_block(p))) = q1_entry; smfk 33 stmtof(first_inst(first_block(p))) := 1; $ for initialised vars 96 97 98 end procedure bld_body; 99 100 1 .=member blde9d 2 3 4 procedure bld_entry(p); 5$ 6$ this routine builds the entry block for p. 7$ 8 repr 9 p: routine; 10 b: elmt blocks; 11 end repr; 12 13 14 b := add_block(om, p, true);$ add a block to the end of p 15 add_inst(b, q1_entry, p); $ add instruction to the end of b 16 rentry(p) := b; $ record that this is the entry block 17 18 19 end procedure bld_entry; 20 21 22 23 24 procedure bld_label(p, l); 25$ 26$ add 'sym:' to the code for procedure p. 27$ 28 repr 29 p: routine; 30 l: symbol; 31 b: elmt blocks; 32 i: elmt insts; 33 end repr; 34 35 36 b := last_block(p); $ current end of p 37 38 add_inst(b, q1_goto, l); $ add a branch to l 39 40 $ add a new block to define the label 'l' 41 b := add_block(b, p, true); 42 i := add_inst(b, q1_label, l); 43 value(l) := i; 44 45 46 end procedure bld_label; 47 48 49 50 51 procedure bld_use(p, sym); 52$ 53$ this routine adds the code for 54$ 55$ if random [ true, false ] then use(sym); end if; 56$ 57$ to the end of procedure 'p'. 'use' is a general use of sym. 58$ 59 repr 60 p: routine; 61 sym: symbol; 62 b: elmt blocks; 63 i: elmt insts; 64 l: symbol; 65 end repr; 66 67 68 b := last_block(p); $ current end of p 69 l := add_label(p); $ label for end 70 71 add_inst(b, q1_ifrand, l ); 72 add_inst(b, q1_use, sym); 73 add_inst(b, q1_goto, l ); 74 75 $ add a new block for the end and define l 76 b := add_block(b, p, true); 77 i := add_inst(b, q1_label, l); 78 value(l) := i; 79 80 81 end procedure bld_use; 82 83 84 85 86 procedure bld_def(p, sym); 87$ 88$ this routine adds the code for 89$ 90$ if random [ true, false ] then def(sym); end if; 91$ 92$ to the end of procedure 'p'. 'def' is a general definition of sym. 93$ 94 repr 95 p: routine; 96 sym: symbol; 97 b: elmt blocks; 98 i: elmt insts; 99 l: symbol; 100 end repr; 101 102 103 b := last_block(p); $ current end of p 104 l := add_label(p); $ label for end 105 106 add_inst(b, q1_ifrand, l ); 107 add_inst(b, q1_def, sym); 108 add_inst(b, q1_goto, l ); 109 110 $ add a new block for the end and define l 111 b := add_block(b, p, true); 112 i := add_inst(b, q1_label, l); 113 value(l) := i; 114 115 116 end procedure bld_def; 117 118 119 120 121 procedure bld_call(p, q); 122$ 123$ this routine adds the code for 124$ 125$ if random [ true, false ] then q(...); end if; 126$ 127$ to the end of procedure p. 128$ 129 repr 130 p, q: routine; 131 b: elmt blocks; 132 i: elmt insts; 133 l: symbol; 134 a: tuple(symbol); 135 x: symbol; 136 j: integer 0..65536; 137 end repr; 138 139 140 b := last_block(p); $ current end of p 141 l := add_label(p); $ label for end 142 143 add_inst(b, q1_ifrand, l); $ add a random branch to the end block 144 145$ next we must invent arguments for the procedure call. since we have 146$ already emitted uses and definitions for all the relevant global 147$ variables, we can assume that all the arguments are local. 148 149 $ generate new local variables for the arguments and define them 150 a := [ add_var(p) : j in [ 1..rnargs(q) ] ]; 151 (forall x in a) bld_def(p, x); end forall; smfi 29 b := last_block(p); $ current end of p after bld_def call 152 smfi 30 $ emit the argins 154 (forall x = a(j)) 155 if name(rptyps(q)(j)) /= 'wr' then 156 add_inst(b, q1_argin, x, q, add_int(p, j)); 157 else 158 add_inst(b, q1_argin, sym_om, q, add_int(p, j)); 159 end if; 160 end forall; 161 smfi 31 $ emit the call block smfi 32 bld_label(p, add_label(p)); $ branch to and define the call block smfi 33 add_inst(last_block(p), q1_call, q, add_int(p, rnargs(q))); smfi 34 bld_label(p, add_label(p)); $ branch to and define the successor smfi 35 b := last_block(p); $ current end of p after call block 164 165 $ emit argouts 166 (forall x = a(j)) smfl 2 if name(rptyps(q)(j)) = 'rd' then 168 add_inst(b, q1_free, x, q, add_int(p, j)); 169 else 170 add_inst(b, q1_argout, x, q, add_int(p, j)); 171 end if; 172 end forall; 173 smfi 36 bld_label(p, l); $ define the end label 180 181 182 end procedure bld_call; 183 184 185 186 187 procedure bld_exitstop(p); 188$ 189$ this routine builds the exit and stop blocks for p. 190$ 191 repr 192 p: routine; 193 b: elmt blocks; 194 i: elmt insts; 195 l1, l2: symbol; 196 end repr; 197 198 199 b := last_block(p); $ current end of p 200 l1 := add_label(p); $ label for exit block 201 l2 := add_label(p); $ label for stop block 202 203 add_inst(b, q1_ifrand, l2); $ add a random branch to the stop block 204 add_inst(b, q1_goto, l1); $ branch to the exit block 205 206 $ add a new block, the exit block 207 b := add_block(b, p, true); 208 i := add_inst(b, q1_label, l1); 209 value(l1) := i; 210 211 add_inst(b, q1_exit, p); 212 rexit(p) := b; 213 214 $ add a new block, the stop block 215 b := add_block(b, p, true); 216 i := add_inst(b, q1_label, l2); 217 value(l2) := i; 218 219 add_inst(b, q1_stop); 220 rstop(p) := b; 221 222 223 end procedure bld_exitstop; 224 225 226 end module setl_optimizer - optinit; 227 228 1 .=member end17a 2 3 4 module setl_optimizer - optend; 5 6 7 repr 8 clean_up: procedure; 9 end repr; 10 11 12 procedure opt_term; 13$ 14 const binary; 15 const tab = ' '; 16 const blank_tab = ' '; 17 const alphameric = 'abcdefghijklmnopqrstuvwxyz' 18 'abcdefghijklmnopqrstuvwxyz' 19 '0123456789' 20 '_'; 21 const headers = [ 'input interface: ', 22 'preliminary first pass: ', 23 'call graph & interval analysis:', 24 'initial live variable analysis:', 25 'available expression analysis: ', 26 'bfrom computation: ', smfb 40 'flow-constant loop detection: ', 27 'type analysis: ', 28 'data structure selection: ', 29 'conversion optimisation: ', 30 'copy optimisation: ', 31 'output interface: ', 32 'total: ' ]; 33 34 repr 35 l, s, v: string; 36 sc: elmt base_scopes; 37 todo: remote set(elmt base_scopes); 38 source_map: smap(integer) tuple(string); 39 message: tuple(string); 40 src_line, msg_line: string; 41 i, k: integer 0..65536; 42 j, stmt_lo, stmt_hi: integer; 43 elapsed_time: integer; 44 total_time: integer; 45 dd, hh, mm, ss, tt: string; 46 pp: string; 47 end repr; 48 49 50 smfk 34 title('cims.setl.' + prog_level); 52 53 s := getspp('interface=all/all'); 54 55 if #s = 0 then 56 todo := {}; 57 elseif s = 'all' then 58$$-- all forms are currently allocated in the system scope, so that 59$$-- all optimiser-introduced bases appear there. hence we must 60$$-- include the system scope here. 61$$-- todo := { sc : sc in scopes | sc_type(sc) /= sc_sys }; 62 todo := { sc : sc in scopes }; 63 else 64 loop 65 init todo := {}; 66 break(s, alphameric); 67 doing v := span(s, alphameric); 68 break(s, alphameric); 69 while v /= om 70 do 71 todo +:= { sc in scopes | name(sc) = v }; 72 end loop; 73 end if; 74 75 print_summary(todo); 76 77 open(ssm_file, binary); getb(ssm_file, source_map); close(ssm_file); 78 79 s := getspp('summary=all/all'); 80 81 if #s = 0 then 82 todo := {}; 83 elseif s = 'all' then 84 todo := { sc : sc in scopes | sc_type(sc) /= sc_sys }; 85 else 86 loop 87 init todo := {}; 88 break(s, alphameric); 89 doing v := span(s, alphameric); 90 break(s, alphameric); 91 while v /= om 92 do 93 todo +:= { sc in scopes | name(sc) = v }; 94 end loop; 95 end if; 96 97 (forall sc = scopes(i) | is_seen(sc) /= om and sc in todo) 98 99 stmt_lo := sc_stmt_ct(sc); smfi 37 if exists k in [ i+1..#scopes ] | smfi 38 scopes(k) /= om and is_seen(scopes(k)) /= om then smfi 39 stmt_hi := sc_stmt_ct(scopes(k)) - 1; smfi 40 else smfi 41 stmt_hi := #source_map; smfi 42 end if; 101 102 $ if this directory was generated by the optimiser, ignore it. 103 if sc_type(sc) = sc_dir and stmt_lo = 0 then continue; end if; 104 105 if at_flag then 106 src_line := '' +/[ source_map(stmt_lo)(j) : 107 j in [ 1..#source_map(stmt_lo) ] ]; 108 span(src_line, blank_tab); rspan(src_line, blank_tab); 109 title(src_line); 110 end if; 111 112 (forall j in [ stmt_lo..stmt_hi ]) 113 (forall src_line in source_map(j)) 114 print(lpad(str (j-stmt_lo+1), 6) + tab + src_line); 115 end forall; smfd 2 (forall l in 'fewis' | l in getspp('all=fewi/fewis')) 117 (forall message in messages{j}{l}) 118 if #message = 0 then continue; end if; 119 print('opt-' + l + '>' + tab + message(1)); 120 (forall k in [ 2..#message ]) smfk 35 print(tab + ' ' + message(k)); 122 end forall; 123 end forall; 124 end forall; 125 end forall; 126 end forall; 127 128 $ reverse the temporary changes to the q1 code 129 clean_up; 130 131 if 's' in dump_string then dmp(om, 'symtab'); end if; 132 if 'f' in dump_string then dmp(om, 'formtab'); end if; 133 if 'c' in dump_string then dmp(om, 'codetab'); end if; 134 135 $ write the q1 file for the code generator 136 open(q1_file, binary); write_q1; close(q1_file); 137 statistics with:= time; smfk 36 smfk 37 if not lcs_flag then return; end if; 138 139 title('cims.setl.' + prog_level + ' - execution statistics'); 140 141 total_time := statistics(#statistics) - statistics(1); 142 143 (forall i in [ 1..#statistics ]) 144 145 if i = #statistics then 146 elapsed_time := total_time; 147 print; 148 else 149 elapsed_time := statistics(i+1) - statistics(i); 150 end if; 151 152 tt := str (elapsed_time mod 1000); 153 ss := str (elapsed_time div 1000 mod 60); 154 mm := str (elapsed_time div 60000 mod 60); 155 hh := str (elapsed_time div 3600000 mod 24); 156 dd := str (elapsed_time div 86400000 ); 157 if #tt = 1 then tt := '00' + tt; end if; 158 if #tt = 2 then tt := '0' + tt; end if; 159 if #ss = 1 then ss := '0' + ss; end if; 160 if #mm = 1 then mm := '0' + mm; end if; 161 if #hh = 1 then hh := '0' + hh; end if; 162 if #dd = 1 then dd := ' ' + dd; end if; 163 164 pp := str fix(1000.0*(float elapsed_time/float total_time)+0.5); 165 166 print( 167 headers(i), 168 dd, hh + ':' + mm + ':' + ss + '.' + tt, 169 lpad(str(elapsed_time div 1000), 11) + '.' + tt, 170 lpad(pp(1..#pp-1), 6) + '.' + pp(#pp) + '%' 171 ); 172 end forall; 173 174 175 end procedure opt_term; 176 177 178 179 180 procedure clean_up; 181$ 182$ this routine scans the q1 code and restores some of the changes that 183$ have been made only for the sake of the optimizer itself and which are 184$ not acceptable by the code generator. specifically, the following is 185$ done in this routine: 186$ 187$ 1. change back q1_arbb + q1_lessb to q1_fromb, and similarly for 188$ q1_frome. (q1_from is currently not restored, as it is actually 189$ equivalent to the arb + less sequence.) 190$ 191$ 2. eliminate the last argument of sinister assignments, added in the 192$ initialization phase, and which is equal to the first argument. 193$ note that in principle the data type or representation of the last 194$ argument may differ from that of the first (output) argument; 195$ however, the type finder and automatic data-structure selection 196$ choice phases should function in such a manner as to guarantee that 197$ no conversion will be required between these two arguments. 199$ 200$ 3. restore the argin and argout instructions. this amounts to 201$ (a) if a system routine has been called, change the temporary 202$ sargin and sargout opcodes back to argin and argout; 203$ (b) otherwise, remove the extra argument added to these 204$ instructions in the initialization phase. 205$ 206$ 4. the handling of external procedures in the initialization phase is 207$ ugly enough to merit searching for a better way. for this reason, 208$ this routine assumes that there are no such external procedures in 209$ the code. this should of course be later modified. 211$ 212$ 5. temporaries are treated in the code generator in a different way 213$ than in the optimizer: the code generator assumes that a 214$ temporary is dead after its first use, so that its storage and 215$ symbol table entry are freed and can be used by other temporaries. 216$ the optimizer, though, assigns a unique temporary to each 217$ computation, so that any computation occurring more than once in 218$ the code should be assigned to an internal variable (i.e. a symbol 219$ whose is_temp bit is off) rather than to a temporary (is_temp is 220$ set) 221$ 222 repr 223 noccs_temp: smap(symbol) integer; 224 r: routine; 225 b: elmt blocks; 226 pi, i, ni: elmt insts; 227 opc: elmt base_opcodes; 228 junk: *; 229 a: symbol; 230 t, t1, t2: symbol; 231 avail_temps: remote mmap{elmt base_scopes} 232 sparse set(symbol); 233 j: integer 0..65536; 234 end repr; 235 236 (forall r in routs) 237 238 avail_temps := {}; 239 240 (for_block(b, r)) 241 pi := om; 242 (for_inst(i, b)) 243 opc := opcode(i); 244 case opc of 245 246 (q1_argin): 247 junk fromb args(i); 248 249 (q1_argout): 250 junk frome args(i); 251 252 (q1_sargin): 253 opcode(i) := q1_argin; 254 255 (q1_sargout): 256 opcode(i) := q1_argout; 257 258 (q1_sof, q1_sofa, q1_ssubst, q1_send): 259 junk frome args(i); 260 261 (q1_next): 262 junk frome args(i); 263 264 (q1_arbb): 265 ni := next_inst(i); 266 $ assert ni /= om; 267 $ assert opcode(ni) = q1_lessb; 268 $ assert arg1(i) = arg3(ni); 269 $ assert arg2(i) = arg1(ni); 270 $ assert arg1(ni) = arg2(ni); 271 opcode(i) := q1_fromb; 272 copy_flag(i) := copy_flag(ni); 273 274 (q1_arbe): 275 ni := next_inst(i); 276 $ assert ni /= om; 277 $ assert opcode(ni) = q1_lesse; 278 $ assert arg1(i) = arg3(ni); 279 $ assert arg2(i) = arg1(ni); 280 $ assert arg1(ni) = arg2(ni); 281 opcode(i) := q1_frome; 282 copy_flag(i) := copy_flag(ni); 283 284 (q1_lessb, q1_lesse, q1_def, q1_noop): 285 del_inst(i, pi, b); 286 continue; 287 288 (q1_push): 289 args(i) with:= arg1(push_former(i)); 290 291 end case; 292 293 if opc in ops_ovar and 294 is_temp(t := args(i)(1)) /= om then 295 296 if (t1 := alias(t)) /= om 297 and is_seen(t) = om 298 and is_seen(t1) /= om then 299 alias(t) := alias(t1) ? t1; 300 is_store(t) := om; 301 is_seen(t) := 1; 302 303 elseif is_seen(t) = om then 304 is_seen(t) := 1; 305 if t1 /= om then is_seen(t1) := 1; end if; 306 307 if avail_temps{scope(t)} /= {} then 308 t2 from avail_temps{scope(t)}; 309 310 alias(t) := t2; 311 is_store(t) := om; 312 313 if t1 /= om then 314 alias(t1) := t2; 315 is_store(t1) := om; 316 end if; 317 end if; 318 end if; 319 end if; 320 321 (forall j in [ first_ivar(opc)..#args(i) ] | 322 is_temp(t := args(i)(j)) /= om and 323 opc /= q1_push ) 324 avail_temps{scope(t)} with:= alias(t) ? t; 325 end forall; 326 pi := i; 327 end; 328 end; 329 end forall; 330 331 end procedure clean_up; 332 333 334 end module setl_optimizer - optend; 335 336 1 .=member inta10 2 3 4 module setl_optimizer - interval_analysis; 5$ 6$ this module contains the interval analysis algorithm described in 7$ section 3 of the technical report. 8$ 9$ flow graph analysis produces two maps which serve as input to the 10$ interval analysis: 11$ 12$ 1. cessor: the successor map for basic blocks 13$ 14$ 2. pred: the predecessor map for basic blocks 15$ 16$ interval analysis produces five maps: 17$ 18$ 1. intof: a map from each node to the interval immediately 19$ containing it. 20$ 21$ 2. ints: maps each routine to a tuple of all its intervals in 22$ reverse preorder. note that iterating over ints(rout) 23$ is equivalent to iterating from innermost to outermost 24$ interval. 25$ 26$ 3. int_nodes: a map sending each interval into a tuple containing the 27$ nodes of the interval in reverse postorder. iterating 28$ over int_nodes(i) is equivalent to iterating forward 29$ over the nodes in i. 30$ 31$ 4. proper_ints: the set of proper (reducible) intervals. 32$ 33$ 5. vedges: the set of all virtual edges added to the flow graph 34$ during interval analysis. a virtual edge is an edge 35$ having the form (i, v), where i is an interval and v 36$ is a node outside i which is a successor of some node 37$ in i. 38$ 39$ all these variables are assumed to be globally accessible in the 40$ setl optimizer. additional global variables that are accessed in 41$ this module are: 42$ 43$ routs: set of all routines in the program being analyzed. 44$ 45$ rentry: maps each routine to its entry block. 46$ 47$ rexit: maps each routine to its exit (return) block. 48$ 49$ rstop: maps each routine to its stop block, if it exists. 50$ 51$ routof: maps each basic block to the routine containing it. 52$ 53$ the module contains three principal routines: 54$ 55$ 1. find_intervals: iterates over 'routs' calling other routines 56$ 57$ 2. get_graph: builds a flow graph for a routine. 58$ 59$ 3. find_ints: finds the intervals of a flow graph. 60$ 61$ the following variables are used globally during interval analysis: 62$ 63 var 64 nodeno, $ preorder node numbering 65 postno, $ postorder numbering 66 ndescs, $ number of descendants of each node 67 nodes, $ tuple of nodes in preorder 68 postnodes, $ tuple of nodes in postorder 69 npre, $ current pos in preorder numbering 70 npost, $ current pos in postorder numbering 71 seen, $ nodes already in spanning tree 72 impropers; $ set of 'heads' of multiple entry loops 73 74 init 75 impropers := {}; 76 77 78 repr 79 nodeno: smap(elmt blocks) integer; 80 postno: smap(elmt blocks) integer; 81 ndescs: smap(elmt blocks) integer; 82 nodes: tuple(elmt blocks); 83 postnodes: tuple(elmt blocks); 84 npre: integer; 85 npost: integer; 86 seen: sparse set(elmt blocks); 87 impropers: sparse set(elmt blocks); 88 89 .intof_lim: operator(elmt blocks) elmt blocks; 90 find_graph: procedure(routine); 91 find_ints: procedure(routine); 92 update: procedure( 93 elmt blocks, 94 elmt blocks, 95 sparse set(elmt blocks) 96 ); 97 dfst: procedure(elmt blocks); 98 get_targ: procedure(elmt blocks) elmt blocks; 99 end repr; 100 101 1 .=member fns10a 2 3 4 procedure find_intervals; 5$ 6$ this routine iterates over all the routines in a setl program 7$ finding the interval graph for each routine. 8$ 9 repr 10 r: routine; 12 end repr; 13 smfc 51 14 title('cims.setl.' + prog_level + ' - interval analysis'); 15 printa(term_file, ' - interval analysis'); 16 17 20 (forall r in routs) 21 find_graph(r); 22 find_ints(r); 23 end forall; 24 25 26 $ delete the static variables global to the module 27 nodeno := om; postno := om; ndescs := om; 28 nodes := om; postnodes := om; seen := om; 29 impropers := om; 30 31 cut_blocks := om; 32 33 $ print the interval graph if requested 34 if 'i' in dump_string then 35 (forall r in routs) dmp(r, 'igraph'); end forall; 36 end if; 37 38 statistics with:= time; $ save time for final statistics 43 44 45 end procedure find_intervals; 46 47 48 49 50 procedure find_graph(r); 51$ 52$ this routine builds the original control flow graph for a routine r. 53$ 54 repr 55 r: routine; 56 work: sparse set(elmt blocks); 57 blks: sparse set(elmt blocks); 58 b: elmt blocks; 59 i: elmt insts; 60 opc: elmt base_opcodes; 61 labels: sparse set(symbol); 62 lab: symbol; 63 new_blks: sparse set(elmt blocks); 64 b1: elmt blocks; 65 bprev: elmt blocks; 66 lprev: symbol; 67 end repr; 68$ 69$ we examine blocks starting with the routine's entry block. this way 70$ we find any unreachable blocks. 71$ 72 work := { rentry(r) }; 73 blks := {}; 74 75 (while work /= {}) 76 b from work; 77 blks with:= b; 78 79 $ iterate over b, looking for branch instrctions 80 (for_inst(i, b)) 81 opc := opcode(i); 82 83 if opc = q1_case then 84 $ arg1 is a map from case values to labels 85 labels := range value(arg1(i)); 86 new_blks := { blockof(value(lab)): lab in labels }; 87 88 elseif opc in ops_goto then 89 lab := args(i)(# args(i)); 90 new_blks := { blockof(value(lab)) }; 91 92 else 93 continue; 94 end if; 95 96 (forall b1 in new_blks) 97 cessor{b} with:= b1; 98 pred{b1} with:= b; 99 end forall; 100 101 work +:= (new_blks - blks); 102 end; 103 end while; 104$ 105$ now check that all blocks in r are reachable from the entry block. 106$ 107 bprev := om; 108 (for_block(b, r)) 109 if b notin blks then 110 if b /= rstop(r) and b /= rexit(r) then 111 if b notin cut_blocks then 112 ermsg(str b + ' is unreachable from entry of ' + 113 name(r)); 114 end if; 115 del_block(b, bprev, r); 116 b := bprev; 117 end if; 118 end if; 119 bprev := b; 120 end; 121 122 $ delete the labels defining the blocks which were deleted in the 123 $ preceding loop. 124 lprev := om; 125 (for_sym(lab, r)) 126 if lab in dead_labs then 127 del_sym(lab, lprev, r); 128 lab := lprev; 129 end if; 130 lprev := lab; 131 end; 132 133 134 end procedure find_graph; 135 136 1 .=member fnt10b 2 3 4 procedure find_ints(r); 5$ 6$ this routine calculates the intervals of an intraprocedural flow 7$ graph corresponding to a given routine r. 8$ 9$ find_ints is called once to process each procedure 'r'. 10$ it produces five maps: 11$ 12$ 1. intof: a map from each node to its interval. 13$ 14$ 2. ints: a map sending each routine 'r' into a tuple 15$ containing the intervals of 'r' in reverse preorder. 16$ note that iterating backward (forward) through 17$ ints(r) is equivalent to iterating from outermost 18$ to innermost(innermost to outermost) interval. 19$ 20$ the outermost interval is not really an interval 21$ at all. instead it contains all nodes not contained 22$ in other intervals. it is acyclic in the reducible 23$ case. 24$ 25$ 3. int_nodes: a map sending each interval into a tuple containing 26$ the nodes of the interval in reverse postorder. 27$ iterating over int_nodes(i) is equivalent to iterating 28$ forward over the nodes in i. 29$ 30$ 4. vedges: the set of all edges which are part of some higher 31$ order graph. 32$ 33$ 5. proper_ints: a set of all proper (reducible) intervals. 34$ 35 repr 36 r: routine; 37 backinv: mmap(elmt blocks) elmt blocks; 38 x, y, z: elmt blocks; 39 root, hd, tbx: elmt blocks; 40 targback: tuple(elmt blocks); 41 reachunder: sparse set(elmt blocks); 42 newreachunder: sparse set(elmt blocks); 43 i: integer; 44 end repr; 45$ 46$ step 1: calculate the following objects: 47$ 48$ 1. nodeno: maps each node into its preorder index 49$ 2. postno: maps each node into its postorder index 50$ 3. ndescs: maps each node into the number of its descendants 51$ 4. nodes: tuple of nodes in preorder. 52$ 5. postnodes: tuple of nodes in postorder 53$ 6. backinv: the set of all (y, x) such that (x, y) is a back edge 54$ 7. targback: a tuple of targets of back edges in preorder. 55$ 56$ (1) - (5) are built by an auxiliary depth-first searching routine 57$ 'dfst'. when we build the node indices we use only even numbers. 58$ this leaves the odd numbers for target blocks (i.e. interval 59$ preheaders). initially only the even elements of nodes and postnodes 60$ are filled in. 61$ 62 $ the following macro tests for tree descendancy 63 macro is_desc(x, y); 64 (nodeno(y) <= nodeno(x) and nodeno(x) <= nodeno(y)+ndescs(y)) 65 endm; 66 67 $ initialize the globals for the depth-first spanning tree routine 68 nodeno := {}; postno := {}; ndescs := {}; 69 nodes := []; postnodes := []; seen := {}; 70 npre := 0; npost := 0; 71 72 $ build the depth-first spanning tree rooted at the entry block of r 73 dfst(rentry(r)); 74 75 $ construct the set backinv of all reverse back edges 76 backinv := {}; 77 (for_block(x, r))(forall y in pred{x} | is_desc(y, x)) 78 backinv with:= [ x, y ]; 79 end forall; end; 80 81 $ construct the tuple targback of all back edge target nodes, 82 $ arranged in reverse preorder. 83 targback := [ nodes(i) : i in [ #nodes, #nodes-1..1 ] | 84 nodes(i) /= om and nodes(i) in domain backinv ]; 85$ 86$ step 2 87$ 88$ at this point 'targback' contains all potential interval heads in 89$ reverse preorder. we iterate over x in targback doing three things: 90$ 91$ 1. build the set 'impropers' of such nodes x which are heads of 92$ multiple-entry loops, and thus are 'sources of irreducibility'. 93$ 94$ 2. for each x find the set 'reachunder' of nodes (in the reduced 95$ graph in which each already processed proper or improper 96$ interval has been logically 'squashed', i.e. identified with 97$ a single node - its target block) which reach x along a path 98$ not passing through x whose final edge is a back edge. if any 99$ node which is not a descendant of x belongs to 'reachunder', 100$ then x is a head of a multiple-entry loop, and we add x to 101$ 'impropers'. otherwise x is a head of a single-entry loop, 102$ and thus is an interval head in our sense; if 103$ reachunder * impropers = {}, then that interval is a proper 104$ interval, and we add it to 'proper_ints'; otherwise it is an 105$ improper interval. 106$ 107$ 3. if x is an interval head then: 108$ 109$ a. create a new target block 'tbx'. 110$ b. add tbx to 'ints(r)' and set int_nodes(tbx) to []. 111$ c. for all y in reachunder, set intof(y) := tbx 112$ d. update the flow graph to show the insertion of tbx. 113$ 114 root := rentry(r); 115 116 ints(r) := []; 117 118 (forall x in targback) 119 reachunder := {x}; 120 newreachunder := { .intof_lim y : y in backinv{x} } - {x}; 121 $ intof .lim y is the largest interval constructed so far 122 $ which contains y (see below for details). 123 124 (while newreachunder /= {}) 125 y from newreachunder; 126 reachunder with:= y; 127 128 if not is_desc(y, x) then $ a multiple-entry loop 129 impropers with:= x; 130 quit while; 131 else 132 newreachunder +:= 133 ({ .intof_lim z : z in pred{y} } - reachunder); 134 end if; 135 end while; 136 137 if x in impropers then continue forall; end if; 138$ 139$ here x is an interval head. 140$ 141 tbx := get_targ(x); 142 int_nodes(tbx) := []; 143 144 ints(r) with:= tbx; 145 $ check if tbx is proper 146 if reachunder * impropers = {} then 147 proper_ints with:= tbx; 148 end if; 149 150 $ map each node in reachunder to its containing interval tbx 151 (forall y in reachunder) intof(y) := tbx; end forall; 152 153 $ update the flow graph to account for the insertion of tbx 154 $ into it. this involves the following actions: 155 $ 1. add an edge [ tbx, x ] to the graph. 156 $ 2. replace all edges entering the interval through 'x' by 157 $ edges entering tbx, and change the corresponding branch 158 $ instructions in the program code. 159 $ 3. for each edge [u, v] leaving the interval whose head 160 $ is x, add a 'virtual' edge [tbx,v] to the graph. this 161 $ edge is added to 'vedges'. 162 163 update(x, tbx, reachunder); 164 end forall; 165$ 166$ build the outermost 'interval', identified by the entry node 'root'. 167$ 168 ints(r) with:= root; 169 int_nodes(root) := []; 170 proper_ints with:= root; $ root will be removed from this set if 171 $ actually improper 172$ 173$ iterate over the nodes in reverse postorder, adding each node to 174$ int_nodes. if a node has its interval head undefined put it in the 175$ outermost interval. 176$ 177 (forall i in [ #postnodes, #postnodes-1..1 ]) 178 x := postnodes(i); 179 if x = om then continue forall i; end if; 180 181 hd := intof(x); 182 if hd = om then 183 hd := intof(x) := root; 184 if x in impropers then 185 proper_ints less:= root; 186 end if; 187 end if; 188 189 int_nodes(hd) with:= x; 190 end forall; 191 192 193 end procedure find_ints; 194 195 1 .=member upd10c 2 3 4 procedure update(x, tbx, inodes); 5$ 6$ this routine updates the flow graph to show the insertion of 7$ the target block 'tbx'. its arguments are: 8$ 9$ x: the interval head 10$ tbx: the target block 11$ inodes: the nodes in the interval 12$ 13 repr 14 x, tbx: elmt blocks; 15 inodes: sparse set(elmt blocks); 16 i: elmt insts; 17 y: elmt blocks; 18 l1, l2: symbol; 19 opc: elmt base_opcodes; 20 a1: symbol; 21 a, b: symbol; 22 u: elmt blocks; 23 end repr; 24$ 25$ we begin by adding a branch from tbx to x, and adding the corres- 26$ ponding edge to the flow graph. 27$ 28 i := add_inst(tbx, q1_goto, blk_label(x)); 29 stmtof(i) := stmtof(first_inst(x)); 30 31 cessor{tbx} with:= x; 32 pred{x} with:= tbx; 33$ 34$ next we iterate over all the predecessors of x which are not in 35$ the interval modifying the cessor and pred maps as we go. 36$ 37 (forall y in pred{x} | y notin inodes and y /= tbx) 38 39 $ update the branch instructions in y 40 l1 := blk_label(x); 41 l2 := blk_label(tbx); 42 43 (for_inst(i, y)) 44 opc := opcode(i); 45 46 if opc in ops_goto then 47 if args(i)(#args(i)) = l1 then 48 args(i)(#args(i)) := l2; 49 end if; 50 if opc = q1_case then 51 a1 := arg1(i); 52 53 (forall [ a, b ] in value(a1) | b = l1) 54 value(a1)(a) := l2; 55 end forall; 56 end if; 57 end if; 58 end; 59 60 $ update the flow graph 61 cessor{y} less:= x; 62 cessor{y} with:= tbx; 63 64 pred{x} less:= y; 65 pred{tbx} with:= y; 66 end forall; 67$ 68$ find all edges which leave the interval and add a virtual edge 69$ from tbx for each such edge. 70$ 71 (forall u in inodes, y in cessor{u} | 72 y notin inodes and intof(y) /= u) 73 cessor{tbx} with:= y; 74 pred{y} with:= tbx; 75 vedges{tbx} with:= y; 76 end forall; 77 78 79 end procedure update; 80 81 1 .=member dft10d 2 3 4 procedure dfst(x); 5$ 6$ this routine builds the depth-first spanning tree rooted at the node x 7$ 8 repr 9 x: elmt blocks; 10 y: elmt blocks; 11 end repr; 12 13 14 nodeno(x) := (npre +:= 2); $ note the use of even indices only 15 ndescs(x) := 0; 16 17 nodes(npre) := x; 18 seen with:= x; 19 20 (forall y in cessor{x} | y notin seen) 21 dfst(y); 22 ndescs(x) +:= (ndescs(y) + 2); 23$ each node is counted as two descendants, to match the usage of 24$ only even indices in nodeno and postno. 25 end forall; 26 27 postno(x) := (npost +:= 2); 28 postnodes(npost) := x; 29 30 end procedure dfst; 31 32 33 34 35 operator .intof_lim(x); 36$ 37$ this operator is an adaption of the general .lim(f,x) operator 38$ restricted by the assumption that the only left operand it is used 39$ with is intof. 40$ 41$ the general operator finds a value 'y' such that y = f(f(f..f(x)..))) 42$ and f(y) = om. 43$ 44$ note that unlike tarjan's original approach we omit path 45$ compression, tree balancing, etc. for the sake of simplicity, 46$ though these could easily be added. 47$ 48$ 49$ the data structure choices used here derive from the use 50$ of .lim in this module. the routine itself, however, is 51$ of a more general nature. 52$ 53 repr 54 x: elmt blocks; 55 y: elmt blocks; 56 end repr; 57 58 59 y := x; 60 (while intof(y) /= om) y := intof(y); end while; 61 62 return y; 63 64 end operator .intof_lim; 65 66 67 68 69 procedure get_targ(x); 70$ 71$ this routine adds a target block before x. we give the target block 72$ a postno of postno(x) + 1. 73$ 74 repr 75 x: elmt blocks; 76 p: routine; 77 targ: elmt blocks; 78 l: symbol; 79 i: elmt insts; 80 end repr; 81 82 83 p := routof(x); 84 targ := add_block(x, p, false); 85 l := add_label(p); 86 i := add_inst(targ, q1_label, l); 87 88 stmtof(i) := stmtof(first_inst(x)); 89 value(l) := i; $ the value of a label is its instruction 90 91 nodeno(targ) := nodeno(x) - 1; 92 nodes(nodeno(targ)) := targ; 93 94 postno(targ) := postno(x) + 1; 95 postnodes(postno(targ)) := targ; 96 97 return targ; 98 99 100 end procedure get_targ; 101 102 103 end module setl_optimizer - interval_analysis; 104 105 1 .=member avex11 2 3 4 module setl_optimizer - availexp_analysis; 5$ 6$ this module performs the available expressions and code motion 7$ analyses, using the data flow solver package. this can be accom- 8$ plished by performing a single data flow analysis, namely the 9$ available expression analysis in the code motion mode (see module 10$ dataflow_solver for more detail). 11$ 12$ in this mode, in addition to the local flow maps of the available 13$ expression analysis, we also have to supply another kind of local 14$ information, which is a map sending each basic block to the set of 15$ all expressions exposed within that block, ie. expressions which are 16$ computed within that block with no prior computation or kill. this 17$ map is denoted as 'exposed', and can be computed in a manner rather 18$ similar to the computation of the local expression availability maps 19$ (see below for more comments on the required initialization phase). 20$ 21$ in addition to the availability map, the availability analysis will 22$ also compute a map 'insert', which maps each interval i into the set 23$ of all expressions movable to the target block of i but not redundant 24$ there. the actual insertion of these computations into the end of 25$ the target block of i, as well as the actual elimination of redundant 26$ computations, has to be done in an auxiliary routine called by the 27$ code motion driver routine. 28$ 29$ standard available expressions analysis is a 'forward-meet' analysis, 30$ whose semi-lattice l is the power set of (ie. all bit vectors over) 31$ the set of all (relevant) program expressions. in this analysis we 32$ associate with each flow edge (m, n) a data propagation map f(m, n), 33$ so that for each x in l 34$ 35$ f(m,n)(x) = x * nokill(m,n) + gen(m,n) 36$ 37$ where 38$ 39$ nokill(m,n) = set of all expressions t such that any kill of t along 40$ any path through m to n is followed by a recomputation 41$ of t. 42$ 43$ gen(m,n) = set of all expressions t such that t is computed with 44$ no subsequent kill along each path through m to n. 45$ 46$ we briefly describe the way in which code motion is accomplished by 47$ our algorithm. 48$ 49$ suppose that i is an interval such that for each node nd of i we have 50$ already computed the following two objects: 51$ 52$ aux_f(nd) = a flow map representing the flow from the entry to the 53$ loop of i, through that loop, to the entry to nd; let 54$ aux_f(nd) be represented by the sets aux_nokill(nd) and 55$ aux_gen(nd), in complete analogy to the representation 56$ of the f maps themselves. 57$ 58$ exposed(nd) = set of all expressions t for which there exists a 59$ computation of t in nd which becomes redundant iff t is 60$ available at the entry to nd. 61$ 62$ then 63$ (aux_nokill(nd) - aux_gen(nd)) * exposed(nd) 64$ 65$ yields precisely those expressions t with the property that nd 66$ contains a computation of t which becomes redundant iff t is 67$ available just before entering the loop of i. hence, the union of 68$ the above sets over all nodes nd in i yields the set of all 69$ expressions movable out of the loop of i, if we use the criterion 70$ that it is profitable to move a computation of an expression t out of 71$ a loop i iff at least one computation of t within i is made redundant 72$ by that motion. note that we do not impose any safety criteria on 73$ code motion, as we assume that code motion will be performed only in 74$ association with the use of a special 'run-time error mode' of the 75$ setl system in which invalid computations do not cause program abort. 76$ 77$ the above description gives the general outline of our approach; for 78$ more technical details see the dataflow_solver module. 79$ 80$ 81$ we assume that the following global objects are available: 82$ 83$ globalexps - set of all expressions which depend on at least one 84$ global variable, and so must participate in the inter- 85$ procedural analysis. 86$ 87$ localexps - maps each procedure p to the set of all expressions 88$ strictly local to p, ie. depending only on local 89$ variables of p. 90$ 91$ dependon - maps each user-defined variable to the set of all 92$ expressions which depend (explicitly or implicitly) on 93$ that variable. 94$ 95$ ops_exps - constant set of all opcodes of instructions which 96$ compute expressions with a well defined value and with 97$ no side effects. 98$ 99$ ops_modify - constant set of all opcodes of instructions which can 100$ modify a program variables. 101$ 102 1 .=member csx11a 2 3 macro df_base; df_base_syms endm; 4 macro .comp; .comp_syms endm; 5 macro interproc_fwd_analysis; interproc_fwd_analysis_syms endm; 6 macro intraproc_fwd_analysis; intraproc_fwd_analysis_syms endm; 7 macro fom; fom_syms endm; 8 macro xom; xom_syms endm; 9 10 var 11 avail, $ maps each block to set of expressions 12 $ available at its entry. 13 insert, $ maps each interval to the set of 14 $ expressions to be inserted at the 15 $ end of its target block. 16 ppi, $ instruction for code motion 17 already_there; $ set of moved expressions 18 19 repr 20 mode df_elmt: df_elmt_syms; 21 mode df_map: df_map_syms; 22 23 avail: sparse smap(elmt blocks) 24 remote set(expression); 25 insert: sparse mmap{elmt blocks} 26 remote set(expression); 27 ppi: elmt insts; 28 already_there: remote set(expression); 29 30 interproc_csx: procedure; 31 intraproc_csx: procedure(routine); 32 move_eliminate: procedure; 33 insert_exp: procedure(expression); 34 csx_blockmaps: procedure(routine, df_elmt) 35 tuple( 36 remote smap(df_edge) df_map, 37 remote mmap{df_node} df_elmt 38 ); 39 .meet: operator(df_map, df_map) df_map; 40 end repr; 41 42 43 procedure csx; 44$ 45$ this is the master procedure for performing the optimizations 46$ described above. it consists of the following phases: 47$ 48$ 1. interprocedural analysis of global variables and expressions. 49$ this phase will move and eliminate expressions involving global 50$ variables and formal parameters of procedures. 51$ 52$ 2. intraprocedural analysis of each procedure, in which strictly 53$ local expressions are moved and eliminated. 54$ 55 repr 56 p: routine; 57 usym1, usym2: symbol; 59 end repr; 60 61 title('cims.setl.' + prog_level + ' - available expressions'); 62 printa(term_file, ' - available expression analysis'); 65 66 $ initialize the static variables global to this module 67 avail := {}; insert := {}; 68 69 $ define the undefined lattice element and flow map 70 xom := { usym1 := newat }; 71 fom := [ { usym1 := newat }, { usym2 := newat } ]; 72 73 interproc_csx; $ interprocedural analysis 74 75 (forall p in routs) $ intraprocedural analysis 76 intraproc_csx(p); 77 end forall; 78 79 $ perform actual motion and elimination of expressions 80 move_eliminate; 81 82 $ delete the static variables global to this module 83 avail := om; insert := om; 84 ppi := om; already_there := om; 85 86 $ delete the expression maps which are not needed anymore 87 globalexps := om; localexps := om; allexps := om; 88 opcexp := om; argsexp := om; dependon := om; 89 90 statistics with:= time; $ save time for final statistics 91 98 99 end procedure csx; 100 101 1 .=member erx11b 2 3 4 procedure interproc_csx; 5$ 6$ this routine performs the optimizations described above interproce- 7$ durally for expressions involving global variables and formal para- 8$ meters. it uses the relevant routines in the general data flow 9$ solver package. it consists of the following phases: 10$ 11$ 1. compute the local maps associated with basic blocks (other than 12$ call blocks) for the available expressions analysis, and also 13$ compute 'exposed' information, using the global objects 14$ 'globalexps' and 'dependon' mentioned above. 15$ 16$ it is probably best to define the 'exposed' map in a manner which 17$ puts exposed{c} := {} for each call block c. this will make 18$ code motion strictly intraprocedural, which is probably the right 19$ choice. 20$ 21$ 2. perform available expressions analysis in 'code motion mode', 22$ using 'exposed' information also. this analysis returns two 23$ objects: 'avail', mapping each basic block to the set of all 24$ expressions known to be available at its entry, and 'insert', 25$ mapping each interval to the set of all expressions which should 26$ be inserted into the end of its target block. together, these 27$ steps accomplish code motion. 28$ 29$ 3. a final pass in which expressions are actually moved and 30$ eliminated. it iterates through each basic block b; if b is not 31$ a target block of an interval, start with the set avail(b) of 32$ expressions found to be available at its start; update this set as 33$ kills and generations within the block are encountered, and use it 34$ to eliminate any computation of an expression which is known to be 35$ available just before this computation. if the basic block is a 36$ target block, we do essentially the same thing, but in addition 37$ insert all expressions belonging to insert{b} at the end of b 38$ (recall that b represents its interval). 39$ 40$ we suggest two alternative techniques for accomplishing this 41$ insertion: 42$ 43$ (a) first topologically sort the set insert{b} according to the 44$ relative dependency relation between expressions, so that if 45$ an expression t1 depends on another expression t2, then t2 46$ precedes t1 in this order. then for each expression t in this 47$ order, insert the instruction defining t. 48$ 49$ (b) proceed in any random order over all the expressions t in 50$ insert{b}. for each such expression t, insert the whole 51$ sequence of instructions needed to compute t from scratch, 52$ and, while doing so, record the availability of subexpressions 53$ of t thus generated. then eliminate computations of available 54$ subexpressions. for example, if both a*b and (a*b)*c are to 55$ be inserted, then if we first insert the whole computation of 56$ (a*b)*c, after which a*b will become available, then its 57$ subsequent insertion can be bypassed. 58$ 59$ our current data structures facilitate the first approach, which 60$ is the one that we will implement. 61$ 62 repr 63 $ data structures for local variables 64 globexps: df_elmt; 65 id: df_map; 66 zero: df_elmt; 67 f: remote smap(df_edge) df_map; 68 exposed: remote mmap{df_node} df_elmt; 69 end repr; 70 71 if globalexps = {} then return; end if; 72 73 globexps := globalexps; $ assign to split variable 74 zero := {}; $ initial availability information 75 id := [ globexps, zero ]; $ identity map 76$ 77$ perform phase 1: computation of local flow maps and exposed 78$ expressions for basic blocks. 79$ 80 [ f, exposed ] := csx_blockmaps(om, globexps); 81 $ (om indicates interprocedural analysis) 82$ 83$ perform phase 2: availability analysis 84$ 85 interproc_fwd_analysis 86 (f, avail, id, zero, true, true, exposed, insert, om); 87 $ meet_flag is true in the 'code motion mode' (the sixth parm 88 $ move_code is true, supplying it with the 'exposed' map and 89 $ obtaining the output 'avail' which maps each block to the set 90 $ of all expressions available at its start; we also compute a 91 $ map 'insert' which sends each target block into the set of 92 $ expressions to be inserted at its end. 93 94 95 end procedure interproc_csx; 96 97 1 .=member arx11c 2 3 4 procedure intraproc_csx(p); 5$ 6$ this routine performs the optimizations described above intraproce- 7$ durally for expressions which depend only on local variables of the 8$ procedure p. apart from this difference, it is quite similar to its 9$ interprocedural analog; the same method and phases are used here. 10$ 11 repr 12 $ data structure for parameter 13 p: routine; 14 15 $ data structures for local variables 16 procexps: df_elmt; 17 id: df_map; 18 zero: df_elmt; 19 f: remote smap(df_edge) df_map; 20 avalx: remote smap(df_node) df_elmt; 21 exposed: remote mmap{df_node} df_elmt; 22 insrtx: remote mmap{df_node} df_elmt; 23 x: elmt blocks; 24 y: remote set(expression); 25 end repr; 26 27 procexps := localexps{p}; 28 29 if procexps = {} then return; end if; 30 31 zero := {}; $ initial information at the entry of p 32 id := [ procexps, zero ]; $ identity map 33 34 $ perform phase 1 35 [ f, exposed ] := csx_blockmaps(p, procexps); 36 37 $ perform phase 2 38 intraproc_fwd_analysis 39 (p, f, avalx, id, zero, true, true, exposed, insrtx, om); 40 41 $ update the avail and insert maps 42 (forall y = avalx(x)) 43 if avail(x) = om then 44 avail(x) := y; 45 else 46 avail(x) +:= y; 47 end if; 48 end forall; 49 50 (forall y = insrtx{x}) 51 insert{x} +:= y; 52 end forall; 53 54 55 end procedure intraproc_csx; 56 57 1 .=member mel11d 2 3 4 procedure move_eliminate; 5 6 7 init 8 elimexps := {}; 9 10 repr 11 r: routine; 12 b: elmt blocks; 13 i, pi: elmt insts; 14 opc: elmt base_opcodes; 15 16 availb: remote set(expression); 17 insexps, elimexps: remote set(expression); 18 temps, killedexps: remote set(expression); 19 oi: occurrence; 20 t: expression; 21 v: symbol; 22 lsin: integer 0..65536; 23 24 x: elmt blocks; 25 y: remote set(expression); 26 z: expression; 27 end repr; 28 29 30 (forall r in routs) 31 (for_block(b, r)) 32 availb := avail(b); $ expressions available at entry to b 33 if availb = om then continue; end; 34 pi := om; $ previous instruction (for deletion) 35 (for_inst(i, b)) 36 opc := opcode(i); 37 38 if opc in ops_modify then 39 v := arg1(i); 40 killedexps := dependon{v}; 41 if opc in ops_iter then 42 killedexps +:= dependon{arg2(i)}; 43 end if; 44 availb := availb - killedexps; 45 if opc in ops_sin then 46 lsin := if opc = q1_ssubst then 4 else 3 end; 47 t := args(i)(lsin); 48 if t in availb then $ redundant embedding 49 del_inst(i, pi, b); 50 elimexps with:= t; smfc 52 messages{stmtof(i)}{'s'} with:= 52 [ 'use available embedding of ' 53 '"' + name(t) + '".' ]; 54 else 55 availb with:= t; 56 end if; 57 end if; 58 59 elseif opc in ops_exps then 60 t := arg1(i); 61 if t in availb then $ redundant computation 62 del_inst(i, pi, b); 63 elimexps with:= t; smfc 53 messages{stmtof(i)}{'s'} with:= 65 [ 'use available computation for ' 66 '"' + name(t) + '".' ]; 67 else 68 availb with:= t; 69 end if; 70 71 elseif opc = q1_entry then $ kill stacked expressions 72 availb := { t in availb | is_stk(t) = om }; 73 end if; 74 pi := i; 75 end; 76 if (insexps := insert{b}) /= {} and 77 b /= rentry(r) then 78 $ b is an interval (a target block) out of which we 79 $ move code 80 81 $ find one-before-last instruction in b 82 $ (an ugly procedure) 83 pi := om; 84 (for_inst(i, b)) 85 ppi := pi; 86 pi := i; 87 end; 88 already_there := availb; 89 (forall t in insexps) 90 is_temp(t) := om; 91 if t notin already_there then 92 insert_exp(t); 93 end if; smfc 54 messages{stmtof(ppi)}{'s'} with:= 95 [ 'insert computation of "'+name(t)+'".' ]; 96 end forall; 97 end if; 98 end; 99 end forall; 100$ 101$ at this point, elimexps contains all common subexpressions and 102$ expresions moved out of loops. to clean up the code, we iterate 103$ again over it and look for pairs of the form 104$ 105$ t := exp; 106$ a := t; 107$ 108$ where t is not a common subexpression (and hence bound to be dead 109$ after the second instruction), and change it into 110$ 111$ a := exp; 112$ 113$ this will shorten the code, and facilitate the detection os locally 114$ based sets, etc. 115$ 116 (forall r in routs) 117 (for_block(b, r)) 118 pi := om; $ preceding instruction 119 (for_inst(i, b)) 120 if opcode(i) = q1_asn and pi /= om then 121 if (t := arg2(i)) = arg1(pi) and 122 is_temp(t) = 1 and t notin elimexps then 123 v := arg1(i); 124 del_inst(i, pi, b); 125 arg1(pi) := v; 126 oi := get_oi(pi, 1); 127 occsof{t} less:= oi; 128 occsof{v} with:= oi; 129 end if; 130 end if; 131 pi := i; 132 end; $ end for_inst 133 end; $ end for_block 134 end forall; 135 136 if 'a' notin dump_string then return; end if; 137 138 print; 139 print('common subexpression elimination and code motion maps ='); 140 print; 141 prints('avail =', 142 [ [ str x, { '"'+name(z)+'"': z in y } ]: y = avail(x) ] ); 143 prints('insert =', 144 [ [ str x, { '"'+name(z)+'"': z in y } ]: y = insert{x} ] ); 145 146 147 end procedure move_eliminate; 148 149 150 151 152 procedure insert_exp(t); 153$ 154$ this procedure inserts in a recursive manner an expression into a 155$ target block of an interval. it uses two global-within-the-module 156$ objects: 157$ 158$ ppi: the instruction after which t should be inserted 159$ already_there: set of expressions already inserted (or already 160$ available at this point). 161$ 162$ the insertion is performed in the following recursive manner: 163$ if all arguments of t are either not expressions or else are 164$ expressions in already_there, insert after ppi an instruction 165$ defining t. 166$ otherwise, call insert_exp(t1) for each argument of t not 167$ satisfying the above condition and then insert the computation 168$ of t. 169$ 170 repr 171 t: expression; 172 tt: expression; 173 end repr; 174 175 176 (forall tt in argsexp(t) | 177 tt in allexps and tt notin already_there) 178 insert_exp(tt); 179 end forall; 180$ 181$ next insert t 182$ 183 insert_ins1(ppi, opcexp(t), [ t ] + argsexp(t)); 184 already_there with:= t; 185 186 end procedure insert_exp; 187 188 1 .=member bma11e 2 3 4 procedure csx_blockmaps(p, exps); 5$ 6$ this routine computes the data-flow block maps and the exposed map 7$ for the available expressions analysis. as always, p = om indicates 8$ the interprocedural case, otherwise p is the routine to be scanned. 9$ 10$ note that call blocks will be assigned identity data-flow maps and 11$ null exposed value, which is correct in the intraprocedural case. 12$ in the interprocedural case, the data-flow maps will be reset anyway, 13$ and this choice of the exposed value will cause, as noted above, code 14$ motion to be strictly intraprocedural. 15$ 16 repr 17 $ data structures for parameters 18 p: routine; 19 exps: df_elmt; 20 21 $ data structures for returned variables 22 f: remote smap(df_edge) df_map; 23 exposed: remote mmap{df_node} df_elmt; 24 25 $ data structures for local variables 26 todo: sparse set(routine); 27 r: routine; 28 b: df_node; 29 i: elmt insts; 30 opc: elmt base_opcodes; 31 32 a: tuple(symbol); 33 v: symbol; 34 lsin: integer; 35 fblk: df_map; 36 t: elmt df_base; 37 thruexps: df_elmt; 38 temps: df_elmt; 39 sblks: sparse set(elmt blocks); 40 lb: symbol; 41 b1: df_node; 42 end repr; 43 44 if p = om then todo := routs; else todo := { p }; end if; 45 46 f := {}; exposed := {}; 47 48 (forall r in todo) 49 (for_block(b, r)) 50 fblk := [exps, {}]; $ initialize block map to identity 51 (for_inst(i, b)) 52 opc := opcode(i); 53 if opc in ops_modify then 54 v := arg1(i); 55 thruexps := exps - dependon{v}; 56 if opc in ops_iter then 57 thruexps -:= dependon{arg2(i)}; 58 end if; 59 fblk := [ thruexps, {} ] .comp fblk; 60$ however, if opc is a sinister assignment, then the item to be 61$ assigned will be the temporary representing the inverse operation, 62$ and this instruction makes this temporary available. 63 if opc in ops_sin then 64 lsin := 65 if opc = q1_ssubst then 4 else 3 end; 66 t := args(i)(lsin); $ get expression 67 if t in exps then 68 fblk := 69 [ exps, (temps := {t}) ] .comp fblk; 70 end if; 71 end if; 72 elseif opc in ops_exps then 73 t := arg1(i); $ get expression computed 74 if t in exps then 75 if t in fblk(1) - fblk(2) then 76 exposed{b} with:= t; 77 end if; 78 fblk := 79 [ exps, (temps := {t}) ] .comp fblk; 80 end if; 81 elseif opc in ops_goto then 82 a := args(i); 83 if opc = q1_case then 84 sblks := { blockof(value(lb)) : 85 lb in range value(a(1)) }; 86 else 87 sblks := { blockof(value(a(#a))) }; 88 end if; 89 (forall b1 in sblks) 90 if b1 = rexit(r) then 91 $ upon return, all stacked expressions of r 92 $ will be killed. we account for this by 93 $ killing these exps along the flow to the 94 $ exit block, and so unify our algoritm. 95 thruexps := { t in exps | is_stk(t) = om }; 96 fblk := [ thruexps, {} ] .comp fblk; 97 end if; 98 f([b, b1]) := fblk .meet f([b, b1]); 99 end forall; 100 elseif opc = q1_entry then 101 $ all stacked expressions are killed at this point 102 thruexps := { t in exps | is_stk(t) = om }; 103 fblk := [ thruexps, {} ] .comp fblk; 104 end if; 105 end; $ end for_inst 106 end; $ end for_block 107 end forall; 108 109 return [ f, exposed ]; 110 111 end procedure csx_blockmaps; 112 113 114 115 116 operator .meet(f, g); 117$ 118$ functional meet of f and g, where only g can be undefined (om). for 119$ convenience, we avoid using a similar, though slightly different 120$ operator available in the dataflow_solver module. 121$ 122 repr 123 $ data structures for parameters 124 f, g: df_map; 125 end repr; 126 127 if g = om then 128 return f; 129 else 130 return [ f(1) * g(1), f(2) * g(2) ]; 131 end if; 132 133 end operator .meet; 134 135 136 drop 137 df_base, 138 .comp, 139 interproc_fwd_analysis, 140 intraproc_fwd_analysis, 141 fom, 142 xom; 143 144 145 end module setl_optimizer - availexp_analysis; 146 147 1 .=member live12 2 3 4 module setl_optimizer - live_analysis; 5$ smfb 41$ live-dead analysis establishes the live/dead status of variables. smfb 42$ a variable is said to be 'live' at a program point n if there exists a smfb 43$ path leading from n to some use of v which is free of any other smfb 44$ occurrence of v (implying that the current value of v may be used smfb 45$ subsequenlty, and therefore cannot be destroyed or discarded); smfb 46$ otherwise v is said to be 'dead' at n. smfb 47$ smfb 48$ standard live variable analysis is a 'backward-join' analysis whose smfb 49$ semi-lattice l = pow(e) is the power set of (i.e. all bit vectors smfb 50$ over) the set of all (relevant) program variables, and where lattice smfb 51$ meet is set-union. in this analysis we associate with each flow edge smfb 52$ (m, n) a data propagation map f(m, n) so that for each x in l we have smfb 53$ smfb 54$ f(m, n)(x) = thru(m, n) * x + livein(m, n) smfb 55$ smfb 56$ where smfb 57$ smfb 58$ thru(m, n) = the set of all variables v in e for which there exists a smfb 59$ path through the flow of f which is either free of any smfb 60$ other occurrence of v or else contains a use of v not smfb 61$ preceded by any other occurrence of v. smfb 62$ smfb 63$ livein(m,n) = the set of all variables v in e for which there exists a smfb 64$ path through the flow of f which contains a use of v not smfb 65$ preceded by any other occurrence of v. smfb 66$ smfb 67$ the output of live-dead analysis is a map liveat, mapping each basic smfb 68$ block n to a set liveat(n) of all variables live at the start of n. smfb 69$ this set can then be propagated (backward) through basics blocks to smfb 70$ establish variable liveness at any program point. smfb 71$ smfb 72$ live variable calculation is performed straightforwardly using our smfb 73$ general-purpose dataflow solver module. the algorithm used are smfb 74$ described in more (technical) detail in the dataflow_solver module. smfb 75$ 6 macro df_base; df_base_syms endm; 7 macro .comp; .comp_syms endm; 8 macro interproc_back_analysis; interproc_back_analysis_syms endm; 9 macro intraproc_back_analysis; intraproc_back_analysis_syms endm; 10 macro fom; fom_syms endm; 11 macro xom; xom_syms endm; 12 13 14 repr 15 mode df_elmt: df_elmt_syms; 16 mode df_map: df_map_syms; 17 18 .join: operator(df_map, df_map) df_map; 19 interproc_live: procedure; 20 intraproc_live: procedure(routine); 21 live_blockmaps: procedure(routine, df_elmt) 22 remote smap(df_edge) df_map; 23 end repr; 24 25 1 .=member lva12a 2 3 4 procedure live; 5$ 6$ this is the master procedure which drives the live analysis. it 7$ consists of the following phases: 8$ 9$ interprocedural analysis for liveness of global variables. 10$ 11$ intraprocedural analysis for liveness of local variables within each 12$ routine. 13$ 14$ for efficiency, we restrict the elements for our analysis here to the 15$ variables which appear in the user's program, or were directly derived 16$ from program variables. also note that formal parameters need not be 17$ analysed. beyond these modifications, the algorithm which follows is 18$ the standard live variable algorithm. furthermore, the results of 19$ each analysis are used as soon as they become available. 20$ 21 repr 22 p: routine; 23 usym1, usym2: symbol; 25 end repr; 26 27 28 title('cims.setl.' + prog_level + ' live analysis'); 29 printa(term_file, ' - live analysis'); 30 33 $ define the undefined lattice element and flow map 34 xom := { usym1 := newat }; 35 fom := [ { usym1 := newat }, { usym2 := newat } ]; 36 37 interproc_live; $ interprocedural analysis 38 39 (forall p in routs) 40 intraproc_live(p); $ intraprocedural analysis 41 end forall; 42 43 statistics with:= time; $ save time for final statistics 48 49 50 end procedure live; 51 52 1 .=member ine12b 2 3 4 procedure interproc_live; 5$ 6$ this procedure performs interprocedural live analysis for the relevant 7$ global variables. 8$ 9 repr 10 pi: elmt insts; 11 v: symbol; 12 globvars: df_elmt; 13 id: df_map; 14 zero: df_elmt; 15 f: remote smap(df_edge) df_map; 16 liveat: remote smap(df_node) df_elmt; 17 end repr; 18 19 20 $ note that we restrict the elements for our analysis here to the 21 $ variables which appear in the user's program, or were directly 22 $ derived from program variables. also note that formal parameters 23 $ need not be analysed. beyond these modifications, the algorithm 24 $ which follows is the standard live variable algorithm. 25 26 globvars := { v in globalvars | is_internal(v) = om }; 27 28 if globvars = {} then return; end if; 29 30 zero := {}; $ nothing live at program exit 31 id := [ globvars, zero ]; $ identity map for the analysis 32 33 $ compute the global flow maps 34 f := live_blockmaps(om, globvars); 35 $ (om indicates interprocedural analysis) 36 37 $ perform interprocedural live analysis 38 interproc_back_analysis(f, liveat, id, zero, false); 39 $ meet_flag is false for the join analysis. 40 41 pi := first_inst(rentry(sym_main)); 42 (forall v in liveat(rentry(sym_main))) 43 if is_init(v)=1 then 44 insert_ins(pi, q1_asn, v, alias(v)); 45 else 46 insert_ins(pi, q1_asn, v, sym_om); smfk 38 messages{sc_stmt_ct(scope(v))}{'w'} with:= 48 [ 'init ' + name(v) + ' := om;' smfi 43 ' $ uninitialised variable.' ]; 50 end if; 51 end forall; 52 53 54 end procedure interproc_live; 55 56 1 .=member ina12c 2 3 4 procedure intraproc_live(p); 5$ 6$ this procedure performs intraprocedural live analysis for the relevant 7$ local variables of the procedure p. 8$ 9 repr 10 p: routine; 11 pi: elmt insts; 12 v: symbol; 13 procvars: df_elmt; 14 id: df_map; 15 zero: df_elmt; 16 f: remote smap(df_edge) df_map; 17 liveat: remote smap(df_node) df_elmt; 18 end repr; 19 20 21 $ note that we restrict the elements for our analysis here to the 22 $ variables which appear in the user's program, or were directly 23 $ derived from program variables. also note that formal parameters 24 $ need not be analysed. beyond these modifications, the algorithm 25 $ which follows is the standard live variable algorithm. 26 27 procvars := { v in localvars{p} | 28 is_internal(v) = om and is_param(v) = om }; 29 30 if procvars = {} then return; end if; 31 32 zero := {}; $ nothing live at routine exit 33 id := [ procvars, zero ]; $ identity map for the analysis 34 35 $ compute the local flow maps 36 f := live_blockmaps(p, procvars); 37 38 $ perform intraprocedural live analysis 39 intraproc_back_analysis(p, f, liveat, id, zero, false); 40 $ meet_flag is false for the join analysis. 41 42 pi := first_inst(rentry(p)); 43 (forall v in liveat(rentry(p))) 44 insert_ins(pi, q1_asn, v, sym_om); 45 messages{sc_estmt_ct(scope(v))}{'w'} with:= 46 [ 'init ' + name(v) + ' := om;' smfi 44 ' $ uninitialised variable.' ]; 48 end forall; 49 50 51 end procedure intraproc_live; 52 53 1 .=member lbk12d 2 3 4 procedure live_blockmaps(p, vars); 5$ 6$ this procedure computes the data-flow block maps for live analysis. 7$ 8$ as always, p = om indicates the interprocedural case, otherwise p is 9$ the routine to be scanned. 10$ 11 repr 12 p: routine; 13 vars: df_elmt; 14 15 todo: sparse set(routine); 16 f: remote smap(df_edge) df_map; 17 r: routine; 18 b: elmt blocks; 19 i: elmt insts; 20 opc: elmt base_opcodes; 21 argsi: tuple(symbol); 22 fblk: df_map; 23 killed, gen: df_elmt; 24 k: integer 0..65536; 25 sblks: sparse set(elmt blocks); 26 lb: symbol; 27 b1: elmt blocks; 28 end repr; 29 30 if p = om then todo := routs; else todo := { p }; end if; 31 32 f := {}; 33 34 (forall r in todo) 35 (for_block(b, r)) 36 fblk := [ vars, {} ]; $ start with the identity 37 38 (for_inst(i, b)) 39 opc := opcode(i); 40 argsi := args(i); 41 42 killed := gen := {}; 43 44 (forall k in [ first_ivar(opc)..#argsi ] | 45 argsi(k) in vars) 46 if k = 2 and (opc = q1_set1 or opc = q1_tup1) then 47 continue forall; 48 end if; 49 50 gen with:= argsi(k); 51 end forall; 52 53 if opc in ops_ovar and argsi(1) in vars then 54 killed with:= argsi(1); 55 end if; 56 57 fblk := fblk .comp [ vars - killed + gen, gen ]; 58 59 if opc in ops_goto then 60 if opc = q1_case then 61 sblks := { blockof(value(lb)) : 62 lb in range value(argsi(1)) }; 63 else 64 sblks := { blockof(value(argsi(#argsi))) }; 65 end if; 66 67 (forall b1 in sblks) 68 f([b, b1]) := fblk .join f([b, b1]); 69 end forall; 70 end if; 71 end; $ end for_inst 72 end; $ end for_block 73 end forall; 74 75 return f; 76 77 78 end procedure live_blockmaps; 79 80 81 82 83 operator .join(f, g); 84$ 85$ functional join of f and g, where only g can be undefined (om). 86$ 87$ for convenience, we avoid using a similar, thought slightly different 88$ operator available in the dataflow_solver module. 89$ 90 if g = om then 91 return f; 92 else 93 return [ f(1) + g(1), f(2) + g(2) ]; 94 end if; 95 96 97 end operator .join; 98 99 100 drop 101 df_base, 102 .comp, 103 interproc_back_analysis, 104 intraproc_back_analysis, 105 fom, 106 xom; 107 108 109 end module setl_optimizer - live_analysis; 110 111 1 .=member bfd11f 2 3 4 module setl_optimizer - bfrom_analysis; 5$ 6$ this module computes the bfrom map and some related maps, using the 7$ general data flow analysis algorithms in the 'dataflow_solver' module. 8$ this version does not employ call strings, and thus may loose a bit of 9$ accuracy, even though the bfrom map for global variables is computed 10$ correctly, using the interprocedural forward algorithm. 11$ 12$ as usual, our analysis is partitioned into interprocedural analysis of 13$ global variable occurrences, followed by intraprocedural analysis of 14$ local variable occurrences within each procedure. 15$ 16$ the bfrom map is defined on variable uses as follows: let vo be a use 17$ of some variable v; then bfrom is defined to be the set of all 18$ occurrences vo1 of v (definitions or uses) which can reach vo along a 19$ path clear of any other occurrences of v. 20$ 21$ we compute the bfrom map rather than the traditional use-definition 22$ map, for the following reasons: 23$ 24$ 1. some optimization analyses use 'shadow variables' rather than the 25$ variables themselves. for example, copy optimization applies to the 26$ share bits of variables. definitions and uses of these shadow 27$ variables need not coincide with definitions and uses of the actual 28$ variables. the use of bfrom allows a uniform treatment of all these 29$ optimizations. 30$ 31$ 2. we expect that using the bfrom instead of the use-def map will 32$ speed up various iterative algorithms, such as the type finder. 33$ 34$ 3. the automatic data structure selection algorithm makes special use 35$ of the bfrom map, and will not function properly if use-def chains are 36$ used instead. 37$ 38$ the data-flow analysis used to compute bfrom is a 'reaching occur- 39$ rences' analysis, which is a forward-join analysis, in which, for each 40$ flow graph node n, we wish to compute a set reach(n) of all occur- 41$ rences vo which can reach n along a path which is free of any other 42$ occurrences of the variable of vo. using this map, we can compute the 43$ bfrom map in one additional linear scan of each basic block, as shown 44$ below. 45$ 46$ this analysis is obviously of the bitvectoring class. indeed, let 47$ 'vars' denote the set of all variables whose occurrences are to be 48$ analysed (global variables or local variables within some procedure), 49$ and let 'occs' denote the set of all their occurrences. then the 50$ lattice l of our analysis is the power set of occs, and for each 51$ flow-graph edge (m, n) we assign a data-flow map f(m, n), defined as: 52$ 53$ f(m, n)(x) = reachthru(m,n) * x + reachfrom(m,n) , x in l 54$ 55$ where 56$ 57$ reachthru(m,n) = set of all occurrences in occs which reach the start 58$ of n if they reach the start of m. (note that this set also includes 59$ occurrences occurring within m that can reach the start of n.) 60$ 61$ reachfrom(m,n) = set of all occurrences in occs occurring within m 62$ which can reach the start of n. 63$ 64$ after establishing the block maps we solve the corresponding data-flow 65$ problem using our general package. a final step computes the required 66$ bfrom map, its inverse map ffrom, and an auxiliary set bfrom_dead, 67$ defined as the set of all occurrences which can either reach a program 68$ exit (or a procedure exit for local variable occurrences) or a 69$ redefinition of their variable. 70$ 71$ we assume that the following global variables are available. 72$ 73$ globalvars: set of all global variables 74$ 75$ localvars: maps each routine to the set of its local variables 76$ 77$ occsof: maps each variable to the set of its occurrences 78$ 1 .=member fbf11g 2 3 4 macro .comp; .comp_ocrs endm; 5 macro interproc_fwd_analysis; interproc_fwd_analysis_ocrs endm; 6 macro intraproc_fwd_analysis; intraproc_fwd_analysis_ocrs endm; 7 macro fom; fom_ocrs endm; 8 macro xom; xom_ocrs endm; 9 10 11 var 12 def_def, $ maps definitions to definitions reached 13 def_exit, $ set of definitions which reach the exit block 14 rem_bfrom_dead; $ split variable to the global bfrom_dead 15 16 17 repr 18 mode df_elmt: df_elmt_ocrs; 19 mode df_map: df_map_ocrs; 20 21 def_def: sparse mmap{occurrence} 22 sparse set(occurrence); 23 def_exit: df_elmt; 24 rem_bfrom_dead: df_elmt; 25 26 .join: operator(df_map, df_map) df_map; 27 global_bfrom: procedure() 28 tuple( 29 remote smap(df_node) df_elmt, 30 df_elmt 31 ); 32 local_bfrom: procedure(routine) 33 tuple( 34 remote smap(df_node) df_elmt, 35 df_elmt 36 ); 37 comp_bfrom: procedure( 38 routine, 39 df_elmt, 40 remote smap(df_node) df_elmt 41 ); 42 bfrom_blockmaps: procedure(routine, df_elmt) 43 remote smap(df_edge) df_map; 44 end repr; 45 46 47 procedure find_bfrom; 48$ 49$ this is the master procedure which drives the bfrom computation. 50$ it consists of the following phases: 51$ 52$ interprocedural analysis for occurrences of global variables. 53$ 54$ intraprocedural analysis for occurrences of local variables within 55$ each routine. 56$ 57$ for efficiency, the results of each such analysis are used immediately 58$ to add entries to bfrom and bfrom_dead, and are discarded when 59$ proceeding to the next analysis. 60$ 61 repr 62 foccs: df_elmt; 63 freach: remote smap(df_node) df_elmt; 64 r: routine; 65 roccs: sparse set(occurrence); 66 uocrs1, uocrs2: occurrence; 67 vo1, vo2: occurrence; 68 errois: remote set(occurrence); 69 x: occurrence; 71 end repr; 72 73 $ initialize output objects 74 bfrom := {}; ffrom := {}; rem_bfrom_dead := {}; 75 def_def := {}; def_exit := {}; 76 77 title('cims.setl.' + prog_level + ' - bfrom analysis'); 78 printa(term_file, ' - bfrom analysis'); 79 82 $ define the undefined lattice element and flow map 83 xom := { uocrs1 := newat }; 84 fom := [ { uocrs1 := newat }, { uocrs2 := newat } ]; 85 86 $ compute reaching occurrences for global variables 87 [ freach, foccs ] := global_bfrom(); 88 89 $ compute corresponding bfrom entries immediately 90 $ (the first parameter = om to indicate the interprocedural case) 91 comp_bfrom(om, foccs, freach); 92 93 (forall r in routs) 94 $ compute reaching occurrences for local variables of r 95 [ freach, foccs ] := local_bfrom(r); 96 $ as before, add corresponding entries to bfrom immediately 97 comp_bfrom(r, foccs, freach); 98 end forall; 99 100 bfrom_dead := rem_bfrom_dead; $ convert between data structures 101 smfi 45 if debug_flag then smfi 46 102 errois := {}; 103 (forall [ vo1, vo2 ] in def_def | oi_sym(vo1) in uservars) 104 if oi_op(vo1) = q1_asn and arg2(instno(vo1)) = sym_om then 105 continue forall; 106 end if; 107 if oi_op(vo2) = q1_asn and arg2(instno(vo2)) = sym_om then 108 continue forall; 109 end if; 110 if ffrom{vo1} = {} then 111 if vo1 in errois then continue; end if; 112 errois with:= vo1; smfc 55 messages{stmtof(instno(vo1))}{'i'} with:= smfc 56 [ 'this definition of "' + oi_name(vo1) + '"' 115 ' is not used and thus redundant.' ]; 116 elseif getipp('full=0/1') = 1 then smfc 57 messages{stmtof(instno(vo1))}{'i'} with:= smfc 58 [ 'this definition of "' + oi_name(vo1) + '"' 119 ' is not used before being redefined at ' + 120 oi_stmt(vo2) + '.' ]; 121 end if; 122 end forall; 123 124 (forall vo1 in def_exit | oi_sym(vo1) in uservars) 125 if oi_op(vo1) = q1_asn and arg2(instno(vo1)) = sym_om then 126 continue forall; 127 end if; 128 if #occsof{oi_sym(vo1)} = 1 then continue forall; end if; 129 if ffrom{vo1} = {} then 130 if vo1 in errois then continue; end if; 131 errois with:= vo1; smfc 59 messages{stmtof(instno(vo1))}{'i'} with:= smfc 60 [ 'this definition of "' + oi_name(vo1) + '"' 134 ' is not used and is thus redundant.' ]; 135 elseif getipp('full=0/1') = 1 then smfc 61 messages{stmtof(instno(vo1))}{'i'} with:= smfc 62 [ 'this definition of "' + oi_name(vo1) + '"' smfc 63 ' is not used before the program exit.' ]; 139 end if; 140 end forall; smfi 47 smfi 48 end if; 141 142 $ delete the static variables global to this module 143 def_def := om; def_exit := om; 144 rem_bfrom_dead := om; 145 146 if 'b' in dump_string then 149 print; 150 print('variable occurrence reaching occurences'); 151 print('---------------------------------------------'); 152 print; 153 prints('', 154 [ [ rpad(oi_name(vo1), 12) + ' ' + 155 rpad(oi_str(vo1), 12), 156 +/[ rpad(oi_str(vo2), 10) : vo2 in roccs ] ] : 157 roccs = bfrom{vo1}] ); 158 print; 159 prints('bfrom_dead =', 160 [ [ rpad(oi_name(vo1), 12), oi_str(vo1) ] : 161 vo1 in bfrom_dead ] ); 166 end if; 167 168 statistics with:= time; $ save time for final statistics 176 177 178 end procedure find_bfrom; 179 180 181 182 183 procedure global_bfrom; 184$ 185$ this routine computes the 'reaching occurrences' map for occurrences 186$ of global variables, using our general interprocedural data flow 187$ algorithm. 188$ 189 repr 190 r: routine; 191 v: symbol; smfi 49 c: elmt blocks; 192 globaloccs: df_elmt; 193 id: df_map; 194 zero: df_elmt; 195 f: remote smap(df_edge) df_map; 196 reach: remote smap(df_node) df_elmt; 197 dum1, dum2: remote mmap{df_node} df_elmt; 198 end repr; 199$ 200$ the following code sequence has been lowered in level to allow for 201$ greater efficiency. the original code has been left as a comment. 202$ 203$ globlvrs := globalvars + { pr : r in routs, pr in rparams(r) }; 204$ 205$ if globlvrs = {} then 206$ print(' no global bfrom entries'); 207$ return []; 208$ end if; 209$ 210$ globaloccs := +/ [ occsof{v} : v in globlvrs ]; 211$ 212 globaloccs := {}; 213 (forall v in globalvars) 214 globaloccs +:= occsof{v}; 215 end forall; 216 (forall r in routs, v in rparams(r)) 217 globaloccs +:= occsof{v}; 218 end forall; smfi 50 smfi 51 if globaloccs = {} and not debug_flag then smfi 52 return [ {}, {} ]; smfi 53 end if; 220 221 id := [ globaloccs, {} ]; $ identity map for analysis 222 zero := {}; $ initial set of reaching occurrences 223 224 $ compute the block data-flow maps. the first parameter is om to 225 $ indicate that all procedures are to be scanned. 226 f := bfrom_blockmaps(om, globaloccs); 227 228 $ perform reaching occurrences analysis. 229 interproc_fwd_analysis 230 (f, reach, id, zero, false, false, dum1, dum2, om); 231 $ here the meet_flag parameter is false (join analysis), the 232 $ move_code parameter is also false (no code motion), and the 233 $ last two parameters (needed only for code motion) are dummies smfi 54 smfi 55 $ next we test for infinite recursion. this is done by testing the smfi 56 $ flow maps from each call block to its successor for fom. smfi 57 if debug_flag then smfi 58 (forall [ -, c ] in callsin | f([c, cessor(c)]) = fom) smfi 59 messages{sc_stmt_ct(callproc(c))}{'e'} with:= smfi 60 [ 'no control path exists to ' smfi 61 'this routine''s exit block.' ]; smfi 62 end forall; smfi 63 end if; 234 235 return [ reach, globaloccs ]; 236 237 238 end procedure global_bfrom; 239 240 241 242 243 procedure local_bfrom(r); 244$ 245$ this routine computes the reach map for occurrences of local variables 246$ within the routine r. 247$ 248 repr 249 $ data structures for parameters 250 r: routine; 251 252 $ data structures for local variables 253 localsofr: sparse set(symbol); 254 pr: symbol; 255 localoccs: df_elmt; 256 v: symbol; 257 reach: remote smap(df_node) df_elmt; 258 id: df_map; 259 zero: df_elmt; 260 f: remote smap(df_edge) df_map; 261 dum1, dum2: remote mmap{df_node} df_elmt; 262 end repr; 263$ 264$ the following code sequence has been lowered in level to allow for 265$ greater efficiency. the original code has been left as a comment. 266$ 267$ localsofr := localvars{r} - { pr : pr in rparams(r)}; 268$ 269$ if localsofr = {} then 270$ print(' no local bfrom entries in routine', name(r)); 271$ return []; 272$ end if; 273$ 274$ localoccs := +/[occsof{v} : v in localsofr]; 275 localoccs := {}; 276 (forall v in localvars{r} | v notin rparams(r)) 277 localoccs +:= occsof{v}; 278 end forall; 279 if localoccs = {} then return [ {}, {} ]; end if; 280 281 reach := {}; 282 id := [ localoccs, {} ]; $ identity map for analysis 283 zero := {}; $ initially reaching occurrences 284 285 $ compute data flow maps for basic blocks 286 f := bfrom_blockmaps(r, localoccs); 287 288 $ perform 'reaching occurrences' analysis 289 intraproc_fwd_analysis(r, f, reach, id, zero, false, false, 290 dum1, dum2, om); 291 $ (see comment in global_bfrom for the significance of these 292 $ parameters.) 293 294 return [ reach, localoccs ]; 295 296 297 end procedure local_bfrom; 298 299 1 .=member cbf11h 2 3 4 procedure comp_bfrom(p, foccs, freach); 5$ 6$ this routine performs a final scan of the code to add entries to the 7$ bfrom map and the bfrom_dead set. this scan will consider all occur- 8$ rences in the set 'occs'. p = om indicates interprocedural analysis, 9$ in which all routines have to be scanned (but only for occurrences of 10$ global variables); otherwise p is the routine to be scanned, and 11$ 'occs' is the set of all occurrences of local variables of p. 'reach' 12$ is the reaching occurrences map, as defined in the introduction to 13$ this module, and as computed by our general data-flow algorithms. 14$ 15 repr 16 $ data structures for parameters 17 p: routine; 18 foccs: df_elmt; 19 freach: remote smap(df_node) df_elmt; 20 21 $ data structures for local variables 22 todo: sparse set(routine); 23 r: routine; 24 b: elmt blocks; 25 i: elmt insts; 26 opc: elmt base_opcodes; 27 v: symbol; 28 argsi: tuple(symbol); 29 vo, vo1: occurrence; 30 iva1: integer; 31 k: integer; 32 reachb: df_elmt; 33 reachd: df_elmt; 34 gen, killed: df_elmt; 35 voccs: df_elmt; 36 end repr; 37 38 39 if p = om then todo := routs; else todo := { p }; end if; 40 41 (forall r in todo) 42 (for_block(b, r)) 43 reachb := freach(b); 44 if reachb = om then continue; end if; 45 (for_inst(i, b)) 46 opc := opcode(i); 47 48 if opc = q1_exit and (r = sym_main or p /= om) 49 or opc = q1_stop then 50$ a program or procedure exit, at which all occurrences in 51$ reachb 'become' dead. add these occurrences to bfrom_dead 52 if debug_flag then 53 def_exit := { vo1 in reachb | is_ovar(vo1) }; 54 end if; 55 rem_bfrom_dead +:= reachb; 56 end if; 57 58 argsi := args(i); $ tuple of inst. arguments 59 $ get the index of the first ivariable 60 iva1 := first_ivar(opc); 61 62$ iterate over all arguments of i in reverse order. as we do this, we 63$ update the value of reachb, to account for killing of the reachabi- 64$ lity of all other occurrences of these variables, and generation of 65$ new occurrences within i. also, the bfrom value of each ivariable 66$ (use) is computed. 67$ 68$ note that we 'freeze' the value of reachb when scanning the 69$ ivariables of i, and treat the ovariable in a different manner than 70$ the ivariables. to understand this, consider the case where i is 71$ 'v1 := v2 + v3' (where all arguments are occurrences of the same 72$ variable v). here we want both ivariables to be linked to the same 73$ preceding occurrences of v, so that none of them should kill reacha- 74$ bility of these occurrences until all ivariables are processed. 75$ however, when we come to process the ovariable, we will want to 76$ regard the ivariables as killing reachability of all preceding occur- 77$ rences. this will yield, for the above i, 78$ bfrom{v2} = bfrom{v3} = all preceding reaching occurrences of v, 79$ and bfrom{ any succeeding occ. of v } = { v1 }. 80$ 81$ note also that if the first argument of i is both an ivariable and an 82$ ovariable, e.g. if i is 'f(x) := f', then an argument quite analogous 83$ to the above one implies that the bfrom value of both these occur- 84$ rences of f is the set of all preceding reaching occurrences of f, 85$ whereas bfrom of a succeeding occurrence of f contains only the 86$ ovariable occurrence in i. 87 88 killed := gen := {}; 89 90 (forall k in [ #argsi, #argsi-1..iva1 ]) 91 vo := get_oi(i, k); $ get occurrence 92 if vo notin foccs then continue forall; end if; 93 v := argsi(k); 94 voccs := occsof{v}; 95 bfrom{vo} := voccs * reachb; 96 if debug_flag then 97 if p = om and v in uservars and smfk 39 opc /= q1_argout then 102 globals_r{r} with:= v; smfk 40 if exists vo1 in bfrom{vo} | smfk 41 oi_rout(vo1) /= r then smfk 42 globals_e{r} with:= v; smfk 43 end if; 103 end if; 104 end if; 105 (forall vo1 in bfrom{vo}) 106 ffrom{vo1} with:= vo; 107 end forall; 108 gen with:= vo; 109 killed +:= voccs; 110 end forall; 111 112 vo := get_oi(i, 1); $ potential output occurrence 113 if is_ovar(vo) and vo in foccs then 114 v := argsi(1); 115 voccs := occsof{v}; 117 reachd := (reachb - killed + gen) * voccs; 120 rem_bfrom_dead +:= reachd; smfb 78 if debug_flag then smfb 79 def_def +:= smfb 80 { [ vo1, vo ] : vo1 in reachd | is_ovar(vo1) }; 121 if p = om and v in uservars and 122 opc /= q1_argin then 123 globals_w{r} with:= v; 124 end if; 125 end if; smfk 44 126$ note that if i is 'v1 := v2 + v3', then the last statement will cause 127$ only v2 and v3 to be added to bfrom_dead, whereas preceding 128$ occurrences of v do not become dead. 129 gen := gen - voccs + {vo}; 130$ note that for the above i, the last statement makes only v1 generated 131$ through i, whereas v2 and v3 are killed. 132 killed +:= voccs; 133 end if; 134 135$ finally, update the reachb value 136 reachb := reachb - killed + gen; 137 end; $ end for_inst 138 end; $ end for_block 139 end forall; 140 141 end procedure comp_bfrom; 142 143 1 .=member bma11i 2 3 4 procedure bfrom_blockmaps(p, foccs); 5$ 6$ this routine computes the basic block data flow maps for the 7$ occurrences in 'occs'. p = om indicates the interprocedural case 8$ (in which we process all procedures, but only for occurrences of 9$ global variables); otherwise p is the routine to be scanned. 10$ 11$ note that the identity map will be associated with call blocks. 12$ this is ok for intraprocedural analysis, and does not matter in 13$ the interprocedural analysis, as these maps are reset then later 14$ anyway. 15$ 16 repr 17 $ data structures for parameters 18 p: routine; 19 foccs: df_elmt; 20 21 $ data structures for local variables 22 todo: sparse set(routine); 23 f: remote smap(df_edge) df_map; 24 r: routine; 25 b: elmt blocks; 26 fblk: df_map; 27 i: elmt insts; 28 opc: elmt base_opcodes; 29 argsi: tuple(symbol); 30 iva1: integer; 31 killed: df_elmt; 32 gen: df_elmt; 33 k: integer; 34 vo: occurrence; 35 voccs: df_elmt; 36 sblks: sparse set(elmt blocks); 37 lb: symbol; 38 b1: elmt blocks; 39 end repr; 40 41 42 if p = om then todo := routs; else todo := { p }; end if; 43 f := {}; $ initialize the edge map f 44 45 (forall r in todo) 46 (for_block(b, r)) 47 fblk := [ foccs, {} ]; $ start with the identity 48 (for_inst(i, b)) 49 opc := opcode(i); 50 argsi := args(i); 51 iva1 := first_ivar(opc); 52 53$ update fblk with the effect of the instruction i. 54$ i is scanned from right to left, and the ovariable occurrence 55$ of i will kill other ivariable occurrences of the same variable. 56$ note that if i is 'v1 := v2 + v3' then only the ovariable v1 is 57$ to be generated in i, and neither v2 nor v3 reach the next 58$ instruction (i.e. both are killed by i). for more details, see 59$ similar comments in the procedure comp_bfrom above. 60 61 killed := gen := {}; 62 (forall k in [ #argsi, #argsi-1..iva1 ]) 63 vo := get_oi(i, k); $ get occurrence 64 if vo notin foccs then continue forall; end if; 65 gen with:= vo; 66 killed +:= occsof{argsi(k)}; 67 end forall; 68 69 vo := get_oi(i, 1); $ potential output occurrence 70 if is_ovar(vo) and vo in foccs then 71 voccs := occsof{argsi(1)}; 72 gen := gen - voccs + {vo}; 73 killed +:= voccs; 74 end if; 75 76 fblk := [ foccs-killed+gen, gen ] .comp fblk; 77$ (.comp is exported from the dataflow_solver module.) 78 79 if opc in ops_goto then 80$ get successor blocks. the last argument of each branch instruction smfb 81$ is a target label. for case statements, the last argument is a con- smfb 82$ stant map from case tag values to their labels. 85 86$ note also that one argument of a case statement (the 'x' in 87$ 'case x of ...' is not a constant, and so may participate in our 88$ analysis. in particular, the effect of this occurrence of x on the 89$ block map has to be analysed before this statement can be treated as 90$ a block exit. 91 92 if opc = q1_case then 93 sblks := { blockof(value(lb)) : 94 lb in range value(argsi(1)) }; 95 else 96 sblks := { blockof(value(argsi(#argsi))) }; 97 end if; 98 99 (forall b1 in sblks) 100 f([b, b1]) := fblk .join f([b, b1]); 101 end forall; 102 end if; 103 end; $ end for_inst 104 end; $ end for_block 105 end forall; 106 107 return f; 108 109 end procedure bfrom_blockmaps; 110 111 112 113 114 operator .join(f, g); 115$ 116$ functional join of f and g, where only g can be undefined (om). 117$ for convenience, we avoid using a similar, though slightly 118$ different operator available in the 'dataflow_solver' module. 119$ 120 if g = om then 121 return f; 122 else 123 return [ f(1) + g(1), f(2) + g(2) ]; 124 end if; 125 126 end operator .join; 127 128 129 drop 130 .comp, 131 interproc_fwd_analysis, 132 intraproc_fwd_analysis, 133 fom, 134 xom; 135 136 137 end module setl_optimizer - bfrom_analysis; 138 139 smfc 64 smfc 65 smfc 66 module setl_optimizer - region_constants; smfc 67$ smfc 68$ 1. mark as 'invariant' all statements whose operands are all either smfc 69$ constant or have their reaching definitions outside the current smfc 70$ interval. smfc 71$ smfc 72$ 2. repeat step (3) until at some repetition no new statements are smfc 73$ marked 'invariant'. smfc 74$ smfc 75$ 3. mark 'invariant' all those statements not previously so marked smfc 76$ whose operands all are either constant, have their reaching smfc 77$ definitions outside the current interval, or have exactly one smfc 78$ reaching definition, and that definition is an statement in the smfc 79$ current interval marked invariant. smfc 80$ smfc 81$ 4. flag all conditional branches which are determined by smfc 82$ loop-invariant computations. in addition, flag the interval if all smfc 83$ conditional branches are determined by loop-invariant computations. smfc 84$ smfc 85 macro ud(oi); (ud_memo(oi) ? ud_rout(oi)) endm; smfc 86 smfc 87 var smfc 88 ud_memo; $ use-definition map, computed on demand smfc 89 smfc 90 repr smfc 91 ud_memo: smap(occurrence) smfc 92 sparse set(occurrence); smfc 93 smfc 94 comp_region_constants: procedure(routine); smfc 95 ud_rout: procedure(occurrence) smfc 96 sparse set(occurrence); smfc 97 end repr; smfc 98 smfc 99 smfc 100 procedure find_region_constants; smfc 101$ smfc 102$ this routine is the main driver routine to detect potentially infinite smfc 103$ loops. smfc 104$ smfc 105 repr smfc 106 p: routine; smfc 107 end repr; smfc 108 smfc 109 smfc 110 title('cims.setl.' + prog_level + ' - flow-constant loops'); smfc 111 printa(term_file, ' - flow-constant loop analysis'); smfc 112 smfc 113 $ initialise global-to-module objects smfc 114 ud_memo := {}; smfc 115 smfc 116 (forall p in routs) smfc 117 comp_region_constants(p); smfc 118 end forall; smfc 119 smfc 120 $ delete global-to-module objects smfc 121 ud_memo := om; smfc 122 smfc 123 statistics with:= time; $ save time for final statistics smfc 124 smfc 125 smfc 126 end procedure find_region_constants; smfc 127 smfc 128 smfc 129 smfc 130 smfc 131 procedure comp_region_constants(p); smfc 132$ smfc 133$ this routine performs the analysis required to detect region-constant smfc 134$ branches. smfc 135$ smfc 136 init smfc 137 is_called := {}, $ maps each interval to the routines smfc 138 $ which might be called from within it. smfc 139 is_desc := {}, $ maps each interval to the intervals it smfc 140 $ contains. smfc 141 need_process := {}; $ set of intervals which need to be smfc 142 $ processed, either because they have smfc 143 $ more than one successor or because smfc 144 $ they are potentially loop-invariant. smfc 145 smfc 146 repr smfc 147 p, q, r: routine; smfc 148 is_called: mmap{elmt blocks} set(routine); smfc 149 is_desc: mmap{elmt blocks} set(elmt blocks); smfc 150 need_process: set(elmt blocks); smfc 151 invariants: set(elmt insts); smfc 152 b, c, i, i1: elmt blocks; smfc 153 inst: elmt insts; smfc 154 opc: elmt base_opcodes; smfc 155 workpile, seen: set(routine); smfc 156 j, k: integer 0..65536; smfc 157 vo, vox, voy: occurrence; smfc 158 convrgd: boolean; smfc 159 has_const, has_cond: boolean; smfc 160 inv_count: integer; smfi 64 l_messages: mmap{integer} smfi 65 mmap{string} smfi 66 set(tuple(string)); smfc 161 end repr; smfc 162 smfc 163 smfc 164 if 'e' in dump_string then $ print heading for statistics smfc 165 print('interval #desc #called #scans #invar step 4'); smfc 166 print('-----------------------------------------------------'); smfc 167 end if; smfc 168 smfc 169$ compute is_desc, which maps each interval to the set of all intervals smfc 170$ which are contained in it. note that since intervals are strictly an smfc 171$ intraprocedural structure, we only need the is_descendant predicate smfc 172$ for the intervals of the routine currently being analysed. since smfc 173$ is_desc is a multi-valued map, this will not cause any problems. smfc 174 smfc 175 $ iterate over the intervals of p in reverse preorder, i.e. inner- smfc 176 $ to-outer. note that we do not include the outer-most interval. smfc 177 smfc 178 (forall j in [ 1..#ints(p)-1 ]) smfc 179 i := ints(p)(j); $ i is the interval header smfc 180 is_desc{i} := { i } +/[ is_desc{b} : b in int_nodes(i) ]; smfc 181 is_called{i} := {} +/[ is_called{b} : b in int_nodes(i) ]; smfc 182 need_process +:= { b in int_nodes(i) | #vedges{b} > 1 }; smfc 183 smfc 184 $ find all routines which can be invoked from within this smfc 185 $ interval. when we copmute the transitive closure of the call smfc 186 $ graph, domain-restricted to the routines called within i, we smfc 187 $ can use the fact that we have already computed the closure for smfc 188 $ all routines in called within an interval contained in the smfc 189 $ current interval, i. smfc 190 smfc 191 workpile := {}; seen := is_called{i}; smfh 10 (forall c in callsin{p} | smfh 11 intof(c) = i and callproc(c) notin seen) smfc 193 workpile with:= callproc(c); smfh 12 end forall; smfh 13 smfc 195 (while workpile /= {}) smfc 196 q from workpile; seen with:= q; smfc 197 is_called{i} with:= q; smfc 198 (forall r in cgraph{q} | r notin seen) smfc 199 workpile with:= r; smfc 200 end forall; smfc 201 end while; smfc 202 smfc 203 $ next we determine for each expression whether it is constant smfc 204 $ within the current interval or not. racall that we do not smfc 205 $ include the outer-most interval. smfc 206 smfc 207 $ step 1: mark 'invariant' those instructions whose operands are smfc 208 $ all either constant or have all their reaching definitions smfc 209 $ outside the interval i. smfc 210 smfc 211 $ step 2: repeat step (3) until at some repetition no new smfc 212 $ instructions are marked 'invariant'. smfc 213 smfc 214 $ step 3: mark 'invariant' al those instructions not previously smfc 215 $ so marked whose operands all are either constant, have all smfc 216 $ their reaching definitions outside i, or have exactly one smfc 217 $ reaching definition, and that definition is an instruction in smfc 218 $ i marked invariant. smfc 219 smfc 220 $ note that we can merge steps (1) and (3) since the high-level smfc 221 $ implementation of invariants requires no separate pass to smfc 222 $ initialise the data structure properly. smfc 223 smfc 224 $ possibly we could speed up this step dramatically if after the smfc 225 $ first iteration over the code we would use a work pile and du smfc 226 $ links to only check instructions which might become invariant, smfc 227 $ rather than scan the entire code over again. however, we must smfc 228 $ be careful when we use ffrom links to make sure that we propa- smfc 229 $ gate correctly, or we must compute du and ud links. smfc 230 smfc 231 loop smfc 232 init invariants := {}; $ invariant computations in i smfc 233 inv_count := 0; $ number of code scans smfc 234 doing convrgd := true; $ flag indicating convergence smfc 235 inv_count +:= 1; smfc 236 until convrgd smfc 237 do smfc 238 (forall i1 in is_desc{i}, b in int_nodes(i1)) smfc 239 (for_inst(inst, b)) smfc 240 if inst in invariants then continue; end if; smfc 241 opc := opcode(inst); smfc 242 if opc notin ops_fold then continue; end if; smfc 243 if forall k in [ first_ivar(opc)..#args(inst) ] | smfc 244 is_const(args(inst)(k))=1 smfh 14 or args(inst)(k) = sym_om smfc 245 or # ud(get_oi(inst, k)) = 1 smfc 246 and instno(arb ud(get_oi(inst, k))) smfc 247 in invariants smfc 248$$-- the next sub-test would test that whenever there exists exactly smfc 249$$-- one preceding definition for a global in a routine within the smfc 250$$-- current region, then we assume that this definition is invariant. smfc 251$$-- this represents a save overestimate. smfc 252$$-- or # ud(get_oi(inst, k)) = 1 smfc 253$$-- and oi_rout(arb ud(get_oi(inst, k))) smfc 254$$-- /= p smfc 255$$-- and oi_rout(arb ud(get_oi(inst, k))) smfc 256$$-- in is_called{i} smfc 257 or forall vo in ud(get_oi(inst, k)) | smfc 258 oi_rout(vo) = p smfc 259 and intof(blockof(instno(vo))) smfc 260 notin is_desc{i} smfc 261 or oi_rout(vo) /= p smfc 262 and oi_rout(vo) notin is_called{i} smfc 263 then smfc 264 invariants with:= inst; smfc 265 convrgd := false; smfc 266 end if; smfc 267 end; $ end for_inst; smfc 268 end forall i1; smfc 269 end loop; smfc 270 smfc 271 if 'e' in dump_string then smfc 272 print( smfc 273 lpad(str i, 8), smfc 274 lpad(str(#(is_desc{i})), 8), smfc 275 lpad(str(#(is_called{i})), 8), smfc 276 lpad(str inv_count, 8), smfc 277 lpad(str(#(invariants)), 8), smfc 278 lpad(str(#(is_desc{i} * need_process with i)), 8) smfc 279 ); smfc 280 end if; smfc 281 smfc 282 $ step 4: flag all conditional branches which are determined by smfc 283 $ loop-invariant computations. in addition, flag the interval smfc 284 $ if all conditional branches are determined by loop-invariant smfc 285 $ computations. smfc 286 smfi 67 has_const := false; has_cond := false; l_messages := {}; smfc 288 smfc 289 $ iterate over the code smfc 290 (forall i1 in is_desc{i} | i1 = i or i1 in need_process) smfc 291 (forall b in int_nodes(i1)) smfc 292 (for_inst(inst, b)) smfc 293 smfc 294 $ look for conditional branches smfc 295 if opcode(inst) notin ops_goto then continue; end if; smfc 296 if opcode(inst) notin ops_ivar then continue; end if; smfg 84 if opcode(inst) = q1_bif then continue; end if; smfg 85 if opcode(inst) = q1_bifnot then continue; end if; smfc 297 smfc 298 $ this is a conditional branch smfe 21 if opcode(inst) = q1_case then smfe 22 vox := get_oi(inst, 2); smfe 23 else smfe 24 vox := get_oi(inst, 1); smfe 25 end if; smfh 15 smfh 16 if forall voy in ud(vox) | oi_op(voy) = q1_pos then smfh 17 continue; smfh 18 end if; smfh 19 smfe 26 voy := arb ud(vox); smfh 20 if is_const(oi_sym(vox))=1 or oi_sym(vox) = sym_om smfc 301 or # ud(vox) = 1 and instno(voy) in invariants smfc 302$$-- the next sub-test would test that whenever there exists exactly smfc 303$$-- one preceding definition for a global in a routine within the smfc 304$$-- current region, then we assume that this definition is invariant. smfc 305$$-- this represents a save overestimate. smfc 306$$-- or # ud(vox) = 1 smfc 307$$-- and oi_rout(voy) /= p smfc 308$$-- and oi_rout(voy) in is_called{i} smfc 309 or forall voy in ud(vox) | smfc 310 oi_rout(voy) = p smfc 311 and intof(blockof(instno(voy))) smfc 312 notin is_desc{i} smfc 313 or oi_rout(voy) /= p smfc 314 and oi_rout(voy) smfc 315 notin is_called{i} smfc 316 then smfi 68 l_messages{stmtof(inst)}{'i'} with:= smfc 318 [ 'expression controlling conditional branch ' smfc 319 + if is_called{i} = {} smfc 320 then 'is' smfc 321 else 'appears to be' smfc 322 end smfc 323 + ' loop-invariant' smfc 324 + if is_called{i} = {} smfc 325 then ' in the loop' smfc 326 else '' smfc 327 end, smfc 328 if is_called{i} = {} smfc 329 then '' smfc 330 else 'in the loop ' smfc 331 end smfc 332 + 'starting at statement ' smfc 333 + str(stmtof(first_inst(i))-sc_stmt_ct(p)+1) smfc 334 ]; smfc 335 has_const := true; $ i has constant branch smfc 336 else smfc 337 has_cond := true; $ i has conditional branch smfc 338 end if; smfc 339 end; $ end for_inst; smfc 340 end forall; smfc 341 end forall; smfc 342 smfc 343 if has_const then smfc 344 messages{stmtof(first_inst(i))} smfc 345 {if has_cond then 'i' else 'w' end} with:= smfc 346 [ 'this loop ' smfc 347 + if not has_cond and is_called{i} = {} smfc 348 then 'is' smfc 349 else 'could be' smfc 350 end smfc 351 + ' flow-constant: ' smfc 352 + if has_cond then 'some' else 'all' end smfc 353 + ' conditional branches ' smfc 354 + if is_called{i} = {} then smfc 355 if has_cond smfc 356 then 'are' smfc 357 else 'are controlled by' smfc 358 end smfc 359 else smfc 360 'appear to' smfc 361 end, smfc 362 if is_called{i} = {} then smfc 363 if has_cond then 'controlled by ' else '' end smfc 364 else smfc 365 'be controlled by ' smfc 366 end smfc 367 + 'loop-invariant expressions.' ]; smfi 69 smfi 70 if has_cond then messages +:= l_messages; end if; smfc 368 smfc 369 $ we might have to scan this interval again smfc 370 need_process with:= i; smfc 371 end if; smfc 372 smfc 373 end forall j; smfc 374 smfc 375 smfc 376 end procedure comp_region_constants; smfc 377 smfc 378 smfc 379 smfc 380 procedure ud_rout(oi); smfc 381$ smfc 382$ this procedure computes the transitive closure of bfrom for the smfc 383$ occurrence oi. smfc 384$ smfc 385 repr smfc 386 oi: occurrence; smfc 387 workoccs, seenoccs: set(occurrence); smfc 388 vox, voy: occurrence; smfc 389 end repr; smfc 390 smfc 391 workoccs := { oi }; $ workpile of preceding occurrences smfc 392 seenoccs := {}; $ occurrences already processed smfc 393 smfc 394 (while workoccs /= {}) smfc 395 vox from workoccs; seenoccs with:= vox; smfc 396 (forall voy in bfrom{vox}) smfc 397 if is_ovar(voy) or smfc 398 oi_op(voy) in ops_iter and argno(voy) = 2 then smfc 399 if ud_memo(oi) = om then smfc 400 ud_memo(oi) := { voy }; smfc 401 else smfc 402 ud_memo(oi) with:= voy; smfc 403 end if; smfc 404 else smfc 405 if ud_memo(voy) /= om then smfc 406 if ud_memo(oi) = om then smfc 407 ud_memo(oi) := ud_memo(voy); smfc 408 else smfc 409 ud_memo(oi) +:= ud_memo(voy); smfc 410 end if; smfc 411 elseif voy notin seenoccs then smfc 412 workoccs with:= voy; smfc 413 end if; smfc 414 end if; smfc 415 end forall; smfc 416 end while; smfl 3 smfl 4 if UD_MEMO(OI) = om then UD_MEMO(OI) := {}; end if; smfc 417 smfc 418 return ud_memo(oi); smfc 419 smfc 420 smfc 421 end procedure ud_rout; smfc 422 smfc 423 smfc 424 drop smfc 425 ud; smfc 426 smfc 427 smfc 428 end module setl_optimizer - region_constants; smfc 429 smfc 430 1 .=member dsol13 2 3 4 module setl_optimizer - dataflow_solver_syms; 5$ 6$ this module contains a package of general purpose routines to solve 7$ bit vector data flow problems either intraprocedurally or interpro- 8$ cedurally. we can distinguish between four basic types of such 9$ analyses, according to the character of the desired analysis: 10$ 11$ forward - data is to be propagated in the direction of the flow, 12$ from procedure entries forward. 13$ 14$ backward - data is to be propagated in the reverse direction of the 15$ flow, from exits backward. 16$ 17$ meet - whenever two paths converge (for forward analysis) or 18$ diverge (for backward analysis) take the meet (set inter- 19$ section) of data values propagated along these paths. 20$ 21$ join - as in meet, except that the join (set union) of the 22$ corresponding data values is to be taken. 23$ 24$ typical examples are: expression availability analysis is a 25$ forward - meet analysis, unconditional exposure of wxpressions (also 26$ known as 'very busy' expressions analysis) is a backward - meet ana- 27$ lysis; reaching definitions analysis is a forward - join analysis, 28$ and live variables analysis is a backward - join analysis. 29$ 30$ as noted in chapters 5 and 6, forward and backward analyses require 31$ substantially different logic, so that each of them is executed in a 32$ different subpackage; however, the difference between meet and join 33$ problems turns out to be rather minor, so that they both can be 34$ handled by the same (forward or backward) package, using a switch to 35$ indicate whether a particular analysis is of meet or join type. 36$ 37$ this module exports the following procedures: 38$ 39$ cgraph_analysis - call graph analysis routine, to be called once 40$ before solving any data flow problem interproce- 41$ durally. 42$ 43$ interproc_fwd_analysis - call this to solve interprocedural 44$ forward data flow analyses. 45$ 46$ intraproc_fwd_analysis - call this to solve intraprocedural 47$ forward data flow analysis for a given 48$ procedure. 49$ 50$ interproc_back_analysis - call this to solve interprocedural 51$ backward data flow analysis 52$ 53$ intraproc_back_analysis - performs intraprocedural backward 54$ analysis for a given procedure. 55$ 56$ this package assumes the following global objects to be 57$ available: 58$ 59$ cgraph - the program call graph, represented as a set 60$ of edges; an edge (p,q) is in cgraph iff p is a 61$ procedure which contains a call to the procedure q. 62$ 63$ routs - set of all program procedures (i.e. all nodes 64$ of the call graph). 65$ 66$ sym_main - main-program identifier (i.e. the entry node of the 67$ call graph). 68$ 69$ routof - maps each block to the procedure containing it. 70$ 71$ rentry - maps each procedure to its entry block. 72$ 73$ rexit - maps each procedure to its exit (return) block. 74$ 75$ rstop - maps each procedure to its stop block, if any. 76$ 77$ callsin - maps each procedure to the set of all call blocks 78$ in it. 79$ 80$ callproc - maps each call block to the procedure it calls. 81$ 82$ cessor - the program flow graph, as a union of the flow graphs of 83$ all procedures. an edge (m, n) is in cessor iff either m 84$ contains a branch to n, or else m is a call block and n 85$ is the block immediately following n. the nodes of the 86$ flow graph are either basic blocks or derived intervals 87$ (which are represented by their target blocks), in which 88$ case an edge (int, v) in cessor can indicate the possi- 89$ bility of a transfer of control from the interval int to 90$ a successor v of some node in int. these edges are 91$ called virtual edges (as above; see the interval analysis 92$ package for more details). 93$ 94$ pred - the inverse map of cessor. 95$ 96$ ints - maps each procedure to the tuple of its intervals 97$ in reverse preorder (relative to a depth first 98$ spanning tree of its flow graph). 99$ 100$ int_nodes - maps each interval to the sequence of its nodes 101$ in interval order (i.e., reverse postorder). 102$ 103$ proper_ints - the set of all proper intervals (those which do 104$ not contain irreducible nucleii). 105$ 106$ intof - maps each flow graph node to the interval containing 107$ it. 108$ 109$ vedges - set of all virtual edges (see the description of 110$ cessor above). 111$ 112$ in addition this module uses the following global-within- 113$ the-module variables, the first three of which are used 114$ to transmit flags and analysis constants between inner routines, 115$ while the rest are built by a recursive depth-first search 116$ procedure during call-graph analysis, and are used later in that 117$ analysis. 118$ 119 macro .comp; .comp_syms endm; 120 macro interproc_fwd_analysis; interproc_fwd_analysis_syms endm; 121 macro intraproc_fwd_analysis; intraproc_fwd_analysis_syms endm; 122 macro interproc_back_analysis; interproc_back_analysis_syms endm; 123 macro intraproc_back_analysis; intraproc_back_analysis_syms endm; 124 macro fom; fom_syms endm; 125 macro xom; xom_syms endm; 126 127 var 128 id, $ identity flow map 129 zero, $ null data state 130 meet_flag, $ true if meet analysis; otherwise false 131 seen, $ procedures already in dfst of cgraph 132 cnpre, $ current preorder index in dfst 133 cnpost, $ current postorder index in dfst 134 nodeno, $ preorder numbering map 135 postno, $ postorder numbering map 136 ndescs; $ no. of descendants map 137 138 139 repr 140 mode df_elmt: df_elmt_syms; 141 mode df_map: df_map_syms; 142 143 .meetjoin: operator(df_map, df_map) df_map; 144 .mjv: operator(df_elmt, df_elmt) df_elmt; 145 .of: operator(df_map, df_elmt) df_elmt; 146 147 cdfst: procedure(routine); 148 149 interproc_fwd_eliminate: procedure( 150 remote smap(df_edge) df_map ) 151 remote smap(df_node) df_map; 152 intraproc_fwd_eliminate: procedure( 153 routine, 154 remote smap(df_node) df_map, 155 remote smap(df_edge) df_map, 156 string ) 157 boolean; 158 propagate_exposed: procedure( 159 routine, 160 remote smap(df_edge) df_map, 161 remote smap(df_node) df_map, 162 remote mmap{df_node} df_elmt, 163 remote mmap{df_node} df_elmt, 164 remote mmap{df_node} df_elmt 165 ); 166 entry_info: procedure( 167 remote smap(df_edge) df_map, 168 remote smap(df_node) df_map, 169 boolean, 170 remote mmap{df_node} df_elmt ) 171 remote smap(routine) df_elmt; 172 fwd_propagate_in: procedure( 173 routine, 174 remote smap(df_edge) df_map, 175 remote smap(df_node) df_map, 176 remote smap(df_node) df_elmt, 177 df_elmt, 178 boolean, 179 remote mmap{df_node} df_elmt 180 ); 181 interproc_back_eliminate: 182 procedure(remote smap(df_edge) df_map) 183 remote smap(df_edge) df_map; 184 intraproc_back_eliminate: 185 procedure( 186 routine, 187 remote smap(df_edge) df_map, 188 remote smap(df_edge) df_map, 189 remote smap(routine) df_map, 190 * ) $$-- flow_flag 191 boolean; 192 intra_aux_eliminate: procedure( 193 routine, 194 remote smap(df_edge) df_map, 195 remote smap(df_edge) df_map, 196 remote smap(df_node) df_map 197 ); 198 exit_info: procedure( 199 remote smap(df_edge) df_map, 200 remote smap(df_edge) df_map, 201 remote smap(df_node) df_map ) 202 remote smap(routine) df_elmt; 203 back_propagate_in: procedure( 204 routine, 205 remote smap(df_node) df_map, 206 remote smap(df_node) df_elmt, 207 df_elmt 208 ); 209 210 $ data structures for variables global to this module 211 id: df_map; 212 zero: df_elmt; 213 meet_flag: boolean; 214 cnpre: integer; 215 cnpost: integer; 216 nodeno: remote smap(routine) integer; 217 postno: remote smap(routine) integer; 218 ndescs: remote smap(routine) integer; 219 seen: remote set(routine); 220 end repr; 221 222 1 .=member cga13a 2 3 procedure cgraph_analysis; 4$ 5$ this procedure performs the call graph analysis needed for 6$ our interprocedural data flow analysis solver. it computes 7$ the following objects: 8$ 9$ cg_sccs - a tuple of (roots of the) strongly connected 10$ components of cgraph, arranged in reverse postorder. 11$ 12$ scc_nodes - maps each (root of a) strongly connected component 13$ into a tuple containing its nodes in reverse 14$ postorder. 15$ 16$ scc_d - maps each (root of a) strongly connected component 17$ s into an estimate of its loop-interconnectedness 18$ parameter d, defined as the maximal number of back 19$ edges along any acyclic path in s (we do not attempt to 20$ obtain that precise value, but rather use a crude 21$ upper bound for it, namely the number of back 22$ edge targets contained in s.) 23$ 24 repr 25 inverse: remote mmap{routine} 26 remote set(routine); 27 p, q, r: routine; 28 invpostnodes: smap(integer) routine; 29 n: integer; 30 backinv: remote mmap{routine} 31 remote set(routine); 32 targback: remote set(routine); 33 junk: remote set(routine); 34 sccroot: remote smap(routine) routine; 35 i: integer; 36 newnodes: remote set(routine); 37 tcl: remote mmap{routine} 38 remote set(routine); 39 tcl_p, new_p, delta: remote set(routine); 41 end repr; 42$ 43$ begin by calling a standard depth first spanning tree 44$ routine, which will compute the following objects: 45$ 46$ nodeno - preorder node numbering map. 47$ postno - postorder node numbering map. 48$ ndescs - number of descendants map. 49$ 50 51 title('cims.setl.' + prog_level + ' - call graph analysis'); 52 printa(term_file, ' - call graph analysis'); 55 56 $ initialize the globals for depth-first spanning tree routine 57 nodeno := {}; postno := {}; ndescs := {}; 58 seen := {}; cnpre := 0; cnpost := 0; 59 60 cdfst(sym_main); $ build the call graph for the main program 61 62 if exists p in routs | p notin seen then $ disconnected call graph 63 (forall p in routs | p notin seen) 64 ermsg(name(p)+' cannot be reached from the main program'); 65 end forall; 66 67 abort('disconnected call graph'); 68 end if; 69 70 $ delete the globals we are done with 71 seen := om; cnpre := om; cnpost := om; 72 73 $ tree-descendancy macro, identical to the one used for interval 74 $ analysis. 75 macro is_desc(p, q); $ test whether p is a descendant of q 76 (nodeno(p) >= nodeno(q) and nodeno(p) <= nodeno(q)+ndescs(q)) 77 endm; 78$ 79$ next compute some auxiliary objects: 80$ 81 inverse := { [ p, q ] : [ q, p ] in cgraph }; $ inverse call graph 82 invpostnodes := { [ #routs+1-n, p ] : n = postno(p) }; 83 $ procedures in their reverse postorder 84 backinv := { [ p, q ] in inverse | is_desc(q, p) }; 85 $ set of all inverse back edges 86 targback := domain backinv; $ back edge targets 87 88 $ initialize globals (rf. above) 89 cg_sccs := []; scc_nodes := {}; scc_d := {}; 90 91 sccroot := {}; $ strongly connected component root map 92$ 93$ iterate through the procedures, looking for strongly 94$ connected components. 95$ 96 (forall i in [ 1..#invpostnodes ]) 97 p := invpostnodes(i); 98 if sccroot(p) = om then $ a new root of a s.c.c. 99 sccroot(p) := p; 100 cg_sccs with:= p; $ p corresponds to a new component 101 scc_nodes(p) := [ p ]; 102 if p in targback then $ a non-trivial s.c.c. 103 newnodes := backinv{p} less p; $ new nodes in s.c.c. 104 scc_d(p) := 1; $ no. of backedge targets in s.c.c. 105 (while newnodes /= {}) 106 q from newnodes; 107 sccroot(q) := p; $ mark q belongs to s.c.c. 108 if q in targback then scc_d(p) +:= 1; end if; 109 newnodes +:= 110 { r in inverse{q} | 111 is_desc(r, p) and sccroot(r) = om }; 112 end while; 113 114 else $ a trivial s.c.c. 115 scc_d(p) := 0; 116 end if; 117 118 else $ p belongs to a scc already scanned 119 scc_nodes(sccroot(p)) with:= p; 120 end if; 121 end forall; 122$ 123$ determine which routines are recursive: this is done by computing the 124$ transitive closure of the call cgraph. 125$ 126 tcl := {}; 127 (forall p in routs) 128 tcl_p := new_p := cgraph{p}; 129 (while new_p /= {}) 130 q from new_p; 131 delta := cgraph{q} - tcl_p; 132 new_p +:= delta; 133 tcl_p +:= delta; 134 end while; 135 tcl{p} := tcl_p; 136 end forall; 137 138 (forall p in routs) 139 if p in tcl{p} then is_rec(p) := 1; else is_rec(p) := om; end; 140 end forall; 141 142 $ delete the static variables global to the module 143 nodeno := om; postno := om; ndescs := om; 148 149 150 end procedure cgraph_analysis; 151 152 153 154 155 procedure cdfst(p); 156$ 157$ this routine builds the depth first spanning tree starting with 158$ node 'p'. this routine differs in various details from the depth 159$ first spanning routine used for interval analysis. 160$ 161 repr 162 q: routine; 163 end repr; 164 165 nodeno(p) := (cnpre +:= 1); 166 ndescs(p) := 0; 167 168 seen with:= p; 169 170 (forall q in cgraph{p} | q notin seen) 171 cdfst(q); 172 ndescs(p) +:= (ndescs(q) + 1); 173 end forall; 174 175 postno(p) := (cnpost +:= 1); 176 177 end procedure cdfst; 178 179 1 .=member efa13b 2 3 4 procedure interproc_fwd_analysis(rw f, wr soln, id_prm, zero_prm, 5 meet_flag_prm, move_code, 6 rw exposed, wr insert, safe); 7$ 8$ note declarations of 'read-write' parameters ('rw') and 'write-only' 9$ parameters ('wr'). 10$ 11$ this is the master routine to perform a specific data flow 12$ analysis interprocedurally. its parameters are: 13$ 14$ f - maps each edge (m, n) in the flow graph to a compact 15$ representation of its data-propagation map f(m,n). 16$ initially this information has to be provided only for 17$ basic blocks (but not for call blocks); the first phase 18$ of the analysis will fill in the additional entries. 19$ each f(m,n) is represented as a pair [a, b] in l x l, 20$ such that for each x in l, f(m,n)(x) = x*a + b, and 21$ a contains b (this latter condition ensures that the 22$ representation is unique, and also simplifies some 23$ functional manipulations). 24$ 25$ soln- the solution vector for the analysis. soln maps each 26$ flow graph node to the data found to be known at its 27$ entry. 28$ 29$ the next three parameters are transmitted internally between 30$ subprocedures by assigning them to global variables, as they 31$ are constant per analysis. the corresponding globals are: 32$ 33$ id - the identity map representation. id = [u, {}], where 34$ u is the universal set over which bitvectors are taken 35$ in this analysis (e.g. set of all program expressions, 36$ set of all variables etc.) 37$ 38$ zero - the initial data value, i.e. flow data assumed at the main 39$ program entry. 40$ 41$ meet_flag - a flag indicating whether the analysis is a meet 42$ analysis or a join analysis. 43$ 44$ aux_f - these are auxiliary propagation maps. for each flow 45$ graph node u, aux_f(u) denotes the effect of propagation 46$ from the entry to i, the interval containing u, through 47$ i, to the entry of u. 48$ 49$ move_code - a flag indicating that code motion is required. 50$ 51$ exposed - this is initially the set of computations (corresponding 52$ to analysis elements (bits)) exposed at the start of each 53$ basic block n (i.e. computed with no prior kill in n). the 54$ inner-to-outer phase of our analysis attaches an 'exposed' 55$ value to each interval processed. exposed{i} is the set of all 56$ expressions t for which there exists a computation of t 57$ within the interval i which would become redundant if and 58$ only if t became available at the entry to (the target block 59$ of) i. note, however, that the logical place at which 60$ computations movable out of an interval i should be inserted 61$ is the end of the target block of i, rather than its start. 62$ thus if that target block is nonempty then exposed{i} 63$ need not represent those movable computations. for this 64$ reason we provide the parameter 'insert' which gives the 65$ desired set of movable code. 66$ 67$ insert - this output parameter will map each interval into 68$ the set of all computations movable out of its loop, 69$ which are to be inserted at the end of the target 70$ block of the interval. the actual insertion should be 71$ performed by the calling procedure. 72$ 73$ our analysis procedures makes frequent use of the following 74$ operators (which could be also written as macros, if it were 75$ not for the convenience of the infix notation that we prefer 76$ to use): 77$ 78$ .comp - functional composition 79$ .meetjoin - functional meet or join, depending on meet_flag 80$ .mjv - meet or join of lattice values 81$ .of - functional application 82$ 83$ all these operators have elementary set expressions; see below 84$ for details. 85$ 86$ note also that these operators must be prepared to handle 87$ undefined flow values, which will be represented 88$ by a special constant 'fom'; for example, 89$ g .comp fom = fom .comp g = fom; 90$ (concatenation of an undefined flow with a defined 91$ one is still undefined) 92$ g .meetjoin fom = fom .meetjoin g = g. 93$ (a join or a meet of an undefined flow with a defined 94$ flow yields the defined flow.) 95$ 96$ another special constant 'xom' is used to denote the undefined data 97$ state in l. 98$ 99 repr 100 $ data structures for parameters 101 f: remote smap(df_edge) df_map; 102 soln: remote smap(df_node) df_elmt; 103 id_prm: df_map; 104 zero_prm: df_elmt; 105 meet_flag_prm: boolean; 106 move_code: boolean; 107 exposed: remote mmap{df_node} df_elmt; 108 insert: remote mmap{df_node} df_elmt; 109 safe: remote mmap{df_node} df_elmt; 110 111 $ data structures for local variables 112 aux_f: remote smap(df_node) df_map; 113 ent_inf: remote smap(routine) df_elmt; 114 p: routine; 115 end repr; 116$ 117$ transfer constant parameters to globals 118$ 119 id := id_prm; 120 zero := zero_prm; 121 meet_flag := meet_flag_prm; 122$ 123$ the master procedure consists of the following three phases: 124$ 125$ 1. interprocedural elimination phase 126$ 127 aux_f := interproc_fwd_eliminate(f); 128$ 129$ if code motion is required then perform an additional 130$ phase, computing the sets of movable code. 131$ 132 if move_code then 133 insert := {}; 134 (forall p in routs) 135 propagate_exposed(p, f, aux_f, exposed, insert, safe); 136 end forall; 137 end if; 138$ 139$ 2. find data at procedure entries 140$ 141 ent_inf := entry_info(f, aux_f, move_code, insert); 142$ 143$ 3. final propagation phase 144$ 145 soln := {}; $ initialize the solution 146 (forall p in routs) 147 fwd_propagate_in(p, f, aux_f, soln, ent_inf(p), 148 move_code, insert); 149 end forall; 150 151 end procedure interproc_fwd_analysis; 152 153 1 .=member efe13c 2 3 4 procedure interproc_fwd_eliminate(rw f); 5$ 6$ this is the driver routine for the first interprocedural 7$ inner-to-outer interval pass. procedures are analyzed in 8$ the following order: we process the strongly connected 9$ components of the call graph in their postorder; for each 10$ such component, we iterate through its procedures in their 11$ postorder, no more than 2*d+1 times, where d is the loop- 12$ interconnectedness parameter of the component. 13$ 14 repr 15 $ data structures for parameters 16 f: remote smap(df_edge) df_map; 17 18 $ data structures for local variables 19 aux_f: remote smap(df_node) df_map; 20 i: integer; 21 scc: routine; 22 scc_procs: tuple(routine); 23 flow_flag: string; 24 j: integer; 25 proc_converge: boolean; 26 k: integer; 27 p: routine; 28 end repr; 29 30 31 aux_f := {}; $ initialize auxiliary maps 32 33 $ iterate through the s.c.c.'s of cgraph 34 (forall i in [ #cg_sccs, #cg_sccs-1..1 ]) 35 36 scc := cg_sccs(i); $ get a s.c.c. 37 scc_procs := scc_nodes(scc); $ procedures in that s.c.c. 38 flow_flag := 'first_inter'; $ first processing of the s.c.c. 39 40 (forall j in [ 1..2*scc_d(scc)+1 ]) 41 proc_converge := true; 42 43 (forall k in [ #scc_procs, #scc_procs-1..1 ]) 44 p := scc_procs(k); 45 proc_converge := 46 intraproc_fwd_eliminate(p, aux_f, f, flow_flag) 47 and proc_converge; 48$ the intraproc_fwd_eliminate routine analyzes p; its fourth parameter 49$ indicates whether the analysis is first-time interprocedural, second 50$ -time interprocedural or intraprocedural; it returns a flag to 51$ indicate whether information in p has stabilized. 52 end forall k; 53 54 flow_flag := 'second_inter'; $ additional passes thru scc 55 56 if proc_converge then quit forall j; end; 57 end forall j; 58 end forall i; 59 60 return aux_f; 61 62 end procedure interproc_fwd_eliminate; 63 64 1 .=member afe13d 2 3 4 procedure intraproc_fwd_eliminate(p, rw aux_f, rw f, flow_flag); 5$ 6$ this routine performs an intraprocedural elimination phase 7$ for the procedure p, using interval analysis. the fourth parameter 8$ indicates whether this routine has been invoked by the 9$ intraprocedural solver or by the interprocedural solver, and 10$ in the second case, whether this is the first time p is 11$ being processed or not. 12$ 13$ in this pass we iterate through the procedure's intervals 14$ in an inner-to -outer order (i.e. in reverse preorder of their 15$ heads in a dfst of the flow graph of p). for each interval 16$ i processed in this manner we compute a set of data-propagation 17$ maps of the form f(i, u), where 18$ 19$ (1) if u is in i, then this map is an auxiliary map (which will 20$ be denoted as aux_f(u), i being implicit in this case) which 21$ represents the propagation effect as control advances from 22$ the start of i, thru i, to the start of u; 23$ 24$ (2) if u is not in i, then u is a successor of some node in i. 25$ here the map f(i, u) represents the propagation effect as control 26$ advances from the start of i, through i, to the start of u; 27$ in this case f(i, u) is needed for the processing of the 28$ intervals containing i. note that [i, u] is a virtual edge 29$ in our flow graph; thus the elimination phase extends the 30$ map f so as to be defined also on virtual edges. 31$ 32$ any interval i processed in this routine is either a proper 33$ strongly connected interval, or, if it contains 'improper' 34$ nodes (i.e. nucleii of irreducibility), is a single-entry 35$ strongly connected subgraph. in the first case we only have to 36$ iterate thru the nodes of i twice, but in the second case till 37$ convergence. 38$ 39$ the outermost 'interval' is either a single entry acyclic 40$ graph (if it does not contain irreducible nucleii), or a 41$ general single-entry graph otherwise. for this 'interval' we 42$ iterate either once in the first case, or till convergence 43$ otherwise. 44$ 45$ if the present routine is to be used for interprocedural analysis, 46$ we first reset the propagation maps for call blocks in p. if none of 47$ these maps have changed from the last processing of p, 48$ then obviously analysis of p has stabilized and we can return 49$ immediately. moreover, intervals need be re-processed if and only 50$ if they contain a call block whose local effect has changed, 51$ or, recursively, contain an interval whose local effects 52$ have changed. in terms of the 'intof' tree, we only have to 53$ re-analyze intervals lying along some path from the 54$ root to a call block whose local effect has changed. this 55$ can make reprocessing of a procedure considerably 56$ faster than initial processing. 57$ 58 repr 59 $ data structures for parameters 60 p: routine; 61 aux_f: remote smap(df_node) df_map; 62 f: remote smap(df_edge) df_map; 63 flow_flag: string; 64 65 $ data structures for local variables 66 need_process: set(df_node); 67 intt: df_node; 68 c: df_node; 69 v: df_node; 70 p1: routine; 71 ep1: df_node; 72 p_ints: tuple(df_node); 73 outint: df_node; 74 k: integer; 75 nodes: tuple(df_node); 76 head: df_node; 77 conv_control: boolean; 78 n_iter: integer; 79 j: integer; 80 d: integer; 81 convrgd: boolean; 82 nd: df_node; 83 ftemp: df_map; 84 pnd: df_node; 85 pv: df_node; 86 end repr; 87 88 if flow_flag = 'second_inter' then 89 $ process only intervals containing calls with new effect 90 need_process := {}; 91 else 92 $ process all intervals 93 need_process := { intt : intt in ints(p) }; 94 end if; 95 96 if flow_flag /= 'intra' then 97 $ interprocedural analysis 98 (forall c in callsin{p}) 99 v := cessor(c); $ the block following the call 100 p1 := callproc(c); $ c calls p1 101 ep1 := rexit(p1); $ the return block of p1 102 103$ (note here that if this routine is modified to include parameter- 104$ passing assignments as part of call blocks, in the manner suggested 105$ in a concluding remark in section 4, then one might manipulate 106$ aux_f(ep1), which defines the local effect of executing p1, to get 107$ f(c,v), rather than just assign the first map to the second one, as 108$ is done below). 109 110 if f([c, v]) /= aux_f(ep1) then 111 112 $ update flow function for call 113 f([c, v]) := aux_f(ep1) ? fom; 114 115 $ interval containing call must be processed 116 need_process with:= intof(c); 117 end if; 118 end forall c; 119 120 $ if no intervals need be processed then information has 121 $ stabilized and no re-processing of p need be done. 122 if need_process = {} then return true; end if; 123 end if; 124 125 p_ints := ints(p); $ intervals of p in reverse preorder 126 outint := p_ints(#p_ints); $ outermost interval 127 128 (forall intt = p_ints(k) | intt in need_process) 129 need_process with:= intof(intt); $ process containing interval 130 nodes := int_nodes(intt); $ nodes of intt in interval order 131 132 head := nodes(1); $ interval head 133 aux_f(head) := id; $ initialize to the identity 134$ 135$ note here that the edge [intt, head] is a real edge in the 136$ flow graph, so that f([intt, head]) will have been pre-computed in 137$ an initialization phase, along with the flow maps for all other 138$ real edges, and is therefore available here. 139$ 140$ three cases are now possible: 141$ 142$ (1) intt is proper, but not outermost; then iterate twice. 143$ (2) intt is proper, and is outermost; then iterate once. 144$ (3) intt is improper; iterate indefinitely (1 + number of 145$ nodes is an adequate upper bound) until convergence. 146$ (note that we do not make use of the better upper bound on 147$ the number of iterations discussed in section 3). 148$ 149 conv_control := intt notin proper_ints; 150 $ test for convergence only in this case 151 152 n_iter := $ maximal number of iterations 153 if intt notin proper_ints then #nodes + 1 154 elseif intt = outint then 1 155 else 2 156 end; 157 158$ if improper interval, initialize aux_f of all non-head nodes 159$ to 'fom'. this is because we cannot guarantee in this case that 160$ when propagating data to a node within intt, all its predecessors 161$ (within intt) have already been processed, so that we have to 162$ prepare for the case where some of these predecessors still 163$ have undefined auxiliary data-flow maps. 164 if conv_control then 165 (forall j in [ 2..#nodes ]) 166 aux_f(nodes(j)) := fom; 167 end forall; 168 end if; 169 170 $ iterate through the nodes of intt 171 172 (forall d in [ 1..n_iter ]) 173 174 convrgd := conv_control; 175 176 $ iterate thrugh the nodes of intt, other than head 177 (forall j in [ 2..#nodes ]) 178 179 nd := nodes(j); 180 ftemp := fom; 181 (forall pnd in pred{nd} | intof(pnd) = intt) 182 ftemp .meetjoin:= 183 (f([pnd,nd]) .comp aux_f(pnd)); 184 end forall; 185 convrgd and:= (ftemp = aux_f(nd)); 186 aux_f(nd) := ftemp; 187 188 end forall j; 189 190 $ test if processing of intt has terminated 191 if d = n_iter or convrgd then quit forall d; end if; 192 193 $ re-compute aux_f(head), taking back edges into account 194 ftemp := fom; 195 (forall pnd in pred{head} | intof(pnd) = intt) 196 ftemp .meetjoin:= (f([pnd,head]) .comp aux_f(pnd)); 197 end forall; 198 ftemp .meetjoin:= aux_f(head); 199 200 if not conv_control then 201 convrgd := aux_f(head) = ftemp; 202 end if; 203 204 aux_f(head) := ftemp; 205 if convrgd then quit forall d; end if; 206 end forall d; 207 208$ 209$ compute f([intt, v]), where v is a successor of some node in 210$ intt; note that this loop will be null for the 211$ outermost interval. 212$ 213 (forall v in vedges{intt}) 214 ftemp := fom; 215 (forall pv in pred{v} | intof(pv) = intt) 216 ftemp .meetjoin:= (f([pv,v]) .comp aux_f(pv)); 217 end forall; 218 f([intt, v]) := ftemp .comp f([intt, head]); 219 220 end forall v; 221 end forall intt; 222 223 return false; $ to indicate no convergence 224 225 end procedure intraproc_fwd_eliminate; 226 227 1 .=member pex13e 2 3 4 procedure propagate_exposed(p, rw f, aux_f, rw exposed, rw insert, 5 safe); 6$ 7$ this procedure performs an inner-to-outer pass over all 8$ intervals to determine the computations which might be moved 9$ out of the loop of each interval i. as explained above, 10$ these computations are not necessarily those exposed in i; 11$ hence, we build up both sets 'exposed' and 'insert' 12$ simultaneously. 13$ 14$ in this analysis, the set of computations movable out of the 15$ loop of i is obtained by taking all computations t with 16$ the property that there exists a node nd in i such that 17$ t is exposed in nd and is available at the start of nd iff 18$ it is available at the end of the target block of i. 19$ 20$ the movable code is always assumed to be appended to the 21$ end of the target block of the interval, to avoid any possible 22$ conflict with code that is already present in the target block. 23$ however, this appending takes place physically only at the end 24$ of the elimination phase. thus, we do not attempt to make 25$ use of the fact that these expressions are potentially 26$ available at the head of i in updating any flow function. 27$ this approach is necessary to ensure convergence of our algorithms 28$ in cases of recursive cycles of interprocedural flow. 29$ 30 repr 31 $ data structures for parameters 32 p: routine; 33 f: remote smap(df_edge) df_map; 34 aux_f: remote smap(df_node) df_map; 35 exposed: remote mmap{df_node} df_elmt; 36 insert: remote mmap{df_node} df_elmt; 37 safe: remote mmap{df_node} df_elmt; 38 39 $ data structures for local variables 40 p_ints: tuple(df_node); 41 outint: df_node; 42 intt: df_node; 43 k: integer; 44 nodes: tuple(df_node); 45 head: df_node; 46 itemp: df_elmt; 47 nd: df_node; 48 ftarg: df_map; 49 expfromentry: df_elmt; 50 end repr; 51 52 p_ints := ints(p); $ intervals of p in reverse preorder 53$ 54$ first extend f to indicate null flow from the entry block to 55$ itself. since the outermost interval has no target block, 56$ and is therefore identified with its head, this trick unifies 57$ the treatment of that interval with the treatment of inner 58$ intervals, as shown below. 59$ 60 outint := p_ints(#p_ints); 61 f([outint, outint]) := id; 62 63 (forall intt = p_ints(k)) 64 nodes := int_nodes(intt); 65 head := nodes(1); 66$ 67$ in computing exposed{intt}, we must reckon with the fact 68$ that the target block of intt (also denoted by intt) 69$ might be non-empty, due to prior code motion. this can mean that 70$ (a) f([intt, head]) is not the identity, and (b) exposed{intt} 71$ (where intt is treated as a basic block) is not null 72$ initially. 73$ 74 $ we proceed as follows: first find all exposed computations 75 $ in the loop of intt, assuming the target block of intt to 76 $ be null. these are the computations movable out of the loop 77 $ of intt. 78 itemp := {}; 79 (forall nd in nodes) 80 itemp +:= (exposed{nd} * (aux_f(nd)(1) - aux_f(nd)(2))); 81 end forall; 82 if safe /= om then itemp := itemp * safe{intt}; end if; 83 insert{intt} := itemp; 84 85 $ next find the new set of computations which are still 86 $ exposed at the entry to the target block of intt. 87 ftarg := f([intt, head]); 88 expfromentry := insert{intt} * (ftarg(1) - ftarg(2)); 89 90 $ add these computations to those exposed in the target block 91 exposed{intt} := exposed{intt} + expfromentry; 92 93 end forall intt; 94 95 96 end procedure propagate_exposed; 97 98 99 100 101 procedure entry_info(f, aux_f, move_code, insert); 102$ 103$ this function calculates and returns a mapping which sends 104$ each procedure p into the flow information available at entry 105$ to p. it is called (only in the interprocedural case) just 106$ before we begin the final outer-to-inner propagation phase. 107$ 108 repr 109 $ data structures for parameters 110 f: remote smap(df_edge) df_map; 111 aux_f: remote smap(df_node) df_map; 112 move_code: boolean; 113 insert: remote mmap{df_node} df_elmt; 114 115 $ data structures for local variables 116 cgf: smap( tuple(routine, routine) ) df_map; 117 p, q: routine; 118 c: df_node; 119 ftemp: df_map; 120 iu: df_node; 121 hiu: df_node; 122 fins: df_map; 123 ent_inf: remote smap(routine) df_elmt; 124 cgrinv: mmap(routine) routine; 125 i: integer; 126 scc: routine; 127 scc_procs: tuple(routine); 128 n: integer; 129 convrgd: boolean; 130 k: integer; 131 temp: df_elmt; 132 end repr; 133$ 134$ first we construct a map 'cgf' assigning to each edge (p, q) 135$ of the call graph a data-propagation map, describing the 136$ propagation effect as control advances from the entry of p 137$ to the entry of q via any call to q from p. 138$ 139 cgf := {}; 140 (forall [p,q] in cgraph) cgf([p,q]) := fom; end; 141 142 (forall q = callproc(c)) $ for all calls within all procedures 143 144 p := routof(c); $ [p, q] is an edge of the call graph 145 146 $ compute the local effect as control advances from the entry 147 $ of p to c. 148 ftemp := aux_f(c); 149 150 (init iu := intof(c); while iu /= rentry(p)) 151 152 hiu := int_nodes(iu)(1); $ head of iu 153 fins := id; 154 $ add also the effect of code moved out of iu 155 if move_code then fins(2) := insert{iu}; end; 156 ftemp := ftemp .comp fins .comp f([iu, hiu]) 157 .comp aux_f(iu); 158 159 iu := intof(iu); 160 161 end; 162 163 cgf([p, q]) := cgf([p, q]) .meetjoin ftemp; 164 165 end forall q; 166$ 167$ next we iterate through the call graph in 'invocation order', i.e. 168$ process the strongly connected components in reverse postorder and 169$ the set of procedures within each strongly connected component in 170$ reverse postorder also. 171$ 172 ent_inf := {[p, xom] : p in routs}; $ initialize solution 173 ent_inf(sym_main) := zero; 174 cgrinv := {[p, q] : [q, p] in cgraph}; 175 176 $ pick strongly-connected components in reverse postorder 177 (forall i in [ 2..#cg_sccs ]) 178 179 $ nb. here we assume that the main program is non-recursive, so 180 $ that the first strongly-connected component of the call graph 181 $ consists of the main program only. thus we can skip it, for 182 $ the entry value of the main program is already assumed known. 183 184 scc := cg_sccs(i); 185 scc_procs := scc_nodes(scc); $ procs in scc in rev. postorder 186 187 (forall n in [ 1..scc_d(scc)+1 ] ) 188 189 convrgd := true; 190 191 (forall p = scc_procs(k)) 192 193 temp := xom; 194 (forall q in cgrinv{p}) 195 temp .mjv:= (cgf([q,p]) .of ent_inf(q)); 196 end forall; 197 198 $ test for convergence 199 convrgd and:= (temp = ent_inf(p)); 200 201 ent_inf(p) := temp; 202 end forall p; 203 204 if convrgd then quit forall n; end if; 205 end forall n; 206 end forall i; 207 208 return ent_inf; 209 210 end procedure entry_info; 211 212 1 .=member fpi13f 2 3 4 procedure fwd_propagate_in(p, rw f, aux_f, rw soln, ent_val, 5 move_code, rw insert); 6$ 7$ this procedure performs outer-to-inner propagation for a 8$ routine p, using the 'interval-effect' flow functions aux_f 9$ to modify the solution map 'soln'. the parameter ent_val 10$ gives the flow information assumed (or known) at procedure 11$ entry. 12$ 13$ if code motion is required, then the computations in insert{i} 14$ are assumed to be available at the end of the target block 15$ of an interval i (but only for the purpose of propagation 16$ inside i). in addition, computations in insert{i} already 17$ available at exit from the target block of i are removed from 18$ insert{i}. 19$ 20$ note that movable computations are assumed to be such that the 21$ insertion of any of them will not 'kill' any others. 22$ 23 repr 24 $ data structures for parameters 25 p: routine; 26 f: remote smap(df_edge) df_map; 27 aux_f: remote smap(df_node) df_map; 28 soln: remote smap(df_node) df_elmt; 29 ent_val: df_elmt; 30 move_code: boolean; 31 insert: remote mmap{df_node} df_elmt; 32 33 $ data structures for local variables 34 p_ints: tuple(df_node); 35 outint: df_node; 36 k: integer; 37 intt: df_node; 38 nodes: tuple(df_node); 39 soln1: df_elmt; 40 u: df_node; 41 end repr; 42 43 soln(rentry(p)) := ent_val; 44 p_ints := ints(p); $ intervals of p in reverse preorder 45$ 46$ extend f to indicate null flow from the entry block to 47$ itself. since the outermost interval has no target block, 48$ and is therefore identified with its head, this trick unifies 49$ the treatment of that interval with the treatment of inner 50$ intervals, as shown below. 51$ 52 outint := p_ints(#p_ints); 53 f([outint, outint]) := id; 54 55 (forall k in [ #p_ints, #p_ints-1..1 ]) 56 57 intt := p_ints(k); 58 nodes := int_nodes(intt); $ nodes of intt 59 60 soln1 := soln(intt); $ data value at entry to intt 61 62 $ convert soln1 to the data attribute value at the end of the 63 $ target block of intt. 64 $ propagate through the target block of intt; if 65 $ intt = outint, the trick noted above will make the following 66 $ statement a no-op. 67 soln1 := f([intt, nodes(1)]) .of soln1; 68 69 $ if code motion is also required, then update insert{intt} 70 $ and add it to soln1. 71 if move_code and intt /= outint then 72 insert{intt} := insert{intt} - soln1; 73 soln1 := soln1 + insert{intt}; 74 end if; 75 76 $ now propagate attributes to the nodes of intt 77 (forall u in nodes) 78 soln(u) := aux_f(u) .of soln1; 79 end forall u; 80 81 end forall; 82 83 84 end procedure fwd_propagate_in; 85 86 1 .=member afa13g 2 3 4 procedure intraproc_fwd_analysis(p, rw f, wr soln, id_prm, zero_prm, 5 meet_flag_prm, move_code, 6 rw exposed, wr insert, safe); 7$ 8$ this is the master routine to perform a specific data flow 9$ analysis intraprocedurally for a given routine p, within which 10$ local variables are analyzed. 11$ 12$ for more details and comments and description of parameters see the 13$ corresponding interprocedural analyser. 14$ 15 repr 16 $ data structures for parameters 17 p: routine; 18 f: remote smap(df_edge) df_map; 19 soln: remote smap(df_node) df_elmt; 20 id_prm: df_map; 21 zero_prm: df_elmt; 22 meet_flag_parm: boolean; 23 move_code: boolean; 24 exposed: remote mmap{df_node} df_elmt; 25 insert: remote mmap{df_node} df_elmt; 26 safe: remote mmap{df_node} df_elmt; 27 28 $ data structures for local variables 29 aux_f: remote smap(df_node) df_map; 31 end repr; 32 33 id := id_prm; 34 meet_flag := meet_flag_prm; 35 36 aux_f := {}; 37 38 intraproc_fwd_eliminate(p,aux_f,f,'intra'); 41 42 if move_code then 43 insert := {}; 44 propagate_exposed(p, f, aux_f, exposed, insert, safe); 45 end if; 46 47 soln := {}; 48 fwd_propagate_in(p, f, aux_f, soln, zero_prm, move_code, insert); 49 50 end procedure intraproc_fwd_analysis; 51 52 53 54 55 procedure interproc_back_analysis(rw f, wr soln, id_prm, zero_prm, 56 meet_flag_prm); 57$ 58$ this is the master routine for performing a specific interprocedural 59$ backward data flow analysis. see the corresponding forward routine 60$ for general comments and a description of parameters. here we comment 61$ only on differences between the forward and backward algorithms, which 62$ are as follows: 63$ 64$ a. functional composition must be computed in reverse order. 65$ 66$ b. the auxiliary maps used in backward analysis are defined as 67$ follows: let i be an interval, u a node in i and v a node outside 68$ i which is a successor of a node in i. then aux_f([u, v]) is 69$ defined to be the propagation effect experienced as control 70$ advances from the start of u, through i, to the start of v. 71$ 72$ to compute this map requires iterating through i in reverse 73$ interval order three times (if i is proper) or till convergence 74$ otherwise. 75$ 76$ since the outermost interval of a procedure p has no successors, 77$ we regard the blocks rexit(p) and rstop(p) as its successors, 78$ 'hidden' inside that interval. this is needed to enable us to 79$ record the effect of the flow through the outermost interval in 80$ a manner similar to that used for inner intervals. 81$ 82$ c. in backward analysis we perform an extra step after the 83$ elimination phase. in this step we compute an additional set 84$ 'fexit' of auxiliary maps. for each node u in p, fexit(u) 85$ represents the propagation effect of the flow from the start 86$ of u to the return block of p, combined with that of flow from 87$ the start of u to the stop block of p. 88$ 89$ d. in our backward analysis code motion issues are completely 90$ ignored. 91$ 92$ e. the technical problem concerning endless loops discussed in 93$ section 5 is assumed to be resolved by preliminary processing 94$ of the flow graph, in the manner suggested there. 95$ 96 repr 97 $ data structures for parameters 98 f: remote smap(df_edge) df_map; 99 soln: remote smap(df_node) df_elmt; 100 id_prm: df_map; 101 zero_prm: df_elmt; 102 meet_flag_prm: boolean; 103 104 $ data structures for local variables 105 aux_f: remote smap(df_edge) df_map; 106 fexit: remote smap(df_node) df_map; 107 p: routine; 108 ex_inf: remote smap(routine) df_elmt; 109 end repr; 110 111 $ transfer constant parmeters to globals 112 id := id_prm; 113 zero := zero_prm; 114 meet_flag := meet_flag_prm; 115$ 116$ this master procedure consists of the following four phases: 117$ 118$ 1. interprocedural elimination phase 119$ 120 aux_f := interproc_back_eliminate(f); 121$ 122$ 2. compute auxiliary fexit maps. 123$ 124 fexit := {}; 125 (forall p in routs) 126 intra_aux_eliminate(p, f, aux_f, fexit); 127 end forall p; 128$ 129$ 3. find data at procedure exits 130$ 131 ex_inf := exit_info(f, aux_f, fexit); 132$ 133$ 4. final propagation phase 134$ 135 soln := {}; $ initialize the solution 136 (forall p in routs) 137 back_propagate_in(p, fexit, soln, ex_inf(p)); 138 end forall; 139 140 end procedure interproc_back_analysis; 141 142 1 .=member ebe13h 2 3 4 procedure interproc_back_eliminate(rw f); 5$ 6$ this is the driver routine for the interprocedural first 7$ inner-to-outer interval pass. procedures are analyzed in 8$ the following order: we process the strongly connected 9$ components of the call graph in their postorder; then, for each 10$ such component, we iterate through its procedures in their 11$ postorder, no more than 2*d+1 times, where d is the loop- 12$ interconnectedness parameter of the component. 13$ 14 repr 15 $ data structures for parameters 16 f: remote smap(df_edge) df_map; 17 18 $ data structures for local variables 19 aux_f: remote smap(df_edge) df_map; 20 f_p: remote smap(routine) df_map; 21 i: integer; 22 scc: routine; 23 scc_procs: tuple(routine); 24 flow_flag: string; 25 j: integer; 26 proc_converge: boolean; 27 k: integer; 28 p: routine; 29 end repr; 30 31 aux_f := {}; $ initialize auxiliary maps 32 f_p := {}; $ propagation effect thru procedures 33 34 $ iterate through the s.c.c.s of cgraph 35 (forall i in [ #cg_sccs, #cg_sccs-1..1 ]) 36 scc := cg_sccs(i); $ get a s.c.c. 37 scc_procs := scc_nodes(scc); $ procedures in that s.c.c. 38 flow_flag := 'first_inter'; $ first processing of the s.c.c. 39 40 (forall j in [ 1..2*scc_d(scc)+1 ]) 41 proc_converge := true; 42 43 (forall k in [ #scc_procs, #scc_procs-1..1 ]) 44 p := scc_procs(k); 45 proc_converge := 46 intraproc_back_eliminate(p,aux_f,f,f_p,flow_flag) 47 and proc_converge; 48 $ this routine analyzes p; its fifth parameter 49 $ indicates whether the analysis is first-time 50 $ interprocedural, second-time interprocedural or 51 $ intraprocedural; it returns a flag to indicate 52 $ whether information has stabilized in p. 53 end forall; 54 55 flow_flag := 'second_inter'; $ additional passes thru scc 56 57 if proc_converge then quit forall j; end if; 58 end forall; 59 end forall; 60 61 return aux_f; 62 63 end procedure interproc_back_eliminate; 64 65 1 .=member abe13i 2 3 4 procedure intraproc_back_eliminate(p, rw aux_f, rw f, rw f_p, 5 flow_flag); 6$ 7$ this routine performs an intraprocedural elimination phase of a 8$ backward data flow analysis for a given procedure p. 9$ 10$ the overall logic is quite similar to its sister routine 11$ 'intraproc_fwd_eliminate', and the reader should consult comments 12$ given there. the differences between these two phases reflects 13$ mainly the reverse tracing of flow, which implies several minor 14$ modifications of the forward approach, as follows: 15$ 16$ a. auxiliary information is computed for each successor of each 17$ interval. that is, for each interval i, each node u in i, and 18$ each successor node v of i, we compute a map aux_f([u, v]), 19$ representing the flow from u through i to v. the outermost 20$ interval has no successors, but we regard the return block of p 21$ and the stop block of p (if any) as its two successors, even 22$ though they lie within it. (note that since these two nodes 23$ can never lie on a cycle through p, they must belong to the 24$ outer-most interval). 25$ 26$ b. nodes of an interval are processed in reverse interval order 27$ (i.e. postorder); this is done three times if i is a proper 28$ inner interval, once if i is a proper outer-most inteval, and 29$ till convergence otherwise. 30$ 31$ c. functional composition is taken in reverse edge order. 32$ 33$ d. an additional data structure f_p is used to hold the propagation 34$ effect through the whole procedure in the interprocedural case. 35$ this is because the flow through p is actually combined of two 36$ flows: one leading to the return block of p, and another leading 37$ to the stop block of p, if any. unlike in the forward case, 38$ where the second flow can be, and is, actually ignored, here we 39$ must take it into account. 40$ 41 repr 42 $ data structures for formal parameters 43 p: routine; 44 aux_f: remote smap(df_edge) df_map; 45 f: remote smap(df_edge) df_map; 46 f_p: remote smap(routine) df_map; 47 flow_flag: *; 48 49 $ data structures for local variables 50 need_process: set(df_node); 51 intt: df_node; 52 c: df_node; 53 v: df_node; 54 p1: routine; 55 p_ints: tuple(df_node); 56 outint: df_node; 57 sp: df_node; 58 k: integer; 59 nodes: tuple(df_node); 60 head: df_node; 61 cesors: sparse set(df_node); 62 conv_control: boolean; 63 n_iter: integer; 64 nd: df_node; 65 d: integer; 66 convrgd: boolean; 67 j: integer; 68 ftemp: df_map; 69 snd: df_node; 70 fzero: df_elmt; 71 end repr; 72 73 74 if flow_flag = 'second_inter' then 75 $ process only intervals containing calls with new effects 76 need_process := {}; 77 else 78 $ process all intervals 79 need_process := { intt : intt in ints(p) }; $ convert to set 80 end if; 81 82 if flow_flag /= 'intra' then $ interprocedural analysis 83 84 (forall c in callsin{p}) 85 v := cessor(c); $ the block following the call 86 p1 := callproc(c); $ c calls p1 87 88 $ (note that if this routine is modified to include 89 $ parameter-passing assignments as part of call blocks, 90 $ in the manner mentioned above, then one might manipulate 91 $ f_p(p1), the local effect of executing p1, to get 92 $ f([c, v]), rather than just assign f_p(p1) 93 $ to f([c, v]), as is done below). 94 95 if f([c, v]) /= f_p(p1) then 96 97 $ update flow function for call 98 f([c, v]) := f_p(p1) ? fom; 99 100 $ interval containing call must be processed 101 need_process with:= intof(c); 102 end if; 103 end forall c; 104 105 $ if no intervals need be processed then information has 106 $ stabilized and no re-processing of p need be done. 107 if need_process = {} then return true; end if; 108 end if; 109 110 111 p_ints := ints(p); $ intervals of p in reverse preorder 112 outint := p_ints(#p_ints); $ outermost interval 113 vedges{outint} := {rexit(p)}; $ 'successors' of outint 114 if (sp := rstop(p)) /= om then 115 vedges{outint} with:= sp; 116 end if; 117 118 (forall intt = p_ints(k) | intt in need_process) 119 120 need_process with:= intof(intt); $ process containing interval 121 nodes := int_nodes(intt); $ nodes of intt in interval order 122 head := nodes(1); $ interval head 123 124 $ get successor nodes 125 cesors := vedges{intt}; 126 127 $ initialize aux_f for successor nodes. this trick simplifies 128 $ subsequent code considerably. 129 (forall v in cesors) 130 aux_f([v, v]) := id; 131 end forall; 132$ 133$ three cases are now possible: 134$ a. intt is proper, but not outermost; then iterate three times. 135$ b. intt is proper, and is outermost; then iterate once. 136$ c. intt is improper; iterate indefinitely (1 + 2*number of 137$ nodes is an adequate upper bound) until convergence. (here, 138$ again, a better bound can be used; cf. section 6). 139$ 140 $ we test for convergence only for improper intervals. 141 conv_control := intt notin proper_ints; 142 143 n_iter := $ maximal number of iterations through nodes of intt 144 if intt notin proper_ints then 1 + 2 * #nodes 145 elseif intt = outint then 1 146 else 3 147 end; 148 149 (forall nd in nodes, v in cesors | nd /= v) 150 aux_f([nd, v]) := fom; 151 end forall; 152 153 $ iterate through the nodes of intt. 154 (forall d in [ 1..n_iter ]) 155 convrgd := conv_control; 156 157 $ iterate thrugh the nodes of intt in reverse interval 158 $ order. 159 (forall j in [ #nodes, #nodes-1..1 ]) 160 nd := nodes(j); 161 162 (forall v in cesors | v /= nd) 163 164 $ since the 'successors' of the outermost interval 165 $ are nodes of that interval, we may have nd = v. 166 $ in this case it would be erroneouss to compute 167 $ aux_f([nd, v]) (which has already been set to 168 $ id) using the following 'propagation from 169 $ successors' formula, so we just skip such cases. 170 171 ftemp := fom; 172 (forall snd in cessor{nd} | 173 intof(snd) = intt or snd = v) 174 ftemp .meetjoin:= 175 (f([nd,snd]) .comp aux_f([snd,v])); 176 end forall; 177 178 $ note that flow graph edges (virtual or real) are 179 $ either edges within an interval, linking two 180 $ nodes in the same interval, or edges going out 181 $ of an interval, or edges going into an interval 182 $ (these last edges are edges from (a target block 183 $ of) an interval to its head. it is this third 184 $ kind of edge that we wish to avoid propagating 185 $ through in the above formula. 186 $ 'intof(snd) = intt' tests for internal edges 187 $ and 'snd = v' tests for outgoing edges whose 188 $ target is v. 189 190 convrgd and:= (ftemp = aux_f([nd, v])); 191 aux_f([nd, v]) := ftemp; 192 end forall v; 193 end forall j; 194 195 if convrgd then quit forall d; end if; 196 end forall d; 197 $ (note that no special handling of intt's head is required.) 198 199 $ except for the outermost interval, compute f([intt, v]), 200 $ where v is a successor of some node in intt. 201 if intt /= outint then 202 $ f([intt, v]) is trivially calculated in this case; we 203 $ also remove the dummy aux_f([v, v]) entries. 204 (forall v in cesors) 205 f([intt, v]) := 206 f([intt, head]) .comp aux_f([head, v]); 207 aux_f([v, v]) := om; 208 end forall v; 209 end if; 210 end forall intt; 211 212 f_p(p) := aux_f([head, rexit(p)]); $ head = rentry(p) 213 214 $ if p contains a stop block, calculate propogation effect to that 215 $ block and combine it with 'normal' flow effect. 216 if rstop(p) /= om then 217 fzero := aux_f([head, rstop(p)]) .of zero; 218 f_p(p) := f_p(p) .meetjoin [ fzero, fzero ]; 219 $ note that a constant function c is represented by [c, c] 220 221 end if; 222 223 $ remove artificial edges added earlier 224 vedges{outint} := {}; 225 226 return false; $ to indicate no convergence 227 228 end procedure intraproc_back_eliminate; 229 230 1 .=member axe13j 2 3 4 procedure intra_aux_eliminate(p, f, aux_f, rw fexit); 5$ 6$ this procedure performs an additional intraprocedural elimination, 7$ during which we compute, for each node n in p, a map fexit(n) repre- 8$ senting the effect of flow from the start of n up to an exit of p. 9$ 10 repr 11 $ data structures for formal parameters 12 p: routine; 13 f: remote smap(df_edge) df_map; 14 aux_f: remote smap(df_edge) df_map; 15 fexit: remote smap(df_node) df_map; 16 17 $ data structures for local variables 18 p_ints: tuple(df_node); 19 outint: df_node; 20 ep: df_node; 21 sp: df_node; 22 outnodes: tuple(df_node); 23 nd: df_node; 24 i: integer; 25 fzero: df_elmt; 26 ftemp: df_map; 27 j: integer; 28 intt: df_node; 29 cesors: sparse set(df_node); 30 nodes: tuple(df_node); 31 k: integer; 32 v: df_node; 33 end repr; 34 35 p_ints := ints(p); 36 outint := p_ints(#p_ints); 37 ep := rexit(p); 38 sp := rstop(p); 39$ 40$ first process nodes of outint 41$ 42 outnodes := int_nodes(outint); 43 44 (forall nd = outnodes(i)) 45 46 fexit(nd) := aux_f([nd, ep]); $ get the effect of flow to ep 47 48 if sp /= om then $ if there is also a stop block 49 50 fzero := aux_f([nd, sp]) .of zero; 51 ftemp := if fzero = xom then fom else [ fzero, fzero ] end; 52 fexit(nd) := fexit(nd) .meetjoin ftemp; 53 54 end if; 55 end forall nd; 56$ 57$ next process all remaining intervals in outer-to-inner order 58$ 59 (forall j in [ #p_ints-1, #p_ints-2..1 ]) 60 61 intt := p_ints(j); 62 cesors := vedges{intt}; 63 64 nodes := int_nodes(intt); 65 (forall nd = nodes(k)) 66 ftemp := fom; 67 (forall v in cesors) 68 ftemp .meetjoin:= (aux_f([nd,v]) .comp fexit(v)); 69 end forall; 70 fexit(nd) := ftemp; 71 end forall nd; 72 end forall j; 73 74 75 end procedure intra_aux_eliminate; 76 77 1 .=member xnf13k 2 3 4 procedure exit_info(f, aux_f, fexit); 5$ 6$ this function calculates and returns a mapping which sends 7$ each procedure p into the flow information available at exit 8$ from p. it is called (only in the interprocedural case) just 9$ before we begin the final outer-to-inner propagation phase. 10$ 11 repr 12 $ data structures for formal parameters 13 f: remote smap(df_edge) df_map; 14 aux_f: remote smap(df_edge) df_map; 15 fexit: remote smap(df_node) df_map; 16 17 $ data structures for local variables 18 cgf: smap( tuple(routine, routine) ) df_map; 19 p, q: routine; 20 c: df_node; 21 c1: df_node; 22 ex_inf: remote smap(routine) df_elmt; 23 cgrinv: mmap(routine) routine; 24 i: integer; 25 scc: routine; 26 scc_procs: tuple(routine); 27 n: integer; 28 convrgd: boolean; 29 k: integer; 30 temp: df_elmt; 31 end repr; 32$ 33$ first we construct a map 'cgf' assigning, to each edge (p, q) 34$ of the call graph, a data-propagation map describing the 35$ propagation effect as control returns from the exit of q to p 36$ after any call in p to q, and then advances to the exit of p. 37$ 38 cgf := {}; 39 (forall [ p, q ] in cgraph) cgf([p,q]) := fom; end forall; 40 41 (forall q = callproc(c)) $ for all calls within all procedures 42 p := routof(c); $ [p, q] is an edge of the call graph 43 c1 := cessor(c); $ c1 is the block following c 44 cgf([p, q]) := cgf([p, q]) .meetjoin fexit(c1); 45 46 $ note that since we are dealing with a backward analysis, we 47 $ want to propagate data from the exit of the calling procedure 48 $ p to the exit of the called procedure q. this direction of 49 $ propagation, however, makes our problem a forward problem for 50 $ the call graph. 51 52 end forall; 53$ 54$ next we iterate through the call graph in 'invocation order', i.e. 55$ process the strongly connected components in reverse postorder 56$ and the set of procedures within each strongly connected 57$ component in reverse postorder also. 58$ 59 ex_inf := { [ p, xom ] : p in routs }; $ initialize solution 60 ex_inf(sym_main) := zero; 61 cgrinv := { [ p, q ] : [ q, p ] in cgraph }; 62 63 $ pick strongly-connected components in reverse postorder 64 (forall i in [ 2..#cg_sccs ]) 65 66 $ note that we assume here that the main program is non-recur- 67 $ sive, so that the first strongly-connected component of the 68 $ call graph consists of the main program only. thus we need 69 $ not process it, for the exit value of the main program is 70 $ already assumed known. 71 72 scc := cg_sccs(i); 73 scc_procs := scc_nodes(scc); $ procs in scc in rev. postorder 74 75 (forall n in [ 1..scc_d(scc)+1 ] ) 76 convrgd := true; 77 (forall p = scc_procs(k)) 78 temp := xom; 79 (forall q in cgrinv{p}) 80 temp .mjv:= (cgf([q,p]) .of ex_inf(q)); 81 end forall; 82 83 $ test for convergence 84 convrgd and:= (temp = ex_inf(p)); 85 ex_inf(p) := temp; 86 87 end forall p; 88 89 if convrgd then quit forall n; end if; 90 end forall n; 91 end forall i; 92 93 return ex_inf; 94 95 end procedure exit_info; 96 97 1 .=member bpi13l 2 3 4 procedure back_propagate_in(p, fexit, rw soln, ex_val); 5$ 6$ this procedure performs outer-to-inner back propagation for 7$ a routine p, using the 'fexit' information. ex_val is the flow 8$ information assumed (or known) at the procedure return block, 9$ where 'zero' is always assumed at the stop block of p (but this 10$ assumption has already been used in calculating the fexit maps). 11$ 12 repr 13 $ data structures for formal parameters 14 p: routine; 15 fexit: remote smap(df_node) df_map; 16 soln: remote smap(df_node) df_elmt; 17 ex_val: df_elmt; 18 19 $ data structures for local variables 20 intt: df_node; 21 u: df_node; 22 end repr; 23 24 (forall intt in ints(p), u in int_nodes(intt)) 25 soln(u) := fexit(u) .of ex_val; 26 end forall; 27 28 end procedure back_propagate_in; 29 30 31 32 33 procedure intraproc_back_analysis(p, rw f, wr soln, 34 id_prm, zero_prm, meet_flag_prm); 35$ 36$ this is the master routine to perform a specific backward data flow 37$ analysis intraprocedurally for a routine p whose local variables are 38$ to be analyzed. for more details, comments, and description of para- 39$ meters see the corresponding interprocedural analyser. 40$ 41 repr 42 $ data structures for formal parameters 43 p: routine; 44 f: remote smap(df_edge) df_map; 45 soln: remote smap(df_node) df_elmt; 46 id_prm: df_map; 47 zero_prm: df_elmt; 48 meet_flag_prm: boolean; 49 50 $ data structures for local variables 51 aux_f: remote smap(df_edge) df_map; 52 f_p: remote smap(routine) df_map; 54 fexit: remote smap(df_node) df_map; 55 end repr; 56 57 id := id_prm; 58 zero := zero_prm; 59 meet_flag := meet_flag_prm; 60 61 aux_f := {}; f_p := {}; 62 63 intraproc_back_eliminate(p, aux_f, f, f_p, 'intra'); 65 66 fexit := {}; 67 intra_aux_eliminate(p, f, aux_f, fexit); 68 69 soln := {}; 70 back_propagate_in(p, fexit, soln, zero); 71$ 72$ note that in the intraprocedural case the last two procedures 73$ can be combined to form a single procedure almost identical 74$ with 'intra_aux_eliminate', except that this procedure computes the 75$ 'soln' map directly instead of the 'fexit' maps. 76$ 77 end procedure intraproc_back_analysis; 78 79 80 81$ 82$ here are the operators which manipulate the data propagation maps 83$ and data states. 84$ 85 op .comp(g, f); $ functional composition g of f 86 87 if f = fom or g = fom then 88 return fom; 89 else 90 return [ f(1) * g(1) + g(2), f(2) * g(1) + g(2) ]; 91 end if; 92 93 end op .comp; 94 95 96 op .meetjoin(g, f); $ functional meet or join 97 98 if f = fom then return g; 99 elseif g = fom then return f; 100 elseif meet_flag then return [ f(1) * g(1), f(2) * g(2) ]; 101 else return [ f(1) + g(1), f(2) + g(2) ]; 102 end if; 103 104 end op .meetjoin; 105 106 107 op .mjv(x, y); $ meet or join of lattice elements 108 109 if x = xom then return y; 110 elseif y = xom then return x; 111 elseif meet_flag then return x * y; 112 else return x + y; 113 end if; 114 115 end op .mjv; 116 117 118 operator .of(f, x); $ functional application 119 return if x = xom or f = fom then xom 120 else f(1)*x + f(2) 121 end; 122 end operator .of; 123 124 125 drop 126 .comp, 127 interproc_fwd_analysis, 128 intraproc_fwd_analysis, 129 interproc_back_analysis, 130 intraproc_back_analysis, 131 fom, 132 xom; 133 134 135 end module setl_optimizer - dataflow_solver_syms; 136 137 1 .=member dfo13m 2 3 4 module setl_optimizer - dataflow_solver_ocrs; 5$ 6$ this module is a duplicate of the preceding module. it differs in 7$ that it operates on the base of occurrences (ocrs) rather than on the 8$ base of symbols (syms). 9$ 10$ this module contains a package of general purpose routines to solve 11$ bit vector data flow problems either intraprocedurally or inter- 12$ procedurally. we can distinguish between four basic types of such 13$ analyses, according to the character of the desired analysis: 14$ 15$ forward - data is to be propagated in the direction of 16$ the flow, from procedure entries forward. 17$ 18$ backward - data is to be propagated in the reverse 19$ direction of the flow, from exits backward. 20$ 21$ meet - whenever two paths converge (for forward analysis) 22$ or diverge (for backward analysis) take the meet (set 23$ intersection) of data values propagated along these 24$ paths. 25$ 26$ join - as in meet, except that the join (set union) of the 27$ corresponding data values is to be taken. 28$ 29$ typical examples are: expression availability analysis 30$ is a forward - meet analysis; unconditional exposure 31$ of expressions (also known as 'very busy' expressions 32$ analysis) is a backward - meet analysis; reaching 33$ definitions analysis is a forward - join analysis, and 34$ live variables analysis is a backward - join analysis. 35$ 36$ as noted in chapters 5 and 6, forward and backward analyses 37$ require substantially different logic, so that each of them 38$ is executed in a different subpackage; however, the 39$ difference between meet and join problems turns out to 40$ be rather minor, so that they both can be handled by 41$ the same (forward or backward) package, using a switch 42$ to indicate whether a particular analysis is of meet or 43$ join type. 44$ 45$ this module exports the following procedures: 46$ 47$ interproc_fwd_analysis - call this to solve interprocedural 48$ forward data flow analyses. 49$ 50$ intraproc_fwd_analysis - call this to solve intraprocedural 51$ forward data flow analysis for a given 52$ procedure. 53$ 54$ interproc_back_analysis - call this to solve interprocedural 55$ backward data flow analysis 56$ 57$ intraproc_back_analysis - performs intraprocedural backward 58$ analysis for a given procedure. 59$ 60$ this package assumes the following global objects to be 61$ available: 62$ 63$ cgraph - the program call graph, represented as a set 64$ of edges; an edge (p,q) is in cgraph iff p is a 65$ procedure which contains a call to the procedure q. 66$ 67$ routs - set of all program procedures (i.e. all nodes 68$ of the call graph). 69$ 70$ sym_main - main-program identifier (i.e. the entry node of the 71$ call graph). 72$ 73$ routof - maps each block to the procedure containing it. 74$ 75$ rentry - maps each procedure to its entry block. 76$ 77$ rexit - maps each procedure to its exit (return) block. 78$ 79$ rstop - maps each procedure to its stop block, if any. 80$ 81$ callsin - maps each procedure to the set of all call blocks 82$ in it. 83$ 84$ callproc - maps each call block to the procedure it calls. 85$ 86$ cessor - the program flow graph, as a union of the flow 87$ graphs of all procedures. an edge (m, n) is in 88$ cessor iff either m contains a branch to n, or else 89$ m is a call block and n is the block immediately 90$ following n. the nodes of the flow graph are either 91$ basic blocks or derived intervals (which are 92$ represented by their target blocks), in which case 93$ an edge (int, v) in cessor can indicate the possibility 94$ of a transfer of control from the interval int to a 95$ successor v of some node in int. these edges are called 96$ virtual edges (as above; see the interval 97$ analysis package for more details). 98$ 99$ pred - the inverse map of cessor. 100$ 101$ ints - maps each procedure to the tuple of its intervals 102$ in reverse preorder (relative to a depth first 103$ spanning tree of its flow graph). 104$ 105$ int_nodes - maps each interval to the sequence of its nodes 106$ in interval order (i.e., reverse postorder). 107$ 108$ proper_ints - the set of all proper intervals (those which do 109$ not contain irreducible nucleii). 110$ 111$ intof - maps each flow graph node to the interval containing 112$ it. 113$ 114$ vedges - set of all virtual edges (see the description of 115$ cessor above). 116$ 117$ in addition this module uses the following global-within- 118$ the-module variables, the first three of which are used 119$ to transmit flags and analysis constants between inner routines, 120$ while the rest are built by a recursive depth-first search 121$ procedure during call-graph analysis, and are used later in that 122$ analysis. 123$ 124 macro .comp; .comp_ocrs endm; 125 macro interproc_fwd_analysis; interproc_fwd_analysis_ocrs endm; 126 macro intraproc_fwd_analysis; intraproc_fwd_analysis_ocrs endm; 127 macro interproc_back_analysis; interproc_back_analysis_ocrs endm; 128 macro intraproc_back_analysis; intraproc_back_analysis_ocrs endm; 129 macro fom; fom_ocrs endm; 130 macro xom; xom_ocrs endm; 131 132 var 133 id, $ identity flow map 134 zero, $ null data state 135 meet_flag, $ true if meet analysis; otherwise false 136 seen, $ procedures already in dfst of cgraph 137 cnpre, $ current preorder index in dfst 138 cnpost, $ current postorder index in dfst 139 nodeno, $ preorder numbering map 140 postno, $ postorder numbering map 141 ndescs; $ no. of descendants map 142 143 144 repr 145 mode df_elmt: df_elmt_ocrs; 146 mode df_map: df_map_ocrs; 147 148 .meetjoin: operator(df_map, df_map) df_map; 149 .mjv: operator(df_elmt, df_elmt) df_elmt; 150 .of: operator(df_map, df_elmt) df_elmt; 151 152 interproc_fwd_eliminate: procedure( 153 remote smap(df_edge) df_map ) 154 remote smap(df_node) df_map; 155 intraproc_fwd_eliminate: procedure( 156 routine, 157 remote smap(df_node) df_map, 158 remote smap(df_edge) df_map, 159 string ) 160 boolean; 161 propagate_exposed: procedure( 162 routine, 163 remote smap(df_edge) df_map, 164 remote smap(df_node) df_map, 165 remote mmap{df_node} df_elmt, 166 remote mmap{df_node} df_elmt, 167 remote mmap{df_node} df_elmt 168 ); 169 entry_info: procedure( 170 remote smap(df_edge) df_map, 171 remote smap(df_node) df_map, 172 boolean, 173 remote mmap{df_node} df_elmt ) 174 remote smap(routine) df_elmt; 175 fwd_propagate_in: procedure( 176 routine, 177 remote smap(df_edge) df_map, 178 remote smap(df_node) df_map, 179 remote smap(df_node) df_elmt, 180 df_elmt, 181 boolean, 182 remote mmap{df_node} df_elmt 183 ); 184 interproc_back_eliminate: 185 procedure(remote smap(df_edge) df_map) 186 remote smap(df_edge) df_map; 187 intraproc_back_eliminate: 188 procedure( 189 routine, 190 remote smap(df_edge) df_map, 191 remote smap(df_edge) df_map, 192 remote smap(routine) df_map, 193 * ) $$-- flow_flag 194 boolean; 195 intra_aux_eliminate: procedure( 196 routine, 197 remote smap(df_edge) df_map, 198 remote smap(df_edge) df_map, 199 remote smap(df_node) df_map 200 ); 201 exit_info: procedure( 202 remote smap(df_edge) df_map, 203 remote smap(df_edge) df_map, 204 remote smap(df_node) df_map ) 205 remote smap(routine) df_elmt; 206 back_propagate_in: procedure( 207 routine, 208 remote smap(df_node) df_map, 209 remote smap(df_node) df_elmt, 210 df_elmt 211 ); 212 213 214 $ data structures for variables global to this module 215 id: df_map; 216 zero: df_elmt; 217 meet_flag: boolean; 218 cnpre: integer; 219 cnpost: integer; 220 nodeno: remote smap(routine) integer; 221 postno: remote smap(routine) integer; 222 ndescs: remote smap(routine) integer; 223 seen: remote set(routine); 224 end repr; 225 226 1 .=member efa13o 2 3 procedure interproc_fwd_analysis(rw f, wr soln, id_prm, zero_prm, 4 meet_flag_prm, move_code, 5 rw exposed, wr insert,safe); 6$ 7$ note declarations of 'read-write' parameters ('rw') and 'write-only' 8$ parameters ('wr'). 9$ 10$ this is the master routine to perform a specific data flow 11$ analysis interprocedurally. its parameters are: 12$ 13$ f - maps each edge (m, n) in the flow graph to a compact 14$ representation of its data-propagation map f(m,n). 15$ initially this information has to be provided only for 16$ basic blocks (but not for call blocks); the first phase 17$ of the analysis will fill in the additional entries. 18$ each f(m,n) is represented as a pair [a, b] in l x l, 19$ such that for each x in l, f(m,n)(x) = x*a + b, and 20$ a contains b (this latter condition ensures that the 21$ representation is unique, and also simplifies some 22$ functional manipulations). 23$ 24$ soln- the solution vector for the analysis. soln maps each 25$ flow graph node to the data found to be known at its 26$ entry. 27$ 28$ the next three parameters are transmitted internally between 29$ subprocedures by assigning them to global variables, as they 30$ are constant per analysis. the corresponding globals are: 31$ 32$ id - the identity map representation. id = [u, {}], where 33$ u is the universal set over which bitvectors are taken 34$ in this analysis (e.g. set of all program expressions, 35$ set of all variables etc.) 36$ 37$ zero - the initial data value, i.e. flow data assumed at the main 38$ program entry. 39$ 40$ meet_flag - a flag indicating whether the analysis is a meet 41$ analysis or a join analysis. 42$ 43$ aux_f - these are auxiliary propagation maps. for each flow 44$ graph node u, aux_f(u) denotes the effect of propagation 45$ from the entry to i, the interval containing u, through 46$ i, to the entry of u. 47$ 48$ move_code - a flag indicating that code motion is required. 49$ 50$ exposed - this is initially the set of computations (corresponding 51$ to analysis elements (bits)) exposed at the start of each 52$ basic block n (i.e. computed with no prior kill in n). the 53$ inner-to-outer phase of our analysis attaches an 'exposed' 54$ value to each interval processed. exposed{i} is the set of all 55$ expressions t for which there exists a computation of t 56$ within the interval i which would become redundant if and 57$ only if t became available at the entry to (the target block 58$ of) i. note, however, that the logical place at which 59$ computations movable out of an interval i should be inserted 60$ is the end of the target block of i, rather than its start. 61$ thus if that target block is nonempty then exposed{i} 62$ need not represent those movable computations. for this 63$ reason we provide the parameter 'insert' which gives the 64$ desired set of movable code. 65$ 66$ insert - this output parameter will map each interval into 67$ the set of all computations movable out of its loop, 68$ which are to be inserted at the end of the target 69$ block of the interval. the actual insertion should be 70$ performed by the calling procedure. 71$ 72$ our analysis procedures makes frequent use of the following 73$ operators (which could be also written as macros, if it were 74$ not for the convenience of the infix notation that we prefer 75$ to use): 76$ 77$ .comp - functional composition 78$ .meetjoin - functional meet or join, depending on meet_flag 79$ .mjv - meet or join of lattice values 80$ .of - functional application 81$ 82$ all these operators have elementary set expressions; see below 83$ for details. 84$ 85$ note also that these operators must be prepared to handle 86$ undefined flow values, which will be represented 87$ by a special constant 'fom'; for example, 88$ g .comp fom = fom .comp g = fom; 89$ (concatenation of an undefined flow with a defined 90$ one is still undefined) 91$ g .meetjoin fom = fom .meetjoin g = g. 92$ (a join or a meet of an undefined flow with a defined 93$ flow yields the defined flow.) 94$ 95$ another special constant 'xom' is used to denote the undefined data 96$ state in l. 97$ 98 repr 99 $ data structures for parameters 100 f: remote smap(df_edge) df_map; 101 soln: remote smap(df_node) df_elmt; 102 id_prm: df_map; 103 zero_prm: df_elmt; 104 meet_flag_prm: boolean; 105 move_code: boolean; 106 exposed: remote mmap{df_node} df_elmt; 107 insert: remote mmap{df_node} df_elmt; 108 safe: remote mmap{df_node} df_elmt; 109 110 $ data structures for local variables 111 aux_f: remote smap(df_node) df_map; 112 ent_inf: remote smap(routine) df_elmt; 113 p: routine; 114 end repr; 115$ 116$ transfer constant parameters to globals 117$ 118 id := id_prm; 119 zero := zero_prm; 120 meet_flag := meet_flag_prm; 121$ 122$ the master procedure consists of the following three phases: 123$ 124$ 1. interprocedural elimination phase 125$ 126 aux_f := interproc_fwd_eliminate(f); 127$ 128$ if code motion is required then perform an additional 129$ phase, computing the sets of movable code. 130$ 131 if move_code then 132 insert := {}; 133 (forall p in routs) 134 propagate_exposed(p, f, aux_f, exposed, insert, safe); 135 end forall; 136 end if; 137$ 138$ 2. find data at procedure entries 139$ 140 ent_inf := entry_info(f, aux_f, move_code, insert); 141$ 142$ 3. final propagation phase 143$ 144 soln := {}; $ initialize the solution 145 (forall p in routs) 146 fwd_propagate_in(p, f, aux_f, soln, ent_inf(p), 147 move_code, insert); 148 end forall; 149 150 end procedure interproc_fwd_analysis; 151 152 1 .=member efe13p 2 3 4 procedure interproc_fwd_eliminate(rw f); 5$ 6$ this is the driver routine for the first interprocedural 7$ inner-to-outer interval pass. procedures are analyzed in 8$ the following order: we process the strongly connected 9$ components of the call graph in their postorder; for each 10$ such component, we iterate through its procedures in their 11$ postorder, no more than 2*d+1 times, where d is the loop- 12$ interconnectedness parameter of the component. 13$ 14 repr 15 $ data structures for parameters 16 f: remote smap(df_edge) df_map; 17 18 $ data structures for local variables 19 aux_f: remote smap(df_node) df_map; 20 i: integer; 21 scc: routine; 22 scc_procs: tuple(routine); 23 flow_flag: string; 24 j: integer; 25 proc_converge: boolean; 26 k: integer; 27 p: routine; 28 end repr; 29 30 aux_f := {}; $ initialize auxiliary maps 31 32$ iterate through the s.c.c.s of cgraph 33 (forall i in [ #cg_sccs, #cg_sccs-1..1 ]) 34 35 scc := cg_sccs(i); $ get a s.c.c. 36 scc_procs := scc_nodes(scc); $ procs in that s.c.c. 37 flow_flag := 'first_inter'; $ first processing of the scc 38 39 (forall j in [ 1..2*scc_d(scc)+1 ]) 40 proc_converge := true; 41 42 (forall k in [ #scc_procs, #scc_procs-1..1 ]) 43 p := scc_procs(k); 44 proc_converge := 45 intraproc_fwd_eliminate(p, aux_f, f, flow_flag) 46 and proc_converge; 47$ the intraproc_fwd_eliminate routine analyzes p; its fourth parameter 48$ indicates whether the analysis is first-time interprocedural, second 49$ -time interprocedural or intraprocedural; it returns a flag to 50$ indicate whether information in p has stabilized. 51 end forall k; 52 53 flow_flag := 'second_inter'; $ additional passes thru scc 54 55 if proc_converge then quit forall j; end; 56 end forall j; 57 end forall i; 58 59 return aux_f; 60 61 end procedure interproc_fwd_eliminate; 62 63 1 .=member afe13q 2 3 4 procedure intraproc_fwd_eliminate(p, rw aux_f, rw f, flow_flag); 5$ 6$ this routine performs an intraprocedural elimination phase 7$ for the procedure p, using interval analysis. the fourth parameter 8$ indicates whether this routine has been invoked by the 9$ intraprocedural solver or by the interprocedural solver, and 10$ in the second case, whether this is the first time p is 11$ being processed or not. 12$ 13$ in this pass we iterate through the procedure's intervals 14$ in an inner-to -outer order (i.e. in reverse preorder of their 15$ heads in a dfst of the flow graph of p). for each interval 16$ i processed in this manner we compute a set of data-propagation 17$ maps of the form f(i, u), where 18$ 19$ (1) if u is in i, then this map is an auxiliary map (which will 20$ be denoted as aux_f(u), i being implicit in this case) which 21$ represents the propagation effect as control advances from 22$ the start of i, thru i, to the start of u; 23$ 24$ (2) if u is not in i, then u is a successor of some node in i. 25$ here the map f(i, u) represents the propagation effect as control 26$ advances from the start of i, through i, to the start of u; 27$ in this case f(i, u) is needed for the processing of the 28$ intervals containing i. note that [i, u] is a virtual edge 29$ in our flow graph; thus the elimination phase extends the 30$ map f so as to be defined also on virtual edges. 31$ 32$ any interval i processed in this routine is either a proper 33$ strongly connected interval, or, if it contains 'improper' 34$ nodes (i.e. nucleii of irreducibility), is a single-entry 35$ strongly connected subgraph. in the first case we only have to 36$ iterate thru the nodes of i twice, but in the second case till 37$ convergence. 38$ 39$ the outermost 'interval' is either a single entry acyclic 40$ graph (if it does not contain irreducible nucleii), or a 41$ general single-entry graph otherwise. for this 'interval' we 42$ iterate either once in the first case, or till convergence 43$ otherwise. 44$ 45$ if the present routine is to be used for interprocedural analysis, 46$ we first reset the propagation maps for call blocks in p. if none of 47$ these maps have changed from the last processing of p, 48$ then obviously analysis of p has stabilized and we can return 49$ immediately. moreover, intervals need be re-processed if and only 50$ if they contain a call block whose local effect has changed, 51$ or, recursively, contain an interval whose local effects 52$ have changed. in terms of the 'intof' tree, we only have to 53$ re-analyze intervals lying along some path from the 54$ root to a call block whose local effect has changed. this 55$ can make reprocessing of a procedure considerably 56$ faster than initial processing. 57$ 58 repr 59 $ data structures for parameters 60 p: routine; 61 aux_f: remote smap(df_node) df_map; 62 f: remote smap(df_edge) df_map; 63 flow_flag: string; 64 65 $ data structures for local variables 66 need_process: set(df_node); 67 intt: df_node; 68 c: df_node; 69 v: df_node; 70 p1: routine; 71 ep1: df_node; 72 p_ints: tuple(df_node); 73 outint: df_node; 74 k: integer; 75 nodes: tuple(df_node); 76 head: df_node; 77 conv_control: boolean; 78 n_iter: integer; 79 j: integer; 80 d: integer; 81 convrgd: boolean; 82 nd: df_node; 83 ftemp: df_map; 84 pnd: df_node; 85 pv: df_node; 86 end repr; 87 88 if flow_flag = 'second_inter' then 89 $ process only intervals containing calls with new effect 90 need_process := {}; 91 else 92 $ process all intervals 93 need_process := { intt : intt in ints(p) }; 94 end if; 95 96 if flow_flag /= 'intra' then 97 $ interprocedural analysis 98 (forall c in callsin{p}) 99 v := cessor(c); $ the block following the call 100 p1 := callproc(c); $ c calls p1 101 ep1 := rexit(p1); $ the return block of p1. 102$ (note here that if this routine is modified to include parameter- 103$ passing assignments as part of call blocks, in the manner suggested 104$ in a concluding remark in section 4, then one might manipulate 105$ aux_f(ep1), which defines the local effect of executing p1, to get 106$ f(c,v), rather than just assign the first map to the second one, as 107$ is done below). 108 109 if f([c, v]) /= aux_f(ep1) then 110 $ update flow function for call 111 f([c, v]) := aux_f(ep1) ? fom; 112 113 $ interval containing call must be processed 114 need_process with:= intof(c); 115 end if; 116 end forall c; 117 118 $ if no intervals need be processed then information has 119 $ stabilized and no re-processing of p need be done. 120 if need_process = {} then return true; end if; 121 end if; 122 123 p_ints := ints(p); $ intervals of p in reverse preorder 124 outint := p_ints(#p_ints); $ outermost interval 125 126 (forall intt = p_ints(k) | intt in need_process) 127 need_process with:= intof(intt); $ process containing interva 128 nodes := int_nodes(intt); $ nodes of intt in interval order 129 130 head := nodes(1); $ interval head 131 aux_f(head) := id; $ init aux_f of head to the identity 132$ 133$ note here that the edge [intt, head] is a real edge in the 134$ flow graph, so that f([intt, head]) will have been pre-computed in 135$ an initialization phase, along with the flow maps for all other 136$ real edges, and is therefore available here. 137$ 138$ three cases are now possible: 139$ 140$ (1) intt is proper, but not outermost; then iterate twice. 141$ (2) intt is proper, and is outermost; then iterate once. 142$ (3) intt is improper; iterate indefinitely (1 + number of 143$ nodes is an adequate upper bound) until convergence. 144$ (note that we do not make use of the better upper bound on 145$ the number of iterations discussed in section 3). 146$ 147 $ test for convergence only in this case 148 conv_control := intt notin proper_ints; 149 150 n_iter := $ maximal number of iterations 151 if intt notin proper_ints then #nodes + 1 152 elseif intt = outint then 1 else 2 end; 153$ 154$ for improper intervals, initialize aux_f of all non-head nodes 155$ to 'fom'. this is because we cannot guarantee in those cases that 156$ when propagating data to a node within intt, all its predecessors 157$ (within intt) have already been processed, so that we have to 158$ prepare for the case where some of these predecessors still 159$ have undefined auxiliary data-flow maps. 160$ 161 if conv_control then 162 (forall j in [ 2..#nodes ]) 163 aux_f(nodes(j)) := fom; 164 end forall; 165 end if; 166 167 $ iterate through the nodes of intt 168 (forall d in [ 1..n_iter ]) 169 170 convrgd := conv_control; 171 172 $ iterate thrugh the nodes of intt, other than head 173 (forall j in [ 2..#nodes ]) 174 175 nd := nodes(j); 176 ftemp := fom; 177 (forall pnd in pred{nd} | intof(pnd) = intt) 178 ftemp .meetjoin:= (f([pnd,nd]) .comp aux_f(pnd)); 179 end forall; 180 181 convrgd and:= (ftemp = aux_f(nd)); 182 aux_f(nd) := ftemp; 183 184 end forall j; 185 186 $ test if processing of intt has terminated 187 if d = n_iter or convrgd then quit forall d; end if; 188 189 $ re-compute aux_f(head), taking back edges into account 190 ftemp := fom; 191 (forall pnd in pred{head} | intof(pnd) = intt) 192 ftemp .meetjoin:= (f([pnd,head]) .comp aux_f(pnd)); 193 end forall; 194 ftemp .meetjoin:= aux_f(head); 195 196 if not conv_control then 197 convrgd := aux_f(head) = ftemp; 198 end if; 199 200 aux_f(head) := ftemp; 201 if convrgd then quit forall d; end if; 202 end forall d; 203 204$ 205$ compute f([intt, v]), where v is a successor of some node in 206$ intt; note that this loop will be null for the 207$ outermost interval. 208$ 209 (forall v in vedges{intt}) 210 ftemp := fom; 211 212 (forall pv in pred{v} | intof(pv) = intt) 213 ftemp .meetjoin:= (f([pv,v]) .comp aux_f(pv)); 214 end forall; 215 216 f([intt, v]) := ftemp .comp f([intt, head]); 217 end forall v; 218 end forall intt; 219 220 return false; $ to indicate no convergence 221 222 end procedure intraproc_fwd_eliminate; 223 224 1 .=member pex13r 2 3 4 procedure propagate_exposed(p, rw f, aux_f, rw exposed, rw insert, 5 safe); 6$ 7$ this procedure performs an inner-to-outer pass over all 8$ intervals to determine the computations which might be moved 9$ out of the loop of each interval i. as explained above, 10$ these computations are not necessarily those exposed in i; 11$ hence, we build up both sets 'exposed' and 'insert' 12$ simultaneously. 13$ 14$ in this analysis, the set of computations movable out of the 15$ loop of i is obtained by taking all computations t with 16$ the property that there exists a node nd in i such that 17$ t is exposed in nd and is available at the start of nd iff 18$ it is available at the end of the target block of i. 19$ 20$ the movable code is always assumed to be appended to the 21$ end of the target block of the interval, to avoid any possible 22$ conflict with code that is already present in the target block. 23$ however, this appending takes place physically only at the end 24$ of the elimination phase. thus, we do not attempt to make 25$ use of the fact that these expressions are potentially 26$ available at the head of i in updating any flow function. 27$ this approach is necessary to ensure convergence of our algorithms 28$ in cases of recursive cycles of interprocedural flow. 29$ 30 repr 31 $ data structures for parameters 32 p: routine; 33 f: remote smap(df_edge) df_map; 34 aux_f: remote smap(df_node) df_map; 35 exposed: remote mmap{df_node} df_elmt; 36 insert: remote mmap{df_node} df_elmt; 37 safe: remote mmap{df_node} df_elmt; 38 39 $ data structures for local variables 40 p_ints: tuple(df_node); 41 outint: df_node; 42 intt: df_node; 43 k: integer; 44 nodes: tuple(df_node); 45 head: df_node; 46 itemp: df_elmt; 47 nd: df_node; 48 ftarg: df_map; 49 expfromentry: df_elmt; 50 end repr; 51 52 p_ints := ints(p); $ intervals of p in reverse preorder 53$ 54$ first extend f to indicate null flow from the entry block to 55$ itself. since the outermost interval has no target block, 56$ and is therefore identified with its head, this trick unifies 57$ the treatment of that interval with the treatment of inner 58$ intervals, as shown below. 59$ 60 outint := p_ints(#p_ints); 61 f([outint, outint]) := id; 62 63 (forall intt = p_ints(k)) 64 nodes := int_nodes(intt); 65 head := nodes(1); 66$ 67$ in computing exposed{intt}, we must reckon with the fact 68$ that the target block of intt (also denoted by intt) 69$ might be non-empty, due to prior code motion. this can mean that 70$ (a) f([intt, head]) is not the identity, and (b) exposed{intt} 71$ (where intt is treated as a basic block) is not null 72$ initially. 73$ 74 $ we proceed as follows: first find all exposed computations 75 $ in the loop of intt, assuming the target block of intt to 76 $ be null. these are the computations movable out of the loop 77 $ of intt. 78 itemp := {}; 79 (forall nd in nodes) 80 itemp +:= (exposed{nd} * (aux_f(nd)(1) - aux_f(nd)(2))); 81 end forall; 82 if safe /= om then itemp := itemp * safe{intt}; end if; 83 insert{intt} := itemp; 84 85 $ next find the new set of computations which are still 86 $ exposed at the entry to the target block of intt. 87 ftarg := f([intt, head]); 88 expfromentry := insert{intt} * (ftarg(1) - ftarg(2)); 89 90 $ add these computations to those exposed in the target block 91 exposed{intt} := exposed{intt} + expfromentry; 92 93 end forall intt; 94 95 96 end procedure propagate_exposed; 97 98 99 100 101 procedure entry_info(f, aux_f, move_code, insert); 102$ 103$ this function calculates and returns a mapping which sends 104$ each procedure p into the flow information available at entry 105$ to p. it is called (only in the interprocedural case) just 106$ before we begin the final outer-to-inner propagation phase. 107$ 108 repr 109 $ data structures for parameters 110 f: remote smap(df_edge) df_map; 111 aux_f: remote smap(df_node) df_map; 112 move_code: boolean; 113 insert: remote mmap{df_node} df_elmt; 114 115 $ data structures for local variables 116 cgf: smap( tuple(routine, routine) ) df_map; 117 p, q: routine; 118 c: df_node; 119 ftemp: df_map; 120 iu: df_node; 121 hiu: df_node; 122 fins: df_map; 123 ent_inf: remote smap(routine) df_elmt; 124 cgrinv: mmap(routine) routine; 125 i: integer; 126 scc: routine; 127 scc_procs: tuple(routine); 128 n: integer; 129 convrgd: boolean; 130 k: integer; 131 temp: df_elmt; 132 end repr; 133$ 134$ first we construct a map 'cgf' assigning to each edge (p, q) 135$ of the call graph a data-propagation map, describing the 136$ propagation effect as control advances from the entry of p 137$ to the entry of q via any call to q from p. 138$ 139 cgf := {}; 140 (forall [p,q] in cgraph) cgf([p,q]) := fom; end; 141 142 (forall q = callproc(c)) $ for all calls within all procedures 143 144 p := routof(c); $ [p, q] is an edge of the call graph 145 146 $ compute the local effect as control advances from the entry 147 $ of p to c. 148 ftemp := aux_f(c); 149 150 (init iu := intof(c); while iu /= rentry(p)) 151 152 hiu := int_nodes(iu)(1); $ head of iu 153 fins := id; 154 $ add also the effect of code moved out of iu 155 if move_code then fins(2) := insert{iu}; end; 156 ftemp := ftemp .comp fins .comp f([iu, hiu]) 157 .comp aux_f(iu); 158 159 iu := intof(iu); 160 161 end; 162 163 cgf([p, q]) := cgf([p, q]) .meetjoin ftemp; 164 165 end forall q; 166$ 167$ next we iterate through the call graph in 'invocation order', i.e. 168$ process the strongly connected components in reverse postorder and the 169$ set of procedures within each strongly connected component in reverse 170$ postorder also. 171$ 172 ent_inf := { [ p, xom ] : p in routs}; $ initialize solution 173 ent_inf(sym_main) := zero; 174 cgrinv := { [ p, q ] : [ q, p ] in cgraph }; 175 176 $ pick strongly-connected components in reverse postorder 177 (forall i in [ 2..#cg_sccs ]) 178 179 $ nb. here we assume that the main program is non-recursive, so 180 $ that the first strongly-connected component of the call graph 181 $ consists of the main program only. thus we can skip it, for 182 $ the entry value of the main program is already assumed known. 183 184 scc := cg_sccs(i); 185 scc_procs := scc_nodes(scc); $ procs in scc in rev. postorder 186 187 (forall n in [ 1..scc_d(scc)+1 ] ) 188 189 convrgd := true; 190 191 (forall p = scc_procs(k)) 192 193 temp := xom; 194 (forall q in cgrinv{p}) 195 temp .mjv:= (cgf([q,p]) .of ent_inf(q)); 196 end forall; 197 198 $ test for convergence 199 convrgd and:= (temp = ent_inf(p)); 200 201 ent_inf(p) := temp; 202 end forall p; 203 204 if convrgd then quit forall n; end if; 205 end forall n; 206 end forall i; 207 208 return ent_inf; 209 210 end procedure entry_info; 211 212 1 .=member fpi13s 2 3 4 procedure fwd_propagate_in(p, rw f, aux_f, rw soln, ent_val, 5 move_code, rw insert); 6$ 7$ this procedure performs outer-to-inner propagation for a 8$ routine p, using the 'interval-effect' flow functions aux_f 9$ to modify the solution map 'soln'. the parameter ent_val 10$ gives the flow information assumed (or known) at procedure 11$ entry. 12$ 13$ if code motion is required, then the computations in insert{i} 14$ are assumed to be available at the end of the target block 15$ of an interval i (but only for the purpose of propagation 16$ inside i). in addition, computations in insert{i} already 17$ available at exit from the target block of i are removed from 18$ insert{i}. 19$ 20$ note that movable computations are assumed to be such that the 21$ insertion of any of them will not 'kill' any others. 22$ 23 repr 24 $ data structures for parameters 25 p: routine; 26 f: remote smap(df_edge) df_map; 27 aux_f: remote smap(df_node) df_map; 28 soln: remote smap(df_node) df_elmt; 29 ent_val: df_elmt; 30 move_code: boolean; 31 insert: remote mmap{df_node} df_elmt; 32 33 $ data structures for local variables 34 p_ints: tuple(df_node); 35 outint: df_node; 36 k: integer; 37 intt: df_node; 38 nodes: tuple(df_node); 39 soln1: df_elmt; 40 u: df_node; 41 end repr; 42 43 soln(rentry(p)) := ent_val; 44 p_ints := ints(p); $ intervals of p in reverse preorder 45$ 46$ extend f to indicate null flow from the entry block to 47$ itself. since the outermost interval has no target block, 48$ and is therefore identified with its head, this trick unifies 49$ the treatment of that interval with the treatment of inner 50$ intervals, as shown below. 51$ 52 outint := p_ints(#p_ints); 53 f([outint, outint]) := id; 54 55 (forall k in [ #p_ints, #p_ints-1..1 ]) 56 57 intt := p_ints(k); 58 nodes := int_nodes(intt); $ nodes of intt 59 60 soln1 := soln(intt); $ data value at entry to intt 61 62 $ convert soln1 to the data attribute value at the end of the 63 $ target block of intt. 64 $ propagate through the target block of intt; if 65 $ intt = outint, the trick noted above will make the following 66 $ statement a no-op. 67 soln1 := f([intt, nodes(1)]) .of soln1; 68 69 $ if code motion is also required, then update insert{intt} 70 $ and add it to soln1. 71 if move_code and intt /= outint then 72 insert{intt} := insert{intt} - soln1; 73 soln1 := soln1 + insert{intt}; 74 end if; 75 76 $ now propagate attributes to the nodes of intt 77 (forall u in nodes) 78 soln(u) := aux_f(u) .of soln1; 79 end forall; 80 81 end forall; 82 83 84 end procedure fwd_propagate_in; 85 86 1 .=member afa13t 2 3 4 procedure intraproc_fwd_analysis(p, rw f, wr soln, id_prm, zero_prm, 5 meet_flag_prm, move_code, 6 rw exposed, wr insert, safe); 7$ 8$ this is the master routine to perform a specific data flow 9$ analysis intraprocedurally for a given routine p, within which 10$ local variables are analyzed. 11$ 12$ for more details and comments and description of parameters see the 13$ corresponding interprocedural analyser. 14$ 15 16 repr 17 $ data structures for parameters 18 p: routine; 19 f: remote smap(df_edge) df_map; 20 soln: remote smap(df_node) df_elmt; 21 id_prm: df_map; 22 zero_prm: df_elmt; 23 meet_flag_parm: boolean; 24 move_code: boolean; 25 exposed: remote mmap{df_node} df_elmt; 26 insert: remote mmap{df_node} df_elmt; 27 safe: remote mmap{df_node} df_elmt; 28 29 $ data structures for local variables 30 aux_f: remote smap(df_node) df_map; 32 end repr; 33 34 id := id_prm; 35 meet_flag := meet_flag_prm; 36 37 aux_f := {}; 38 39 intraproc_fwd_eliminate(p, aux_f, f, 'intra'); 42 43 if move_code then 44 insert := {}; 45 propagate_exposed(p, f, aux_f, exposed, insert, safe); 46 end if; 47 48 soln := {}; 49 fwd_propagate_in(p, f, aux_f, soln, zero_prm, move_code, insert); 50 51 end procedure intraproc_fwd_analysis; 52 53 54 55 56 procedure interproc_back_analysis(rw f, wr soln, id_prm, zero_prm, 57 meet_flag_prm); 58$ 59$ this is the master routine for performing a specific interprocedural 60$ backward data flow analysis. see the corresponding forward routine 61$ for general comments and a description of parameters. here we comment 62$ only on differences between the forward and backward algorithms, which 63$ are as follows: 64$ 65$ a. functional composition must be computed in reverse order. 66$ 67$ b. the auxiliary maps used in backward analysis are defined as 68$ follows: let i be an interval, u a node in i and v a node outside 69$ i which is a successor of a node in i. then aux_f([u, v]) is 70$ defined to be the propagation effect experienced as control 71$ advances from the start of u, through i, to the start of v. 72$ 73$ to compute this map requires iterating through i in reverse 74$ interval order three times (if i is proper) or till convergence 75$ otherwise. 76$ 77$ since the outermost interval of a procedure p has no successors, 78$ we regard the blocks rexit(p) and rstop(p) as its successors, 79$ 'hidden' inside that interval. this is needed to enable us to 80$ record the effect of the flow through the outermost interval in 81$ a manner similar to that used for inner intervals. 82$ 83$ c. in backward analysis we perform an extra step after the 84$ elimination phase. in this step we compute an additional set 85$ 'fexit' of auxiliary maps. for each node u in p, fexit(u) 86$ represents the propagation effect of the flow from the start 87$ of u to the return block of p, combined with that of flow from 88$ the start of u to the stop block of p. 89$ 90$ d. in our backward analysis code motion issues are completely 91$ ignored. 92$ 93$ e. the technical problem concerning endless loops discussed in 94$ section 5 is assumed to be resolved by preliminary processing 95$ of the flow graph, in the manner suggested there. 96$ 97 repr 98 $ data structures for parameters 99 f: remote smap(df_edge) df_map; 100 soln: remote smap(df_node) df_elmt; 101 id_prm: df_map; 102 zero_prm: df_elmt; 103 meet_flag_prm: boolean; 104 105 $ data structures for local variables 106 aux_f: remote smap(df_edge) df_map; 107 fexit: remote smap(df_node) df_map; 108 p: routine; 109 ex_inf: remote smap(routine) df_elmt; 110 end repr; 111 112 $ transfer constant parmeters to globals 113 id := id_prm; 114 zero := zero_prm; 115 meet_flag := meet_flag_prm; 116$ 117$ this master procedure consists of the following four phases: 118$ 119$ 1. interprocedural elimination phase 120$ 121 aux_f := interproc_back_eliminate(f); 122$ 123$ 2. compute auxiliary fexit maps. 124$ 125 fexit := {}; 126 (forall p in routs) 127 intra_aux_eliminate(p, f, aux_f, fexit); 128 end forall p; 129$ 130$ 3. find data at procedure exits 131$ 132 ex_inf := exit_info(f, aux_f, fexit); 133$ 134$ 4. final propagation phase 135$ 136 soln := {}; $ initialize the solution 137 (forall p in routs) 138 back_propagate_in(p, fexit, soln, ex_inf(p)); 139 end forall; 140 141 end procedure interproc_back_analysis; 142 143 1 .=member ebe13u 2 3 4 procedure interproc_back_eliminate(rw f); 5$ 6$ this is the driver routine for the interprocedural first 7$ inner-to-outer interval pass. procedures are analyzed in 8$ the following order: we process the strongly connected 9$ components of the call graph in their postorder; then, for each 10$ such component, we iterate through its procedures in their 11$ postorder, no more than 2*d+1 times, where d is the loop- 12$ interconnectedness parameter of the component. 13$ 14 repr 15 $ data structures for parameters 16 f: remote smap(df_edge) df_map; 17 18 $ data structures for local variables 19 aux_f: remote smap(df_edge) df_map; 20 f_p: remote smap(routine) df_map; 21 i: integer; 22 scc: routine; 23 scc_procs: tuple(routine); 24 flow_flag: string; 25 j: integer; 26 proc_converge: boolean; 27 k: integer; 28 p: routine; 29 end repr; 30 31 aux_f := {}; $ initialize auxiliary maps 32 f_p := {}; $ propagation effect thru procedures 33 34 $ iterate through the s.c.c.s of cgraph 35 (forall i in [ #cg_sccs, #cg_sccs-1..1 ]) 36 scc := cg_sccs(i); $ get a s.c.c. 37 scc_procs := scc_nodes(scc); $ procs in that s.c.c. 38 flow_flag := 'first_inter'; $ first processing of the scc 39 40 (forall j in [ 1..2*scc_d(scc)+1 ] ) 41 proc_converge := true; 42 43 (forall k in [ #scc_procs, #scc_procs-1..1 ]) 44 p := scc_procs(k); 45 proc_converge := 46 intraproc_back_eliminate(p,aux_f,f,f_p,flow_flag) 47 and proc_converge; 48 $ this routine analyzes p; its fifth parameter 49 $ indicates whether the analysis is first-time 50 $ interprocedural, second-time interprocedural or 51 $ intraprocedural; it returns a flag to indicate 52 $ whether information has stabilized in p. 53 end forall k; 54 55 flow_flag := 'second_inter'; $ additional passes thru scc 56 57 if proc_converge then quit forall j; end if; 58 end forall j; 59 end forall i; 60 61 return aux_f; 62 63 end procedure interproc_back_eliminate; 64 65 1 .=member abe13v 2 3 4 procedure intraproc_back_eliminate(p, rw aux_f, rw f, rw f_p, 5 flow_flag); 6$ 7$ this routine performs an intraprocedural elimination phase of a 8$ backward data flow analysis for a given procedure p. 9$ 10$ the overall logic is quite similar to its sister routine 11$ 'intraproc_fwd_eliminate', and the reader should consult comments 12$ given there. the differences between these two phases reflects 13$ mainly the reverse tracing of flow, which implies several minor 14$ modifications of the forward approach, as follows: 15$ 16$ a. auxiliary information is computed for each successor of each 17$ interval. that is, for each interval i, each node u in i, and 18$ each successor node v of i, we compute a map aux_f([u, v]), 19$ representing the flow from u through i to v. the outermost 20$ interval has no successors, but we regard the return block of p 21$ and the stop block of p (if any) as its two successors, even 22$ though they lie within it. (note that since these two nodes 23$ can never lie on a cycle through p, they must belong to the 24$ outer-most interval). 25$ 26$ b. nodes of an interval are processed in reverse interval order 27$ (i.e. postorder); this is done three times if i is a proper 28$ inner interval, once if i is a proper outer-most inteval, and 29$ till convergence otherwise. 30$ 31$ c. functional composition is taken in reverse edge order. 32$ 33$ d. an additional data structure f_p is used to hold the propagation 34$ effect through the whole procedure in the interprocedural case. 35$ this is because the flow through p is actually combined of two 36$ flows: one leading to the return block of p, and another leading 37$ to the stop block of p, if any. unlike in the forward case, 38$ where the second flow can be, and is, actually ignored, here we 39$ must take it into account. 40$ 41 repr 42 $ data structures for formal parameters 43 p: routine; 44 aux_f: remote smap(df_edge) df_map; 45 f: remote smap(df_edge) df_map; 46 f_p: remote smap(routine) df_map; 47 flow_flag: *; 48 49 $ data structures for local variables 50 need_process: set(df_node); 51 intt: df_node; 52 c: df_node; 53 v: df_node; 54 p1: routine; 55 p_ints: tuple(df_node); 56 outint: df_node; 57 sp: df_node; 58 k: integer; 59 nodes: tuple(df_node); 60 head: df_node; 61 cesors: sparse set(df_node); 62 conv_control: boolean; 63 n_iter: integer; 64 nd: df_node; 65 d: integer; 66 convrgd: boolean; 67 j: integer; 68 ftemp: df_map; 69 snd: df_node; 70 fzero: df_elmt; 71 end repr; 72 73 74 if flow_flag = 'second_inter' then 75 $ process only intervals containing calls with new effects 76 need_process := {}; 77 else 78 $ process all intervals 79 need_process := { intt : intt in ints(p)}; $ convert to set 80 end if; 81 82 if flow_flag /= 'intra' then $ interprocedural analysis 83 (forall c in callsin{p}) 84 v := cessor(c); $ the block following the call 85 p1 := callproc(c); $ c calls p1 86 87 $ (note that if this routine is modified to include 88 $ parameter-passing assignments as part of call blocks, 89 $ in the manner mentioned above, then one might manipulate 90 $ f_p(p1), the local effect of executing p1, to get 91 $ f([c, v]), rather than just assign f_p(p1) 92 $ to f([c, v]), as is done below). 93 94 if f([c, v]) /= f_p(p1) then 95 $ update flow function for call 96 f([c, v]) := f_p(p1) ? fom; 97 98 $ interval containing call must be processed 99 need_process with:= intof(c); 100 end if; 101 end forall c; 102 103 $ if no intervals need be processed then information has 104 $ stabilized and no re-processing of p need be done. 105 if need_process = {} then return true; end if; 106 end if; 107 108 109 p_ints := ints(p); $ intervals of p in reverse preorder 110 outint := p_ints(#p_ints); $ outermost interval 111 vedges{outint} := {rexit(p)}; $ 'successors' of outint 112 if (sp := rstop(p)) /= om then 113 vedges{outint} with:= sp; 114 end if; 115 116 (forall intt = p_ints(k) | intt in need_process) 117 need_process with:= intof(intt); $ process containing interva 118 nodes := int_nodes(intt); $ nodes of intt in interval order 119 head := nodes(1); $ interval head 120 121 $ get successor nodes 122 cesors := vedges{intt}; 123 124 $ initialize aux_f for successor nodes. this trick simplifies 125 $ subsequent code considerably. 126 (forall v in cesors) 127 aux_f([v, v]) := id; 128 end forall v; 129 130$ 131$ three cases are now possible: 132$ a. intt is proper, but not outermost; then iterate three times. 133$ b. intt is proper, and is outermost; then iterate once. 134$ c. intt is improper; iterate indefinitely (1 + 2*number of 135$ nodes is an adequate upper bound) until convergence. (here, 136$ again, a better bound can be used; cf. section 6). 137$ 138 $ we test for convergence only for improper intervals. 139 conv_control := intt notin proper_ints; 140 141 n_iter := $ maximal number of iterations thru nodes of intt 142 if intt notin proper_ints then 1 + 2 * #nodes 143 elseif intt = outint then 1 else 3 end; 144 145 (forall nd in nodes, v in cesors | nd /= v) 146 aux_f([nd, v]) := fom; 147 end forall; 148 149 $ iterate through the nodes of intt. 150 (forall d in [ 1..n_iter ]) 151 convrgd := conv_control; 152 153 $ iterate thrugh the nodes of intt in reverse interval 154 $ order. 155 (forall j in [ #nodes, #nodes-1..1 ]) 156 nd := nodes(j); 157 158 (forall v in cesors | v /= nd) 159 160 $ since the 'successors' of the outermost interval 161 $ are nodes of that interval, we may have nd = v. 162 $ in this case it would be erroneouss to compute 163 $ aux_f([nd, v]) (which has already been set to 164 $ id) using the following 'propagation from 165 $ successors' formula, so we just skip such cases. 166 167 ftemp := fom; 168 (forall snd in cessor{nd} | 169 intof(snd) = intt or snd = v) 170 ftemp .meetjoin:= 171 (f([nd,snd]) .comp aux_f([snd,v])); 172 end forall; 173 174 $ note that flow graph edges (virtual or real) are 175 $ either edges within an interval, linking two 176 $ nodes in the same interval, or edges going out 177 $ of an interval, or edges going into an interval 178 $ (these last edges are edges from (a target block 179 $ of) an interval to its head. it is this third 180 $ kind of edge that we wish to avoid propagating 181 $ through in the above formula. 182 $ 'intof(snd) = intt' tests for internal edges 183 $ and 'snd = v' tests for outgoing edges whose 184 $ target is v. 185 186 convrgd and:= (ftemp = aux_f([nd, v])); 187 aux_f([nd, v]) := ftemp; 188 end forall; 189 end forall; 190 191 if convrgd then quit forall d; end if; 192 end forall; 193 $ (note that no special handling of intt's head is required.) 194 195 $ except for the outermost interval, compute f([intt, v]), 196 $ where v is a successor of some node in intt. 197 if intt /= outint then 198 $ f([intt, v]) is trivially calculated in this case; we 199 $ also remove the dummy aux_f([v, v]) entries. 200 (forall v in cesors) 201 f([intt, v]) := 202 f([intt, head]) .comp aux_f([head, v]); 203 aux_f([v, v]) := om; 204 end forall; 205 end if; 206 end forall; 207 208 f_p(p) := aux_f([head, rexit(p)]); $ head = rentry(p) 209 210 $ if p contains a stop block, calculate propogation effect to that 211 $ block and combine it with 'normal' flow effect. 212 if rstop(p) /= om then 213 fzero := aux_f([head, rstop(p)]) .of zero; 214 f_p(p) := f_p(p) .meetjoin [fzero, fzero]; 215 $ note that a constant function c is represented by [c, c] 216 217 end if; 218 219 $ remove artificial edges added earlier 220 vedges{outint} := {}; 221 222 return false; $ to indicate no convergence 223 224 end procedure intraproc_back_eliminate; 225 226 1 .=member axe13w 2 3 4 procedure intra_aux_eliminate(p, f, aux_f, rw fexit); 5$ 6$ this procedure performs an additional intraprocedural elimination, 7$ during which we compute, for each node n in p, a map fexit(n) repre- 8$ senting the effect of flow from the start of n up to an exit of p. 9$ 10 repr 11 $ data structures for formal parameters 12 p: routine; 13 f: remote smap(df_edge) df_map; 14 aux_f: remote smap(df_edge) df_map; 15 fexit: remote smap(df_node) df_map; 16 17 $ data structures for local variables 18 p_ints: tuple(df_node); 19 outint: df_node; 20 ep: df_node; 21 sp: df_node; 22 outnodes: tuple(df_node); 23 nd: df_node; 24 i: integer; 25 fzero: df_elmt; 26 ftemp: df_map; 27 j: integer; 28 intt: df_node; 29 cesors: sparse set(df_node); 30 nodes: tuple(df_node); 31 k: integer; 32 v: df_node; 33 end repr; 34 35 p_ints := ints(p); 36 outint := p_ints(#p_ints); 37 ep := rexit(p); 38 sp := rstop(p); 39$ 40$ first process nodes of outint 41$ 42 outnodes := int_nodes(outint); 43 44 (forall nd = outnodes(i)) 45 46 fexit(nd) := aux_f([nd, ep]); $ get the effect of flow to ep 47 48 if sp /= om then $ if there is also a stop block 49 50 fzero := aux_f([nd, sp]) .of zero; 51 ftemp := if fzero = xom then fom else [ fzero, fzero ] end; 52 fexit(nd) := fexit(nd) .meetjoin ftemp; 53 54 end if; 55 end forall nd; 56$ 57$ next process all remaining intervals in outer-to-inner order 58$ 59 (forall j in [ #p_ints-1, #p_ints-2..1 ]) 60 61 intt := p_ints(j); 62 cesors := vedges{intt}; 63 64 nodes := int_nodes(intt); 65 (forall nd = nodes(k)) 66 ftemp := fom; 67 (forall v in cesors) 68 ftemp .meetjoin:= (aux_f([nd,v]) .comp fexit(v)); 69 end forall; 70 fexit(nd) := ftemp; 71 end forall nd; 72 end forall j; 73 74 75 end procedure intra_aux_eliminate; 76 77 1 .=member xnf13x 2 3 4 procedure exit_info(f, aux_f, fexit); 5$ 6$ this function calculates and returns a mapping which sends 7$ each procedure p into the flow information available at exit 8$ from p. it is called (only in the interprocedural case) just 9$ before we begin the final outer-to-inner propagation phase. 10$ 11 repr 12 $ data structures for formal parameters 13 f: remote smap(df_edge) df_map; 14 aux_f: remote smap(df_edge) df_map; 15 fexit: remote smap(df_node) df_map; 16 17 $ data structures for local variables 18 cgf: smap( tuple(routine, routine) ) df_map; 19 p, q: routine; 20 c, c1: df_node; 21 ex_inf: remote smap(routine) df_elmt; 22 cgrinv: mmap(routine) routine; 23 i: integer; 24 scc: routine; 25 scc_procs: tuple(routine); 26 n: integer; 27 convrgd: boolean; 28 k: integer; 29 temp: df_elmt; 30 end repr; 31$ 32$ first we construct a map 'cgf' assigning, to each edge (p, q) of the 33$ call graph, a data-propagation map describing the propagation effect 34$ as control returns from the exit of q to p after any call in p to q, 35$ and then advances to the exit of p. 36$ 37 cgf := {}; 38 (forall [ p, q ] in cgraph) cgf([p,q]) := fom; end forall; 39 40 (forall q = callproc(c)) $ for all calls within all procedures 41 p := routof(c); $ [p, q] is an edge of the call graph 42 c1 := cessor(c); $ c1 is the block following c 43 cgf([p, q]) := cgf([p, q]) .meetjoin fexit(c1); 44 45 $ note that since we are dealing with a backward analysis, we 46 $ want to propagate data from the exit of the calling procedure 47 $ p to the exit of the called procedure q. this direction of 48 $ propagation, however, makes our problem a forward problem for 49 $ the call graph. 50 51 end forall; 52$ 53$ next we iterate through the call graph in 'invocation order', i.e. 54$ process the strongly connected components in reverse postorder and the 55$ set of procedures within each strongly connected components in reverse 56$ postorder also. 57$ 58 ex_inf := { [ p, xom ] : p in routs }; $ initialize solution 59 ex_inf(sym_main) := zero; 60 cgrinv := { [ p, q ] : [ q, p ] in cgraph }; 61 62 $ pick strongly-connected components in reverse postorder 63 (forall i in [ 2..#cg_sccs ]) 64 65 $ note that we assume here that the main program is non-recur- 66 $ sive, so that the first strongly-connected component of the 67 $ call graph consists of the main program only. thus we need 68 $ not process it, for the exit value of the main program is 69 $ already assumed known. 70 71 scc := cg_sccs(i); 72 scc_procs := scc_nodes(scc); $ procs in scc in rev. postorder 73 74 (forall n in [ 1..scc_d(scc)+1 ] ) 75 convrgd := true; 76 (forall p = scc_procs(k)) 77 temp := xom; 78 (forall q in cgrinv{p}) 79 temp .mjv:= (cgf([q,p]) .of ex_inf(q)); 80 end forall; 81 82 $ test for convergence 83 convrgd and:= (temp = ex_inf(p)); 84 ex_inf(p) := temp; 85 86 end forall p; 87 88 if convrgd then quit forall n; end if; 89 end forall n; 90 end forall i; 91 92 return ex_inf; 93 94 end procedure exit_info; 95 96 1 .=member bpi13y 2 3 4 procedure back_propagate_in(p, fexit, rw soln, ex_val); 5$ 6$ this procedure performs outer-to-inner back propagation for 7$ a routine p, using the 'fexit' information. ex_val is the flow 8$ information assumed (or known) at the procedure return block, 9$ where 'zero' is always assumed at the stop block of p (but this 10$ assumption has already been used in calculating the fexit maps). 11$ 12 repr 13 $ data structures for formal parameters 14 p: routine; 15 fexit: remote smap(df_node) df_map; 16 soln: remote smap(df_node) df_elmt; 17 ex_val: df_elmt; 18 19 $ data structures for local variables 20 intt: df_node; 21 u: df_node; 22 end repr; 23 24 (forall intt in ints(p), u in int_nodes(intt)) 25 soln(u) := fexit(u) .of ex_val; 26 end forall; 27 28 end procedure back_propagate_in; 29 30 31 32 33 procedure intraproc_back_analysis(p, rw f, wr soln, 34 id_prm, zero_prm, meet_flag_prm); 35$ 36$ this is the master routine to perform a specific backward data flow 37$ analysis intraprocedurally for a routine p whose local variables are 38$ to be analyzed. for more details, comments, and description of para- 39$ meters see the corresponding interprocedural analyser. 40$ 41 repr 42 $ data structures for formal parameters 43 p: routine; 44 f: remote smap(df_edge) df_map; 45 soln: remote smap(df_node) df_elmt; 46 id_prm: df_map; 47 zero_prm: df_elmt; 48 meet_flag_prm: boolean; 49 50 $ data structures for local variables 51 aux_f: remote smap(df_edge) df_map; 52 f_p: remote smap(routine) df_map; 54 fexit: remote smap(df_node) df_map; 55 end repr; 56 57 id := id_prm; 58 zero := zero_prm; 59 meet_flag := meet_flag_prm; 60 61 aux_f := {}; f_p := {}; 62 63 intraproc_back_eliminate(p, aux_f, f, f_p, 'intra'); 65 66 fexit := {}; 67 intra_aux_eliminate(p, f, aux_f, fexit); 68 69 soln := {}; 70 back_propagate_in(p, fexit, soln, zero); 71$ 72$ note that in the intraprocedural case the last two procedures 73$ can be combined to form a single procedure almost identical 74$ with 'intra_aux_eliminate', except that this procedure computes the 75$ 'soln' map directly instead of the 'fexit' maps. 76$ 77 end procedure intraproc_back_analysis; 78 79 80 81$ 82$ here are the operators which manipulate the data propagation maps 83$ and data states. 84$ 85 op .comp(g, f); $ functional composition g of f 86 87 if f = fom or g = fom then 88 return fom; 89 else 90 return [ f(1) * g(1) + g(2), f(2) * g(1) + g(2) ]; 91 end if; 92 93 end op .comp; 94 95 96 op .meetjoin(g, f); $ functional meet or join 97 98 if f = fom then return g; 99 elseif g = fom then return f; 100 elseif meet_flag then return [ f(1) * g(1), f(2) * g(2) ]; 101 else return [ f(1) + g(1), f(2) + g(2) ]; 102 end if; 103 104 end op .meetjoin; 105 106 107 op .mjv(x, y); $ meet or join of lattice elements 108 109 if x = xom then return y; 110 elseif y = xom then return x; 111 elseif meet_flag then return x * y; 112 else return x + y; 113 end if; 114 115 end op .mjv; 116 117 118 operator .of(f, x); $ functional application 119 return if x = xom or f = fom then xom 120 else f(1)*x + f(2) 121 end; 122 end operator .of; 123 124 125 drop 126 .comp, 127 interproc_fwd_analysis, 128 intraproc_fwd_analysis, 129 interproc_back_analysis, 130 intraproc_back_analysis, 131 fom, 132 xom; 133 134 135 end module setl_optimizer - dataflow_solver_ocrs; 136 137 1 .=member tfnd14 2$ 3$ this module determines the 'type' of every occurrence in the program. 4$ its output is a map called 'typ' which sends each occurrence into a 5$ tuple with the following fields: 6$ 7$ 1. grosstyp 8$ 9$ a set of strings indicating all possible 'real types' an 10$ object might take on over different program paths. 11$ 12$ the 'real type' of an occurrence is a string such as 'int' 13$ or 'real' which we might get by applying the setl 'type' 14$ operator to the occurence. the possible real types are: 15$ 'int', 'real', 'string', 'atom', 'tuple', and 'set'. 16$ 17$ 2. comptyp 18$ 19$ this field contains recursive information on the component 20$ types of sets and tuples. it is interpreted in one of two 21$ ways depending on the value of 'is_knt' (see below). 22$ 23$ is_knt = true: comptyp is a tuple whose i-th component is a 24$ type descriptor for the i-th component type. 25$ 26$ is_knt = false: comptyp is a type descriptor for the component 27$ type of the set or tuple. 28$ 29$ 3. is_knt 30$ 31$ a boolean predicate which is true for known-length tuples. 32$ 33$ 4. is_om 34$ 35$ this is a three valued field, indicating whether the object 36$ is definitely omega, definitely not omega, or possibly omega. 37$ the values of this field are given by the constants yes, no, 38$ and maybe. 39$ 40$ is_om is used as follows: 41$ 42$ a. a set or tuple is considered to be null iff its component 43$ type is definitely omega. 44$ 45$ b. a tuple is considered to be a pair iff its first 2 components 46$ are definitely not omega and its remaining components 47$ definitely are. 48$ 49$ c. a set can be stored as a map iff its elements are definitely 50$ pairs. 51$ 52$ the type finder exports three predicates for making the above 53$ tests. these predicates are called is_null, is_pair, and 54$ is_map. like the is_om field they have values of yes, no, and 55$ maybe. 56$ 57$ at the end of the algorithm the type of each ovariable is defined by 58$ the instruction which defines it, while the type of each ivariable 59$ is a function of its use and the definitions and uses of all 60$ occurrences it is linked to. 61$ 62$ we may wind up with two occurrences 'o' and 'i' which are linked by 63$ bfrom but have different types. this means that the code generator 64$ must insert a conversion along the path from 'o' to 'i'. the type 65$ fider checks whether such a conversion would be legal, and gives an 66$ error message if it would not. 67$ 1 .=member tla14a 2 3 4$ the type lattice 5$ ----------------- 6 7$ the best way to understand the type finder is to think about 8$ a simplified version in which type descriptors are pairs 9$ [ grosstyp, comptyp ]. these simple type descriptors form a lattice 10$ of information states, in which 'vagueness' increases towards the 11$ top. 12$ 13$ before studying the actual algorithm we will review a few things 14$ about the lattice: 15$ 16$ 1. the basic points on the lattice correspond to the standard types 17$ such as int, real, and string. the set of type descriptors with a 18$ given grosstyp form a sublattice. the lattice is essentially 19$ infinite. however, we can make the lattice finite by enforcing a 20$ nesting limit on type descriptors, and by treating all known-length 21$ tuples beyond a certain length as unknown length tuples. 23$ 24$ 2. the basic types such as int and real are somewhere near the bottom 25$ of the lattice. 'union' types are somewhere near the top. 27$ 28$ 3. the lattice has a maximal type, called 'type_gen'. this point 29$ corresponds to the union of all types. types belonging to this 30$ grosstyp are always represented by a set containing all other 31$ basic types. 32$ 33$ 4. the lattice also has a minimal type, called 'type_zero'. 34$ the significance of type_zero is different in different 35$ parts of the algorithm. 36$ 37$ at the end of the first or 'forward' pass an occurrence with 38$ type_zero corresponds to a use of an uninitialized variable. 39$ at the end of the second or 'backward' pass an occurrence 40$ with type_zero corresponds to the result of an instruction 41$ whose input types are incompatible. 42$ 43$ note that type_zero does not correspond to omega. 44$ 45$ 5. the 'conjunction'('and', 'intersection') of two lattice elements 46$ is always a point lower in the lattice. the 'disjunction'('or', 47$ 'union') is always a point higher on the lattice. 48$ 49$ problems with the simplified type finder 50$ ---------------------------------------- 51$ 52$ the type descriptors discussed above give relatively crude information 53$ about the type of an occurrence. there are several additional 54$ attributes which one might consider part of a 'type'. these include: 56$ 57$ 1. whether an occurrence is definitely om, possibly om, or definitely 58$ not om. this information is useful since it allows us to distin- 59$ guish between pairs and non-pairs, and thus between sets and maps. 61$ 62$ 2. the range of values for integers. 63$ 64$ (1) is extremely important: we cannot do automatic data structure 65$ selection without it. (2) is less important, and is currently 66$ ignored. 67$ 68$ the extra attributes can be saved separately, i.e. as separate fields 69$ of a type descriptor, or can be added as new points on the lattice. 70$ at first the latter solution seemed to make the lattice too complex 71$ to understand, but eventually was chosen because it is more elegant 72$ and also simplifies the the algorithm. 73$ 74$ 75$ basic algorithm 76$ --------------- 77$ 78$ the type finder consists of three passes, called the forward, 79$ backward, and final passes. the forward and backward passes do the 80$ actual type determination using standard workpile propagation 81$ algorithms. the final phase does cleanup and error detection. 82$ 83$ the forward pass starts by initializing the types of all ivariables 84$ whose values are known. it then calculates the type of every 85$ ovariable as a function of its ivariables and the operation which 86$ creates them. it calculates the type of ivariables i as the 87$ disjunction of the types of all oi in bfrom{i}. 88$ 89$ the second or backwards pass refines the information gathered by the 90$ forward pass. for example, if we have: 94$ 95$ (1) x := i + j; 96$ (2) t := [1, 2]; 97$ (3) print(t(i)); 98$ 99$ then the forward pass will give us: 100$ 101$ typ(x1) = typ(i1) = typ(j1) = int, real, string, tuple, or set. 102$ typ(t2) = tuple 103$ typ(t3) = tuple 104$ typ(i3) = int, real, string, tuple, or set. 105$ 106$ backwards analysis will tell us that if the program is correct then 107$ typ(i3) and therefore typ(i1) must be integer. furthermore, typ(x1) 108$ and typ(j1) must be integer as well. 109$ 110$ as another example, 111$ 112$ (4) read(a); 113$ (5) b := a + '1'; 114$ 115$ here forward analysis will tell us that typ(b5) is string, and 116$ backward analysis will tell us that typ(a5) and therefore typ(a4) must 117$ also be string. clearly we must check the type of a somewhere between 118$ between the time it is read and the time it is used. the mechanism 119$ for inserting this type test is as follows: 121$ 122$ in the final version of the "types" map returned by the type finder, 123$ the type of an ivariable will be its backward type, whereas the type 124$ of an ovariable will be the type determined only by its forward 125$ propagation of the final types of its ivariables. (thus, e.g., read 126$ ovariables will still have type general.) thus a4 will receive a 127$ type of general, and a5 will receive a type of string. when name 128$ splitting is performed, we will see that it is necessary to split a 129$ into two separate variables, "a" with type general, and "a.1" with 130$ type string. we will then insert an assignment "a.1 := a" at some 131$ optimal point between (4) and (5). 133$ 1 .=member tmn14b 2 3 4 module setl_optimizer - typfind; 5 6 7 var vtyp, $ maps variables to their given type smff 1 work, $ work pile for forward and backward propagation smff 3 maxtype_sargs, $ maps system routine arguments to their smff 4 $ upper bounds. 8 stck, $ stack of occurrences in depth-first search 9 npre, $ preorder index in depth first search 10 preorder, $ preorder map in depth first search 11 nstck, $ length of stack 12 place, $ virtual place in stack of component roots 13 nlev, $ level number for ocuurrences in search 14 occ_sccs, $ tuple of strongly-connected comps 15 occ_graph, $ flow graph of occurrences 16 oscc_nodes; $ maps each s.c.c. to a tuple of its nodes 17 $ in reverse postorder 18 19 const $ various gross types 20 grstup = { t_tuple }, 21 grsset = { t_set }, 22 grsmap = { t_map }; 23 24 25 repr 26 vtyp: sparse smap(symbol) elmt types; smff 2 work: set(tuple(string, general)); smff 5 maxtype_sargs: sparse smap(symbol) tuple(elmt types); 27 stck: tuple(occurrence); 28 npre: integer; 29 preorder: remote smap(occurrence) integer; 30 nstck: integer; 31 place: remote smap(occurrence) integer; 32 nlev: remote smap(occurrence) integer; 33 occ_sccs: tuple(occurrence); 34 occ_graph: remote mmap(occurrence) occurrence; 35 oscc_nodes: smap(occurrence) tuple(occurrence); 36 grstup, grsset, grsmap: gross_type; 37 38 .con: operator(elmt types, elmt types) 39 elmt types; 40 .dis: operator(elmt types, elmt types) 41 elmt types; 42 .sub: operator(elmt types, elmt types) 43 elmt types; 44 45 type_forward: procedure; 46 type_backward: procedure; 47 type_final: procedure; 48 49 forward: procedure(occurrence) elmt types; 50 backward: procedure(elmt insts) 51 tuple(elmt types); 52 type_constant: procedure(elmt insts) string; smfe 28 smfe 29 string_length: procedure(elmt types) integer; smfd 3 constant_equality: procedure(elmt types, elmt types) smfd 4 boolean; 53 54 given_type: procedure(elmt forms) elmt types; 55 dfs: procedure(occurrence); 56 const_typ: procedure(general) elmt types; 57 ntyp: procedure( 58 gross_type, 59 elmt types ) 60 elmt types; 61 knt_type: procedure( tuple(elmt types) ) 62 elmt types; 64 pair_type: procedure(elmt types, elmt types) 65 elmt types; 66 trim: procedure(elmt types, integer) 67 elmt types; 68 norm: procedure(elmt types); 69 ads_type: procedure(elmt types) elmt types; 70 end repr; 71 72 73 procedure type_find; 74$ 75$ this is the main routine of the type finder. it initializes various 76$ tables then calls the individual passes of the type finder. 78$ 79 repr smfg 86 r: routine; smfg 87 b: elmt blocks; smfg 88 i1, i2: elmt insts; smfg 89 oi, vox, voy: occurrence; smfg 90 tp: elmt types; 83 end repr; 84 85 title('cims.setl.' + prog_level + ' - type finder'); 86 printa(term_file, ' - type finder'); 87 90 $ initialize output object 91 typ := {}; 92 93 $ initialize maps global to the module smff 6 vtyp := {}; work := {}; maxtype_sargs := {}; 95 96 type_forward; $ forward analysis 97$$++ smfc 607 if 't' in dump_string and 'z' in dump_string then 99 print; 100 print('variable occurrence forward type'); 101 print('--------------------------------------'); 102 prints('', 103 [ [ rpad(oi_name(oi),12) + ' ' + rpad(oi_str(oi),12), 104 format_type(tp) ] : tp = typ(oi) ] ); 105 end if; 106$$-- 107 type_backward; $ backward analysis 108$$++ smfc 608 if 't' in dump_string and 'z' in dump_string then 110 print; 111 print('variable occurrence backward/forward type'); 112 print('-----------------------------------------------'); 113 prints('', 114 [ [ rpad(oi_name(oi),12) + ' ' + rpad(oi_str(oi),12), 115 format_type(tp) ] : tp = typ(oi) ] ); 116 end if; 117$$-- 118 type_final; $ check for type errors and finalize 119 $ the type of all o-variables. 120$$++ smfc 609 if 't' in dump_string and 'z' in dump_string then 122 print; 123 print('variable occurrence final type'); 124 print('------------------------------------'); 125 prints('', 126 [ [ rpad(oi_name(oi),12) + ' ' + rpad(oi_str(oi),12), 127 format_type(tp) ] : tp = typ(oi) ] ); 128 end if; 129$$-- smfg 91 smfg 92 (forall r in routs) smfg 93 (for_block(b, r)) smfg 94 i1 := om; smfg 95 (for_inst(i2, b)) smfg 96 if opcode(i2) = q1_isom or opcode(i2) = q1_notom then smfg 97 vox := get_oi(i2, 1); smfg 98 (forall voy in bfrom{vox}) smfg 99 ffrom less:= [ voy, vox ]; smfg 100 (forall oi in ffrom{vox}) smfg 101 bfrom less:= [ oi, vox ]; smfg 102 bfrom with:= [ oi, voy ]; ffrom with:= [ voy, oi ]; smfg 103 end forall; smfg 104 if vox in bfrom_dead then bfrom_dead with:= voy; end if; smfg 105 end forall; smfg 106 bfrom lessf:= vox; ffrom lessf:= vox; bfrom_dead less:= vox; smfg 107 typ lessf:= vox; smfg 108 del_inst(i2, i1, b); smfg 109 end if; smfg 110 i1 := i2; smfg 111 end; $ end for_inst; smfg 112 end; $ end for_block; smfg 113 end forall; 130 131 (forall tp = typ(oi)) 132 typ(oi) := ads_type(tp); 133 end forall; 134 135 136 $ delete the static variables global to the module smff 7 vtyp := om; work := om; maxtype_sargs := om; 138 139 if 't' in dump_string then 142 print; 143 print('variable occurrence type'); 144 print('------------------------------'); 145 prints('', 146 [ [ rpad(oi_name(oi),12) + ' ' + rpad(oi_str(oi),12), 147 format_type(tp) ] : tp = typ(oi) ] ); 152 end if; 153 154 statistics with:= time; $ save time for final statistics 159 160 161 end procedure type_find; 162 163 1 .=member tyf14c 2 3 4 procedure type_forward; 5$ 6$ this is the first, or forward propagation phase of the type finder. 7$ it consists of two sections. the first section initializes the work 8$ pile, and the second propagates types forward through the program 9$ until the work pile is empty. 10$ 11$ work pile initialization 12$ ------------------------ 13$ 14$ the work pile is a set of pairs [ act, oi ] where oi is an occurrence 15$ whose type is to be calculated, and act is the method of calculating 16$ oi's type. 17$ 18$ 1. 'fwd': 19$ 20$ here oi is an ovariable, and we calculate its type as a function 21$ of the type of the inputs to the instruction which defines it. 23$ 24$ 2. 'bfrm' 25$ 26$ here oi is an ivariable, and we calculate its type as the 27$ disjunction of all i in bfrom{oi}. 28$ 29$ 30$ we initialize the workpile by iterating over all occurrences looking 31$ for three cases: 32$ 33$ 1. constant ivariables. here we simply set the type of the ivariable. 34$ 35$ 2. ovariables whose type is uniquely determined by its opcode. for 36$ example, if we have 't := # s' then t must be an integer. in this 37$ case we set the type of t and make workpile entries of the form 38$ ['bfrm', i], i in ffrom{t}. 39$ 40$ 3. ovariables which have at least one constant ivariable. here we add 41$ ['fwd', o] to the workpile. 42$ 43$ the type finder can also take into account user-supplied reprs. they 44$ are used as upper bounds for actual computed types. therefore only 45$ this forward typefinding phase (in which computed types 'grow' in 46$ their lattice) and the final type propagation phase need manipulate 47$ these given types; the backward propagation phase ignores these 48$ types, as it only tries to make types smaller in their lattice. 50$ 51$ we prepare for the handling of these types by computing a map 'vtyp', 52$ mapping each progam variable to its given type. this type is computed 53$ from the form of the variable by the recursive function 'given_type', 54$ in which some data-form details may be lost. 56$ smfe 30 init smfe 31 back_count := 0,$ counts the number of times backward is called. smfe 32 maxtype := {}; $ maps opcodes to the upper bound for its smfe 33 $ operand types. 57 repr 58 sc: elmt base_scopes; 59 v: symbol; 60 occ_roots: sparse set(occurrence); 61 r: routine; 62 b: elmt blocks; 63 i: elmt insts; 64 opc: elmt base_opcodes; 65 iva1: integer; 66 var_ivars: set(occurrence); 67 j: integer; 68 fm: elmt forms; 69 voj, vo1: occurrence; 70 nam: symbol; 71 t: elmt types; 72 e, oscc: occurrence; 73 fwd_count: integer; 74 bfrm_count: integer; smfe 34 back_count: integer; 75 x: occurrence; 76 onodes: tuple(occurrence); 77 newoccs: remote set(occurrence); 78 convrgd: boolean; 79 vo, oi: occurrence; 80 newtype, tp: elmt types; smfe 35 maxtype: remote smap(elmt base_opcodes) smfe 36 tuple(elmt types); smfe 37 newtypes: tuple(elmt types); smfe 38 k: integer; 81 end repr; 82 83 (forall sc in scopes) 84 (for_sym(v, sc)) 85 vtyp(v) := given_type(form(v)); 86 end; 87 end forall; smff 8 (forall v in system_routs) smff 9 $ redefine the return value type for system routines that cannot smff 10 $ return omega. smff 11 vtyp(rretn(v)) := smff 12 case name(v) of smff 13 ('open'): type_boolean, smff 14 ('eof'): type_boolean, smff 15 ('getipp'): type_int, smff 16 ('getspp'): type_string, smff 17 ('lpad'): type_string, smff 18 ('rpad'): type_string smff 19 else vtyp(rretn(v)) smff 20 end; smff 21 $ redefine the argument types for system routines that cannot be smff 22 $ omega. smff 23 maxtype_sargs(v) := smff 24 case name(v) of smff 25 ('read'): [ type_gen ], smff 26 ('print'): [ type_gen ], smff 27 ('reada'): [ type_string, type_gen ], smff 28 ('printa'): [ type_string, type_gen ], smff 29 ('get'): [ type_string, type_gen ], smff 30 ('getb'): [ type_string, type_gen ], smff 31 ('putb'): [ type_string, type_gen ], smff 32 ('getk'): [ type_string, type_gen ], smff 33 ('putk'): [ type_string, type_gen ] smff 34 else [ type_notom : j in [ 1..rnargs(v) ] ] smff 35 end; smff 36 end forall; 88$ 89$ initialize the work pile and build the occurrence flow graph 90$ 91 if 'e' in dump_string then 92 print('forward propagation'); 93 end if; 94 95 occ_graph := ffrom; 96 occ_roots := {}; 97 (forall r in routs) 98 (for_block(b, r)) 99 (for_inst(i, b)) 100 opc := opcode(i); 101 iva1 := first_ivar(opc); smfe 39 if opc in ops_typeback and opc /= q1_sargin then smfe 40 if opc = q1_set or opc = q1_tup then smfe 41 if maxtype(opc) = om or #maxtype(opc) < #args(i)-1 then smfe 42 maxtype(opc) := smfe 43 [ type_notom : j in [ iva1..#args(i) ] ]; smfe 44 end if; smfe 45 elseif maxtype(opc) = om then smfe 46 (forall j in [ 1..#args(i) ]) smfe 47 typ(get_oi(i, j)) := type_gen; smfe 48 end forall; smfe 49 maxtype(opc) := backward(i); back_count +:= 1; smfe 50 end if; smfe 51 end if; 102 var_ivars := {}; 103 (forall j in [ iva1..#args(i) ]) 104 voj := get_oi(i, j); 105 nam := oi_sym(voj); 106 if nam notin variables then 107 if ft_type(form(nam)) /= f_proc and 108 ft_type(form(nam)) /= f_lab then 109 typ(voj) := const_typ(value(nam)); 110 else 111 typ(voj) := type_zero; 112 end if; 113 else 114 typ(voj) := type_zero; 115 var_ivars with:= voj; 116 end if; 117 end forall; 118 if opc in ops_ovar then 119 vo1 := get_oi(i, 1); $ the o-variable 120 if (t := fixed_typ(opc)) /= om then 121 typ(vo1) := t .con vtyp(oi_sym(vo1)); 122 occ_roots with:= vo1; 123 elseif var_ivars = {} then 124 typ(vo1) := forward(vo1) .con vtyp(oi_sym(vo1)); 125 occ_roots with:= vo1; 126 else 127 typ(vo1) := type_zero; 128 occ_graph +:= { [ voj, vo1 ] : voj in var_ivars }; 129 end if; 130 end if; 131 end; 132 end; 133 end forall; 134$ 135$ build depth-first spanning tree for the occurrence flow graph 136$ 137 occ_sccs := []; $ strongly connected comp's of occurrence graph 138 oscc_nodes := {}; $ nodes of each strongly connected component 139 $ initialize auxiliary variables 140 stck := []; nstck := 0; place := {}; 141 preorder := {}; npre := 0; nlev := {}; 142 143 (forall e in occ_roots) dfs(e); end forall; 144 145 $ release storage for garbage collection 146 stck := om; place := om; 147 nlev := om; preorder := om; 148$ 149$ actual forward propagation 150$ 151 fwd_count := bfrm_count := 0; 152 newoccs := {} +/[ occ_graph{x} : x in occ_roots ]; 153 (forall j in [ #occ_sccs, #occ_sccs-1..1 ]) 154 oscc := occ_sccs(j); 155 onodes := oscc_nodes(oscc); 156 (until convrgd) 157 convrgd := true; $ flag indicating convergence in oscc 158 (forall vo in onodes | vo in newoccs) 159 convrgd := false; 160 newoccs less:= vo; 161 if is_ovar(vo) then 162 fwd_count +:= 1; 163 newtype := forward(vo) .con vtyp(oi_sym(vo)); 164 elseif is_ivar(vo) then 165 bfrm_count +:= 1; 166 newtype := type_zero .dis/ smfe 52 [ typ(vo1) : vo1 in bfrom{vo} ]; smfe 53 if maxtype(oi_op(vo)) /= om then smfe 54 k := argno(vo) - first_ivar(oi_op(vo)) + 1; smff 5 tp := newtype .con maxtype(oi_op(vo))(k); smff 6 if tp /= newtype then smff 7 $ record ffrom propagation for next phase. smff 8 $ note that the required bfrom propagation smff 9 $ will be done during this phase in the smff 10 $ workpile implicit in the o.s.c.c. graph. smff 11 work +:= { [ 'ffrm', oi ] : smff 12 oi in bfrom{vo} | smff 13 oi notin bfrom_dead }; smff 14 end if; smff 15 newtype := tp; smfe 56 end if; 168 else 169 continue forall; 170 end if; 171 if newtype /= typ(vo) then 172 typ(vo) := newtype; 173 newoccs +:= occ_graph{vo}; 174 end if; 175 end forall; 176 end until; 177 end forall; 178 179 $ release storage for garbage collection 180 occ_sccs := om; occ_graph := om; oscc_nodes := om; smfe 57 smfe 58 if 't' in dump_string and 'z' in dump_string then smfe 59 (forall newtypes = maxtype(opc)) smfe 60 print(opc); smfe 61 (forall tp in newtypes) print(' ', format_type(tp)); end; smfe 62 end forall; smfe 63 end if; 181 182 if 'e' in dump_string then 183 print(fwd_count, 'forward propagations'); smfe 64 print(back_count, 'backward propagations'); 184 print(bfrm_count, 'bfrom propagations'); 185 end if; 186 187 188 end procedure type_forward; 189 190 191 192 193 procedure given_type(fm); 194$ 195$ compute a type (lattice point) from a given form. see section 196$ 'forms' above for description of data forms. 197$ 198 repr 199 fm: elmt forms; 200 ftp: elmt base_ft_types; 201 fsimtp: string; 202 tp, ctp: elmt types; 203 fm1: elmt forms; 204 end repr; 205 206 ftp := ft_type(fm); 207 fsimtp := simple_type(ftp); 208 209 case fsimtp of 210 211 ('gen'): tp := type_gen; 212 ('int'): tp := type_int; 213 ('real'): tp := type_real; 214 ('string'): tp := type_string; 215 ('atom'): tp := type_atom; 216 ('elmt'): tp := given_type(ft_elmt(ft_base(fm))); 217 ('set'): tp := ntyp(grsset, given_type(ft_elmt(fm))); 218 219 ('map'): ctp := given_type(ft_elmt(fm)) .con type_pair; 220 tp := ntyp(grsset, ctp .dis type_om); 221 222 ('tuple'): 223 if ftp = f_mtuple then 224 tp := knt_type([ given_type(fm1) : fm1 in ft_elmt(fm) ]); 225 else 226 tp := ntyp(grstup, given_type(ft_elmt(fm))); 227 end if; 228 229 else 230 tp := type_zero; 231 end case; 232 233 return tp .dis type_om; 234 235 236 end procedure given_type; 237 238 239 240 241 procedure dfs(x); 242 243 244 repr 245 x, y, z: occurrence; 246 i: integer; 247 end repr; 248 249 250 preorder(x) := nlev(x) := (npre +:= 1); 251 place(x) := nstck; 252 (forall y in occ_graph{x}) 253 if nlev(y) = om then 254 dfs(y); 255 if nlev(y) /= -1 then 256 nstck +:= 1; 257 stck(nstck) := y; 258 end if; 259 end if; 260 if nlev(y) /= -1 then 261 nlev(x) := nlev(x) min nlev(y); 262 end if; 263 end forall; 264 if nlev(x) = preorder(x) then $ root of a strongly connected comp 265 occ_sccs with:= x; 266 oscc_nodes(x) := [ x ]; 267 (forall i in [ nstck, nstck-1..place(x)+1 ]) 268 z := stck(i); 269 oscc_nodes(x) with:= z; 270 nlev(z) := -1; 271 end forall; 272 nstck := place(x); 273 nlev(x) := -1; 274 end if; 275 276 end procedure dfs; 277 278 1 .=member tyb14d 2 3 4 procedure type_backward; 5$ 6$ this is the second, or backwards phase of the type finder. it 7$ propagates the type of each ivariable backwards towards its 8$ definition. 9$ 10$ as usual, the routine has two sections. the first initializes the 11$ work pile, and the second iterates until it is empty. 12$ 13 repr 15 r: routine; 16 b: elmt blocks; 17 inst: elmt insts; 18 opc: elmt base_opcodes; 19 fwd_count: integer; 20 back_count: integer; 21 bfrm_count: integer; 22 ffrm_count: integer; 23 key: string; 24 targ: general; 25 oi: occurrence; 26 newtype: elmt types; 27 i: occurrence; 28 newtypes: tuple(elmt types); 29 j, j1: integer; 30 tp: elmt types; 31 end repr; 32$ 33$ work pile initialisation 34$ ---- ---- -------------- 35$ 36$ the work pile once again consists of pairs [ key, targ ]. we 37$ initialize the work pile to force backwards propagation of all 38$ relevant ivariables. 39$ 40 if 'e' in dump_string then 41 print; 42 print('backward propagation'); 43 end if; 44 46 (forall r in routs) (for_block(b, r)) (for_inst(inst, b)) 47 if opcode(inst) in ops_typeback then 48 work with:= [ 'back', inst ]; 49 end if; 50 end; end; end; 51$ 52$ propagation 53$ ----------- 54$ 55$ in this phase we propagate types in four directions: 56$ 57$ 1. 'fwd': 58$ 59$ here we determine the type of an ovariable from those of its 60$ ivariables. this uses the same method as employed in type_forward. 62$ 63$ 2. 'back': 64$ 65$ this calculates the types of the ivariables of an instruction 66$ as a function of the types of the other arguments in the same 67$ instruction. 68$ 69$ 3. 'bfrm': 70$ 71$ this corresponds to the 'bfrm' action in type_forward. 72$ 73$ 4. 'ffrm': 74$ 75$ this calculates the type of an occurrence oi as the conjunction of smfh 21$ the types of all i in ffrom, provided, however, that no program 77$ exit can be reached from oi along a path clear of other occurrences 78$ of the variable of oi. 79$ 80 fwd_count := back_count := 0; 81 bfrm_count := ffrm_count := 0; 82 83 (while work /= {}) 84 [ key, targ ] from work; 85 86 case key of 87 88 ('fwd'): $ forward propagation 89 fwd_count +:= 1; 90 oi := targ; 91 newtype := forward(oi) .con typ(oi); 92 if newtype = typ(oi) then continue; end if; 93 work +:= { [ 'bfrm', i ]: i in ffrom{oi} }; 94 95 $ if this was not a unary operation, propagate back 96 $ to the remaining i-variables 97 if (opc := oi_op(oi)) notin ops_un and 98 opc in ops_typeback and 99 opc notin {q1_asn, q1_argin, q1_argout} then 100 work with:= [ 'back', instno(oi) ]; 101 end if; 102 103 typ(oi) := newtype; 104 105 ('bfrm'): $ propagate along bfrom 106 bfrm_count +:= 1; 107 oi := targ; 108 newtype := (type_zero .dis/[ typ(i) : i in bfrom{oi} ]) 109 .con typ(oi); 110 if newtype = typ(oi) then continue; end if; 111 112 work +:= { [ 'bfrm', i ] : i in ffrom{oi} }; 113 if oi_op(oi) in ops_ovar then 114 work with:= [ 'fwd', get_ovar(oi) ]; 115 end if; 116 117 typ(oi) := newtype; 118 119 ('ffrm'): $ propagate along ffrom 120 ffrm_count +:= 1; 121 oi := targ; 122 newtype := (type_zero .dis/[ typ(i) : i in ffrom{oi} ]) 123 .con typ(oi); 124 if newtype = typ(oi) then continue; end if; 125 126 if is_ovar(oi) then 127 if oi_op(oi) in ops_typeback then 128 work with:= [ 'back', instno(oi) ]; 129 end if; 130 else 131 work +:= { [ 'ffrm', i ] : 132 i in bfrom{oi} | i notin bfrom_dead }; 133 if oi_op(oi) in ops_ovar then 134 work with:= [ 'fwd', get_ovar(oi) ]; 135 end if; 136 end if; 137 138 typ(oi) := newtype; 139 140 ('back'): $ analogous to the 'fwd' case 141 back_count +:= 1; 142 inst := targ; 143 newtypes := backward(inst); 144 145 j1 := first_ivar(opcode(inst)); 146 (forall j in [ j1..#args(inst) ]) 147 oi := get_oi(inst, j); 148 newtype := newtypes(j-j1+1) .con typ(oi); 149 if newtype = typ(oi) then continue forall; end if; 150 151 work +:= { [ 'bfrm', i ] : i in ffrom{oi} }; 152 work +:= { [ 'ffrm', i ] : i in bfrom{oi} | 153 i notin bfrom_dead }; 154 155 typ(oi) := newtype; 156 end forall; 157 158 159 end case; 160 161 end while; 162 163 if 'e' in dump_string then 164 print(fwd_count, 'forward propagations'); 165 print(back_count, 'backward propagations'); 166 print(bfrm_count, 'bfrom propagations'); 167 print(ffrm_count, 'ffrom propagations'); 168 end if; 169 170 171 end procedure type_backward; 172 173 1 .=member tfn14e 2 3 4 procedure type_final; 5$ 6$ this is the final phase of the type finder. here we re-compute the 7$ type of each ovariable, from the types of its ivariables, so that we 8$ can detect precisely when a type check is necessary and when it is 9$ redundant, as explained above. 10$ 11$ we also check for type errors. they can be one of the following: 12$ 13$ 1. an occurence having a type type_zero. in this case, for every 14$ possible execution of the program, this occurence will have an 15$ error value (or the program will abort). this is therefore an 16$ error. 17$ 18$ 2. two occurences, linked by the bfrom map and having incompatible 19$ types, though none of them has the type type_zero. this means 20$ that there might be an execution flow, along which one of the 21$ occurences will get an error value, but this need not be the case 22$ for every execution. consider the following example: 23$ 24$ (1) a := [ 1, 2 ]; 25$ (2) if cond1 then a := 1; end if; 26$ (3) if cond2 then x := a(1); end if; 27$ 28$ here, after re-computation of the type of ovariables, a2 will have 29$ type integer, while a3 will have type tuple. it may indeed be the 30$ case that cond1 and cond2 can never be true simultaneously, in 31$ which case the program will never abort. if, however, the link 32$ a2 to a3 is ever materialized, we shall have an error. thus, in 33$ this case, the optimizer should issue a warning, but non-fatal 34$ message. 35$ 36$ begin by re-computing the types of all ovariables. 37$ note, however, that in operations like 's := {};' or 't := [];' 38$ we still want to retain the backward type of s to some extent. 39$ for example: 40$ (1) s := {}; 41$ ... 42$ (2) s with:= int; 43$ in this case we prefer to have typ(s at 1) = set(int) rather than 44$ set(general), which will force an unnecessary conversion/check 45$ between (1) and (2). 46$ smfi 71 init smfk 45 errvars := {}, $ variables with type_zero message. smfk 46 newstmt := false, newblk := false; 47 repr smfi 75 r: routine; smfi 76 b: elmt blocks; smfi 77 i: elmt insts; smfi 78 v: symbol; smfi 81 errvars: set(symbol); smfk 47 newstmt, newblk: boolean; smfi 82 k: integer 0..65536; smfi 83 smfi 84 o, oi: occurrence; 50 newtype: elmt types; smfe 65 tp, t1, t2: elmt types; 56 57 workoccs, seenoccs: set(occurrence); smfk 48 precoccs, reloccs: set(occurrence); smfk 49 tp_stmts: mmap{elmt types} set(string); smfi 85 relstmts: set(string); 61 vox, voy, vo1, vo2: occurrence; 62 ivs: tuple(occurrence); 66 message: tuple(string); smfi 86 text, tail: string; 68 j: integer 0..65536; 69 l: string; 70 end repr; 71 smfi 87 (forall r in routs) (for_block(b, r)) (for_inst(i, b)) smfi 88 if opcode(i) notin ops_ovar then continue; end if; smfi 89 o := get_oi(i, 1); $ get the output occurrence smfi 90 if opcode(i) = q1_asn or opcode(i) = q1_argin then smfi 91 v := args(i)(2); smfi 92 if is_const(v) = 1 and value(v) = {} then 77 newtype := smfi 93 (type_zero .dis/[ typ(oi) : oi in ffrom{o} ]) 79 .con type_set; 80 if newtype /= type_zero then typ(o) := newtype; end if; smfi 94 continue; 82 smfi 95 elseif is_const(v) = 1 and value(v) = [] then 84 newtype := smfi 96 (type_zero .dis/[ typ(oi) : oi in ffrom{o} ]) 86 .con type_tuple; 87 if newtype /= type_zero then typ(o) := newtype; end if; smfi 97 continue; 89 smfi 98 elseif v = sym_om then smfi 99 newtype := type_om .dis/[ typ(oi) : oi in ffrom{o} ]; 92 typ(o) := newtype; smfi 100 continue; 94 end if; 95 end if; smfi 101 typ(o) := (fixed_typ(opcode(i)) ? forward(o)) smfi 102 .con vtyp(args(i)(1)); smfi 103 end; end; end forall; 99 100 if not debug_flag then return; end if; 101 smfi 104 (forall r in routs) smfk 50 (for_block(b, r)) newblk := true; smfk 51 (for_inst(i, b)) if opcode(i) = q1_stmt then newstmt := true; end; smfk 52 (forall k in [ 1..#args(i) ]) smfi 108 if (v := args(i)(k)) notin variables then continue forall; end; smfk 53 if newstmt and newblk then smfk 54 errvars := {}; newstmt := false; newblk := false; smfk 55 end if; smfi 109 vox := get_oi(i, k); 103 104 if typ(vox) = type_zero then 105 106 $ some serious problem: the type finder could not deduce any 107 $ type for this occurrence. 108 smfk 56 if opcode(i) = q1_isom or opcode(i) = q1_notom then smfk 57 $ the occurrence will either have been added to errvars smfk 58 $ in the preceding q1_if, ..., or this block consists smfk 59 $ exactly this instruction. in either case we do not smfk 60 $ want to generate an additional message. smfk 61 continue forall; smfk 62 end if; smfk 63 smfk 64 if opcode(i) = q1_free then smfk 65 $ either the corresponding q1_argin occurrence has smfk 66 $ type_zero as well, or the invoked routine cannot smfk 67 $ be completed. in either case, we prefer to smfk 68 $ suppress the error message. smfk 69 errvars with:= oi_sym(vox); 111 continue forall; 112 end if; 113 114 if is_ovar(vox) then smfi 120 ivs := [ vo1 in occs(i)(2..) | smfk 70 typ(vo1) = type_zero smfk 71 and oi_sym(vo1) notin routs smfk 72 or typ(vo1) = type_om ]; smfi 123 if ivs = [] then ivs := occs(i)(2..); end if; 118 else 119 ivs := [ vox ]; 120 end if; 121 122 if exists vo1 in ivs | smfi 124 typ(vo1) = type_zero smfl 5 and OI_SYM(VO1) in ERRVARS then smfi 128 (forall vo1 in ivs) errvars with:= oi_sym(vo1); end; smfi 129 errvars with:= oi_sym(vox); 127 continue forall; 128 end if; smfk 76 smfk 77 if opcode(i) in ops_iter then smfk 78 if is_ivar(vox) and smfk 79 opcode(i) = q1_inext or opcode(i) = q1_inextd smfk 80 then smfk 81 t1 := type_zero smfk 82 .dis/[ typ(vo1) : vo1 in bfrom{vox} ]; smfk 83 messages{stmtof(i)}{'e'} with:= smfk 84 [ 'illegal iteration: "' + name(v) + '" ' smfk 85 + case t1 of smfk 86 (type_zero): 'cannot be evaluated.', smfk 87 (type_om): 'is undefined.' smfk 88 else 'is ' + format_type(t1) + '.' smfk 89 end smfk 90 ]; smfk 91 end if; smfk 93 errvars with:= oi_sym(vox); smfk 94 continue forall; smfk 95 end if; 129 130 message := [ if is_ovar(vox) then 131 'the evaluation of ' smfi 130 '"' + format_inst(i, om) + '"' 133 else smfi 131 'the use of "' + name(v) + '"' 135 end + 136 ' will always cause an execution error:' ]; 137 138 (forall voy = ivs(j)) 139 140 if typ(voy) = type_zero then 141 142 $ either none of the definitions preceding this 143 $ occurrence could be evaluated, or this occurrence 144 $ is part of a set of occurrences which, taken 145 $ together, form a set of inconsistent uses. 146 147 workoccs := { voy };$ workpile of prec. occurrences 148 seenoccs := {}; $ occurrences already seen 149 reloccs := { voy };$ relevant occurrences smfk 96 precoccs := {}; $ prec. non-error occurrences 150 151 (while workoccs /= {}) 152 vo1 from workoccs; seenoccs with:= vo1; smfk 97 (forall vo2 in bfrom{vo1}) smfk 98 if typ(vo2) = type_zero smfk 99 or typ(vo2) = type_om then smfk 100 reloccs with:= vo2; smfk 101 if vo2 notin seenoccs then smfk 102 workoccs with:= vo2; smfk 103 end if; smfk 104 else smfk 105 precoccs with:= vo2; smfk 106 end if; smfk 107 end forall; 161 end while; 162 smfi 134 workoccs := { vo1 in reloccs | is_ovar(vo1) smfi 135 and typ(vo1) = type_zero }; smfi 136 relstmts := { oi_stmt(vo1) : vo1 in workoccs }; smfi 137 smfi 138 if #relstmts = 0 then smfi 139 text := om; smfi 140 else smfi 141 text := '"' + oi_name(voy) + '"' smfi 142 ' cannot be evaluated at '; smfi 143 tail from relstmts; smfi 144 smfi 145 if #relstmts = 0 then smfi 146 text +:= tail; smfi 147 elseif #relstmts = 1 then smfi 148 text +:= arb relstmts + ' and ' + tail; smfi 149 else smfi 150 text := text +/[ l + ', ' : l in relstmts ]; smfi 151 text +:= 'and ' + tail; smfi 152 end if; smfi 153 end if; smfi 154 smfi 155 (while workoccs /= {}) smfi 156 $ remove all occurrences reachable from these smfi 157 $ definitions. smfi 158 vo1 from workoccs; reloccs less:= vo1; smfi 159 (forall vo2 in ffrom{vo1} | vo2 in reloccs) smfi 160 reloccs less:= vo2; workoccs with:= vo2; smfi 161 end forall; smfi 162 end while; smfi 163 smfi 164 $ next we find all occurrences which will cause smfi 165 $ type conflicts at this instruction. smfi 166 smfi 167 workoccs := { vo1 in reloccs | typ(vo1) = type_om }; smfi 168$$-- problem with statement numbers: if statements are numbered smfi 169$$-- linearly, then any reference to the (implicit) term block will smfi 170$$-- receive the statement number of the loop header; if statements smfi 171$$-- are numbered according to their block order, then any explicit smfi 172$$-- statement in an step/until/term block will cause the statement smfi 173$$-- numbering for the entire loop body to be off since these blocks smfi 174$$-- appear to the parser at the loop header, and are moved behind the smfi 175$$-- loop body. smfi 176$$-- the only know place where this causes problems is the use of an smfi 177$$-- exhausted loop index. smfi 178$$-- relstmts := { oi_stmt(vo1) : vo1 in workoccs }; smfi 179 smfi 180 if #workoccs /= 0 then smfi 181 if text = om then smfi 182 text := '"' + oi_name(voy) + '"' smfi 183 ' is om'; smfi 184$$-- ' is undefined at '; smfi 185 else smfi 186 text +:= ', and is om along some path'; smfi 187$$-- text +:= ', and is undefined at '; smfi 188 end if; smfi 189 smfi 190$$-- tail from relstmts; smfi 191 smfi 192$$-- if #relstmts = 0 then smfi 193$$-- text +:= tail; smfi 194$$-- elseif #relstmts = 1 then smfi 195$$-- text +:= arb relstmts + ' and ' + tail; smfi 196$$-- else smfi 197$$-- text := text +/[ l + ', ' : l in relstmts ]; smfi 198$$-- text +:= 'and ' + tail; smfi 199$$-- end if; smfi 200 end if; smfi 201 smfi 202 (while workoccs /= {}) smfi 203 $ remove all occurrences reachable from these smfi 204 $ occurrences. smfi 205 vo1 from workoccs; reloccs less:= vo1; smfi 206 (forall vo2 in ffrom{vo1} | vo2 in reloccs) smfi 207 reloccs less:= vo2; workoccs with:= vo2; smfi 208 end forall; smfi 209 end while; smfi 210 smfi 211 $ next we find all occurrences linked to the smfi 212 $ remaining occurrences in the forward direction. smfi 213 smfi 214 workoccs := reloccs; smfi 215 smfi 216 (while workoccs /= {}) smfi 217 vo1 from workoccs; seenoccs with:= vo1; smfi 218 (forall vo2 in ffrom{vo1} | smfi 219 typ(vo2) = type_zero) smfi 220 reloccs with:= vo2; smfi 221 if vo2 notin seenoccs then smfi 222 workoccs with:= vo2; smfi 223 end if; smfi 224 end forall; smfi 225 end while; smfi 226 smfi 227 relstmts := { oi_stmt(vo1) : vo1 in reloccs }; smfi 228 smfi 229 if #relstmts /= 0 then smfi 230 if text = om then smfi 231 text := '"' + oi_name(voy) + '" '; smfi 232 else smfi 233 text := text + ', and '; smfi 234 end if; smfi 235 smfi 236 if oi_stmt(voy) in relstmts then smfi 237 relstmts less:= oi_stmt(voy); smfi 238 tail := 'here'; smfi 239 else smfi 240 tail from relstmts; smfi 241 end if; smfi 242 smfi 243 if #reloccs = 1 then smfk 108 text +:= 'cannot be evaluated'; smfi 245 elseif #relstmts = 0 then smfi 246 text +:= 'is used inconsistently here'; smfi 247 elseif #relstmts = 1 then smfi 248 text +:= 'is used inconsistently between ' smfi 249 + arb relstmts + ' and ' + tail; smfi 250 else smfi 251 text +:= 'is used inconsistently between '; smfi 252 text := text +/[ l + ', ' : l in relstmts ]; smfi 253 text +:= 'and ' + tail; smfi 254 end if; smfi 255 end if; smfk 109 smfk 110 tp_stmts := { [ typ(vo1), oi_stmt(vo1) ] : smfk 111 vo1 in precoccs }; smfk 112 if #tp_stmts /= 0 then smfk 113 if text = om then smfk 114 text := 'the type of "' + oi_name(voy) + '"' smfk 115 ' is '; smfk 116 else smfk 117 text +:= ', and its type is '; smfk 118 end if; smfk 119 (forall relstmts = tp_stmts{t1}) smfk 120 if text(#text) /= ' ' then smfk 121 text +:= ', '; smfk 122 end if; smfk 123 tail from relstmts; smfk 124 text +:= format_type(t1) + ' at ' + smfk 125 case #relstmts of smfk 126 (0): '', smfk 127 (1): arb relstmts + ' and ' smfk 128 else smfk 129 '' +/[ l + ', ' : l in relstmts ] + smfk 130 'and ' smfk 131 end + tail; smfk 132 end forall; smfk 133 end if; 188 189 elseif typ(voy) = type_om then 190 text := '"' + oi_name(voy) + '" is undefined'; 191 else 192 text := 193 if j = 1 then 'the ' else 'the ' end + 194 'type of "' + oi_name(voy) + '"' 195 ' is ' + format_type(typ(voy)); 196 end if; 197 198 text +:= if j = #ivs then '.' else ', ' end; smfc 618 if j > 1 and #message(#message) + #text < 64 then 200 message(#message) +:= text; 201 else 202 message with:= text; 203 end if; 260 261 end forall; smfi 256 messages{stmtof(i)}{'e'} with:= message; smfi 258 (forall vo1 in ivs) errvars with:= oi_sym(vo1); end; smfi 259 errvars with:= oi_sym(vox); 263 264 265 elseif is_ivar(vox) and smfi 260 opcode(i) /= q1_isom and opcode(i) /= q1_notom and 266 exists voy in bfrom{vox} | 267 typ(voy) .con typ(vox) /= typ(voy) then 268 269 $ an input occurrence with some kind of execution problem: 270 $ we find all preceding occurrences which can assume values 271 $ which can cause some execution problem. 272 273 workoccs := { vox };$ workpile of preceding occurrences 274 seenoccs := {}; $ occurrences already seen smfg 115 reloccs := {}; $ preceding occurrences 277 278 (while workoccs /= {}) 279 smfg 116 vo1 from workoccs; seenoccs with:= vo1; 282 283 (forall vo2 in bfrom{vo1} | 284 typ(vo2) .con typ(vox) /= typ(vo2)) 285 smfg 117 if is_ovar(vo2) then reloccs with:= vo2; end if; 289 290 if vo2 notin seenoccs then 291 workoccs with:= vo2; 292 end if; 293 294 end forall; 295 298 end while; 299 smfg 118 (forall o in reloccs) 301 smfe 66 t1 := typ(o) .con typ(vox); smfe 67 t2 := typ(o) .sub typ(vox); 304 smfe 68 if t1 = type_zero then 306 l := 'e'; 307 308 elseif ( oi_op(o) = q1_asn or oi_op(o) = q1_argin ) and 309 arg2(instno(o)) = sym_om and 310 is_notom(typ(vox)) then 311 l := 'e'; 312 smfe 69 elseif t2 = type_om and getipp('full=0/1') = 0 and 314 ( (oi_op(o) = q1_sargout and 315 name(arg2(instno(o))) in 316 { 'read', 'reada', 317 'get', 'getb', 'getf' } ) or 318 oi_op(o) = q1_of or 319 oi_op(o) = q1_arb ) then 320 continue forall; 321 smfe 70 elseif t2 = ntyp(grsset, type_om) and 323 getipp('full=0/1') = 0 and 324 oi_op(o) = q1_ofa then 325 continue forall; 326 327 else 328 l := 'w'; 329 end if; 330 smfe 71 text := 'error if "' + oi_name(o) + '" is '; smfe 72 if t1 = type_zero then smfe 73 text +:= format_type(t2); smfe 74 elseif t2 = type_gen then smfe 75 text +:= 'not ' + format_type(t1); smfe 76 elseif string_length(t1) < string_length(t2) then smfe 77 text +:= 'not ' + format_type(t1); smfe 78 else smfe 79 text +:= format_type(t2); smfe 80 end if; smfe 81 message := [ text + '.' ]; smfi 261 messages{stmtof(i)}{l} with:= message; 351 end forall; 352 353 smfe 82 elseif is_ovar(vox) and smfi 262 (text := type_constant(i)) /= om then smfe 84 message := smfi 263 [ 'expression "' + format_inst(i, om) + '"' smfe 86 ' is constant: ' ]; smfe 87 text +:= '.'; smfe 88 if #message(1) + #text < 72 then smfe 89 message(1) +:= text; smfe 90 else smfe 91 message with:= text; smfe 92 end if; smfi 264 messages{stmtof(i)}{'w'} with:= message; smfe 94 smfe 95 smfi 265 elseif v notin itervars and smfi 266 opcode(i) notin ops_typepred and smfh 22 #grosstyp( (typ(vox) .con type_notom) ) > 1 and smfh 23 ( ( is_ivar(vox) and notexists voy in bfrom{vox} | smfh 24 #grosstyp( (typ(voy) .con type_notom) ) > 1 ) or smfh 25 ( is_ovar(vox) and ffrom{vox} /= {} and smfi 267 notexists voy in occs(i)(2..) | smfh 29 #grosstyp( (typ(voy) .con type_notom) ) > 1) smfh 30 ) then smfe 100 $ we only include o-variables in this test that reach a use. smfe 101 $ we ignore the internal iteration variable of iterators, smfe 102 $ which always will have type general. smfi 268 messages{stmtof(i)}{'i'} with:= smfi 269 [ '"' + name(v) + '" has an ambiguous type.' ]; 361 end if; smfi 270 end forall; smfi 271 end; end; end forall; 363 364 365 end procedure type_final; 366 367 1 .=member fwd14f 2 3 4 procedure forward(o); 5$ 6$ this routine calculates the type of an ovariable 'o' from the types of 7$ its inputs. 8$ 9 repr 10 o: occurrence; 11 opc: elmt base_opcodes; 12 ivs: tuple(occurrence); 13 tps: tuple(elmt types); 14 i: occurrence; 15 grtps: tuple(gross_type); smff 37 i1, i2, i3, i4: occurrence; 17 t1, t2, t3, t4: elmt types; 18 g1, g2, g3, g4: gross_type; 19 g, gx: gross_type; 20 tp, tpx: elmt types; smff 38 v, v1, v2, v3: integer; 22 ct1: tuple(elmt types); 23 j: integer; 24 ctp: elmt types; 25 ctp1, ctp2: elmt types; 26 tx, ty, tz: elmt types; 27 r: symbol; 28 end repr; 29 30 opc := oi_op(o); 31 ivs := [ get_oi(instno(o), j) : 32 j in [ first_ivar(opc)..#args(instno(o)) ] ]; 33 tps := [ typ(i) : i in ivs ]; 34 grtps := [ grosstyp(typ(i)) : i in ivs ]; 35 36 [ i1, i2 ] := ivs; 37 [ t1, t2 ] := tps; 38 [ g1, g2 ] := grtps; 39 40 tp := type_zero; $ set to default 41 42 case opc of 44$ 45$ binary operators 46$ 47 (q1_in, q1_notin): 48 $ if t2 is a set or tuple the result is boolean. 49 if g2 * str_tup_set /= {} then 50 tp := type_boolean; 51 end if; 52 53 (q1_incs): 54 $ if both inputs are sets the result is boolean 55 if t_set in g1*g2 then 56 tp := type_boolean; 57 end if; 58 59 (q1_eq, q1_ne): 60 $ relational operators always return a boolean. 61 tp := type_boolean; 62 smfh 31 (q1_ge, q1_lt, q1_pos): 64 $ if the operands are valid, then the result is boolean 65 if int_real_str * g1 * g2 /= {} then 66 tp := type_boolean; 67 end if; 68 69 (q1_with): 70 $ if t1 is a set or unknown-length tuple, then its type is: 71 $ grosstyp: set, tuple, or both, depending on the type of t1 72 $ comptyp: disjunction of t2 and the component type of t1 73 $ nb. with is not defined on known-length tuples. 74 if t_set in g1 then 75 ctp := comptyp(t1) .con type_notom; 76 t2 := t2 .con type_notom; 77 if ctp /= type_zero or t2 /= type_zero then 78 tp := ntyp( grsset, ctp .dis t2 ); 79 else 80 tp := ntyp( grsset, type_om ); 81 end if; 82 end if; 83 if t_tuple in g1 then 84 if is_knt(t1) then norm(t1); end if; 85 tp .dis:= ntyp( grstup, comptyp(t1) .dis t2 ); 86 end if; 87 88 (q1_less): 89 $ if t1 is a set, then the result has the same type as t1 90 if t_set in g1 then 91 tp := ntyp( grsset, comptyp(t1) .dis type_om ); 92 end if; 93 94 (q1_lessb, q1_lesse): 95 $ these opcodes are part of the simulation for q1_fromb 96 $ and frome, resp. 97 $ nb. fromb and frome are not defined for known-length tuples. 98 if t_tuple in g1 then 99 if is_knt(t1) then norm(t1); end if; 100 tp := ntyp( grstup, comptyp(t1) .dis type_om ); 101 end if; 102 103 (q1_lessf): 104 $ if t1 is a set, it must be a map or a set of pairs 105 if t_set in g1 then 106 ctp := comptyp(t1); 107 if ctp = type_om then 108 tp := ntyp( grsset, type_om ); 109 elseif (ctp .con:= type_pair) /= type_zero then 110 tp := ntyp( grsset, ctp .dis type_om ); 111 end if; 112 end if; 113 114 (q1_npow): 115 $ one input is a set, the other an integer; 116 $ the result is set(set_type). 117 if t_set in g1 and t_int in g2 then 118 tp := ntyp( grsset, t1 .dis type_om ); 119 end if; 120 if t_int in g1 and t_set in g2 then 121 tp .dis:= ntyp( grsset, t2 .dis type_om ); 122 end if; 123 124 (q1_max, q1_min): 125 tp := t1 .con t2 .con type_int_real_str; 126 127 (q1_add): 128 gx := g1 * g2; 129 tp := t1 .con t2 .con type_int_real_str; 130 if t_tuple in gx and is_knt(t1) and is_knt(t2) then 131 tp .dis:= knt_type(comptyp(t1) + comptyp(t2)); 132 elseif not is_prim(gx) then 133 if is_knt(t1) then norm(t1); end if; 134 if is_knt(t2) then norm(t2); end if; 135 ctp1 := comptyp(t1); 136 ctp2 := comptyp(t2); 137 ctp := ctp1 .dis ctp2; 138 if is_notom(ctp1) or is_notom(ctp2) then 139 ctp .con:= type_notom; 140 end if; 141 tp .dis:= ntyp( gx*set_tup, ctp ); 142 end if; 143 144 (q1_sub): 145 tp := t1 .con t2 .con type_int_real; 146 if t_set in g1 and t_set in g2 then 147 tp .dis:= ntyp( grsset, comptyp(t1) .dis type_om ); 148 end if; 149 150 (q1_mult): 151 tpx := t1 .con t2; 152 tp := tpx .con type_int_real; 153 if t_set in g1 and t_set in g2 then 154 tp .dis:= ntyp( grsset, comptyp(tpx) .dis type_om ); 155 end if; 156 if t_int in g1 then 157 if t_string in g2 then 158 tp .dis:= type_string; 159 end if; 160 if t_tuple in g2 then 161 if is_knt(t2) then norm(t2); end if; 162 tp .dis:= (t2 .con type_tuple); 163 end if; 164 end if; 165 if t_int in g2 then 166 if t_string in g1 then 167 tp .dis:= type_string; 168 end if; 169 if t_tuple in g1 then 170 if is_knt(t1) then norm(t1); end if; 171 tp .dis:= (t1 .con type_tuple); 172 end if; 173 end if; 174 175 (q1_div): 176 tp := t1 .con t2 .con type_int; 177 178 (q1_slash): 179 if type_int_real .con t1 .con t2 /= type_zero then 180 tp := type_real; 181 end if; 182 183 (q1_mod): 184 if t_set in g1 and t_set in g2 then 185 ctp := comptyp(t1) .dis comptyp(t2) .dis type_om; 186 tp := ntyp( grsset, ctp ); 187 end if; 188 if t_int in g1 and t_int in g2 then 189 tp .dis:= type_int; 190 end if; 191 192 (q1_exp): 193 tp := type_int_real .con t1 .con t2; 194 if t_real in g1 and t_int in g2 then 195 tp .dis:= type_real; 196 end if; 197 smff 39 (q1_atan2): smff 40 tp := t1 .con t2 .con type_real; 198$ 199$ unary operators 200$ 201 (q1_not): 202 if t_atom in g1 then 203 tp := type_boolean; 204 end if; 205 206 (q1_even, q1_odd): 207 if t_int in g1 then 208 tp := type_boolean; 209 end if; 210 211 (q1_isint, q1_isreal, q1_isstr, q1_isbool, 212 q1_isatom, q1_istup, q1_isset, q1_ismap): 213 tp := type_boolean; 214 215 (q1_arb): 216 $ if t1 is a set, then the result type is the component type. 217 if t_set in g1 then 218 tp := comptyp(t1); 219 end if; 220 221 (q1_arbb, q1_arbe): 222 $ these opcodes are part of the simulation for q1_fromb 223 $ and q1_frome, resp. 224 if t_tuple in g1 then 225 if is_knt(t1) then norm(t1); end if; 226 tp := comptyp(t1); 227 end if; 228 229 (q1_dom, q1_range): 230 $ if the input is a set, the result is its domain or image 231 $ type. 232 j := if opc = q1_dom then 1 else 2 end; 233 if t_set in g1 then 234 ctp := comptyp(t1); 235 if ctp = type_om then 236 $ no pair type yet: pass null set along 237 tp := ntyp( grsset, type_om ); 238 elseif (ctp .con:= type_pair) /= type_zero then 239 ct1 := comptyp(ctp); 240 tp := ntyp( grsset, ct1(j) .dis type_om ); 241 end if; 242 end if; 243 244 (q1_pow): 245 $ if t1 is a set, then the result is set(t1). 246 if t_set in g1 then 247 tp := ntyp( grsset, t1 .con type_set ); 248 end if; 249 250 (q1_nelt): 251 $ for valid input types, the result type is int. 252 if g1 * str_tup_set /= {} then 253 tp := type_int; 254 end if; 255 smff 41 (q1_abs): 257 $ the result type is int or real, depending on the input type smff 42 $ the result type is int if the input is a string (of length 1). 258 tp := t1 .con type_int_real; smff 43 if t_string in g1 then tp .dis:= type_int; end if; 259 260 (q1_char): 261 tp := type_string; 262 263 (q1_ceil, q1_floor, q1_fix): 264 if t_real in g1 then 265 tp := type_int; 266 end if; 267 268 (q1_float): 269 if t_int in g1 then 270 tp := type_real; 271 end if; 272 273 (q1_sin, q1_cos, q1_tan, 274 q1_arcsin, q1_arccos, q1_arctan, 275 q1_tanh, 276 q1_expf, q1_log, 277 q1_sqrt ): 278 if t_real in g1 then 279 tp := type_real; 280 end if; 281 282 (q1_rand): 283 $ if t1 is a known length tuple, then the result type is the 284 $ disjunction of the component types. if t1 is a set or 285 $ unknown length tuple, then the result type is the component 286 $ type. 287 tp := t1 .con type_int_real_str; 288 if not is_prim(g1) then 289 if is_knt(t1) then norm(t1); end if; 290 tp .dis:= comptyp(t1); 291 end if; 292 293 (q1_sign): 294 if int_real * g1 /= {} then 295 tp := type_int; 296 end if; 297 298 (q1_type, q1_str): 299 tp := type_string; 300 301 (q1_val): 302 $ this operation converts a string representing any setl 303 $ value to its value, so the result type is general. 304 if t_string in g1 then 305 tp := type_gen; 306 end if; 307 smff 44 (q1_umin): smff 45 $ the result type is int or real, depending on the input type. smff 46 tp := t1 .con type_int_real; 308$ 309$ slicing operations 310$ 311$ nb. these operations are only defined for strings and tuples. 315$ 316 (q1_subst): 317 $ t2 and t3 must be integers. smff 47 [ -, -, i3 ] := ivs; smff 48 [ -, -, t3 ] := tps; smff 49 [ -, -, g3 ] := grtps; smff 50 if t_int in g2 and t_int in g3 then 323 if t_tuple in g1 then smff 51 if is_knt(t1) and smff 52 is_const_int(i2) and is_const_int(i3) then smff 53 ct1 := comptyp(t1); smff 54 v2 := oi_val(i2); smff 55 v3 := oi_val(i3); smff 56 if 1 <= v2 and v2 <= v3+1 and v3 <= #ct1 then smff 57 tp := knt_type(ct1(v2..v3)); smff 58 else smff 59 tp := type_zero; smff 60 end if; smff 61 else smff 62 if is_knt(t1) then norm(t1); end if; smff 63 tp := ntyp( grstup, comptyp(t1) .dis type_om ); smff 64 end if; 326 end if; smff 65 smff 66 if t_string in g1 then smff 67 tp .dis:= type_string; smff 68 end if; 327 end if; 328 329 (q1_end): 330 $ t2 must be an integer. 331 if t_int in g2 then 336 if t_tuple in g1 then smff 69 if is_knt(t1) and is_const_int(i2) then smff 70 ct1 := comptyp(t1); v2 := oi_val(i2); smff 71 if 1 <= v2 and v2 <= #ct1 then smff 72 tp := knt_type(ct1(v2..)); smff 73 else smff 74 tp := type_zero; smff 75 end if; smff 76 else smff 77 if is_knt(t1) then norm(t1); end if; smff 78 tp := ntyp( grstup, comptyp(t1) .dis type_om ); smff 79 end if; 339 end if; smff 80 smff 81 if t_string in g1 then smff 82 tp .dis:= type_string; smff 83 end if; 340 end if; 341 342 (q1_ssubst): 343 $ t1 and t2 must be integer, 344 $ t3 is the right-hand side, 345 $ t4 is the i-occurrence of the output 346 [ -, -, t3, t4 ] := tps; 347 [ -, -, g3, g4 ] := grtps; 348 if t_int in g1 and t_int in g2 then 352 if t_tuple in g3*g4 then smff 84 if is_knt(t3) and is_knt(t4) and smff 85 is_const_int(i1) and is_const_int(i2) then smff 86 ct1 := comptyp(t4); smff 87 v1 := oi_val(i1); smff 88 v2 := oi_val(i2); smff 89 if 1 <= v1 and v1 <= v2+1 and v2 <= #ct1 then smff 90 tp := knt_type( ct1(1..v1-1) smff 91 + comptyp(t3) smff 92 + ct1(v2+1..) ); smff 93 else smff 94 tp := type_zero; smff 95 end if; smff 96 else smff 97 if is_knt(t3) then norm(t3); end if; smff 98 if is_knt(t4) then norm(t4); end if; smff 99 tp := t3 .dis t4; smff 100 end if; 356 end if; smff 101 smff 102 if t_string in g3 and t_string in g4 then smff 103 tp .dis:= type_string; smff 104 end if; 357 end if; 358 359 (q1_send): 360 $ t1 must be an integer. smff 105 [ -, -, t3 ] := tps; smff 106 [ -, -, g3 ] := grtps; 362 if t_int in g1 then 366 if t_tuple in g2*g3 then smff 107 if is_const_int(i1) and is_knt(t2) and is_knt(t3) then smff 108 ct1 := comptyp(t3); v1 := oi_val(i1); smff 109 if 1 <= v1 and v1-1 <= #ct1 then smff 110 tp := knt_type( ct1(1..v1-1) + comptyp(t2) ); smff 111 else smff 112 tp := type_zero; smff 113 end if; smff 114 else smff 115 if is_knt(t2) then norm(t2); end if; smff 116 if is_knt(t3) then norm(t3); end if; smff 117 tp := t2 .dis t3; smff 118 end if; 370 end if; smff 119 smff 120 if t_string in g2 and t_string in g3 then smff 121 tp .dis:= type_string; smff 122 end if; 371 end if; 372 373$ 374$ assigning operators 375$ 376 (q1_asn, q1_argin): 377 tp := t1; 378 379 (q1_argout): 380 tp := tps(3); 381 382 (q1_sargout): 383 $ i1 is the routine name 384 $ i2 is the actual parameter number 385 r := oi_sym(i1); j := oi_val(i2); 386 if rvary(r)=1 and rnargs(r) < j then 387 $ the routine has a variable number of arguments, and the 388 $ current argument has the form of the last formal argument 389 j := rnargs(r); 390 end if; 391 tp := given_type(ft_elmt(form(r))(j)); smff 123 if maxtype_sargs(r) /= om then smff 124 tp .con:= maxtype_sargs(r)(j); smff 125 end if; 392 393 (q1_def): 394 $ most general definition for external procedure simulation 395 tp := type_gen; 396$ 397$ map operations 398$ 399 (q1_of): smfb 405 $ the first input can be a string, a tuple, or a set. we handle smfb 406 $ each case separately. 402 403 if t_string in g1 and t_int in g2 then 404 tp := type_string; 405 end if; 406 407 $ next we handle f(x) where f is a known length tuple and x is 408 $ an integer constant. there are three possibilities: 409 $ a. x <= 0: error. 410 $ b. 1 <= x <= # f: set result to proper component type. 411 $ c. # f < x: set result to omega type. 412 413 if t_tuple in g1 and t_int in g2 then 414 if is_knt(t1) and is_const_int(i2) then 415 v := oi_val(i2); 416 417 if 1 <= v and v <= # comptyp(t1) then 418 tp .dis:= ctypn(t1, v); 419 elseif # comptyp(t1) < v then 420 tp .dis:= type_om; 421 end if; 422 423 else 424 $ handle remaining tuple cases by or-ing in component 425 $ type 426 $ nb. the result may be omega. 427 if is_knt(t1) then norm(t1); end if; smfd 15 tp .dis:= comptyp(t1) .dis type_om; 430 end if; 431 end if; 432 433 $ finally handle sets by 'or'ing in image type. once again, 434 $ the result may be omega. 435 436 if t_set in g1 then 437 ctp := comptyp(t1); 438 if ctp = type_om then 439 $ no pair type yet: keep type small 440 tp .dis:= type_om; 441 elseif (ctp .con:= type_pair) /= type_zero then 442 ct1 := comptyp(ctp); 443 tx := ct1(1) .con t2; 444 if tx /= type_zero then smfd 16 tp .dis:= ct1(2) .dis type_om; smfi 272 elseif t2 /= type_zero then 447 $ index not in map domain: result is omega 448 tp .dis:= type_om; 449 end if; 450 end if; 451 end if; 452 453 (q1_ofa): 454 $ the result is set(image type of f) 455 if t_set in g1 then 456 ctp := comptyp(t1); 457 if ctp = type_om then 458 $ no pair type yet: pass null set along 459 tp := ntyp( grsset, type_om ); 460 elseif (ctp .con:= type_pair) /= type_zero then 461 ct1 := comptyp(ctp); 462 tx := ct1(1) .con t2; 463 if tx /= type_zero then smfd 17 tp := ntyp( grsset, ct1(2) .dis type_om ); smfi 273 elseif t2 /= type_zero then 466 $ index not in map domain: result is null set 467 tp := ntyp( grsset, type_om ); 468 end if; 469 end if; 470 end if; 471 472 (q1_sof): 473 t3 := tps(3); g3 := grtps(3); 474 475 $ first we handle the case where f is a known-length tuple 476 $ and x is an integer constant. there are three cases: 477 $ a. x <= 0: error. 478 $ b. 1 <= x <= # f: set x-th component type of result to x-th 479 $ component type of f .dis y 480 $ c. x > # f: set result to mixed tuple whose first #f 481 $ components have the component types of f 482 $ and whose x-th component has the type of 483 $ y. 484 485 if t_tuple in g3 and t_int in g1 then 486 if is_knt(t3) and is_const_int(i1) then 487 v := oi_val(i1); 488 ct1 := comptyp(t3); 489 490 if 1 <= v and v <= #ct1 then 491 tp := t3; 492 ctypn(tp, v) := t2; 493 elseif #ct1 < v then 494 tp := t3; 495 ctypn(tp, v) := t2; 496 (forall j in [ #ct1+1..v-1 ]) 497 ctypn(tp, j) := type_om; 498 end forall; 499 end if; 500 501 else 502 $ otherwise if f is a tuple then the result is a tuple 503 $ whose component type is the disjunction of the com- 504 $ ponent type(s) of f and the type of y. 505 if is_knt(t3) then norm(t3); end if; 506 tp := ntyp( grstup, comptyp(t3) .dis t2 ); 507 end if; 508 end if; 509 510 $ if f can be a set then we 'or' in the type for [x, y]. 511 $ there are two special cases: 512 $ 1. either x or y are definitely omega. in this case we 513 $ shall never insert the pair into f, so we treat the 514 $ instruction as a noop. 515 $ 2. either x or y might be omega. in this case we shall 516 $ perform a run-time test and insert the pair only if 517 $ neither element is omega. before building the type 518 $ descriptor for the pair we set both is_om flags to no. 519 520 if t_set in g3 then 521 $ ctp1 is the map element type derived from x and y 522 ctp1 := pair_type(t1, t2); 523 if is_notom(t1) and is_om(t2) then 524 $ cannot say whether a pair is actually inserted 525 ctp1 .dis:= type_om; 526 end if; 527 $ ctp2 is the map element type from the i-occurrence of f 528 ctp2 := (type_pair .dis type_om) .con comptyp(t3); 529 $ ctp is the new map element type 530 ctp := ctp1 .dis ctp2; 531 if is_notom(ctp1) or is_notom(ctp2) then 532 ctp .con:= type_notom; 533 end if; 534 tp .dis:= ntyp( grsset, ctp ); 535 end if; 536 537 if t_int in g1 and t_string in g2 and t_string in g3 then 538 tp .dis:= type_string; 539 end if; 540 541 (q1_sofa): 542 $ the output f will always be a map 543 t3 := tps(3); g3 := grtps(3); 544 if t_set in g3 and t_set in g2 then 545 tz := t2 .con type_set; $ range set type 546 ty := comptyp(tz); $ range element type 547 $ ctp1 is the map element type derived from x and y 548 ctp1 := pair_type(t1, ty); 549 if is_notom(t1) and is_om(ty) then 550 $ cannot say whether a pair is actually inserted 551 ctp1 .dis:= type_om; 552 end if; 553 $ ctp2 is the map element type from the i-occurrence of f 554 ctp2 := (type_pair .dis type_om) .con comptyp(t3); 555 $ ctp is the new map element type 556 ctp := ctp1 .dis ctp2; 557 if is_notom(ctp1) or is_notom(ctp2) then 558 ctp .con:= type_notom; 559 end if; 560 tp .dis:= ntyp( grsset, ctp ); 561 end if; 562 563$ 564$ iterators 565$ 566 (q1_next, q1_inext): smfb 407 $ the second input, i.e. the object being iterated over, can be smfb 408 $ a string, a tuple, or a set. 571 572 if opc = q1_next then 573 tp := tps(3); 574 end if; 575 576 if t_string in g2 then 577 tp .dis:= type_string; 578 end if; 579 580 if not is_prim(g2) then 581 if is_knt(t2) then norm(t2); end if; 582 tp .dis:= comptyp(t2); smfe 105 if t_tuple notin g2 then tp .con:= type_notom; end if; 583 end if; 585 586 (q1_nextd, q1_inextd): 587 $ here we compute the type for 'x' in 'for y = f(x)'. 588 $ t2 is the type of f. 589 $ if t2 is a tuple or string, then x is an integer; 590 $ if t2 is a set, it must be a map, and consequently the 591 $ type of x is the type of the first component of the 592 $ component type of t2. 593 if str_tup * g2 /= {} then 594 tp := type_int; 595 end if; 596 if t_set in g2 then 597 ctp := comptyp(t2) .con type_pair; 598 if ctp /= type_zero then 599 ct1 := comptyp(ctp); 600 tp .dis:= ct1(1); 601 end if; 602 end if; 603 604 (q1_set): 605 $ enumerative set former - { x1, x2, x3, ..., xn } 606 $ the result is set(disjunction of individual elements) 607 ctp := type_zero .dis/ tps; 608 tp := ntyp( grsset, ctp .con type_notom ); 609 610 (q1_set1): 611 $ iterative set former - { : x in s | c(x) } 612 $ the first input argument gives the (see comment above) 613 $ type of . the result type is set(t1). 614 tp := ntyp( grsset, t1 .dis type_om ); 615 616 (q1_tup): 617 $ enumerative tuple former - [ x1, x2, x3, ..., xn ] 618 $ the result is a known-length tuple. 619 (forall ctp = tps(j)) 620 tps(j) := ctp .con type_notom; 621 end forall; 622 if #tps = 1 then 623 tp := ntyp( grstup, tps(1) ); 624 else 625 tp := knt_type(tps); 626 end if; 627 628 (q1_tup1): 629 $ iterative tuple former - [ : x in s | c(x) ] 630 $ the result type is tuple(t1); see above comment on first 631 $ argument of iterative set and tuple formers. 632 tp := ntyp( grstup, t1 .dis type_om ); 633 634 else 635 print('*** missing opcode in forward *** ', o, opc); 636 637 end case; 638 639 return tp; 640 641 642 end procedure forward; 643 644 1 .=member bak14g 2 3 4 procedure backward(inst); 5$ 6$ this routine computes the type of the ivariables of inst based on how 7$ they are used. 8$ 9$ certain operations such as addition are only legal if the inputs are 10$ not omega. when we process: 11$ 12$ (1) read(x); 13$ (2) print(x+1); 14$ 15$ we have: 16$ 17$ is_om(x1) = maybe by forward analysis 18$ is_om(x2) = no by backward analysis 19$ 20$ this means that x1 and x2 have different types, and a conversion 21$ must be inserted between lines 1 and 2. this conversion amounts 22$ to checking that x is not omega. 23$ 24 repr 25 inst: elmt insts; 26 opc: elmt base_opcodes; 27 ivs: tuple(occurrence); 28 j: integer; 29 tps: tuple(elmt types); 30 i: occurrence; 31 otyp: elmt types; 32 gotyp: gross_type; 33 i1, i2: occurrence; 34 t1, t2: elmt types; 35 outps: tuple(elmt types); 36 tp, tpa, tpb, tpx: elmt types; 37 tp1, tp2: elmt types; 38 ctp: elmt types; 39 ct1: tuple(elmt types); 40 tx, ty: elmt types; 41 c: general; 42 v: integer; 43 r: symbol; 44 end repr; 45$ 46$ begin by getting the type of the ovariable, etc. 47$ 48 opc := opcode(inst); 49 ivs := [ get_oi(inst, j) : j in [ first_ivar(opc)..#args(inst) ] ]; 50 tps := [ typ(i) : i in ivs ]; 51 otyp := typ(get_oi(inst, 1)); 52 gotyp := grosstyp(otyp); 53 54 [ t1, t2 ] := tps; 55 outps := tps; 56 57 macro change1; outps(1) := tp endm; 58 macro change2; outps(2) := tp endm; 59 macro change3; outps(3) := tp endm; 60 smfc 620$ check whether otyp is type_zero. this indicates that the instruction smfc 621$ contains an error. in this situation we replace otyp by type_general smfc 622$ to return the mildest constraint on this instruction. note that the smfc 623$ results of this routine are always conjuncted with the previous smfc 624$ results; hence this upper bound will not cause divergence. smfc 625 smfc 626 if otyp = type_zero then smfc 627 otyp := type_gen; gotyp := grosstyp(otyp); smfc 628 end if; 66 67 case opc of 68$ 69$ binary operators 70$ 71 72 (q1_in, q1_notin): 73 $ we are looking at s in x in s. s must be a set, 74 $ string, or tuple. 75 tp := type_str_tup_set; 76 change2; 77 78 (q1_incs): 79 tp := type_set; 80 change1; change2; 81 smfh 32 (q1_ge, q1_lt, q1_pos): 83 $ the inputs are integers, reals, or strings. smfi 274 tp := type_int_real_str; smfi 275 if t1 /= type_zero then tp .con:= t1; end if; smfi 276 if t2 /= type_zero then tp .con:= t2; end if; 85 change1; change2; 86 87 (q1_with): 88 $ s.in has the same type as s.out 89 $ x.in has the element type of s.out 90 $ s can not be omega; x can be omega iff s is a tuple 91 $ with is not defined for known-length tuples 92 if t_tuple in gotyp and is_knt(otyp) then norm(otyp); end if; 93 if not is_prim(gotyp) then 94 ctp := comptyp(otyp) .dis type_om; 95 tp := ntyp( gotyp * set_tup, ctp ); 96 else 97 tp := type_zero; 98 end if; 99 change1; 100 101 tp := comptyp(tp); 102 if t_tuple notin gotyp then tp .con:= type_notom; end if; 103 change2; 104 105 (q1_less): 106 $ s.in must be a set 107 $ s.in can not be omega 108 tp := if t_set in gotyp then type_set else type_zero end; 109 change1; 110 111 (q1_lesse, q1_lessb): 112 $ these opcodes are part of the simulation for q1_fromb and 113 $ q1_frome. 114 $ t.in must be a tuple 115 $ t.in can not be omega 116 tp := if t_tuple in gotyp then type_tuple else type_zero end; 117 change1; 118 119 (q1_lessf): 120 $ arg2 is a map, arg3 can be anything 121 if t_set in gotyp then 122 tp := ntyp( grsset, type_pair .dis type_om ); 123 end if; 124 change1; 125 126 (q1_npow): 127 tp := (comptyp(otyp) .con type_set) .dis type_int; 128 change1; change2; 129 130 (q1_add): 131 tp := otyp .con type_int_real_str; 132 if not is_prim(gotyp) then 133 if is_knt(otyp) then norm(otyp); end if; 134 ctp := comptyp(otyp) .dis type_om; 135 tp .dis:= ntyp( gotyp*set_tup, ctp ); 136 end if; 137 change1; change2; 138 139 (q1_mult): 140 tp := otyp .con type_int_real; 141 142 if t_set in gotyp then 143 tp .dis:= type_set; 144 end if; 145 146 tpx := otyp .con type_str_tup; 147 tpa := tpb := tp; 148 if tpx /= type_zero then 149 if (tp1 := tpx .con t1) /= type_zero and smfi 277 t_int in grosstyp(t2) or t1 = type_zero then 151 tpa .dis:= tp1; 152 tpb .dis:= type_int; 153 end if; 154 if (tp2 := tpx .con t2) /= type_zero and smfi 278 t_int in grosstyp(t1) or t2 = type_zero then 156 tpa .dis:= type_int; 157 tpb .dis:= tp2; 158 end if; 159 end if; 160 tp := tpa; change1; 161 tp := tpb; change2; 162 163 (q1_mod, q1_sub): 164 tp := otyp .con type_int_real; 165 if t_set in gotyp then 166 tp .dis:= type_set; 167 end if; 168 change1; change2; 169 170 (q1_exp): 171 tp := otyp .con type_int_real; 172 change1; 173 174 if t_real in gotyp then 175 tp .dis:= type_int; 176 end if; 177 change2; 178 179 (q1_max, q1_min): 180 tp := type_int_real_str; 181 change1; change2; 182 183 (q1_slash): 184 tp := type_int_real; 185 change1; change2; 186 187 (q1_div): 188 tp := type_int; 189 change1; change2; 190 smff 126 (q1_atan2): smff 127 tp := type_real; smff 128 change1; change2; 191$ 192$ unary operators 193$ 194 (q1_not, q1_asrt): 195 tp := type_boolean; 196 change1; 197 198 (q1_even, q1_odd): 199 tp := type_int; 200 change1; 201 202 (q1_isint, q1_isreal, q1_isstr, q1_isbool, smfe 106 q1_isatom, q1_istup, q1_isset, q1_ismap): 204 tp := type_notom; 205 change1; 206 207 (q1_arb): 208 $ q1_arb is only defined for sets and maps. 209 $ the input must be set(output type) 210 tp := ntyp( grsset, otyp .dis type_om ); 211 change1; 212 213 (q1_arbb, q1_arbe): 214 $ these opcode are part of the simulation of q1_fromb, q1_frome 215 $ t.in must be a tuple; t.in can not be omega 216 tp := ntyp( grstup, otyp .dis type_om ); 217 change1; 218 219 (q1_dom): 220 $ the input is a map with domain type 'otyp' and 221 $ image type general. 222 ctp := pair_type(comptyp(otyp), type_gen) .dis type_om; 223 tp := ntyp( grsset, ctp ); 224 change1; 225 226 (q1_range): 227 $ the input is a map with domain type general and 228 $ image type 'otyp'. 229 ctp := pair_type(type_gen, comptyp(otyp)) .dis type_om; 230 tp := ntyp( grsset, ctp ); 231 change1; 232 233 (q1_pow): 234 $ the output will be a set of sets, by forward propagation 235 tp := comptyp(otyp) .con type_set; 236 change1; 237 238 (q1_nelt): 239 $ q1_nelt is defined for strings, tuples, and sets. 240 tp := type_str_tup_set; 241 change1; 242 smff 129 (q1_abs): smff 130 tp := otyp .con type_int_real; smff 131 if t_int in gotyp then tp .dis:= type_string; end if; 245 change1; smff 132 smff 133 (q1_char): smff 134 tp := type_int; smff 135 change1; 246 247 (q1_ceil, q1_floor, q1_fix): 248 $ the input must be real. 249 tp := type_real; 250 change1; 251 252 (q1_float): 253 $ the input must be integer. 254 tp := type_int; 255 change1; 256 257 (q1_sin, q1_cos, q1_tan, 258 q1_arcsin, q1_arccos, q1_arctan, 259 q1_tanh, 260 q1_expf, q1_log, 261 q1_sqrt ): 262 $ the input must be real. 263 tp := type_real; 264 change1; 265 266 (q1_rand): 267 268 tp := (otyp .dis ntyp( set_tup, otyp )) .con type_notom; 273 change1; 274 275 (q1_sign): 276 tp := type_int_real; 277 change1; smfe 107 smfe 108 (q1_type, q1_str): smfe 109 tp := type_notom; smfe 110 change1; 278 279 (q1_val): 280 tp := type_string; 281 change1; smff 136 smff 137 (q1_umin): smff 138 tp := otyp .con type_int_real; smff 139 change1; 282 smfg 119 (q1_if, q1_ifnot, q1_bif, q1_bifnot): 284 $ the input is boolean 285 tp := type_boolean; 286 change1; 287 288 (q1_next, q1_inext): smfk 134 $ input is set(otyp), tuple(otyp), or string 290 tp := ntyp( str_tup_set, otyp .dis type_om ); 291 change2; 292 293 (q1_nextd, q1_inextd): 294 $ the first argument is the domain element of the iteration. 295 ctp := pair_type(otyp, type_gen); 296 tp := ntyp( grsset, ctp .dis type_om ); 297 if t_int in gotyp then $ could be string or tuple iterator 298 tp .dis:= type_str_tup; 299 end if; 300 change2; 301 302 (q1_set): 303 tp := comptyp(otyp) .con type_notom; 304 outps := [ tp : j in [ 1..#ivs ] ]; 305 306 (q1_set1): 307 tp := comptyp(otyp) .con type_notom; 309 change1; 310 311 tp := type_int; 312 change2; 313 314 (q1_tup): 315 if is_knt(otyp) then 316 ct1 := comptyp(otyp); 317 (forall j in [ 1..#ivs ]) 320 outps(j) := ct1(j) .con type_notom; 321 end forall; 322 else 323 tp := comptyp(otyp) .con type_notom; 325 outps := [ tp : j in [ 1..#ivs ] ]; 326 end if; 327 328 (q1_tup1): 329 $ the input type is the element type of the final tuple. 330 $ if there are several element types (i.e. for known-length 331 $ tuples) we take their disjunction. 332 if is_knt(otyp) then 333 tp := type_zero .dis/ comptyp(otyp); 334 else 335 tp := comptyp(otyp); 336 end if; 337 tp .con:= type_notom; 338 change1; 339 340 tp := type_int; 341 change2; 342 343$ 344$ slicing operations 345$ 346 (q1_end, q1_subst): 347 if t_string in gotyp then 348 tp := type_string; 349 else 350 tp := type_zero; 351 end if; 352 if t_tuple in gotyp then 353 tp .dis:= type_tuple; 354 end if; 356 change1; 357 358 tp := type_int; 359 change2; 360 if opc = q1_subst then change3; end if; 361 362 (q1_ssubst, q1_send): 363 if t_tuple in gotyp then 364 if is_knt(otyp) then norm(otyp); end if; 365 tp := otyp .con type_notom; 366 else 367 tp := type_zero; 368 end if; 369 if t_string in gotyp then 370 tp .dis:= type_string; 371 end if; 373 j := if opc = q1_ssubst then 4 else 3 end; smff 140 outps(j) := tp; smff 141 outps(j-1) := tp; 376 377 tp := type_int; 378 change1; 379 if opc = q1_ssubst then change2; end if; 380$ 381$ map operations 382$ 383 (q1_of): 384 ctp := pair_type(type_gen, otyp); smfc 629 if ctp = type_zero then ctp := type_pair; end if; 385 tp := ntyp( grsset, ctp .dis type_om ); 386 if t_int in grosstyp(t2) then 387 if t_string in gotyp then 388 tp .dis:= type_string; 389 end if; 390 if t_tuple in grosstyp(t1) then 391 i2 := ivs(2); 392 if is_const_int(i2) and is_knt(t1) then 393 v := oi_val(i2); 394 c := comptyp(t1); 395 c(v) := otyp; 396 tp .dis:= knt_type(c); 397 else 398 tp .dis:= type_tuple; 399 end if; 400 end if; smfi 279 elseif t2 = type_zero then smfi 280 tp .dis:= type_str_tup; 401 end if; 403 change1; 404 smfi 281 if t_set in grosstyp(t1) or t1 = type_zero then 406 tp := type_notom; 407 else 408 tp := type_int; 409 end if; 410 change2; 411 412 (q1_ofa): 413 if t_set in gotyp then 414 ctp := pair_type(type_gen, comptyp(otyp)); smfc 630 if ctp = type_zero then ctp := type_pair; end if; 415 else 416 ctp := type_pair; 417 end if; 418 tp := ntyp( grsset, ctp .dis type_om ); 419 change1; smfe 111 tp := type_notom; smfe 112 change2; 420 421 (q1_sof): 422 if t_set in gotyp then 423 ctp := comptyp(otyp) .con type_pair; 424 if ctp /= type_zero then 425 [ tx, ty ] := comptyp(ctp); 426 else 427 tx := type_notom; ty := type_gen; 428 end if; 429 tp := tx .con type_notom; 430 else 431 tp := type_zero; 432 end if; 433 if str_tup * gotyp /= {} then 434 tp .dis:= type_int; 435 end if; 437 change1; 438 439 if t_string in gotyp then 440 tp := type_string; 441 else 442 tp := type_zero; 443 end if; 444 if t_set in gotyp then 446 tp .dis:= ty .dis type_om; 447 end if; 448 if t_tuple in gotyp then 449 if is_knt(otyp) then 450 i1 := ivs(1); 451 ct1 := comptyp(otyp); 452 if is_const_int(i1) then 453 v := oi_val(i1); 454 tp .dis:= ct1(v) .dis type_om; 455 else 456 tp .dis:= (type_om .dis/ ct1); 457 end if; 458 else 459 tp .dis:= comptyp(otyp) .dis type_om; 460 end if; 461 end if; 462 change2; 463 464 if t_string in gotyp then 465 tp := type_string; 466 else 467 tp := type_zero; 468 end if; 469 if t_tuple in gotyp then 470 if is_knt(otyp) then 471 ct1 := comptyp(otyp); 472 (forall ctp = ct1(j)) 473 ct1(j) := ctp .dis type_om; 474 end forall; 475 tp .dis:= knt_type(ct1); 476 else 477 ctp := comptyp(otyp); 478 tp .dis:= ntyp( grstup, ctp .dis type_om ); 479 end if; 480 end if; 481 if t_set in gotyp then 482 tp .dis:= ntyp( grsset, pair_type(tx, ty) .dis type_om ); 483 end if; 484 change3; 485 486 (q1_sofa): 487 $ the output must be a map, by forward propagation. 488 ctp := comptyp(otyp) .con type_pair; 489 if ctp /= type_zero then 490 [ tx, ty ] := comptyp(ctp); 491 else 492 tx := type_notom; ty := type_gen; 493 end if; 494 495 tp := tx .con type_notom; 496 change1; 497 498 tp := ntyp( grsset, ty .dis type_om ); 499 change2; 500 501 tp := ntyp( grsset, pair_type(tx, ty) .dis type_om ); 502 change3; 503 504 (q1_asn, q1_argin): 505 tp := otyp; 506 change1; 507 508 (q1_argout): 509 tp := otyp; 510 change3; 511 512 (q1_sargin): 513 r := args(inst)(2); 514 j := value(args(inst)(3)); 515 if rvary(r)=1 and rnargs(r) < j then 516 $ the routine has a variable number of arguments, and the 517 $ current argument has the form of the last formal argument 518 j := rnargs(r); 519 end if; 520 tp := given_type(ft_elmt(form(r))(j)); smff 142 if maxtype_sargs(r) /= om then smff 143 tp .con:= maxtype_sargs(r)(j); smff 144 end if; 521 change1; smfg 120 smfg 121 (q1_isom): smfg 122 tp := type_om; smfg 123 change1; smfg 124 smfg 125 (q1_notom): smfg 126 tp := type_notom; smfg 127 change1; 522 523 else 524 print('*** missing opcode in back', opc, inst); 525 end case; 526 527 528 return outps; 529 530 531 end procedure backward; 532 533 1 .=member tcn14h 2 3 4 procedure type_constant(inst); 5$ 6$ this procedure checks whether the result of the instruction inst is 7$ determined by the types of its inputs. 8$ 9 repr 10 inst: elmt insts; 11 opc: elmt base_opcodes; 12 occsi: tuple(occurrence); 13 tps: tuple(elmt types); 14 grtps: tuple(gross_type); 15 i: occurrence; 16 t1, t2, t3: elmt types; 17 g1, g2, g3: gross_type; 18 tx, ty, tp: elmt types; 19 ct1: tuple(elmt types); 20 ctp: elmt types; 21 j: integer; 22 end repr; 23 24 25 opc := opcode(inst); 26 occsi := [ get_oi(inst, j) : j in [ 1..#args(inst) ] ]; 27 tps := [ typ(i) : i in occsi ]; 28 grtps := [ grosstyp(tp) : tp in tps ]; smfk 135 smfk 136 if exists tp in tps | tp = type_zero then return; end if; 29 30 [ t1, t2, t3 ] := tps; 31 [ g1, g2, g3 ] := grtps; 32 33 case opc of 34 35 (q1_eq, q1_ne): 36 $ type a2 = type a3 37 if t2 = type_om or t3 = type_om then 38 if t2 = t3 then 39 return 'both operands are omega'; 40 elseif is_notom(t2) then smff 145 return 'the left operand cannot be omega, ' smff 146 'the right operand is omega'; 43 elseif is_notom(t3) then smff 147 return 'the left operand is omega, ' smff 148 'the right operand cannot be omega'; 46 end if; smfd 22 elseif constant_equality(t2, t3) then $ do recursive test smfd 23 return 'the operands have different types'; 49 end if; 50 51 (q1_in, q1_notin): 52 $ in: a1 := exists x in a3 | x = a2 53 $ notin: a1 := forall x in a3 | x /= a2 54 $ type a2 = type arb a3 55 if t_string in g3 then 56 if t2 .con t3 .con type_string = type_zero then 57 return 'both operands should be strings'; 58 end if; 59 end if; 60 if t_tuple in g3 and t2 /= type_om then 61 if is_knt(t3) then 62 ct1 := comptyp(t3); 63 if forall tx in ct1 | smfd 24 constant_equality(t2, tx) then 65 return 'the left operand cannot be ' 66 'an element of the right operand'; 67 end if; 68 else 69 tx := comptyp(t3); smfd 25 if constant_equality(t2, tx) then 71 return 'the left operand cannot be ' 72 'an element of the right operand'; 73 end if; 74 end if; 75 end if; 76 if t_set in g3 then 77 tx := comptyp(t3); smfd 26 if constant_equality(t2, tx) then 79 return 'the left operand cannot be ' 80 'an element of the right operand'; 81 end if; 82 end if; 83 84 (q1_incs, q1_sub, q1_mult, q1_mod): 85 $ incs: a1 := forall x in a3 | x in a2 86 $ sub: a1 := { x in a2 | x notin a3 } 87 $ mult: a1 := { x in a2 | x in a3 } 88 $ mod: a1 := ( a2 - a3 ) + ( a3 - a2 ) 89 $ type arb a2 = type arb a3 90 if t_set in g2 then 91 tx := comptyp(t2); 92 ty := comptyp(t3); smfd 27 if constant_equality(tx, ty) then smfd 28 return smfd 29 case opc of smfd 30 (q1_incs): 'the right operand cannot include ' smfd 31 'the left operand', smfd 32 (q1_sub): 'the result will be the left operand', smfd 33 (q1_mult): 'the result will be a null set', smfd 34 (q1_mod): 'the result will be ' smfd 35 'the union of the operands' smfd 36 else '*** error in type_constant ***' smfd 37 end; 95 end if; 96 end if; 97 98 (q1_less): 99 $ a1 := { x in a2 | x /= a3 } 100 $ type arb a2 = type a3 101 if t_set in g2 then 102 tx := comptyp(t2); smfd 38 if tx = type_om then smfd 39 return 'the left operand is a null set'; smfd 40 end if; smfd 41 if constant_equality(tx, t3) then 104 return 'the right operand cannot be ' 105 'an element of the left operand'; 106 end if; 107 end if; 108 109 (q1_lessf): 110 $ a1 := { [ x, y ] in a2 | x /= a3 } 111 $ type arb domain a2 = type a3 112 if (t2 .con:= type_map) /= type_zero then 113 ctp := comptyp(t2); 114 if ctp = type_om then smfd 42 return 'the left operand is a null map'; 116 end if; 117 ct1 := comptyp(ctp); 118 tx := ct1(1); $ domain element of map smfd 43 if constant_equality(tx, t3) then 120 return 'the right operand cannot be ' 121 'an element of the left operand''s domain'; 122 end if; 123 end if; 124 125 (q1_type, q1_isint, q1_isreal, q1_isstr, 126 q1_isbool, q1_isatom, q1_istup, q1_isset): 127 $ a2 should have an ambiguous type 128 if #g2 = 1 then smfk 137 return 'operand has a unique type'; 130 end if; 131 132 (q1_ismap): 133 $ a1 := is_set a2 and forall x in a2 | 134 $ is_tuple x and #x = 2 and x(1) /= om 135 if g2 = grsset then 136 if .is_pair(comptyp(t2)) then 137 return 'operand is always a map'; 138 end if; 139 elseif #g2 = 1 then 140 return 'operand cannot be a map'; 141 end if; 142 143 (q1_of): 144 $ type arb domain a2 = type a3 145 if t_string in g2 or t_tuple in g2 then 146 if t3 .con type_int = type_zero then 147 return 'string or tuple index is not an integer'; 148 end if; 149 end if; 150 if t_set in g2 then 151 if (t2 .con:= type_map) /= type_zero then 152 ctp := comptyp(t2); 153 if ctp = type_om then smfd 44 return 'a retrieval from a null map always yields ' smfd 45 'omega'; 155 end if; 156 ct1 := comptyp(ctp); 157 tx := ct1(1); $ domain element of map smfd 46 if constant_equality(tx, t3) then smfd 47 return 'the index is not in the map''s domain'; 160 end if; 161 end if; 162 end if; 163 164 (q1_ofa): 165 $ a1 := { y : [ x, y ] in a2 | x = a3 } 166 $ type arb domain a2 = type a3 167 if (t2 .con:= type_map) /= type_zero then 168 ctp := comptyp(t2); 169 if ctp = type_om then smfd 48 return 'a retrieval from a null map always yields ' smfd 49 'a null set'; 171 end if; 172 ct1 := comptyp(ctp); 173 tx := ct1(1); $ domain element of map smfd 50 if constant_equality(tx, t3) then smfd 51 return 'the index is not in the map''s domain'; 176 end if; 177 end if; 178 179 (q1_case): 180 $ type arb domain a1 = type a2 181 if (t1 .con:= type_map) /= type_zero then 182 ctp := comptyp(t1); 183 if ctp = type_om then 184 return 'trivial case statement'; 185 end if; 186 ct1 := comptyp(ctp); 187 tx := ct1(1); $ domain element of map smfd 52 if constant_equality(tx, t3) then 189 return 'expression cannot match any case tag value'; 190 end if; 191 end if; 192 193 end case; 194 195 196 end procedure type_constant; 197 198 smfd 53 smfd 54 smfd 55 procedure constant_equality(t1, t2); smfd 56$ smfd 57$ this routine performs the recursive test whether the values described smfd 58$ by t1 and t2 are always equal (not equal) due to their types. smfd 59$ smfd 60 repr smfd 61 t1, t2: elmt types; smfd 62 g1, g2: gross_type; smfd 63 c1, c2: elmt types; smfd 64 ct1, ct2: tuple(elmt types); smfd 65 i: integer; smfd 66 end repr; smfd 67 smfd 68 smfd 69 g1 := grosstyp(t1); if t_om in g1 then g1 less:= t_om; end if; smfd 70 g2 := grosstyp(t2); if t_om in g2 then g2 less:= t_om; end if; smfd 71 smfd 72 if g1 * g2 = {} then smfd 73 $ no common gross type: this will always evaluate to false. smfd 74 return true; smfd 75 smfd 76 elseif #g1 > 1 or #g2 > 1 then smfd 77 $ ambiguous gross types: we cannot know which combination of smfd 78 $ values will be tested. smfd 79 return false; smfd 80 smfd 81 elseif is_prim(g1) then smfd 82 $ here the gross types must be equal: if they are primitive, no smfd 83 $ no component type needs to be tested. smfd 84 return false; smfd 85 smfd 86 elseif is_knt(t1) and is_knt(t2) then smfd 87 $ two known-length tuples smfd 88 $ note that known-length tuples cannot be just null tuples. smfd 89 ct1 := comptyp(t1); ct2 := comptyp(t2); smfd 90 if #ct1 /= #ct2 then smfd 91 return false; smfd 92 else smfd 93 return forall i in [ 1..#ct1 ] | smfd 94 constant_equality(ct1(i), ct2(i)); smfd 95 end if; smfd 96 smfd 97 else smfd 98 $ two non-primitive types smfd 99 if is_knt(t1) then norm(t1); end if; smfd 100 if is_knt(t2) then norm(t2); end if; smfd 101 smfd 102 c1 := comptyp(t1); c2 := comptyp(t2); smfd 103 smfd 104 $ see if one operand is a null set or null tuple: the test is smfd 105 $ constant if the other operand cannot be a null set or tuple. smfd 106 if c1 = type_om then return not is_om(c2); end if; smfd 107 if c2 = type_om then return not is_om(c1); end if; smfd 108 smfd 109 return constant_equality(c1, c2); smfd 110 end if; smfd 111 smfd 112 smfd 113 end procedure constant_equality; smfd 114 smfd 115 1 .=member knt14i 2 3 4 procedure ntyp(g, comp); 5$ 6$ this routine builds a new type descriptor for any type except 7$ 'known length tuple'. its arguments are: 8$ 9$ g: the grosstype of the result 10$ comp: the component type of the result 11$ 12$ this routine always returns a type which is definitely not om, 13$ so that is_om must be separately set if this assumption is false 14$ 15 repr 16 g: gross_type; 17 comp: elmt types; 18 end repr; 19$ 20$ we begin by building the tuple for the type descriptor, then 21$ check that it is not nested to a depth greater than max_depth. 22$ 23 if g = {} then return type_zero; end if; 24 25 if not is_prim(g) and comp = type_zero then smfe 113 return [ g * int_real_str_atom ]; 27 else 28 return [ g less t_om, trim(comp, 2), false ]; 29 end if; 30 31 32 end procedure ntyp; 33 34 35 36 37 procedure knt_type(tps); 38$ 39$ this routine builds the type descriptor for a known length tuple. 40$ 'tps' is a tuple containing the types of the components. 41$ 42$ in order to make sure that the type lattice is finite, we restrict 43$ known length tuples to a maximum length. if #tps exceeds this length, 44$ we return a type descriptor for a homogeneous tuple. 48$ 49 repr 50 tps: tuple(elmt types); 51 tp: elmt types; 52 i, ii: integer; 53 end repr; 54 55 56 if exists tp in tps | tp = type_zero then 57 return type_zero; 58 59 elseif #tps > max_len then 60 return ntyp(grstup, type_zero .dis/ tps); 61 62 elseif exists ii in [ #tps, #tps-1..1 ] | tps(ii) /= type_om then 63 return 64 [ grstup, 65 [ trim(tps(i), 2) : i in [ 1..ii ] ], 66 true ]; 67 68 else 69 return [ grstup, type_om, false ]; 70 end if; 71 72 73 end procedure knt_type; 74 75 1 .=member par14j 29 30 31 procedure pair_type(t1, t2); 32 33$ this routine builds the type descriptor for a pair whose component 34$ types are t1 and t2. 35$ 36 repr smfe 114 t1, t2: elmt types; 39 end repr; 40 smfe 115 return knt_type( [ t1 .con type_notom, t2 .con type_notom ] ); 42 43 end procedure pair_type; 44 45 1 .=member trm14k 2 3 4 procedure trim(tp, n); 5$ 6$ this routine walks recursively through the component type of tp. if 7$ tp is too deeply nested, it replaces the inner most component type 8$ with type_gen. n is the nesting level of the recursive walk. 10$ 11 repr 12 t: elmt types; 13 tp: elmt types; 14 g: gross_type; 15 c: general; 16 n: integer; 17 end repr; 18 smfe 116 if tp = type_zero then return type_zero; end if; smfe 117 if tp = type_gen then return type_gen; end if; smfe 118 smfe 119 if n > max_depth then return type_gen; end if; smfe 120 smfe 121 [ g, c ] := tp; 27 smfe 122 if is_prim(g) then 29 return tp; 30 31 elseif is_knt(tp) then 32 return [ g, [ trim(t, n+1) : t in c ], true ]; 33 34 else 35 return [ g, trim(c, n+1), false ]; 36 end if; 37 38 end procedure trim; 39 40 41 42 43 procedure norm(rw tp); 44$ 45$ convert mixed tuple type to tuple. 46$ 47 repr 48 tp: elmt types; 49 end repr; 50 51 52 tp := ntyp(grosstyp(tp), type_zero .dis/ comptyp(tp)); 53 54 end procedure norm; 55 56 1 .=member con14l 2 3 4 operator .con(t1, t2); 5$ smfd 116$ this routine computes the meet (conjunction, 'and') of two types. 8$ 9 repr 10 t1, t2, tp: elmt types; 11 g1, g2, g: gross_type; 12 c1, c2, c: general; smfe 123 ct1: tuple(elmt types); 14 i: integer; 15 can_be_om: boolean; 16 end repr; 17 18 if t1 = type_zero then return type_zero; end if; 19 if t2 = type_zero then return type_zero; end if; smfd 117 smfd 118 if t1 = type_gen then return t2; end if; smfd 119 if t2 = type_gen then return t1; end if; smfd 120 smfd 121 if t1 = t2 then return t1; end if; 20 21 [ g1, c1 ] := t1; 22 [ g2, c2 ] := t2; 23 24 can_be_om := is_om(t1) and is_om(t2); 25 smfd 122 if is_prim(g1) or is_prim(g2) then $ one is primitive 36 tp := [ g1 * g2 ]; 37 smfd 123 elseif is_knt(t1) and is_knt(t2) then $ both known length smfe 124 ct1 := [ ]; 40 41 (forall i in [ 1..(#c1 min #c2) ]) smfe 125 ct1(i) := c1(i) .con c2(i); 43 smfe 126 if ct1(i) = type_zero then 45 return if can_be_om then type_om else type_zero end; 46 end if; 47 end forall; 48 smfe 127 tp := [ g1*g2, ct1, true ]; 50 smfd 124 else $ both sets or tuples 52 g := g1 * g2; 53 if g = {} then return type_zero; end if; 54 55 if is_knt(t1) or is_knt(t2) then 56 $ one known-length, one unknown-length tuple smfe 128 if is_knt(t2) then c := c2; c2 := c1; c1 := c; end if; 58 $ c1 is known-length tuple smfe 129 ct1 := [ ]; 60 (forall i in [ 1..#c1 ]) smfe 130 ct1(i) := c1(i) .con c2; smfe 131 if ct1(i) = type_zero then 63 return if can_be_om then type_om else type_zero end; 64 end if; 65 end forall; 66 smfe 132 tp := [ g, ct1, true ]; 68 69 else 70 c := c1 .con c2; 71 if c = type_zero then 72 if t_set notin g then return type_zero; end if; 73 c := type_om; 74 end if; 75 tp := [ g, c, false ]; 76 end if; 77 end if; 78 smfd 125 assert can_be_om = is_om(tp); smfd 126 assert can_be_om impl tp /= type_zero; smfd 127 return tp; 83 84 85 end operator .con; 86 87 1 .=member dis14m 2 3 4 operator .dis(t1, t2); 5$ smfd 128$ this routine computes the join (disjunction, 'or') of two types. smfd 129$ smfe 133$ assert t_set in bsctyps; smfe 134$ assert t_set in grosstyp(tp) impl not is_knt(tp); smfd 130$ assert is_prim(grosstyp(type_om)); 8$ 9 repr 10 t1, t2, tp: elmt types; smfe 135 g1, g2, g: gross_type; smfe 136 c1, c2, c: general; smfe 137 ct1: tuple(elmt types); 13 i: integer; 15 end repr; 16 17 if t1 = type_zero then return t2; end if; 18 if t2 = type_zero then return t1; end if; smfd 131 smfd 132 if t1 = type_gen then return type_gen; end if; smfd 133 if t2 = type_gen then return type_gen; end if; smfd 134 smfd 135 if t1 = t2 then return t1; end if; 19 20 [ g1, c1 ] := t1; 21 [ g2, c2 ] := t2; 22 smfd 136 if is_prim(g1) and is_prim(g2) then $ both are primitive 38 tp := [ g1 + g2 ]; 39 smfd 137 elseif is_prim(g1) then $ t1 primitive smfd 138 tp := [ g1+g2, c2, is_knt(t2) ]; 43 smfd 139 elseif is_prim(g2) then $ t2 primitive smfd 140 tp := [ g1+g2, c1, is_knt(t1) ]; 47 smfd 141 elseif is_knt(t1) and is_knt(t2) then $ both known length 49 $ for the following note that if the length of the two 50 $ tuples differ, we can view the shorter on to have an 51 $ arbitrary number of type_om components at the end. smfe 138 ct1 := if #c1 >= #c2 then c1 else c2 end; 56 (forall i in [ 1..(#c1 min #c2) ]) smfe 139 ct1(i) := c1(i) .dis c2(i); 58 end forall; smfe 140 (forall i in [ (#c1 min #c2)+1..#ct1 ]) smfe 141 ct1(i) .dis:= type_om; 61 end forall; 62 smfe 142 tp := [ g1+g2, ct1, true ]; 64 smfd 142 else $ both sets or tuples 66 if is_knt(t1) then c1 := type_zero .dis/ c1; end if; 67 if is_knt(t2) then c2 := type_zero .dis/ c2; end if; 68 69 tp := [ g1+g2, c1 .dis c2, false ]; 70 end if; smfe 143 smfe 144 loop $ normalise type_gen smfe 145 doing [ g, c ] := tp; smfe 146 while g = bsctyps and grosstyp(c) = bsctyps smfe 147 do smfe 148 tp := c; smfe 149 end loop; 71 smfd 143 assert (is_om(t1) or is_om(t2)) = is_om(tp); 74 return tp; 75 76 end operator .dis; 77 78 1 .=member sub14n 2 3 4 operator .sub(t1, t2); 5$ 6$ this operator computes the difference between the types t1 and t2. 7$ the difference between two types is defined to mean the type which t1 8$ can assume but t2 cannot. 9$ 10 repr 11 t1, t2, c1, c2: elmt types; 12 tp1, tp2: elmt types; 13 g, gprim, g1, g2: gross_type; smfd 144 ct1, ct2, c: tuple(elmt types); smfd 145 i: integer; 14 end repr; 15 16 g1 := grosstyp(t1); 17 g2 := grosstyp(t2); 18 19 if t2 = type_gen then 20 return type_zero; 21 22 elseif t1 = type_om and is_notom(t2) then 23 return type_om; 24 25 elseif is_prim(g1) then 26 return [ g1 - g2 ]; 27 28 elseif is_prim(g2) then 29 return [ g1 - g2, comptyp(t1), is_knt(t1) ]; 30 31 else 32 gprim := g1 - g2 - tup_set_map; 33 smfd 146 if is_knt(t1) and is_knt(t2) then $ two know-length tuples smfd 147 ct1 := comptyp(t1); ct2 := comptyp(t2); c := ct1; smfd 148 (forall i in [ 1..(#ct1 min #ct2) ]) smfd 149 c(i) := ct1(i) .sub ct2(i); smfd 150 end forall; smfd 151 smfd 152 return smfd 153 if forall c1 in c | c1 = type_zero then smfd 154 $ this tuple does not describe any value: don't smfd 155 $ include it into the result. smfd 156 [ gprim ] smfd 157 else smfd 158 $ recall that the type descriptor for a known-length smfd 159 $ tuple cannot describe a set type. smfd 160 [ gprim with t_tuple, c, true ] smfd 161 end; smfd 162 end if; smfd 163 34 if t_tuple in g1 and is_knt(t1) then norm(t1); end if; 35 if t_tuple in g2 and is_knt(t2) then norm(t2); end if; 36 37 c1 := comptyp(t1); 38 c2 := comptyp(t2); 39 40 if (g := g1-g2) * tup_set_map /= {} then 41 42 $ t1 contains a composite type which t2 does not contain: 43 $ tp1 describes this type. 44 smfd 164 tp1 := [ g, c1, false ]; 46 47 else 48 tp1 := type_zero; 49 end if; 50 51 if (g := g1 * g2 * tup_set_map) /= {} then 52 53 $ t1 and t2 have a common composite type: tp2 will descibe 54 $ its difference type. 55 56 if c1 .con c2 = type_zero then tp2 := t1; 57 elseif c1 .con c2 = c1 then tp2 := type_zero; 58 else 59 tp2 := [ g, c1 .sub c2, false ]; 60 end if; 61 62 else 63 tp2 := type_zero; 64 end if; 65 66 return [ gprim ] .dis tp1 .dis tp2; 67 68 end if; 69 70 71 end operator .sub; 72 73 1 .=member cnt14o 2 3 4 procedure const_typ(v); 5$ 6$ this routine returns a type descriptor for a constant value. 7$ 8 repr 9 x, v: general; 10 g: gross_type; 11 end repr; 12 13 g := { if is_integer v then t_int 14 elseif is_real v then t_real 15 elseif is_string v then t_string 16 elseif is_boolean v then t_atom 17 elseif is_tuple v then t_tuple 18 elseif is_set v then t_set 19 else t_om 20 end }; 21 22 return 23 if g = grsset then 24 if v = {} then 25 [ grsset, type_om, false ] 26 else 27 ntyp( grsset, type_zero .dis/[ const_typ(x) : x in v ] ) 28 end 29 30 elseif g = grstup then 31 knt_type( [ const_typ(x) : x in v ] ) 32 33 else 34 [ g ] 35 end; 36 37 end procedure const_typ; 38 39 smfe 150 smfe 151 smfe 152 procedure string_length(tp); smfe 153$ smfe 154$ this routine does a recursive tree walk of the type descriptor tp to smfe 155$ compute a measure for the length of the string formatted by smfe 156$ format_type. smfe 157$ smfe 158 repr smfe 159 tp, tx: elmt types; smfe 160 g: gross_type; smfe 161 c: general; smfe 162 end repr; smfe 163 smfe 164 smfe 165 if tp = type_zero then return 1; end if; smfe 166 if tp = type_gen then return 1; end if; smfe 167 smfe 168 if tp = type_notom then return 1; end if; smfe 169 smfe 170 [ g, c ] := tp; smfe 171 smfe 172 if c = type_om then smfe 173 if g = grstup then return 1; end if; smfe 174 if g = grsset then return 1; end if; smfe 175 if g = grsmap then return 1; end if; smfe 176 end if; smfe 177 smfe 178 return smfe 179 if is_om(tp) then 1 else 0 end $ omega smfe 180 + #(g * int_real_str_atom) $ primitive types smfe 181 + if t_tuple in g and is_knt(tp) then $ known-length tuple smfe 182 0 +/[ string_length(tx) : tx in c ] smfe 183 elseif g * tup_set_map /= {} then $ set or tuple smfe 184 #(g * tup_set_map) * string_length(c) smfe 185 else smfe 186 0 smfe 187 end; smfe 188 smfe 189 smfe 190 end procedure string_length; smfe 191 smfe 192 40 41 42 operator .is_pair(tp); 43$ 44$ this operator returns yes or no depending on whether tp is a type 45$ descriptor for a pair of non-omega values. 46$ 47 repr 48 tp: elmt types; 49 c: tuple(elmt types); 50 end repr; 51 52 return 53 if grosstyp(tp) less t_om = grstup 54 and is_knt(tp) 55 and #(c := comptyp(tp)) = 2 56 and is_notom(c(1)) 57 and is_notom(c(2)) 58 then true 59 else false 60 end; 61 62 end operator .is_pair; 63 64 65 66 67 operator .is_map(tp); 68$ 69$ this operator returns yes, no, or maybe depending on whether tp is 70$ a type descriptor for a map. 71$ 72 repr 73 tp: elmt types; 74 end repr; 75 76 if grosstyp(tp) less t_om = grsset then 77 return .is_pair(comptyp(tp)); 78 else 79 return false; 80 end if; 81 82 83 end operator .is_map; 84 85 1 .=member adt14p 2 3 4 procedure ads_type(tp); 5$ 6$ this procedure (recursively) transforms types into the form desired 7$ by the automatic data-structure selection module. the transformations 8$ are the following: 9$ 10$ 1. transform the type set(tuple(x,y)) into the type map(tuple(x,y)) 11$ 2. transform the type [ bsctyps [ type_gen ] ] into type_gen 12$ 3. drop the is_om flag 13$ 14 repr 15 tp: elmt types; 16 g: gross_type; 17 ctp: elmt types; 18 end repr; 19 20 21 g := grosstyp(tp) less t_om; 22 23 if tp = type_om then 24 return type_om; 25 26 elseif tp = type_gen then 27 return type_notom; 28 29 elseif is_prim(g) then 30 return [ g ]; 31 32 elseif g = bsctyps less t_om and 33 grosstyp(tp(2)) with t_om = bsctyps then 34 $ 'normalise' type_gen: since we are about to throw away the 35 $ t_om value, this test does what it should. 36 return ads_type(tp(2)); 37 38 elseif g = grsset then 39 return [ if .is_pair(comptyp(tp)) then grsmap else grsset end, 40 ads_type(tp(2)) ]; 41 42 elseif is_knt(tp) then 43 return [ g, [ ads_type(ctp) : ctp in comptyp(tp) ], true ]; 44 45 else 46 return [ g, ads_type(comptyp(tp)), false ]; 47 end if; 48 49 50 end procedure ads_type; 51 52 53 end module setl_optimizer - typfind; 54 55 1 .=member admn15 2 3 4 module setl_optimizer - auto_dstruct; smfd 165$ smfd 166$ the following automatic data structure selection algorithm uses an smfd 167$ approach to this problem differing from that described in smfd 168$ and . although the approach suggested there has a rather smfd 169$ simple structure, it suffers from several deficiencies which have led smfd 170$ us to an alternative approach. the new approach, to be described smfd 171$ below, is closer to ed schonberg's algorithm and seems to be smfd 172$ faster than the earlier approach. in spite of the differences between smfd 173$ these two methods, they have a similar overall logic which is simpler smfd 174$ than that of previously suggested algorithms. among these smfd 175$ simplifications are: use of the bfrom and ffrom maps instead of smfd 176$ value-flow maps; and elimination of a phase which inserts 'locate' smfd 177$ instructions into the code. smfd 178$ smfd 179$ we first describe the new automatic data structure selection algorithm smfd 180$ heuristically: smfd 181$ smfd 182$ (1) initially, all instructions i in the code to be processed are smfd 183$ analysed separately. in this analysis we proceed in a manner smfd 184$ depending on the opcode of i and the types of its arguments, and smfd 185$ generate bases b1, b2, ..., bn, such that all of the occurrences in i smfd 186$ can be given a data structure representation such that each of the smfd 187$ above bases appear in at least one such declaration, and such that if smfd 188$ these representations are used, the execution time of i will either smfd 189$ remain substantially the same, or else become faster. smfd 190$ smfd 191$ a base bi is generated only if at least one occurrence in i accesses smfd 192$ its elements, and if introduction of this base does not slow i down. smfd 193$ i could slow down if hashing of a value into bi is required at i (e.g. smfd 194$ if a new value of an element of bi may have been created in executing smfd 195$ i), or if base conversions at i may be required (e.g. if different smfd 196$ bases are assigned to the arguments in a set union instruction). smfd 197$ smfd 198$ a second property that we require the generated bases to possess is smfd 199$ that even after the introduction of repred arguments, the instruction smfd 200$ i should be equivalent to i with its arguments having their original smfd 201$ types (and forms). thus, in an instruction 'c := a + b;', if a is a smfd 202$ set of integers, b is a set of characters and c is (necessarily) a set smfd 203$ of general elements, no bases are generated, for in order for the smfd 204$ instruction not to slow down, all three arguments must be based on the smfd 205$ same base, whose elements must therefore be of general type. thus, a smfd 206$ and b become sets of general elements, overestimating their previous smfd 207$ types. smfd 208$ smfd 209$ this restriction reflects one of the underlying principles of our smfd 210$ approach, namely: the types of variable occurrences, as produced by smfd 211$ the type finder, should not be modified during automatic data smfd 212$ structure selection. such modifications are possible in two cases: smfd 213$ smfd 214$ (i) during the initial basing pass, if types are converted into based smfd 215$ reprs which are not equivalent to the original types (as in the above smfd 216$ set union example). smfd 217$ smfd 218$ (ii) by merging based reprs of two occurrences having different types. smfd 219$ such a merge may cause equivalencing of two bases b1 and b2 whose smfd 220$ element-modes are not equal, so that the new base will not be really smfd 221$ equivalent either to b1 nor to b2, and consequently the types of reprs smfd 222$ based on b1 or b2 will have changed. smfd 223$ smfd 224$ in both cases, types become more general, and never more restricted. smfd 225$ hence, the new types will over-estimate the actual types, so that the smfd 226$ code will still be safe. however, because types may become less smfd 227$ specific, we are apt to generate less efficient code, in the sense smfd 228$ that some q2 instructions may become more general (and therefore more smfd 229$ time consuming), and extra type checks and conversions may be smfd 230$ required. smfd 231$ smfd 232$ for these reasons, we prefer to make sure that cases (i) and (ii) will smfd 233$ not occur in our algorithm, and so keep the types of occurrences smfd 234$ unchanged (see also remark (1) below). smfd 235$ smfd 236$ not all generated bases actually speed up the program execution. smfd 237$ those that do not are useless, and, unless we can later merge them smfd 238$ with more useful bases, will be suppressed (see (4) below). however, smfd 239$ we find it useful to introduce these extra bases since doing so makes smfd 240$ it easier to propagate basings across instructions (see (3) below). smfd 241$ smfd 242$ generated bases whose introduction speeds up the execution of the smfd 243$ instruction in connection with which they are introduced will be smfd 244$ called 'effective bases', and all other generated bases will be called smfd 245$ 'neutral bases'. smfd 246$ smfd 247$ examples: smfd 248$ smfd 249$ (a) t := s with x; smfd 250$ smfd 251$ if t and s are sets with elements of the same type, which is also smfd 252$ equal to the type of x, we generate one effective base b, repr s and t smfd 253$ as set(elmt b), and repr x as elmt b. if, in addition, we know that s smfd 254$ is a set(tuple(tx, ty)), and that x is a tuple(tx, ty), we annotate smfd 255$ the repr for t to mark that t is a potentially multi-valued map. if s smfd 256$ and t are homogeneous tuples having the same element type, which is smfd 257$ also equal to the type of x, we generate one neutral base b, with smfd 258$ reprs analogous to the above. smfd 259$ smfd 260$ (b) y := f(x); smfd 261$ smfd 262$ if f is a map, with a domain type equal to the type of x, and a range smfd 263$ type equal to the type of y, we generate one effective base b1 and one smfd 264$ neutral base b2, and generate the following representations: smfd 265$ smfd 266$ f: smap(elmt b1) elmt b2; x: elmt b1; y: elmt b2; smfd 267$ smfd 268$ we also annotate the repr for f to indicate that f is involved in an smfd 269$ operation which requires single-valuedness, for which local basing smfd 270$ would be most appropriate. smfd 271$ smfd 272$ if the type of y is not equal to the range type of f, we do not smfd 273$ generate b2, and if the type of x is not equal to the domain type of smfd 274$ f, we do not generate b1. smfd 275$ smfd 276$ if f is a homogeneous tuple, and the type of y is equal to the smfd 277$ component type of f, we generate one neutral base b and repr f as smfd 278$ tuple(elmt b) and y as elmt b. smfd 279$ smfd 280$ if f is a string or of an ambiguous type, no bases are generated. smfd 281$ smfd 282$ (c) y := x; smfd 283$ smfd 284$ unless the types of x and y are unequal, we generate one neutral base smfd 285$ b, and repr x and y as elmt b. smfd 286$ smfd 287$ note that many of the restrictions imposed in the above examples will smfd 288$ be satisfied automatically in view of the action of the final phase of smfd 289$ the type finder, which assigns to each o-variable the 'forward' type smfd 290$ of its i-variable. for example, in (a) above, if s is a set and the smfd 291$ type of its elements is equal to the type of x, then the type of t smfd 292$ will always be equal to that of s; similarly, in (c) above, the type smfd 293$ of y will always be equal to the type of x. however, we have stated smfd 294$ the above restrictions in order to make our data structure selection smfd 295$ algorithm as independent of the type finder as possible. smfd 296$ smfd 297$ smfd 298$ (2) after the initial base generation phase, most variable occurrences smfd 299$ will have been based either on effective bases or on neutral bases. smfd 300$ our algorithm now assumes that effective bases, as well as bases that smfd 301$ can be merged with effective bases, are advantageous. moreover, base smfd 302$ merging is performed by passing basing information between smfd 303$ instructions according to the following heuristics: smfd 304$ smfd 305$ let vo1, vo2 be two occurrences of the same variable which are linked smfd 306$ by the bfrom map, and suppose that we want to merge the base smfd 307$ information of vo1 with that of vo2. let repr1, repr2 be the smfd 308$ generated reprs of vo1, vo2, respectively. in order to merge these smfd 309$ reprs, vo1 and vo2 must have the same type. if this is the case, then smfd 310$ repr1 and repr2 describe objects having the same type, and by smfd 311$ comparing their structures we can either equivalence or find other smfd 312$ relations between the bases which these objects involve. a more smfd 313$ detailed description of this procedure is given in phase 2 of our smfd 314$ algorithm below. smfd 315$ smfd 316$ smfd 317$ (3) the base generation pre-pass described above enables us to avoid smfd 318$ propagation of base information between arguments of the same smfd 319$ instruction, a task which would call for some messy routines, smfd 320$ resembling the 'forward' and 'backward' routines of the type finder, smfd 321$ and would also increase the time consumed by our algorithm smfd 322$ (see ). however, base propagation across instructions is smfd 323$ already performed implicitly within the initial base generation phase. smfd 324$ subsequent base merging only needs to consider bfrom links. to smfd 325$ convince ourselves that this is indeed the case, we consider several smfd 326$ examples. smfd 327$ smfd 328$ example a. smfd 329$ smfd 330$ (i1) s with:= x; $ s is a set. smfd 331$ (i2) v(i) := x; $ v is a tuple. smfd 332$ (i3) y := v(j); smfd 333$ (i4) z := f(y); $ f is a map. smfd 334$ smfd 335$ assume that v is a homogeneous tuple. then the initial basing pass we smfd 336$ will produce the following basings (where only b1, b4 are effective): smfd 337$ smfd 338$ s1: set(elmt b1); x1: elmt b1; smfd 339$ v2: tuple(elmt b2); x2: elmt b2; smfd 340$ y3: elmt b3; v3: tuple(elmt b3); smfd 341$ y4: elmt b41; z4: elmt b42; smfd 342$ f4: map(elmt b41) elmt b42; smfd 343$ smfd 344$ then, when bases are merged along bfrom links, b1 and b2 will be smfd 345$ merged (using the x-link from i1 to i2); b2 and b3 will be merged smfd 346$ (via the v-link); b3 and b41 will be merged (via the y-link). if we smfd 347$ did not introduce neutral bases for i2 and i3, we would have to smfd 348$ propagate basings across i2 and i3 in order to deduce that b1 and b41 smfd 349$ should be merged. smfd 350$ smfd 351$ note also that in the steps just described b42 has not been merged smfd 352$ with an effective base. if this condition persists, b42 will be smfd 353$ dropped during the base adjustment phase. smfd 354$ smfd 355$ example b. (s, u and t are assumed to be sets) smfd 356$ smfd 357$ (i1) s with:= x; smfd 358$ (i2) u with:= s; smfd 359$ (i3) t from u; smfd 360$ (i4) y from t; smfd 361$ smfd 362$ here, after the pre-pass, we would have: smfd 363$ smfd 364$ s1: set(elmt b1); x1: elmt b1; smfd 365$ u2: set(elmt b2); s2: elmt b2; smfd 366$ t3: elmt b3; u3: set(elmt b3); smfd 367$ y4: elmt b4; t4: set(elmt b4); smfd 368$ smfd 369$ using the bfrom links which apply to this fragment of code, we would smfd 370$ first merge the two reprs elmt b2 and set(elmt b1) of the s smfd 371$ occurrences; this will give us information about the element mode of smfd 372$ b2, i.e. elmt b2 = set(elmt b1). then we equivalence b2 and b3 via smfd 373$ the bfrom link for u. finally, using the bfrom link for t, we deduce smfd 374$ that elmt b3 = set(elmt b4). this example shows that repr merging has smfd 375$ to be done transitively, so that b1 and b4 ought to be equivalenced smfd 376$ once b2 and b3 are equivalenced, since the merging of b2 and b3 calls smfd 377$ for the merging of the reprs set(elmt b1) and set(elmt b4). this smfd 378$ additional merge must also be taken care of during the base adjustment smfd 379$ phase of our algorithm. smfd 380$ smfd 381$ smfd 382$ (4) if, after merging, all the effective bases in some equivalence smfd 383$ class of bases support occurrences of only one composite object, all smfd 384$ the bases in this class should be suppressed. this remark applies smfd 385$ also to the case in which the class contains no effective bases. smfd 386$ smfd 387$ smfd 388$ (5) a delicate issue arising in previous automatic data-structure smfd 389$ selection algorithms was the insertion of 'locate' operations into the smfd 390$ code being processed. these operations compute base pointers for smfd 391$ elements of a base, inserting them into the base when necessary. this smfd 392$ problem is still delicate, but we have shifted it to the (subsequent) smfd 393$ conversion optimisation phase of the optimiser, where it is treated as smfd 394$ a special case of a general conversion insertion algorithm. we can smfd 395$ therefore ignore this problem completely in the present algorithm, smfd 396$ simplifying the algorithm considerably. smfd 397$ smfd 398$ smfd 399$ (6) in our original design of this algorithm we determined refined smfd 400$ representations (such as local, remote, and sparse) during a final smfd 401$ phase of our algorithm. our experience, however, has shown that the smfd 402$ selection of set-types (i.e. local, remote, and sparse) as well as the smfd 403$ selection of map-types (i.e. single-valued map v. multi-valued map) smfd 404$ can be done naturally during the pre-pass and the subsequent merge smfd 405$ phase. our algorithm now selects during the pre-pass appropriate set- smfd 406$ and map-types (where applicable), and our merge phase merges these smfd 407$ attributes while it merges the base element modes. smfd 408$ smfd 409 smfd 410$ global variables and abstract data structures of the algorithm smfd 411$ ------ --------- --- -------- ---- ---------- -- --- --------- smfd 412 smfd 413$ the above remarks suggest a rather simple automatic data-structure smfd 414$ selection algorithm. the detailed algorithm is given below. smfd 415$ smfd 416$ the input to this algorithm consists of the data flow maps bfrom and smfd 417$ ffrom, and the type map typ, which gives the computed type of each smfd 418$ variable occurrence. smfd 419$ smfd 420$ the output of the algorithm is another map on occurrences, called smfd 421$ oi_repr, mapping each occurrence to a suggested repr. note that the smfd 422$ actual form of repred variables is not modified until the subsequent smfd 423$ name-splitting phase. smfd 424$ smfd 425$ during automatic data-structure selection, reprs are treated as smfd 426$ extended type descriptors. more precisely, each repr is (at least) a smfd 427$ four component tuple having the form smfd 428$ smfd 429$ rpr := [ grosstyp(rpr): set(elmt basic_types, including t_elmt), smfd 430$ comptyp(rpr): repr, smfd 431$ is_knt: boolean, smfd 432$ is_based: boolean ] smfd 433$ smfd 434$ set types (i.e. sets and maps) have a fifth component indicating the smfd 435$ basing type (i.e. local, remote, or sparse) that should be used if smfd 436$ this set type becomes a based type. map types in addition have a smfd 437$ sixth component indicating whether this map is definitively single- smfd 438$ valued (e.g. f in f(x) which is only defined for single-valued maps), smfd 439$ definitively multi-valued (e.g. f in f{x}), or whether we don't know smfd 440$ so far. smfd 441$ smfd 442$ note that a repr is represented in much the same way as a type, but smfd 443$ may involve the additional gross type element_of_base (denoted by smfd 444$ t_elmt) whose component type is the base name. (see the type finder smfd 445$ for additional information concerning the representation of types). 291$ 292 const $ auxiliary repr mnemonics 293 based = 1, unbased = om; 294 295 macro base_of(rpr); rpr(2) endm; 296 297 298$ the following global variables are used in this phase: 299 300 var 301 bases, $ set of all generated bases 302 repbase, $ maps each base to its representative base 303 nbases, $ maps each representative base to number of 304 $ bases in its eqivalence class 305 is_effective, $ maps each base to an effectiveness indicator 306 basedoccs, $ set of all (tentatively) based occurrences 307 aux_repr; $ maps each occurrence to its (tentative) repr 308 309$ additional various global variables, constants and macros are: 310 311 const $ various tuples of argument indices 312 tup1 = [ 1 ], 313 tup2 = [ 2 ], 314 tup3 = [ 3 ], 315 tup12 = [ 1, 2 ], 316 tup13 = [ 1, 3 ], 317 tup14 = [ 1, 4 ], 318 tup23 = [ 2, 3 ], 319 tup123 = [ 1, 2, 3 ], 320 tup124 = [ 1, 2, 4 ], 321 tup134 = [ 1, 3, 4 ]; 322 323 const $ various gross types 324 grsset = { t_set }, 325 grsmap = { t_map }, 326 grstup = { t_tuple }, 327 grselmt = { t_elmt }; 328 329 macro based_pair(ebx, eby); 330 [ grstup, [ ebx, eby ], true, based ] 331 endm; 332 smfl 6 macro BASED_TUP(EB); smfl 7 [ GRSTUP, EB, false, BASED ] smfl 8 endm; smfl 9 smfl 10 macro BASED_KNT(C); smfl 11 [ GRSTUP, C, true, BASED ] smfl 12 endm; 340 smfl 13 macro BASED_SET(EB, STP); smfl 14 GENABASE( smfl 15 [ GRSSET, EB, om, BASED, STP ], smfl 16 [], smfl 17 [] ) smfl 18 endm; smfl 19 smfl 20 macro BASED_MAP(EBX, EBY, STP, MTP); smfl 21 GENABASE( smfl 22 [ GRSMAP, BASED_PAIR(EBX, EBY), om, BASED, STP, MTP ], smfl 23 [], smfl 24 [] ) smfl 25 endm; 352 353 var 354 workpile, $ used for repr merging 355 ins, $ current instruction during base generation 356 argsi, $ args of current instruction 357 droppables, $ set of all non-effective bases 358 seendrops; $ set of all non-effective bases which had all 359 $ non-effective bases in their element mode 360 $ (recursively) replaced by the respective 361 $ element mode of the non-effective base. 362 363 364 repr 365 bases: remote set(tent_base); 366 droppables: remote set(tent_base); 367 seendrops: remote set(tent_base); 368 repbase: remote smap(tent_base) tent_base; 369 nbases: remote smap(tent_base) integer; 370 is_effective: remote smap(tent_base) general; 371 basedoccs: remote set(occurrence); 372 aux_repr: remote smap(occurrence) elmt types; 373 ins: elmt insts; 374 argsi: tuple(symbol); 375 workpile: set(tuple(elmt types, elmt types)); 376 grselmt: gross_type; 377 grstup: gross_type; 378 grsset, grsmap: gross_type; 379 tup1, tup2, tup3: tuple(integer); 380 tup12, tup13, tup14: tuple(integer); 381 tup23: tuple(integer); 382 tup123, tup124, tup134: tuple(integer); 383 basegen: procedure; 384 genbases: procedure; 385 genabase: procedure( 386 elmt types, 387 tuple(integer), 388 tuple(integer) ) 389 elmt types; 390 maxscope: procedure(tuple(elmt base_scopes)) 391 elmt base_scopes; 392 basemerge: procedure; 393 equibase: procedure(tent_base, tent_base); 394 .lim: operator(tent_base) tent_base; 395 baseadjust: procedure; 396 fancy_output: procedure; 397 real_repr: procedure(elmt types) elmt types; 398 end repr; 399 400 401 402 procedure auto_data; 403$ 404$ this is the main driving routine of our algorithm. it consists 405$ of calls to the various phases of the algorithm. 406$ 411 title('cims.setl.' + prog_level + ' - data structure choice'); 412 printa(term_file, ' - data structure selection'); 413 416 basegen; $ the base generation pre-pass. 417 basemerge; $ the base merging phase. 418 baseadjust; $ the base and repr adjustment phase. 419 420 $ delete the static variables global to the module 421 bases := om; repbase := om; nbases := om; 422 is_effective := om; basedoccs := om; aux_repr := om; 423 workpile := om; ins := om; argsi := om; 424 seendrops := om; droppables := om; 425 426 statistics with:= time; $ save time for final statistics 431 432 433 end procedure auto_data; 434 435 1 .=member bgn15a 2 3 smfd 446 smfd 447$ 1. the base generation pre-pass smfd 448$ -- --- ---- ---------- -------- smfd 449 smfd 450$ during this phase we iterate through the code, generating bases smfd 451$ whenever appropriate. these bases are linked across instructions, smfd 452$ subject to constraints determined by the types of the i-variables and smfd 453$ the instruction. the output of this phase is the map aux_repr, which smfd 454$ maps occurrences into (based) reprs. smfd 455$ smfd 456$ for each instruction ins generate bases as described in (1) above. smfd 457$ this is done in a manner depending on the opcode of the instruction smfd 458$ ins and on the type of its arguments. for each base generated, we smfd 459$ compute the form of its elements from the types of the arguments of smfd 460$ ins, modify the aux_repr map of the based arguments of ins to show the smfd 461$ appropriate based representations, and classify each generated base as smfd 462$ effective or neutral. smfd 463$ smfd 464$ the following instructions are relevant: smfd 465$ smfd 466$ q1_in, q1_notin, q1_incs, q1_eq, q1_ne, smfd 467$ q1_add, q1_sub, q1_mult, q1_mod, smfd 468$ q1_with, q1_less, q1_lessb, q1_lesse, q1_lessf, smfd 469$ q1_arb, q1_arbb, q1_arbe, q1_dom, q1_range, smfd 470$ q1_set, q1_set1, q1_tup, q1_tup1, smfd 471$ q1_inext, q1_next, q1_inextd, q1_nextd, smfd 472$ q1_of, q1_ofa, q1_subst, q1_end, smfd 473$ q1_sof, q1_sofa, q1_ssubst, q1_send, smfd 474$ q1_asn, q1_argin, q1_argout smfd 475$ smfd 476$ these opcodes generate bases linked across the instruction if their smfd 477$ argument types satisfy specific constraints. otherwise, they use the smfd 478$ same heuristic the remaining opcodes use, namely that for each smfd 479$ occurrence voi we generate a neutral base which supports only voi. we smfd 480$ do this so that basing informations are propagated across a program as smfd 481$ much as possible, reducing the risk of repeated conversions which smfd 482$ might be needed otherwise, as the following example shows: smfd 483$ smfd 484$ (1) s := {}; $ s1: set(elmt b1) smfd 485$ (while ...) smfd 486$ read(x); smfd 487$ (2) if x in s then $ s2: elmt b2 smfd 488$ (3) s with:= 2*x; $ s3: set(elmt b3) smfd 489$ end if; smfd 490$ end while; smfd 491$ smfd 492$ in this case, basing s2 as shown will finally base all three smfd 493$ occurrences of s as set(elmt b), for some base b, but will not add smfd 494$ input values of x to b, which will force hashing in instruction (2). smfd 495$ smfd 496$ this will keep b as small as possible. the other alternative, namely smfd 497$ to leave s2 unbased, is much worse, as it forces a repeated conversion smfd 498$ between the based and unbased forms of s. (note that in instruction smfd 499$ (2) x has the type general, while s has the type set(integer). in smfd 500$ this case, our heuristic principle for the q1_in instruction forbids smfd 501$ common basing of x and s in this instruction). smfd 502$ 6 7 procedure basegen; 8$ 9$ this procedure iterates through the code, generating bases whenever 10$ appropriate. it builds up initial versions of various maps on bases 11$ and occurences. 12$ 13 repr 14 vo: occurrence; 15 tp: elmt types; 16 r: routine; 17 b: elmt blocks; 18 end repr; 19 20 21 aux_repr := typ; $ maps occurrences to tentively based types: 22 $ start with the known (unbased) types of 23 $ variable occurences, the result of the 24 $ type finder. 25 bases := {}; $ set of all bases 26 elmt_mode := {}; $ a map from bases to their element-mode. 27 bscope := {}; $ a map from bases to their scope. 28 userbase := {}; $ a map which sends each base to an 29 $ equivalent user-supplied base (if any). 30 is_effective := {}; $ an effectiveness indicator for bases, which 31 $ can have three kinds of values: 32 $ (i) 'neutral', if the base is neutral. 33 $ (ii) a variable name v, if the base is 34 $ effective, but the only composite object 35 $ (set or map) it supports is v. 36 $ (iii) 'effective', if at least two composite 37 $ objects are effectively supported by 38 $ the base. 39 basedoccs := {}; $ set of all based occurences 40 $ (see section 'scopes of bases' above) 41 42$$$ ???? for efficiency, may want to do a.d.s. for each procedure 43$$$ ???? separately. this will, however, complicate the logic of the 44$$$ ???? following algorithm, and so this possibility is ignored in the 45$$$ ???? current code 46 47 if 'y' in dump_string then 48 print(' - base generation phase'); 49 end if; 50 51 (forall r in routs) 52 (for_block(b, r)) 53 (for_inst(ins, b)) 54 genbases; 55 56$ for each instruction ins generate bases as described in (1) above. 57$ this is done in a manner depending on the opcode of the instruction 58$ ins and on the type of its arguments. for each base generated, we 59$ compute the form of its elements from the types of the arguments of 60$ ins, modify the aux_repr map of the based arguments of ins to show 61$ the appropriate based representations, and classify each generated 62$ as effective or neutral. 63 64 end; $ end for_inst; 65 end; $ end for_block; 66 end forall; 67 68 end procedure basegen; 69 70 1 .=member gnb15b 2 3 4 procedure genbases; 5$ 6$ this routine analyzes an instruction ins, generates initial bases for 7$ ins, and sets up appropriate basings for the occurences in ins. this 8$ is done using a case statement involving the opcode of ins and the 9$ types of its arguments. 10$ 11$ this routine uses an auxiliary routine genabase to generate a new base 12$ (i.e. a new atom) and to update various maps related to all tentative 13$ bases (e.g. the set of all bases, bases, and the set of all based 14$ occurrences, basedoccs). it takes as arguments the element mode of 15$ the base to be generated, plus a tuple of integers indicating which 16$ arguments of the current instruction ins are to be supported by this 17$ base, plus a tuple of integers indicating which arguments are 18$ supported effectively (i.e. composite objects such as sets or maps for 19$ which the introduction of the base would eliminate a hashing 20$ operation). 21$ 22 macro locspr_of(b); set_type(elmt_mode(base_of(b))) endm; 23 macro maptyp_of(b); map_type(elmt_mode(base_of(b))) endm; 24 25 26 repr 27 j: integer 0..65536; 28 v: symbol; 29 ivs: tuple(occurrence); 30 tps: tuple(elmt types); 31 grtps: tuple(gross_type); 32 vo1, vo2, vo3, vo4: occurrence; 33 i: occurrence; 34 typ1, typ2, typ3, typ4: elmt types; 35 g1, g2, g3, g4: gross_type; 36 opc: elmt base_opcodes; 37 jbig, jsml, k, q: integer; 38 comps2, comps3, bcomps2, bcomps3, 39 comps: tuple(elmt types); 40 ind: integer; 41 tp, tpc, tpx: elmt types; 42 voj: occurrence; 43 typj: elmt types; 44 tupj: tuple(integer); 45 ebx, eby, ebz, 46 eb, eb1, eb2, eb3: elmt types; 47 end repr; 48 49 opc := opcode(ins); 50 argsi := args(ins); 51 ivs := [ get_oi(ins, j) : 52 j in [ 1..#argsi ] | get_oi(ins, j) in all_oi ]; smfl 26 TPS := [ TYP(I) ? TYPE_ZERO : I in IVS ]; 54 grtps := [ grosstyp(tp) : tp in tps ]; 55 56 [ vo1, vo2, vo3 ] := ivs; 57 [ typ1, typ2, typ3 ] := tps; 58 [ g1, g2, g3 ] := grtps; 59 100 case opc of 101 102 (q1_with, q1_less): 103 104 case g1 of 105 106 (grsmap): 107 108$ it follows from the logic of the type finder that in this case 109$ both vo1 and vo2 are maps and vo3 is a pair. 110$ in this case we generate two bases for ins; one for the domain 111$ and one for the image of these maps. 112$ i.e. if 'ins' is 'f := g with p' then we interpret it as something 113$ like 'f := g; f{p(1)} with:= p(2);'. this means that ins would 114$ ordinarily be executed using two hashing operations. nevertheless, 115$ only the domain base is assumed to be effective, as we suspect 116$ that the range of these maps will tend to be sparse over its 117$ would-be base. 118 if typ1 = typ2 and typ3 = comptyp(typ1) then 119 120 $ nb. only the first two arguments are effectively 121 $ supported by the domain base ebx. 122 ebx := genabase(domtyp(typ1), tup123, tup12); 123 eby := genabase(rangetyp(typ1), tup123, [] ); 124 137 $ if ov = iv1, then local basing is advantageous, 138 $ and we link the two (neutral) bases to reduce the 139 $ chance that a conversion might be required. 140 $ otherwise, a value transfer between ov and iv1 takes 141 $ place, and local basing would require a full conver- 142 $ sion. hence remote basing is more advantageous. smfl 27 smfl 28 if OPC = Q1_WITH then smfl 29 smfl 30 $ EBZ is the range set type for the multi-valued map smfl 31 EBZ := BASED_SET(EBY, SPRSE); smfl 32 smfl 33 if ARGSI(1) = ARGSI(2) then smfl 34 EB1 := BASED_MAP(EBX, EBZ, LOCL, FT_MMAP); smfl 35 EB2 := EB1; smfl 36 else smfl 37 EB1 := BASED_MAP(EBX, EBZ, REMT, FT_MMAP); smfl 38 EB2 := BASED_MAP(EBX, EBY, REMT, FT_MAP ); smfl 39 end if; smfl 40 smfl 41 else $ OPC = Q1_LESS smfl 42 smfl 43 if ARGSI(1) = ARGSI(2) then smfl 44 EB1 := BASED_MAP(EBX, EBY, LOCL, FT_MAP ); smfl 45 EB2 := EB1; smfl 46 else smfl 47 EB1 := BASED_MAP(EBX, EBY, REMT, FT_MAP ); smfl 48 EB2 := BASED_MAP(EBX, EBY, REMT, FT_MAP ); smfl 49 end if; smfl 50 smfl 51 end if; 155 156 aux_repr(vo1) := eb1; 157 aux_repr(vo2) := eb2; 158 aux_repr(vo3) := based_pair(ebx, eby); 159 end if; 160 161 (grsset): 162 163 if typ1 = typ2 and comptyp(typ1) = typ3 then 164 165 ebx := genabase(typ3, tup123, tup12); 166 167 if argsi(1) = argsi(2) then smfl 52 EB1 := BASED_SET(EBX, LOCL); 171 eb2 := eb1; 173 else smfl 53 EB1 := BASED_SET(EBX, REMT); smfl 54 EB2 := BASED_SET(EBX, REMT); 179 end if; 180 181 aux_repr(vo1) := eb1; 182 aux_repr(vo2) := eb2; 183 aux_repr(vo3) := ebx; 184 end if; 185 186 (grstup): 187 188$ generate a neutral base (several bases for known length tuples) and 189$ make the involved tuples into tuples of elements of this base(s) 190 if is_knt(typ1) then 191 192$ find the index of the larger tuple (jbig) and that of the smaller one 193 jbig := if opc = q1_with then 1 else 2 end; 194 jsml := 3 - jbig; $ 1 for less, 2 for with 195 comps := comptyp(typ(get_oi(ins, jbig))); 196 197 if comps(#comps) = typ3 then 198 199 (forall k in [ 1..#comps-1 ]) 200 comps(k) := genabase(comps(k), tup12, []); 201 end forall; 202 203 comps(#comps) := aux_repr(vo3) := 204 genabase(comps(#comps), [ jbig, 3 ], []); smfl 55 AUX_REPR(GET_OI(INS, JBIG)) := BASED_KNT(COMPS); 207 aux_repr(get_oi(ins, jsml)) := smfl 56 BASED_KNT(COMPS(1..#COMPS-1)); 212 213 end if; 214 215 else $ homogeneous tuple 216 217 if typ1 = typ2 and typ3 = comptyp(typ1) then 218 219 ebx := genabase(typ3, tup123, []); 220 smfl 57 AUX_REPR(VO1) := BASED_TUP(EBX); smfl 58 AUX_REPR(VO2) := BASED_TUP(EBX); 223 aux_repr(vo3) := ebx; 224 225 end if; 226 end if; 227 end case; 228 229 230 (q1_lessb, q1_lesse): $ a1 := a2 less a3; a2 must be a tuple 231 smfl 59 if G2 = GRSTUP then 233 if is_knt(typ2) then 234 comps2 := comptyp(typ2); 235 if forall tpc in comps2 | tpc = typ3 then 236 ebx := genabase(typ3, tup123, []); 237 smfl 60 EB1 := BASED_KNT([ EBX : J in [ 1..#COMPS-1 ] ]); smfl 61 EB2 := BASED_KNT([ EBX : J in [ 1..#COMPS ] ]); 246 247 aux_repr(vo1) := eb1; 248 aux_repr(vo2) := eb2; 249 aux_repr(vo3) := ebx; 250 end if; 251 else 252 ebx := genabase(typ3, tup123, []); smfl 62 EB1 := BASED_TUP(EBX); 254 255 aux_repr(vo1) := eb1; 256 aux_repr(vo2) := eb1; 257 aux_repr(vo3) := ebx; 258 end if; 259 end if; 260 261 262 (q1_lessf): 263 smfl 63 if G1 = GRSMAP and TYP1 = TYP2 and DOMTYP(TYP1) = TYP3 then 266 267 ebx := genabase(domtyp(typ1), tup123, tup12); 268 eby := genabase(rangetyp(typ1), tup12, [] ); 269 270 if argsi(1) = argsi(2) then smfl 64 EB1 := BASED_MAP(EBX, EBY, LOCL, FT_MAP); 275 eb2 := eb1; 277 else smfl 65 EB1 := BASED_MAP(EBX, EBY, REMT, FT_MAP); smfl 66 EB2 := BASED_MAP(EBX, EBY, REMT, FT_MAP); 285 end if; 286 287 aux_repr(vo1) := eb1; 288 aux_repr(vo2) := eb2; 289 aux_repr(vo3) := ebx; 290 end if; 291 292 (q1_of): $ a1 := a2(a3) 293 294$ here a2 is a map or tuple (or char. string, which case we ignore 295$ since it has no consequences for basing) 296 297 case g2 of 298 299 (grsmap): 300 301 if domtyp(typ2) = typ3 and rangetyp(typ2) = typ1 then 302 303 ebx := genabase(domtyp(typ2), tup23, tup2); 304 eby := genabase(rangetyp(typ2), tup12, [] ); 305 smfl 67 EB2 := BASED_MAP(EBX, EBY, LOCL, FT_SMAP); 309 310 aux_repr(vo1) := eby; 311 aux_repr(vo2) := eb2; 312 aux_repr(vo3) := ebx; 313 end if; 314 315 (grstup) : 316 if is_knt(typ2) then 317 318 comps := comptyp(typ2); 319 320 if is_const(arg3(ins))=1 then 321 322 ind := value(arg3(ins)); 323 324 if 1 <= ind and ind <= #comps then 325 326 if typ1 = comps(ind) then 327 328$ here a2 is a known length tuple and a3 is an integer constant 329$ falling within range of a2. we generate one base for each 330$ component of a2, and construct the appropriate basings. the base 331$ for the component being retrieved is used also to base a1. 332 aux_repr(vo1) := comps(ind) := 333 genabase(typ1, tup12, []); 334 335 (forall j in [ 1..#comps ] | j /= ind) 336 comps(j) := 337 genabase(comps(j), tup2, []); 338 end forall; 339 smfl 68 AUX_REPR(VO2) := BASED_KNT(COMPS); 342 343 end if; 344 345 end if; 346 347 else 348 349$ mixed tuple with non-constant index; meaningful basings can 350$ still be generated, provided that all components of a2 have 351$ the same type. 352 if (forall tpc = comps(j) | tpc = typ1) then 353 ebx := genabase(typ1, tup12, []); 354 aux_repr(vo1) := ebx; 355 aux_repr(vo2) := smfl 69 BASED_KNT( [ EBX : Q in [ 1..#COMPS ] ] ); 357 end if; 358 end if; 359 else $ homogeneous tuple 360 361 if typ1 = comptyp(typ2) then 362 363 ebx := genabase(typ1, tup12, []); 364 aux_repr(vo1) := ebx; smfl 70 AUX_REPR(VO2) := BASED_TUP(EBX); 366 367 end if; 368 369 end if; 370 371 end case; 372 373 (q1_sof): $ f(a2) := a3; a4 is f before this operation 374 $ and a1 is f afterwards. 375 376$ here a1,a4 are maps or tuples (or char. strings, which case we ignore 377$ since it has no consequences for basing) 378 379 vo4 := ivs(4); typ4 := tps(4); 380 381 case g1 of 382 383 (grsmap): 384 385 if domtyp(typ1) = typ2 and 386 rangetyp(typ1) = typ3 and 387 typ1 = typ4 then 388 389 ebx := genabase(domtyp(typ1), tup124, tup14); 390 eby := genabase(rangetyp(typ1), tup134, [] ); 391 smfl 71 EB1 := BASED_MAP(EBX, EBY, LOCL, FT_SMAP); 395 396 aux_repr(vo1) := aux_repr(vo4) := eb1; 397 aux_repr(vo2) := ebx; 398 aux_repr(vo3) := eby; 399 400 else 401 $ there is no conversion possible between a1 and a4: 402 $ make sure that this does not happen by linking their 403 $ neutral bases. 404 ebx := genabase(domtyp(typ1), tup14, tup14); 405 eby := genabase(rangetyp(typ1), tup14, [] ); 406 smfl 72 EB1 := BASED_MAP(EBX, EBY, LOCL, FT_SMAP); 410 411 aux_repr(vo1) := eb1; 412 aux_repr(vo4) := eb1; 413 414 end if; 415 416 (grstup): 417 418 if is_knt(typ1) and typ1 = typ4 then 419 420 comps := comptyp(typ1); 421 422 if is_const(arg2(ins))=1 then 423 424 $ mixed tuple with constant index: meaningful 425 $ basings can be generated if the index is in range. 426 427 ind := value(arg2(ins)); 428 429 if 1 <= ind and ind <= #comps and 430 typ3 = comps(ind) then 431 432 (forall j in [ 1..#comps ] | j /= ind) 433 comps(j) := genabase(comps(j), tup14, []); 434 end forall; 435 436 ebx := genabase(typ3, tup134, []); 437 comps(ind) := ebx; 438 smfl 73 EB1 := BASED_KNT(COMPS); 440 441 aux_repr(vo1) := eb1; 442 aux_repr(vo3) := ebx; 443 aux_repr(vo4) := eb1; 444 end if; 445 446 else 447 $ mixed tuple with non-constant index: meaningful 448 $ basings can still be generated, provided that all 449 $ components of a1 have the same type. 450 451 if (forall tpc = comps(j) | tpc = typ3) then 452 ebx := genabase(typ3, tup134, []); smfl 74 EB1 := BASED_KNT([ EBX : Q in [ 1..#COMPS ] ]); 456 aux_repr(vo1) := eb1; 457 aux_repr(vo3) := ebx; 458 aux_repr(vo4) := eb1; 459 end if; 460 end if; 461 462 elseif typ1 = typ4 then $ homogeneous tuple 463 464 if typ3 = comptyp(typ1) then 465 ebx := genabase(typ3, tup134, []); smfl 75 EB1 := BASED_TUP(EBX); 467 468 aux_repr(vo1) := eb1; 469 aux_repr(vo3) := ebx; 470 aux_repr(vo4) := eb1; 471 end if; 472 473 end if; 474 475 end case; 476 477 (q1_ofa): $ a1 := a2{a3}; a2 must be a map 478 smfl 76 if G2 = GRSMAP and 480 domtyp(typ2) = typ3 and 481 rangetyp(typ2) = comptyp(typ1) then 482 483 ebx := genabase(domtyp(typ2), tup23, tup23); 484 eby := genabase(rangetyp(typ2), tup12, [] ); 485 486 $ ebz is the range set type of the multi-valued map. smfl 77 EBZ := BASED_SET(EBY, SPRSE); 489 smfl 78 EB2 := BASED_MAP(EBX, EBZ, LOCL, FT_MMAP); 493 494 aux_repr(vo1) := ebz; 495 aux_repr(vo2) := eb2; 496 aux_repr(vo3) := ebx; 497 end if; 498 499 500 (q1_sofa): $ a1{a2} := a3; a4 is the input a1 501 502 typ4 := tps(4); vo4 := ivs(4); 503 smfl 79 if G1 = GRSMAP then smfd 504 if typ1 = typ4 and domtyp(typ1) = typ2 and smfd 505 rangetyp(typ1) = comptyp(typ3) then smfd 506 smfd 507 ebx := genabase(domtyp(typ1), tup124, tup14); smfd 508 eby := genabase(rangetyp(typ1), tup134, [] ); smfd 509 smfd 510 $ ebz is the range set type of the multi-valued map. smfl 80 EBZ := BASED_SET(EBY, SPRSE); smfd 513 smfl 81 EB1 := BASED_MAP(EBX, EBZ, LOCL, FT_MMAP); smfd 517 smfd 518 aux_repr(vo1) := aux_repr(vo4) := eb1; smfd 519 aux_repr(vo2) := ebx; smfd 520 aux_repr(vo3) := ebz; smfd 521 smfd 522 else smfd 523 $ there is no conversion possible between a1 and a4: smfd 524 $ make sure that this does not happen by linking their smfd 525 $ neutral bases. smfd 526 ebx := genabase(domtyp(typ1), tup14, tup14); smfd 527 eby := genabase(rangetyp(typ1), tup14, [] ); smfd 528 smfl 82 $ EBZ is the range set type of the multi-valued map. smfl 83 EBZ := BASED_SET(EBY, SPRSE); smfl 84 smfl 85 EB1 := BASED_MAP(EBX, EBZ, LOCL, FT_MMAP); smfd 532 smfd 533 aux_repr(vo1) := aux_repr(vo4) := eb1; smfd 534 end if; smfd 535 smfd 536 else smfd 537 $ there is some error here, but no conversion is possible smfd 538 $ between a1 and a4: make sure that this does not happen by smfd 539 $ linking their neutral bases. smfd 540 eb1 := genabase(typ1, tup14, []); smfd 541 aux_repr(vo1) := aux_repr(vo4) := eb1; smfd 542 end if; 537 538 539 (q1_in, q1_notin): $ a1 := (a2 in a3); (or notin) 540 541 case g3 of 542 543 (grsset) : 544 545 if typ2 = comptyp(typ3) then 546 ebx := genabase(typ2, tup23, tup3); 547 smfl 86 EB3 := BASED_SET(EBX, LOCL); 550 551 else 552 ebx := genabase(typ2, tup2, []); 553 554 eb3 := genabase(typ3, tup3, []); 555 locspr_of(eb3) := locl; 556 end if; 557 558 aux_repr(vo2) := ebx; 559 aux_repr(vo3) := eb3; 560 561 (grsmap): 562$ the test 'a2 in a3' is in this case a test of 'pair in map' 563$ which suggests introduction of two bases, an effective base 564$ b and a neutral base b1, and repr a2 as tuple(elmt b, elmt b1) 565$ and a3 as a map (elmt b) elmt b1. 566 if typ2 = comptyp(typ3) then 567 568 ebx := genabase(domtyp(typ3), tup23, tup3); 569 eby := genabase(rangetyp(typ3), tup23, [] ); 570 smfl 87 EB2 := BASED_PAIR(EBX, EBY); smfl 88 EB3 := BASED_MAP(EBX, EBY, LOCL, FT_MAP); 576 577 aux_repr(vo2) := eb2; 578 aux_repr(vo3) := eb3; 579 580 end if; 581 582 end case; 583 584 585 (q1_asn, q1_argin): $ a1 := a2; 586 $ formal parameter := actual parameter; 587 if typ1 = typ2 then $ link the bases 588 eb1 := genabase(typ1, tup12, []); 589 eb2 := eb1; 590 else $ still check for nullset/nullmap assignments 591 eb1 := genabase(typ1, tup1, []); 592 eb2 := om; 593 end if; 594 595 if is_const(v := oi_sym(vo2))=1 and value(v) = {} then 596 $ nullset or nullmap 597 locspr_of(eb1) := if opc=q1_asn then neutrl else sprse end; smfl 89 if G1 = GRSMAP then 599 maptyp_of(eb1) := ft_smap; 600 end if; 601 602 else smfl 90 if G1 = GRSSET then 604 locspr_of(eb1) := sprse; 605 end if; smfl 91 if G1 = GRSMAP then 607 locspr_of(eb1) := sprse; 608 maptyp_of(eb1) := ft_map; 609 end if; 610 end if; 611 612 aux_repr(vo1) := eb1; 613 aux_repr(vo2) := eb2; 614 615 (q1_argout): $ actual parameter := formal parameter 616 617 if typ1 = tps(4) then 618 eb1 := genabase(typ1, tup14, []); 619 aux_repr(vo1) := eb1; 620 aux_repr(ivs(4)) := eb1; 621 end if; 622 623 (q1_add, q1_sub, q1_mod, q1_mult): $ a1 := a2 op a3; 624 625 case g1 of 626 627 (grsset): 628 629 if typ1 = typ2 and typ2 = typ3 then 630 631 ebx := genabase(comptyp(typ1), tup123, tup123); 632 smfl 92 EB1 := BASED_SET(EBX, REMT); smfl 93 EB2 := BASED_SET(EBX, REMT); smfl 94 EB3 := BASED_SET(EBX, REMT); 641 642 aux_repr(vo1) := eb1; 643 aux_repr(vo2) := eb2; 644 aux_repr(vo3) := eb3; 645 end if; 646 647 648 (grsmap): 649 650 if typ1 = typ2 and typ2 = typ3 then 651 652 ebx := genabase(domtyp(typ1), tup123, tup123); 653 eby := genabase(rangetyp(typ1), tup123, [] ); 654 655 $ ebz is the range set type of the multi-valued map smfl 95 EBZ := BASED_SET(EBY, SPRSE); smfl 96 smfl 97 EB1 := BASED_MAP(EBX, EBZ, REMT, FT_MMAP); smfl 98 EB2 := BASED_MAP(EBX, EBY, REMT, FT_MAP ); smfl 99 EB3 := BASED_MAP(EBX, EBY, REMT, FT_MAP ); 670 671 aux_repr(vo1) := eb1; 672 aux_repr(vo2) := eb2; 673 aux_repr(vo3) := eb3; 674 end if; 675 676 677 (grstup): 678 if opc = q1_add then 679 if is_knt(typ2) then 680 comps2 := comptyp(typ2); 681 if is_knt(typ3) then 682 comps3 := comptyp(typ3); 683 bcomps2 := []; 684 bcomps3 := []; 685 (forall tpc = comps2(j)) 686 bcomps2 with:= genabase(tpc, tup12,[]); 687 end forall; 690 (forall tpc = comps3(j)) 691 bcomps3 with:= genabase(tpc, tup13, []); 692 end forall; smfl 100 AUX_REPR(VO1) := BASED_KNT(BCOMPS2 + BCOMPS3); smfl 101 AUX_REPR(VO2) := BASED_KNT(BCOMPS2); smfl 102 AUX_REPR(VO3) := BASED_KNT(BCOMPS3); 698 else 699 if (forall tpc = comps2(j) | 700 tpc = comptyp(typ3)) then 701 eb := genabase(comptyp(typ3), tup123, []); smfl 103 EB1 := BASED_TUP(EB); smfl 104 EB2 := smfl 105 BASED_KNT([ EB : Q in [ 1..#COMPS2 ] ]); smfl 106 smfl 107 AUX_REPR(VO1) := EB1; smfl 108 AUX_REPR(VO2) := EB2; smfl 109 AUX_REPR(VO3) := EB1; 708 end if; 709 end if; 710 else 711 if is_knt(typ3) then 712 comps3 := comptyp(typ3); 713 if (forall tpc = comps3(j) | 714 tpc = comptyp(typ2) ) then 715 eb := genabase(comptyp(typ2),tup123,[]); smfl 110 EB1 := BASED_TUP(EB); smfl 111 EB3 := smfl 112 BASED_KNT([ EB : Q in [ 1..#COMPS3 ] ]); smfl 113 smfl 114 AUX_REPR(VO1) := EB1; smfl 115 AUX_REPR(VO2) := EB1; smfl 116 AUX_REPR(VO3) := EB3; 722 end if; 723 else 724 if (tpc := comptyp(typ2)) = comptyp(typ3) then 725 eb := genabase(tpc, tup123, []); smfl 117 EB1 := BASED_TUP(EB); smfl 118 smfl 119 AUX_REPR(VO1) := EB1; smfl 120 AUX_REPR(VO2) := EB1; smfl 121 AUX_REPR(VO3) := EB1; 729 end if; 730 end if; 731 end if; 732 end if; 733 734 end case; 735 736 (q1_nextd, q1_inextd): 737 738$ iteration over a tuple or a map (or a string, which case we ignore) 739$ this is not a pro-basing instruction in either case, but neutral 740$ bases are introduced here to facilitate later basing propagation 741$ (see the base merging phase for details) 742 743 case g3 of 744 745 (grsmap): 746 747 ebx := genabase(domtyp(typ3), tup13, []); 748 eby := genabase(rangetyp(typ3), tup3, []); 749 smfl 122 EB3 := BASED_MAP(EBX, EBY, SPRSE, FT_MAP); 753 754 aux_repr(vo1) := ebx; 755 aux_repr(vo3) := eb3; 756 757 (grstup): 758 759 if is_knt(typ3) then 760 comps := comptyp(typ3); 761 if (forall tpc = comps(j) | tpc = comps(1)) then 762 ebx := genabase(comps(1), tup3, []); 763 smfl 123 EB3 := BASED_KNT( [ EBX : Q in [ 1..#COMPS ] ] ); 768 769 aux_repr(vo3) := eb3; 770 end if; 771 else 772 ebx := genabase(comptyp(typ3), tup3, []); smfl 124 AUX_REPR(VO3) := BASED_TUP(EBX); 774 end if; 775 776 end case; 777 778 (q1_next, q1_inext): 779 780 case g3 of 781 782 (grsset): 783 784 ebx := genabase(comptyp(typ3), tup13, []); 785 smfl 125 EB3 := BASED_SET(EBX, SPRSE); 788 789 aux_repr(vo1) := ebx; 790 aux_repr(vo3) := eb3; 791 792 (grsmap): 793 794 ebx := genabase(domtyp(typ3), tup13, []); 795 eby := genabase(rangetyp(typ3), tup13, []); 796 smfl 126 EB3 := BASED_MAP(EBX, EBY, SPRSE, FT_MAP); 800 801 aux_repr(vo1) := based_pair(ebx, eby); 802 aux_repr(vo3) := eb3; 803 804 (grstup): 805 806 if is_knt(typ3) then $ probably rather unlikely 807 comps := comptyp(typ3); 808 if (forall tpc = comps(j) | tpc = typ1) then 809 ebx := genabase(typ1, tup13, []); 810 smfl 127 EB3 := BASED_KNT( [ EBX : Q in [ 1..#COMPS ] ] ); 815 816 aux_repr(vo1) := ebx; 817 aux_repr(vo3) := eb3; 818 end if; 819 else 820 ebx := genabase(comptyp(typ3), tup13, []); 821 aux_repr(vo1) := ebx; smfl 128 AUX_REPR(VO3) := BASED_TUP(EBX); 823 end if; 824 825 end case; 826 827 if opc = q1_next and vo1 in basedoccs then 828 basedoccs with:= (vo4 := ivs(4)); 829 aux_repr(vo4) := aux_repr(vo1); 830 end if; 831 832 (q1_set1): $ iterative set former 833 834 case g1 of 835 836 (grsset): 837 838 ebx := genabase(typ2, tup12, tup1); 839 smfl 129 EB1 := BASED_SET(EBX, NEUTRL); 842 843 aux_repr(vo1) := eb1; 844 aux_repr(vo2) := ebx; 845 846 (grsmap): 847 848 ebx := genabase(domtyp(typ1), tup12, tup1); 849 eby := genabase(rangetyp(typ1), tup12, [] ); 850 851 $ ebz is the range set type for the multi-valued map. smfl 130 EBZ := BASED_SET(EBY, SPRSE); 854 smfl 131 EB1 := BASED_MAP(EBX, EBZ, NEUTRL, FT_MMAP); 858 859 aux_repr(vo1) := eb1; 860 aux_repr(vo2) := based_pair(ebx, eby); 861 862 end case; 863 864 865 (q1_tup1): $ iterative tuple former 866 867 ebx := genabase(typ2, tup12, []); smfl 132 AUX_REPR(VO1) := BASED_TUP(EBX); 869 aux_repr(vo2) := ebx; 870 871 872 (q1_dom): $ a1 := domain a2 873 smfl 133 if G2 = GRSMAP then 875 ebx := genabase(domtyp(typ2), tup12, tup1); 876 eby := genabase(rangetyp(typ2), tup12, [] ); 877 smfl 134 EB1 := BASED_SET(EBX, NEUTRL); smfl 135 EB2 := BASED_MAP(EBX, EBY, SPRSE, FT_MAP); 884 885 aux_repr(vo1) := eb1; 886 aux_repr(vo2) := eb2; 887 end if; 888 889 (q1_range): $ a1 := range a2 890 smfl 136 if G2 = GRSMAP then 892 ebx := genabase(domtyp(typ2), tup12, [] ); 893 eby := genabase(rangetyp(typ2), tup12, tup1); 894 smfl 137 EB1 := BASED_SET(EBY, NEUTRL); smfl 138 EB2 := BASED_MAP(EBX, EBY, SPRSE, FT_MAP); 901 902 aux_repr(vo1) := eb1; 903 aux_repr(vo2) := eb2; 904 end if; 905 906 (q1_incs): 907 908 if typ2 = typ3 then 909 case g2 of 910 911 (grsset): 912 913 ebx := genabase(comptyp(typ2), tup23, tup23); 914 smfl 139 EB2 := BASED_SET(EBX, SPRSE); smfl 140 EB3 := BASED_SET(EBX, LOCL); 920 921 aux_repr(vo2) := eb2; 922 aux_repr(vo3) := eb3; 923 924 (grsmap): 925 926 ebx := genabase(domtyp(typ2), tup23, tup23); 927 eby := genabase(rangetyp(typ2), tup23, [] ); 928 smfl 141 EB2 := BASED_MAP(EBX, EBY, SPRSE, FT_MAP); smfl 142 EB3 := BASED_MAP(EBX, EBY, LOCL, FT_MAP); 936 937 aux_repr(vo2) := eb2; 938 aux_repr(vo3) := eb3; 939 940 end case; 941 end if; 942 943 (q1_arb): 944 945 case g2 of 946 947 (grsset): 948 949 ebx := genabase(typ1, tup12, []); 950 smfl 143 EB2 := BASED_SET(EBX, SPRSE); 953 954 aux_repr(vo1) := ebx; 955 aux_repr(vo2) := eb2; 956 957 (grsmap): 958 959 ebx := genabase(domtyp(typ2), tup12, []); 960 eby := genabase(rangetyp(typ2), tup12, []); 961 smfl 144 EB1 := BASED_PAIR(EBX, EBY); smfl 145 EB2 := BASED_MAP(EBX, EBY, SPRSE, FT_MAP); 967 968 aux_repr(vo1) := eb1; 969 aux_repr(vo2) := eb2; 970 971 end case; 972 973 974 (q1_arbb, q1_arbe): $ a1 := arb a2; a2 must be a tuple 975 smfl 146 if G2 = GRSTUP then 977 978 if is_knt(typ2) then 979 980 comps2 := comptyp(typ2); 981 982 if forall tpc in comps2 | tpc = typ1 then 983 ebx := genabase(typ1, tup12, []); 984 smfl 147 EB2 := BASED_KNT( [ EBX : J in [ 1..#COMPS2 ] ] ); 989 990 aux_repr(vo1) := ebx; 991 aux_repr(vo2) := eb2; 992 end if; 993 994 elseif typ1 = comptyp(typ2) then 995 996 ebx := genabase(typ1, tup12, []); 997 aux_repr(vo1) := ebx; smfl 148 AUX_REPR(VO2) := BASED_TUP(EBX); 999 1000 end if; 1001 end if; 1002 1003 1004 (q1_eq, q1_ne): 1005 1006 if typ2 = typ3 then 1007 eb := genabase(typ2, tup23, []); 1008 aux_repr(vo2) := eb; 1009 aux_repr(vo3) := eb; 1010 end if; 1011 1012 1013 (q1_tup): $ enumerative tuple former 1014 smfl 149 if G1 = GRSTUP then 1016 if #ivs = 2 then $ treat as homogeneous tuple 1017 1018 ebx := genabase(typ2, tup12, []); smfl 150 AUX_REPR(VO1) := BASED_TUP(EBX); 1020 aux_repr(vo2) := ebx; 1021 1022 else $ must be known-length tuple 1023 1024 comps := []; 1025 (forall j in [ 2..#ivs ]) 1026 voj := ivs(j); 1027 1028 ebx := genabase(tps(j), [ 1, j ], []); 1029 comps with:= ebx; 1030 aux_repr(voj) := ebx; 1031 end forall; 1032 smfl 151 AUX_REPR(VO1) := BASED_KNT(COMPS); 1034 end if; 1035 end if; 1036 1037 1038 (q1_set): $ enumerative set former 1039 1040 case g1 of 1041 1042 (grsset): 1043 1044 if (forall j in [ 2..#ivs ] | 1045 tps(j) = comptyp(typ1)) then 1046 ebx := genabase(comptyp(typ1), [ 1..#ivs ], []); 1047 smfl 152 EB1 := BASED_SET(EBX, NEUTRL); 1050 1051 aux_repr(vo1) := eb1; 1052 1053 (forall j in [ 2..#ivs ]) 1054 aux_repr(ivs(j)) := ebx; 1055 end forall; 1056 end if; 1057 1058 (grsmap): 1059 if (forall j in [ 2..#ivs ] | 1060 tps(j) = comptyp(typ1)) then 1061 ebx := genabase(domtyp(typ1), [ 1..#ivs ], []); 1062 eby := genabase(rangetyp(typ1), [ 1..#ivs ], []); 1063 1064 if #ivs > 2 then 1065 $ potentially multi-valued result smfl 153 EBZ := BASED_SET(EBY, SPRSE); 1068 smfl 154 EB1 := BASED_MAP(EBX, EBZ, NEUTRL, FT_MMAP); 1072 1073 else smfl 155 EB1 := BASED_MAP(EBX, EBY, NEUTRL, FT_SMAP); 1077 end if; 1078 1079 aux_repr(vo1) := eb1; 1080 smfl 156 EB2 := BASED_PAIR(EBX, EBY); 1082 1083 (forall j in [ 2..#ivs ]) 1084 aux_repr(ivs(j)) := eb2; 1085 end forall; 1086 end if; 1087 1088 end case; 1089 1090 (q1_subst, q1_end, q1_ssubst, q1_send): 1091 [ voj, typj, tupj ] := 1092 if opc = q1_subst or opc = q1_end then 1093 [ vo2, typ2, tup12 ] 1094 elseif opc = q1_send then 1095 [ vo3, typ3, tup13 ] 1096 else 1097 [ ivs(4), tps(4), tup14 ] 1098 end; 1099 1100 if grosstyp(typj) = grstup then smfl 157 if IS_KNT(TYPJ) then smfl 158 if IS_KNT(TYP1) then smfl 159 pass; $ ABORT('Missing code: genbases.subst.'); smfl 160 else smfl 161 COMPS := COMPTYP(TYPJ); smfl 162 if forall TPC = COMPS(J) | TPC = COMPTYP(TYP1) then smfl 163 EBX := GENABASE(COMPTYP(TYP1), TUPJ, []); smfl 164 smfl 165 EB1 := BASED_TUP(EBX); smfl 166 EB2 := BASED_KNT( [ EBX : Q in [ 1..#COMPS ] ]); smfl 167 smfl 168 AUX_REPR(VO1) := EB1; smfl 169 AUX_REPR(VOJ) := EB2; smfl 170 end if; smfl 171 end if; 1112 else 1113 ebx := genabase(comptyp(typj), tupj, []); 1114 smfl 172 EB1 := BASED_TUP(EBX); smfl 173 smfl 174 AUX_REPR(VO1) := EB1; smfl 175 AUX_REPR(VOJ) := EB1; 1117 end if; 1118 end if; 1119 1120 1121 (q1_case): $ t := a1(a2); if t /= om then go to t; 1122 smfl 176 if G1 = GRSMAP and DOMTYP(TYP1) = TYP2 then 1124 ebx := genabase(typ2, tup12, tup1); 1125 eby := genabase(type_gen, tup1, [] ); 1126 smfl 177 EB1 := BASED_MAP(EBX, EBY, REMT, FT_SMAP); 1130 1131 aux_repr(vo1) := eb1; 1132 aux_repr(vo2) := ebx; 1133 end if; 1134 1135 1136 end case; 1137 1138$ if no bases have been introduced, we assign a neutral base 1139$ for each occurrence in the instruction. 1140 1141 (forall voj = ivs(j) | 1142 is_ivar(voj) and 1143 voj notin basedoccs and is_const(argsi(j)) = om ) 1144 1145 eb := genabase((typj := tps(j)), [ j ], []); 1146 1147 if t_set in grosstyp(typj) then 1148 locspr_of(eb) := neutrl; 1149 end if; 1150 1151 if t_map in grosstyp(typj) then 1152 locspr_of(eb) := neutrl; 1153 maptyp_of(eb) := ft_map; 1154 end if; 1155 1156 aux_repr(voj) := eb; 1157 end forall; 1158 1159 end procedure genbases; 1160 1161 1 .=member gab15c 2 3 4 procedure genabase(tp, boccs, effoccs); 5$ 6$ this routine generates a temporary base (= blank atom), with 7$ 'tp' as its element-mode. 'boccs' is a tuple of indices of 8$ arguments of the current instruction which are to be based on 9$ this base, and 'effoccs' is a tuple of indices of arguments 10$ which are to be effectively based on this base (i.e. composite 11$ objects for which this base eliminates a hash operation). 12$ 13 repr 14 tp: elmt types; 15 boccs, effoccs: tuple(integer); 16 17 b: tent_base; 18 effvars: sparse set(symbol); 19 j: integer; 20 end repr; 21 22 b := newat; 23 bases with:= b; 24 elmt_mode(b) := tp; 25 26$ note that at this phase all base element modes are unbased (so 27$ that the fifth component of 'tp' (= om) need not be changed. 28 29$ note additional based occurrences 30 basedoccs +:= { get_oi(ins, j) : j in boccs}; 31$ note that the current instruction, here called 'ins', is passed 32$ globally. 33 34 bscope(b) := maxscope([scope(argsi(j)) : j in boccs]); 35 $ (see explanatory section on 'scopes of bases' at the start of 36 $ this module for more details.) 37 38 effvars := {argsi(j) : j in effoccs}; 39 is_effective(b) := 40 if effvars = {} then 'neutral' 41 elseif #effvars = 1 then arb effvars $ see preceeding comment 42 else 'effective' end; $ on the desired map value in this case 43 44 return [ grselmt, b, om, based ]; 45 46 end procedure genabase; 47 48 49 50 51 procedure maxscope(tup_scps); 52$ 53$ this routine computes the maximal scope in a tuple 'tup_scps' 54$ of scopes which are contained within each other. 55$ the following types of scopes can appear together: 56$ 1. 'sc_proc', 'sc_mod', 'sc_dir' 57$ 2. 'sc_proc', 'sc_prog', 'sc_dir' 58$ 3. 'sc_proc', 'sc_lib' 59$ 60$ see comment 'scopes of bases' above. 61$ 62 repr 63 tup_scps: tuple(elmt base_scopes); 64 65 scp: elmt base_scopes; 66 maxscp: elmt base_scopes; 67 end repr; 68 69$ maxscp is the largest containing scope found so far 70 maxscp := om; 71 72 (forall scp in tup_scps | scp /= om) 73 if maxscp = om then maxscp := scp; end if; 74 if maxscp = scp then continue forall; end if; 75 76 case sc_type(scp) of 77 78 (sc_sys, sc_lib): return sym_sys; 79 80 (sc_dir, sc_prog, sc_mod): 81 case sc_type(maxscp) of 82 (sc_sys, sc_lib): return sym_sys; 83 (sc_dir, sc_prog, sc_mod): maxscp := sym_dir; 84 (sc_proc): 85 $ account for the fact that the main program is part of 86 $ the system scope 87 if maxscp = sym_main then 88 maxscp := maxscope( [ sym_prog, scp ] ); 89 else 90 maxscp := maxscope( [ scope(maxscp), scp ] ); 91 end if; 92 end case; 93 94 (sc_proc): 95 $ account for the fact that the main program is part of the 96 $ system scope 97 if scp = sym_main then 98 maxscp := maxscope( [ sym_prog, maxscp ] ); 99 else 100 maxscp := maxscope( [ scope(scp), maxscp ] ); 101 end if; 102 103 end case; 104 end forall; 105 106 return maxscp; 107 108 end procedure maxscope; 109 110 1 .=member bsm15d 2 3 smfd 543 smfd 544$ 2. base merging and adjustment smfd 545$ -- ---- ------- --- ---------- smfd 546 smfd 547$ in this phase we merge based reprs of all pairs of occurrences linked smfd 548$ by a bfrom link. this merging operation proceeds approximately as smfd 549$ follows: smfd 550$ smfd 551$ let vo and vo1 be two occurrences which have the same type and which smfd 552$ are linked by bfrom. it follows from the mechanism of the base smfd 553$ generation pre-pass that any repr rpr generated at this phase can only smfd 554$ have one of the following three forms: smfd 555$ smfd 556$ (i) rpr can be unbased. smfd 557$ smfd 558$ (ii) rpr can have the form 'elmt b'. smfd 559$ smfd 560$ (iii) rpr can have the form smfd 561$ composite(elmt b) smfd 562$ (for sets and homogeneous tuples), or the form smfd 563$ composite(elmt b1, elmt b2, ..., elmt bn) smfd 564$ (for maps (then n = 2) and mixed tuples). smfd 565$ smfd 566$ these initial reprs will not be modified during the base merging and smfd 567$ adjustment phase. consequently, only the following cases can arise smfd 568$ when merging the reprs of vo and vo1: smfd 569$ smfd 570$ 1. if one of these reprs is unbased, do nothing. smfd 571$ smfd 572$ 2. if both reprs are of the second category, the merge simply smfd 573$ equivalences the corresponding bases. this equivalencing action smfd 574$ calls for the merging of the element-modes of the bases, thus repr smfd 575$ merging is a recursive (or transitive) process. for more details, smfd 576$ see below. smfd 577$ smfd 578$ 3. if both reprs are of the third category, then their structures smfd 579$ must be identical, but may possibly involve different bases. in smfd 580$ this case the merge equivalences all pairs of corresponding bases smfd 581$ in these reprs, as is done in (2)). smfd 582$ smfd 583$ 4. if one repr is elmt b, and the other is of the third category, smfd 584$ then if elmt_mode(b) is unbased we replace elmt_mode(b) by the smfd 585$ second repr, and if elmt_mode(b) is based, we merge it with the smfd 586$ second repr (this is another source of transitive closure in the smfd 587$ merging process). smfd 588$ smfd 589$ we use the following implementation: the set 'bases' of all generated smfd 590$ bases is represented as a forest in which each equivalence class of smfd 591$ bases is a tree whose root is the representing base for that class. smfd 592$ we can then use a highly-efficient compressed balanced tree smfd 593$ representation to manipulate this forest. for this purpose let smfd 594$ 'repbase' denote the father mapping in this forest, and let smfd 595$ repbase .lim b denote the root mapping (see ). in addition to smfd 596$ repbase we also maintain an auxiliary map 'nbases' which maps each smfd 597$ root of the forest to its number of descendents, and the map smfd 598$ 'elmt_mode', which has to be kept only for the roots of the forest. smfd 599$ since elmt_mode(b) has to be kept iff repbase(b) is undefined, these smfd 600$ two maps can be merged into one, to obtain a compact data-structure. smfd 601$ smfd 602$ for the suppression of useless bases we also need to maintain an smfd 603$ 'is_effective' map at the roots of the forest, combining its values at smfd 604$ the roots of two trees which are to be merged in order to determine smfd 605$ the effectivity of the merged tree. this map can only have the three smfd 606$ kinds of values outlined above, and according to our heuristic smfd 607$ principle (4), all classes for which is_effective of their root is not smfd 608$ true will be suppressed in a subsequent phase of our algorithm. smfd 609$ smfd 610$ essentially, only two operations need to be performed on this forest: smfd 611$ smfd 612$ 1. root determination: to compute the root of a given base, we apply smfd 613$ the lim operator. this is explained in , except that here smfd 614$ the 'virtual forest' mentioned in can be identical with smfd 615$ the actual one, so that path compression can be applied directly smfd 616$ to repbase. note, however, that or treatment of map types smfd 617$ requires that whenever we merge two map types where one designates smfd 618$ a multi-valued map and the other does not, the root of the new smfd 619$ tree must be the multi-valued map type. this is due to the fact smfd 620$ that a multi-valued map type carries information about the range smfd 621$ set type (which also contains information about the range element smfd 622$ type), while the other map types contain information only about smfd 623$ the range element type. smfd 624$ smfd 625$ 2. base equivalencing: this is accomplished by a balanced linking of smfd 626$ two trees into one, but with the following additional operations: smfd 627$ (a) the set types of elmt_mode's are merged, according to the smfd 628$ heuristic outlined below; (b) the map types of map reprs are smfd 629$ merged; and (c) the elmt_mode's of the roots of the linked trees smfd 630$ are merged. to perform this latter operation we may have to smfd 631$ update the elmt_mode of the new root. this is required when smfd 632$ elmt_mode(root of larger tree) is unbased, and elmt_mode(root of smfd 633$ smaller tree) is based. in this case we replace the unbased mode smfd 634$ by the based one. note that this can induce additional base smfd 635$ equivalences. smfd 636$ smfd 637$ smfd 638$ user-declared basings are partly reflected in the typ map, available smfd 639$ at the start of the automatic data structure selection algorithm. smfd 640$ however, they raise several problems concerning which the current smfd 641$ implementation has made somewhat arbitrary decisions. for example, it smfd 642$ is not clear whether we ever want to merge two user-supplied bases, or smfd 643$ always keep them distinct. an argument for not merging them is that smfd 644$ by doing so we may cause some based objects to become sparse over the smfd 645$ merged base, which may well have been the reason why the user supplied smfd 646$ two distinct bases instead of one. thus we have chosen not to merge smfd 647$ user-supplied bases, though in other cases it might be better to do smfd 648$ so. smfd 649$ smfd 650$ to avoid such merging, we maintain a map at the roots of our forest, smfd 651$ called 'userbase', indicating which user-supplied base, if any, is a smfd 652$ member of the corresponding class. in this way, we can avoid linking smfd 653$ two classes together if they contain different user-supplied bases. smfd 654$ smfd 655$ note that currently the userbase map is not initialised during the smfd 656$ base generation pre-pass. this calls for some modifications of the smfd 657$ pre-pass. consequently the comment just made can be ignored. note smfd 658$ that the equibase routine given below will not equivalence bases for smfd 659$ which different user bases have been declared. smfd 660$ smfd 661$ let us emphasise again that our algorithm merges basings only when it smfd 662$ encounters occurrences of the same type. this is a restriction which smfd 663$ simplifies the logic of the algorithm, avoiding several troublesome smfd 664$ issues that would arise otherwise. see also remark (1) at the end of smfd 665$ the present module. smfd 666$ smfd 667$ however, we may want to consider some relaxations, e.g. tuple vs. smfd 668$ known-length tuple. smfd 669$ 127 procedure basemerge; 128 129 130 repr 131 vo, vo1: occurrence; 132 rpr, rpr1, rpr2: elmt types; 133 grstyp1, grstyp2: basic_type; 134 i: integer; 135 mptp1, mptp2: elmt base_ft_mapcs; 136 temp1, temp2: elmt types; 137 b, rb1, rb2: tent_base; 138 sc: elmt base_scopes; 139 end repr; 140 141 142$ initially, 143 144 repbase := {}; 145 nbases := { [ b, 1 ] : b in bases}; 146 147 if 'y' in dump_string then 148 print(' - base merging phase'); 149 end if; 150 if 'x' in dump_string then 151 prints('aux_repr =', 152 [ [ rpad(oi_name(vo), 20) + rpad(oi_str(vo), 10), rpr ] : 153 rpr = aux_repr(vo) ] ); 154 prints('elmt_mode =', [ [ str b, rpr ] : rpr = elmt_mode(b) ] ); 155 prints('bscope', [ [ str b, sc ] : sc = bscope(b) ] ); 156 end if; 157 158 (forall vo in basedoccs, vo1 in bfrom{vo} | 159 vo1 in basedoccs and typ(vo) = typ(vo1) ) 160 161$ let us emphasize again that our algorithm merges basings only when it 162$ encounters occurences of the same type. this is a restriction which 163$ simplifies the logic of the algorithm, avoiding several troublesome 164$ issues that would arise otherwise. see also remark (1) at the end of 165$ the present module. 166$$$ ???? may want however to consider some relaxations, e.g. tuple 167$$$ ???? vs. known-length tuple 168$ 169 workpile := { [ aux_repr(vo), aux_repr(vo1) ] }; 170 171 (while workpile /= {}) 172 [ rpr1, rpr2 ] from workpile; 173 if 'x' in dump_string then 174 print; 175 print('merge representation', rpr1); 176 print(' with', rpr2); 177 end if; 178 179 if not is_based(rpr1) then continue; end if; 180 if not is_based(rpr2) then continue; end if; 181 182$ otherwise both reprs are based, so that their conjunction may yield 183$ additional merging actions. 184 185 grstyp1 := arb grosstyp(rpr1); 186 grstyp2 := arb grosstyp(rpr2); 187$ note that based occurrences will have only one basic type in their 188$ 'grosstyp' field. this is because the base generation prepass would 189$ not have generated bases for occurrences having ambiguous types. 190 191 if grstyp1 = grstyp2 then 192 193 if grstyp1 = 'elmt' then 194 195$ both reprs are element-of-base. equivalence their bases, which are 196$ the component-types of these reprs. this equivalencing may trigger 197$ the merging of the element-modes of these bases. 199 200 equibase(base_of(rpr1), base_of(rpr2)); 201 202 elseif grstyp1 = 'map' then 203 204$ if a map is repred as a based map, its domain type and range type are 205$ both element-of-base types, and we have to equivalence these bases. 207 208 equibase(base_of(domtyp(rpr1)), 209 base_of(domtyp(rpr2))); 210 211 mptp1 := map_type(rpr1); 212 mptp2 := map_type(rpr2); 213 214 if mptp1 = ft_mmap and mptp2 /= ft_mmap then 215 rb1 := .lim base_of(rangetyp(rpr1)); 216 temp1 := comptyp(elmt_mode(rb1)); 217 $ nb. it follows from the logic of the pre-pass 218 $ that the element mode of rb1 is based. 219 220 equibase(base_of(temp1), 221 base_of(rangetyp(rpr2)) ); 222 223 $ nb. it follows from the logic of the 224 $ pre-pass and the equibase routine that rpr2 225 $ is updated correctly. 226 $ rangetyp(rpr2) := 227 $ [ { t_elmt }, rb1, om, based ]; 228 $ map_type(rpr2) := ft_mmap; 229 230 elseif mptp1 /= ft_mmap and mptp2 = ft_mmap then 231 rb2 := .lim base_of(rangetyp(rpr2)); 232 temp2 := comptyp(elmt_mode(rb2)); 233 $ nb. it follows from the logic of the pre-pass 234 $ that the element mode of rb2 is based. 235 236 equibase(base_of(rangetyp(rpr1)), 237 base_of(temp2) ); 238 239 $ nb. it follows from the logic of the 240 $ pre-pass and the equibase routine that rpr1 241 $ is updated correctly. 242 $ rangetyp(rpr1) := 243 $ [ { t_elmt }, rb2, om, based ]; 244 $ map_type(rpr1) := ft_mmap; 245 246 else 247 equibase(base_of(rangetyp(rpr1)), 248 base_of(rangetyp(rpr2)) ); 249 end if; 250 251 elseif grstyp1 = t_tuple and is_knt(rpr1) then 252 $ equivalence all the components of these 253 $ mixed tuples 254 (forall i in [ 1..#comptyp(rpr1) ]) 255 equibase(base_of(ctypn(rpr1, i)), 256 base_of(ctypn(rpr2, i))); 257 end forall; 258 259 else 260 $ for sets and homogeneous tuples, only one 261 $ base equivalencing need be performed 262 263 temp1 := comptyp(rpr1); 264 temp2 := comptyp(rpr2); 265 equibase(base_of(temp1), base_of(temp2)); 266 end if; 267 268 elseif grstyp1 = t_elmt then 269$ here we merge 'elmt b' with a composite repr. this calls for merging 270$ the element-mode of b with the other repr. however, if as yet the 271$ element mode of b is not based, we simply change it to the second 272$ (necessarily based) repr. 273 274 rb1 := .lim base_of(rpr1); 275 276 if not is_based(elmt_mode(rb1)) then 277$$-- here bscope is not updated correctly: if the scope of rpr1 is 278$$-- greater than the scope of rpr2, then this information is lost. 279$$-- for as long as we allocate all bases in the system scope, this 280$$-- does not matter. 281 elmt_mode(rb1) := rpr2; 282 else 283 workpile with:= [ elmt_mode(rb1), rpr2 ]; 284 end if; 285 286 elseif grstyp2 = t_elmt then 287 288 rb2 := .lim base_of(rpr2); 289 290 if not is_based(elmt_mode(rb2)) then 291$$-- here bscope is not updated correctly: if the scope of rpr2 is 292$$-- greater than the scope of rpr1, then this information is lost. 293$$-- for as long as we allocate all bases in the system scope, this 294$$-- does not matter. 295 elmt_mode(rb2) := rpr1; 296 else 297 workpile with:= [ elmt_mode(rb2), rpr1 ]; 298 end if; 299 end if; 300 301 end while; 302 end forall; 303 304 typ := om; $ free for garbage collection 305 306 end procedure basemerge; 307 308 1 .=member eqb15e 2 3 4 procedure equibase(b1, b2); 5$ 6$ this is our base equivalencing routine. most of its code performs 7$ standard tree-balancing, but "elmt_mode", "is_effective", "bscope", 8$ and "userbase" also need to be adjusted appropiately. 9$ 10$ the 'workpile' variable is global. 11$ 12 repr 13 b1, b2: tent_base; 14 15 rb1, rb2, root, desc: tent_base; 16 ubase: symbol; 17 eff, eff1, eff2: general; 18 lcsp, lcsp1, lcsp2: elmt base_based_modes; 19 mptp, mptp1, mptp2: elmt base_ft_mapcs; 20 bscp: elmt base_scopes; 21 end repr; 22 23 rb1 := .lim b1; 24 rb2 := .lim b2; 25 26 if rb1 = rb2 then return; end if; 27 28$ 29$ first determine the user-declared base of the equivalence class, 30$ if there exists such a base. 31$ 32 if (ubase := userbase(rb1)) = om then 33 ubase := userbase(rb2); 34 elseif userbase(rb2) /= om and userbase(rb2) /= ubase then 35 return; $ do not merge classes with different user bases 36 end if; 37$ 38$ next compute the effectivity of the new class 39$ 40 eff1 := is_effective(rb1); 41 eff2 := is_effective(rb2); 42 43 eff := case eff1 of 44 ('neutral'): eff2, 45 ('effective'): 'effective' 46 else $ eff1 is now a variable name 47 case eff2 of 48 ('neutral'): eff1, 49 ('effective'): 'effective' 50 else $ both are variable names 51 if eff1 = eff2 then eff1 else 'effective' end 52 end 53 end; 54$ 55$ next compute the maptyp and locspr entries for the new class 56$ 57 lcsp1 := set_type(elmt_mode(rb1)); 58 lcsp2 := set_type(elmt_mode(rb2)); 59 60 if lcsp1 = lcsp2 then 61 lcsp := lcsp1; 62 else 63 if lcsp1 = om then lcsp1 := sprse; end; 64 if lcsp2 = om then lcsp2 := sprse; end; 65 lcsp := if lcsp1 = neutrl then lcsp2 66 elseif lcsp2 = neutrl then lcsp1 67 elseif lcsp1 = lcsp2 then sprse 68 else remt end; 69 end if; 70 71 mptp1 := map_type(elmt_mode(rb1)); 72 mptp2 := map_type(elmt_mode(rb2)); 73 74 if mptp1 /= mptp2 and mptp1 /= ft_mmap and mptp2 /= ft_mmap then 75 mptp := if mptp1 = ft_smap then ft_smap 76 elseif mptp2 = ft_smap then ft_smap 77 else ft_map 78 end; 79 else 80 mptp := om; 81 end if; 82 83 bscp := maxscope([bscope(rb1), bscope(rb2)]); 84$ 85$ determine the new root, balancing the resulting tree whenever 86$ possible 87$ 88 if mptp1 = ft_mmap and mptp2 /= ft_mmap then 89 root := rb1; desc := rb2; 90 91 elseif mptp1 /= ft_mmap and mptp2 = ft_mmap then 92 root := rb2; desc := rb1; 93 94 elseif nbases(rb1) > nbases(rb2) then 95 root := rb1; desc := rb2; 96 else 97 root := rb2; desc := rb1; 98 end if; 99 100 repbase(desc) := root; 101 102 nbases(root) +:= nbases(desc); 103 is_effective(root) := eff; 104 userbase(root) := ubase; 105 bscope(root) := bscp; 106 107 set_type(elmt_mode(root)) := lcsp; 108 set_type(elmt_mode(desc)) := lcsp; 109 110 if mptp /= om then 111 map_type(elmt_mode(root)) := mptp; 112 map_type(elmt_mode(desc)) := mptp; 113 end if; 114 115 if is_based(elmt_mode(desc)) then 116 if not is_based(elmt_mode(root)) then 117$$-- here bscope is not updated correctly: if the scope of root is 118$$-- greater than the scope of desc, then this information is lost. 119$$-- for as long as we allocate all bases in the system scope, this 120$$-- does not matter. 121 elmt_mode(root) := elmt_mode(desc); 122 else 123 $ both are based, so additional equivalencing is needed 124 workpile with:= [ elmt_mode(root), elmt_mode(desc) ]; 125 end if; 126 end if; 127 128 if 'x' in dump_string then smfl 178 print(' New root', ROOT, smfl 179 'with element mode', ELMT_MODE(ROOT)); smfl 180 print(' has', NBASES(ROOT), 'descendents' smfl 181 ' and effectiveness', EFF); 135 end if; 136 137 138 end procedure equibase; 139 140 1 .=member rpb15f 2 3 4 op .lim(b); 5$ 6$ this routine computes the root of b in our forest, and applies 7$ "path compression". 8$ 9 repr 10 b: tent_base; 11 12 rb1, rb2, rbx: tent_base; 13 s1: remote set(tent_base); 14 end repr; 15 16 rb1 := repbase(b); 17 18 if rb1 = om then 19 return b; 20 21 elseif (rb2 := repbase(rb1)) = om then 22 return rb1; 23 24 else 25 s1 := {b}; 26 27 (while (rbx := repbase(rb2)) /= om) 28 s1 with:= rb1; 29 rb1 := rb2; 30 rb2 := rbx; 31 end while; 32 33 (forall rb1 in s1) 34 repbase(rb1) := rb2; 35 end forall; 36 37 return rb2; 38 end if; 39 40 end op .lim; 41 42 43 1 .=member baj15g 2 3 smfd 670$ 3. base and repr adjustment phase smfd 671$ -- ---- --- ---- ---------- ----- smfd 672 smfd 673$ this phase is a 'clean-up' phase which suppresses useless bases, and smfd 674$ computes the oi_repr map for all occurrences. thus it consists of the smfd 675$ following two subphases: smfd 676$ smfd 677$ (a) we first suppress (equivalence classes of) bases that have not smfd 678$ turned out to be useful, according to the heuristic principle (4) smfd 679$ above. each droppable equivalence class is flagged as such, and any smfd 680$ other repr containing a base b1 in such a class should be modified so smfd 681$ that each elmt b1 appearance in it is replaced by smfd 682$ elmt_mode(repbase .lim b1). the output of this phase is a set smfd 683$ droppables containing all droppable bases. smfd 684$ smfd 685$ (b) next, we iterate over all occurrences, computing the oi_repr map. smfd 686$ for each occurrence vo, oi_repr(vo) is an actual repr which is smfd 687$ obtained from aux_repr(vo) by replacing bases by their smfd 688$ representatives, or dropping them as described in (a) above. during smfd 689$ this step we also enforce certain compile restriction, such as that smfd 690$ the result of a value creating operation is a value; if such a value smfd 691$ is wanted in a based form, it must be converted to such a smfd 692$ representation explicitly. again note that at this point we compute smfd 693$ repr on an occurrence bases, so if we have 's := s1 + s2' and require smfd 694$ s to have the form elmt b, then the above restriction would give s the smfd 695$ repr elmt_mode(b), and the subsequent conversion analysis phase would smfd 696$ insert a locate instruction at some appropriate point to ensure that s smfd 697$ would be in the elmt b format. smfd 698$ smfd 699$ (c) finally we dertermine the set of surviving representative bases, smfd 700$ which is passed to the next phase, the conversion analsysis phase. 6 7 procedure baseadjust; 8 9$ this phase is a "clean-up" phase which suppresses useless bases, 10$ computes the oi_repr map for all occurences, and enters the 11$ surviving bases into the symbol table. thus it consists of 12$ the following three subphases: 13 22 repr 23 rb: tent_base; 24 rpr: elmt types; 25 vo: occurrence; 26 opc: elmt base_opcodes; 27 bmode: elmt types; 28 repbases: remote set(tent_base); 29 vorpr: elmt types; 30 end repr; 31 32 if 'y' in dump_string then 33 print(' - base adjustment phase'); 34 end if; 35 36 repbases := { rb in bases | repbase(rb) = om }; 37 droppables := 38 { rb in repbases | is_effective(rb) /= 'effective'}; 39 47 oi_repr := {}; 48 seendrops := {}; $ 'seendrops' is the set of all droppable bases 49 $ b for which the real elmt_mode(b) has already 50 $ been computed. 51 52 (forall rpr = aux_repr(vo)) 53 if vo in basedoccs then 54 rpr := real_repr(rpr); 55 if grosstyp(rpr) = grselmt then smfd 701 opc := oi_op(vo); 57 if (is_ovar(vo) and 58 opc in ops_ovar and opc notin ops_iter and 59 (opc = q1_ofa or opc notin ops_nonewval)) 60 or 61 $ nb. opc in ops_sin and argno(vo) = 1 is part 62 $ of preceding "is_ovar(vo) and ..." test 63 (opc in ops_sin and argno(vo) = 4) 64 or 65 (opc in ops_iter and argno(vo) = 3) 66$$-- nb. we also need to check that if opc in { q1_nextd, q1_inextd } 67$$-- and argno(vo) = 3 and grosstyp(rpr) = grsset then error: conversion 68$$-- will be attempted at q1_inextd, a3 will be changed 69 then 70 rpr := real_repr(elmt_mode(base_of(rpr))); 71 end if; 72 end if; 73 end if; 74 oi_repr(vo) := rpr; 75 end forall; 76 77 if 'y' in dump_string then 78 prints('oi_repr =', 79 [ [ rpad(oi_name(vo), 20) + rpad(oi_str(vo), 10), vorpr ] : 80 vorpr = oi_repr(vo) ] ); 81 end if; 82 83 aux_repr := om; $ free space for garbage collection 84 92 actual_bases := repbases - droppables; 93$$$ ???? for garbage collection, it may be advantageous to 94$$$ ???? delete all map entries on all bases other than those 95$$$ ???? in actual_bases. 96 if 'y' in dump_string then 97 print; 98 print('actual bases and their element forms are'); 99 end if; 100 101 (forall rb in actual_bases) 102 if is_based(elmt_mode(rb)) then 103 elmt_mode(rb) := real_repr(elmt_mode(rb)); 104 end if; 105 if 'y' in dump_string then 106 print(rb, elmt_mode(rb)); 107 end if; 108 end forall; 109 110 if 'd' in dump_string then fancy_output; end if; 111 112 end procedure baseadjust; 113 114 1 .=member fpr15h 2 3 4 procedure fancy_output; 5$ 6$ this routine prints the repr information collected in this phase in a 7$ format resembling that of the data-representation sublanguage, only 8$ that a variable may be assigned several different reprs in different 9$ occurrences of it. we print each repr of each variable togoether with 10$ the list of all occurrences of that variable having that repr. 11$ 12 repr 13 v: symbol; 14 vo: occurrence; 15 rpr: elmt types; 16 b: tent_base; 17 vreprs: sparse mmap{symbol} 18 sparse mmap{elmt types} 19 sparse set(occurrence); 20 r_occs: sparse mmap{elmt types} 21 sparse set(occurrence); 22 v_occs: sparse set(occurrence); 23 end repr; 24 25 26 vreprs := {}; 27 (forall rpr = oi_repr(vo) | 28 (v := oi_sym(vo)) in variables and is_internal(v) = om) 29 vreprs{v}{rpr} with:= vo; 30 end forall; 31 32 print; print; 33 print('suggested data structures:'); 34 print; print; 35 36 (forall b in actual_bases) 37 print(rpad('base ads' + str b + ':', 24), 38 format_repr(elmt_mode(b)) + ';' ); 39 end forall; 40 41 print; 42 43 (forall r_occs = vreprs{v}, v_occs = r_occs{rpr} | 44 ( exists vo in v_occs | ffrom{vo} /= {}) ) 45 print(rpad(name(v) + ': ', 24), format_repr(rpr) + ';' ); 46 print(' $ at occurences ' 47 +/[ ' ' + oi_str(vo) : vo in v_occs ] ); 48 end forall; 49 50 51 end procedure fancy_output; 52 53 1 .=member rrp15i 2 3 4 procedure real_repr(rpr); 5$ 6$ this routine transforms a based repr rpr into a new repr in the 7$ following way: 8$ each appearance of a droppable base b in this repr is replaced 9$ (recursively) by its element mode; 10$ each appearance of an effective base b in this repr is replaced 11$ by the actual base representing b. 12$ 13 repr 14 rpr: elmt types; 15 grs: basic_type; 16 temp5: tuple(elmt types); 17 rb: tent_base; 18 rprx: elmt types; 19 i: integer; 20 end repr; 21 22 23 $ it follows from the logic of the base generation pass that 24 $ ambiguous types can not have based reprs. consequently we 25 $ take an immediate exit. 26 if # grosstyp(rpr) /= 1 then return rpr; end if; 27 28 grs := arb grosstyp(rpr); 29 30 case grs of 31 32 (t_elmt): 33$ get representing base and check whether it is droppable 34 rb := .lim base_of(rpr); 35 if rb in droppables then 36$ if droppable, but already processed, return its element mode 37$ (in which all required replacements have already taken place) 38 if rb in seendrops then 39 return elmt_mode(rb); 40 else 41$ otherwise note this base as processed, get its element mode 42$ and transform it recursively if based. 43 seendrops with:= rb; 44 rprx := elmt_mode(rb); 45 if not is_based(rprx) then 46 return elmt_mode(rb) := rprx; 47 else 48 return elmt_mode(rb) := real_repr(rprx); 49 end if; 50 end if; 51 else 52$ if not droppable, return 'elmt of representing base' 53 return [ grselmt, rb ]; 54 end if; 55 56 (t_tuple): 57$ return a tuple repr, with transformed component reprs 58 if is_knt(rpr) then 59 temp5:=comptyp(rpr); 60 return 61 [ grstup, 62 [ real_repr(temp5(i)) : i in [ 1..#temp5 ] ], 63 true ]; 64 else 65 return [ grstup, real_repr(comptyp(rpr)), false ]; 66 end if; 67 68 (t_map): 69$ return a map repr, with transformed element repr 70 return 71 [ grsmap, 72 [ grstup, 73 [ real_repr(domtyp(rpr)), real_repr(rangetyp(rpr)) ], 74 true ], 75 om, 76 om, 77 set_type(rpr), 78 map_type(rpr) ]; 79 80 (t_set): 81$ return a set repr, with transformed element repr 82 return 83 [ grsset, 84 real_repr(comptyp(rpr)), 85 om, 86 om, 87 set_type(rpr) ]; 88 89 else 90 return rpr; 91 92 end case; 93 94 end procedure real_repr; 95 96 97 smfd 702 smfd 703$ remarks smfd 704$ ------- smfd 705 smfd 706$ (1) the transitive closure of base equivalences carried out during the smfd 707$ merging procedure always relates 'more composite' bases to 'more smfd 708$ primitive' ones. equivalencing two bases whose element-modes are smfd 709$ composite can cause bases appearing in these modes to be equivalenced smfd 710$ too, as in example b above. however, equivalencing is not induced in smfd 711$ the opposite direction. for example: smfd 712$ smfd 713$ example c smfd 714$ smfd 715$ s with:= x; $ s: set(elmt b1); x: elmt b1; smfd 716$ u with:= s; $ u: set(elmt b2); s: elmt b2; smfd 717$ t with:= x; $ t: set(elmt b3); x: elmt b3; smfd 718$ v with:= t; $ v: set(elmt b4); t: elmt b4; smfd 719$ smfd 720$ in this example, b1 and b3 are equivalenced in view of the x-link, but smfd 721$ b2 and b4 are not merged. this approach is probably desirable, since smfd 722$ such a merging would not improve the execution of the above code smfd 723$ fragment, but might make u and v sparse over the merged base (of smfd 724$ course, further information may make us merge b2 and b4, e.g. an smfd 725$ instruction such as 'if s in v then ...'). smfd 726$ smfd 727$ (2) as with any recursive or transitive-closure mechanism, we must smfd 728$ guarantee convergence of the merging process. since the number of smfd 729$ generated bases is finite, divergence could occur only if there exist smfd 730$ cyclic dependencies between bases, the simplest of which could be: smfd 731$ base b1: set(elmt b1). if such a configuration occurred and b1 were smfd 732$ equivalenced with base b2: set(elmt b2), then the merging process smfd 733$ would repeat equivalencing operations involving b1 and b2 infinitely smfd 734$ many times. also, during the base-dropping phase, if b1 were smfd 735$ droppable then we might attempt to replace each elmt b1 appearance in smfd 736$ a repr by set(elmt b1), which would obviously lead to endless looping. smfd 737$ smfd 738$ we claim, however, that such situations will never occur, indeed, a smfd 739$ cyclic dependency could only be derived by base merging along a cyclic smfd 740$ execution path, and only if there is a cyclic type dependency along smfd 741$ this path, as in the loop smfd 742$ smfd 743$ (forall ...) x with:= x; end forall; smfd 744$ smfd 745$ but in this situation the type finder will produce different types for smfd 746$ the o-variable and the i-variable of the statement in the loop, e.g. smfd 747$ set(general) and general, respectively (recall that o-variables are smfd 748$ assigned the forward type of their i-variables in the final phase of smfd 749$ the type finder). hence, no base merging will take place along such smfd 750$ loops. smfd 751$ smfd 752$ it can also be noted that if the base generated for this statement smfd 753$ (call it b1) is not dropped, then the conversion analysis phase will smfd 754$ split the variable x into two variable x.1 and x.2, and will transform smfd 755$ the above loop into smfd 756$ smfd 757$ (forall ...) x.2 := x.1; x.1 with:= x.2; end forall; smfd 758$ smfd 759$ where we have: smfd 760$ smfd 761$ b1: base(general); x.2: elmt b1; x.1: set(elmt b1); smfd 762$ smfd 763$ and the assignment x.2 := x.1; is a locate of the value of x.1 in b1. smfd 764$ smfd 765$ it should also be noted that in order to ensure proper operation of smfd 766$ the type finder, its above-mentioned final phase should compute smfd 767$ o-variable types without applying the standard artificial limit on the smfd 768$ complexity of generated types, so as to avoid any accidental type smfd 769$ identification. smfd 770$ smfd 771$ note in this connection that if we process the loop smfd 772$ smfd 773$ (forall ...) x := { x }; end forall; smfd 774$ smfd 775$ both x occurrences would get the type set(set(...set(general)...)) smfd 776$ with a maximal nesting level, unless, in the final phase, we increase smfd 777$ the nesting level of the o-variable by 1. smfd 778$ smfd 779$ (3) return for the moment to example c above, where there are two smfd 780$ linked occurrences of s, one of which is repred set(elmt b1) and the smfd 781$ other elmt b2. at a first glance it seems that we ought to produce a smfd 782$ common repr for these occurrences, but a better choice is to leave smfd 783$ these reprs as they are. then, after base merging and name-splitting, smfd 784$ the code will be transformed into: smfd 785$ smfd 786$ sa with:= x; $ sa: set(elmt b1); x: elmt b1; smfd 787$ (a1) sb := sa; smfd 788$ u with:= sb; $ u: set(elmt b2); sb: elmt b2; smfd 789$ tb from u; $ u: set(elmt b2); tb: elmt b2; smfd 790$ (a2) ta := tb; smfd 791$ y from ta; $ ta: set(elmt b1); y: elmt b1; smfd 792$ smfd 793$ where a1 is a base locate of the value of sa in b2 and a2 is smfd 794$ essentially a dereferencing of the value of tb, originally a pointer smfd 795$ to an element of b2, but after dereferencing a pointer to the set smfd 796$ value of ta (note that here type checking is necessary unless the smfd 797$ element mode of b2 is set(elmt b1)). smfd 798$ smfd 799$ this approach again reflects the basic philosophy of the final phases smfd 800$ of the optimiser, namely: reprs and types should be assigned to smfd 801$ occurrences in such a way that each instruction will be executed in smfd 802$ the most efficient manner, and any type or repr checks and conversions smfd 803$ which must precede an instruction should be moved and inserted into smfd 804$ the code in an appropriate place preceding that instruction. smfd 805$ smfd 806$ final remarks smfd 807$ ----- ------- smfd 808$ smfd 809$ (1) our algorithm merges reprs only if they have the same type, and smfd 810$ consequently equivalences bases only if they have the same smfd 811$ element-type. for example, set(integers) and set(general) are smfd 812$ considered as distinct types. hence, even if there is a link between smfd 813$ two occurrences having such types, their bases will not be merged, and smfd 814$ eventually we shall have to convert from one base to the other. it is smfd 815$ not clear whether this approach is to be preferred, and there may be a smfd 816$ point in merging bases of this kind, even though this can lead to smfd 817$ creation of additional type checks and conversions which would not smfd 818$ have been otherwise needed. at any rate, our approach is simple and smfd 819$ should be quite acceptable in most cases. smfd 820$ smfd 821$ (2) we expect the present data structure algorithm to be more smfd 822$ efficient than the variant algorithm described in . in the smfd 823$ present algorithm, base propagation is accomplished by a single pass smfd 824$ through the bfrom links between based occurrences, with very efficient smfd 825$ processing of each such link. however, a time consuming part of the smfd 826$ present algorithm is the manipulation of completely useless and thus smfd 827$ droppable bases, and corresponding based occurrences. it is not clear smfd 828$ how to estimate this additional time usage, which depends heavily on smfd 829$ the nature of the program being analysed. smfd 830$ smfd 831$ since it generates these additional bases, the present algorithm will smfd 832$ require more space than the previous one. this space usage could be smfd 833$ reduced by somewhat more intricate programming (e.g. by folding the smfd 834$ pre-pass into the base merging phase), but then the algorithm would smfd 835$ lose some of its clarity. smfd 836$ smfd 837$ note, however, that the space required by the older automatic smfd 838$ data-structure selection algorithms (of schonberg, schwartz and liu) smfd 839$ which use value-flow, is at least the cardinality of the value-flow smfd 840$ maps, in comparison with which the space requirement of the present smfd 841$ algorithm is rather modest. 245 246 247 end module setl_optimizer - auto_dstruct; 248 249 1 .=member cnv15j 2 3 module setl_optimizer - conversion_analysis; 4$ 5$ this module performs data-structure conversion analysis. this is a 6$ necessary supplementary phase to be performed after the preceding 7$ type analysis and automatic data-structure selection phases have com- 8$ puted data types and representations for the variable occurrences in 9$ the program being analyzed. 10$ 11$ this phase performs the following tasks: 12$ 13$ (a) name-splitting: 14$ 15$ each variable v whose occurrences do not get all the same repre- 16$ sentation, is 'split' into several variables, one for each pos- 17$ sible data representation computed for occurrences of v. each 18$ occurrence of v is then replaced by an occurrence of the corres- 19$ ponding split-variable. all the split variables of v are entered 20$ into the symbol table as storage-sharing variables. the problem 21$ with this transformation is that conversions between different 22$ variables split from the same variable still have to be inserted 23$ into the code, to avoid situations in which one such variable is 24$ defined and then another split variable is used. this is taken 25$ care of by the following conversion analysis subphase. 26$ 27$ (b) conversion analysis: 28$ 29$ in this phase we perform three bit-vectoring data flow analyses 30$ to determine where and when to insert conversions from one split 31$ variable to another. the rationale of these analyses is 32$ discussed in section 9 of the tech. report, and is mentioned here 33$ with only little detail. 34$ 35$ the first analysis being performed is a 'backward-union' safety 36$ analysis whose purpose is to determine which conversions can oc- 37$ cur at a given program point (to be mainly an interval preheader, 38$ into which we try to move such conversions). 39$ 40$ the second analysis is a 'forward-intersection' availability ana- 41$ lysis, which, using the safety information available from the 42$ previous analysis, moves conversions out of loops if possible, 43$ and determines whether a conversion is required prior to any 44$ given use of a split variable. the actual conversion insertion 45$ takes place later on in this module. 46$ 47$ the third analysis is a 'forward-union' reachability analysis, in 48$ which we determine, for each basic block n, the set mayreach(n) 49$ of all split variables which can reach the start of n. this will 50$ enable us to emit appropriate conversions as follows: if we want 51$ to insert at a certain point a conversion to a split variable vx, 52$ we compute the set of all variables vy split from the same origi- 53$ nal variable as vx, which can reach the conversion. if this set 54$ consists of exactly one variable vy, then we emit a conversion of 55$ vy to vx; if there is more than one such variable vy, then we 56$ emit a conversion from a type-general variable sharing storage 57$ with vx to vx. 58$ 59 macro .comp; .comp_syms endm; 60 macro df_base; df_base_syms endm; 61 macro interproc_fwd_analysis; interproc_fwd_analysis_syms endm; 62 macro intraproc_fwd_analysis; intraproc_fwd_analysis_syms endm; 63 macro interproc_back_analysis; interproc_back_analysis_syms endm; 64 macro intraproc_back_analysis; intraproc_back_analysis_syms endm; 65 macro fom; fom_syms endm; 66 macro xom; xom_syms endm; 67 68 var 69 all_splits, $ set of all split variables 70 split_vars, $ maps each variable v to all of its split 71 $ variables 72 split_from, $ maps each split variable to original variable 73 forminv, $ maps each tuple of form attributes and a 74 $ scope to a form in this scope having those 75 $ attributes 76 can_convert; $ a relation containing [ fm1, fm2 ] iff any 77 $ value having form fm1 can be converted to 78 $ form fm2 79 80 81 macro base_of(rpr); userbase(rpr(2)) endm; 82 83$ the following macro constructs a tuple of form attributes from a form 84 85 macro form_maps(f); 86 [ ft_type(f), 87 ft_mapc(f), 88 ft_elmt(f), 89 ft_dom(f), 90 ft_im(f), 91 ft_base(f), 92 ft_lim(f), 93 ft_tup(f), 94 ft_hashok(f), 95 ft_neltok(f), 96 ft_pos(f), 97 ft_num(f) ] 98 endm; 99 100 macro ft_type_; nfm_maps(1) endm; 101 macro ft_mapc_; nfm_maps(2) endm; 102 macro ft_elmt_; nfm_maps(3) endm; 103 macro ft_dom_; nfm_maps(4) endm; 104 macro ft_im_; nfm_maps(5) endm; 105 macro ft_base_; nfm_maps(6) endm; 106 macro ft_lim_; nfm_maps(7) endm; 107 macro ft_tup_; nfm_maps(8) endm; 108 macro ft_hashok_; nfm_maps(9) endm; 109 macro ft_neltok_; nfm_maps(10) endm; 110 macro ft_pos_; nfm_maps(11) endm; 111 macro ft_num_; nfm_maps(12) endm; 112 113 const null_ft_num = 114 { [ f_lset, 0 ], 115 [ f_lmap, 0 ], 116 [ f_limap, 0 ], 117 [ f_lpmap, 0 ], 118 [ f_lrmap, 0 ] }; 119 120 const $ various gross types 121 grselt = { t_elmt }, 122 grstup = { t_tuple }, 123 grsset = { t_set }, 124 grsmap = { t_map }; 125 126 127 repr 128 mode df_elmt: df_elmt_syms; 129 mode df_map: df_map_syms; 130 131 base splitvars: symbol; 132 mode splitvar: elmt splitvars; 133 134 all_splits: remote set(splitvar); 135 split_vars: remote mmap{splitvar} 136 sparse set(splitvar); 137 split_from: remote smap(splitvar) splitvar; 138 139 forminv: mmap{elmt base_scopes} 140 smap(tuple(general)(12)) 141 elmt forms; 142 can_convert: mmap{elmt forms} set(elmt forms); 143 grselt: gross_type; 144 grstup: gross_type; 145 grsset, grsmap: gross_type; 146 147 .meet: operator(df_map, df_map) df_map; 148 .join: operator(df_map, df_map) df_map; 149 compute_splits: procedure; 150 place_conversions: procedure; 151 conv_blockmaps: procedure(routine, df_elmt) 152 tuple( 153 remote smap(df_edge) df_map, 154 remote smap(df_edge) df_map, 155 remote smap(df_edge) df_map, 156 remote mmap{df_node} df_elmt 157 ); 158 insert_convs: procedure( 159 routine, 160 remote smap(df_node) df_elmt, 161 remote mmap{df_node} df_elmt, 162 remote smap(df_node) df_elmt, 163 df_elmt 164 ); 165 sharp_form: procedure( 166 elmt forms, 167 elmt types, 168 elmt base_scopes ) 169 elmt forms; 170 newform: procedure( 171 tuple(general)(12), 172 elmt base_scopes ) 173 elmt forms; 174 elmt_type: procedure(elmt types) elmt types; 175 insert_base: procedure(tent_base, elmt base_scopes); 176 add_conv: procedure( 177 splitvar, 178 elmt insts, 179 df_elmt 180 ); 181 add_split: procedure(splitvar) splitvar; 182 end repr; 183 184 185 procedure conv_optimize; 186$ 187$ this is the main driver routine for conversion analysis. it consists 188$ of the following phases: 189$ 190$ 1. base insertion: during this phase, we actually insert the effec- 191$ tive bases into the symbol table. 192$ 193$ 2. computation of split variables: for each variable occurrence, we 194$ find (a possibly new) variable with the form for the variable at 195$ the occurrence, and substitute the original variable by the newly 196$ variable. 197$ 198$ 3. conversion insertion: during this phase, we solve the data flow 199$ problems metioned above, and insert the conversions required bet- 200$ ween variables within each equivalence class. 201$ 202 repr 203 entry_time: integer; 204 end repr; 205 206 title('cims.setl.' + prog_level + ' - conversion analysis'); 207 printa(term_file, ' - conversion analysis'); 208 209 entry_time := time; 210 211 all_splits := {}; $ set of all variables being split 212 split_vars := {}; $ maps each variable v to all of its split 213 $ variables 214 split_from := {}; $ maps each split variable to original variable 215 216 217 compute_splits; $ generate the needed split variables 218 place_conversions; $ find low-frequency places for conversions 219 220 221 $ delete the static variables global to this module 222 all_splits := om; split_vars := om; split_from := om; 223 forminv := om; can_convert := om; 224 225 statistics with:= time; $ save time for final statistics 226 227 if 'e' in dump_string then 228 print; 229 print(time - entry_time, 'msecs spent in conversion analysis'); 230 end if; 231 232 233 end procedure conv_optimize; 234 235 236 procedure compute_splits; 237$ 238$ this routine does the actual insertion of the effective bases into 239$ the symbol table. 240$ 241$ 242$ this routine computes the equivalence classes for each variable in 243$ the program. 244$ 245 init 246 memo_form := {}; 247 init 248 argin_inst := {}, argout_inst := {}, 249 argin_form := {}, argout_form := {}; 250 251 repr 252 sc: elmt base_scopes; 253 b: tent_base; 254 fm, nfm: elmt forms; 255 fm1, fm2: elmt forms; 256 v: symbol; 257 vx, vy: splitvar; 258 vo: occurrence; 259 rpr: elmt types; 260 vforms: sparse set(elmt forms); smfc 631 nfm_maps: tuple(general)(12); 262 memo_form: smap( 263 tuple(elmt forms, elmt types, symbol) 264 ) elmt forms; smfk 138 new_occsof: sparse mmap{symbol} smfk 139 sparse set(occurrence); smfk 140 voccs: sparse set(occurrence); 265 266 r: routine; 267 inst: elmt insts; 268 argins: sparse set(elmt insts); 269 argin_inst: sparse mmap(symbol) elmt insts; 270 argout_inst: sparse mmap(symbol) elmt insts; 271 argin_form: sparse mmap(symbol) elmt forms; 272 argout_form: sparse mmap(symbol) elmt forms; 273 split_time: integer; 274 end repr; 275 276 277 split_time := time; 278 279 forminv := {}; 280 (forall sc in scopes) 281 282 $ build forminv, which sends each scope and vector of form 283 $ attributes to its form table entry. 284 285$$-- we do not compute the scope of tentative bases correctly at the 286$$-- moment: consequently we move all forms into the system scope 287$$-- here. if the scoping was done correctly, the following (disabled 288$$-- i.e. commented-out) code would suffice. the corrective code has 289$$-- has been marked as such. 290 291$$-- (for_form(fm, sc)) 292$$-- if not is_fbase(fm) then 293$$-- forminv{sc}(form_maps(fm)) := fm; 294$$-- end if; 295$$-- end; 296 297$$-- start of corrective code 298 (for_form(fm, sc)) 299 if is_fbase(fm) then 300 if sc_type(sc) /= sc_sys then 301 ermsg('cannot yet handle user-defined bases'); 302 end if; 303 else 304 if forminv{sc}(form_maps(fm)) = om then 305 if sc_type(sc) /= sc_sys then 306 last_form(sym_sys) := 307 next_form(last_form(sym_sys)) := fm; 308 end if; 309 forminv{sym_sys}(form_maps(fm)) := fm; 310 end if; 311 end if; 312 end; $ end for_form; 313 314 if sc_type(sc) /= sc_sys then 315 first_form lessf:= sc; last_form lessf:= sc; 316 end if; 317 next_form lessf:= last_form(sym_sys); 318 end forall; 319 320 (forall sc in scopes) 321$$-- end of corrective code 322 323 $ insert the actual bases of this scope into the symbol table. 324 325$$-- (forall b in actual_bases | 326$$-- bscope(b) = sc and userbase(b) = om) 327 (forall b in actual_bases | userbase(b) = om) 328 insert_base(b, sym_sys); 329 330$$-- note that we allocate all bases in the system scope 331 332$$-- if userbase(b) is not om, or not in this scope, we must assure 333$$-- that maxscope([ scope(userbase(b)), bscope(b) ]) = 334$$-- scope(userbase(b)), i.e. that the userbase is in a larger scope 335$$-- than b, and furthermore that the ft_elmt(form(userbase(b))) is 336$$-- consistent with the elmt_mode(b). this is currently not done. 337 338 end forall; 339 340 $ finally compute all forms for this scope smfk 141 smfk 142 new_occsof := {}; $ occurrences of new split variables. 341 342 (for_sym(v, sc)) 343 if v notin variables then continue; end if; 344 smfk 143 $ assert domain occsof = variables; except as follows: smfk 144 $ 1. there exist variables which are not in domain(occsof): smfk 145 $ these are dead variables, generated e.g. by available smfk 146 $ expression analysis when merging redundant assignments. smfk 147 $ 2. there are some constants added to occsof when instruc- smfk 148 $ tions are added. 346 347 (forall vo in occsof{v}) 348 rpr := oi_repr(vo) ? type_zero; 349 if rpr = type_zero then continue; end if; 350 if is_repr(v) = 1 then 351 fm := form(v); 352 else 353 fm := std_form(f_gen); 354 end if; 355$ 356$ now 'merge' the given form 'fm' of v and the suggested repr 'rpr' 357$ at the occurrence vo, to obtain a new form 'nfm' which is 358$ more specific than fm and is compatible with rpr. this new form 359$ is also inserted into the form table of the appropriate scope 360$ if not there already. all these is carried out by the routine 361$ 'sharp_form' 362$ 363 if (nfm := memo_form( [ fm, rpr, v ] )) = om then 364 nfm := sharp_form(fm, rpr, sym_sys); 365$$-- note that we allocate all forms in the system scope 366 memo_form( [ fm, rpr, v ] ) := nfm; 367 end if; 368 369 if form(v) = nfm then 370 vx := v; 371 split_vars{vx} with:= vx; 372 split_from(vx) := vx; 373 374 elseif exists vx in split_vars{v} | 375 form(vx) = nfm then 376 pass; 377 378 elseif split_from(v) = om and not is_repr(v)=1 then 379 form(v) := nfm; 380 381 vx := v; 382 split_vars{vx} with:= vx; 383 split_from(vx) := vx; 384 385 elseif exists r in routs | v = rretn(r) then 386 $ this could be handles somewhat more efficient... 387 form(v) := std_form(f_gen); 388 389 vx := v; 390 split_vars{vx} with:= vx; 391 split_from(vx) := vx; 392 393 elseif oi_op(vo) = q1_argin and is_ovar(vo) and 394 name( rptyps(args(instno(vo))(3)) 395 (value(args(instno(vo))(4))) ) = 'wr' 396 then 397 $ recall that the third operand of a q1_argin gives 398 $ the routine name, and the fourth operand gives the 399 $ argument number in the call: if this is a wr 400 $ parameter, ignore it as far as generation of split 401 $ variables is concerned. 402 403 $ assert args(instno(vo))(2) = sym_om; 404 405 vx := v; 406 else 407 vx := add_split(v); 408 form(vx) := nfm; 409 end if; 410 smfk 149 if v /= vx then smfk 150 args(instno(vo))(argno(vo)) := vx; smfk 151 occsof{v} less:= vo; new_occsof{vx} with:= vo; smfk 152 end if; 412 413 if oi_op(vo) = q1_argin and is_ovar(vo) then 414 argin_inst{v} with:= instno(vo); 415 argin_form{v} with:= form(vx); 416 417 elseif oi_op(vo) = q1_argout and is_ivar(vo) then 418 argout_inst{v} with:= instno(vo); 419 argout_form{v} with:= form(vx); 420 end if; 421 end forall vo; 422 end; $ end for_sym; smfk 153 smfk 154 (forall voccs = new_occsof{v}) occsof{v} +:= voccs; end; smfk 155 new_occsof := om; $ free storage. smfk 156 423 end forall sc; 424 430 431$ formal parameters of procedures must be handled differently during 432$ the generation of split variables, for the following two reasons: 433$ 434$ (1) if a write-parameter (ie. a parameter with a rw or wr declaration) 435$ requires a conversion, our general algorithm would insert the conver- 436$ sion before the instruction which requires the conversion, i.e. the 437$ q1_argout instruction. this is the wrong place, because at this point 438$ the actual parameter is still on the stack, and consequently a conver- 439$ sion of the symbol table entry's value would produce the wrong result. 440$ note that the proper place for this conversion is in the called 441$ procedures exit block. 442$ 443$ (2) the code generator uses the form of the procedure to determine the 444$ conversions required between actual and formal parameters, plus the 445$ conversion which might be required for the procedure's return value. 446$ to compute the proper procedure form we need to know which of the 447$ possibly existing split variables was actually used in the q1_argin 448$ and q1_argout instructions, to then use their forms to build the 449$ procedure form. (note that since setl does not provide generic 450$ procedures, the form of an ambigous formal parameter becomes general) 451$ 452$ we then handle formal parameters as follows: whenever we generate a 453$ split variable for the formal parameter of a q1_argin or q1_argout 454$ instruction, we update two maps: the first maps each formal 455$ parameter to all the forms it assumes in a q1_argin or q1_argout 456$ instruction; the second maps each formal parameter to all q1_argin 457$ or q1_argout instructions in which it appears. if, after generating 458$ all split variables, we find that some formal parameter is found with 459$ more than one form, we generate an additional split variable of form 460$ general, and use the second map to modify all q1_argin and q1_argout 461$ instructions to use this variable (note that such a split might 462$ already exist). we then update the routine parameter list, rparams, 463$ to reflect any change. finally, we generate the new procedure form. 464$ (nb. we really keep separate maps for the q1_argin and q1_argout cases 465$ to simplify the update process.) 466 467 $ at this point, we are done with the ads_maps: delete them 468 actual_bases := om; userbase := om; bscope := om; 469 oi_repr := om; elmt_mode := om; 470 471 (forall argins = argin_inst{v}) 472 if # (argin_form{v} + argout_form{v}) > 1 then 473 474 if ft_type(form(v)) = f_gen then 475 vx := v; 476 split_vars{vx} with:= vx; 477 split_from(vx) := vx; 478 479 elseif exists vx in split_vars{v} | 480 ft_type(form(vx)) = f_gen then 481 is_param(vx) := 1; 482 483 else 484 vx := add_split(v); 485 form(vx) := std_form(f_gen); 486 end if; 487 488 (forall inst in argins) 489 args(inst)(1) := vx; smfk 157 if v /= vx then smfk 158 vo := get_oi(inst, 1); smfk 159 occsof{v} less:= vo; occsof{vx} with:= vo; smfk 160 end if; 490 end forall; 491 492 (forall inst in argout_inst{v}) 493 args(inst)(4) := vx; smfk 161 if v /= vx then smfk 162 vo := get_oi(inst, 4); smfk 163 occsof{v} less:= vo; occsof{vx} with:= vo; smfk 164 end if; 494 end forall; 495 496 elseif args(arb argins)(1) = v then 497 continue forall; 498 499 else 500 vx := args(arb argins)(1); 501 end if; 502 503 inst := arb argins; 504 rparams(args(inst)(3))(value(args(inst)(4))) := vx; 505 end forall; 506 507 (forall r in routs) 508 nfm_maps := []; 509 ft_type_ := f_proc; 510 ft_elmt_ := [ form(v) : v in rparams(r) ] with form(rretn(r)); 511 ft_lim_ := #rparams(r) + 1; 512 513 if forall fm in ft_elmt_ | ft_type(fm) = f_gen then 514 form(r) := std_form(f_proc); 515 else 516$$--cf. above comment relating to the scope of forms 517 form(r) := newform(nfm_maps, sym_sys); 518 end if; 519 end forall; 520 521 all_splits := { vx : vx = split_from(vy) | #split_vars{vx} > 1 }; 522 523 argin_inst := om; argout_inst := om; 524 argin_form := om; argout_form := om; 525$ 526$ compute the relation can_convert 527$ 528 can_convert := {}; 529 (forall vx in all_splits) 530 vforms := { form(vy) : vy in split_vars{vx} }; 531 (forall fm1 in vforms, fm2 in vforms | can_conv(fm1, fm2)) 532 can_convert with:= [ fm1, fm2 ]; 533 end forall; 534 end forall; 535 536 if 'e' in dump_string then 537 print(time - split_time, 'msecs to compute split variables'); 538 end if; 539 540 end procedure compute_splits; 541 542 543 544 545 procedure place_conversions; 546$ 547$ this routine solves the data flow problems and computes the maps to 548$ insert the required conversions. 549$ 550$ note that in the data structures selected below we reflected that we 551$ use the results of an analysis as soon as it becomes available. this 552$ led us to use the data flow base for most of our work, as this base 553$ should be smaller than the set of all split variables. 554$ 555 repr 556 globsplits, locsplits: df_elmt; 557 558 zero: df_elmt; 559 id: df_map; 560 561 maysafe: remote smap(df_node) df_elmt; 562 avail: remote smap(df_node) df_elmt; 563 exposed: remote mmap{df_node} df_elmt; 564 insert: remote mmap{df_node} df_elmt; 565 safe: remote mmap{df_node} df_elmt; 566 mayreach: remote smap(df_node) df_elmt; 567 dum1, dum2, dum3: remote mmap{df_node} df_elmt; 568 569 ffwd, ffwdj, fbak: remote smap(df_edge) df_map; 570 571 r: routine; 572 intt, hd: elmt blocks; 573 v, vy: splitvar; 574 vx: elmt df_base; 575 ksplits, gsplits: df_elmt; 576 fnewcnvs: df_map; 577 usym1, usym2: symbol; 578 df_time: integer; 579 end repr; 580 581 582 df_time := time; 583$ 584$ construct the undefined flow values for the dataflow_solver routines. 585$ 586 xom := { usym1 := newat }; 587 fom := [ { usym1 := newat }, { usym2 := newat } ]; 588 589$ conversion analysis for global variables (and formal parameters) 590 591 globsplits := {}; 592 (forall v in globalvars | v in all_splits) 593 globsplits +:= split_vars{v}; 594 end forall; 595 (forall r in routs, v in rparams(r) | 596 split_from(v) in all_splits ) 597 globsplits +:= split_vars{split_from(v)}; 598 end forall; 599 600 if globsplits = {} then 601 pass; 602 603 else 604 zero := globsplits; $ analysis data state at exits 605 id := [ zero, {} ]; $ identity map for analysis 606 607 $ get the data_flow maps for both analyses and the 'exposed' map 608 [ffwd, ffwdj, fbak, exposed] := conv_blockmaps(om, globsplits); 609 610 $ invoke the interprocedural analyzers 611 interproc_back_analysis(fbak, maysafe, id, zero, false); 612 fbak := om; $ free storage 613 614$ before calling the forward analyzer, convert the 'maysafe' map 615$ produced by the backward analyzer to the form required by the 616$ forward analyzer. (see section 9 of the tech. report.) 617 618 safe := {}; 619 (forall r in routs, intt in ints(r) | 620 (hd := int_nodes(intt)(1)) /= intt) 621 safe{intt} := 622 { vx in globsplits | 623 (forall vy in split_vars{split_from(vx)} | 624 vy in maysafe(hd) and 625 form(vx) in can_convert{form(vy)} ) }; 626 end forall; 627 maysafe := om; $ free storage 628 629 zero := {}; $ forward analysis data state at entries 630 interproc_fwd_analysis(ffwd, avail, id, zero, true, 631 true, exposed, insert, safe); 632 ffwd := om; $ free storage 633 634$ next update the flow maps 'ffwdj' of the third analysis to take 635$ into account conversions moved out of loops 636 (forall r in routs, intt in ints(r) | 637 (hd := int_nodes(intt)(1)) /= intt) 638 fnewcnvs := id; 639 (forall vx in insert{intt}) 640 ksplits := split_vars{split_from(vx)} less vx; 641 gsplits := { vx }; 642 fnewcnvs := [ id(1) - ksplits, gsplits ] .comp fnewcnvs; 643 end forall; 644 ffwdj([intt, hd]) := fnewcnvs .comp ffwdj([intt, hd]); 645 end forall; 646 647 interproc_fwd_analysis(ffwdj, mayreach, id, zero, false, 648 false, dum1, dum2, dum3); 649 ffwdj := om; $ free storage 650 651$ finally, perform the actual conversion insertion 652 insert_convs(om, avail, insert, mayreach, globsplits); 653 654 $ free storage 655 avail := om; insert := om; mayreach := om; 656 globsplits := om; 657 658 end if; 659 660$ repeat the above procedure for the local variables of each 661$ procedure r 662 663 (forall r in routs) 664 665 locsplits := {}; 666 (forall v in localvars{r} | 667 v in all_splits and 668 forall vy in split_vars{v} | vy notin rparams(r) ) 669 locsplits +:= split_vars{v}; 670 end forall; 671 672 if locsplits = {} then continue forall; end if; 673 674 zero := locsplits; $ analysis data state at exits 675 id := [ zero, {} ]; $ identity map for analysis 676 677 [ ffwd, ffwdj, fbak, exposed ] := 678 conv_blockmaps(r, locsplits); 679 680 intraproc_back_analysis(r, fbak, maysafe, id, zero, false); 681 fbak := om; $ free storage 682 683 safe := {}; 684 (forall intt in ints(r) | (hd := int_nodes(intt)(1)) /= intt) 685 safe{intt} := 686 { vx in locsplits | 687 (forall vy in split_vars{split_from(vx)} | 688 vy in maysafe(hd) and 689 form(vx) in can_convert{form(vy)} ) }; 690 end forall; 691 maysafe := om; $ free storage 692 693 zero := {}; 694 695 intraproc_fwd_analysis(r, ffwd, avail, id, zero, true, 696 true, exposed, insert, safe); 697 ffwd := om; $ free storage 698 699$ next update the flow maps 'ffwdj' of the third analysis to take 700$ into account conversions moved out of loops 701 (forall intt in ints(r) | 702 (hd := int_nodes(intt)(1)) /= intt) 703 fnewcnvs := id; 704 (forall vx in insert{intt}) 705 ksplits := split_vars{split_from(vx)} less vx; 706 gsplits := { vx }; 707 fnewcnvs := [ id(1)-ksplits, gsplits ] .comp fnewcnvs; 708 end forall; 709 ffwdj([intt, hd]) := fnewcnvs .comp ffwdj([intt, hd]); 710 end forall; 711 712 intraproc_fwd_analysis(r, ffwdj, mayreach, id, zero, false, 713 false, dum1, dum2, dum3); 714 ffwdj := om; $ free storage 715 716 insert_convs(r, avail, insert, mayreach, locsplits); 717 718 $ free storage 719 avail := om; insert := om; mayreach := om; 720 locsplits := om; 721 end forall; 722 723 if 'e' in dump_string then 724 print(time - df_time, 'msecs to solve dataflow problem'); 725 end if; 726 727 728 end procedure place_conversions; 729 730 1 .=member cbm15k 2 3 4 procedure conv_blockmaps(p, splits); 5$ 6$ this procedure computes data flow maps and exposed representations for 7$ the backward and forward analyses required in conversion optimisation. 8$ the first parameter 'p' is either a routine to be scanned, or, if p is 9$ omega, then all routines have to be scanned. the second parameter, 10$ 'splits', is the set of all split variables relevant for the analysis. 11$ 12 repr 13 $ data structures for parameters 14 p: routine; 15 splits: df_elmt; 16 17 $ data structures for return values 18 ffwd, ffwdj, fbak: remote smap(df_edge) df_map; 19 exposed: remote mmap{df_node} df_elmt; 20 21 $ data structures for local variables 22 todo: sparse set(routine); 23 r: routine; 24 b: df_node; 25 i: elmt insts; 26 opc: elmt base_opcodes; 27 argsi: tuple(symbol); 28 29 v: splitvar; 30 vx: symbol; 31 vx1: splitvar; 32 vx2: elmt df_base; 33 vy: elmt df_base; 34 fmx: elmt forms; 35 iva1: integer; 36 k: integer; 37 38 fblkfwd, fblkbak: df_map; 39 inpvars: df_elmt; 40 killed: df_elmt; 41 fwdgen, bakgen: df_elmt; 42 can_expose: df_elmt; 43 vsplits: df_elmt; 44 45 sblks: sparse set(df_node); 46 lb: symbol; 47 b1: df_node; 48 end repr; 49 50 if p = om then todo := routs; else todo := { p }; end if; 51 52 ffwd := {}; ffwdj := {}; fbak := {}; 53 exposed := {}; 54 55 (forall r in todo) 56 (for_block(b, r)) 57 fblkfwd := fblkbak := [splits, {}]; 58 59 (for_inst(i, b)) 60 opc := opcode(i); 61 argsi := args(i); 62 iva1 := first_ivar(opc); 63$$$ ???? need to worry about two input arguments of i being different 64$$$ ???? split variables of the same original variable. 65$$$ ???? this case should never happen, and we assume here that it 66$$$ ???? indeed does not occur 67 inpvars := {}; $ set of all input arguments of i 68 69 killed := fwdgen := bakgen := {}; 70$ these sets are defined as follows: 71$ killed - the set of all split variables whose original variable has 72$ appeared so far in i 73$ fwdgen - the set of all split variables occurring in i (where the 74$ output occurrence in i suppresses any input occurrences in i 75$ of the same original variable from appearing in this set. 77$ bakgen - set of all split variables vx split from some v such that 78$ either v originally occurred in i as an input argument, 79$ actually being represented by another split variable vx1 80$ such that one can always convert from vx to vx1, or, if this 81$ is not the case, v occurs in i as its output argument. 82 83 can_expose := fblkfwd(1) - fblkfwd(2); 84 (forall k in [ #argsi, #argsi-1..iva1 ] | 85 (vx2 := vx := argsi(k)) in splits) 86 v := split_from(vx); 87 inpvars with:= v; 88 vsplits := split_vars{v}; 89 fmx := form(vx); 90 91 if vx2 in can_expose then 92 exposed{b} with:= vx2; 93 end if; 94 95 killed +:= vsplits; 96 fwdgen with:= vx2; 97 bakgen +:= { vy in vsplits | 98 fmx in can_convert{form(vy)} }; 99 100 end forall; 101 102 if opc in ops_ovar and 103 (vx2 := vx := argsi(1)) in splits then 104 v := split_from(vx); 105 vsplits := split_vars{v}; 106 killed +:= vsplits; 107 fwdgen := fwdgen - vsplits + { vx2 }; 108 if v notin inpvars then bakgen +:= vsplits; end; 109 end if; 110 111 fblkfwd := 112 [ splits-killed+fwdgen, fwdgen ] .comp fblkfwd; 113 114 fblkbak := 115 fblkbak .comp [ splits-killed+bakgen, bakgen ]; 116 117 if opc in ops_goto then 118 if opc = q1_case then 119 sblks := { blockof(value(lb)) : 120 lb in range value(argsi(1)) }; 121 else 122 sblks := { blockof(value(argsi(#argsi))) }; 123 end if; 124 (forall b1 in sblks) 125 ffwd([b,b1]) := fblkfwd .meet ffwd([b,b1]); 126 ffwdj([b,b1]) := fblkfwd .join ffwd([b,b1]); 127 fbak([b,b1]) := fblkbak .join fbak([b,b1]); 128 end forall; 129 end if; 130 end; $ end for_inst 131 end; $ end for_block 132 end forall; 133 134 return [ ffwd, ffwdj, fbak, exposed ]; 135 136 end procedure conv_blockmaps; 137 138 139 140 141 operator .meet(f, g); 142$ 143$ this is a general routine, used here with the following data 144$ structures: 145$ 146 repr 147 f, g: df_map; 148 end repr; 149 150 if g = om then 151 return f; 152 else 153 return [ f(1) * g(1), f(2) * g(2) ]; 154 end if; 155 156 end operator .meet; 157 158 159 operator .join(f, g); 160$ 161$ this is a general routine, used here with the following data 162$ structures: 163$ 164 repr 165 f, g: df_map; 166 end repr; 167 168 if g = om then 169 return f; 170 else 171 return [ f(1) + g(1), f(2) + g(2) ]; 172 end if; 173 174 end operator .join; 175 176 1 .=member inc15l 2 3 4 procedure insert_convs(p, avail, insert, mayreach, splits); 5$ 6$ this procedure scans each basic block and determines whether conver- 7$ sions between split variables are required, based on the global avail 8$ information. it also inserts conversions into interval preheaders. 9$ 10$ the logic of this routine is quite similar to the preceding 11$ conv_blockmaps routine, and more extensive comments can be found there 12$ 13 repr 14 $ data structures for parameters 15 p: routine; 16 avail: remote smap(df_node) df_elmt; 17 insert: remote mmap{df_node} df_elmt; 18 mayreach: remote smap(df_node) df_elmt; 19 splits: df_elmt; 20 21 $ data structures for local variables 22 todo: sparse set(routine); 23 r: routine; 24 b: df_node; 25 i, iprev: elmt insts; 26 opc: elmt base_opcodes; 27 argsi: tuple(symbol); 28 v: splitvar; 29 vx: symbol; 30 vx1: splitvar; 31 vx2: elmt df_base; 32 iva1: integer; 33 k: integer; 34 availb, mayreachb: df_elmt; 35 insertb: df_elmt; 36 killed, gen: df_elmt; 37 vsplits, vreach: df_elmt; 38 end repr; 39 40 41 if p = om then todo := routs; else todo := { p }; end if; 42 43 (forall r in todo) 44 (for_block(b, r)) 45 availb := avail(b); 46 if availb = om then continue; end if; 47 mayreachb := mayreach(b); 48 iprev := om; 49 50 (for_inst(i, b)) 51 opc := opcode(i); 52 argsi := args(i); 53 iva1 := first_ivar(opc); 54 55 killed := gen := {}; $ only for forward propagation 56 (forall k in [ #argsi, #argsi-1..iva1 ] | 57 (vx2 := vx := argsi(k)) in splits) 58 59 if vx2 notin availb then 60 killed +:= split_vars{split_from(vx1 := vx)}; 61 if opc /= q1_argout then 62 $ nb. conversions for formal paramters are 63 $ inserted in the called routine's exit 64 $ block (below), and consequently excluded 65 $ here. 66 add_conv(vx1, iprev, mayreachb); 67 end if; 68 gen with:= vx2; 69 end if; 70 end forall; 71 72 if opc in ops_ovar and 73 (vx2 := vx := argsi(1)) in splits then 74 v := split_from(vx); 75 vsplits := split_vars{v}; 76 killed +:= vsplits; 77 gen := gen - vsplits +{vx2}; 78 end if; 79 80 availb := availb - killed + gen; 81 mayreachb := mayreachb - killed + gen; 82 83 $ is this the last instruction in a preheader ? 84 if i = last_inst(b) and 85 (insertb := insert{b}) /= {} then 86 87 $ see comment above relating to formal parameters 88 $ assert opc /= q1_argout; 89 (forall vx in insertb) 90 add_conv(vx, iprev, mayreachb); 91 end forall; 92 end if; 93 94 if opc = q1_exit then 95 (forall vx in rparams(r) | (vx2 := vx) in splits 96 and is_write(vx) = 1) 97 if vx2 notin availb then 98 add_conv(vx, iprev, mayreachb); 99 end if; 100 end forall; 101 end if; 102 103 iprev := i; 104 end; $ end for_inst 105 end; $ end for_block 106 end forall; 107 108 end procedure insert_convs; 109 110 111 112 113 procedure add_conv(vx, rw iprev, mayreachb); 114 115 repr 116 vx: splitvar; 117 iprev: elmt insts; 118 mayreachb: df_elmt; 119 v, vy, vz: splitvar; 120 vsplits, vreach: set(splitvar); 121 str1, str2, str3: string; 122 end repr; 123 124 v := split_from(vx); 125 vsplits := split_vars{v}; 126 vreach := { vy in vsplits | vy in mayreachb }; 127 128 if #vreach = 1 then 129 vy := arb vreach; 130 131 elseif ft_type(form(v)) = f_gen then 132 vy := v; 133 134 elseif exists vy in vsplits | ft_type(form(vy)) = f_gen then 135 pass; 136 137 else 138 vy := add_split(v); 139 form(vy) := std_form(f_gen); 140 end if; 141 142 if vx = vy then return; end if; 143 144 if is_temp(vx) = 1 then 145 (forall vz in split_vars{v}) is_temp(vz) := om; end forall; 146 end if; 147 148 insert_ins(iprev, q1_asn, vx, vy); 149 150 str1 := 'convert "' + name(v) + '"'; 151 str2 := ' from "' + format_form(form(vy)) + '"'; 152 str3 := ' to "' + format_form(form(vx)) + '".'; 153 154 if #str1 + #str2 + #str3 < 72 then smfc 632 messages{stmtof(iprev)}{'s'} with:= [ str1 + str2 + str3 ]; 156 elseif #str1 + # str2 < 72 then smfc 633 messages{stmtof(iprev)}{'s'} with:= [ str1 + str2, str3 ]; 159 else smfc 634 messages{stmtof(iprev)}{'s'} with:= [ str1, str2, str3 ]; 162 end if; 163 164 165 end procedure add_conv; 166 167 168 169 170 procedure add_split(v2); 171$ 172$ this routine adds a new split variable vy to the scope of v. 173$ nb. form(vy) is undefined on exit of this routine. 174$ 175 repr 176 v1, vy1: symbol; 177 v2, vy2: splitvar; 178 end repr; 179 180 181 vy1 := add_var(scope(v1 := v2)); 182 183 name(vy1) := name(v1) + '.' + str # split_vars{v2}; 184 value(vy1) := value(v1); 185 186 alias(vy1) := v1; 187 is_store(vy1) := om; 188 189 is_read(vy1) := is_read(v1); 190 is_write(vy1) := is_write(v1); 191 is_param(vy1) := is_param(v1); 192 is_stk(vy1) := is_stk(v1); 193 is_temp(vy1) := is_temp(v1); 194 is_internal(vy1) := is_internal(v1); 195 is_const(vy1) := is_const(v1); 196 197 split_vars{v2} with:= (vy2 := vy1); 198 split_from(vy2) := v2; 199 200 if v1 in variables then variables with:= vy1; end if; 201 if v1 in uservars then uservars with:= vy1; end if; 202 203 return vy2; 204 205 206 end procedure add_split; 207 208 209 210 211 procedure can_conv(fm1, fm2); 212$ 213$ this routine computes a flag indicating whether a value with form fm1 214$ can be converted to the form fm2. 215$ 216 repr 217 fm1, fm2: elmt forms; 218 tp1, tp2: elmt base_ft_types; 219 simtp1, simtp2: string; 220 comps1, comps2: tuple(elmt forms); 221 cfm1: elmt forms; 222 j: integer; 223 end repr; 224 225 tp1 := ft_type(fm1); $ get the basic types of the forms 226 tp2 := ft_type(fm2); 227 228 simtp1 := simple_type(tp1); 229 simtp2 := simple_type(tp2); 230 231 if simtp2 = 'gen' then $ fm2 is a type general 232 return true; 233 234 elseif simtp2 = 'elmt' then 235 return can_conv(fm1, ft_elmt(ft_base(fm2))); 236 237 elseif simtp1 = 'elmt' then 238 return can_conv(ft_elmt(ft_base(fm1)), fm2); 239 240 elseif simtp1 = 'gen' then 241 return false; 242 243$ at this point, both fm1 and fm2 should have the same basic type, 244$ if conversion is to succeed, except for the types 'map' and 'set'. 245 elseif simtp1 /= simtp2 then 246 if { simtp1, simtp2 } /= { 'map', 'set' } then 247 return false; 248 end if; 249 end if; 250 251 case simtp1 of 252 253 ('set', 'map'): 254 $ nb. a conversion from a multi-valued map to a single-valued 255 $ map is not save, and consequently we assume here that is is 256 $ not possible. 257 if ft_mapc(fm2) = ft_smap and ft_mapc(fm1) /= ft_smap then 258 return false; 259 end if; 260 261 $ note that the conversion from set to map is possible iff 262 $ the conversion between their elements is possible 263 return can_conv(ft_elmt(fm1), ft_elmt(fm2)); 264 265 ('tuple'): 266 if tp1 = f_mtuple then 267 if tp2 = f_mtuple then 268 if #(comps1 := ft_elmt(fm1)) <= 269 #(comps2 := ft_elmt(fm2)) then 270$$$ ???? note that we assume that it is possible to convert e.g. 271$$$ ???? [ int ] to [ int, int ] 272 return (forall cfm1 = comps1(j) | 273 can_conv(cfm1, comps2(j))); 274 else 275 return false; 276 end if; 277 else 278 return (forall cfm1 in ft_elmt(fm1) | 279 can_conv(cfm1, ft_elmt(fm2))); 280 end if; 281 282 elseif tp2 = f_mtuple then 283 return false; 284 else 285 return can_conv(ft_elmt(fm1), ft_elmt(fm2)); 286 end if; 287 288$$$ ???? for simplicity we assume below that any primitive type 289$$$ ???? can be converted to any other such type having the same 290$$$ ???? 'simple type'. this assumption is not true in general 291$$$ ???? and should be modified later. 292 else 293 return true; 294 295 end case; 296 297 end procedure can_conv; 298 299 1 .=member sfm15m 2 3 4 procedure sharp_form(fm, rpr, sc); 5$ 6$ this recursive procedure gets as input a form fm, a computed repr rpr, 7$ and a scope sc. it computes a new form nfm such that: 8$ 9$ 1. nfm is more specific than, or equivalent to fm 10$ 2. nfm is compatible with rpr, that is converting nfm into a repr (in 11$ the format used in our automatic data structure selection algo- 12$ rithm) would yield rpr. 13$ 3. nfm belongs to the form table of sc, or the form table of a contai- 14$ ning scope. 15$ 16$ note that nfm may point to other (component) forms. if these forms do 17$ not already exist, they are also generated, and placed into the appro- 18$ priate form table in a place preceding that of nfm. (this is compa- 19$ tible with the original structure of the form tables.) 20$ 21$ the code below is rather complex. moreover, it may not be completely 22$ accurate due to the following reasons: 23$ 24$ --- some important design decisions have so far been left out. these 25$ decisions concern the way in which we want to incorporate user- 26$ supplied information, and whether such information has always 27$ higher precedence over the reprs computed by the optimiser. a few 28$ issues of this sort are mentioned below, but the choices made 29$ below are rather arbitrary, and should be carefully reviewed. 30$ 31$ since user-supplied information is already incorporated in our type 32$ analysis phase, where it serves as an upper bound on computed types, 33$ we can assume here that rpr is always more specific than, or 34$ equivalent to fm, but only in the attributes considered in our 35$ data-type and structure computations. other attributes, currently 36$ ignored in those analyses, such as range of integers, short vs. long 37$ primitive types, etc. which are currently missing in rpr have to be 38$ taken from fm. 39$ 40 repr 41 fm: elmt forms; 42 rpr: elmt types; 43 sc: elmt base_scopes; 44 45 tp: elmt base_ft_types; 46 simtp: string; 47 grs: gross_type; 48 lcsp: elmt base_based_modes; 49 mptp: elmt base_ft_mapcs; 50 g: basic_type; 51 fmt: elmt forms; 52 ftp: elmt base_ft_types; 53 fm1, fm2, fm3: elmt forms; 54 comps: tuple(elmt types); 55 crpr: elmt types; 56 j: integer; 57 fmx: elmt forms; 58 b: tent_base; 59 bx: symbol; 60 bfm, dfm, ifm: elmt forms; 61 rstp, rtp: elmt types; 62 nfm_maps, nfmx_maps: tuple(general)(12); 63 nfm, nfmx: elmt forms; 64 end repr; 65 66 67 68 tp := ft_type(fm); 69 simtp := simple_type(tp); 70 grs := grosstyp(rpr); 71 lcsp := set_type(rpr); 72 mptp := map_type(rpr); 73$ if gross type is ambiguous, or is null (designating 'om'), return 74$ the original form. 75 if #grs /= 1 then return fm; end if; 76 77 g := arb grs; $ otherwise get the gross type 78 79$ first, if fm is a general form, we create 'nfm' only from 'rpr' 80 if simtp = 'gen' then 81 82 case g of 83 84 (t_int): return std_form(f_int); 85 86 (t_real): return std_form(f_real); 87 88 (t_string): return std_form(f_string); 89 90 (t_atom): return std_form(f_atom); 91 92 (t_set): 93$ first get the component form. then create a tuple 'nfm_maps' 94$ consisting of all relevant form attributes of nfm (see the macro 95$ 'form_maps' above). this tuple will then be used to construct 96$ nfm and insert it into the properform table. 97 fm1 := sharp_form(fm, comptyp(rpr), sc); 98 nfm_maps := []; 99 ftp := f_uset; 100 if lcsp /= om and ft_type(fm1) = f_elmt and 101 (bfm := ft_base(fm1)) /= om then 102 if lcsp = locl then 103 ftp := localtp(ftp); 104 ft_pos_ := (ft_num(bfm)(ftp) +:= 1); 105 ft_base_ := bfm; 106 elseif lcsp = remt then 107 ftp := remotetp(ftp); 108 ft_base_ := bfm; 109 end if; 110 end if; 111 ft_type_ := ftp; 112 ft_elmt_ := fm1; 113 114 (t_map): 115 116 fm1 := sharp_form(fm, domtyp(rpr), sc); 117 118 if mptp = ft_smap then 119 fm2 := sharp_form(fm, rangetyp(rpr), sc); 120 fm3 := sharp_form(fm, comptyp(rpr), sc); 121 122 elseif mptp = ft_mmap then 123 rstp := rangetyp(rpr); $ the range set type 124 if grosstyp(rstp) = grselt then 125 $ nb. the range set type of an mmap can not 126 $ have the form 'elmt b': transform it here 127 $ if it has. 128 rstp := elmt_mode(rstp(2)); 129 end if; 130 $ nb. the range set type rstp is a set type, and conse- 131 $ quently could be an mmap-type; in this case, the 132 $ element type of rstp is not necessarily the component 133 $ type of rstp. 134 rtp := elmt_type(rstp); 135 fm2 := sharp_form(fm, rstp, sc); 136 fm3 := sharp_form( 137 fm, 138 [ grstup, 139 [ domtyp(rpr), rtp ], 140 true ], 141 sc ); 142 143 else $ map_type(rpr) = ft_map: convert to ft_mmap 144 fm2 := sharp_form( 145 fm, 146 [ grsset, 147 rangetyp(rpr), 148 om, 149 om, 150 sprse ], 151 sc ); 152 fm3 := sharp_form(fm, comptyp(rpr), sc); 153 mptp := ft_mmap; 154 end if; 155 156 if lcsp = remt and ft_type(fm1) = f_elmt and 157 (bfm := ft_base(fm1)) /= om then 158$ first generate the embedded tuple form 159 nfm_maps := []; 160 ft_type_ := f_tuple; 161 ft_elmt_ := fm2; 162 fmt := newform(nfm_maps, sc); 163 end if; 164 nfm_maps := []; 165 ftp := f_umap; 166 if lcsp /= om and ft_type(fm1) = f_elmt and 167 (bfm := ft_base(fm1)) /= om then 168 if lcsp = locl then 169 ftp := localtp(ftp); 170 ft_pos_ := (ft_num(bfm)(ftp) +:= 1); 171 ft_base_ := bfm; 172 elseif lcsp = remt then 173 ftp := remotetp(ftp); 174 ft_base_ := bfm; 175 ft_tup_ := fmt; 176 end if; 177 end if; 178 ft_type_ := ftp; 179 ft_mapc_ := mptp; 180 ft_elmt_ := fm3; 181 ft_dom_ := fm1; 182 ft_im_ := fm2; 183 184 (t_tuple): 185 if is_knt(rpr) then 186 comps := comptyp(rpr); 187 nfm_maps := []; 188 ft_type_ := f_mtuple; smfh 33 ft_elmt_ := [ sharp_form(fm, crpr, sc): crpr in comps ]; 190 ft_lim_ := #comps; 194 195 else 197 nfm_maps := []; 198 ft_type_ := f_tuple; smfh 34 ft_elmt_ := sharp_form(fm, comptyp(rpr), sc); 200 ft_lim_ := 0; 201 end if; 202 203 $ the nelt field of tuples is maintained: 204 ft_neltok_ := 1; 205 206 (t_elmt): 207 if base_of(rpr) = om then insert_base(rpr(2), sc); end if; 208 nfm_maps := []; 209 ft_type_ := f_elmt; 210 ft_base_ := form(base_of(rpr)); 211 212 (t_om): return std_form(f_gen); 213 214 end case; 215 216 elseif simtp = 'elmt' and g /= t_elmt then 217 return sharp_form(ft_elmt(ft_base(fm)), rpr, sc); 218 219 else 220$ here fm is a non-general type. we begin by copying into 'nfm_maps' 221$ all attributes of fm. these will then be modified depending on the 222$ information available in 'rpr'. 223 nfm_maps := form_maps(fm); 224 225 case g of 226 227 (t_int, t_real, t_string, t_atom): 228 return fm; 229$$$ ???? what if fm is 'elmt b'? are we to override this user-supplied 230$$$ ???? repr and replace it by the primitive type itself? 231 232 (t_elmt): 233 if base_of(rpr) = om then insert_base(rpr(2), sc); end if; 234 if simtp = 'elmt' then 235 if ft_base_ = form(base_of(rpr)) then 236 return fm; 237 else 238 print('** form and repr have different bases', 239 fm, rpr); 240 end if; 241 else 242 nfm_maps := []; 243 ft_type_ := f_elmt; 244 ft_base_ := form(base_of(rpr)); 245 end if; 246 247 (t_set): 248 if simtp = 'map' then 249 $ since we assume rpr to be more specific than fm, 250 $ this case cannot occur. 251 print('form is a map but repr is a set', fm, rpr); 252 else 253 fm1 := sharp_form(ft_elmt_, comptyp(rpr), sc); 254 ftp := f_uset; 255 if ftp = ft_type_ and lcsp /= om and 256 ft_type(fm1) = f_elmt and 257 (bfm := ft_base(fm1)) /= om then 258 if lcsp = locl then 259 ftp := localtp(ftp); 260 ft_pos_ := (ft_num(bfm)(ftp) +:= 1); 261 ft_base_ := bfm; 262 elseif lcsp = remt then 263 ftp := remotetp(ftp); 264 ft_base_ := bfm; 265 end if; 266 end if; 267 ft_type_ := ftp; 268 ft_elmt_ := fm1; 269 end if; 270 271 (t_map): 272 273 if simtp = 'map' then 274 ft_dom_ := sharp_form(ft_dom_, domtyp(rpr), sc); 275 if ft_mapc_ = ft_smap then 276 if mptp = ft_mmap then 277 rstp := rangetyp(rpr); $ the range set type 278 if grosstyp(rstp) = grselt then 279 $ nb. the range set type of an mmap can not 280 $ have the form 'elmt b': transform it here 281 $ if it has. 282 rstp := elmt_mode(rstp(2)); 283 end if; 284 $ nb. the range set type rstp is a set type, and 285 $ consequently could be an mmap-type; in this 286 $ case, the element type of rstp is not neces- 287 $ sarily the component type of rstp. 288 rtp := elmt_type(rstp); 289 else 290 rtp := rangetyp(rpr); 291 end if; 292 ft_im_ := sharp_form(ft_im_, rtp, sc); 293 ft_elmt_ := sharp_form( 294 ft_elmt_, 295 [ grstup, 296 [ domtyp(rpr), rtp ], 297 true ], 298 sc ); 299 mptp := ft_smap; 300 301 elseif mptp = ft_mmap then 302 assert ft_mapc_ = ft_mmap; 303 rstp := rangetyp(rpr); $ the range set type 304 if grosstyp(rstp) = grselt then 305 $ nb. the range set type of an mmap can not 306 $ have the form 'elmt b': transform it here 307 $ if it has. 308 rstp := elmt_mode(rstp(2)); 309 end if; 310 $ nb. the range set type rstp is a set type, and 311 $ consequently could be an mmap-type; in this 312 $ case, the element type of rstp is not neces- 313 $ sarily the component type of rstp. 314 rtp := elmt_type(rstp); 315 ft_im_ := sharp_form(ft_im_, rstp, sc); 316 ft_elmt_ := sharp_form( 317 ft_elmt_, 318 [ grstup, 319 [ domtyp(rpr), rtp ], 320 true ], 321 sc ); 322 323 else $ map_type(rpr) = ft_smap or ft_map, 324 assert ft_mapc_ = ft_mmap; 325 $ so convert the map to an mmap. 326 ft_im_ := sharp_form( 327 ft_im_, 328 [ grsset, 329 rangetyp(rpr), 330 om, 331 om, 332 sprse ], 333 sc ); 334 ft_elmt_ := sharp_form(ft_elmt_, comptyp(rpr), sc); 335 mptp := ft_mmap; 336 end if; 337 338 else $ simtp = 'set' 339 fm1 := ft_elmt_; 340$ check whether the element form of fm is definitely a pair. if so 341$ we may improve the resulting domain and image forms of the new 342$ map, by taking the component forms of this pair into account. 343 if ft_type(fm1) = f_mtuple and ft_lim(fm1) = 2 then 344 dfm := ft_elmt(fm1)(1); 345 ifm := ft_elmt(fm1)(2); 346 else 347 dfm := ifm := std_form(f_gen); 348 end if; 349 350 ft_dom_ := sharp_form(dfm, domtyp(rpr), sc); 351 352 if mptp = ft_mmap then 353 rstp := rangetyp(rpr); $ the range set type 354 if grosstyp(rstp) = grselt then 355 $ nb. the range set type of an mmap can not 356 $ have the form 'elmt b': transform it here 357 $ if it has. 358 rstp := elmt_mode(rstp(2)); 359 end if; 360 $ nb. the range set type rstp is a set type, and 361 $ consequently could be an mmap-type; in this 362 $ case, the element type of rstp is not neces- 363 $ sarily the component type of rstp. 364 rtp := elmt_type(rstp); 365 else 366 rtp := rangetyp(rpr); 367 end if; 368 369 fmx := sharp_form(ifm, rtp, sc); $ range element form 370 371 nfmx_maps := []; 372 nfmx_maps(1) := f_uset; $ ft_type 373 nfmx_maps(3) := fmx; $ ft_elmt 374 375 ft_im_ := newform(nfmx_maps, sc); $ range set form 376 ft_elmt_ := sharp_form( 377 ft_elmt_, 378 [ grstup, 379 [ domtyp(rpr), rtp ], 380 true ], 381 sc ); 382 ft_type_ := f_umap; 383 ft_mapc_ := ft_mmap; 384 mptp := ft_mmap; 385 end if; 386 ftp := f_umap; 387 if ftp = ft_type_ then 388 if lcsp = remt and ft_type(ft_dom_) = f_elmt and 389 (bfm := ft_base(ft_dom_)) /= om then 390 $ first generate the embedded tuple form 391 nfmx_maps := []; 392 nfmx_maps(1) := f_tuple; 393 nfmx_maps(3) := ft_im_; 394 fmt := newform(nfmx_maps, sc); 395 end if; 396 397 if lcsp /= om and ft_type(ft_dom_) = f_elmt and 398 (bfm := ft_base(ft_dom_)) /= om then 399 if lcsp = locl then 400 ftp := localtp(ftp); 401 ft_pos_ := (ft_num(bfm)(ftp) +:= 1); 402 ft_base_ := bfm; 403 elseif lcsp = remt then 404 ftp := remotetp(ftp); 405 ft_base_ := bfm; 406 ft_tup_ := fmt; 407 end if; 408 end if; 409 ft_type_ := ftp; 410 ft_mapc_ := if ft_mapc_ = ft_smap then 411 ft_smap 412 elseif mptp = ft_smap then 413 ft_smap 414 else 415 ft_mmap 416 end; 417 end if; 418 419 (t_tuple): 420 if is_knt(rpr) then 421 comps := comptyp(rpr); 422 if tp = f_mtuple then 423 if # ft_elmt_ /= # comps then 424 print('fm and rpr are diff. length mtuples', 425 fm, rpr); 426 else 427 (forall j in [ 1..#ft_elmt_ ]) 428 fm1 := ft_elmt_(j); 429 fm2 := sharp_form(fm1, comps(j), sc); 430 ft_elmt_(j) := fm2; 431 end forall; 432 end if; 433 else 434 ft_type_ := f_mtuple; 435 ft_lim_ := # comps; 436 fm1 := ft_elmt_; 437 ft_elmt_ := []; 438 (forall crpr = comps(j)) 439 fm2 := sharp_form(fm1, crpr, sc); 440 ft_elmt_ with:= fm2; 441 end forall; 442 end if; 443 elseif tp = f_mtuple then 444 print('fm is a mtuple, rpr is a tuple', fm, rpr); 445 else 446 ft_elmt_ := sharp_form(ft_elmt_, comptyp(rpr), sc); 447 end if; 448 449 $ the nelt field of tuples is maintained: 450 ft_neltok_ := 1; 451 452 (t_om): return fm; 453 454 end case; 455 end if; 456 457$ at this point, the tuple 'nfm_maps' contains the attributes of the 458$ new form. using the map 'forminv', which maps each such tuple and 459$ a scope to a form in this scope having these attributes (if such 460$ a form exists there), we try to locate 'nfm' in 'sc' or in a 461$ containing scope. if unable to find it, we insert nfm into the 462$ form table of sc. 463 smfh 35 return newform(nfm_maps, sc); 467 468 end procedure sharp_form; 469 470 471 472 473 procedure newform(nfm_maps, sc); 474$ 475$ this routine checks, given the new form maps, whether such a form 476$ exists already in the scope sc. if it finds such a form, it returns 477$ it; otherwise it returns a new form. 478$ 479 repr 480 nfm_maps: tuple(general)(12); 481 sc, scx: elmt base_scopes; 482 nfm, f1: elmt forms; 483 end repr; 484 485 486 if exists scx in cont_scopes(sc) | 487 forminv{scx}(nfm_maps) /= om then 488 return forminv{scx}(nfm_maps); 489 end if; 490 491$ no form has been found 492 493 nfm := add_form(sc); 494 form_maps(nfm) := nfm_maps; 495 forminv{sc}(nfm_maps) := nfm; 496 497 (init f1 := nfm; while ft_type(f1) = f_elmt) 498 if ft_type(ft_base(f1)) = f_pbase then quit; end if; 499 f1 := ft_elmt(ft_base(f1)); 500 end init; 501 ft_deref(nfm) := f1; 502 503 return nfm; 504 505 506 end procedure newform; 507 508 509 510 511 procedure elmt_type(rpr); 512$ 513$ this procedure recursively builds the element form for a map repr. 514$ 515 repr 516 rpr: elmt types; 517 rstp: elmt types; 518 end repr; 519 520 521 if grosstyp(rpr) = grsmap and map_type(rpr) = ft_mmap then 522 523 rstp := rangetyp(rpr); $ the range set type 524 525 if grosstyp(rstp) = grselt then 526 $ nb. the range set type of an mmap can not have the 527 $ form 'elmt b': transform it here if it has. 528 rstp := elmt_mode(rstp(2)); 529 end if; 530 531 return [ grstup, [ domtyp(rpr), elmt_type(rstp) ], true ]; 532 533 else 534 return comptyp(rpr); 535 end if; 536 537 538 end procedure elmt_type; 539 540 541 procedure insert_base(b, sc); 542$ 543 repr 544 b: tent_base; 545 sc: elmt base_scopes; 546 fm, xfm: elmt forms; 547 v: symbol; 548 end repr; 549 550 551 sc := sym_sys; 552$$-- we really ought to use the maxscope of sc and bscope(b) here - 553$$-- at the moment, thoght, this is a little pointless, since we 554$$-- allocate all forms in the system scope 555$$-- should be make this change, recall that maxscope is local to 556$$-- auto_dstruct, hence needs to be exported/imported to get here. 557$$-- sc := maxscope([ sc, bscope(b) ]); 558 xfm := sharp_form(std_form(f_gen), elmt_mode(b), sc); 559 560 fm := add_form(sc); 561 ft_type(fm) := f_base; 562 ft_elmt(fm) := xfm; 563 ft_num(fm) := null_ft_num; 564 565 (while ft_type(xfm) = f_elmt) 566 if ft_type(ft_base(xfm)) = f_pbase then quit; end if; 567 xfm := ft_elmt(ft_base(xfm)); 568 end while; 569 ft_deref(fm) := xfm; 570 571 v := add_sym(sc); smfk 165 name(v) := 'opt#' + str(#basesymb + 1); is_internal(v) := om; 573 form(v) := fm; 574 is_read(v) := 1; is_write(v) := 1; 575 is_repr(v) := 1; is_store(v) := 1; 576 userbase(b) := v; basesymb(fm) := v; 577 578 579 end procedure insert_base; 580 581 582 drop 583 .comp, 584 interproc_fwd_analysis, 585 intraproc_fwd_analysis, 586 interproc_back_analysis, 587 intraproc_back_analysis, 588 fom, 589 xom; 590 591 592 end module setl_optimizer - conversion_analysis; 593 594 1 .=member copy18 2 3 4 module setl_optimizer - copy_optimization; 5$ 6$ this module performs copy optimization using an algorithm which 7$ approximates the more complicated value-flow analysis technique 8$ suggested by j. schwartz. it results in a simplified approach 9$ which is likely to be much more efficient than the value-flow 10$ approach, but which is still based on essentially the same value 11$ relationships computed by value-flow. having calculated these 12$ relationships, our algorithm can then determine which potentially 13$ destructive operations can be performed without having to copy by 14$ using live-dead information about the program variable occurrences. 15$ 16$ let us first describe briefly our modified approach to value-flow 17$ analysis. the purpose of that analysis is to determine, for each 18$ potentially destructive use vo, the set of all variable occurrences 19$ vo' whose value can contain the value of vo as a member (or be equal 20$ to this value). this set is significant in that the values of 21$ occurrences in it are the only values that can possibly contain 22$ pointers to the value that we wish to destroy at vo. using standard 23$ arguments taken from value-flow analysis as originally described, 24$ we can show that vo' and vo must be related in the following way: 25$ there exists an occurrence vo'' which precedes both vo and vo' 26$ in execution flow, and contains vo as a member. (vo'' may be vo or 27$ vo'). moreover, this membership relationship is realized by a series 28$ of operations which retrieve vo from vo'', and by another series of 29$ operations which, through retrievals, imbeddings and assignments, 30$ create vo' from vo''. in addition, the relationships between vo' and 31$ vo'' and between vo'' and vo' must be such that their composition 32$ still makes vo a member of vo' (or equal to it). 33$ 34$ using these facts, we carry out our analysis as follows: start at 35$ each destructive use vo; perform an upward propagation, tracking 36$ membership relationships from vo to other preceding occurrences, 37$ thereby finding all occurrences vo'' mentioned before. then by a 38$ downward propagation step find all occurrences vo' mentioned above by 39$ propagating membership relationships from the vo''s detected earlier, 40$ and ensuring that vo' can still contain vo. 41 42$ the following macros are used to check the memo functions, and invoke 43$ the routine if the function has not yet been evaluated 44 45 macro must_copy(r, fm); 46 (must_copy_memo(r, fm) ? must_copy_rout(r, fm)) 47 endm; 48 49 macro rel_comp(r, rx); 50 (rel_comp_memo(r, rx) ? rel_comp_rout(r, rx)) 51 endm; 52 53 macro inv(r); 54 (inv_memo(r) ? inv_rout(r)) 55 endm; 56 57 macro rel_inst(vo); 58 (rel_inst_memo(vo) ? rel_inst_rout(vo)) 59 endm; 60 61$ the following global variables are used in this module: 62 63 var 64 contain, $ maps each pot. destructive use to 65 $ set of containing occurrences 66 all_reldefs, $ definitions that can contain destructively 67 $ used values as their parts 68 globreldefs, $ the subset of all_reldefs which must be 69 $ analysed inter-procedurally 70 psoccsof, $ maps each destructive use to preceding 71 $ occurrences of the same variable, 72 $ which can set the share bit. 73 puoccsof, $ maps each destructive use to preceding 74 $ occurrences of the same variable that 75 $ definitely yields an unshared value for it. 76 destconsts, $ set of constant occurrences appearing in 77 $ value-flow. 78 79 must_copy_memo, $ memo map for must_copy predicate 80 rel_comp_memo, $ memo map for relationship composition 81 inv_memo, $ memo map for relationship inversion 82 rel_inst_memo, $ memo map for output-input relationship 83 $ in an instruction 84 85 livethru, $ maps each potentially destructive use n to the 86 $ set of all definitions which are live at n 87 destuses, $ destructive uses at which copy may be 88 $ required 89 potdestuses, $ set of all potentially destructive uses 90 dstuseini; $ maps each instruction i of potdestuses to a 91 $ potentially destructive use in i. 92 93$ the following constants are used in this module: 94 95$ the first group contains various strings which denote elementary 96$ membership relationships between setl objects (see procedure 97$ value_flow for more details). 98 99 const 100 elt = 'elmt', eltinv = 'elmt-1', 101 cmp = 'comp', cmpinv = 'comp-1', 102 ncmp1 = 'comp1', ncmp1inv = 'comp1-1', 103 ncmp2 = 'comp2', ncmp2inv = 'comp2-1', 104 ncmp3 = 'comp3', ncmp3inv = 'comp3-1', 105 ncmp4 = 'comp4', ncmp4inv = 'comp4-1', 106 ncmp5 = 'comp5', ncmp5inv = 'comp5-1', 107 ncmp6 = 'comp6', ncmp6inv = 'comp6-1', 108 ncmp7 = 'comp7', ncmp7inv = 'comp7-1', 109 ncmp8 = 'comp8', ncmp8inv = 'comp8-1', 110 ncmp9 = 'comp9', ncmp9inv = 'comp9-1', 111 rngmmap = 'mmap_range', rngmmapinv = 'mmap_range-1', 112 anymb = 'anymemb', 113 illeg = 'illegal'; 114 115 const 116 elem_memb_rel = 117 { elt, eltinv, cmp, cmpinv, 118 ncmp1, ncmp1inv, ncmp2, ncmp2inv, ncmp3, ncmp3inv, 119 ncmp4, ncmp4inv, ncmp5, ncmp5inv, ncmp6, ncmp6inv, 120 ncmp7, ncmp7inv, ncmp8, ncmp8inv, ncmp9, ncmp9inv, 121 rngmmap, rngmmapinv, 122 anymb, illeg }, 123 124 inverses = 125 { eltinv, cmpinv, 126 ncmp1inv, ncmp2inv, ncmp3inv, 127 ncmp4inv, ncmp5inv, ncmp6inv, 128 ncmp7inv, ncmp8inv, ncmp9inv, 129 rngmmapinv }, 130 131 ncmpis = 132 { ncmp1, ncmp2, ncmp3, 133 ncmp4, ncmp5, ncmp6, 134 ncmp7, ncmp8, ncmp9 }, 135 136 ncmpi_invs = 137 { ncmp1inv, ncmp2inv, ncmp3inv, 138 ncmp4inv, ncmp5inv, ncmp6inv, 139 ncmp7inv, ncmp8inv, ncmp9inv }, 140 141 ncmpofi = 142 [ ncmp1, ncmp2, ncmp3, 143 ncmp4, ncmp5, ncmp6, 144 ncmp7, ncmp8, ncmp9 ]; 145 146 147 const 148 rid = [], $ encodes the identity relationship 149 anymemb = [ anymb ], $ encodes anymember relationship 150 illegal = [ illeg ], $ encodes illegal relationship 151 nestlim = 10; $ maximal allowed nesting of 152 $ elementary relatonships 153 154 const 155 inverse = 156 { [ elt, eltinv ], [ eltinv, elt ], 157 [ cmp, cmpinv ], [ cmpinv, cmp ], 158 [ ncmp1, ncmp1inv ], [ ncmp1inv, ncmp1 ], 159 [ ncmp2, ncmp2inv ], [ ncmp2inv, ncmp2 ], 160 [ ncmp3, ncmp3inv ], [ ncmp3inv, ncmp3 ], 161 [ ncmp4, ncmp4inv ], [ ncmp4inv, ncmp4 ], 162 [ ncmp5, ncmp5inv ], [ ncmp5inv, ncmp5 ], 163 [ ncmp6, ncmp6inv ], [ ncmp6inv, ncmp6 ], 164 [ ncmp7, ncmp7inv ], [ ncmp7inv, ncmp7 ], 165 [ ncmp8, ncmp8inv ], [ ncmp8inv, ncmp8 ], 166 [ ncmp9, ncmp9inv ], [ ncmp9inv, ncmp9 ], 167 [ rngmmap, rngmmapinv ], [ rngmmapinv, rngmmap ], 168 [ anymb, anymb ], [ illeg, illeg ] }; 169 170 const 171 iofncmp = 172 { [ ncmp1, 1 ], [ ncmp2, 2 ], [ ncmp3, 3 ], 173 [ ncmp4, 4 ], [ ncmp5, 5 ], [ ncmp6, 6 ], 174 [ ncmp7, 7 ], [ ncmp8, 8 ], [ ncmp9, 9 ] }; 175 176 177 repr 178 base elem_memb_rel: string; 179 mode vf_elem_rel: elmt elem_memb_rel; 180$ 181$ for storage savings we keep all value flow relations in a base. 182$ 183 base vf_relations: tuple(vf_elem_rel); 184 mode vf_relation: elmt vf_relations; 185 186 elt, eltinv, cmp, cmpinv, 187 ncmp1, ncmp1inv, ncmp2, ncmp2inv, ncmp3, ncmp3inv, 188 ncmp4, ncmp4inv, ncmp5, ncmp5inv, ncmp6, ncmp6inv, 189 ncmp7, ncmp7inv, ncmp8, ncmp8inv, ncmp9, ncmp9inv, 190 rngmmap, rngmmapinv, anymb, illeg: 191 vf_elem_rel; 192 193 nestlim: integer; 194 inverses: local set(vf_elem_rel); 195 ncmpis: local set(vf_elem_rel); 196 ncmpi_invs: local set(vf_elem_rel); 197 ncmpofi: tuple(vf_elem_rel); 198 inverse: local smap(vf_elem_rel) vf_elem_rel; 199 iofncmp: local smap(vf_elem_rel) integer; 200 201 rid, anymemb: vf_relation; 202 illegal: vf_relation; 203 204 mode destuse: occurrence; 205 mode reldef: occurrence; 206 mode relocc: occurrence; 207 208 potdestuses: set(destuse); 209$$-- potdestuses: remote set(destuse); 210 dstuseini: sparse smap(elmt insts) destuse; 211 all_reldefs: set(reldef); 212$$-- all_reldefs: remote set(reldef); 213 globreldefs: sparse set(reldef); 214 puoccsof, psoccsof: mmap{destuse} set(relocc); 215$$-- puoccsof, psoccsof: remote mmap{destuse} set(relocc); 216 destconsts: sparse set(relocc); 217 218 must_copy_memo: smap(vf_relation, elmt forms) boolean; 219 inv_memo: smap(vf_relation) 220 vf_relation; 221 rel_comp_memo: smap(vf_relation, vf_relation) 222 vf_relation; 223 rel_inst_memo: smap(occurrence) 224 mmap{occurrence} 225 set(vf_relation); 226 227 contain: mmap{destuse} sparse set(relocc); 228 livethru: mmap{destuse} sparse set(reldef); 229$$-- contain: remote mmap{destuse} 230$$-- sparse set(relocc); 231$$-- livethru: remote mmap{destuse} 232$$-- sparse set(reldef); 233 destuses: set(destuse); 234$$-- destuses: remote set(destuse); 235 236 value_flow: procedure; 237 rel_comp_rout: procedure(vf_relation, vf_relation) 238 vf_relation; 239 inv_rout: procedure(vf_relation) vf_relation; 240 rel_inst_rout: procedure(relocc) 241 sparse mmap{relocc} 242 set(vf_relation); 243 must_copy_rout: procedure(vf_relation, elmt forms) 244 boolean; 245 246 live_dead_analysis: procedure; 247 .ofx_s: operator(df_map_syms, df_elmt_syms) 248 df_elmt_syms; 249 .ofx_o: operator(df_map_ocrs, df_elmt_ocrs) 250 df_elmt_ocrs; 251 block_flowmaps: procedure(routine, df_elmt_ocrs) 252 tuple( 253 remote smap(df_edge) df_map_ocrs, 254 remote smap(df_edge) df_map_syms, 255 smap(destuse) df_map_ocrs, 256 smap( 257 tuple(destuse, df_node) ) 258 df_map_syms 259 ); 260 .join_s: operator(df_map_syms, df_map_syms) 261 df_map_syms; 262 .join_o: operator(df_map_ocrs, df_map_ocrs) 263 df_map_ocrs; 264 copy_eliminate: procedure; 265 copy_share_improve: procedure; 266 end repr; 267 268 procedure copy_optimize; 269$ 270$ this is the master procedure for copy optimization. it consists of the 271$ following phases: 272$ 273$ 1. value relationship computation: for each potentially destructive 274$ use we compute the set of all variable occurrences which may 275$ contain the value being used at this point as a part. 276$ 277$ 2. live-dead analysis: we perform live-dead analysis using our 278$ bit-vectoring data flow analysis package. for efficiency, we carry 279$ out this analysis only for occurrences participating in the 280$ relationships built in phase 1 (in typical programs there should be 281$ relatively few such occurrences). 282$ 283$ 3. copy elimination: using the results of phases 1 and 2, we determine 284$ which potentially destructive uses definitely do not require 285$ copying. appropriate copy flags are set accordingly. 286$ 287$ 4. additional (relatively minor) optimizations can then be carried out 288$ using the bitvectoring approach outlined in cims rep #17. these 289$ include: suppression of share-bit setting, changing conditional 290$ copies to unconditional copies, and motion of copies out of loops. 291$ 292$$$ ???? the set potdestuses of potentially destructive uses is computed 293$$$ ???? here, but in practice this should probably be moved to an 294$$$ ???? earlier phase. 295$ smfi 282 init smfi 283 du_count := 0; $ counts the destructive uses. 296 repr 297 r: routine; 298 b: elmt blocks; 299 i: elmt insts; 300 opc: elmt base_opcodes; 301 dvo: occurrence; smfi 284 du_count: integer 0..65536; 302 entry_time: integer; 303 end repr; 304 305 title('cims.setl.' + prog_level + ' - copy optimization'); 306 printa(term_file, ' - copy optimisation'); 307 308 entry_time := time; 309$ 310$ compute the set potdestuses of all potentially destructive uses 311$ 312 potdestuses := {}; 313 dstuseini := {}; 314 (forall r in routs) 315 (for_block(b, r)) 316 (for_inst(i, b)) 317 opc := opcode(i); 318$ determine the argument potentially subject to destructive use 319$ ops_destuse1 (defined in the directory) contains opcodes potentially 320$ destroying their first ivariable (i.e. their second argument); 321$ ops_destuse3 and ops_destuse4 have similar meanings. 322 if opc in ops_destuse1 then 323 dvo := get_oi(i, 2); 324 elseif opc in ops_destuse3 then 325 if opc = q1_next 326 $ only the iteration value of a map is used 327 $ destructively: don't analyse remaining cases 328 and not is_fmap(ft_deref(form(args(i)(3)))) 329 and not ft_type(ft_deref(form(args(i)(3)))) = f_gen 330 then 331 continue; 332 end if; 333 dvo := get_oi(i, 4); 334 elseif opc in ops_destuse4 then 335 dvo := get_oi(i, 5); 336 else 337 continue; 338 end if; 339 340$ check the form of dvo to determine whether this is really a 341$ potentially destructive use (addition of integers is certainly not a 342$ destructive use, but addition of sets is). 343$ 344$ the test that we use is somewhat coarse, but should serve as a good 345$ approximation to find potentially destructive uses: simply test 346$ whether the form of the variable of dvo is a 'long' object, i.e. is a 347$ set, map or tuple (type general is also considered to be long). 348 smff 149 if not is_fprim(ft_deref(oi_form(dvo))) or smff 150 ft_type(ft_deref(oi_form(dvo))) = f_string then smff 151 if ft_type(oi_form(dvo)) = f_elmt or smff 152 ft_type(oi_form(dvo)) = f_string then 351 copy_flag(i) := copy_yes; smfi 285 du_count +:= 1; smff 153 messages{stmtof(i)}{'s'} with:= 353 [ 'an unconditional copy is required for ' 354 '"' + oi_name(dvo) + '".' ]; 355 else 356 potdestuses with:= dvo; $ use is potentially dest. 357 dstuseini(i) := dvo; smfi 286 du_count +:= 1; 358 if oi_sym(dvo) in uservars and 359 oi_sym(dvo) in globalvars then 360 globals_du{r} with:= dvo; 361 end if; 362 end if; 363 end if; 364 end; $ end for_inst; 365 end; $ end for_block; 366 end forall; smfi 287 smfi 288 if 'e' in dump_string then smfi 289 print('#all_oi =', #all_oi, smfi 290 ' #all_o =', #all_o, smfi 291 ' #all_i =', #all_i, smfi 292 ' #destructive uses =', du_count); smfi 293 print; smfi 294 end if; 367 368 value_flow; $ value relationship computation 369 live_dead_analysis; $ live-dead analysis 370 copy_eliminate; $ copy elimination phase 371 copy_share_improve; $ 'bookkeeping' optimisations 372 373 $ delete the static variables global to the module 374 contain := om; all_reldefs := om; psoccsof := om; 375 puoccsof := om; destconsts := om; rel_comp_memo := om; 376 inv_memo := om; rel_inst_memo := om; 377 livethru := om; destuses := om; potdestuses := om; 378 dstuseini := om; globreldefs := om; must_copy_memo := om; 379 380 statistics with:= time; $ save time for final statistics 381 382 if 'e' in dump_string then 383 print(time - entry_time, 'msecs spent in copy optimization'); 384 end if; 385 386 387 end procedure copy_optimize; 388 389 1 .=member vfl18a 2 3 4 procedure value_flow; 5$ 6$ this routine accomplishes phase 1 of our copy optimization. here, for 7$ each potentially destructive use vo, we compute the set of all 8$ variable occurrences vo' whose value can contain the value of vo as a 9$ part at the point of use. to this end we build up a collection of 10$ nodes having the form [vo1, r1], where vo1 is a variable occurrence 11$ and r is a value-relationship indicating how vo1 is related to some 12$ other value which is a part of vo1. 13$ 14$ r is represented as a tuple of elementary relationships, and stands 15$ for their composition. these elementary relationships are: 16$ 17$ elt - element of a set 18$ 19$ cmp - component of a tuple 20$ 21$ ncmpi - the i-th component of a known length tuple 22$ note that only the first 9 components of such a tuple 23$ are handled explicitly; the rest are treated as 'cmp' 24$ 25$ we begin our analysis with nodes [vo, []], where vo can be any 26$ potentially destructive use (i.e. belongs to potdestuses). then, 27$ tracing bfrom links and ouput-input links within instructions, we 28$ collect all other nodes which can be linked to such a vo by a series 29$ of forward links. then a forward propagation from these new nodes 30$ will yield all other nodes that can be linked to a potentially 31$ destructive use by a valid membership relationship. (see an 32$ accompanying report for explanation and justification of the facts 33$ used here). 34$ 35$ the algorithm is organized so that it does this tracing separately for 36$ each potentially destructive use. this simplifies the algorithm, at 37$ the slight expense of duplicating some value-flow propagations in case 38$ several potentially destructive uses are linked to the same preceding 39$ occurrences. 40$ 41$ this routine computes the gobal object contain, which maps each 42$ potentially destructive use to the set of all occurrences which 43$ may contain it as a member (or be equal to it). 44$ smfh 36 init smfi 295 sr1 := 0.0, $ summation of relationship lengths. smfi 296 sr2 := 0.0, $ summation of squares of relationship lengths. smfi 297 sr3 := 0.0; $ number of relationships. 45 repr 46 voxoccs: sparse set(relocc); 47 precsoccs, precuoccs: set(relocc); 48 allpoccs: mmap{destuse} set(relocc); 49$$-- allpoccs: remote mmap{destuse} set(relocc); 50 seenoccs: set(relocc); 51$$-- seenoccs: remote set(relocc); 52 workoccs: sparse set(relocc); 53 all_reloccs: set(relocc); 54$$-- all_reloccs: remote set(relocc); 55 globreloccs: sparse set(relocc); 56 57 relnodes: set( tuple(relocc, vf_relation) ); 58 uppile, downpile: set( tuple(relocc, vf_relation) ); 59 inst_props: set( tuple(relocc, vf_relation) ); 60 node, node1: tuple(relocc, vf_relation); 61 62 opc: elmt base_opcodes; 63 r, r1, rx, rx_inv: vf_relation; 64 v, vv: symbol; 65 vo, vo1: occurrence; 66 vox: destuse; 67 voy, voz: relocc; 68 rin: sparse mmap{relocc} set(vf_relation); 69 x: destuse; 70 y: sparse set(relocc); smfc 635 z: relocc; smfi 298 sr1, sr2, sr3: real; smfi 299 tr1, tr2, tr3: real; smfi 300 mu, sigma: real; smfi 301 m1, s1: integer; 71 entry_time: integer; 72 end repr; 73 74 entry_time := time; 75 76$ begin by initializing the various memo maps and propagation related 77$ objects. 78 79 80$ the four following maps are used to store the results computed by 81$ the routines must_copy, rel_comp, rel_inst and inv, respectively, 82$ in order to avoid recomputation. 83$ 84$ must_copy examines a form under a value relationship to determine 85$ whether a value under this relationship is an element of a base. 86$ in such a case we must copy unconditionally the value before using 87$ it destructively, as bases are considered live throughout a program. 88$ 89$ rel_comp computes the composition of two membership relationships 90$ (in practice, the second one will always be a relationship between 91$ output and input arguments of an instruction); 92 93$ rel_inst computes the membership/containment relationships between 94$ the arguments of a given instruction; 95 96$ inv computes the inverse of a relationship computed by rel_inst. 97 98 must_copy_memo := {}; 99 rel_comp_memo := {}; 100 rel_inst_memo := {}; 101 inv_memo := {}; 102 103$ in order to track all occurrences and membership relationships that a 104$ potentially destructive use can be linked to, we can think of the 105$ algorithm as implicitly constructing two (virtual) graphs upgraph and 106$ downgraph, whose nodes are pairs of the form 107$ [ occurrence, relationship ], 108$ and whose edges constitute direct links between these pairs. a direct 109$ link between the pairs [ vo1, r1 ] and [ vo2, r2 ] exists either when 110$ vo1 and vo2 are linked by bfrom (and r1 = r2), or when vo1 and vo2 111$ are an input and an output occurrences within the same instruction. 112$ upgraph would consist of all such links collected during the first 113$ (upward) propagation phase of the algorithm, while downgraph would 114$ consist of links collected during the second, downward propagation 115$ phase. 116 117$ initialize the output map of this analysis, as defined above. 118 contain := {}; 119 120 all_reloccs := {}; $ relevant occurrences collected by value flow 121 globreloccs := {}; $ subset of all_reloccs to be analysed globally 122 123 psoccsof := {}; $ maps each destructive use to preceding 124 $ occurrences of the same variable 125 puoccsof := {}; 126 allpoccs := {}; $ set of all occurrences preceding some 127 $ destructive use 128$ process each potentially destructive use 129 (forall vox in potdestuses) 130 precsoccs := {}; $ preceding occurrences which can set 131 $ the share bit 132 precuoccs := {}; $ preceding occurrences yielding 133 $ unshared values. 134 workoccs := {vox}; $ workpile of preceding occurrences 135 seenoccs := {}; $ occurrences allready seen 136 (while workoccs /= {}) 137 voy from workoccs; 138 seenoccs with:= voy; 139 (forall voz in bfrom{voy} | voz notin seenoccs) smfd 842 opc := oi_op(voz); 141 if (case argno(voz) of 142 (1): opc in ops_share1, 143 (2): opc in ops_share2, 144 (3): opc in ops_share3, 145 (4): opc in ops_share4 146 else false 147 end) 148 then 149 if opc = q1_asn and 150 argno(voz) = 1 and 151 is_const(vv := arg2(instno(voz))) = 1 and 152 (value(vv) = {} or value(vv) = []) then 153 precuoccs with:= voz; 154 else 155 precsoccs with:= voz; 156 end if; 157 158 elseif is_ovar(voz) and opc notin ops_nonewval then 159 precuoccs with:= voz; 160 161 else 162 workoccs with:= voz; 163 end if; 164 end forall; 165 end while; 166 psoccsof{vox} := precsoccs; 167 puoccsof{vox} := precuoccs; 168 allpoccs{vox} := precsoccs + precuoccs; 169 end forall; 170 171 172 (forall [ voy, vox ] in allpoccs) smfc 636 if 'p' in dump_string and 'z' in dump_string then smfc 637 print('start new batch at ', time); smfc 638 print(' destructive use:', oi_str(voy), '=', oi_name(voy)); smfc 639 print(' prec occurrence:', oi_str(vox), '=', oi_name(vox)); smfc 640 end if; 173 174 $ start the propagation at potentially destructive use vox 175 node := [ vox, rid ]; 176 $ initialize relnodes, uppile and downpile. 177 $ relnodes is the set of all nodes encountered so far 178 relnodes := { node }; 179$ the elements of uppile are pairs of the form 180$ [ occ, rel ] 181$ indicating a desired propagation from occ and rel to 182$ preceding occurrences. if occ is an ovariable, then rel should 183$ be propagated to ivariables of the instruction containing occ; 184$ if occ is an ivariable then rel should be propagated to previous 185$ occurrences of the same variable linked to occ by bfrom. 186 187 uppile := { node }; 188 189$ downpile is a similar workpile for downward propagation; it consists 190$ of pairs like those appearing in uppile. each such pair 191$ [ occ, rel ] indicates a desired propagation from occ in the 192$ direction of execution flow. such a propagation would be 193$ to other occurrences of the same varible linked to occ by ffrom, 194$ and, if occ is an ivariable, also to the ovariable of its 195$ instruction. 196$ 197$ in order to avoid that extra propagation in cases where the 198$ reverse propagation (from ovariable to ivariable) has already 199$ taken place during the upward propagation phase, we maintain an 200$ additional set inst_props of all such pairs [ occ, rel ] where 201$ occ is an ivariable and rel is relationship that has been propagated 202$ to occ from its ovariable. see below for the use of that set. 203 204 inst_props := {}; 205 206 downpile := { node }; 207$ 208$ carry out upward propagation 209$ 210 (while uppile /= {}) 211 smfc 641 node from uppile; smfc 642 [ vo, r ] := node; smfc 643 smfc 644 if 'p' in dump_string and 'z' in dump_string then smfc 645 print(' uppile:', oi_str(vox), r, oi_str(vo), '=', smfc 646 oi_name(vo)); smfc 647 end if; 213 214 if is_ivar(vo) then 215 216 (forall vo1 in bfrom{vo} | vo1 /= vo) 217 if (node := [ vo1, r ]) notin relnodes then smfc 648 $ new node for propagation 219 relnodes with:= node; 220 221 uppile with:= node; 222 downpile with:= node; 223 end if; 224 end forall; smfc 649 225 elseif is_ovar(vo) then 226 smfc 650 $ call a memo routine to obtain the output-input value smfc 651 $ relationships for the instruction of vo. 229 rin := rel_inst(vo); 230 231 (forall [ vo1, rx ] in rin) $ vo1 is ivar of this inst smfc 652 $ call another memo routine to compose r with rx. 233 r1 := rel_comp(r, rx); 234 235 if r1 /= illegal then smfc 653 $ composition yields a valid membership smfc 654 $ relationship r1. 237 node := [ vo1, r1 ]; smfc 655 smfc 656 $ check if node has not been traced yet. 239 if node notin relnodes then $ new node for pro 240 relnodes with:= node; 241 242$ put node in inst_props to record the fact that propagation from vo 243$ and r to vo1 and r1 has already taken place. note that we assume 244$ here that this propagation is one-to-one, so that in particular it 245$ is fully determined by . this is generally the case, except 246$ for cases involving ambiguous memb relationships (see comments in 247$ rel_comp), or cases where the maximal nesting level has been exceeded. 248$ in this cases backward propagation from vo1 and r1 to vo might result 249$ in a relationship r' which is always an overestimation of r. thus 250$ the use of inst_props can help us in avoiding that overestimation. 251 252 inst_props with:= node; 253 254 uppile with:= node; 255 downpile with:= node; 256 end if; 257 258 end if; 259 end forall; 260 261 end if; 262 end while; 263$ 264$ now perform downward propagation 265$ 266$ the logic of the downward propagation step is quite similar to that 267$ of the upward propagation step; see that step for more comments. 269$ 270 (while downpile /= {}) 271 node from downpile; 272 [ vo, r ] := node; smfc 657 smfc 658 if 'p' in dump_string and 'z' in dump_string then smfc 659 print(' downpile:', oi_str(vox), r, oi_str(vo), '=', smfc 660 oi_name(vo)); smfc 661 end if; 273 if is_ivar(vo) and node notin inst_props then 274 opc := oi_op(vo); $ get the opcode 275 if opc in ops_ovar then 276$ vo's instruction has an ovariable. propagate relationship to it 277 vo1 := get_ovar(vo); $ get the o-variable 278 inst_props with:= node; 279 rin := rel_inst(vo1); 280 (forall rx in rin{vo}) 281$ compose r with the inverse of rx to get the relation at vo1 282 rx_inv := inv(rx); 283 r1 := rel_comp(r, rx_inv); 284 if r1 /= illegal then 285 node1 := [ vo1, r1 ]; 286 if node1 notin relnodes then 287 relnodes with:= node1; 288 downpile with:= node1; 289 end if; 290 end if; 291 end forall; 292 end if; 293 end if; 294 295 $ in any case, propagate forward via ffrom 296 (forall vo1 in ffrom{vo}) 297 if (node := [ vo1, r ]) notin relnodes then 298 relnodes with:= node; 299 downpile with:= node; 300 end if; 301 end forall; 302 end while; 303 smfi 302 if 'e' in dump_string then smfi 303 tr1 := tr2 := tr3 := 0.0; smfi 304 (forall [ vo, r ] in relnodes) smfi 305 sr1 +:= float(#r); sr2 +:= float(#r) ** 2; sr3 +:= 1.0; smfi 306 tr1 +:= float(#r); tr2 +:= float(#r) ** 2; tr3 +:= 1.0; smfi 307 end forall; smfi 308 mu := tr1 / tr3; smfi 309 sigma := sqrt( (tr2 - tr1**2/tr3) / tr3 ); smfi 310 m1 := fix(mu * 1000.0); s1 := fix(sigma * 1000.0); smfi 311 print('#rels =', fix tr3, '=', #relnodes, smfi 312 ' min =', min/[ #r : [ -, r ] in relnodes ], smfi 313 ' max =', max/[ #r : [ -, r ] in relnodes ], smfi 314 ' mean =', str(m1 div 1000)+'.'+str(m1 mod 1000), smfi 315 ' sdev =', str(s1 div 1000)+'.'+str(s1 mod 1000), smfi 316 ' cv =', if mu /= 0.0 then 100.0*sigma/mu else 0 end, '%' smfi 317 ); smfi 318 end if; 304 smfh 40 if exists [ vo, r ] in relnodes | smfh 41 must_copy(r, oi_form(vo)) then 306 copy_flag(instno(voy)) := copy_yes; smfc 662 messages{stmtof(instno(voy))}{'s'} with:= 308 [ 'an unconditional copy is required for ' 309 '"' + oi_name(voy) + '".' ]; 310 potdestuses less:= voy; 311 dstuseini(instno(voy)) := om; 312 else 313 voxoccs := domain relnodes; 314 contain{vox} := voxoccs; 315 all_reloccs +:= voxoccs; 316 globreloccs +:= { vo in voxoccs | 317 (v := oi_sym(vo)) in globalvars 318 or is_param(v)=1 319 or scope(v) /= scope(oi_sym(vox)) }; 320 end if; 321 end forall; 322 323 if 'p' in dump_string then smfc 663 prints('contain =', smfc 664 [ [ oi_str(x), { oi_str(z) : z in y } ] : y = contain{x} ] smfc 665 ); 325 end if; 326 327 $ reldefs is the set of relevant definitions collected by this phase 328 all_reldefs := { vo in all_reloccs | is_ovar(vo) }; 329 330 $ globreldefs is the subset of reldefs to be analysed globally 331 globreldefs := { vo in all_reldefs | vo in globreloccs }; 332 333 $ destconsts is the set of constants involved in the analysis 334 destconsts := { vo in all_reloccs | 335 is_const(v := oi_sym(vo))=1 and 336 value(v) /= {} and value(v) /= [] }; 337 338 $ free some space to be garbage collected 339 rel_comp_memo := om; 340 inv_memo := om; 341 rel_inst_memo := om; 342 must_copy_memo := om; 343 344 if 'e' in dump_string then smfi 319 if sr3 > 0.0 then smfi 320 print; smfi 321 mu := sr1 / sr3; smfi 322 sigma := sqrt( (sr2 - sr1**2/sr3) / sr3 ); smfi 323 m1 := fix(mu * 1000.0); s1 := fix(sigma * 1000.0); smfi 324 print('#rels =', fix sr3, smfi 325 ' mean =', str(m1 div 1000)+'.'+str(m1 mod 1000), smfi 326 ' sdev =', str(s1 div 1000)+'.'+str(s1 mod 1000), smfi 327 ' cv =', if mu /= 0.0 then 100.0*sigma/mu else 0 end, '%' smfi 328 ); smfi 329 end if; smfh 46 print; 345 print(time - entry_time, 'msecs for value flow'); 346 end if; 347 348 end procedure value_flow; 349 350 351 352 353 procedure rel_comp_rout(r, rx); 354$ 355$ this routine computes the composition of the membership relationship 356$ r, given as a tuple of elementary membership relationships (such as 357$ elt, cmp, ncmpi etc.) with the relationship rx, which designates a 358$ value relationship between output and input arguments of an instruc- 359$ tion. rx is also given as a tuple, but each component of rx can 360$ designate either a membership relationship or its inverse (i.e. a 361$ containment relationship such as eltinv, cmpinv, ncmpiinv, etc.). 363$ 364$ to ensure convergence of our algorithm we impose a nesting level 365$ limit nestlim on membership relatioships. any relation r containing 366$ more than nestlim elementary components is crudely represented as the 367$ symbol anymemb, indicating at least nestlim levels of elementary 368$ memberships. anymemb relationships do not change by composition with 369$ any other relationship. this is an overestimation which is safe in 370$ the sense that it cannot cause a possible membership relationship to 371$ disappear (although it can add spurious membership relationships). 373$ 374$ in some cases of ambiguous types (e.g. 'a set or a tuple'), rx may 375$ contain ambiguous membership relationships, (e.g. 'an element of a 376$ set or a component of a tuple'). in these cases we keep the set of 377$ all valid membership relationships in a given relationship rx. for 378$ example, in 'y := f(x)' where f can be either a map or a tuple, y can 379$ be either a cmp of f or a ncmp2 of an elt of f. in such cases we 380$ represent the relationship as n consecutive memb's, where n is the 381$ maximal possible number of nested elementary relationships that can 383$ constitute that relationship (2 in the above example). 384$ 385$ a problem can arise in the above treatment of ambiguous level of 386$ membership. consider for example a relationship r having the form 387$ elt.cmp; suppose that we compose r with a relationship 388$ rx = memb.memb which indicates either one or two possible levels of 389$ membership. this will produce the relationship elt.cmp.memb.memb; 390$ now suppose that we want to compose it first with the inverse of rx 391$ (a relationship representing either one or two levels of containment) 392$ and then with the inverse of r. to play safe, we must represent the 394$ inverse of rx as memb-1 (using the minimal possible level of 395$ containment, for otherwise we run into the risk of removing too many 396$ levels of membership, which may make us treat certain valid membership 397$ relationships as invalid), so that the first composition, if treated 398$ casually, will yield elt.cmp.memb, and then the second composition of 399$ this relationship with cmp-1.elt-1 will be found to be illegal, due 400$ to a failure to match direct relationships correctly. 402$ 403$ the solution that we will use is as follows: whenever a relationship 404$ is composed with memb or memb-1, we first convert it by replacing each 405$ component by memb, and then performing the composition using standard 406$ rules. thus, in the above example the composition of r with rx will 407$ yield memb.memb.memb.memb and the next two compositions can then be 408$ carried out safely resulting in the relationship memb. 410$ 411$ to make this routine more efficient, we maintain a 'memo' map which 412$ records all previously computed compositions. 413$ 414 repr 415 r, r1, rx: vf_relation; 416 rxi, last: vf_elem_rel; 417 i, ir1: integer; 418 end repr; 419 420 r1 := r; 421 (forall rxi = rx(i)) 422 if rxi notin inverses then 423 if # r1 = nestlim then $ convert to anymemb repr 424 r1 := anymemb; 425 elseif r1 = anymemb then 426$ r already has the anymemb representation; leave it unchanged 427 quit forall; 428 else $ normal case 429 r1 with:= rxi; $ append new membership to r1 430 end if; 431 else $ a containment relationship 432 if r1 = [] then 433 r1 := illegal; 434 quit forall; 435 else $ normal case 436$ remove the last elementary subrelationship from r1 and check that it 437$ matches the current subrelationship of rx. (we allow matching of 438$ memb with any relationship, and also matching of cmp with any ncmpi.) 439 440 last frome r1; 441 if inverse(last) /= rxi and 442 (last /= cmp or rxi notin ncmpi_invs) and 443 (rxi /= cmpinv or last notin ncmpis) then 444 r1 := illegal; 445 quit forall; 446 end if; 447 end if; 448 end if; 449 end forall; 450 451 return rel_comp_memo(r, rx) := r1; 452 453 end procedure rel_comp_rout; 454 455 456 457 458 procedure inv_rout(rx); 459$ 460$ compute the inverse of rx. this simply reverses the components of rx 461$ and the flag bits which these components may contain. 463$ 464 repr 465 rx, ry: vf_relation; 466 i: integer; 467 end repr; 468 469 470 ry := [ inverse(rx(i)) : i in [ #rx, #rx-1..1 ] ]; 471 472 return inv_memo(rx) := ry; 473 474 end procedure inv_rout; 475 476 1 .=member rln18b 2 3 4 procedure rel_inst_rout(vo); 5$ 6$ this routine computes the output-input relationships for an 7$ instruction whose output occurrence is vo. it returns a map rin 8$ which maps each input occurrence iv in that instruction to the 9$ relationship of the value of vo to the value of iv, given in the form 10$ described in the comments associated with the routine rel_comp. 12$ 13$ for efficiency, this routine uses a memo map to record the 14$ relationships computed for previously processed instructions. 15$ 16$ the following macros are used in this procedure: 17$ 22 macro maybe_tup(fm); (is_ftup(fm) or ft_type(fm) = f_gen) endm; 23 macro maybe_set(fm); (is_fset(fm) or ft_type(fm) = f_gen) endm; 24 macro maybe_map(fm); (is_fmap(fm) or ft_type(fm) = f_gen) endm; 25 26 repr 27 vo, voj: occurrence; 28 rin: mmap{occurrence} 29 set(vf_relation); 30 inst: elmt insts; 31 opc: elmt base_opcodes; 32 argsi: tuple(symbol); 33 ivs: tuple(occurrence); 34 iv1, iv2, iv3, iv4: occurrence; 35 a1, a2, a3, a4: symbol; 36 fm: elmt forms; 37 ncmpi, ncmpj: vf_elem_rel; 38 j: integer; 39 end repr; 40 41 42 rin := {}; 43 inst := instno(vo); 44 opc := opcode(inst); 45 argsi := args(inst); 46 [ a1, a2, a3, a4 ] := argsi; 47 48 $ nb. none of the operators of interest in this routine are in 49 $ ops_ivar (ie. operators whose first argument is an i-variable): 50 $ hence the following loop has a lower bound of two. 51 ivs := [ get_oi(inst, j) : j in [ 2..#argsi ] ]; 52 [ iv1, iv2, iv3, iv4 ] := ivs; 53 54 case opc of 55 56 (q1_asn, q1_argin): $ vo = iv1 57 rin{iv1} with:= rid; 58 59 (q1_argout): $ vo = iv3 60 rin{iv3} with:= rid; 61 62 (q1_arb, q1_rand): 63 $ vo elt iv1 if iv1 is a set or map; 64 $ vo cmp iv1 if a tuple 65 fm := ft_deref(form(a2)); 66 if maybe_set(fm) then 67 rin{iv1} with:= [ elt ]; 68 end if; 69 70 if maybe_tup(fm) then 71 rin{iv1} with:= [ cmp ]; 72 end if; 73 74 (q1_with): 75 $ vo elt-1.elt iv1 if vo is a set or a map; i.e. vo contain 76 $ elements that are also elements of iv1. also vo elt-1 iv2. 77 $ similar relationships apply for tuples and ambiguous types. 78 fm := ft_deref(form(a1)); 79 if maybe_set(fm) then 80 rin{iv1} with:= [ eltinv, elt ]; 81 rin{iv2} with:= [ eltinv ]; 82 end if; 83 84 if maybe_tup(fm) then 85 rin{iv1} with:= [ cmpinv, cmp ]; 86 rin{iv2} with:= [ cmpinv ]; 87 end if; 88 89 (q1_of): 90 $ vo cmp iv1, if iv1 is a tuple; 91 $ vo ncmpi iv1, if in addition the value of iv2 = i is known 92 $ and less than 9 93 $ vo ncmp2.elt iv1, if iv1 is a map; 94 fm := ft_deref(form(a2)); 95 if maybe_tup(fm) then 96 if ft_type(fm) = f_mtuple and 97 is_const(oi_sym(iv2)) = 1 and 98 ft_type(oi_form(iv2)) = f_sint and 99 (ncmpi := ncmpofi(oi_val(iv2))) /= om then 100 rin{iv1} with:= [ ncmpi ]; 101 else 102 rin{iv1} with:= [ cmp ]; 103 end if; 104 end if; 105 106 if maybe_set(fm) then 107 rin{iv1} with:= [ ncmp2, elt ]; 108 end if; 109 110 (q1_sof): 111 $ vo cmp-1 (or ncmpi-1) iv2, and 112 $ vo cmp-1.cmp iv3, if vo is a tuple; 113 $ vo elt-1.ncmp1-1 iv1, 114 $ vo elt-1.ncmp2-1 iv2, and 115 $ vo elt-1.elt iv3, if vo is a map; 116 fm := ft_deref(form(a1)); 117 if maybe_tup(fm) then 118 if ft_type(fm) = f_mtuple and 119 is_const(oi_sym(iv1)) = 1 and 120 ft_type(oi_form(iv1)) = f_sint and 121 (ncmpi := ncmpofi(oi_val(iv1))) /= om then 122 rin{iv2} with:= [ inverse(ncmpi) ]; 123 else 124 rin{iv2} with:= [ cmpinv ]; 125 end if; 126 rin{iv3} with:= [ cmpinv, cmp ]; 127 end if; 128 129 if maybe_set(fm) then 130 rin{iv1} with:= [ eltinv, ncmp1inv ]; 131 rin{iv2} with:= [ eltinv, ncmp2inv ]; 132 rin{iv3} with:= [ eltinv, elt ]; 133 end if; 134 135 (q1_ofa): 136 $ vo elt-1.ncmp2.elt iv1, if iv1 is a map 137 fm := ft_deref(form(a2)); 138 if maybe_set(fm) then 139 rin{iv1} := { [ eltinv, ncmp2, elt ], [ rngmmap ] }; 140 end if; 141 142 (q1_sofa): 143 $ vo elt-1.ncmp2-1.elt iv2, 144 $ vo elt-1.ncmp1-1 iv1, and 145 $ vo elt-1.elt iv3, if vo is a map; 146 fm := ft_deref(form(a1)); 147 if maybe_set(fm) then 148 rin{iv1} with:= [ eltinv, ncmp1inv ]; 149 rin{iv2} := { [ eltinv, ncmp2inv, elt ], [ rngmmapinv ] }; 150 rin{iv3} with:= [ eltinv, elt ]; 151 end if; 152 153 (q1_add): 154 $ vo elt-1.elt iv1 and iv2, if they are sets or maps; 155 $ vo cmp-1.cmp iv1 and iv2, if tuples. 156 fm := ft_deref(form(a1)); 157 if maybe_set(fm) then 158 rin{iv1} with:= [ eltinv, elt ]; 159 rin{iv2} with:= [ eltinv, elt ]; 160 end if; 161 162 if maybe_tup(fm) then 163 rin{iv1} with:= [ cmpinv, cmp ]; 164 rin{iv2} with:= [ cmpinv, cmp ]; 165 end if; 166 167 (q1_sub): 168 $ same as q1_add for sets and maps 169 fm := ft_deref(form(a1)); 170 if maybe_set(fm) then 171 rin{iv1} with:= [ eltinv, elt ]; 172 end if; 173 174 (q1_mult): 175 $ vo elt-1.elt iv1 and iv2, if they are sets or maps 176 $ for vo a tuple, interpret as tuple repitition. the 177 $ vo cmp-1.cmp iv can hold for any ivariable iv which can be 178 $ a tuple. 179 fm := ft_deref(form(a1)); 180 if maybe_set(fm) then 181 rin{iv1} with:= [ eltinv, elt ]; 182 rin{iv2} with:= [ eltinv, elt ]; 183 end if; 184 185 if maybe_tup(fm) then 186 if maybe_tup(ft_deref(form(a2))) then 187 rin{iv1} with:= [ cmpinv, cmp ]; 188 end if; 189 if maybe_tup(ft_deref(form(a3))) then 190 rin{iv2} with:= [ cmpinv, cmp ]; 191 end if; 192 end if; 193 194 (q1_less): 195 $ same as q1_with, but only for iv1 and only for sets/maps. 196 fm := ft_deref(form(a1)); 197 if maybe_set(fm) then 198 rin{iv1} with:= [ eltinv, elt ]; 199 end if; 200 201 (q1_lesse, q1_lessb): 202 $ same as q1_with, but only for iv1 and only for tuples. 203 rin{iv1} with:= [ cmpinv, cmp ]; 204 205 (q1_lessf): 206 $ same as q1_with, but only for iv1 and only for maps. 207 fm := ft_deref(form(a1)); 208 if maybe_set(fm) then 209 rin{iv1} with:= [ eltinv, elt ]; 210 end if; 211 212 (q1_pow): 213 $ vo elt-1.elt-1.elt iv1 214 rin{iv1} with:= [ eltinv, eltinv, elt ]; 215 216 (q1_npow): 217 $ vo elt-1.elt-1.elt iv2 218 rin{iv2} with:= [ eltinv, eltinv, elt ]; 219 220 (q1_dom): 221 $ vo elt-1.ncmp1.elt iv1 222 rin{iv1} with:= [ eltinv, ncmp1, elt ]; 223 224 (q1_range): 225 $ vo elt-1.ncmp2.elt iv1 226 rin{iv1} with:= [ eltinv, ncmp2, elt ]; 227 228 (q1_end, q1_subst): 229 $ vo cmp-1.cmp iv1, if they are tuples. 230 fm := ft_deref(form(a1)); 231 if maybe_tup(fm) then 232 rin{iv1} with:= [ cmpinv, cmp ]; 233 end if; 234 235 (q1_send): 236 $ vo cmp-1.cmp iv2 and iv3, if tuples 237 fm := ft_deref(form(a1)); 238 if maybe_tup(fm) then 239 rin{iv2} with:= [ cmpinv, cmp ]; 240 rin{iv3} with:= [ cmpinv, cmp ]; 241 end if; 242 243 (q1_ssubst): 244 $ same as in q1_send, for iv3 and iv4 245 fm := ft_deref(form(a1)); 246 if maybe_tup(fm) then 247 rin{iv3} with:= [ cmpinv, cmp ]; 248 rin{iv4} with:= [ cmpinv, cmp ]; 249 end if; 250 251 (q1_set): 252 $ vo elt-1 ivj, for j = 2,... 253 (forall j in [ 2..#argsi ]) 254 voj := get_oi(inst, j); 255 rin{voj} with:= [ eltinv ]; 256 end forall; 257 258 (q1_tup): 259 $ vo ncmpj-1 (or cmp-1) ivj, for j = 2,... 260 (forall j in [ 2..#argsi ]) 261 voj := get_oi(inst, j); 262 if (ncmpj := ncmpofi(j-1)) /= om then 263 rin{voj} with:= [ inverse(ncmpj) ]; 264 else 265 rin{voj} with:= [ cmpinv ]; 266 end if; 267 end forall; 268 269 (q1_set1): 270 $ vo elt-1 iv1 271 rin{iv1} with:= [ eltinv ]; 272 273 (q1_tup1): 274 $ vo cmp-1 iv1 275 rin{iv1} with:= [ cmpinv ]; 276 277 (q1_inext, q1_next): 278 $ vo.ncmpj-1.ncmpj.elt.iv2, j = 1,2, if iv2 is a map 279 $ vo.rid.iv3, if iv2 is a map and opc = q1_next (see below) 280 $ vo.elt.iv2, if iv2 is a set 281 $ vo.cmp.iv2, if iv2 is a tuple 282 $ in the current implementation, q1_inext creates a new pair 283 $ for the map elements, and q1_next uses this pair destruc- 284 $ tively in order to create the next map element. iv3 is an 285 $ additional argument introduced by the optimiser, and desi- 286 $ gnates the destructive use of the output variable. 287 fm := ft_deref(form(a3)); 288 if maybe_map(fm) then 289 rin{iv2} := { [ ncmp1inv, ncmp1, elt ], 290 [ ncmp2inv, ncmp2, elt ] }; 291 if opc = q1_next then 292 rin{iv3} with:= rid; 293 end if; 294 end if; 295 296 if maybe_set(fm) and not is_fmap(fm) then 297 rin{iv2} with:= [ elt ]; 298 end if; 299 300 if maybe_tup(fm) then 301 rin{iv2} with:= [ cmp ]; 302 end if; 303 304 (q1_inextd, q1_nextd): 305 $ vo ncmp1.elt iv2, if iv2 is a map 306 fm := ft_deref(form(a3)); 307 if maybe_set(fm) then 308 rin{iv2} with:= [ ncmp1, elt ]; 309 end if; 310 311 (q1_arbb, q1_arbe): 312 $ vo cmp iv1, if iv1 is a tuple 313 rin{iv1} with:= [ cmp ]; 314 315 end case; 316 317 return rel_inst_memo(vo) := rin; 318 319 320 end procedure rel_inst_rout; 321 322 323 324 325 procedure must_copy_rout(r, fm); 326$ 327$ this procedure examines the form fm under the value relationship r. 328$ an unconditional copy is required if the containment expressed in r 329$ involves a base element. this is a slight overestimate, since bases 330$ are asumed to be always live. it simplifies, however, our algoritm 331$ considerably. 332$ 333 repr 334 r: vf_relation; 335 fm: elmt forms; 336 337 r1: vf_relation; 338 xfm: elmt forms; 339 rx: vf_elem_rel; 340 end repr; 341 342 343 if ft_type(fm)= f_elmt and is_fprim(ft_deref(fm)) then 344 return must_copy_memo(r, fm) := false; 345 346 elseif ft_type(fm) = f_elmt then 347 return must_copy_memo(r, fm) := true; 348 349 elseif ft_type(fm) = f_gen then 350 return must_copy_memo(r, fm) := false; 351 352 elseif r = rid then 353 return must_copy_memo(r, fm) := false; 354 355 else 356 r1 := r; rx frome r1; 357 358 return 359 must_copy_memo(r, fm) := case rx of 360 361 (elt): if is_fset(fm) then 362 must_copy(r1, ft_elmt(fm)) 363 else 364 false 365 end, 366 367 (cmp): if ft_type(fm) = f_mtuple then 368 if exists xfm in ft_elmt(fm) | 369 must_copy(r1, xfm) then 370 true 371 else 372 false 373 end 374 elseif is_ftup(fm) then 375 must_copy(r1, ft_elmt(fm)) 376 else 377 false 378 end, 379 380 (arb ncmpis): 381 if ft_type(fm) = f_mtuple then 382 must_copy(r1, ft_elmt(fm)(iofncmp(rx))) 383 elseif is_ftup(fm) then 384 must_copy(r1, ft_elmt(fm)) 385 else 386 false 387 end, 388 389 (rngmmap): if is_fmap(fm) and ft_mapc(fm) = ft_mmap then 390 must_copy(r1, ft_im(fm)) 391 else 392 false 393 end, 394 395 (anymb): if ft_type(fm) = f_mtuple then 396 if exists xfm in ft_elmt(fm) | 397 must_copy(r, xfm) then 398 true 399 else 400 false 401 end 402 elseif is_ftup(fm) or is_fset(fm) then 403 must_copy(r, ft_elmt(fm)) 404 else 405 false 406 end 407 408 else 409 expr 410 print; 411 print('error in must_copy: r =', r, 'form =', fm); 412 stop; 413 yield false; 414 end 415 end; 416 end if; 417 418 end procedure must_copy_rout; 419 420 1 .=member lva18c 2 3 4 procedure live_dead_analysis; 5$ 6$ this routine performs live-dead analysis for all variable occurrences 7$ in reloccs. an occurrence vo of a variable v is said to be live at a 8$ given point n if there exists a path from vo to a use of v which 9$ passes through n and which is free of any modifications of v. 10$ 11$ for simplicity we break the analysis into two subphases: first, for 12$ each point n within a potentially destructive use, we compute the set 13$ of all occurrences in reloccs which can reach n (this is done in a way 14$ resembling our method of bfrom computation. for efficiency we 15$ restrict this analysis only to variable definitions belonging to 16$ reloccs. note that it is sufficient to consider only definitions 17$ rather than both definitions and uses in reloccs for the simple reason 18$ that if a use in reloccs can reach a given program point n, then there 19$ must exist a definition of the same variable preceding the use which 20$ also belongs to reloccs and can reach n (assuming no uninitialized 21$ variables)). 22$ 23$ then we compute the set of all variables v which have occurrences in 24$ reloccs and which are live at n (in the usual sense of liveness at a 25$ point). combination of the results of these two analyses gives us the 26$ information we need. we caution that a slight overestimation may 27$ occur in the interprocedural case (in the analysis of global 28$ variables), since the existence of two interprocedurally valid 29$ subpaths (from an occurrence vo of a global variable v to a 30$ potentially destructive operation n and then from n to a use of v) 31$ does not necessarily imply that their concatenation is also 32$ interproceduraly valid. 33$ 34$ liveness information is returned in a map livethru which maps each 35$ potentially destructive use n to the set of all definitions in reloccs 36$ which are live at n. 37$ 38$ it should be noted that our copy optimization algorithm avoids an 39$ important issue that would have arisen if we were to use a simpler 40$ approach based on bitvectoring data-flow analysis, such as in setl 41$ nl. 195. this is the issue of 'globalization' of share-bit setting. 42$ suppose that a variable v which will later be used destructively is 43$ passed as a parameter to some procedure p. this valuetransfer already 44$ causes v to be shared with the corresponding formal parameter, and v 45$ may also be shared with other local variables of p. however, unless 46$ the value of v became part of a global object (or a write parameter) 47$ during the execution of p, the call to p should not be viewed as 48$ sharing the value of v after the call has been completed. this fact, 49$ which is very difficult to pick up by using a bitvectoring scheme, is 50$ handled implicitly by the live analysis used by our algorithm. this 51$ comment has been included here as a reminder and warning if an attempt 52$ is made to replace the value-flow based algorithm by a simpler 53$ bitvectoring scheme. this issue also needs to be considered carefully 54$ in the bitvectoring 'bookkeeping optimization' phase (see procedure 55$ copy_share_improve below). 56$ 57 repr 58 freach: remote smap(df_edge) df_map_ocrs; 59 flive: remote smap(df_edge) df_map_syms; 60 frdestuse: smap(destuse) df_map_ocrs; 61 fldestuse: smap( tuple(destuse, df_node) ) 62 df_map_syms; 63 zero_o: df_elmt_ocrs; 64 id_o: df_map_ocrs; 65 zero_s: df_elmt_syms; 66 id_s: df_map_syms; 67 dum1, dum2: remote mmap{df_node} df_elmt_ocrs; 68 usym1, usym2: symbol; 69 uocrs1, uocrs2: occurrence; 70 71 reach: remote smap(df_node) df_elmt_ocrs; 72 canreach: mmap{destuse} df_elmt_ocrs; 73 fvo: df_map_ocrs; 74 globrelvars: df_elmt_syms; 75 livat: remote smap(df_node) df_elmt_syms; 76 bvo: elmt blocks; 77 w: df_node; 78 livatvo: df_elmt_syms; 79 vo: destuse; 80 vo1: elmt df_base_ocrs; 81 vo2: occurrence; 82 r: routine; 83 p: symbol; 84 locreldefs: df_elmt_ocrs; 85 locrelvars: df_elmt_syms; 86 x: destuse; 87 y: sparse set(reldef); smfg 128 z: reldef; 88 entry_time: integer; 89 end repr; 90 91 entry_time := time; 92 93$ 94$ initialise a special data flow value and map which denote the effect 95$ of an undefined (unreachable) data state and an undefined (untrace- 96$ able) data flow, respectively. 97$ 98 xom_syms := { usym1 := newat }; 99 fom_syms := [ { usym1 := newat }, { usym2 := newat } ]; 100 101 xom_ocrs := { uocrs1 := newat }; 102 fom_ocrs := [ { uocrs1 := newat }, { uocrs2 := newat } ]; 103$ 104$ initialize the output map livethru 105$ 106 livethru := {}; 107$ 108$ first perform interprocedural analysis 109$ 110$ initialize the block mappings required by our standard data-flow 111$ package, and a few auxiliary mappings. for efficiency, one scan 112$ through the code is used to compute the mappings for both forward 113$ (reachability) and backward (liveness) analyses. 114$ see block_flowmaps for details. 115$ smfk 166 if globreldefs /= {} then smfk 167 116 [ freach, flive, frdestuse, fldestuse ] := 117 block_flowmaps(om, globreldefs); 118$ 119$ reachability analysis 120$ 121 zero_o := {}; 122 id_o := [ globreldefs, zero_o ]; 123 124 interproc_fwd_analysis_ocrs 125 (freach, reach, id_o, zero_o, false, false, dum1, dum2, om); 126 freach := om; $ free storage 127$ 128$ next compute the map canreach which maps each potentially 129$ destructive use vo to the set of all relevant definitions 130$ which can reach vo. this is done by propagating reach 131$ through the appropriate portion of the block containing vo. 132$ 133 canreach := {}; 134 (forall fvo = frdestuse(vo)) 135 $ ofx is a variant of the functional application operator 136 canreach{vo} := fvo .ofx_o reach(blockof(instno(vo))); 137 end forall; 138 139 frdestuse := om; reach := om; $ free storage 140$ 141$ live analysis 142$ 143 globrelvars := { oi_sym(vo2) : vo2 in globreldefs }; 144 zero_s := {}; 145 id_s := [ globrelvars, zero_s ]; 146 147 interproc_back_analysis_syms 148 (flive, livat, id_s, zero_s, false); 149 flive := om; $ free storage 150$ 151$ next compute the livethru map, combining reachability and liveness 152$ 153 (forall vo in potdestuses) 154 bvo := blockof(instno(vo)); 155$ compute livatvo - the set of all variables live at vo 156$ see a description of fldestuse in procedure block_flowmaps. 157 livatvo := zero_s +/[ fldestuse([vo,w]) .ofx_s livat(w) : 158 w in cessor{bvo}]; 159$ then livethru(vo) is computed as the set of all definitions 160$ that can reach vo whose variable is in livatvo 161 livethru{vo} := 162 { vo1 in canreach{vo} | oi_sym(vo1) in livatvo }; 163 end forall; 164 $ free storage 165 fldestuse := om; canreach := om; 166 livat := om; livatvo := om; 167 168 if 'q' in dump_string then 169 prints('livethru =', [ [ str x, y ] : y = livethru{x} ] ); 170 end if; smfk 168 smfk 169 end if; 171$ 172$ next perform the corresponding intraprocedural analysis 173$ for each procedure 174$ 175 (forall r in routs) 176 177 locreldefs := { vo2 in all_reldefs | 178 vo2 notin globreldefs 179 and oi_sym(vo2) in localvars{r} }; smfk 170 smfk 171 if locreldefs = {} then continue forall; end if; 180 181 [ freach, flive, frdestuse, fldestuse ] := 182 block_flowmaps(r, locreldefs); 183 184 zero_o := {}; 185 id_o := [ locreldefs, zero_o ]; 186 187 intraproc_fwd_analysis_ocrs(r, freach, reach, id_o, zero_o, 188 false, false, dum1, dum2, om); 189 freach := om; $ free storage 190 191 canreach := {}; 192 (forall fvo = frdestuse(vo)) 193 canreach{vo} := fvo .ofx_o reach(blockof(instno(vo))); 194 end forall; 195 196 reach := om; frdestuse := om; $ free storage 197 198 locrelvars := { oi_sym(vo2) : vo2 in locreldefs }; 199 zero_s := {}; 200 id_s := [ locrelvars, zero_s ]; 201 202 intraproc_back_analysis_syms 203 (r, flive, livat, id_s, zero_s, false); 204 flive := om; $ free storage 205 206 (forall vo in potdestuses) 207 bvo := blockof(instno(vo)); 208 livatvo := zero_s +/[ fldestuse([vo,w]) .ofx_s livat(w) : 209 w in cessor{bvo}]; 210 livethru{vo} +:= 211 { vo1 in canreach{vo} | oi_sym(vo1) in livatvo }; 212 end forall; 213 214 $ free storage 215 fldestuse := om; canreach := om; 216 livat := om; livatvo := om; 217 end forall; 218 219 if 'p' in dump_string then smfg 129 prints('livethru =', smfh 47 [ [ oi_str(x), { oi_str(z): z in y } ] : y = livethru{x} ] smfg 131 ); 221 end if; 222 223 if 'e' in dump_string then 224 print(time - entry_time, 'msecs for live analysis'); 225 end if; 226 227 end procedure live_dead_analysis; 228 229 230 231 232 operator .ofx_s(f, x); 233 234 repr 235 f: df_map_syms; 236 x: df_elmt_syms; 237 end repr; 238 239 if f = om then return {}; else return f(1) * x + f(2); end if; 240 241 end operator .ofx_s; 242 243 244 operator .ofx_o(f, x); 245 246 repr 247 f: df_map_ocrs; 248 x: df_elmt_ocrs; 249 end repr; 250 251 if f = om then return {}; else return f(1) * x + f(2); end if; 252 253 end operator .ofx_o; 254 255 256 procedure block_flowmaps(p, rldefs); 257 258$ the rldefs parameter is the set of all definitions of variables 259$ (i.e. ovariables) that are linked to potentially destructive 260$ uses considered in a particular invocation of this routine 261$ (i.e. global variables or local variables of some procedure). 262$ 263$ this routine builds up four kinds of data flow maps 264$ by scanning the code, either in all routines (when p = om, 265$ indicating interprocedural analysis), or just in the routine 266$ p. the maps built are: 267$ 268$ freach - maps each edge in the flow graph to its 269$ effect on reachability information 270$ 271$ flive - maps each edge in the flow graph to its effect 272$ on liveness information. 273$ 274$ recall that the data-flow functions needed for our analyses 275$ i.e. the functions which express the data-flow effect of particular 276$ parts of the program flow graph, such as edges, intervals, portions 277$ of basic blocks etc., can be represented as follows: 278$ 279$ for reachability: 280$ 281$ f = (thru, gen), where thru is the set of all definitions which, if 282$ reaching the start of the flow described by f, can still reach its 283$ end, and where gen is the set of definitions which occur during that 284$ flow and can reach its end. 285$ (this comment applies to the data-flow function frblk used below.) 286$ 287$ for liveness: 288$ 289$ f = (thru, exp), where thru is the set of all variables which, if live 290$ at the end of the flow described by f, are still live at its start, 291$ and where exp is the set of all variables which have an upward-exposed 292$ use within that flow, and are therefore unconditionally live at the 293$ start of that flow. 294$ (this comment applies to the data-flow functions flblk, flins, flaft, 295$ flbef and fluse used below.) 296$ 297$ two additional objects are computed in this routine to eliminate the 298$ need for a second scan of the code after carrying out the data-flow 299$ analyses. without these objects available we would have to scan 300$ basic blocks once more, in order to propagate the information 301$ computed by the data flow analysis, which only gives data at block 302$ entries and exits, to the program points at which this information is 303$ actually needed. since in our case we know these points, namely the 304$ potentially destructive uses, in advance, we can compute the data-flow 305$ effects of the flow between these points and the entry and exits of 306$ the blocks containing them in advance. specifically, the objects 307$ computed are: 308$ 309$ frdestuse - maps each potentially destructive use to the data-flow 310$ map representing the effect on reachability of the flow 311$ from the start of the basic block containing that use to 312$ a point logically placed between the input arguments 313$ and the output argument of that use ('midpoint' of that 314$ use). 315$ 316$ fldestuse - maps each pair consisting of a potentially destructive 317$ use vo and a successor block sb of the block containing 318$ vo to a data-flow function representing the effect on 319$ liveness of the flow from the 'midpoint' of the use at 320$ vo to the start of sb. 321$ 322$ the reason for computing flow effects up to (or from) a 'midpoint' 323$ of a potentially destructive operation are illustrated by the 324$ following example: 325$ 326$ v := v with x; 327$ 328$ if we considered variable liveness just before that instruction, 329$ we would conclude that v is live there (since it is used 330$ immediately thereafter). likewise, if we considered liveness 331$ just after that instruction, v might be seen as live due to a 332$ subsequent use. the 'right' place for considering liveness is 333$ the midpoint of the instruction, between input and output, where 334$ v will be found to be dead. 335$ 336 repr 337 $ data structures for parameters 338 p: routine; 339 rldefs: df_elmt_ocrs; 340 341 $ data structures for returned variables 342 freach: remote smap(df_edge) df_map_ocrs; 343 flive: remote smap(df_edge) df_map_syms; 344 frdestuse: smap(destuse) df_map_ocrs; 345 fldestuse: smap( tuple(destuse, df_node) ) 346 df_map_syms; 347 348 $ data structure for local variables 349 todo: sparse set(routine); 350 rlvars: df_elmt_syms; 351 defofvars: mmap{symbol} df_elmt_ocrs; 352 vo: occurrence; 353 v: symbol; 354 id1: df_map_ocrs; 355 id2: df_map_syms; 356 r: routine; 357 b: elmt blocks; 358 i: elmt insts; 359 opc: elmt base_opcodes; 360 argsi: tuple(symbol); 361 frblk: df_map_ocrs; 362 flblk: df_map_syms; 363 dstusesinb: sparse set(occurrence); 364 fluse: smap(occurrence) df_map_syms; 365 used: df_elmt_syms; 366 j: integer; 367 flbef: df_map_syms; 368 ov: occurrence; 369 flaft: df_map_syms; 370 flins: df_map_syms; 371 vo1: occurrence; 372 sblks: sparse set(elmt blocks); 373 lb: symbol; 374 b1: elmt blocks; 375 end repr; 376 377 if p = om then todo := routs; else todo := { p }; end if; 378$ 379$ compute rlvars - the set of all variables appearing in rldefs 380$ and defofvars - a map from each such variable to its definitions 381$ in rldefs. 382$ 383 rlvars := {}; defofvars := {}; 384 (forall vo in rldefs) 385 rlvars with:= (v := oi_sym(vo)); 386 defofvars with:= [ v, vo ]; 387 end forall; 388 389 id1 := [ rldefs, {} ]; $ identity map for reachability anal. 390 id2 := [ rlvars, {} ]; $ identity map for live analysis 391 392 freach := {}; flive := {}; 393 frdestuse := {}; fldestuse := {}; 394 395 (forall r in todo) 396 (for_block(b, r)) 397$ initialize maps that summarize the data-flow through the block b 398$ so far (frblk for reachability and flblk for liveness). 399 frblk := id1; 400 flblk := id2; 401 dstusesinb := {}; $ set of pot. destructive uses in b 402$ fluse maps each use vo in dstusesinb to the data-flow effect on 403$ liveness of the flow from the midpoint of vo onward within b. 404 fluse := {}; 405 406 (for_inst(i, b)) $ iterate over instructions of block 407 opc := opcode(i); 408 argsi := args(i); 409 used := { v : j in [ first_ivar(opc)..#argsi ] | 410 (v := argsi(j)) in rlvars }; 411$ flbef represents the effect on liveness of the right-hand side of i 412$ (i.e. of the appearances of the input arguments in i). 413 flbef := [ rlvars, used ]; 414 415$ check whether i contains a relevant pot. destructive use vo 416 if (vo := dstuseini(i)) /= om then 417$ record effect on reachability of flow up to that use 418 frdestuse(vo) := frblk; 419$ add to collection of destructive uses in b 420 dstusesinb with:= vo; 421 end if; 422 423$ check whether i has an ovariable 424 if opc in ops_ovar then 425 v := argsi(1); 426$ if v is in rlvars update frblk and flaft by the effects of this 427$ definition. 428 if v in rlvars then 429 ov := get_oi(i, 1); 430$ the effect on reachability: ov can now reach the current point 431$ in the block, whereas all other definitions of v cannot 432 433$ note: the use of the map composition operator .comp (imported 434$ from the data-flow solver package) makes the tracing of flow 435$ through the block very simple to calculate; otherwise 436$ an explicit computation would be required. 437 frblk := 438 [ rldefs-defofvars{v}+{ov}, {ov} ] 439 .comp_ocrs frblk; 440$ the effect on liveness: v becomes dead, and nothing else 441$ becomes live 442 flaft := [ rlvars - {v}, {} ]; 443 end if; 444 else 445$ in this case the left hand side of i has no effect on liveness 446$ of the relevant variables. 447 flaft := id2; 448 end if; 449$ if there is a pot. destructive use vo in i (i.e. if vo as 450$ computed earlier is not om), initialize fluse(vo). 451 if vo /= om then 452 fluse(vo) := flaft; 453 end if; 454$ get the effect of i on liveness 455 flins := flbef .comp_syms flaft; 456$ update the flblk map by the effect of i on liveness 457 flblk := flblk .comp_syms flins; 458 459$ update fluse of previously encountered destructive uses in b 460$ note: an alternative approach might have been to propagate 461$ liveness information backwards through b. while this would 462$ eliminate the need to maintain a separate data-flow function 463$ for each destructive use in b (the fluse map), it would require 464$ maintainance of a separate data-flow function for each 465$ successor of b. moreover, this alternative approach would require 466$ us to process b in two different directions, instead of a single 467$ pass over b, as is done here. 468 469 (forall vo1 in dstusesinb | vo1 /= vo) 470 fluse(vo1) := fluse(vo1) .comp_syms flins; 471 end forall; 472 473$ if i is a branch instruction, use the current frblk, flblk etc. 474$ to update the various edge mappings that we wish to compute 475 if opc in ops_goto then 476$ get blocks that can be reached by this jump 477 if opc = q1_case then 478 sblks := { blockof(value(lb)) : 479 lb in range value(argsi(1)) }; 480 else 481 sblks := { blockof(value(argsi(#argsi))) }; 482 end if; 483 (forall b1 in sblks) 484 485$ update freach and flive using the current frblk and flblk maps 486$ the values are join'ed together, since both analyses determine 487$ facts that may happen (rather than must happen) as execution 488$ reaches (or leaves) a given program point. 489 freach([b, b1]) := 490 frblk .join_o:= freach([b, b1]); 491 flive([b, b1]) := 492 flblk .join_s:= flive([b, b1]); 493 494$ update fldestuse using the current fluse maps 495 (forall vo in dstusesinb) 496 fldestuse([vo,b1]) := 497 fluse(vo) .join_s fldestuse([vo, b1]); 498 end forall; 499 end forall; 500 end if; 501 end; $ end for_inst 502 end; $ end for_block 503 end forall r; 504 505 return [ freach, flive, frdestuse, fldestuse ]; 506 507 end procedure block_flowmaps; 508 509 510 511 512 operator .join_s(f, g); 513 514 repr 515 f, g: df_map_syms; 516 end repr; 517 518 if g = om then 519 return f; 520 else 521 return [ f(1) + g(1), f(2) + g(2) ]; 522 end if; 523 524 end operator .join_s; 525 526 527 operator .join_o(f, g); 528 529 repr 530 f, g: df_map_ocrs; 531 end repr; 532 533 if g = om then 534 return f; 535 else 536 return [ f(1) + g(1), f(2) + g(2) ]; 537 end if; 538 539 end operator .join_o; 540 541 1 .=member cel18d 2 3 procedure copy_eliminate; 4 5$ in this routine we use the value relationships and the liveness 6$ information computed in the preceding phases to determine 7$ which potentially destructive operations can be performed 8$ without having to copy the object whose value is being destroyed 9 10$ the general rule is as follows: 11 12$ copy elimination rule: let n be the 'midpoint' of 13$ a potentially destructive operation, and let vo denote the 14$ potential destructive use there. let containvo denote the set 15$ of all variable occurrences vo' for which there exists 16$ a membership relationship r such that [vo', r] can be reached 17$ from [vo, rid] by a path through (the virtual) upgraph followed by 18$ a path through (the virtual) downgraph. suppose that each vo' 19$ in containvo is dead at n. (recall that liveness is computed on a 20$ 'per occurrence' basis.) then no copy is required at n. 21 22$ the actual graph traversals have been accomplished in the 23$ preliminary value-flow algorithm (see value_flow). the map 24$ contain produced by that algorithm can be used to obtain 25$ containvo directly. 26$ 27$ another application of copy elimination is to optimize 28$ iterations. currently (unless the diter flag is turned on) 29$ if an object s is to be iterated over it is first copied 30$ to another object s', and then iteration is performed 31$ over s'. this is done because if s is modified during iteration 32$ we want to continue iteration over its old value. this however 33$ can be checked by our copy elimination procedure. indeed, 34$ let "s' := s" be the assignment of s to s' before the iteration. 35$ suppose that the occurrence of s' in this statement can 36$ reach some potentially destructive use of this value, and 37$ that s' is live at that use. only in this case the value 38$ of s will have to be copied before or during the iteration 39$ and so direct iteration over s is impossible. on the other 40$ hand, if this case does not arise, then it is safe to 41$ iterate over s directly. 42 43$ this optimization can be accomplished as follows: s' will 44$ always appear at exactly four q1 instructions: 45 46$ (1) q1_asn s' s 47$ (2) q1_inext (or q1_inextd) 48$ (3) q1_next (or q1_nextd) 49$ (4) q1_asn s' om (at the end of iteration). 50 51$ by iterating over all q1_next and q1_nextd appearing in 52$ the code being analyzed, and by tracing bfrom and ffrom 53$ links from it, we can collect all such quadruples. then, 54$ during copy elimination, we check, for each destructive 55$ use iv whether the corresponding contain entries include 56$ any occurrence of an s' in its defining assignment (1). 57$ if so, and if that occurrence is live at iv, then we 58$ tag this quadruple as not being amenable to optimization. 59$ after elimination has been completed, all untagged quadruples 60$ can then be optimized, the optimization simply being the 61$ deletion of statements (1) and (4) and substitution of s 62$ in place of s' in statements (2) and (3). 63$ 64$ we use the following data structures: 65$ 66$ iter_asns: set of all occurrences of a s' in assignments 67$ of the form (1). 68$ 69$ other_insts: maps each such assignment to the three statements 70$ (2) - (4). 71$ 72 repr 73 vo: occurrence; 74 containvo: df_elmt_ocrs; 75 liveconts: df_elmt_ocrs; 76 livevo: df_elmt_ocrs; 77 precuoccs: df_elmt_ocrs; 78 vox: occurrence; 79 instx: elmt insts; 80 share_insts: sparse set(elmt insts); 81 copy_cond, copy_fl: boolean; 82 copy_text: string; 83 end repr; 84 85$ copy_iters := {}; 86 destuses := {}; $ destructive uses where copy may be required 87 88$ process each potentially destructive use vo. 89 (forall vo in potdestuses) 90 livevo := livethru{vo} + destconsts; 91 copy_fl := false; 92 copy_cond := false; 93 share_insts := {}; 94 95 (forall vox in psoccsof{vo}) 96$ vo_copy := contain{vox} * livevo; 97 if contain{vox} * livevo /= {} then 98 copy_fl := true; $ vo requires copying 99 share_insts with:= instno(vox); 100$ if (vo_copy_iters := iter_asns * vo_copy) /= {} then 101$ copy_iters +:= vo_copy_iters; 102$ end if; 103 else 104 copy_cond := true; 105 end if; 106 end forall; 107 precuoccs := puoccsof{vo}; 108 if precuoccs * livevo /= {} then 109 copy_cond := false; 110 copy_fl := true; 111 elseif precuoccs /= {} then 112 copy_cond := true; 113 end if; 114 115 if not copy_fl then 116 copy_flag(instno(vo)) := copy_no; 117 copy_text := 'no'; 118 else 119 destuses with:= vo; 120 if copy_cond then 121 $ a conditional copy; set share bits 122 copy_flag(instno(vo)) := copy_test; 123 copy_text := 'a conditional'; 124 (forall instx in share_insts) 125 share_flag(instx) := 1; 126 end forall; 127 else $ unconditional copy; no share bit setting 128 copy_flag(instno(vo)) := copy_yes; 129 copy_text := 'an unconditional'; 130 end if; 131 end if; smfc 666 messages{stmtof(instno(vo))}{'s'} with:= 133 [ copy_text + ' copy is required for ' 134 '"' + oi_name(vo) + '".' ]; 135 end forall; 136 137$ we now perform the iteration optimization described above. 138$ (forall vox in iter_asns - copy_iters) 139$ (forall [ inxt, nxt, asnom ] in other_insts(vox)) 140$ del_insx(i := instno(vox)); 141$ del_insx(asnom); 142$ arg3(inxt) := arg3(nxt) := arg2(i); 143$ end forall; 144$ end forall; 145 146 end procedure copy_eliminate; 147 148 1 .=member csi18e 2 3 4 procedure copy_share_improve; 5$ 6$ this routine performs (?) various bookkeeping optimizations 7$ related to copying, such as suppression of share-bit settings, 8$ changing conditional copying into unconditional copying, and 9$ moving copy operations out of loops. 10$ 11$ for the time being this is an empty procedure. 12 13 pass; 14 15 end procedure copy_share_improve; 16 17 18 end module setl_optimizer - copy_optimization; 19 20 1 .=member util16 2 3 4 module setl_optimizer - util; 5 6$ this library contains various utility routines 7 8 1 .=member sym16a 2 3 4$ utilities for symbol table manipulation 5$ --------------------------------------- 6 7 8 procedure add_sym(sc); 9$ 10$ allocate a new symbol and add it to the end of the list of symbols 11$ in scope 'sc'. 12$ 13 repr 14 sc: elmt base_scopes; 15 s: symbol; 16 end repr; 17 18 s := newat; 19 20 if first_sym(sc) = om then 21 first_sym(sc) := last_sym(sc) := s; 22 else 23 last_sym(sc) := next_sym(last_sym(sc)) := s; 24 end if; 25 26 name(s) := str s; 27 scope(s) := sc; 28 is_internal(s) := 1; 29 30 return s; 31 32 end procedure add_sym; 33 34 35 36 37 procedure add_var(sc); 38$ 39$ add a variable to the scope 'sc'. 40$ 41 repr 42 sc: elmt base_scopes; 43 s: symbol; 44 end repr; 45 46 s := add_sym(sc); 47 48 form(s) := std_form(f_gen); 49 is_read(s) := 1; 50 is_write(s) := 1; 51 52 return s; 53 54 end procedure add_var; 55 56 57 58 59 procedure add_int(sc, i); 60$ 61$ add an integer constant with value 'i' to scope 'sc' 62$ 63 repr 64 sc: elmt base_scopes; 65 i: integer; 66 fm: elmt forms; 67$$-- old: symbol; 68 s: symbol; 69 end repr; 70 71 fm := std_form(f_int); 72$$-- value_inv is undefined here 73$$--old := value_inv(i, sc, fm); 74$$--if old /= om then return old; end; 75 76 s := add_sym(sc); 77 78 name(s) := str i; 79 form(s) := fm; 80 value(s) := i; 81 is_const(s) := 1; 82 83 return s; 84 85 end procedure add_int; 86 87 88 89 90 procedure add_label(sc); 91$ 92$ add a label to scope sc. 93$ 94 repr 95 sc: elmt base_scopes; 96 l: symbol; 97 end repr; 98 99 l := add_sym(sc); 100 101 form(l) := std_form(f_lab); 102 is_const(l) := 1; 103$$$ ???? art-this needs more comment. also, where is the value 104$$$ ???? of the label defined. 105 106 return l; 107 108 end procedure add_label; 109 110 111 112 113 procedure del_sym(sym, presym, sc); 114$ 115$ this routine deletes 'sym' from the symbol table of 'sc', 116$ where 'presym' is the symbol preceding sym in this table. 117$ 118 repr 119 sym: symbol; 120 presym: symbol; 121 sc: elmt base_scopes; 122 123 s, nextsym: symbol; 124 end repr; 125 126$ we first update the 'next_sym', 'first_sym' and 'last_sym' 127$ maps, and then remove 'sym' from the domain of all symbol- 128$ table maps. 129 130 if presym = om then 131 (for_sym(s, sc)) 132 if s = sym then quit; end if; smfc 667 presym := s; 134 end; $ end for_sym 135 end if; 136 137 nextsym := next_sym(sym); 138 if presym /= om then 139 if nextsym /= om then 140 next_sym(presym) := nextsym; 141 else 142 next_sym(presym) := om; 143 last_sym(sc) := presym; 144 end if; 145 else 146 if nextsym /= om then 147 first_sym(sc) := nextsym; 148 else $ sym is the only symbol in sc 149 first_sym(sc) := om; 150 last_sym(sc) := om; 151 end if; 152 end if; 153 154 name lessf:= sym; 155 scope lessf:= sym; 156 form lessf:= sym; 157 value lessf:= sym; is_const lessf:= sym; 158 alias lessf:= sym; is_store lessf:= sym; 159 is_temp lessf:= sym; is_internal lessf:= sym; 160 is_read lessf:= sym; is_write lessf:= sym; 161 is_stk lessf:= sym; is_param lessf:= sym; 162 is_repr lessf:= sym; is_init lessf:= sym; 163 is_seen lessf:= sym; is_back lessf:= sym; 164 is_rec lessf:= sym; 165 next_sym lessf:= sym; 166 167 end procedure del_sym; 168 169 1 .=member frm16b 2 3 4 procedure add_form(sc); 5$ 6$ this routine adds a new form (plex base member) to the scope 'sc'. 7$ 8 repr 9 sc: elmt base_scopes; 10 fm: elmt forms; 11 end repr; 12 13 fm := newat; 14 15 if first_form(sc) = om then 16 first_form(sc) := last_form(sc) := fm; 17 else 18 last_form(sc) := next_form(last_form(sc)) := fm; 19 end if; 20 21 return fm; 22 23 end procedure add_form; 24 25 1 .=member blk16c 2 3 4 procedure add_block(pb, sc, isafter); 5$ 6$ allocate a new block and add it to the scope sc either after 7$ the block pb (if isafter = true) or before pb otherwise. 8$ 9 repr 10 pb: elmt blocks; 11 sc: elmt base_scopes; 12 isafter: boolean; 13 b, qb, rb: elmt blocks; 14 end repr; 15 16$ if isafter = false, iterate through the blocks of sc to find 17$ the block preceding pb 18 19 if not isafter then 20 qb := om; 21 (for_block(b, sc)) 22 if b = pb then quit; end; 23 qb := b; 24 end; 25 else 26 qb := pb; 27 end if; 28 29 b := newat; 30 31 if qb = om then $ insert at end 32 if first_block(sc) = om then 33 first_block(sc) := last_block(sc) := b; 34 else 35 last_block(sc) := next_block(last_block(sc)) := b; 36 end if; 37 else $ insert after qb 38 if (rb := next_block(qb)) = om then $ qb is last 39 last_block(sc) := next_block(qb) := b; 40 else 41 next_block(b) := rb; 42 next_block(qb) := b; 43 end if; 44 end if; 45 46 routof(b) := sc; 47 48 return b; 49 50 end procedure add_block; 51 52 53 54 55 procedure del_block(b, pb, r); 56$ 57$ deletes a block b from the scope r. pb is the block preceding 58$ b in this scope. 59$ 60 repr 61 b,pb,nb: elmt blocks; 62 i,pi: elmt insts; 63 r: elmt base_scopes; 64 end repr; 65 66 pi := om; 67 (for_inst(i, b)) 68 if pi = om then 69 pi := i; 70 else 71 del_inst(i, pi, b); 72 end if; 73 end; 74 75$ delete the label of the block from the symbol table 76 dead_labs with:= arg1(pi); 77 78 del_inst(pi, om, b); 79 80 nb := next_block(b); 81 if nb = om then 82 if pb = om then 83 first_block(r) := om; 84 last_block(r) := om; 85 else 86 next_block(pb) := om; 87 last_block(r) := pb; 88 end if; 89 else 90 if pb = om then 91 first_block(r) := nb; 92 else 93 next_block(pb) := nb; 94 end if; 95 end if; 96 97 98 end procedure del_block; 99 100 1 .=member ins16d 2 3 4 procedure add_inst(b, opc, a(*)); smfi 330$ smfi 331$ add a new instruction at the end of block b. 5$ 6$ the final, variable-length group of parameters of this routine are 7$ the arguments of the instruction being added. note that this 8$ instruction may not be a call or label. smfi 332$ this instruction may not be a call or label because inserting such smfi 333$ an instruction requires that various maps on blocks are updated. 11$ 15 repr 16 b: elmt blocks; 17 opc: elmt base_opcodes; 18 a: tuple(symbol); 19 20 i: elmt insts; 21 v: symbol; 22 oi: occurrence; 23 occsi: tuple(occurrence); 24 iva1, j: integer 0..65536; 25 end repr; 26 27 i := newat; 28 29 if first_inst(b) = om then smfk 172 stmtof(i) := 65535; 30 first_inst(b) := last_inst(b) := i; 31 else smfi 335 stmtof(i) := stmtof(last_inst(b)); 32 last_inst(b) := next_inst(last_inst(b)) := i; 33 end if; 34 35 blockof(i) := b; 36 37$ update all_o etc. 38 iva1 := first_ivar(opc); 39 occsi := []; 40 41 (forall v = a(j)) 42 oi := newat; 43 instno(oi) := i; 44 argno(oi) := j; 45 occsi(j) := oi; 46 47 if j = 1 and opc in ops_ovar then all_o with:= oi; end if; 48 if j >= iva1 then all_i with:= oi; end if; 49 smfk 173 all_oi with:= oi; smfk 174 smfl 182 if V in VARIABLES then smfk 176 occsof{v} with:= oi; smfk 177 end if; 52 end forall; 53 54 opcode(i) := opc; 55 args(i) := a; 56 occs(i) := occsi; 57 58 return i; 59 60 end procedure add_inst; 61 62 63 64 65 procedure insert_ins(rw old, opc, a(*)); 66 67 repr 68 old: elmt insts; 69 opc: elmt base_opcodes; 70 a: tuple(symbol); 71 end repr; 72 73 insert_ins1(old, opc, a); 74 75 end procedure insert_ins; 76 77 78 79 80 procedure insert_ins1(rw old, opc, a); 81 82$ add a new instruction after instruction 'i'. 83 84 repr 85 old: elmt insts; 86 opc: elmt base_opcodes; 87 a: tuple(symbol); 88 89 new: elmt insts; 90 b: elmt blocks; 91 v: symbol; 92 oi: occurrence; 93 occsi: tuple(occurrence); 94 iva1, j: integer 0..65536; 95 end repr; 96 97 new := newat; 98 99 next_inst(new) := next_inst(old); 100 next_inst(old) := new; 101 102 b := blockof(old); 103 blockof(new) := b; 104 stmtof(new) := stmtof(old); 105 106 if last_inst(b) = old then last_inst(b) := new; end if; 107 108$ update oi_sets and oi_maps. 109 iva1 := first_ivar(opc); 110 occsi := []; 111 112 (forall v = a(j)) 113 oi := newat; 114 instno(oi) := new; 115 argno(oi) := j; 116 occsi(j) := oi; 117 118 if j = 1 and opc in ops_ovar then all_o with:= oi; end if; 119 if j >= iva1 then all_i with:= oi; end if; 120 smfk 178 all_oi with:= oi; smfk 179 smfl 183 if V in VARIABLES then smfk 181 occsof{v} with:= oi; smfk 182 end if; 123 end forall; 124 125 opcode(new) := opc; 126 args(new) := a; 127 occs(new) := occsi; 128 129 old := new; 130 131 end procedure insert_ins1; 132 133 134 135 136 procedure del_inst(rw i, pi, b); 137$ 138$ this routine deletes an instruction from the q1 code. 139$ i is the instruction to be deleted and pi is the instruction 140$ preceding i in its basic block b. 141$ it resets i to pi, so that, if we iterate over the block b using 142$ the for_inst macro, we step to the instruction following i. 143$ 144$ nb. this routine can not be used to delete the first instruction 145$ of a block while iterating through a block using the for_inst 146$ macro, since the step block of the iteration would look for 147$ next_inst(om), which yields an error. 148$ 149 150 repr 151 i: elmt insts; 152 pi: elmt insts; 153 b: elmt blocks; 154 155 inst, ni: elmt insts; 156 opc: elmt base_opcodes; 157 argsi: tuple(symbol); 158 v: symbol; 159 oi: occurrence; 160 iva1, j: integer 0..65536; 161 end repr; 162 163 164 if pi = om then 165 (for_inst(inst, b)) 166 if inst = i then quit; end if; 167 pi := inst; 168 end; $ end for_inst 169 end if; 170 171 ni := next_inst(i); 172 if pi /= om then 173 if ni /= om then 174 next_inst(pi) := ni; 175 else 176 next_inst(pi) := om; 177 last_inst(b) := pi; 178 end if; 179 else 180 if ni /= om then 181 first_inst(b) := ni; 182 else 183 first_inst(b) := om; 184 last_inst(b) := om; 185 end if; 186 end if; 187 188 argsi := args(i); 189 opc := opcode(i); 190 iva1 := first_ivar(opc); 191 192 (forall v = argsi(j)) 193 oi := get_oi(i, j); 194 195 if j = 1 and opc in ops_ovar then all_o less:= oi; end if; 196 if j >= iva1 then all_i less:= oi; end if; 197 198 all_oi less:= oi; 199 occsof{v} less:= oi; 200 end forall; 201 202 i := pi; 203 204 end procedure del_inst; 205 206 1 .=member pru16e 2 3 4 procedure ermsg(s); 5 6$ print error message 's' 7 8 print('**** error', s, '****'); 9 10 end procedure ermsg; 11 12 13 14 15 procedure abort(s); 16 17$ print error message and abort 18 19 ermsg(s); 20 stop; 21 22 end procedure abort; 23 24 25 26 27 procedure prints(hdr, t); 28$ 29$ this utility routine prints its second argument sorted. this is a 30$ tuple 't' of pairs [ c, x ], where c is a string and x can be any 31$ object. we first sort t in lexicographical order of the string 32$ components, and then print it one pair per line. we use a simple 33$ version of heapsort to do the sort. 34$ 35 repr 36 hdr: string; 37 t: tuple(tuple(string, general)); 38 x: tuple(string, general); 39 j, k, l, m, n: integer 0..65536; 40 end repr; 41 42 43 macro before(l, r); $ defines partial order 44 ( t(l)(1) < t(r)(1) ) 45 endm; 46 47 48 print; 49 print(hdr); 50 51 if #t = 0 then return t; end if; $ trivial case 52 53 $ sort the pairs lexographically by their first component, using 54 $ heap sort 55 (init n := #t; j := n div 2; while j >= 1 step j -:= 1;) 56 (init k := j; while (l := k+k) <= n) 57 $ which child will be promoted ? 58 m := if l < n and before(l, l+1) then l+1 else l end; 59 60 $ will a child be promoted ? 61 if before(k, m) then 62 x := t(k); t(k) := t(m); t(m) := x; k := m; 63 else 64 quit init k; 65 end if; 66 end init k; 67 end init n; 68 69 (init j := n; while j > 1) 70 x := t(j); t(j) := t(1); t(1) := x; j -:= 1; 71 (init k := 1; while (l := k+k) <= j) 72 $ which child will be promoted ? 73 m := if l < j and before(l, l+1) then l+1 else l end; 74 75 $ will a child be promoted ? 76 if before(k, m) then 77 x := t(k); t(k) := t(m); t(m) := x; k := m; 78 else 79 quit init k; 80 end if; 81 end init k; 82 end init j; 83 84 (forall x in t) print(x(1), x(2)); end forall; 85 86 87 end procedure prints; 88 89 90 91 smfe 193 procedure format_type(tp); smfe 194$ smfe 195$ this routine returns a string corresponding to the type tp. smfe 196$ smfe 197$ assert is_string arb grosstyp(tp); smfe 198$ assert grosstyp(tp) subset bsctyps; smfe 199$ assert tp = type_zero or #grosstyp >= 1; smfe 200$ smfe 201 const smfe 202 grstup = { t_tuple }, smfe 203 grsset = { t_set }, smfe 204 grsmap = { t_map }; smfe 205 smfe 206 repr smfe 207 tp, tx: elmt types; smfe 208 g: gross_type; smfe 209 c: general; smfe 210 ct1: tuple(elmt types); smfe 211 x: basic_type; smfe 212 text: string; smfe 213 j: integer; smfe 214 grstup, grsset, grsmap: gross_type; smfe 215 end repr; smfe 216 smfe 217 smfe 218 if tp = type_zero then return '-'; end if; smfe 219 if tp = type_gen then return 'general'; end if; smfe 220 smfe 221 if tp = type_notom then return 'not-om'; end if; smfe 222 smfe 223 [ g, c ] := tp; smfe 224 smfe 225 if c = type_om then smfe 226 if g = grstup then return 'nulltup'; end if; smfe 227 if g = grsset then return 'nullset'; end if; smfe 228 if g = grsmap then return 'nullmap'; end if; smfe 229 end if; smfe 230 smfe 231 if g*tup_set_map /= {} and smfe 232 (t_tuple in g impl not is_knt(tp)) and is_om(c) then smfe 233 (forall x in g | x in tup_set_map) smfe 234 if text = om then smfe 235 text := 'null' + x; smfe 236 else smfe 237 text +:= ' | null' + x; smfe 238 end; smfe 239 end forall; smfe 240 if c = type_gen then $ c .con:= type_notom; smfe 241 c := type_notom; smfe 242 else smfe 243 grosstyp(c) less:= t_om; smfe 244 end if; smfe 245 if grosstyp(c) = {} then g -:= tup_set_map; end if; smfe 246 tp := [ g, c, false ]; smfe 247 end if; smfe 248 smfe 249 (forall x in g) smfe 250 smfe 251 if text = om then text := x; else text +:= ' | ' + x; end; smfe 252 smfe 253 case x of smfe 254 smfe 255 (t_set): smfe 256 text +:= '(' + format_type(comptyp(tp)) + ')'; smfe 257 smfe 258 (t_map): smfe 259 text +:= '(' + format_type(domtyp(tp)) + ') ' smfe 260 + format_type(rangetyp(tp)); smfe 261 smfe 262 (t_tuple): smfe 263 if is_knt(tp) then smfe 264 ct1 := comptyp(tp); smfe 265 text +:= '(' +/[ format_type(tx) + smfe 266 if j = #ct1 then ')' else ', ' end : smfe 267 tx = ct1(j) ]; smfe 268 else smfe 269 text +:= '(' + format_type(comptyp(tp)) + ')'; smfe 270 end if; smfe 271 smfe 272 $ note that the primitive types fall into the else-clause of smfe 273 $ this case statement. smfe 274 smfe 275 end case; smfe 276 smfe 277 end forall; smfe 278 smfe 279 return if '|' in text then '{ ' + text + ' }' else text end; smfe 280 smfe 281 smfe 282 end procedure format_type; 140 141 142 143 144 procedure format_repr(rpr); 145$ 146$ this routine returns a string containing the description of 'rpr' 147$ in the syntax of the setl data-representation sublanguage. 148$ 149 repr 150 g: gross_type; 151 fr: basic_type; 152 comps: tuple(elmt types); 153 j: integer; 154 rpr, crpr: elmt types; 155 brckts: string; 156 end repr; 157 158 159 g := grosstyp(rpr); 160 if #g /= 1 then return 'general'; end if; 161 162 fr := arb g; 163 case fr of 164 165 (t_om): return 'omega'; 166 (t_int): return 'integer'; 167 (t_real): return 'real'; 168 (t_string): return 'string'; 169 (t_atom): return 'atom'; 170 (t_elmt): return 'elmt ads' + str rpr(2); 171 172 (t_set): return case set_type(rpr) of 173 (locl): 'local set(', 174 (remt): 'remote set(', 175 (sprse): 'sparse set(' 176 else 'set(' 177 end + format_repr(comptyp(rpr)) + ')'; 178 179 (t_map): return 180 case set_type(rpr) of 181 (locl): 'local ', 182 (remt): 'remote ', 183 (sprse): 'sparse ' 184 else '' 185 end + 186 case map_type(rpr) of 187 (ft_smap): 'smap(' + format_repr(domtyp(rpr)) + ') ', 188 (ft_mmap): 'mmap{' + format_repr(domtyp(rpr)) + '} ' 189 else 'map(' + format_repr(domtyp(rpr)) + ') ' 190 end + format_repr(rangetyp(rpr)); 191 192 (t_tuple): 193 if is_knt(rpr) then 194 comps := comptyp(rpr); 195 brckts := (+/[ format_repr(crpr) + 196 (if j = #comps then ')' else ', ' end) 197 : crpr = comps(j) ]); 198 return 199 if brckts = om then 'tuple' else 'tuple('+brckts end; 200 else 201 return 'tuple(' + format_repr(comptyp(rpr)) + ')'; 202 end if; 203 204 else 205 return ' (error in format_repr) '; 206 end case; 207 208 209 end procedure format_repr; 210 211 212 213 214 procedure format_form(fm); 215$ 216$ this routine formats a form table entry into a human-readable string. 217$ 218 repr 219 fm: elmt forms; 220 t: tuple(elmt forms); 221 j, n: integer; 222 end repr; 223 224 225 return 226 case ft_type(fm) of 227 228(f_gen): 'general', 229 230(f_sint): 'integer ' + str ft_low(fm) + '..' + 231 if ft_lim(fm) /= om and ft_lim(fm) /= 0 then 232 str ft_lim(fm) 233 else 234 'maxsi' 235 end, 236 237(f_sstring): 'string', 238(f_atom): 'atom', 239(f_latom): 'atom', 240(f_elmt): 'elmt ' + name(basesymb(ft_base(fm))), 241(f_uint): 'untyped integer', 242(f_ureal): 'untyped real', 243(f_int): 'integer', 244(f_string): 'string', 245(f_real): 'real', 246(f_ituple): 'tuple(untyped integer)', 247(f_rtuple): 'tuple(untyped real)', 248(f_ptuple): 'packed tuple(' + format_form(ft_elmt(fm)) + ')', 249 250(f_tuple): 'tuple(' + format_form(ft_elmt(fm)) + ')' + 251 if ft_lim(fm) /= om and ft_lim(fm) /= 0 then 252 '(' + str ft_lim(fm) + ')' 253 else 254 '' 255 end, 256 257(f_mtuple): 'tuple(' +/ 258 [ format_form(ft_elmt(fm)(j)) + 259 if j = #ft_elmt(fm) then ')' else ', ' end : 260 j in [ 1..#ft_elmt(fm) ] ], 261 262(f_uset): if ft_type(ft_elmt(fm)) = f_elmt then 263 'sparse ' 264 else 265 '' 266 end + 267 'set(' + format_form(ft_elmt(fm)) + ')', 268 269(f_lset): 'local set(' + format_form(ft_elmt(fm)) + ')', 270 271(f_rset): 'remote set(' + format_form(ft_elmt(fm)) + ')', 272 273(f_umap): if ft_type(ft_dom(fm)) = f_elmt then 274 'sparse ' 275 else 276 '' 277 end + 278 case ft_mapc(fm) of 279 (ft_map): 'map(' + format_form(ft_dom(fm)) + ') ', 280 (ft_smap): 'smap(' + format_form(ft_dom(fm)) + ') ', 281 (ft_mmap): 'mmap{' + format_form(ft_dom(fm)) + '} ' 282 else om end + 283 format_form(ft_im(fm)), 284 285(f_lmap): 'local ' + 286 case ft_mapc(fm) of 287 (ft_map): 'map(' + format_form(ft_dom(fm)) + ') ', 288 (ft_smap): 'smap(' + format_form(ft_dom(fm)) + ') ', 289 (ft_mmap): 'mmap{' + format_form(ft_dom(fm)) + '} ' 290 else om end + 291 format_form(ft_im(fm)), 292 293(f_rmap): 'remote ' + 294 case ft_mapc(fm) of 295 (ft_map): 'map(' + format_form(ft_dom(fm)) + ') ', 296 (ft_smap): 'smap(' + format_form(ft_dom(fm)) + ') ', 297 (ft_mmap): 'mmap{' + format_form(ft_dom(fm)) + '} ' 298 else om end + 299 format_form(ft_im(fm)), 300 301(f_lpmap): 'packed local ' + 302 case ft_mapc(fm) of 303 (ft_map): 'map(' + format_form(ft_dom(fm)) + ') ', 304 (ft_smap): 'smap(' + format_form(ft_dom(fm)) + ') ' 305 else om end + 306 format_form(ft_im(fm)), 307 308(f_limap, f_lrmap): 309 'local ' + 310 case ft_mapc(fm) of 311 (ft_map): 'map(' + format_form(ft_dom(fm)) + ') ', 312 (ft_smap): 'smap(' + format_form(ft_dom(fm)) + ') ' 313 else om end + 314 format_form(ft_im(fm)), 315 316(f_rpmap): 'packed remote ' + 317 case ft_mapc(fm) of 318 (ft_map): 'map(' + format_form(ft_dom(fm)) + ') ', 319 (ft_smap): 'smap(' + format_form(ft_dom(fm)) + ') ' 320 else om end + 321 format_form(ft_im(fm)), 322 323(f_rimap, f_rrmap): 324 'remote ' + 325 case ft_mapc(fm) of 326 (ft_map): 'map(' + format_form(ft_dom(fm)) + ') ', 327 (ft_smap): 'smap(' + format_form(ft_dom(fm)) + ') ' 328 else om end + 329 format_form(ft_im(fm)), 330 331(f_base): 'base(' + format_form(ft_elmt(fm)) + ')', 332 333(f_pbase): 'plex base', 334 335(f_uimap, f_urmap): 336 if ft_type(ft_dom(fm)) = f_elmt then 337 'sparse ' 338 else 339 '' 340 end + 341 case ft_mapc(fm) of 342 (ft_map): 'map(' + format_form(ft_dom(fm)) + ') ', 343 (ft_smap): 'smap(' + format_form(ft_dom(fm)) + ') ' 344 else om end + 345 format_form(ft_im(fm)), 346 347(f_error): 'error', 348 349(f_proc): 'procedure' + 350 if (n := #(t := ft_elmt(fm))) /= 0 then 351 '(' +/[ format_form(t(j)) + 352 if j = n-1 then '' else ', ' end : 353 j in [ 1..n-1 ] ] + 354 ') ' + format_form(t(n)) 355 else 356 '' 357 end, 358 359(f_memb): 'member', 360 361(f_lab): 'label' 362 363 else 364 ' (error in format_form) ' 365 end; 366 367 368 end procedure format_form; 369 370 371 372 373 procedure format_inst(i, aivs); 374$ 375$ this routine attempts to re-build the source text corresponding to the 376$ instruction i. aivs, if given, is a tuple with the new arguments for 377$ the instruction. 378$ 379 repr 380 i: elmt insts; 381 aivs: tuple(symbol); 382 opc: elmt base_opcodes; smfc 673 a1, a2, a3: symbol; smfc 674 name1, name2, name3: string; smfc 675 pname1, pname2, pname3: string; 386 end repr; 387 388 389 opc := opcode(i); 390 assert opc in ops_ovar; 391 assert first_ivar(opc) = 2; smfc 676 aivs := aivs ? args(i)(2..); [ a1, a2, a3 ] := aivs; 393 394 name1 := if #name(a1) > 2 and name(a1)(1) = 't' and 395 (name(a1)(2) = '.' or name(a1)(2) = '#') then 396 '' 397 else 398 name(a1) 399 end; 400 pname1 := if is_internal(a1) /= om then 401 '(' + name1 + ')' 402 else 403 name1 404 end; 405 406 if a2 /= om then 407 name2 := if #name(a2) > 2 and name(a2)(1) = 't' and 408 (name(a2)(2) = '.' or name(a2)(2) = '#') then 409 '' 410 else 411 name(a2) 412 end; 413 pname2 := if is_internal(a2) /= om then 414 '(' + name2 + ')' 415 else 416 name2 417 end; 418 else 419 name2 := pname2 := '***'; 420 end if; smfc 681 smfc 682 if a3 /= om then smfc 683 name3 := if #name(a3) > 2 and name(a3)(1) = 't' and smfc 684 (name(a3)(2) = '.' or name(a3)(2) = '#') then smfc 685 '' smfc 688 else smfc 689 name(a3) smfc 690 end; smfc 691 pname3 := if is_internal(a3) /= om then smfc 692 '(' + name3 + ')' smfc 693 else smfc 694 name3 smfc 695 end; smfc 696 else smfc 697 name3 := pname3 := '***'; smfc 698 end if; 421 422 return case opc of 423 424(q1_in): pname1 + ' in ' + pname2, 425(q1_notin): pname1 + ' notin ' + pname2, 426(q1_incs): pname1 + ' incs ' + pname2, 427(q1_eq): pname1 + ' = ' + pname2, 428(q1_ne): pname1 + ' /= ' + pname2, 429(q1_lt): pname1 + ' < ' + pname2, smfh 48(q1_pos): pname1 + ' > ' + pname2, 430(q1_ge): pname1 + ' >= ' + pname2, 431 432(q1_add): pname1 + ' + ' + pname2, 433(q1_sub): pname1 + ' - ' + pname2, 434(q1_mult): pname1 + ' * ' + pname2, 435(q1_slash): pname1 + ' / ' + pname2, 436(q1_div): pname1 + ' div ' + pname2, 437(q1_mod): pname1 + ' mod ' + pname2, 438(q1_exp): pname1 + ' ** ' + pname2, 439(q1_atan2): pname1 + ' atan2 ' + pname2, 440(q1_max): pname1 + ' max ' + pname2, 441(q1_min): pname1 + ' min ' + pname2, 442(q1_npow): pname1 + ' npow ' + pname2, 443(q1_from): pname1 + ' from ' + pname2, 444(q1_fromb): pname1 + ' fromb ' + pname2, 445(q1_frome): pname1 + ' frome ' + pname2, 446(q1_with): pname1 + ' with ' + pname2, 447(q1_less): pname1 + ' less ' + pname2, 448(q1_lessb): pname2 + ' fromb ' + pname1, 449(q1_lesse): pname2 + ' frome ' + pname1, 450(q1_lessf): pname1 + ' lessf ' + pname2, 451 452(q1_not): 'not ' + pname1, 453(q1_even): 'even ' + pname1, 454(q1_odd): 'odd ' + pname1, 455(q1_isint): 'is_integer ' + pname1, 456(q1_isreal): 'is_real' + pname1, 457(q1_isstr): 'is_string ' + pname1, 458(q1_isbool): 'is_boolean ' + pname1, 459(q1_isatom): 'is_atom ' + pname1, 460(q1_istup): 'is_tuple ' + pname1, 461(q1_isset): 'is_set ' + pname1, 462(q1_ismap): 'is_map ' + pname1, 463 464(q1_arb): 'arb ' + pname1, 465(q1_arbb): ' fromb ' + pname1, 466(q1_arbe): ' frome ' + pname1, 467(q1_dom): 'domain ' + pname1, 468(q1_range): 'range ' + pname1, 469(q1_pow): 'pow ' + pname1, 470(q1_nelt): '# ' + pname1, 471(q1_abs): 'abs ' + pname1, 472(q1_char): 'char ' + pname1, 473(q1_ceil): 'ceil ' + pname1, 474(q1_floor): 'floor ' + pname1, 475(q1_fix): 'fix ' + pname1, 476(q1_float): 'float ' + pname1, 477(q1_sin): 'sin ' + pname1, 478(q1_cos): 'cos ' + pname1, 479(q1_tan): 'tan ' + pname1, 480(q1_arcsin): 'asin ' + pname1, 481(q1_arccos): 'acod ' + pname1, 482(q1_arctan): 'atan ' + pname1, 483(q1_tanh): 'tanh ' + pname1, 484(q1_expf): 'exp ' + pname1, 485(q1_log): 'log ' + pname1, 486(q1_sqrt): 'sqrt ' + pname1, 487(q1_sign): 'sign ' + pname1, 488(q1_type): 'type ' + pname1, 489(q1_str): 'str ' + pname1, 490(q1_val): 'val ' + pname1, 491(q1_umin): '-' + pname1, 492(q1_rand): 'random ' + pname1, 493 494(q1_newat): 'newat', 495(q1_time): 'time', 496(q1_date): 'date', 497(q1_na): 'nargs', 498 499(q1_set): '{ ' + 500 if #aivs >= 1 then name1 else '' end + 501 if #aivs >= 2 then ', ' + name2 else '' end + 502 if #aivs >= 3 then ', ...' else '' end + 503 ' }', 504 505(q1_set1): '{ ' + name1 + ' : }', 506 507(q1_tup): '[ ' + 508 if #aivs >= 1 then name1 else '' end + 509 if #aivs >= 2 then ', ' + name2 else '' end + 510 if #aivs >= 3 then ', ...' else '' end + 511 ' ]', 512 513(q1_tup1): '[ ' + name1 + ' : ]', 514 515(q1_inext, q1_next, q1_inextd, q1_nextd): 516 '', 517 518(q1_of): pname1 + '(' + name2 + ')', 519(q1_ofa): pname1 + '{' + name2 + '}', smfc 699(q1_subst): pname1 + '(' + name2 + '..' + name3 + ')', 521(q1_end): pname1 + '(' + name2 + '..)', 522 smfc 700(q1_sof): pname3 + '(' + name1 + ')', smfc 701(q1_sofa): pname3 + '{' + name1 + '}', smfc 702(q1_ssubst): '(' + name1 + '..' + name2 + ')', smfc 703(q1_send): pname3 + '(' + name1 + '..)', 527 528(q1_asn): name1, 529(q1_argin): name1 530 531else '' 532 533 end; 534 535 536 end procedure format_inst; 537 538 539 540 541 end module setl_optimizer - util; 542 543 1 .=member dmps17 2 3 4 module setl_optimizer - dumps; 5 6$ this module contains procedures to dump the q1 code and various 7$ graphs. 8 9 var 10 line, $ current line number in segment 11 outint; $ outermost interval of routine being dumped 12 13 var 14 all_bases; $ all bases in scope 15 16 const 17 line_size = 130, $ linesize of output file 18 lines_per_segment = 54; $ lines per segment 19 20 var smfk 183 act_exposd, $ globals actually used in a scope before they smfk 184 $ are defined by that member smfk 185 act_reads, $ globals actually used by a member 25 act_writes, $ globals actually changed by a member 26 act_calls, $ routines actually called by a member smfk 186 globals, $ maps each member to the globals it defines smfk 187 routines, $ maps each member to the routines it defines 27 seen; $ member has been processed before 28 29 init smfk 188 act_exposd := {}, act_reads := {}, act_writes := {}, smfk 189 act_calls := {}, globals := {}, routines := {}, smfk 190 seen := {}; 33 34 repr 35 line: integer 0..lines_per_segment; 36 outint: elmt blocks; 37 all_bases: tuple(symbol); 38 39 symdmp: procedure(elmt base_scopes); 40 formdmp: procedure(elmt base_scopes); 41 codedmp: procedure(elmt base_scopes); 42 intdmp: procedure(elmt base_scopes); 43 dmp_int: procedure(elmt blocks, integer); 44 45 base members: elmt base_scopes; 46 mode member: elmt members; 47 48 globals: local mmap(member) symbol; 49 routines: local mmap(member) routine; 50 smfk 191 act_exposd: local mmap(member) symbol; 51 act_reads: local mmap(member) symbol; 52 act_writes: local mmap(member) symbol; 53 act_calls: local mmap(member) routine; 54 55 seen: local set(member); 56 57 print_dir_summary: procedure(elmt base_scopes, string); 58 print_memb_summary: procedure(elmt base_scopes, string); 59 print_member_interface: procedure(member, integer 0..65536); 60 print_proc_summary: procedure(routine); 61 print_decls: procedure( 62 elmt base_scopes, 63 integer 0..65536 ); 64 print_reprs: procedure( 65 elmt base_scopes, 66 integer 0..65536 ); 67 sort_by_name: procedure(tuple(symbol)) 68 tuple(symbol); 69 sort_by_occs: procedure(tuple(occurrence)) 70 tuple(occurrence); 71 end repr; 72 73 74 procedure dmp(scp, tables(*)); 75$ 76$ this is the main dump routine. its arguments are: 77$ 78$ scp: name of scope to be dumped. if 'scope' is omega we dump 79$ all scopes. 80$ 81$ tables: a tuple of strings giving the tables to be dumped. 82$ 83 repr 84 scp: elmt base_scopes; 85 tables: tuple(string); 86 todo: tuple(elmt base_scopes); 87 t: string; 88 sc: elmt base_scopes; 89 end repr; 90 91 if scp = om then todo := scopes; else todo := [ scp ]; end if; 92 93 (forall t in tables) 94 95 case t of 96 97 ('symtab'): (forall sc in todo) symdmp(sc); end; 98 ('formtab'): (forall sc in todo) formdmp(sc); end; 99 ('codetab'): (forall sc in todo) codedmp(sc); end; 100 ('igraph'): (forall sc in todo) intdmp(sc); end; 101 end case; 102 end forall; 103 104 end procedure dmp; 105 106 107 108 109 procedure symdmp(sc); 110$ 111$ this routine dumps the symbols of the scope sc. 112$ 113$ assert if is_atom x then # str x <= 6 else true end; 114$ 115 repr 116 sc: elmt base_scopes; 117 s: symbol; 118 t, undrs: string; 119 end repr; 120 121 title('symbol table dump for ' + name(sc)); 122 123 undrs := line_size * '-'; 124 line := lines_per_segment; 125 126 (for_sym(s, sc)) 127 if line >= lines_per_segment then 128 print('s name form scope alias', 129 'tm in rd wr cn st sk pr rp in sn rc value' ); 130 print(undrs); 131 print; 132 line := 3; 133 else 134 line +:= 1; 135 end if; 136 137 print( 138 rpad(str s, 6), 139 if #(t := name(s)) > 9 then t(1..9) else rpad(t, 9) end, 140 rpad(str form(s), 6), 141 rpad(str scope(s), 6), 142 rpad(str alias(s), 6), 143 if is_temp(s) = om then '- ' else '+ ' end, 144 if is_internal(s) = om then '- ' else '+ ' end, 145 if is_read(s) = om then '- ' else '+ ' end, 146 if is_write(s) = om then '- ' else '+ ' end, 147 if is_const(s) = om then '- ' else '+ ' end, 148 if is_store(s) = om then '- ' else '+ ' end, 149 if is_stk(s) = om then '- ' else '+ ' end, 150 if is_param(s) = om then '- ' else '+ ' end, 151 if is_repr(s) = om then '- ' else '+ ' end, 152 if is_init(s) = om then '- ' else '+ ' end, 153 if is_seen(s) = om then '- ' else '+ ' end, 154 if is_rec(s) = om then '- ' else '+ ' end, 155 if #(t:=str value(s)) > 52 then t(1..52) else t end 156 ); 157 158 if line >= lines_per_segment then eject; end if; 159 end; 160 161 end procedure symdmp; 162 163 164 165 166 procedure formdmp(sc); 167$ 168$ this routine dumps the forms of the scope sc. 169$ 170$ assert if is_atom x then # str x <= 6 else true end; 171$ assert is_string ft_type(fm); 172$ assert # ft_type(fm) <= 9; 173$ assert if ft_mapc(fm) /= om then is_string ft_mapc(fm) else true end; 174$ assert if ft_mapc(fm) /= om then # ft_mapc(fm) <= 7 else true end; 175$ 176 repr 177 sc: elmt base_scopes; 178 fm: elmt forms; 179 el: general; 180 t, undrs: string; 181 end repr; 182 183 184 title('form table dump for ' + name(sc)); 185 186 undrs := line_size * '-'; 187 line := lines_per_segment; 188 189 (for_form(fm, sc)) 190 if line >= lines_per_segment then 191 print('form type mapc elmt dom im ', 192 'base lim tup hash nelt low pos num/elmt'); 193 print(undrs); 194 print; 195 line := 3; 196 else 197 line +:= 1; 198 end if; 199 200 print( 201 rpad(str fm, 6), 202 rpad(ft_type(fm), 9), 203 if (t:=ft_mapc(fm)) = om then ' ' else rpad(t,7) end, 204 205 if is_atom (el:=ft_elmt(fm)) then 206 rpad(str el, 6) 207 else 208 ' ' 209 end, 210 211 rpad(str ft_dom(fm), 6), 212 rpad(str ft_im(fm), 6), 213 rpad(str ft_base(fm), 6), 214 if #(t:=str ft_lim(fm)) > 6 then t(1..6) else rpad(t,6) end, 215 rpad(str ft_tup(fm), 6), 216 if ft_hashok(fm) = om then '- ' else '+ ' end, 217 if ft_neltok(fm) = om then '- ' else '+ ' end, 218 if #(t:=str ft_low(fm)) > 4 then t(1..4) else rpad(t,4) end, 219 if #(t:=str ft_pos(fm)) > 4 then t(1..4) else rpad(t,4) end, 220 221 if is_fbase(fm) then 222 if #(t:=str ft_num(fm)) > 42 then t(1..42) else t end 223 elseif not is_atom el then 224 if #(t:=str el) > 42 then t(1..42) else t end 225 else 226 rpad(str ft_deref(fm), 6) 227 end 228 ); 229 230 if line >= lines_per_segment then eject; end if; 231 end; 232 233 end procedure formdmp; 234 235 236 237 238 procedure print_summary(todo); 239$ 240$ this routine prints the summary for scopes todo. the summary contains 241$ a interface description and the representations chosen for all local 242$ variables. consequently, the information printed by print_symtab is 243$ properly contained in the information printed by print_summary. 244$ 245 init 246 header := ''; 247 248 repr 249 todo: sparse set(elmt base_scopes); 250 sc: elmt base_scopes; 251 header: string; 252 end repr; 253 254 255 (forall sc in scopes | sc in todo) 256 257 if sc_type(sc) /= sc_proc and header /= '' then 258 print; 259 print('end', header); 260 end if; 261 262 print; 263 264 case sc_type(sc) of 265 266 (sc_sys): $ system scope 267 268 print_reprs(sc, 1); 269 270 (sc_lib): $ library scope 271 272 header := 'library ' + name(sc) + ';'; 273 print_memb_summary(sc, header); 274 275 (sc_dir): $ directory scope 276 277 header := 'directory ' + name(sc) + ';'; 278 print_dir_summary(sc, header); 279 280 (sc_prog): $ program scope 281 282 header := 'program '+name(sym_dir)+' - '+name(sc)+';'; 283 print_memb_summary(sc, header); 284 285 (sc_mod): $ module scope 286 287 header := 'module '+name(sym_dir)+' - '+name(sc)+';'; 288 print_memb_summary(sc, header); 289 290 (sc_proc): $ procedure scope 291 292 print_proc_summary(sc); 293 294 end case; 295 296 297 end forall; 298 299 if header /= '' then 300 print; 301 print('end', header); 302 end if; 303 304 305 end procedure print_summary; 306 307 308 309 310 procedure print_dir_summary(sc, header); 311$ 312$ this routine prints the summary for the directory d. 313$ 314 repr 315 sc: elmt base_scopes; 316 header: string; 317 318 d, m: member; 319 p: routine; 320 v: symbol; 321 end repr; 322 323 324 print; 325 print(header); 326 327 d := sc; 328 329 (forall m in all_modules | m notin seen) 330 globals{m} := { v in globalvars | 331 v in uservars and scope(v) = m }; smfk 192 if m = d then continue forall; end if; smfk 193 smfk 194 $ recall that value(m)(5) are the exported procedures of m. smfk 195 routines{m} := { p in routs | membof(p) = m } + value(m)(5); smfk 196 if sc_type(m) = sc_prog then routines{m} with:= sym_main; end; 333 smfk 197 act_exposd{m} := {}+/[ globals_e{p}: p in routines{m} ]; 334 act_reads{m} := {}+/[ globals_r{p}: p in routines{m} ]; 335 act_writes{m} := {}+/[ globals_w{p}: p in routines{m} ]; 336 act_calls{m} := {}+/[ cgraph{p}: p in routines{m} ]; 337 338 seen with:= m; 339 end forall; 340 smfk 198 act_exposd{d} := {} +/[ act_exposd{m} : m in all_modules ]; 341 act_reads{d} := {} +/[ act_reads{m} : m in all_modules ]; 342 act_writes{d} := {} +/[ act_writes{m} : m in all_modules ]; 343 act_calls{d} := {} +/[ act_calls{m} : m in all_modules ]; 344 345 seen with:= d; 346 347 print_decls(sc, 1); 348 349 print; 350 print(' program', name(sym_dir), '-', name(sym_prog) + ':'); 351 print_member_interface(sym_prog, 2); 352 353 (forall m in scopes | sc_type(m) = sc_mod) 354 print; 355 print(' module', name(sym_dir), '-', name(m) + ':'); 356 print_member_interface(m, 2); 357 end forall; 358 359 print_reprs(sc, 1); 360 361 362 end procedure print_dir_summary; 363 364 365 366 367 procedure print_memb_summary(sc, header); 368$ 369$ this routine prints the summary for the member m, where m is not a 370$ directory. 371$ 372 repr 373 sc: elmt base_scopes; 374 header: string; 375 376 m: member; 377 p: routine; 378 v: symbol; 379 end repr; 380 381 382 print; 383 print(header); 384 385 if (m := sc) notin seen then 386 globals{m} := { v in globalvars | 387 v in uservars and scope(v) = sc }; 388 routines{m} := { p in routs | membof(p) = sc }; 389 smfk 199 act_exposd{m} := {}+/[ globals_e{p}: p in routines{m} ]; 390 act_reads{m} := {}+/[ globals_r{p}: p in routines{m} ]; 391 act_writes{m} := {}+/[ globals_w{p}: p in routines{m} ]; 392 act_calls{m} := {}+/[ cgraph{p}: p in routines{m} ]; 393 394 seen with:= m; 395 end if; 396 smfk 200 if sym_dir notin seen then print_member_interface(m, 1); end if; smfk 201 398 print_decls(m, 1); 399 print_reprs(m, 1); 400 401 402 end procedure print_memb_summary; 403 404 405 406 407 procedure print_member_interface(m, indent); 408$ 409$ this routine prints the interface descriptions for member m. 410$ 411 repr 412 m: member; 413 indent: integer 0..65536; 414 415 libs: sparse set(elmt base_scopes); 416 rds, rds_d, wrts_d: sparse set(symbol); 417 imps, exps: sparse set(routine); 418 l: elmt base_scopes; 419 t: tuple(symbol); 420 p, v: symbol; 421 j: integer 0..65536; 422 fill1, fill2: string; 423 end repr; 424 425 426 $ the value of a member is a quintuple, giving (in this order) the 427 $ libraries referenced, the globals read, the globals written, the 428 $ procedures imported, and the procedures exported. 429 [ libs, rds_d, wrts_d, imps, exps ] := value(m); 430 431 $ the imports list of a module is really the union of its declared 432 $ imports list and of all the procedure exported by the libraries 433 $ of its libraries list. 434 imps := imps +/[ value(l)(5) : l in libs ]; 435 436 437 fill2 := (indent*4+3) * ' '; fill1 := fill2(5..#fill2); 438 439 if libs /= {} then $ print libraries list 440 print; 441 print(fill1, 'libraries'); 442 443 t := sort_by_name( [ v : v in libs ] ); 444 445 (forall v in t) 446 print( 447 fill2, 448 rpad(name(v) + if v = t(#t) then ';' else ',' end, 449 (39-indent*4) ), 450 if forall p in value(v)(5) | 451 p notin act_calls{m} then smfk 202 '$ declared but not used.' 453 else 454 '' 455 end 456 ); 457 end forall; 458 end if; 459 460 461 rds := rds_d + { v in act_reads{m} | 462 v notin globals{m} and is_param(v) = om }; 463 if rds /= {} then $ print reads list 464 print; 465 print(fill1, 'reads'); 466 467 t := sort_by_name( [ v : v in rds ]); 468 469 (forall v in t) 470 print( 471 fill2, 472 rpad(name(scope(v)) + '.' + name(v) + 473 if v = t(#t) then ';' else ',' end, 474 (39-indent*4) ), 475 if v notin act_reads{m} then smfk 203 '$ declared but not used.' smfk 204 elseif v notin act_exposd{m} then smfk 205 '$ not used before its first definition.' 477 elseif v notin rds_d and v notin wrts_d then 478 '$ ===used but not declared===' 479 else 480 '' 481 end 482 ); 483 end forall; 484 end if; 485 486 487 if wrts_d /= {} then $ print writes list 488 print; 489 print(fill1, 'writes'); 490 491 t := sort_by_name( [ v : v in wrts_d ] ); 492 493 (forall v in t) 494 print( 495 fill2, 496 rpad(name(scope(v)) + '.' + name(v) + 497 if v = t(#t) then ';' else ',' end, 498 (39-indent*4) ), 499 if v notin act_writes{m} then 500 if v in act_reads{m} then smfk 206 '$ writes declared but only reads used.' 502 else smfk 207 '$ declared but not used.' 504 end 505 else 506 '' 507 end 508 ); 509 end forall; 510 end if; 511 512 513 if imps /= {} then $ print imports list 514 print; 515 print(fill1, 'imports'); 516 517 t := sort_by_name( [ v : v in imps ] ); 518 519 (forall p in t) 520 print( 521 fill2, 522 rpad(name(membof(p)) + '.' + name(p) + 523 '(' +/[ case name(rptyps(p)(j)) of 524 ('rd'): 'rd', ('wr'): 'wr', ('rw'): 'rw' 525 else om end + 526 if j = rnargs(p) then 527 if rvary(p) /= om then '(*)' else '' end 528 else 529 ', ' 530 end : j in [ 1..rnargs(p) ] ] + ')' + 531 if p = t(#t) then ';' else ',' end, 532 (39-indent*4) ), 533 if p notin act_calls{m} then smfk 208 '$ imported but not used.' 535 else 536 '' 537 end 538 ); 539 end forall; 540 end if; 541 542 543 if exps /= {} then $ print exports list 544 print; 545 print(fill1, 'exports'); 546 547 t := sort_by_name( [ v : v in exps ] ); 548 549 (forall p in t) 550 print( 551 fill2, 552 name(p) + 553 '(' +/[ case name(rptyps(p)(j)) of 554 ('rd'): 'rd', ('wr'): 'wr', ('rw'): 'rw' 555 else om end + 556 if j = rnargs(p) then 557 if rvary(p) /= om then '(*)' else '' end 558 else 559 ', ' 560 end : j in [ 1..rnargs(p) ] ] + ')' + 561 if p = t(#t) then ';' else ',' end 562 ); 563 end forall; 564 end if; 565 566 567 end procedure print_member_interface; 568 569 570 571 572 procedure print_proc_summary(p); 573$ 574$ this routine prints the summary for the procedure p. 575$ 576 repr 577 p, q: routine; 578 t: tuple(symbol); 579 v: symbol; 580 o: tuple(occurrence); 581 dvo: occurrence; 582 j: integer 0..65536; 583 end repr; 584 585 586 print(' procedure', name(p) + ';'); 587 smfk 209 if exists v in globals_e{p} | is_param(v) = om then 589 print; 590 print(' reads'); 591 592 t := sort_by_name( smfk 210 [ v : v in globals_e{p} | is_param(v) = om ] ); 594 595 (forall v in t) 596 print( 597 ' ', 598 name(scope(v)) + '.' + name(v) + 599 if v = t(#t) then ';' else ',' end 600 ); 601 end forall; 602 end if; 603 604 605 if exists v in globals_w{p} | is_param(v) = om then 606 print; 607 print(' writes'); 608 609 t := sort_by_name( 610 [ v : v in globals_w{p} | is_param(v) = om ] ); 611 612 (forall v in t) 613 print( 614 ' ', 615 name(scope(v)) + '.' + name(v) + 616 if v = t(#t) then ';' else ',' end 617 ); 618 end forall; 619 end if; 620 621 622 if cgraph{p} /= {} then $ print imports list 623 print; 624 print(' imports'); 625 626 t := sort_by_name( [ q : q in cgraph{p} ] ); 627 628 (forall q in t) 629 print( 630 ' ', 631 name(membof(q)) + '.' + name(q) + 632 '(' +/[ case name(rptyps(q)(j)) of 633 ('rd'): 'rd', ('wr'): 'wr', ('rw'): 'rw' 634 else om end + 635 if j = rnargs(q) then 636 if rvary(q) /= om then '(*)' else '' end 637 else 638 ', ' 639 end : j in [ 1..rnargs(q) ] ] + ')' + 640 if q = t(#t) then ';' else ',' end 641 ); 642 end forall; 643 end if; 644 645 646 $ print the representations of the local variables 647 print_reprs(p, 2); 648 649 650 if globals_du{p} /= {} then 651 print; 652 o := sort_by_occs( [ dvo : dvo in globals_du{p} ] ); 653 654 (forall dvo in o) 655 print( 656 ' ', 657 name(scope(oi_sym(dvo))) + '.' + 658 name(alias(oi_sym(dvo)) ? oi_sym(dvo)), 659 'is used destructively at', oi_stmt(dvo) 660 ); 661 end forall; 662 end if; 663 664 print; 665 print(' end procedure', name(p) + ';'); 666 667 668 end procedure print_proc_summary; 669 670 671 672 673 procedure print_decls(sc, indent); 674$ 675$ this routine prints the global variables defined in scope sc together 676$ with their forms. sc is a static scope, i.e. a librar-, directory-, 677$ program-, or module scope. 678$ 679 repr 680 sc: elmt base_scopes; 681 indent: integer 0..65536; 682 683 m: member; 684 t: tuple(symbol); 685 v: symbol; 686 687 fill1, fill2: string; 688 end repr; 689 690 691 if globals{m := sc} = {} then return; end if; 692 693 fill2 := (indent*4+3) * ' '; fill1 := fill2(5..#fill2); 694 695 print; 696 print(fill1, 'var'); 697 698 t := sort_by_name( [ v : v in globals{m} ] ); 699 700 (forall v in t) 701 print( 702 fill2, 703 rpad(name(v) + 704 if value(v) /= om then ' := ' + value(v) else '' end + 705 if v = t(#t) then ';' else ',' end, (39-indent*4)), 706 if v notin act_reads{m} and v notin act_writes{m} then smfk 211 '$ declared but not used.' 708 else 709 '' 710 end 711 ); 712 end forall; 713 714 715 end procedure print_decls; 716 717 718 719 720 procedure print_reprs(sc, indent); 721$ 722$ this procedure prints the local variables of the scope sc together 723$ with their forms. the list is sorted alphabetically, and takes the 724$ form of a declaration. 725$ 726 repr 727 sc: elmt base_scopes; 728 indent: integer 0..65536; 729 730 v: symbol; 731 t: tuple(symbol); smfk 212 vo: occurrence; 732 733 fill1, fill2: string; 734 end repr; 735 736 737 all_bases := []; $ set of all bases 738 739 $ build a tuple with all variables of sc 740 t := []; 741 (for_sym(v, sc)) 742 if is_fbase(form(v)) then 743 all_bases with:= v; smff 154 elseif '(' in name(v) or '$' in name(v) then 745 pass; $ system internal name 746 elseif v = sym_main then 747 pass; 748 elseif v in variables and is_internal(v) = om then smfk 213 if occsof{v} /= {} smfk 214 and forall vo in occsof{v} | argno(vo) = 1 smfk 215 and oi_op(vo) = q1_asn smfk 216 and arg2(instno(vo)) = sym_om smfk 217 and ffrom{vo} = {} smfk 218 then smfk 219 continue; smfk 220 end if; 749 t with:= v; 750 elseif v in routs then 751 t with:= v; 752 end if; 753 end; $ end for_sym 754 755 $ for program and module scopes, we repeat the declarations for 756 $ exported procedures. for library and directory scopes, these 757 $ symbols have already been added to t in the preceding loop. 758 if sc_type(sc) = sc_prog or sc_type(sc) = sc_mod then 759 $ (the value of a member is a quintuple, giving (in this order) 760 $ the libraries referenced, the globals read, the globals writ- 761 $ ten, the procedures imported, and the procedures exported.) 762 (forall v in value(sc)(5)) t with:= v; end forall; 763 end if; 764 765 if #all_bases = 0 and #t = 0 then return; end if; 766 767 fill2 := (indent*4+3) * ' '; fill1 := fill2(5..#fill2); 768 print; 769 print(fill1, 'repr'); 770 771 (forall v in all_bases) 772 print( 773 fill2, 774 'base', 775 rpad(name(v) + ':', (18-indent*4)), 776 format_form(ft_elmt(form(v))) + ';' 777 ); 778 end forall; 779 780 if #all_bases > 0 and #t > 0 then print; end if; 781 782 (forall v in sort_by_name(t)) 783 print( 784 fill2, 785 rpad(name(v) + ':', (23-indent*4)), 786 format_form(form(v)) + ';' 787 ); 788 end forall; 789 790 print(fill1, 'end repr;'); 791 792 793 end procedure print_reprs; 794 795 796 797 798 procedure sort_by_name(t); 799$ 800$ this function returns the tuple t of symbols sorted by the variable 801$ name of the symbol. 802$ 803 repr 804 t: tuple(symbol); 805 sym: symbol; 806 j, k, m, n, l: integer 0..65536; 807 end repr; 808 809 810 macro before(l, r); $ defines partial order 811 ( name(t(l)) < name(t(r)) ) 812 endm; 813 814 815 if #t = 0 then return t; end if; $ trivial case 816 817 $ sort the variables alphabetically, using heap sort 818 (init n := #t; j := n div 2; while j >= 1 step j -:= 1;) 819 (init k := j; while (l := k+k) <= n) 820 $ which child will be promoted ? 821 m := if l < n and before(l, l+1) then l+1 else l end; 822 823 $ will a child be promoted ? 824 if before(k, m) then 825 sym := t(k); t(k) := t(m); t(m) := sym; k := m; 826 else 827 quit init k; 828 end if; 829 end init k; 830 end init n; 831 832 (init j := n; while j > 1) 833 sym := t(j); t(j) := t(1); t(1) := sym; j -:= 1; 834 (init k := 1; while (l := k+k) <= j) 835 $ which child will be promoted ? 836 m := if l < j and before(l, l+1) then l+1 else l end; 837 838 $ will a child be promoted ? 839 if before(k, m) then 840 sym := t(k); t(k) := t(m); t(m) := sym; k := m; 841 else 842 quit init k; 843 end if; 844 end init k; 845 end init j; 846 847 return t; 848 849 850 end procedure sort_by_name; 851 852 853 854 855 procedure sort_by_occs(t); 856$ 857$ this function returns the tuple t of occurrences sorted by the 858$ variable name and the statement number of the occurrence. aliased 859$ variables are grouped by their original name. 860$ 861 repr 862 t: tuple(occurrence); 863 oi: occurrence; 864 j, k, m, n, l, r: integer 0..65536; 865 end repr; 866 867 868 macro before(l, r); $ defines partial order 869 ( ( name(alias(oi_sym(t(l))) ? oi_sym(t(l))) < 870 name(alias(oi_sym(t(r))) ? oi_sym(t(r))) ) 871 or 872 ( ( name(alias(oi_sym(t(l))) ? oi_sym(t(l))) = 873 name(alias(oi_sym(t(r))) ? oi_sym(t(r))) ) 874 and 875 ( stmtof(instno(t(l))) < stmtof(instno(t(r))) ) ) ) 876 endm; 877 878 879 if #t = 0 then return t; end if; $ trivial case 880 881 $ sort the variables alphabetically, using heap sort 882 (init n := #t; j := n div 2; while j >= 1 step j -:= 1;) 883 (init k := j; while (l := k+k) <= n) 884 $ which child will be promoted ? 885 m := if l < n and before(l, l+1) then l+1 else l end; 886 887 $ will a child be promoted ? 888 if before(k, m) then 889 oi := t(k); t(k) := t(m); t(m) := oi; k := m; 890 else 891 quit init k; 892 end if; 893 end init k; 894 end init n; 895 896 (init j := n; while j > 1) 897 oi := t(j); t(j) := t(1); t(1) := oi; j -:= 1; 898 (init k := 1; while (l := k+k) <= j) 899 $ which child will be promoted ? 900 m := if l < j and before(l, l+1) then l+1 else l end; 901 902 $ will a child be promoted ? 903 if before(k, m) then 904 oi := t(k); t(k) := t(m); t(m) := oi; k := m; 905 else 906 quit init k; 907 end if; 908 end init k; 909 end init j; 910 911 return t; 912 913 914 end procedure sort_by_occs; 915 916 917 918 919 procedure codedmp(sc); 920$ 921$ this routine dumps the code for the scope sc. 922$ 923$ assert if is_atom x then # str x <= 6 else true end; 924$ assert is_string opcode(i); 925$ assert # opcode(i) <= 10; 926$ assert is_string copy_flag(i); 927$ assert # copy_flag(i) <= 9; 928$ 929 repr 930 sc: elmt base_scopes; 931 b: elmt blocks; 932 i: elmt insts; 933 v: symbol; 934 t, undrs: string; 935 j1, j2: integer 0..65536; 936 cstmt_count: integer; 937 ustmt_count: integer; 938 end repr; 939 940 title('code table dump for ' + name(sc)); 941 942 undrs := 80 * '-'; 943 line := 0; 944 945 cstmt_count := ustmt_count := sc_estmt_ct(sc); 946 947 (for_block(b, sc)) 948 print('routine:', rpad(name(routof(b)), 10), 949 ' interval:', rpad(str intof(b), 6), 950 ' block:', rpad(str b, 6), 951 ' statement:', cstmt_count-ustmt_count+1 952 ); 953 print; 954 print('i opcode copy_flag shr arguments'); 955 print(undrs); 956 line +:= 4; 957 958 (for_inst(i, b)) 959 if opcode(i) = q1_stmt then 960 cstmt_count +:= 1; 961 t := rpad(str (cstmt_count-ustmt_count+1), 10) + ' ' + 962 rpad(str cstmt_count, 10) + ' '; 963 else 964 t := '' +/[ rpad(name(v), 10) + ' ' : v in args(i) ]; 965 end if; 966 967 print( 968 rpad(str i, 6), 969 rpad(opcode(i), 10), 970 rpad(copy_flag(i) ? '', 9), 971 if share_flag(i) = om then ' ' else '+ ' end, 972 t(1..(48 min #t)) 973 ); 974 975 if line >= lines_per_segment then 976 eject; 977 print('i opcode copy_flag shr arguments'); 978 print(undrs); 979 line := 2; 980 else 981 line +:= 1; 982 end if; 983 984 loop init j1 := 1; j2 := 48; 985 doing j1 +:= 48; j2 +:= 48; 986 while j1 <= #t 987 do 988 print(rpad('', 31), t(j1..(j2 min #t))); 989 990 if line >= lines_per_segment then 991 eject; 992 print('i opcode copy_flag shr arguments'); 993 print(undrs); 994 line := 2; 995 else 996 line +:= 1; 997 end if; 998 999 end loop; 1000 1001 end; $ end for_inst; 1002 1003 if lines_per_segment - line <= 8 then 1004 eject; 1005 line := 0; 1006 else 1007 print; print; 1008 line +:= 2; 1009 end if; 1010 1011 end; $ end for_block; 1012 1013 end procedure codedmp; 1014 1015 1016 1017 1018 procedure intdmp(sc); 1019 1020$ this routine dumps the interval graph for scope 'sc' 1021 1022 repr 1023 sc: elmt base_scopes; 1024 end repr; 1025 1026 if sc notin routs then return; end if; 1027 1028 title('interval graph dump for ' + name(sc)); 1029 line := lines_per_segment; 1030 1031$ walk the graph recursively starting with the entry point 1032 outint := rentry(sc); 1033 dmp_int(rentry(sc), 0); 1034 1035 end procedure intdmp; 1036 1037 1038 1039 1040 procedure dmp_int(i, indent); 1041$ 1042$ this routine dumps the interval graph for the inteval i. 1043$ 1044 repr 1045 i: elmt blocks; 1046 m, n: elmt blocks; 1047 t, undrs: string; 1048 j1, j2: integer 0..65536; 1049 end repr; 1050 1051 undrs := 80 * '-'; 1052 1053 (forall n in int_nodes(i)) 1054 1055 if line >= lines_per_segment then 1056 print('interval nodes cessors'); 1057 print(undrs); 1058 line := 2; 1059 end if; 1060 1061 t := '' +/[ rpad(str m, 9) : m in cessor{n} | 1062 intof(m) /= n or n = outint ]; 1063 print( 1064 lpad(rpad(str i, 8), ((indent+1) min 6)*9-1), 1065 rpad(str n, 6), ':', 1066 t(1..( (80 - ((indent+2) min 8)*9) min #t) ) 1067 ); 1068 line +:= 1; 1069 1070 if line >= lines_per_segment then 1071 eject; 1072 print('interval nodes cessors'); 1073 print(undrs); 1074 line := 2; 1075 end if; 1076 smff 155 loop init j1 := 1; smff 156 doing j1 +:= (80-((indent+2) min 8)*9); 1080 while j1 <= #t 1081 do 1082 print( 1083 rpad('', ((indent+2) min 8) * 9 - 1), smff 157 t(j1..(j1 + (80-((indent+2) min 8)*9)) min #t) 1085 ); 1086 line +:= 1; 1087 1088 if line >= lines_per_segment then 1089 eject; 1090 print('interval nodes cessors'); 1091 print(undrs); 1092 line := 2; 1093 end if; 1094 end loop; 1095 1096 $ descend recursively 1097 if n /= outint and int_nodes(n) /= om then 1098 dmp_int(n, indent+1); 1099 end if; 1100 1101 end forall; 1102 1103 end procedure dmp_int; 1104 1105 1106 end module setl_optimizer - dumps; 1107 1108