How EiffelStudio Compiles
So far we have relied on the compiling capabilities of EiffelStudio without exploring them in any detail. We must now understand the principles behind EiffelStudio's compiling strategy, in particular how it reconciles fast turnaround, efficient generated code, and strong typing.
Compilation is automatic
Any speed issue aside, the most important property of the compilation process is that it is entirely automatic.
You've seen it from the beginning of this Tour: all the information the compiler has -- obtained from a configuration file, as here, or generated automatically by the other options -- is the name of the root class and the list of directories holding Eiffel clusters. In fact it only needs these directories for non-precompiled clusters; here, because we are using precompiled EiffelBase, and because we've started EiffelStudio from the Tour's own root cluster directory, EiffelStudio has all the information it needs.
The compiler takes care of finding all the classes that must be compiled.
There is never any need, when compiling Eiffel systems, to supply "Make files", "include files", or other manual descriptions of inter-module dependencies.
Compilation modes
EiffelStudio offers several forms of compilation, which you can see in the entries of the Compile
menu (don't trigger any of them right now) as well as keyboard shortcuts and, in some cases, buttons:
- Melt: quick incremental recompilation, doesn't optimize code for changed parts.
- Freeze: incremental recompilation, not as fast as Melt, but generates more efficient code for changed parts.
- Finalize: recompile entire system, generating highly optimized code.
- Precompile (available both in the
Project
menu and throughTools --> Precompilation wizard
), to process an entire library, on which many systems can then rely without having to compile it.
You'll quickly learn to use each of these modes to suit your needs.
Criteria
EiffelStudio's Melting Ice Technology reconciles the following goals:
- Security and efficiency of the generated code: compiling techniques for the strongly typed Eiffel programming language ensure that compilers can catch many errors before it is too late, and generate more efficient code. The "validity constraints" of the language, whose violations are caught as compilation errors, are particularly useful here, playing the role of enforceable design rules.
- Quick turnaround : you should experience an almost immediate transition from the time you write or (more commonly) modify software to the time you can execute it.
- C code generation: for portability, it is useful to take advantage of C in its proper role, that of a portable assembly language. C's closeness to machine concepts -- one of the very properties making it less suitable for human programming except in the case of short routines to access low-level mechanisms --, its almost universal availability, and its good level of standardization, make it an excellent target language for a code generator. This also enables the environment to benefit from the often extensive optimizations performed by good C compilers, and facilitates interfacing new software with the large body of existing C-based systems, tools and libraries. As the final output of Eiffel compilation, you can obtain a complete C package that you can either C-compile on the same machine or port to other platforms, making EiffelStudio a tool of choice for cross-platform development: develop on one platform, deploy on one or more others.
The Melting Ice Principle
The idea of the melting ice is based on the observation that, for the practicing software developer, the crucial day-to-day compilation problem is not how to process an entire system but how best to process a changed system, of which an earlier state had previously been processed.
The change may be big or small; the system may be big or small. ("Small system" here means up to a few tens of thousands of lines.) This gives four possible cases, of which only one is really critical:
Small System | Large System | |
Small Change | xxx | |
Big Change |
If the system is small, as in both of the left column entries, speed of recompilation with a good compiler will be acceptable.
In the bottom-right box, the developers have spent days or weeks changing many classes in a large system, so they will not resent having to wait a little to see the results of the recompilation, as long as the time remains reasonable. In EiffelStudio this corresponds, as we'll see shortly, to finalization, which is in fact fairly fast anyway, but not as fast as the incremental modes.
In the day-to-day, minute-by-minute practice of building and modifying software, the case that recurs by far the most often -- and can cause most frustration -- is the one marked xxx: you change only a small share of a big system. Then the result should come quickly enough. More precisely:
The "logical size" of a change may be different from its physical size because a small physical change in a class may have consequences in many others. Imagine for example that you add a feature to class ANY
, although this is an extreme case and won't normally happen. Since every class is a descendant of ANY
, the logical change may affect the entire system.
In practice, however, most small physical changes will also be small logical changes and will only cause minimal recompilation. In particular, EiffelStudio will detect that a change does not affect the interface of a class -- for example if it's only a change to non-exported features -- avoiding the need to re-process its clients.
Processing such incremental changes, in time proportional to the logical size of the changes, is known in EiffelStudio as melting. The reason for this terminology is the metaphor illustrated on the following figure. Think of a compiled system as a block of ice; it may have taken some time to "freeze" -- compile. Now you start working on it again; the changes are like melted drops of water, dripping from the ice as a result of the heat generated by your work.
The Melting Ice Technology ensures that incremental compilation will only process the "melted" part, usually small, leaving alone the "frozen" part, which may be large. This is crucial to the incrementality of the mechanism.
The roles of the four compilation modes follow from this analysis:
- Melting is the fastest mode: it processes the melted part without affecting the frozen part. With EiffelStudio, the melted elements will be interpreted while the rest is compiled.
- Freezing is the process of putting back the melted parts into the "freezer": bringing them to the same compiled state as the parts that have not been modified.
- Finalizing is the non-incremental process of producing a stand-alone C package and the resulting executable, extensively optimized, from the current system.
- Precompiling is the process of compiling an entire set of reusable classes, once and for all, so that it can be shared by many systems and many users without duplicating the code or compiling it again for each project.
Properties of the compilation modes
The following table summarizes the differences between the four compilation modes:
Regenerate C Code? | Incremental? | Compilation result shared between projects? | |
Melt | No | Yes (fast) | No |
Freeze | Yes | Yes (but requires C compilation of changes and linking) | No |
Finalize | Yes | No | No |
Precompile | Yes | No | Yes |
During the production and modification of your software, you will usually alternate between melting and freezing, since both of these modes are incremental. Most of the time, you will simply melt, since melting satisfies the Melting Ice Principle: the time to get back to a working system is very short -- proportional to the size of the changes. Note in particular that the unit of melting is the smallest possible one: each feature of a class -- attribute or routine -- may be melted separately.
The main difference between melting and freezing is that freezing implies re-generating C code for the changed elements, and hence relinking the system as well. In contrast, when you melt changes, you do not change any C code: it remains frozen.
As a consequence, melting can only process changes to Eiffel code. If you add new external code (in C, C++ or other languages whose modules will require linking), you must freeze. This is also true if you add new Eiffel agents. If you ask for a Melt in such cases, the operation will trigger a freeze anyway. More generally, the Compile
button, which you have used a number of times to recompile the system in this Tour, triggers a Melt by default, and a Freeze when it has to.
EiffelStudio knows how to hide the differences and present you with a uniform view of the frozen parts (the C code) and the melted parts. Here indeed is the full view of the picture that was previously given in part:
When you examine a component of the system -- to edit a class, produce a view such as Contract or Interface, enable a breakpoint on a routine, run the system, inspect a run-time object -- EiffelStudio automatically knows where to look for the corresponding information: melted or frozen part. If one of your actions requires melting or freezing more elements, EiffelStudio will also handle this automatically.
As suggested by the lower red arrow, successive melting operations "pour water into the bowl", corresponding to the elements that you have changed since the last freeze. Freezing, represented by the top red arrow, updates the C code so that it integrates all the latest changes, emptying the bowl in the process.
Because the difference between melted and frozen code is largely invisible to users of the environment, the term workbench code will cover both kinds; workbench code is code resulting from a succession of freezing and melting operations. As long as you are working within EiffelStudio, you are using workbench code.
When you are happy with the results of your development, you will normally finalize the system, thereby generating final code. Although not strictly required, this step is in most cases appropriate since final code is significantly more efficient than workbench code in both time and space: finalization performs a number of optimizations -- dead code removal, replacement of dynamic by static binding -- that wouldn't be justified in incremental development where, for example, some code element that is "dead" one minute may be resurrected the next moment through the addition of just one line of text. In addition, because finalized code is more efficient than frozen code, it is the natural choice if, using EiffelStudio for cross-platform development, you wish to port the resulting C-package to other architectures.
If you have a set of reusable classes that may be useful to many applications, you can precompile them into a library. This set of classes must be self-contained in the sense that all the classes needed by any of them must be either in the library itself or in another library that you will include in the precompilation.
Bytecode
The result of melting operations -- the contents of the "bowl" -- is an internal software representation known as melted code or (for no particularly good reason) as bytecode. EiffelStudio generated bytecode serves two complementary purposes:
- It can be executed directly. This is what happens during melting: while the rest of your system, the frozen part, is executed in the form produced by Eiffel compilation generation and C-compilation of the result, the melted part is interpreted "as is" without further translation.
- It can be compiled into C for further processing.
Internally, the melted code is in a file simple
.melted
(where simple
is our project's name) in the subdirectory EIFGENs/simple/W_CODE
of the project directory. The file is not human-readable, but as you add elements to your software and melt you watch its size grow. Whenever you freeze, it's emptied.
Degrees
You can now see the reason behind the terminology used to describe compilation steps, called degrees on the messages that flash on the screen when you do a compilation. The names are inspired by the international temperature scale -- Celsius, also known as centigrade -- where water freezes at 0 (and boils at 100, but Eiffel software never reaches that). For EiffelStudio:
- Compilation starts at degree 6, which examines the clusters of your system to determine what classes may have changed. In many cases the compilation can safely skip part of this degree.
- Degree 5 parses modified classes. It's executed not only when you explicitly request a compilation, but also when you save a class from the EiffelStudio editor, or exit from an external editor, so that you can see and fix syntax errors without delay.
- Degrees 4 down to 1 take care of melting.
- Negative degrees only take place when you freeze or finalize.
- After negative degrees comes C-compilation if needed.
Using melting and freezing
When should you melt, freeze, finalize or precompile? The answers are simple and follow directly from the preceding overview; they provide the key to getting the environment to work for you in the most effective way possible.
Melting is the bread and butter of the Eiffel developer. As you build your software, either from scratch or by modifying an existing system, you will regularly melt to benefit from the various checks that compilation performs and, of course, to generate executable code that you can test and debug immediately. During this process, there is no need to refreeze, since this operation (although still incremental) takes significantly more time than melting.
Only two operations, noted above, require freezing: the addition of external (non-Eiffel) routines, such as C functions or C++ classes, and the addition of agents. The reason is easy to understand: the EiffelStudio compiler knows how to melt Eiffel software, but not software written in C or other languages; agents similarly require special code generation.
For the first compilation of a system that does not use precompiled EiffelBase, a Freeze is needed since class ANY
, from which all other classes inherit, uses some external routines. In this case the environment automatically starts a freeze even if you just click Melt. This does not apply if you have access to precompiled EiffelBase.
Except for the addition of external routines or agents, freezing is never strictly necessary. It is indeed possible to use melting throughout a development, never requesting a freeze after the first compilation. But as the melted-to-frozen ratio grows, you may detect a certain degradation in the performance of the system (determined by how big a share of your system is melted, not how many times you melt it). After a while, then, you may want to refreeze. Like melting, freezing is incremental: only those parts of a system that have been logically changed will be recompiled; as with melting, the determination of what needs to be recompiled is entirely performed by the environment, without any manual intervention on the developer's part.
The principal difference is that freezing takes longer than melting. Because of this you are requested to confirm the first time you freeze. Freeze the example system by choosing the menu entryProject --> Freeze
You get the following dialog:
Note the No
option: by default, freezing will start a C compilation, but you can stop after C generation if you wish. This is useful for example if you want to generate a C package for cross-development, C-compiling the result on a different platform.
Click Yes
to confirm freeze and C-compilation. Once the Eiffel compilation is complete, a message in the Development Window ( C compilation launched in background
) tells you when that C-compilation has started. C-compilation does not block EiffelStudio: at this point you can continue working with the environment. Any messages from C compiler will appear in the External compilation pane of the Outputs tool.
You will be able to execute the frozen system as soon as the C compilation finishes.
You will note that freezing, although it takes more time than melting, is actually quite fast, both due to the speed of Eiffel compilation and to the structure of the generated C code, designed to optimize the operation of the C compiler.
Using finalizing
The main reason for finalizing a system is run-time performance of the generated system. Finalization enables you to generate the high-performance executables that are among the hallmarks of ISE Eiffel. As a consequence, finalized code is the best vehicle for cross-development: you can port the resulting C package to various target platforms and C-compile them on these platforms.
The optimizations performed by finalization affect both space and time:
- Dead code removal strips the executable module of all the routines in the system that are not actually called, directly or indirectly, by the root's creation procedure. In a large system relying on many general-purpose classes, dead code removal can easily reduce an executable's size by one third or more.
- Finalization also applies static binding to non-polymorphic calls, and inlines some routine calls.
As long as you continue changing, melting and freezing your system, the workbench compiling mechanisms cannot perform such optimizations: if a routine is "dead" today you may resurrect it tomorrow by adding a new call to it somewhere; and if a call is non-polymorphic a single additional assignment may require dynamic binding. Compilation can only generate optimal code by working on a full, stable system. This is the task of finalization.
Cross-development, the second reason for finalizing, is important if you are taking advantage of the portability of ISE Eiffel to develop your system on a certain platform and then run the result on target computers with possibly different architectures. A target machine may lack an ISE Eiffel compiler (unmistakably signaling its owner's backwardness) but include a C compiler. If the development and target platforms are of different architectures you will need to obtain a copy of the run-time system for the target architecture. The run-time system is also ANSI-C-based, so porting it is usually a straightforward matter.
Note that cross-development does not require finalization, since you can cross-compile a frozen version. In practice, however, the finalized version is usually the preferred form for porting a C package because of the performance advantage.
Finalize the example system now by selecting the menu entry Project --> Finalize
Here too you will be asked to confirm, although the dialog enables you to suppress that confirmation for later attempts, and you may skip C compilation. You will note that finalization is longer than freezing, but still remains quite reasonable thanks to the extensive optimization of the Eiffel compilation process and the structure of the generated C code.