Views
SETL Optimizer
by
Paul McJones
—
last modified
2021-02-24 17:27
- History
-
Action Performed by Date and Time Comment Publish Paul McJones 2021-02-24 17:27 No comments.
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