Appendix A contains the following files in one CD-ROM (of which two identical copies are attached hereto) and is a part of the present disclosure and is incorporated by reference herein in its entirety.
The above files contain source code for a computer program written in the C language for one embodiment of the invention that implement expression simplification (file ATSIMP which illustrates
A portion of the disclosure of this patent document contains material that is subject to copyright protection. The copyright owner has no objection to the facsimile reproduction by anyone of the patent document or the patent disclosure, as it appears in the Patent and Trademark Office patent files or records, but otherwise reserves all copyright rights whatsoever.
Dynamically-typed programming languages (such as the MATLAB® programming language) provide a powerful prototyping and development mechanism for programmers. Because such programming languages allow variables to take on the types of expressions that are assigned to them during program execution, programmers do not have to worry about details such as declaring the variable types or creating functions specific for a given variable type. Such languages support a programming style where programmers create (or in some cases, recreate) variables based on local contexts. Variables are frequently used in several different ways and for several different purposes because programmers basically just create variables as they need them.
While dynamically-typed languages support a relaxed programming style for programmers, they present significant challenges for the programming tools that support them. In particular, the most obvious methods for executing dynamically-typed languages provide extremely slow execution speeds. The result is that programmers cannot develop large applications in a dynamically-typed language because a program of any significant size requires too much time to run. The key to making dynamically-typed languages useful is optimizing their execution performance, increasing their execution speed and thereby decreasing the time required to execute programs of any significant size. The technology behind such execution improvement is commonly called “code optimization”, and the tool used to effect those improvements is commonly called a “code optimizer” or just “optimizer”.
Optimizers work by “statically” analyzing a program prior to its execution (or its “run-time”) to predict how the program will behave when executed on input data. Using those predictions, optimizers change the code that is executed so as to minimize the run time required to perform the calculation. In a very simplistic example, an optimizer will analyze a program that always computes and prints “7*6”, and realize that the program will always print “42”. In such a case, the optimizer will remove all instructions used in the computation, and leave in only the instructions required to print “42”. The effectiveness of an optimizer depends on its ability to predict, prior to program execution, how a program will behave when it executes.
Dynamically-typed languages present a significant challenge for optimizers, since by their very nature dynamically-typed languages hide information until execution time. Since the optimizer has less information prior to execution about how a program behaves when it executes, the optimizer is less able to statically predict program behavior and is thereby limited in its ability to improve program execution. One significant piece of information that is missing in dynamically-typed languages by their very definition is the “types” of variables. A compiler generates very different code for the addition of two integers than it does for the addition of two double precision numbers. Without knowledge of whether two variables that are being added are integer, double precision, or some other type, a compiler cannot generate code to do the addition directly. Instead, it must generate code that checks the types at runtime and selects the appropriate instruction sequence. Such checking sequences are very inefficient.
The MATLAB® programming language (as defined by the MATLAB interpreter version 14.3) is one example of a dynamically-typed language. Variables are not explicitly declared by users writing programs; instead, variables inherit their types from the type of the expressions assigned to them.
The MATLAB programming language is defined by the actions of the interpreter provided for the language by The MathWorks, Inc. Interpreters are useful programming tools for dynamically-typed languages, in that they provide a mechanism naturally suited for resolving typing questions during execution. Interpreters create and maintain an execution state environment (such as a symbol table) while they dynamically execute a program. This environment allows an interpreter at any point during execution to examine the state of the program, including the values and types that have been assigned to variables. This environment allows an interpreter to easily determine the types of variables while executing and to decide then the appropriate sequence of instructions to employ. Such code, while easy for the user to program, is very inefficient in terms of execution speed—the dynamic selection of code sequences imposes heavy overhead. As a result, users are limited in the sizes of the programs that they can write in such languages. As a general rule, the larger the program, the longer it takes to execute, and given the slow execution speed of interpreted code. Being able to compile programs written in dynamically-typed languages provides many benefits: improved execution speed, the ability to implement such programs in contexts where the virtual machine is not available, reduced memory, etc. Effecting that compilation, however, requires type information on variables and expressions. The invention disclosed in this application allows more programs written in dynamically-typed languages to be more efficiently compiled.
For clarity, this application will refer to the “type” of a variable or expression as that property which indicates what kinds of operations can be performed on the variable or expression, how those operations are performed, and how many times those operations are performed. Types are comprised of two pieces of information: a “base type” and a “shape”. The “base type” indicates what kinds of operations may be performed on the variable and how those operations will be performed. For example, a variable whose base type is integer can be used in operations such as addition, subtraction, multiplication, and division. In most computer languages, a variable whose base type is boolean typically cannot be used in these operations. The operations of addition, subtraction, multiplication, and division can also all be performed on double precision variables, but the method by which the operations are performed and the results are very different than the methods and results on integers. Examples of base types include integer, single precision, double precision (also called “real”), complex, fixed-point real, character, Boolean, and fixed-point complex.
The “shape” of a variable's type indicates the number of elements in the variable and how those elements are accessed. The simplest shape is “empty”, which indicates that the type contains no elements. A “scalar” type indicates only one element. More interesting are types with more than one element. Elements may be arranged linearly, so that each element is accessed by a single unique index (i.e. A(1), A(2), or A(n)). Such shapes are commonly referred to as “vectors”. Alternatively, the elements may be arranged as a collection of vectors, accessed by two indices (i.e. A(2,3) or A(n,n)). One index indicates which vector to select; the second element which element of that vector to select. Such a shape is called a “matrix”. Two special matrices are ones where there is only one vector but some number of elements in that vector, which is often called a “row vector” and ones where there is only one element in each vector but some number of vectors, which is often called a “column vector”. Similarly, the elements may be arranged as a collection of matrices, accessed by three indices: one indicating which matrix to select; one indicating which vector to select from that matrix; and one indicating which element to select from that vector. This process can be continued ad infinitum, leading to two essential parts of the “shape” of a type. One part is the number of dimensions, or how many indices are used to access the elements. The other part is the number of elements in each dimension (i.e. how many elements in each vector; how many vectors in each matrix; how many matrices in each 3-dimensional order object). The product of the number of elements in each dimension gives the total number of elements associated with the type. While examples are most frequently presented with just base type, the methods disclosed in this application apply to both base type and shape, and “type” refers to the combination of the two components.
Programs are comprised of variables, expressions, and statements. Variables represent symbols in a users program. An expression represents a collection of variables and the operations performed among them; for instance “a+b”. “a” and “b” are variables, and the entire “a+b” is an expression. Null operations are also allowed in the definition of an expression, so “a” is both a variable and an expression. Function invocations are also expressions. Statements are representations that affect control flow or perform assignments.
Most computer languages support types such as real, integer, character, Boolean, double, and complex. Not so common, but important in many contexts, is a fixed-point type. Fixed-point types are integers with an implied decimal point other than at the right of the integer. They have a specified number of bits to the left of the decimal, a specified number of bits to the right of the decimal, and may be signed or unsigned. Because fixed-point types have a specified precision, which cannot be increased without changing their semantics, they cannot be straightforwardly performed with integer or double arithmetic.
“Type lattice” is a term often associated with types (see the books by Cooper et al. or by Aho et al. for more details). Because some types can accommodate all the operations encompassed by other types (for instance, real numbers can be represented as complex numbers with a 0 imaginary component), types have a hierarchy. This hierarchy forms a lattice, where types that are higher in the lattice can perform the operations of those types underneath which can reach them. There are some types that alone in the lattice; ie., they are the only types that can perform their operations with the specific semantics. For instance, Booleans cannot be exactly emulated by other types.
The essential aspect of a dynamically-typed language is that a user does not have to explicitly specify (or “declare”) the type of a variable before the variables use. Instead, he simply assigns an expression to the variable; the variable then takes on the base type and shape of that expression. By assigning a value to the variable, the user is defining it, but not explicitly declaring it, and the same variable may have different types in different parts of a program. The concept of “define” is assigning a value to a variable. The concept of “declare” or “explicitly specifying” type is forcing a variable to be of a specified type. For instance, in C, variables are declared. If a variable is declared to be of type character, and an array of double precision values is assigned to it, it is an error and the program is incorrect. In MATLAB, if a variable is first used as a character variable, then an array of double precision values is assigned to it, the variable silently becomes an array of double precision.
In addition to being dynamically-typed, MATLAB is also polymorphic. This means that a function or statement written in it will work on variables with any type. That is, the statement “a=b+c” will execute for b and c being integers, double, complex, or fixed-point. This feature is very handy for functions, and is often associated with dynamically-typed languages.
Dataflow analysis frameworks, lattices, and techniques are well known in the art and are discussed fully in Chapter 4 of a book by Allen, Randy and Kennedy, Ken entitled “Optimizing Compilers for Modern Architectures”, Morgan Kaufmann publishers, 2002. This chapter is incorporated by reference herein in its entirety. The goal of dataflow analysis is to relate each “use” of a variable in the program (where “use” means any programming construct that may read or in any other way use the value that the variable contains in the computer's memory) to all possible “definitions” of that variable in the program (where “definition” means any programming construct that may set or change the value that the variable contains in the computer's memory) that can possibly set the value that the use may receive. “Definitions” are also commonly called “defs”. A “reference” (or “ref”) is any form of reference to a variable, either a use of the variable or a definition of the variable
It is well known in the art how to go from a definition of a variable to all locations in a computer program that may use the definition at execution time. Specifically, a “definition-use chain” is a data structure that is commonly used to perform such an operation. A definition-use chain is comprised of nodes and edges, where nodes represent variable references in the user's program, and an edge exists between two nodes when one node is a definition whose value may be used by the second node. In other words, an edge connects a definition to all possible runtime uses of that definition. While edges are normally indicated as going from definition to use, following the flow of data within the program, they may be as easily thought of as flowing from use to def (indicating a use that needs a value defined by the def), and a skilled artisan can easily construct data structures that allow both forms to be used. Note that the term “definition-use graph” is more appropriate than the traditional “definition-use chains” because “graph” more correctly characterizes the nature of the information the data structure contains. The definition-use chain (or graph) is essentially a scalar version of true dependences within a program.
Constructing definition-use edges within a single straight-line block of code is well known. One visits each statement in order in the basic block, noting the variables defined by each statement as well as the variables used by each statement. For each use, an edge is added to the definition use graph for that use back to the last exposed definition in the block of that variable—in other words, to every definition that reaches the use. Whenever a new definition is encountered for a variable, the new definition kills (i.e. over-writes) the existing definition, so that later uses are linked only to the new definition, not to the old. When the end of the block is reached, the definition use graph is complete.
Constructing a definition-use graph across a program comprised of more than a single straight-line block of code is more complicated. Standard art contains many different methods for computing definition-use graphs for programs containing control flow, many of which are summarized in Chapter 1 by Kennedy, Ken entitled “A survey of data-flow analysis techniques”, In a book by S. S. Muchnick and N. D. Jones, editors, “Program Flow Analysis: Theory and Applications,”, pp. 1-51. Prentice Hall publishers, 1981. At a high level, the methods all work by decomposing a program into simpler units (basic blocks, intervals, or others) and a control flow graph indicating the flow between the units. In a local pass, information is computed for each individual unit, regardless of the control flow among the units. Such information typically consists of sets of variables that are used, defined, killed (“kills” are definitions where all existing values in a variable can safely be assumed to be replaced), and reaches (“reaches” are definitions that can reach a given use). This local information is then combined into global information by propagating it along the control flow graph, using any of a number of dataflow propagation techniques (including iterative, interval, parse, and others). After the global information is available for the whole program, a definition-use graph can then be constructed by distributing the information back across the local units.
Most techniques for propagating dataflow information (i.e. iterative, interval, and so on) are theoretically based on framing the problem inside a lattice, also referred to in this document as a dataflow framework. A lattice, as defined in S. Muchnick, Advanced Compiler Design and Implementation, Morgan Kaufmann, 1997, consists of a set of values and two operations “meet” and “join”, both of which are closed, commutative, associative, distributive (in this document, but not in general), and monotonic (again in this document but not in general). A lattice also has two designated elements “top” and “bottom”. All the dataflow propagation techniques discussed so far can be applied to any problem that can be embedded in such a lattice. Propagating uses and definitions of variables is certainly one type of information commonly embedded in a lattice, but other information is as well. Much of the prior art described in this document is based on formulating a lattice based around other information than definitions and uses of variables.
When definitions and uses are propagated through a lattice, it is often convenient to abstract the resulting flow of data in a definition-use graph. Definition-use graphs can be embodied in a number of different forms, including linked lists, bit matrices, sets, bit vectors, etc. While the description of the techniques most often refers to a linked list of edges, skilled practitioners will readily recognize that all representations are equivalent in terms of the application of this invention.
“Type propagation” is a term used frequently in the literature, but with different meanings. “Type propagation” to many practitioners means propagating a type from a definition of a variable in one statement to a use of the variable in a statement, typically utilizing a data structure such as def-use chains or other representations of reaching information. This is the definition for “type propagation” assumed in this application. “Type propagation” to other practitioners means propagating the results of a type up an expression tree: i.e. if an expression is an addition of two integers, propagating the integer type from the variables to the expression node representing the addition. This application will refer to this operation as “type balancing”. “Type propagation” always proceeds from definitions to uses; type balancing occurs on expression trees, but not across statements.
“Entry points” and “entry nodes” are well defined terms in compiler literature. An entry point is a program location by which control may enter a function. In many programming languages, that is a single statement, such as in MATLAB, where the function header is the only entry point. In other languages, such as FORTRAN, multiple entry points into a procedure are supported, and any of those serves as an entry point. For analysis, compilers often simplify programs with multiple entry points by creating one unique entry point and by making the multiple entry points labels. When control reaches the unique entry point, it immediately branches to the appropriate label representing the former entry point to which control was to transfer. An “entry node” is the intermediate representation of the unique entry point.
Compilers typically work on programs by first translating them into an “intermediate representation” which is more convenient to work with than textual representations. There are many forms of intermediate representations in use. One of the primary reasons for intermediate representations is to facility “program transformations”. A program transformation is any transformation which changes the intermediate representation of a users program (hopefully in a beneficial way) without changing the semantics of his program. Commonly used program transformations include a) constant propagation (replaces uses of variables whose values are known to be constant with the constant value), b) constant folding (simplifying an expression, all of whose operands are constant, into a constant, e.g. 1+1 is replaced with 2), c) symbolic simplification (simplifying symbolic versions of known identities, e.g. i−i is replaced with 0), d) reassociation (changing the order in which associative operations are performed, e.g. 1+b−1 goes to 1−1+b), e) function evaluation (evaluation a function that has constant arguments), f) expression simplification (applying a wide range of techniques to make expressions simpler in form), g) identity replacement (replacing known identities, such as x+0 by x), h) unreachable code elimination (eliminating code which can never be executed), i) type propagation (propagating known types forward to their uses), j) type balancing (building up the types of expressions from the types of their components), and k) Boolean shortcircuiting (0 && x is replaced by 0).
Two common program transformations which are often confused in the literature are unreachable code elimination and dead code elimination. “Dead code” are statements which are executed during the flow of execution of the program, but whose results are never used. For instance, a program which always assigns a value of 5 to a variable “x”, but never uses “x” again, has dead code. “Unreachable code” is code that will never be executed in any flow of the program. Since it can never be executed, its computation cannot impact the program. The invention in this application is focused around unreachable code elimination, not dead code elimination.
“Worklist” is any data structure which supports two operations: “add” (remember the entry; if the entry has all been added and not retrieved, do nothing) and “retrieve” (return a “remembered entry” and no longer remember it). No relationship is implied on the order on which entries are retrieved. Examples of worklists include queues (entries are retrieved in the order in which they are added), stacks (entries are retrieved in the inverse order in which they are added), heaps (entries are retrieved based on a property), and priority heaps (entries are retrieved based on two properties).
The invention disclosed in this application is focused on solving the problem of uncovering the types of variables and expressions within a single procedure or function. An equally important problem is uncovering types across function calls; that is, procedure a calls procedure b, which calls procedure c: what are the types of the parameters in procedure c for this call chain, and what is the type if the return value that procedure c passes back. These two problems are obviously related (you must solve the problem of one function to answer the call chain, and you must solve the call chain to know what parameters to pass into called procedures). This application assumes methods are in place for solving the call chain problem that will deliver the types of parameters. Because the two problems are related, there are no assumptions about the order of the two procedures.
In “Compiling High-Level Languages to DSPs” in the May 2006 issue of IEEE Signal Processing, John R. Allen described a method for inferring types in a dynamically-typed program. An earlier implementation propagated type information, without performing repetition of acts that are part to this invention. Because this invention uses those methods as a basis for acts,
In accordance with several methods of the prior art, propagating one of definitions forward, the type of the use of “cos_t” in S7 is discovered to be either integer or real of unknown length. Assuming a meet function, the resulting type for “cos_t” in S7 will be a real vector of unknown length. The initial type propagation step is then complete.
Summarizing, the type information known at the end of the type propagation performed just once (illustrated in
After propagating types, embodiments illustrated in
Other program transformations effected in act 150 of embodiments illustrated in
If type propagation (act 140) were to be re-applied to this intermediate representation, the results would be essentially identical to those obtained in
At this point, prior art has uncovered as much type information as is possible for it. Other optimizations may be invoked (for instance,
A computer is programmed in accordance with the invention to identify types of variables, in a computer program which includes a number of variables that are used without any explicit indication of their type, by repeatedly performing at least propagation of types from variables' definitions to variables' uses and removal of unreachable code. Performance of type propagation after removal of unreachable code is a critical aspect of the invention. In some embodiments an operation, which includes type propagation and unreachable code elimination, is repeatedly performed in a loop. The repetition can be terminated differently in different embodiments. In many embodiments, the repetition is performed until no unreachable code is found.
Specifically, the computer first initializes the type of each variable to unknown, then transforms a representation of the program (while preserving its semantics) by use of certain program transformations in order to uncover and propagate types, from statements which define the type of the variables (with or without the user declaring the type of each variable) to statements which use the type of the variables. Upon completion of the program transformations, a changed representation of the computer program results which associates one or more types with a variable in the program whose type was previously unknown. At that point, the computer is programmed to check if the changed representation comprises unreachable code; if so, the unreachable code is eliminated from the changed representation. As noted above, type propagation is performed again after elimination of unreachable code in one aspect of the invention to develop more precise information about variables' types (for example, a variable that is initially associated with multiple types becomes associated a single type after unreachable code elimination followed by type propagation). Program transformations that are used in some embodiments include constant propagation and constant folding. In some embodiments, initial types other than unknown may be used for some of the variables, e.g. based on user input.
In some embodiments, the repetition of an operation including the described acts of initialization, program transformation, type association, and unreachable code detection refines the type information available in a representation of the computer program, so that a variable that is first associated with multiple types is later associated with a single type. Some embodiments of the invention construct a structure of definitions and uses for names in the computer program (also called “def-use chains” or a “def-use graph”), including the name of the variable whose type is made more precise.
In some embodiments, the computer is programmed to check if after each repetition of the operation every variable in the program representation is associated with a single type, and if so, some embodiments are further programmed to indicate the fact that the program is compilable to a user. Some embodiments proceed further to automatically compile programs where a final representation of the computer program associates every variable with a single type. If after the repetition of the described acts any variables are associated with multiple types, some embodiments of the invention identify one or more such variables to the user or report that the program is not compilable.
Some embodiments of the invention perform an extra repetition of the described operation after the termination condition has been reached (for example, after no unreachable code is detected). During this extra repetition, these embodiments use a different set of rules for associating types with variables than the rules used in earlier repetitions. Some embodiments indicate unsupported use of types only during this extra repetition of the operation.
After receiving said program and converting it into a convenient intermediate representation, embodiments of a computer that has been programmed in accordance with this invention begin a repetition of an operation which includes type propagation and unreachable code elimination. Type propagation after unreachable code elimination is performed in some embodiments of the invention because inventors of the current application have found that such a sequence may yield more precise information and/or more accurate information about the type of one or more variables in certain computer programs which appear non-compilable if evaluated by traditional measures after these two acts are performed just one time. More specifically, type propagation after unreachable code elimination provides unexpected results, in the form of an intermediate representation which is compilable, although prepared from traditionally non-compilable computer programs. Neither the sequence nor the unexpected results are anywhere disclosed or suggested by any prior art which is known to the inventors of this invention.
Some embodiments of the invention illustrated in
Once act 130 completes, embodiments illustrated by
In response to such indication by the programmed computer, the user may re-write the computer program (e.g. to explicitly indicate the types of certain variables or to rename multiple occurrences of one or more of the certain variables in the computer program), or alternatively the user may indicate to the programmed computer a specific type to be associated with one or more of the certain variables, depending on the embodiment. Hence, several embodiments repeat the just-described process shown in
A computer may be programmed in some embodiments of the type shown in
The current inventors' recognize that elimination of unreachable code provides an opportunity to make traditionally uncompilable computer programs compilable by performance of type propagation again. The inventors have further recognized that traditionally compilable computer programs are more efficiently compiled after repetition of acts 140 and 150 and subsequent to act 160. Such repetition of acts 140 and 150 is nowhere disclosed or suggested in any prior art known to the inventors.
Wegman and Zadeck in “Constant propagation with conditional branches” (ACM Transactions on Programming Languages and Systems, Vol 13, No. 2, pages 181-210, April 1991) present a method for propagating constants and removing unreachable code that does not require repetition via looping as illustrated in
Embodiments of
Embodiments of
Embodiments of
Many embodiments store type information for both symbols and for program representation in separate tables as well, as illustrated by structures 320 and 340 in
Some embodiments build a dataflow graph (also common called “def-use graph” or “def-use chains” in the literature) in the computer memory, as illustrated by structure 350. This structure links the definitions and uses of variables in the program representation, as indicated by the links from structure 350 to structures 310 and 330. This structure typically includes names for variables in the computer program. The essential aspect of this structure is that it relates the definitions of variables in the program representation to the uses of those variables; a skilled artisan will recognize in light of these disclosures that there are many ways of effecting this relationship.
Some embodiments (for instance, those illustrated in
Some embodiments perform constant propagation and type propagation using worklists to drive the operation. These structures are illustrated by structures 380 and 390, and are held in computer memory, and used as discussed below.
Embodiments of
The order in which the constant worklist 380 and the type worklist 390 are checked is not significant in this invention. Some embodiments test the type worklist first; some embodiments test the constant worklist first. As detailed more fully in
Once the join of all incoming definition types is found, the type of the use is set to that join (act 525). Note that a use may be visited multiple times (in fact, it probably will) and that its type may change during those visits. In the early stages of type propagation, most definitions have unknown types. In embodiments that use the type lattice embedded in
Once the type of the use is set (act 525), the statement containing the use is both simplified (using any standard technique for expression simplification) and type balanced (using any standard technique for type balancing expressions or propagating types up the intermediate representation); both are performed in act 525.
Going through
Act 505 indicates that some embodiments of the invention then loop, repeatedly executing acts 510, 515, 520, 525, 530, 535, 540, 545, until all the uses of “sym” have been examined.
In act 510, one of the unexamined uses of “sym” is chosen, and designated as “u”. Said unexamined use “u” is marked as examined, and a temporary variable designated as “current_type” is set to “sym_type”.
In act 515 and 520, all the definitions which reach the use “u” are examined, and “current_type” is updated. Act 515 indicates a loop in which each definition reaching “u” is designated by the temporary variable “d”. In act 520, for each definition “d” indicated by the loop of act 515, the variable “current_type” is updated to the type determined by joining the “current_type” with the type produced by the definition “d”.
When all the definitions which reach “u” have been processed by act 515 and act 520, the type of “u” is set to “current_type”, the statement containing “u” is simplified and type balancing is performed on said statement.
In act 530, some embodiments of this invention determine if, after the program transformations applied during simplification in act 525, the statement which used to contain “u” now produces a constant value. If the result of said statement is now of constant value, in act 535 an entry referring to said statement is added to the constant work list.
In act 540, some embodiments of this invention determine if, after the program transformations applied during simplification in act 525, the statement which used to contain “u” now produces a known type. If the result of said statement now produces a known type, in act 535 an entry referring to said statement is added to the type work list.
Going through
Act 555 indicates that some embodiments of the invention then loop, repeatedly executing acts 560, 565, 570, 575, 580, 585, 590, 591, 595, and 596, until all the uses of “sym” have been examined.
In act 560, one of the unexamined uses of “sym” is chosen, and designated as “u”. Said unexamined use “u” is marked as examined, and a temporary variable designated as “all_the_same” is set to TRUE.
In acts 565, 570, and 580 all the definitions which reach the use “u” are examined one at a time to determine if they all cause the “u” to be of the same constant value. A loop over all the definitions “d” which reach “u” is indicated by act 565. In act 570, if “d” does not produce a constant value, or if the constant value produced by “d” differs from “const_val”, “all_the_same” is set to FALSE in act 580.
In act 575, if all the definitions which reach “u” do not produce the value “const_val” as indicated by “all_the_same” not being set to TRUE, the constant value is not propagated, and control returns to act 555 to examine the next unexamined use of “sym”. If “all_the_same” is true, control continues to act 585.
In act 585, the program is transformed such that “u” is replaced by “const_val”. The statement containing “u” is simplified and type balancing is performed on said statement, using any of the numerous techniques available from literature.
In act 590, some embodiments of this invention determine if, after the program transformations applied during simplification in act 585, the statement of the computer program which used to contain “u” now produces a constant value. If the result of said statement is now of constant value, in act 591 an entry referring to said statement is added to the constant work list.
In act 595, some embodiments of this invention determine if, after the program transformations applied during simplification in act 585, the statement which used to contain “u” now produces a known type. If the result of said statement now produces a known type, in act 596 an entry referring to said statement is added to the type work list.
As example of a part of the operation of type balancing performed on statements,
In act 600, the type of the function invocation is initialized to unknown. In act 610, it is determined from the type of “x” if the number of dimensions of the array “x” is known. If the number of dimensions of the array “x” is unknown, then the function type remains unknown as indicated in act 630.
If the number of dimensions is known, in act 620 it is determined if the number of dimensions is less than or equal to 2. If the number of dimensions is less than or equal to 2, then in act 650, the type of the result of the function invocation is set to a 1×2 array of integers. If the number of dimensions is >2, then in act 640, the type of the result of the function invocation is set to 1×(number of dimensions of x) array of integers.
In act 700, it is determined if the number of parameters input to the function is one. If the number of parameters is one, then in act 710 it is determined if the type of x is known. If the type of x is known, then in act 720 it is determined if all type information has been propagated to this use of x. If the condition of act 720 is true, then in act 730, it is determined if the number of dimensions of the type of the variable x is constant. If the condition of act 730 is true, then the program is transformed such that the function invocation ndims(x) is replaced by the integer constant “number of dimensions of x”.
In act 800, some embodiments of this invention visit all the computations in the expression in a loop. For each computation in the expression, as illustrated in act 810, all input expressions to the computation are simplified, in some embodiments of this invention by recursive execution of the expression simplification method as indicated by act 820. In act 830, it is determined if all the inputs to the computation are constant. If the condition of act 830 is true, then in act 840 the computation is performed on using the constant inputs in the program representation. In act 850, the program representation is transformed by replacing the expression with the constant result of the computation as computed in act 840.
If a variable in the program can be set to several different types based on different possible execution paths, then the computer is programmed to combine these types to find a type which can hold all the possible different types to which the variable can be set. The join step on two types “t1” and “t2” is performed using the type lattice by determining the first point in the type lattice where paths from the two types meet.
One example of the operation of the type lattice can be seen if type t1 is integer and type t2 is double. The type lattice in
Another example of the operation of the type lattice can be seen if type t1 is fixed point <1, 15, u>, a 16-bit unsigned number with 1 bit to the left of the binary point and 15 bits to the right of the binary point, box 1020, and type t2 is fixed point <8, 8, s>, a 16-bit signed number with 8 bits to the left of the binary point and 8 bits to the right of the binary point, box 1030. In this example the two types are first joined at the box 1000 which indicates that there is in some embodiments of this invention, no support in the computer's architecture to represent the join of these two types by a single type, and the program is determined not to be compilable.
Type lattices can be implemented in a number of ways, including direct coding, table lookups, and bit manipulations.
In this process the computer program is transformed so as to remove any statements from the intermediate representation which will never be executed. In some embodiments of this invention, the process illustrated in
In act 1205 illustrates the setting of the “unreachable_code_found” flag to false to indicate that no unreachable code has been found. Act 1210 illustrates the visiting of all statements in the program. For each visited statement, in act 1220, one specific transforming act is selected from acts 1230, 1240, 1250, 1260, and 1270, depending on the statement kind. Act 1280 indicates that there are a plurality of other possible statement kinds and corresponding transforming processes which produce the result of unreachable code elimination which were not included as a part of this example. Act 1290 indicates the return of the determination of whether or not any unreachable code was found and eliminated.
Act 1230 is selected if the statement to be examined is an assignment statement.
If the condition in act 1310 is not true, then it is determined in act 1330 if the left had side is used. If the condition of act 1330 is true, then the statement is converted from an assignment statement to an expression statement in act 1340.
Act 1240 is selected if the statement to be examined is an IF statement.
In act 1370, it is determined if the control expression for the IF statement is always true. If the condition of act 1370 is determined to be true, then the program is transformed such that the IF statement and the else part of the IF statement are replaced by the then part of the IF statement in act 1380. Act 1380 also sets the “unreachable_code_found” flag to true.
In act 1390, it is determined if the control expression for the IF statement is always false. If the condition of act 1390 is determined to be true, then the program is transformed such that the IF statement and the then part of the IF statement are replaced by the else part of the if statement in act 1400. Act 1400 sets the “unreachable_code_found” flag to true.
Act 1250 is selected if the statement to be examined is a WHILE statement.
Act 1260 is selected if the statement to be examined is a FOR statement.
Act 1270 is selected by the programmed computer if the statement to be examined is an expression statement. An expression statement is a statement placeholder for holding expressions that are treated like statements. As an example, in C (and MATLAB), it is possible to write the statement “1;”. The result of that statement is “1”, but since nothing is done with it, the statement is determined to be “dead code”, which may be removed. Similarly, the expression statement “1+1;” is legal, but is also dead code. Expression statements that may be useful are function calls (i.e. “f(x)”) where the function has side effects, such as printing out a message or setting a global variable.
In the first iteration of the process described in
In the first iteration of the process described in
If the constant value of TRUE was propagated to the variable “p” in the control expression of the IF statement, then box 1610 illustrates the program after the completion of step 160 of
If another iteration of the process of
As another example of the improvements offered by a programmed computer performing the acts illustrated in
Obtaining user information that enables the programmed computer to associate types with variables is useful only if advantage is taken of the information. For that to happen, the equivalent of act 12 in
In some embodiments of this invention, information identifying variables and their corresponding types is received as shown in act 1703, which may occur via a user interface of the type shown in
After receiving said program, some embodiments of the invention begin a loop to determine the type of remaining variables of unknown type in said program. This loop is indicated in act 1710 and branch 170.
Some embodiments of this invention set up a data structure to contain the type of every variable in the program. In some embodiments of this invention, the types of all the variables in the program are set to types determined from the identification of variables and corresponding types from act 1703.
Some embodiments of this invention then propagate types and constant valued variables throughout the program as indicated in act 1720. The types and constant values are propagated alternately, until all constant valued variables and known types have been fully propagated as illustrated in acts 1725 and 1730.
Some embodiments of this invention the perform transformations on the representation of the program to accomplish the removal of unreachable code from the program Said removal of unreachable code may cause a repetition of the acts 1760, 1720, 1725, 1730, and 1735 to produce improved accuracy or improved precision in types for the variables of the program.
Numerous modifications, variations, and adaptations of the embodiment described herein will be apparent to the skilled artisan in view of this disclosure, all of which are encompassed by the scope of this invention. In some embodiments, a computer readable storage medium is encoded with instructions to perform the method illustrated in
Some embodiments of the invention create and use a definition-use graph, as described in detail in U.S. application Ser. No. 10/826,978 filed by J. R. Allen on Apr. 16, 2004 which is incorporated by reference herein in its entirety (including the computer program appendix therein).