To save content items to your account,
please confirm that you agree to abide by our usage policies.
If this is the first time you use this feature, you will be asked to authorise Cambridge Core to connect with your account.
Find out more about saving content to .
To save content items to your Kindle, first ensure no-reply@cambridge.org
is added to your Approved Personal Document E-mail List under your Personal Document Settings
on the Manage Your Content and Devices page of your Amazon account. Then enter the ‘name’ part
of your Kindle email address below.
Find out more about saving to your Kindle.
Note you can select to save to either the @free.kindle.com or @kindle.com variations.
‘@free.kindle.com’ emails are free but can only be saved to your device when it is connected to wi-fi.
‘@kindle.com’ emails can be delivered even when you are not connected to wi-fi, but note that service fees apply.
Within the last 20 years, much research and development has been conducted to deliver computer tools to assist engineers in performing their tasks. We are now experiencing the power of worldwide networked computing environments and the ability to easily share large quantities of information over geographically dispersed environments. However, are the computing environments available today supporting engineers in doing their job or defining how they must do their job? We need to develop systems that are more transparent and understandable to the users and that are more responsive to the individual needs and idiosyncrasies of the persons using these assistants.
This article addresses the issue of educating undergraduate engineering students in the appropriate use of computer simulation in the design process. The premise that poorly designed assignments involving simulation can actually impair understanding is addressed. A set of goals for simulation-based exercises is suggested, and some tactics for meeting these goals are introduced. Finally, a specific example of a half-term assignment that is used to meet these goals is provided for illustration.
The process of writing large parallel programs is complicated by the need to specify both the parallel behaviour of the program and the algorithm that is to be used to compute its result. This paper introduces evaluation strategies: lazy higher-order functions that control the parallel evaluation of non-strict functional languages. Using evaluation strategies, it is possible to achieve a clean separation between algorithmic and behavioural code. The result is enhanced clarity and shorter parallel programs. Evaluation strategies are a very general concept: this paper shows how they can be used to model a wide range of commonly used programming paradigms, including divide-and-conquer parallelism, pipeline parallelism, producer/consumer parallelism, and data-oriented parallelism. Because they are based on unrestricted higher-order functions, they can also capture irregular parallel structures. Evaluation strategies are not just of theoretical interest: they have evolved out of our experience in parallelising several large-scale parallel applications, where they have proved invaluable in helping to manage the complexities of parallel behaviour. Some of these applications are described in detail here. The largest application we have studied to date, Lolita, is a 40,000 line natural language engineering system. Initial results show that for these programs we can achieve acceptable parallel performance, for relatively little programming effort.
Hewlett-Packard develops and markets a family of computer-aided engineering products used by high-frequency designers to model the signal path in contemporary communications systems. As design frequencies, clock speeds and packaging densities continue to increase, more designers are finding that system and circuit simulation products need to be complemented by electromagnetic simulation software to develop models for basic circuit functionality or to characterize and compensate undesired parasitic effects. The HP High-Frequency Structure Simulator (HP HFSS) is a frequency-domain, finite element-based simulator, which enables engineers to characterize high-frequency behavior in 2D (transmission lines) and arbitrary 3D structures. Links with mechanical computer aided design (CAD) software have also become more important as the 3D structures to be analyzed by HP HFSS can involve packaging parasitics when the housing in which the electrical circuitry is enclosed becomes an influence on the signal path. Depending upon the complexity of the structure to be analyzed, HP HFSS can require hundreds of Mbytes of RAM and disk during automated adaptive solution convergence processes which determine field and circuit parameter solution results to user-specified accuracies. Although computer resource requirements will always be an important consideration for users of this type of product, another important situation to address for the future involves the exchange of data between the different simulation and modelling tools required to take design from concept through simulation to manufacture. The introduction of physical simulation tools into the traditional circuit simulation arena changes the design process flow and increases the demand for improved integration and interoperability of circuit simulators, numerical EM simulators, and mechanical CAD software. This paper provides an overview of data exchange issues in high-frequency electrical–physical–mechanical design processes.
This paper reports a visual tracking system that can track moving objects in real-time with a modest workstation equipped with a pan-tilt device. The algorithm essentially has three parts: (1) feature detection, (2) tracking and (3) control of the robot head. Corners are viewpoint invariant, hence being utilised as the beacon for tracking. Tracking is performed in two stages of Kalman filtering and affine transformation. A technique of reducing greatly the computational time for the correlaton is also described. The Kalman filter predicts intelligently the fovea window and reduced computation dramatically. The affine transformation deals with the unexpected events when there is partial occlusion.
Chapters 2–11 have described the fundamental components of a good compiler: a front end, which does lexical analysis, parsing, construction of abstract syntax, type-checking, and translation to intermediate code; and a back end, which does instruction selection, dataflow analysis, and register allocation.
What lessons have we learned? I hope that the reader has learned about the algorithms used in different components of a compiler and the interfaces used to connect the components. But the author has also learned quite a bit from the exercise.
My goal was to describe a good compiler that is, to use Einstein's phrase, “as simple as possible – but no simpler.” I will now discuss the thorny issues that arose in designing Tiger and its compiler.
Nested functions. Tiger has nested functions, requiring some mechanism (such as static links) for implementing access to nonlocal variables. But many programming languages in widespread use −C, C++, Java – do not have nested functions or static links. The Tiger compiler would become simpler without nested functions, for then variables would not escape, and the FindEscape phase would be unnecessary. But there are two reasons for explaining how to compile nonlocal variables. First, there are programming languages where nested functions are extremely useful – these are the functional languages described in Chapter 15.
Over the past decade, there have been several shifts in the way compilers are built. New kinds of programming languages are being used: object-oriented languages with dynamic methods, functional languages with nested scope and first-class function closures; and many of these languages require garbage collection. New machines have large register sets and a high penalty for memory access, and can often run much faster with compiler assistance in scheduling instructions and managing instructions and data for cache locality.
This book is intended as a textbook for a one- or two-semester course in compilers. Students will see the theory behind different components of a compiler, the programming techniques used to put the theory into practice, and the interfaces used to modularize the compiler. To make the interfaces and programming examples clear and concrete, I have written them in the ML programming language. Other editions of this book are available that use the C and Java languages.
Implementation project. The “student project compiler” that I have outlined is reasonably simple, but is organized to demonstrate some important techniques that are now in common use: abstract syntax trees to avoid tangling syntax and semantics, separation of instruction selection from register allocation, copy propagation to give flexibility to earlier phases of the compiler, and containment of target-machine dependencies. Unlike many “student compilers” found in textbooks, this one has a simple but sophisticated back end, allowing good register allocation to be done after instruction selection.
A compiler was originally a program that “compiled” subroutines [a link-loader]. When in 1954 the combination “algebraic compiler” came into use, or rather into misuse, the meaning of the term had already shifted into the present one.
Bauer and Eickel [1975]
This book describes techniques, data structures, and algorithms for translating programming languages into executable code. A modern compiler is often organized into many phases, each operating on a different abstract “language.” The chapters of this book follow the organization of a compiler, each covering a successive phase.
To illustrate the issues in compiling real programming languages, I show how to compile Tiger, a simple but nontrivial language of the Algol family, with nested scope and heap-allocated records. Programming exercises in each chapter call for the implementation of the corresponding phase; a student who implements all the phases described in Part I of the book will have a working compiler. Tiger is easily modified to be functional or object-oriented (or both), and exercises in Part II show how to do this. Other chapters in Part II cover advanced techniques in program optimization. Appendix A describes the Tiger language.
The interfaces between modules of the compiler are almost as important as the algorithms inside the modules. To describe the interfaces concretely, it is useful to write them down in a real programming language. This book uses the C programming language.
lex-i-cal: of or relating to words or the vocabulary of a language as distinguished from its grammar and construction
Webster's Dictionary
To translate a program from one language into another, a compiler must first pull it apart and understand its structure and meaning, then put it together in a different way. The front end of the compiler performs analysis; the back end does synthesis.
The analysis is usually broken up into
Lexical analysis: breaking the input into individual words or “tokens”;
Syntax analysis: parsing the phrase structure of the program; and
Semantic analysis: calculating the program's meaning.
The lexical analyzer takes a stream of characters and produces a stream of names, keywords, and punctuation marks; it discards white space and comments between the tokens. It would unduly complicate the parser to have to account for possible white space and comments at every possible point; this is the main reason for separating lexical analysis from parsing.
Lexical analysis is not very complicated, but we will attack it with high-powered formalisms and tools, because similar formalisms will be useful in the study of parsing and similar tools have many applications in areas other than compilation.
LEXICAL TOKENS
A lexical token is a sequence of characters that can be treated as a unit in the grammar of a programming language. A programming language classifies lexical tokens into a finite set of token types.
From the outset, the idea was to try to draw together parts of three vast areas of inquiry – ethics, computing, and medicine – and produce a document that would be accessible by and useful to scholars and practitioners in each domain. Daunting projects are made possible by supportive institutions and colleagues. Fortunately in this case, the support exceeded the daunt.
The idea for the book came while I was at Carnegie Mellon University's Center for Machine Translation and Center for the Advancement of Applied Ethics, and the University of Pittsburgh's Center for Medical Ethics. These three centers of excellence fostered a congenial and supportive environment in which to launch a novel project. Deep thanks are due Jaime Carbonell, Preston Covey, Peter Madsen, Alan Meisel, and Sergei Nirenburg.
In 1992 I organized a session on “Computers and Ethics in Medicine” at the annual meeting of the American Association for the Advancement of Science (AAAS) in Chicago. Four of the contributors to this volume – Terry Bynum, Randy Miller, John Snapper, and I – made presentations. A special acknowledgment is owed to Elliot R. Siegel of the National Library of Medicine and the AAAS Section on Information, Computing, and Communication for encouraging this effort, and for his wise counsel.
The original idea for a book blossomed as it became increasingly clear that there was a major gap in the burgeoning literatures in bioethics and medical informatics.
The birth and evolution of a scientific method is an exciting development. Meta-analysis, described in this chapter as “one of the most important and controversial methodological developments in the history of science,” has changed aspects of scientific inquiry in ways that have not been fully calculated. The technique is nearly as old as this century but as fresh, immediate, and important as this week's journal articles and subsequent lay accounts. In a meta-analysis, the results of previous studies are pooled and then analyzed by any of a number of statistical tools. Meta-analyses are performed on data stored in computers and subjected to computational statistics. The technique grew rapidly in psychology beginning two decades ago and since has become a fixture in the observational investigations of epidemiologists, in reviews of clinical trials in medicine, and in the other health sciences. It has engendered extraordinarily heated debate about its quality, accuracy, and appropriate applications. Meta-analysis raises ethical issues because doubts about its accuracy raise doubts about (1) the proper protections of human subjects in clinical trials; (2) the proper treatment of individual patients by their physicians, nurses, and psychologists; and (3) the correct influence on public policy debates. This chapter lays out ethical and policy issues, and argues for high educational standards for practitioners in each domain.
Introduction
The growth of knowledge presents some of the most interesting and demanding problems in all human inquiry.
Heap-allocated records that are not reachable by any chain of pointers from program variables are garbage. The memory occupied by garbage should be reclaimed for use in allocating new records. This process is called garbage collection, and is performed not by the compiler but by the runtime system (the support programs linked with the compiled code).
Ideally, we would say that any record that is not dynamically live (will not be used in the future of the computation) is garbage. But, as Section 10.1 explains, it is not always possible to know whether a variable is live. So we will use a conservative approximation: we will require the compiler to guarantee that any live record is reachable; we will ask the compiler to minimize the number of reachable records that are not live; and we will preserve all reachable records, even if some of them might not be live.
Figure 13.1 shows a Tiger program ready to undergo garbage collection (at the point marked garbage-collect here). There are only three program variables in scope: p, q, and r.
MARK-AND-SWEEP COLLECTION
Program variables and heap-allocated records form a directed graph. The variables are roots of this graph. A node n is reachable if there is a path of directed edges r → … → n starting at some root r. A graph-search algorithm such as depth-first search (Algorithm 13.2) can mark all the reachable nodes.
reg-is-ter: a device for storing small amounts of data
al-lo-cate: to apportion for a specific purpose
Webster's Dictionary
The Translate, Canon, and Codegen phases of the compiler assume that there are an infinite number of registers to hold temporary values and that move instructions cost nothing. The job of the register allocator is to assign the many temporaries to a small number of machine registers, and, where possible, to assign the source and destination of a move to the same register so that the move can be deleted.
From an examination of the control and dataflow graph, we derive an interference graph. Each node in the inteference graph represents a temporary value; each edge (t1, t2) indicates a pair of temporaries that cannot be assigned to the same register. The most common reason for an interference edge is that t1 and t2 are live at the same time. Interference edges can also express other constraints; for example, if a certain instruction a ← b ⊕ c cannot produce results in register r12 on our machine, we can make a interfere with r12.
Next we color the interference graph. We want to use as few colors as possible, but no pair of nodes connected by an edge may be assigned the same color. Graph coloring problems derive from the old mapmakers' rule that adjacent countries on a map should be colored with different colors.
mem-o-ry: a device in which information can be inserted and stored and from which it may be extracted when wanted
hi-er-ar-chy: a graded or ranked series
Webster's Dictionary
An idealized random access memory (RAM) has N words indexed by integers such that any word can be fetched or stored – using its integer address – equally quickly. Hardware designers can make a big slow memory, or a small fast memory, but a big fast memory is prohibitively expensive. Also, one thing that speeds up access to memory is its nearness to the processor, and a big memory must have some parts far from the processor no matter how much money might be thrown at the problem.
Almost as good as a big fast memory is the combination of a small fast cache memory and a big slow main memory; the program keeps its frequently used data in cache and the rarely used data in main memory, and when it enters a phase in which datum x will be frequently used it may move x from the slow memory to the fast memory.
It's inconvenient for the programmer to manage multiple memories, so the hardware does it automatically. Whenever the processor wants the datum at address x, it looks first in the cache, and – we hope – usually finds it there.