• Nenhum resultado encontrado

Parallelizing Java programs using transformation laws

N/A
N/A
Protected

Academic year: 2021

Share "Parallelizing Java programs using transformation laws"

Copied!
101
0
0

Texto

(1)Universidade Federal de Pernambuco Centro de Inform´atica. P´os Gradua¸c˜ao em Ciˆencia da Computa¸c˜ao. PARALLELIZING JAVA PROGRAMS USING TRANSFORMATION LAWS Rafael Machado Duarte ˜ DE MESTRADO DISSERTAC ¸ AO. Recife 22 de agosto de 2008.

(2)

(3) Universidade Federal de Pernambuco Centro de Inform´atica. Rafael Machado Duarte PARALLELIZING JAVA PROGRAMS USING TRANSFORMATION LAWS. Trabalho apresentado ao Programa de P´ os Gradua¸ c˜ ao em Ciˆ encia da Computa¸ c˜ ao do Centro de Inform´ atica da Universidade Federal de Pernambuco como requisito parcial para obten¸ c˜ ao do grau de. Mestre em Ciˆ encia da Com-. puta¸ c˜ ao.. Orientador: Alexandre Cabral Mota Co-orientador: Augusto Cezar Alves Sampaio. Recife 22 de agosto de 2008.

(4) Duarte, Rafael Machado Parallelizing Java programs using transformation laws / Rafael Machado Duarte. - Recife: O Autor, 2008. xix, 77 p. : il., fig., tab. Dissertação (mestrado) – Universidade Pernambuco. CIn. Ciência da computação, 2008.. Federal. de. Inclui bibliografia e apêndice. 1. Engenharia de software. transformação. I. Título. 005.1. CDD (22. ed.). 2. Métodos formais. 3. Leis de. MEI2010 – 079.

(5)

(6)

(7) aos meus pais.

(8)

(9) ACKNOWLEDGEMENTS If you wish your merit to be known, acknowledge that of other people. —ORIENTAL PROVERB. Muitas pessoas contribu´ıram para a conclus˜ao desta dissertac˜ao de mestrado. Algumas cientes de que estavam ajudando, outras n˜ao. Cit´a-las nos agradecimentos ´e sempre dif´ıcil, pois tenho que decidir quais nomes ser˜ao inclu´ıdos. Citarei as pessoas mais diretamente envolvidas em meu trabalho, mas deixo registrado meus agradecimentos a todos, que mesmo n˜ao aqui citados, colaboraram para a conclus˜ao deste trabalho. Come¸co agradecendo aos professores Alexandre Mota e Augusto Sampaio. Sem a orienta¸c˜ao deles, esse trabalho seria imposs´ıvel. Agrade¸co tamb´em pelo tempo que dedicaram a meu trabalho, o que me proporcionou as condi¸c˜oes necess´arias ao t´ermino desta disserta¸ca˜o. ` minha fam´ılia, agrace¸co por todo o apoio que me foi dado, especialmente a meu A irm˜ao felipe, pela consultoria na ´area de estat´ıstica. Muito importante tamb´em foi o apoio de todos meus colegas da p´os gradua¸ca˜o do CIn, pelo companheirismo e por terem me proporcionado v´arios momentos de descontra¸c˜ao durante o atribulado cotidiano de um mestrando. Dedico um agradecimento especial a Nat´alia, minha namorada, por ter me apoiado esse tempo todo e ter tornado minha vida mais feliz pelo tempo que tenho desfrutado de sua companhia. Agrade¸co tamb´em aos professores M´arcio Corn´elio e Paulo Borba, por terem aceito participar de minha banca e pelos relevantes coment´arios que fizeram sobre meu trabalho. Finalmente, agrade¸co ao CNPq, por ter financiado parcialmente este projeto de mestrado.. vii.

(10)

(11) Fais. de ta vie un rˆ eve, et d’un rˆ eve, une r´ ealit´ e. ´ RY —ANTOINE DE SAINT-EXUPE.

(12)

(13) RESUMO Com a ado¸c˜ao pelo mercado dos processadores de n´ ucleos m´ ultiplos, o uso de threads em Java se torna cada vez mais proveitoso. O desenvolvimento de sistemas paralelos ´e, entretanto, uma tarefa que poucos desenvolvedores est˜ao capacitados a enfrentar. Dado esse contexto, foi desenvolvida uma abordagem de paraleliza¸c˜ao de programas java baseada em leis de transforma¸ca˜o, com o intuito de facilitar esse processo e permitir uma paraleliza¸ca˜o sistem´atica. O primeiro passo da abordagem utiliza leis de transforma¸c˜ao para converter um programa Java em uma forma normal que utiliza um conjunto restrito de recursos da linguagem. Neste passo, foram definidas leis de transforma¸ca˜o adaptadas de trabalhos anteriores, assim como novas leis foram propostas. A partir de um programa na forma normal, s˜ao utilizadas regras de transforma¸ca˜o focadas em introduzir paralelismo. Ap´os a aplica¸c˜ao dessas regras de acordo com a estret´egia desenvolvida, um programa paralelo ´e produzido. Dois casos de estudo foram realizados para validar a abordagem: c´alculo de s´eries de Fourier e o algoritmo de criptografia IDEA. Ambos c´odigos foram obtidos do Java Grande Benchmark Suite. A execu¸c˜ao dos estudos de caso comprova o ˆexito da abordagem em melhorar a performance do c´odigo original. Palavras-chave: Java, Programa¸c˜ao Paralela, Leis de Transforma¸ca˜o. xi.

(14)

(15) ABSTRACT With the ever increasing adoption of multicore processors by the market, using Java threads becomes very fruitful. Developing parallel systems, though, is a task few developers are able to face. Given this context, we developed a parallelization approach based on transformation laws, which intends to facilitate this process and provide a systematic parallelization. The first step of our approach uses transformation laws to convert a Java program into a normal form that allows only a restricted set of features from the language. In this step, some laws were adapted from previous work, as well as new laws were defined. Starting from a program in the normal form, tranformation rules focused on introducing parallelism are used. After applying these rules in accordance to the developed strategy, a parallel program is produced. Two case studies were performed to validate our approach: Fourier series calculation and the IDEA encryption algorithm. Both source codes were obtained from the Java Grande Benchmark suite. The experiments we performed provided very interesting results, delivering parallel code with a performance comparable to manually parallelized ones. Keywords: Java, Parallel Programming, Transformation Laws. xiii.

(16)

(17) CONTENTS. Chapter 1—Introduction 1.1 1.2. 1. Contributions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Organization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. Chapter 2—Background 2.1 2.2 2.3 2.4. Java . . . . . . . . . . . . . 2.1.1 Concurrency Model . Algebraic Laws . . . . . . . Program Parallelization . . . 2.3.1 Dependence Analysis Final Considerations . . . .. 5 . . . . . .. . . . . . .. . . . . . .. . . . . . .. . . . . . .. . . . . . .. . . . . . .. . . . . . .. . . . . . .. . . . . . .. . . . . . .. . . . . . .. . . . . . .. . . . . . .. . . . . . .. . . . . . .. . . . . . .. . . . . . .. . . . . . .. . . . . . .. . . . . . .. . . . . . .. . . . . . .. . . . . . .. . . . . . .. Chapter 3—Normal Form Reduction 3.1 3.2. 3.3. 3.4 3.5. 5 6 9 10 13 14 15. Open systems . . . . . . . . . . . . Laws . . . . . . . . . . . . . . . . . 3.2.1 Classes . . . . . . . . . . . . 3.2.2 Attributes . . . . . . . . . . 3.2.3 Methods . . . . . . . . . . . 3.2.4 Constructors . . . . . . . . 3.2.5 Commands and Expressions 3.2.6 Summary of Laws . . . . . . Normal Form Reduction . . . . . . 3.3.1 Normal Form . . . . . . . . 3.3.2 Reduction Strategy . . . . . Case Study . . . . . . . . . . . . . Final Considerations . . . . . . . .. . . . . . . . . . . . . .. . . . . . . . . . . . . .. . . . . . . . . . . . . .. . . . . . . . . . . . . .. . . . . . . . . . . . . .. . . . . . . . . . . . . .. . . . . . . . . . . . . .. . . . . . . . . . . . . .. . . . . . . . . . . . . .. . . . . . . . . . . . . .. . . . . . . . . . . . . .. . . . . . . . . . . . . .. . . . . . . . . . . . . .. . . . . . . . . . . . . .. . . . . . . . . . . . . .. . . . . . . . . . . . . .. . . . . . . . . . . . . .. . . . . . . . . . . . . .. . . . . . . . . . . . . .. . . . . . . . . . . . . .. . . . . . . . . . . . . .. Chapter 4—Parallelization 4.1 4.2 4.3. 3 4. 15 16 17 18 21 32 38 41 41 43 43 44 46 49. Parallelization Laws . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Parallelization Strategy . . . . . . . . . . . . . . . . . . . . . . . . . . . . Final Considerations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xv. 49 53 56.

(18) xvi. CONTENTS. Chapter 5—Case Studies 5.1 5.2 5.3 5.4. Methodology . . . . Fourier Series . . . . IDEA Encryption . . Final Considerations. 57 . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. Chapter 6—Concluding Remarks 6.1 6.2. Related Work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Future Work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. Appendix A—Exp1 Interpreter Source Code A.1 Original Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A.2 Normalized classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 57 58 60 62 63 64 65 73 73 76.

(19) LIST OF FIGURES. 1.1 1.2. Overview of our strategy . . . . . . . . . . . . . . . . . . . . . . . . . . . Example of parallelization . . . . . . . . . . . . . . . . . . . . . . . . . .. 2 3. 2.1 2.2 2.3 2.4 2.5 2.6. Threads in Java . . . . . . . . . . . . . . . . . . Defining threads using inheritance . . . . . . . . Defining threads using interface implementation The lifecycle of a Thread . . . . . . . . . . . . . Synchronized method example . . . . . . . . . . Synchronized block example . . . . . . . . . . .. . . . . . .. 6 6 7 7 8 9. 3.1 3.2 3.3 3.4. 16 16 45. 3.5 3.6. General open system . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Limited open system . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exp1 interpreter (original class diagram) . . . . . . . . . . . . . . . . . . Exp1 interpreter class diagram (attributes moved up and reduced constructors) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exp1 interpreter class diagram (methods moved up) . . . . . . . . . . . . Exp1 interpreter class diagram (normal form) . . . . . . . . . . . . . . .. 4.1 4.2 4.3. Source code of class Main . . . . . . . . . . . . . . . . . . . . . . . . . . Source code of method eval after reordering . . . . . . . . . . . . . . . . Source code of method eval parallelized . . . . . . . . . . . . . . . . . . .. 54 55 56. 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8. Fourier series benchmark starting classes . . . . . . . . Fourier series benchmark in the normal form . . . . . . Execution time in the different Fourier implementations Speedups in the different Fourier implementations . . . IDEA encryption benchmark starting classes . . . . . . IDEA encryption benchmark in the normal form . . . . Execution time in the different IDEA implementations Speedups in the different IDEA implementations . . . .. 58 59 60 60 61 61 62 62. xvii. . . . . . .. . . . . . .. . . . . . .. . . . . . .. . . . . . .. . . . . . . . .. . . . . . .. . . . . . . . .. . . . . . .. . . . . . . . .. . . . . . .. . . . . . . . .. . . . . . .. . . . . . . . .. . . . . . .. . . . . . . . .. . . . . . .. . . . . . . . .. . . . . . .. . . . . . . . .. . . . . . .. . . . . . . . .. . . . . . . . .. 46 46 47.

(20)

(21) LIST OF TABLES. 3.1. Complete set of laws . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 42. 5.1 5.2. Fourier series benchmark execution data . . . . . . . . . . . . . . . . . . IDEA encryption benchmark execution data . . . . . . . . . . . . . . . .. 60 62. xix.

(22)

(23) CHAPTER 1. INTRODUCTION There is nothing more difficult to take in hand, more perilous to conduct or more uncertain in its success than to take the lead in the introduction of a new order of things. —NICCOLO MACHIAVELLI (”The Prince”). Developing parallel applications is a major challenge for programmers. Parallel systems can present issues which do not occur in their sequential counterparts, like nondeterminism, race conditions, and deadlocks [And00]. Reasoning with these issues is considered more difficult than reasoning with ordinary sequential programs. Thus, few developers specialize on this kind of software, and a great amount of time is spent on its development. Nowadays, multicore processors are widely available to ordinary PCs [Rat05], and to explore their multiple processing capabilities, the running softwares must be multithreaded. It becomes clear that a powerful hardware resource is not being exploited as it could. One possible way to overcome these difficulties is to provide some parallelization mechanism. The idea is to hide the parallelism from the developer, either by an automatic process, either by providing high-level abstractions. These techniques, known as implicit parallelism, can take an ordinary sequential program and, with minor or no human interference, produce a parallel version of that program. They can be more cost-effective than explicit parallelism, when executing the original sequential system in parallel does not require a major restructuring. By hiding the parallelism from the developer, it becomes possible to explore it, without the need of manipulating error prone parallel code. These techniques are well established in the supercomputing community, with reliable tools in use for languages such as High Performance Fortran [KLZS94]. As a language to explore parallelism in general computing, Java [GJSB05] is as a very interesting alternative. Recent surveys indicated that it is one of the most popular programming languages today [Tio08]. Beyond this, Java supports parallel programming with built-in threads, and has already proved its capabilities in different domains, such as web applications and even scientific computing [MMG00]. Some approaches have been proposed to explore implicit parallelism in Java, such as [BA07, CA04, Fel03, Che03, MMG+ 01]. They work both in the bytecode and source code level, and a common point among them is the additional support necessary. This support is provided by extra code introduced in the application, or by additional runtime tools. They provide good results, but they explore the parallelization in a very pragmatic way, without focusing on the correctness of the transformations. One promising approach 1.

(24) 2. INTRODUCTION. to address this issue is the use of algebraic laws. They can define the equivalence between two systems, as well as be used to relate sequential and parallel versions of the same system. Algebraic laws have been defined for languages of several programming paradigms [HHJ+ 87, RH88, QJ94, SSH99, BSCC04, SB06]. They provide insight on the algebraic semantics of languages, and are also used as a basis for the definition of reliable refactorings [Cor04, GM05]. By using them, a framework for formal proofs can be established, which facilitates assuring the correctness of the transformations. Our work proposes a strategy to parallelize Java programs using algebraic laws. Its goal is to provide a lightweight program transformation scheme, more amenable to future formal proofs, and capable of speeding up the execution of Java programs executed in multiple processors. By lightweight program transformations we mean they are very simple, when compared to other parallelization frameworks, which often perform complex code transformations and require the use of classes from third party libraries. We were inspired by the ideas presented in [Sam97, SB06], but adapting them to a more practical scenario, using Java, which proved to be challenging. Our strategy consists in two major activities, as can be seen in Figure 1.1. We first use algebraic laws to transform the original source code into a normal form, using a normal form reduction strategy (Section 3.3). Our normal form, similarly to [SB06], moves most of the code to the method main, which in our case, is defined within the class called Main. Only the attributes are maintained in a newly created Object class. After that, we use our parallelization strategy to introduce parallelism into the normalized program (Section 4.2).. Figure 1.1 Overview of our strategy. To provide a closer look on how our strategy works, consider the code depicted in Figure 1.2, which is already in the normal form to ease the discussion. In this code, there is a sequence of six commands, explicitly enumerated. By performing a dependence analysis we can discover that the commands at lines 3 and 6 are independent of those at 1, 2, 4, and 5. Thus, the commands 3 and 6 do not depend on the computations performed by the other commands, and vice-versa. That is, there is no need of ordering in their execution. In this situation, we can group the related commands, by moving them.

(25) 3. 1.1 CONTRIBUTIONS. to form two new partitions; finally, we execute each partition in parallel, exploring the multiple processors possibly available. In our strategy, changing the order of commands and their parallel execution will be achieved by applying the laws we propose.. Figure 1.2 Example of parallelization. To evaluate whether our strategy delivers significant performance improvements in real-world systems, we perform two case studies using the source code from the Java Grande Benchmark suite (JGB) [SBO01]. Our experiments provided some evidence that our strategy can be applied in practice, delivering competitive speedups when compared to parallel code manually written by the developers of the suite. In one case study we obtained practically the same speedup (0.2% better, 78.6% vs 78.5%) than the parallel version created by hand, while the other delivered a 24% worse speedup (54.24% vs 70.53%), which we consider competitive, if we take into account the effort involved in developing each version. Although we do not provide formal proofs for our laws, in the experiments with the case studies, the observed behavior is preserved. Each case study has a built-in validation mechanism to check wether the results are correct. After using our parallelization strategy, the code continued to successfully pass the existing validations. 1.1. CONTRIBUTIONS. The summary of our contributions is presented as follows: Adaptation of ROOL laws to Java - the laws defined for ROOL [BS00] were adapted to a subset of Java, where some of them required substantial modification or even the definition of new laws. Proposition of new laws for Java - laws for features not present in ROOL, such as constructors, were proposed. Approach to cope with limited open systems - previous work involving laws for object-oriented programming required the system to be closed, i.e. no binary external code could be used. We have devised a solution to address a limited form of open systems, enabling them as targets for the laws. Normal form reduction strategy for Java - a reduction strategy for Java is provided to transform Java programs (with a restricted set of constructs) into a normal form that uses a limited set of features of the language..

(26) 4. INTRODUCTION. Parallelization laws - a novel strategy to parallelization that provides lightweight source to source transformations and a framework for building correctness proofs. Moreover, they provide a way to improve the performance of sequential systems, exploiting available multiple processors. 1.2. ORGANIZATION. The remaining of this thesis is divided into five chapters and one appendix. The contents of each one is described as follows: Chapter 2: we introduce the main concepts involved in the subsequent chapters. It presents Java, our target language, focusing on its concurrency features; describes the main concepts about algebraic laws, the strategy used in our work; and it discusses about program parallelization and its challenges, the domain where we focused our work. Chapter 3: we discuss in depth the algebraic set of laws devised for Java, the strategy to cope with open systems, the proposed normal form, and the reduction strategy. Chapter 4: we describe the strategy to parallelize sequential Java programs. We also present the transformation laws focused on restructuring the code for parallel execution. Chapter 5: we discuss two case studies we have performed, describing the application of our strategy from the original Java source code until it becomes parallel. We also compare our normal form and parallel version with those from the JGB. Chapter 6: we present our concluding remarks, as well as some significant related work, and future improvements to our current work. Appendix A: the source code of the example used in Chapter 3 is presented..

(27) CHAPTER 2. BACKGROUND When a man’s knowledge is not in order, the more of it he has, the greater will be his confusion. —HERBERT SPENCER. This chapter briefly introduces the main concepts involved in this work. Its purpose is to provide the background needed to understand the subsequent chapters. The first section gives an overview of Java, our target language, focusing on its concurrency features; the next one discusses about algebraic laws, the approach we have used in our work; and the final section presents the main topics about program parallelization, the area where we focused the use of algebraic laws. 2.1. JAVA. Java [GM96] was conceived in 1991, originally designed for embedded software. Nevertheless, in 1994, it had its focus redirected to the internet, and started to bring attention from the developers community. Since then, Java’s popularity has greatly increased, just as it happened to C, due to the popularity of UNIX systems. According to [GM96], its major features are: Simple, object oriented, and familiar - object orientation is the current dominant paradigm in the industry. Java’s model is simple and very similar to C++; Robust - compile and runtime verifications assure the reliability of programs. Distributed and safe - a language and runtime system designed to execute in distributed environments has to incorporate security aspects; Interpreted, portable and neutral architecture - by using a virtual machine to execute bytecodes (java compiled code), which are platform independent, it provides the portability to execute them on different platforms. High performance - techniques like just-in-time (JIT) compilation allow critical performace code to be compiled to native code, speeding up execution and reducing the overheads caused by interpretation. Dynamic - classes are loaded only when needed, and new classes can be added as the system evolves. Multithreaded - built-in support to concurrent programming allows the development of portable multithreaded applications. 5.

(28) 6. BACKGROUND. 2.1.1. Concurrency Model. Actually, Java is one of the most interesting concurrent object oriented languages [Wel04]. Since its conception, it was designed to provide a concurrency model within the object oriented framework. It was done by using the concept of active objects, which have their own behavior, in contrast to the passive ones that only respond to external calls. In Java, active objects can be defined in two ways: creating a new class that inherits from Thread, or creating a new class that implements the interface Runnable, as depicted in Figure 2.1. To execute a newly created thread, one must call the method start. It then executes the commands from the method run in an independent execution thread (in parallel).. Figure 2.1 Threads in Java. To define a thread using inheritance, one firstly must define a class that extends from the class Thread (defined in Java’s standard library), and redefine the method run. The method run defines the behavior of the thread. Its body has the commands to be executed in a new thread. To execute a thread defined in this way, it suffices to create an object from the defined class, and call its start method, as depicted in Figure 2.2. public class HelloWorldThread extends Thread { public void run() { System.out.println("Hello World!"); } public static void main(String[] args) { HelloWorldThread helloWorld = new HelloWorldThread(); helloWorld.start(); } } Figure 2.2 Defining threads using inheritance.

(29) 7. 2.1 JAVA. In the other way, the user defined class implements the Runnable interface. This interface defines only the run method, that must be implemented by the class. It is an alternative to cases where the class defining the run method already inherits from another class (Java supports only single inheritance). Figure 2.3 depicts how the previous example could be coded in this approach. public class HelloWorldRunnable implements Runnable { public void run() { System.out.println("Hello World!"); } public static void main(String[] args) { HelloWorldRunnable helloWorld = new HelloWorldRunnable(); Thread thread = new Thread(helloWorld); thread.start(); } } Figure 2.3 Defining threads using interface implementation. The life cycle of a thread can be observed in Figure 2.4. When it is created, it remains idle until the start method is called. At this moment it changes to the running state. In this state, it can let other threads execute when the yield method is called while remaining in the execution queue, as well as the virtual machine can interrupt it, and let other threads execute. It can also move to the not runnable state when the methods wait or sleep are executed. When the run method finishes, the thread goes to the dead state.. Figure 2.4 The lifecycle of a Thread. The Thread class presents useful methods to control its execution. Among them, we highlight the following ones: start - starts the execution of the thread as an independent task, calling the run method defined by the programmer. When the run method finishes, the thread is finalized; stop - stops the thread execution. This method is deprecated, because it is deadlock prone [Sun99];.

(30) 8. BACKGROUND. isAlive - returns true if the thread has started its execution, but not yet finished; sleep - suspends the thread execution by a determined period of time; join - waits until the thread finishes. It can also receive a timeout as parameter, to avoid waiting for undetermined time; and yield - suspends the thread execution, letting other threads execute Concurrency Control In Java, the standard method adopted for inter-thread communication is memory sharing, which happens when two or more threads access the same object. Sharing memory between threads is one of the major sources of bugs in multithreaded software [GPB+ 06]. There are three basic alternatives to avoid this kind of problem: ˆ Do not share objects between threads; ˆ Make shared objects immutable; ˆ Access the shared area exclusively.. When mutable objects must be shared, the only solution is to control the access to that shared area, assuring that only one thread has access to it at a time . In Java, it can be achieved by using its locking mechanism, that provides each object with its own lock. This lock is then used by synchronization to avoid simultaneous access to the same memory area. There are two ways of using synchronization in Java: using the synchronized modifier in method declarations, or using synchronized blocks. When a method is marked as synchronized, it can be executed only if it obtains the lock from the object it is being executed on. By using this technique, no more than one thread can execute the same method, on the same object, simultaneously. The rule of thumb is that every method accessing shared members in an object must be synchronized if multiple threads execute that method. An example with synchronized is depicted in Figure 2.5. public synchronized void write(int newValue) { theData = newValue; } Figure 2.5 Synchronized method example. Synchronized blocks provide the same locking mechanism, but in a more flexible way. One can declare a synchronized block, specify from which object it will use the lock, and enclose within it the code to be executed in a mutually exclusive fashion. They deliver a finer grained synchronization, while the synchronized modifier affects the entire method body. Moreover, the lock is not fixed. This means that the lock being used is.

(31) 9. 2.2 ALGEBRAIC LAWS. public void write(int newValue) { synchronized(this) { theData = newValue; } } Figure 2.6 Synchronized block example. defined by an object passed as parameter to the block. The previous example, coded using synchronized blocks, is depicted in Figure 2.6. There are more concurrency control features in Java, such as the modifier volatile, used to provide atomic accesses to primitive type attributes; and the methods wait, and notify, used to implement conditional synchronization. We do not provide further details, since they are not used in this work. 2.2. ALGEBRAIC LAWS. Formal semantics are used to precisely specify the meaning of programs by using rigorous mathematical abstractions. According to [Win93], there are three major styles to define the semantics of a program language: Denotational semantics translates phrases in the language into a denotation, i.e. a phrase in some other language. It uses abstract mathematical concepts as denotations to language constructs. Denotational semantics corresponds loosely to compilation, but the target language is a mathematical formalism, instead of a programming language. Operational semantics describes directly the execution of a program. It is done by specifying how it executes on an abstract machine. Operational semantics corresponds loosely to interpretation, where the interpretation language is usually a mathematical formalism. Axiomatic semantics tries to fix the meaning of a programming construct by giving proof rules for it within a program logic. It emphasizes proof of correctness right from the start. Defining a set of algebraic laws for a language is a common way of defining its semantics in an axiomatic style. Algebraic laws define equations that establish the equivalence between elements in the language, a very useful tool to build proofs and verify systems. Algebraic laws for programming languages adopt a principle widely used in mathematics [HHJ+ 87], where a theory is built around its axiomatic laws. The Arithmetics, for example, provides us with several laws, such as: Laws (1), and (3) enounce the symmetry of the addition and multiplication operators. Laws (2), and (4) establish the identity operands for both operations. These laws are successfully used to define the properties of the arithmetic’s operators. We can use the.

(32) 10. BACKGROUND. (1) x × y = y × x. (3) x + y = y + x. (2) x × 1 = x. (4) x + 0 = x. same approach to define properties of programming languages. For instance, assigning P to itself does not have any effect in the program execution; therefore it is equivalent to skip (the empty command). P := P = skip This is an example of a very simple law for an imperative language, taken from [HHJ+ 87]. Algebraic laws have been successfully defined for the most important paradigms of programming languages [HHJ+ 87, RH88, QJ94, SSH99, SB06], and proved to be a useful approach to provide their semantics. An algebraic approach to provide the semantics of a programming language raises the issue of completeness. It is well known that there is no complete and consistent set of axioms, as stated by G¨odel’s incompleteness theorem. Nevertheless, it is paramount to address their comprehensiveness; to establish that they are expressive enough to define the semantics of the language. In this sense, a standard approach is to provide a reduction strategy, that, using the defined laws, leads to the reduction of any program to a normal form. This normal form features only a limited set of language constructs, usually the most simple ones. More recently, algebraic laws have been used to provide a formal basis for refactorings [Fow99]. Refactorings are transformations performed in code aiming at improving its structure. In most cases they are defined informally, which can lead to transformations that do not maintain the expected behavior. It has been shown that algebraic laws can be successfully used in this context [Cor04, GM05], as they can be composed to prove more complex transformations. Another new trend that has fruitfully used algebraic laws is the model-driven architecture (MDD) [KWB03, OMG04]. It is based on models and transformations performed between different levels of models. These transformations can be specified and verified using algebraic laws, as in [RSM06, Mas07], to assure that newly generated models maintain the expected behavior from their originating models. In this work, we use algebraic laws to transform Java programs, while preserving their observable semantics (behavior), but introducing new properties (parallelism). 2.3. PROGRAM PARALLELIZATION. Parallel systems are a major field in Computer Science. They have been studied intensively, and diverse techniques have been created to cope with the challenges that arise when developing these kind of systems. Their main characteristic is having multiple threads of control, whereas sequential programs have only one. The different threads of control work together by communicating to each other. The communication between them is implemented by shared variables or message passing..

(33) 2.3 PROGRAM PARALLELIZATION. 11. Another important point is how parallel systems are programmed. Concerning this aspect, they can be classified as: Explicit parallelism: the developer himself defines how the parallel execution will be performed, providing each necessary synchronization and execution details. Implicit parallelism: the developer does not manipulate low level parallelism constructs while writing his code. It can, however, indicate which points of the code are candidates to be parallelized. It is well accepted that explicit parallelism can be used in more situations, presents better speedups, as well as it yields systems that are better structured. Its major drawback, however, is the effort needed to develop them, as well as the high risk of introducing bugs due to issues particular to parallel systems (such as non-determinism, deadlocks, and race conditions). On the other hand, the idea of implicit parallelism is to hide the details from the developer, leaving the responsibility of managing it to some compiler or runtime engine. Having these issues in mind, Whu et al [mHRU+ 07] argues that explicit parallelism is counterproductive in the long run, while implicit parallelism is the technology of choice for the forthcoming many core processors. We agree up to some extent with them. Programming explicitly for many processors is a hard task for the average programmer, and having implicit mechanisms to hide the parallelism complexity is an interest way to build parallel systems more efficiently. We believe, however, that some classes of system require human intervention to be implemented, and are not adequate to implicit models. Moreover, the parallelization can be automatic or manual. When an automatic parallelization is used, some engine detects where parallelism can be introduced and changes the code or its execution. Its manual counterpart, however, relies on the developer to identify the point where parallelism can be exploited. According to Lea [Lea99], parallel programs can be divided into two categories, according to their nature: Task-based: Parallelism is used to asynchronously invoke a method that performs some task. The task might range from a single method to an entire session. Parallel techniques can support message-passing schemes that escape the limitations of pure procedural calls. Task-based designs are seen in event frameworks, parallel computation, and IO-intensive systems. Agent-based: Parallelism is used to create and set into motion a new autonomous, active, process-like object. This object may in turn react to external events, interact with other actors, and so on. Actor-based designs are seen in reactive, control, and distributed systems. They are also the focus of most formal approaches to concurrency. Program parallelization is focused mostly on task-based parallel systems, because programs that fit into this scenario are more amenable to systematic parallelization..

(34) 12. BACKGROUND. Agent-based systems usually present more complex behaviors that need more human intervention to be expressed in a parallel fashion. In the task-based category, parallel computing, which is focused on solving a large problem faster than it could be done sequentially, is a domain that has concentrated a strong research effort. This is the kind of computation usually needed by the scientific computing community, one of the main responsibles for the recent advances in program parallelization. Nevertheless, most of their tools and methods are focused on niche languages such as Fortran [MR90]. In the end of the 90s, some effort has begun to bring some of these techniques to Java, after being proved that it could deliver execution times comparable with those provided by other languages [MMG00]. In order to parallelize tasks, there must be some degree of independence between them. Strongly tangled tasks cannot be executed concurrently, since one must finish before the next one begins. As stated in [And00], parallel computing can present two different types of parallelism: Data parallelism - the same operations are performed in partitions of the data. This is usually performed when there are large amounts of data to be processed, and to speed up their processing, the data is divided to many working threads that will execute the same tasks in different parts of the data. This category has been the focus of supercomputing for many years, and the techniques to take advantage of it are better established than its task counterpart. Task parallelism - distinct operations are performed in parallel. This can happen when there are independent tasks to be performed on the same data, or even in non related data. One can have, for instance, different threads reading the same data and compute different calculations. The most common form of task parallelism is called pipelining. It is done by inputting data successively to different working threads. It is usually easier to parallelize systems that present data parallelism, since one needs only to divide the data into partitions and distribute them to working threads. Scientific and technical computing are typical examples of domains which present strong data parallelism, because they manipulate huge amounts of data. Task parallelism can be more difficult, because determining dependencies between tasks is often more complicated. Nevertheless, it is more pervasive in computing, but frequently it is not worth parallelizing an individual task or small groups of them, due to the overhead incurred in a parallel execution. A very promising work on developing a modern programming language focused on parallelism and scientific computing is Fortress [Ste05, ACH+ 07]. It provides models for seamlessly exploring implicit parallelism, as well as a notation that resembles mathematical formulae. Moreover, it presents features like object orientation and parametric polymorphism, which are not usually present in traditional scientific computing languages..

(35) 2.3 PROGRAM PARALLELIZATION. 2.3.1. 13. Dependence Analysis. Program parallelization relies heavily on the notion of data independence [BENP93, Wol95, PK03]. In order to execute commands in parallel, there must be no coupling between them, because each one will have its own execution flow. In most cases, these dependencies are determined by the data accessed by each command. Dependence analysis is a field with a strong ongoing research; its advances were very important to the development of compiler theory and program parallelization. Take as an example the following code: (1) (2) (3) (4). A B C D. = = = =. 0 A A + D 2. Statement 2 cannot be executed before 1, since it could get an old value of A. Likewise, executing 4 before 3, would make 3 use the wrong value of D. It is clear that there are dependencies between the statements, and they impose an ordering on their execution. This same ordering prevents them from being executed in parallel, since the statements cannot execute simultaneously. Dependence analysis can be performed statically or dynamically. Static analysis is performed in compile time and is often called conservative, because when some dependencies cannot be determined statically, it assumes that the dependency exists. Dynamic analysis, on the other hand, can use runtime information to discover whether dependencies exist, being capable therefore to determine them in more cases. More recently, hybrid approaches have also been developed [RRH03], which try to combine both analyses. Static dependence analysis, however, has the advantages of posing a lighter overhead, and being easier to implement. Dynamic analysis, on the other hand, yields a heavier overhead, since it must perform a comprehensive set of execution flows in order to determine any property. Several dependence analysis algorithms have been developed, the one proposed by Banerjee [Ban88], I-Test [PKK93], and Omega test [Pug91] are among the most important ones in the literature. The problem of determining dependencies is typically reduced to solving a linear system of equation and inequations, in this sense, the Banerjee test was the first one proposed. It can discover whether no dependence exists, but in some cases it fails to do so, even when there are no dependencies. The I-Test is an improvement over the Banerjee test, being capable of disproving dependencies where its counterpart is not. The Omega test, in its turn, is more powerful than both of them, being able to produce exact yes/no answers, but it delivers worst case exponential complexity. More recently, new techniques have been devised, such as [KP05], that have improved previous approaches, and overcame some of their limitations. Nevertheless, dependence analysis remains a challenge, even more when applied to modern object oriented programming languages..

(36) 14 2.4. BACKGROUND. FINAL CONSIDERATIONS. This chapter briefly presented the essential concepts involved in this thesis. We have described the main features of Java, the target of our proposed transformations, focusing on its concurrency support. After that, we provided an overview about algebraic laws, the approach we adapt, highlighting their utility and how it has been used for diverse purposes. Finally, we presented some important concepts involved in program parallelization, the goal of our approach, describing different types of parallelism which can be detected in existing systems, as well as the strong relation between program parallelization and dependence analysis..

(37) CHAPTER 3. NORMAL FORM REDUCTION 2 is not equal to 3, not even for large values of 2. —GRABEL’S LAW. Inspired by the laws of ROOL (an object oriented language with copy semantics) [BSCC04, SB06], this chapter presents laws for a significant subset of Java [GJSB05], considering Java’s concrete syntax and the provisos for laws. Some entirely new laws were introduced, as laws involving constructors and static methods, which were not considered before. Aiming at covering a broader range of applications, a strategy to cope with open systems is presented and incorporated into the laws. We also show a reduction strategy to transform Java programs into a normal form, expressed in a small subset of Java features. This allows us to establish relative completeness of the set of laws. 3.1. OPEN SYSTEMS. Addressing open systems is a fundamental issue in our approach. Java is well known to have an extensive set of built-in libraries; not dealing with them would exclude most Java systems as targets for our transformations. Moreover, every Java class extends directly or indirectly from Object, implying that there are no closed systems in Java. Most importantly, open systems can reference classes without having access to their source code, or there can be other modules that depend on their code. In these cases, changing the interface of the system can affect other modules. By further investigating this issue, we noticed that we can divide open systems into two categories: general open systems (Figure 3.1), and limited open systems (Figure 3.2). General open systems can depend on other systems, as well as other systems can depend on them. Limited open systems can only depend on external elements; no external elements depend on them. Our focus is on limited open systems, which is enough to explore fine grained parallelism while using third-party code. From now on we refer to this category simply as open systems, leaving the qualifier “limited” implicit. The main idea is to change only the accessible source code, ignoring external (and therefore preserving) elements in the transformations. Each law affects specific code constructs, whilst leaving the remaining code unchanged; only the affected constructs must be accessible when performing transformations. It is important, then, to precisely identify what belongs to the accessible system and what comes from external libraries. We define the set of class declarations that compose the entire system as follows: cds = cdsinternal ∪ cdsexternal names(cdsinternal ) ∩ names(cdsexternal ) = ∅ 15.

(38) 16. NORMAL FORM REDUCTION. Figure 3.1 General open system. Figure 3.2 Limited open system. where cdsinternal is the set of class declarations with accessible source code (that can be changed), and cdsexternal is the set of class declarations that come from third-party binary code. Moreover, they are disjoint sets: no class declaration can belong to both sets. To capture this property we used the function names() to capture the name of the class declaration, avoiding repeated names. To enable the application of the laws in an open system context, we add new provisos to specify which elements must belong to cdsinternal for the laws to hold. When working with open systems, external classes are usually imported from their packages. Laws involved in this context would also need to address the import clauses present in the code, as transformations could possibly move elements to different classes. We have chosen to ignore this issue, since we can eliminate all import clauses if we use the complete (qualified) name of each class. After dividing open systems into these two categories, we have then observed that the existing laws could be easily used for limited open systems. We have simply interpreted the laws considering the presence of external elements, instead of only internal elements, as the closed system hypothesis assumes. 3.2. LAWS. Transformation laws are usually presented in an equational style with left and right hand sides. For each equation, there might be a set of conditions that must be fulfilled to perform the transformation in each direction. Most of our laws are context dependent, as expected for an object-oriented program. The laws are written in Java, representing the equivalence between two different pieces of well typed code. Our aim is to address the complete syntax of Java, but currently we exclude the following features: new features of Java 1.5, exceptions, threads, synchronization, and interfaces. We also adopt the convention that a Java system has the form cds M ain, where cds is a set of class declarations and M ain is a class with the only main method present in the system. We also consider that all internal class declarations are in the default package..

(39) 3.2 LAWS. 17. One important point to discuss concerning our laws is the notion of equality we adopt. We consider two complete Java systems of the form cds1 M ain1 = cds2 M ain2 equivalent when they present the same observable behavior. Some semantics have been proposed for Java as, for example, [AF99], and can be used to formalize this equality relation. Nevertheless, to present our laws, it is convenient to focus only on the elements affected by the laws, like two class declarations cd1 and cd2 ; thus, we use the notation cd1 =cds,M ain cd2 to capture the context for the law, as an abbreviation of cd1 cds M ain = cd2 cds M ain. It is also worth mentioning that in our laws, cds represents the entire set of class declarations, including the external ones. We present laws in three sections according to the constructs they affect. Some laws are adapted from [SB06], which presents laws for ROOL, an object-oriented language with copy semantics. For these laws, we emphasize the conditions for them to hold in Java, which were not present in the laws defined for ROOL. To our knowledge, the remaining laws are entirely new. An overview of the proposed laws and how they were defined is presented in Section 3.2.6. The conditions required in each law obey the following convention. Conditions marked with (↔) must hold when performing the transformation in both directions; conditions marked with (→) must hold when performing the transformation from left to right, and those with (←) from right to left. 3.2.1. Classes. Hereafter we use: ads, cnds, and mds to represent attributes, constructors, and methods declarations, respectively; T represents a type. The symbol ≤ represents the subtype relation between classes. To simplify the conditions, we will consider that the classes whose declarations are explicit belong to the internal class set. We can eliminate a class declaration if it is not used anywhere in the program. Conversely, we can introduce a new class declaration if it is not already present, and if it has a valid superclass. Law 1. hclass elimination/introductioni cds cd1 M ain = cds M ain provided (→) The class declared in cd1 is not referred in cds or Main; (←) (1) The name of the class declared in cd1 is distinct from those of all classes declared in cds; (2) the superclass appearing in cd1 is either Object or declared in cds. 2 We must also consider Object as a valid class, because it is the default superclass for Java classes. It is worth noting that this law holds even when cd1 is a external class, as their definitions can be also removed and introduced..

(40) 18. NORMAL FORM REDUCTION. In the following law, the notation B.a refers to uses of the name a via expressions whose static type is exactly B, as opposed to any of its subclasses. For example, if we write that B.a does not appear in mds, we mean that the bodies of the methods in mds does not contain any expression such as e.a, for any e of type B, strictly. Law 2. hchange superclass: from Object to another classi class C extends Object { ads cnds mds }. =cds,M ain. class C extends B { ads cnds mds }. provided (←) (1) C or any of its subclasses in cds, cnds, and mds is not used in type casts or tests involving any expression of type B or of any supertype of B; (2) There are no assignments of the form le = exp, for any le whose declared type is B or any superclass of B and any exp whose type is C or any subclass of C; (3) Expressions of type C or of any subclass of C are not used as arguments in calls with a corresponding formal parameter whose type is B or any superclass of B; (4) Expressions whose declared type is B or any of its superclasses are not returned as a method result in calls with an expected result whose declared type is C or any subclass of C; (5) this.a does not appear in C, nor in any subclass of C, for any public or protected attribute a of B or of any of its superclasses; (6) le.a, for any le : C, does not appear in cds or c for public attribute a of B or of any of its superclasses; (7) There is no D.m, for any m and D such that m is declared in B or in any of its superclasses, but not in mds, and D ≤ C; (8) super does not appear in any method in mds. 2 It is important to mention that on the right-hand side of the law, Object continues to be C’s superclass, but indirectly. When changing the superclass to Object, there is a much larger set of provisos, because C will no longer be a subclass of B, not even indirectly. 3.2.2. Attributes. The subsequent laws involve the variations on how attributes can be declared and their equivalences. For the following visibility laws, it is important to emphasize that we are considering that all accessible code is in the same package..

(41) 19. 3.2 LAWS. Law 3. hchange attribute visibility: from default to publici class C extends D { T a; ads cnds mds }. =cds,M ain. class C extends D { public T a; ads cnds mds }. 2 Law 4. hchange attribute visibility: from protected to publici class C extends D { protected T a; ads cnds mds }. =cds,M ain. class C extends D { public T a; ads cnds mds }. 2 Law 5. hchange attribute visibility: from private to publici class C extends D { private T a; ads cnds mds }. =cds,M ain. class C extends D { public T a; ads cnds mds }. provided (←) B.a, for any B ≤ C, appears only in C’s body 2 Laws to change the visibility of attributes are rather similar. Changing the visibility to a less restrict one is always possible, but the opposite must respect the specific conditions of each visibility modifier.. The next law shows when it is possible to move an attribute to a super or subclass, by applying the transformation from left to right and from right to left, respectively..

(42) 20. NORMAL FORM REDUCTION. Law 6. hmove attribute to superclassi class B extends A { ads cnds mds } class C extends B { public T a; ads0 cnds0 mds0 }. =cds,M ain. class B extends A { public T a; ads cnds mds } class C extends B { ads0 cnds0 mds0 }. provided (→) The attribute name a is not declared in ads; (←) (1) The attribute name a is not declared in ads’; (2) D.a, for any D ≤ B and D  C, does not appear in cds, Main, cnds, cnds’, mds, or mds’. 2 The type of an attribute can be changed to any other related type in its class hierarchy, if the provisos hold. This law does not take in consideration primitive types, because the subtype relation ≤ is defined only for classes. In the following law we consider a assignable occurrence, when an attribute or variable is in the left side of a expression. Thus, the remaining occurrences are non-assignable. Law 7. hchange attribute typei class C extends D { public T a; ads cnds mds }. =cds,M ain. class C extends D { public T 0 a; ads cnds mds }. provided (↔) T ≤ T 0 and every non-assignable occurrence of a in expressions of mds, cds and Main is cast with T or any subtype of T declared in cds. (←) every expression assigned to a, in mds, cds and c, is of type T or any subtype of T; 2.

(43) 21. 3.2 LAWS. 3.2.3. Methods. The following laws concern methods, both their declarations and calls. In the following laws, m is the name of the method, rt is its return type, mbody is its body, and pds is its declared list of parameters. Law 8. hchange method visibility: from default to publici class C extends D { ads cnds rt m(pds){mbody} mds }. =cds,M ain. class C extends D { ads cnds public rt m(pds){mbody} mds }. 2 Law 9. hchange method visibility: from protected to publici class C extends D { ads cnds protected rt m(pds){mbody} mds }. =cds,M ain. class C extends D { ads cnds public rt m(pds){mbody} mds }. 2 Law 10. hchange method visibility: from private to publici class C extends D { ads cnds private rt m(pds){mbody} mds }. =cds,M ain. class C extends D { ads cnds public rt m(pds){mbody} mds }. provided (←) B.m(), for any B ≤ C, appears only in C’s body..

(44) 22. NORMAL FORM REDUCTION. 2. These laws are very similar to the laws to change the visibility of attributes, as the visibility modifiers have the same semantics for both of attributes and methods. It is also important to mention that Laws 8 and 9 do not have any conditions because we are considering that the internal classes are in the same package.. Law 11. hintroduce void method redefinitioni. class B extends A { ads cnds void m(pds) {mbody} mds } class C extends B { ads0 cnds0 mds0 }. =cds,M ain. class B extends A { ads cnds void m(pds) {mbody} mds } class C extends B { ads0 cnds0 void m(pds) { super.m(α(pds)); } mds0 }. provided. (→) m(pds) is not abstract and it is not declared in mds’.. 2. This law introduces a trivial redefinition of a superclass method, inserting a call to the original method in its body. α(pds) is the list of identifiers of the formal parameters. The adaptation of this law from ROOL to Java had to consider the possibility of method overloading, by using the complete method signature as its identifier, not just its name.. Law 12. hintroduce non void method redefinitioni.

(45) 23. 3.2 LAWS. class B extends A { ads cnds rt m(pds) {mbody} mds } class C extends B { ads0 cnds0 mds0 }. =cds,M ain. class B extends A { ads cnds rt m(pds) {mbody} mds } class C extends B { ads0 cnds0 rt m(pds) { return super.m(α(pds)); } mds0 }. provided (→) m(pds) is not abstract and it is not declared in mds’. 2 The law above is very similar to Law 11, but it requires the introduction of the return clause, since the method must yield the result of executing super.m(α(pds)). Law 13. hmove redefined method to superclassi. class B extends A { ads cnds rt m(pds) {mbody} mds } class C extends B { ads0 cnds0 rt m(pds) {mbody 0 } mds0 }. =cds,M ain. class B extends A ads cnds rt m(pds) { if (! this instanceof C) {mbody} else {mbody 0 } } mds } class C extends B { ads0 cnds0 mds0 }. provided (↔) (1) super and private attributes do not appear in mbody 0 ; (2) super.m(pds) does not appear in mds0 ..

(46) 24. NORMAL FORM REDUCTION. (→) mbody 0 does not contain uncast occurrences of this nor expressions of the form ((C)this).a for any protected attribute a in ads0 . (←) m(pds) is not declared in mds0 . 2 In the law above, we eliminate a method redefinition by moving its body to the superclass. The original method body is modified to test the object type before choosing which body to execute, as a means to simulate dynamic binding.. Law 14. hmove original method to superclassi class B extends A { ads cnds mds } class C extends B { ads0 cnds0 rt m(pds) {mbody} mds0 }. =cds,M ain. class B extends A { ads cnds rt m(pds) {mbody} mds } class C extends B { ads0 cnds0 mds0 }. provided (↔) (1) super and private attributes do not appear in mbody; (2) m(pds) is not declared in any subclass of B in cds; (3) m(pds) is not private. (→) (1) m(pds) is not declared in mds; (2) mbody does not contain uncast occurrences of this nor expressions in the form ((C)this).a for any protected attribute a in ads0 . (←) (1) m(pds) is not declared in mds0 ; (2) D.m(e), for any D ≤ B and D 6≤ C, does not appear in cds, M ain, mds or mds0 . 2 Methods can be moved up to a superclass, since they will be inherited by their original owner classes. This transformation can be useful to allow calling a method in a broader range of objects..

(47) 25. 3.2 LAWS. Law 15. hchange parameter typei class C extends D { ads cnds rt m(T x, pds) {mbody} mds }. =cds,M ain. class C extends D { ads cnds rt m(T 0 x, pds) {mbody} mds }. provided (↔) T ≤ T 0 and every non-assignable occurrence of x in expressions of mbody are cast with T or any subtype of T . (←) (1) every actual parameter associated with x in mds, cds and M ain is of type T or any subtype of it; (2) every expression assigned to x in mbody is of type T or any subtype of T ; (3) every use of x as the method return in mbody is for a corresponding declared return of type T or any supertype of T . 2 It is possible to change the type of a parameter, if the new type is related to the original one by a typing hierarchy. When changing to a supertype, casts are needed where the subtype was expected; when changing to a subtype, the restrictions are stronger, requiring every actual parameter to be compatible with the new type. In the following law, return types can be changed in a similar way. Law 16. hchange return typei class C extends D { ads cnds rt m(pds) {mbody} mds }. =cds,M ain. class C extends D { ads cnds rt0 m(pds) {mbody} mds }. provided (↔) rt ≤ rt0 (→) every call to m(pds) used as a expression is cast to rt. (←) every expression used in return clauses in mbody is of type rt or of any subtype of rt. 2.

(48) 26. NORMAL FORM REDUCTION. In the following law, [result = val/return val] means that every occurrence of return val inside the method body is replaced by result = val. Law 17. heliminate multiple return pointsi. class C extends D { ads cnds rt m(pds) { mbody } mds }. =cds,M ain. class C extends D { ads cnds rt m(pds) { rt result; mbody [result = val/return val] return result; } mds }. provided (→) (1) the variable result is not already declared in mbody; (2) return clauses are present only inside mutually exclusive paths. 2 Given some conditions, multiple return points can be eliminated by declaring a return variable, changing its value in the previous return locations, and returning the variable in the last line of the method. We can only eliminate them if they are not used to control the execution flow, this is captured by the condition that requires the return clauses to be inside mutually exclusive paths. If they are used to break loops, for instance, we cannot eliminate them, since it would change the original behavior of the method. Law 18. hmethod elimination/introductioni class C extends D { ads rt m(pds) {mbody} cnds mds }. =cds,M ain. class C extends D { ads cnds mds }. provided (→) B.m(e) does not appear in cds, M ain nor in cnds, mds, for any B such that B ≤ C. (←) m(pds) is not declared in mds nor in any superclass or subclass of C in cds..

(49) 27. 3.2 LAWS. 2 It is possible to remove a method declaration if it is not used. Methods that are not called anywhere else are often useless, and can be therefore eliminated. To introduce a new method declaration, it suffices that no other method exists in mds with the same signature. In the next law, we use the notation cds CDS, N B c = d. In this notation, cds CDS, C is the union of the class declarations in cds and CDS, and cds, N B c = d indicates that the equation c = d holds inside the class named N , in the context defined by the set of class declarations cds. The function vardecs(pds, e) introduces a set of variables which have the same names and types of the formal parameters pds, and are initialized with the arguments used to call the method. Consider the following example: pds = (int a, String s) e = (100, “ABC”) vardecs(pds, e) ⇒ int a = 100; String s = “ABC”; In this way we can capture the call by value mechanism of Java. Using this function, we can replace a method call by its body, as in the following law, which targets calls to methods via super. Law 19. heliminate calls to void methods via superi Consider that CDS is a set of two class declarations as follows. class B extends A { ads cnds void m(pds) {mbody} mds }. class C extends B { ads0 cnds0 mds0 }. Then we have that cds CDS, C B super.m(e) = vardecs(pds, e); mbody provided (→) (1) super, this, and the private attributes in ads do not appear in mbody; (2) mbody does not contain return clauses. 2 Eliminating calls to void methods require only replacing their call by their body, since both are commands. When the method is not void, it can be used as an expression, preventing a simple replacement for its body. To address calls to methods via super in this case, we have devised the following law..

(50) 28. NORMAL FORM REDUCTION. Law 20. heliminate calls to non void methods via superi Consider that CDS is a set of two class declarations as follows. class B extends A { ads cnds rt m(pds) {mbody} mds }. class C extends B { ads0 cnds0 mds0 }. is included in cds. Then cdsCDS, C B. rt a = super.m(e). =. rt a; vardecs(pds, e); mbody [a = result/return result]. provided (→) (1) super, this, and the private attributes in ads do not appear in mbody; (2) m(pds) does not have multiple return points. 2 It is important to remark that we consider only one possible context for the method call, which is using it as right side expression in an assignment. Addressing every possible occurrence of a method call could complicate the law, and as we have laws to convert other occurrences to this form, this law can be applied in most cases. Law 21. hvoid method call eliminationi Consider that the following class declaration class C extends D ads cnds void m(pds) {mbody} mds } is included in cds, and that cds, A B le : C, meaning that le has static type C in class A. Then cds, A B le.m(e) = assert le ! = null; vardecs(pds, e); mbody[le/this] provided (→) (1) m(pds) is not redefined in cds and mbody does not contain references to super; (2) all attributes which appear in mbody of m(pds) are not private; (3) C’s methods called within mbody are not private; (4) mbody does not contain recursive calls; (5) pds does not occur in e; (6) mbody does not contain return clauses; (7) attributes and methods of C within mbody are preceded by this..

(51) 29. 3.2 LAWS. 2 Once there is no redefinition of methods, it is possible to copy its body where it is called, after performing some parameter substitution. In the above law, the method call le.m(e) might throw an exception when le is null. This is captured on the right-hand side by the assertion statement. We can only eliminate method calls to non-recursive methods. In ROOL, there are recursive commands, then one could easily rewrite a recursive method as a recursive command and inline it, but in Java there is no such a feature. Nevertheless, we can get close to an imperative program by transforming a method into a static method, as captured by Law 24. Eliminating non void calls requires different laws, since calls of this type can be used as expressions, and contain return clauses inside their body. The following laws address this case. Law 22. hnon void method call elimination - when used as an expressioni Consider that the following class declaration class C extends D ads cnds rt m(pds) {mbody} mds } is included in cds, and that cds, A B le : C. Then. rt a = le.m(e). =. rt a; assert le ! = null; vardecs(pds, e); mbody[le/this] [a = result/return result]. provided (↔) (1) m(pds) is not redefined in cds and mbody does not contain references to super; (2) all attributes which appear in mbody of m(pds) are public; (3) mbody does not contain recursive calls; (4) pds does not occur in e. (→) (1) m(pds) does not have multiple return points; (2) attributes and methods of C within mbody are preceded by this. 2 Law 23. hnon void method call elimination - when used as a statementi Consider that the following class declaration.

(52) 30. NORMAL FORM REDUCTION. class C extends D ads cnds rt m(pds) {mbody} mds } is included in cds, and that cds, A B le : C. Then. le.m(e). =. assert le ! = null; vardecs(pds, e); mbody[le/this] [skip/return val]. provided (→) (1) m(pds) is not redefined in cds and mbody does not contain references to super; (2) all attributes which appear in mbody of m(pds) are public; (3) mbody does not contain recursive calls; (4) pds does not occur in e; (5) attributes and methods of C within mbody are preceded by this. 2 When eliminating non-void method calls used as statements, its return value can be ignored. It is done by removing the return clause from its body, replacing it by skip (syntactic sugar used to represent an empty command). Law 24. hMake method statici. class C extends D { ads rt m(pds) {mbody} cnds mds } cds, M ain where, given o of type C: cnds0 = cnds[C.m(o, e)/o.m(e)] mds0 = mds[C.m(o, e)/o.m(e)] cds0 = cds[C.m(o, e)/o.m(e)] M ain0 = M ain[C.m(o, e)/o.m(e)]. class C extends D { ads static rt m(C c, pds) { mbody[c/this] = } cnds0 mds0 } cds0 , M ain0.

(53) 3.2 LAWS. 31. provided (↔) (1) there are no references to super in mbody; (2) there are no redefinitions of m(pds); (3) attributes and methods of C within mbody are preceded by this. 2 This law is more complicated than the ones presented before. The main reason is that it changes the code in the method definition, as well as in every occasion the original method is called. To convert an instance method into a static method, the object where it is called is now passed as a parameter and references to this inside mbody are replaced by the formal parameter identifier. After changing the method signature, every call to o.m(αpds) is converted to a call to C.m(o, αpds). It is also worth mentioning that the original call is converted into a static call..

(54) 32. NORMAL FORM REDUCTION. Law 25. hmove static method to another classi class B extends A { adsb cndsb static rt m(pds) {mbody} mdsb } class C extends D { adsc cndsc mdsc } cds M ain. class B extends A { adsb cndsb 0 mdsb 0 } class C extends D { = adsc cndsc 0 static rt m(pds) {mbody} mdsc 0 } cds0 M ain0. where: cndsb 0 = cndsb [B.m(e)/C.m(e)] mdsb 0 = mdsb [B.m(e)/C.m(e)] cndsc 0 = cndsc [B.m(e)/C.m(e)] mdsc 0 = mdsc [B.m(e)/C.m(e)] cds0 = cds[B.m(e)/C.m(e)] M ain0 = M ain[B.m(e)/C.m(e)]. provided (↔) (1) static attributes and methods are accessed by the class name in mbody; (2) attributes accessed in mbody are public; (→) m(pds) is not declared in mdsc ; (←) m(pds) is not declared in mdsb ; 2 A static method can be moved to another class, once its body only accesses members which are visible in the class to which it is moved. 3.2.4. Constructors. This subsection presents the laws for constructors. They are an important contribution of this work, since, to our knowledge, there is not a comprehensive set of laws defined for constructors in object-oriented languages, and particularly in Java. For instance,.

(55) 33. 3.2 LAWS. ROOL [SB06] has a very limited form of constructor, and laws for this feature are not deeply explored. The set laws devised for Java is presented in the sequel. Law 26. hintroduce empty default public constructori class C extends B { ads cnds mds }. =cds,M ain. class C extends B { ads public C() {} cnds mds }. provided (→) a default constructor is not already defined in C. 2 The law depicted above shows when it is possible to introduce or remove a default constructor. When a class does not define any constructor, a default constructor is automatically inserted by the compiler. Law 27. heliminate calls to super() inside constructorsi class C extends B { ads C(pds) { super(); cbody } cnds mds }. =cds,M ain. class C extends B { ads C(pds) { cbody } cnds mds }. provided (←) cbody does not contain any call to a super constructor. 2 Removing calls to super() is very useful to later on inline the constructor body, as presented in Law 31. A subtle fact about this law is that Java compilers automatically insert the super() call in the constructor first line, when no other constructor is explicitly called. This is the reason why both pieces of code are equivalent; it is just a matter of choosing to keep the call explicit or implicit..

(56) 34. NORMAL FORM REDUCTION. Law 28. heliminate calls to super(α(pds))i class B extends A { ads B(pds) {cbody} cnds mds } class C extends B { ads0 C(pds) { super(α(pds)); cbody 0 } cnds0 mds0 }. =cds,M ain. class B extends A { ads B(pds) {cbody} cnds mds } class C extends B { ads0 C(pds) { cbody cbody 0 } cnds0 mds0 }. provided (→) (1) cbody does not contain calls to super; (2) B contains an empty default constructor; (3) all attributes which appear in cbody of B(pds) are public; (←) B has a non private constructor B(pds), whose body is cbody. 2 The next law is similar to the previous one, but instead of eliminating calls to the default super constructor, it eliminates calls to the non-default ones. In this case, we have to copy the super constructor body where it was called. No substitution of names were needed, since both constructors have the same formal parameters pds. There is also a similar law to eliminate calls to this()..

(57) 35. 3.2 LAWS. Law 29. heliminate calls to this(e)i class C extends B { ads C(pds){ cbody } C (pds0 ){ this(e); cbody 0 } cnds mds }. =cds,M ain. class C extends B { ads C(pds){ cbody } C(pds0 ){ vardecs(pds, e) cbody cbody 0 } cnds mds }. provided (↔) e matches pds; (→) (1) cbody does not contain calls to this(); (2) cbody’ does not contain calls to super. 2 The above law holds for default or non-default constructors. When a call to a default constructor is eliminated, we need only to copy its body inside the callee constructor body, since, in this case, pds is empty. To assure that e corresponds to pds, one condition was introduced, as there can be multiple constructors defined with parameters. Law 30. heliminate non-default constructorsi class C extends B { ads C(pds) {cbody} cnds mds }. =cds,M ain. class C extends B { ads cnds mds }. provided (→) There are no calls to new C(pds) in ads, cnds, mds, cds, and Main (including calls via super or this); (←) C(pds) is not already declared in C. 2.

(58) 36. NORMAL FORM REDUCTION. This law is similar to the elimination of methods. It removes the constructor definition when there are no calls to it, a means of eliminating unnecessary code. It can also be used to introduce new constructors. Law 31. heliminate calls to non-default constructorsi Consider that the following class declaration class C extends D ads C(pds) {cbody} cnds mds } is included in cds, and that cds, A B C le. Then C le = new C(e);. =cds,M ain. C le = new C(); vardecs(pds, e); cbody[le/this]. provided (→) (1) C has an empty default constructor;(2) there are no calls to super or this() in cbody; (3) pds does not occur in e; (4) attributes and methods of C within cbody are preceded by this; (5) all attributes which appear in cbody are not private; (6) C’s methods called within cbody are not private. 2 Calls to constructors can be eliminated in a similar way it was done to methods. The main difference is that we cannot completely eliminate it, since calling the constructor is mandatory to object creation. Our strategy is to keep a call to an empty default constructor, and then execute the commands to initialize the object attributes, maintaining the original constructor behavior. The next law addresses the same issue for default constructors. Law 32. hinline default constructor calls and eliminate its bodyi class C extends B { ads C() {cbody} cnds mds } cds M ain. class C extends B { ads C() {} cnds0 = mds0 } cds0 M ain0.

(59) 37. 3.2 LAWS. where cnds0 = cnds[C c = newC(); cbody[c/this]/C c = newC()] mds0 = mds[C c = newC(); cbody[c/this]/C c = newC()] cds0 = cds[C c = newC(); cbody[c/this]/C c = newC()] M ain0 = M ain[C c = newC(); cbody[c/this]/C c = newC()] provided (→) (1) cbody does not contain references to super nor calls to this(); (2) attributes and methods of C within cbody are preceded by this; (3) all attributes which appear in cbody are not private; (4) C’s methods called within cbody are not private. 2 Law 33. hchange constructor visibility: from private to publici class C extends D { ads private C(pds) {cbody} cnds mds }. =cds,M ain. class C extends D { ads public C(pds) {cbody} cnds mds }. provided (←) calls to new C() appears only in C’s body 2 Law 34. hchange constructor visibility: from protected to publici class C extends D { ads protected C(pds) {cbody} cnds mds }. =cds,M ain. class C extends D { ads public C(pds) {cbody} cnds mds }. 2.

Referências

Documentos relacionados

A fim da adequação aos quesitos notificados pelo MTE, a empresa desenvolveu um Plano de Ação com algumas medidas a serem tomadas.. ações não despendiam de recursos

A Equipa de Saúde Mental Comunitária do DPSM – HSFX presta assistên- cia especializada a adultos na área da Saúde Mental no Centro de Saúde de Oeiras, realizando as consultas

Sobre o fato do empresário saber ou ser informado pelo contador que pode ser excluído do Simples Nacional se suas despesas pagas forem 20% superiores que seus ingressos

Assim, tendo como perspectiva tratar da violência infantil no Brasil (questão universal), este projeto de conclusão irá abordar o tema a partir de histórias singulares relatadas

5.5 Conta bancária em Bancos tradicionais e idade, género e habilitações académicas

Para a quantificação do risco e desenvolvimento do processo de análise e ges- tão, alguns elementos são importantes, primeiramente deve-se identificar o agente de perigo, ou o risco

[r]

This article describes the work that the authors are developing towards the specification of a layer for real-time management of user interactions with LMSs,