| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394 |
- <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
- <html data-lt-installed="true"><head><meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Turbo Pascal 3.0 compiler and code generation internals</title>
- <meta name="description" content="Turbo Pascal 3.0 compiler / code generation internals">
- <style type="text/css">
- body {
- font-family: "Trebuchet MS", Arial, sans-serif;
- background-color: white;
- color: black;
- }
- td {
- vertical-align:top;
- padding: 0.2rem;
- }
- .menu td {
- color: white;
- font-size: 1.3rem;
- background-color: #CC0000;
- vertical-align: center;
- padding: 0.2rem;
- }
- .menu a {
- color: white;
- font-size: 1.3rem;
- text-decoration: none;
- }
- .menu a:hover {
- color: #cccccc;
- }
- #copy {
- background-color: #cccccc;
- text-align: center;
- color: #cc0000;
- }
- .tabmenu td {
- background: #dddddd;
- border:none;
- color: #666666;
- display: inline-block;
- margin:0 10px 0 0;
- font-size: 20px;
- line-height: 20px;
- border-radius: 4px 4px 0 0;
- padding:10px 16px;
- width:auto;
- cursor:pointer;
- }
- .tabmenu td:hover:not(.active) {
- background: #aaaaaa;
- color:#ffffff;
- }
- .tabmenu td.active {
- background: #666666;
- color:#ffffff;
- }
- .tabmenu td.cart {
- background: #888888;
- color:#ffffff;
- }
- .img {
- padding: 0;
- }
- table {
- border-spacing: 0px;
- border: none;
- table-layout: fixed;
- }
- th {
- background: #cccccc;
- text-align: left;
- font-size: 1.3rem;
- font-weight: normal;
- padding: 0.2rem;
- }
- ul {
- padding: 0.4rem;
- margin: 0 0 0 0.8rem;
- }
- p {
- margin: 0.5rem 0 0 0;
- }
- h2 {
- font-size: 1.3rem;
- margin: 0.5rem 0 0.5rem 0;
- }
- h3 {
- font-size: 1rem;
- margin: 0.5rem 0 0.5rem 0;
- }
- .t tr:nth-child(odd) {
- background: #eeeeee;
- }
- #main tr:nth-child(odd) {
- background: #eeeeee;
- }
- #main tr.separator {
- background: #aaaaaa;
- padding: 0px;
- height: 1px;
- }
- #main td.separator {
- background: #aaaaaa;
- padding: 0px;
- height: 1px;
- }
- #submit {
- background: #aaaaaa;
- margin: 4px 2px;
- border: none;
- color: white;
- padding: 10px 16px;
- text-align: center;
- text-decoration: none;
- display: inline-block;
- font-size: 20px;
- cursor: pointer;
- }
- </style><style id="_fc_">[d__],[d__][style]{color:rgba(0, 0, 0, 1)!important}::placeholder{opacity:1!important;}[b__]{border:1px solid black!important}</style></head>
- <body><table width="100%"><tbody><tr><th>
- <a href="https://www.pcengines.ch/index.htm"><img src="Turbo%20Pascal%203.0%20compiler%20and%20code%20generation%20internals_fichiers/logo.gif" width="331" height="52" alt="PC Engines Home"></a>
- </th></tr>
- <tr class="menu"><td> <a href="https://pcengines.ch/about.htm">About</a>
- | <a href="https://pcengines.ch/apu2.htm">APU2</a>
- | <a href="https://pcengines.ch/cflash.htm">Flash</a>
- | <a href="https://pcengines.ch/test.htm">Tools</a>
- | <a href="https://pcengines.ch/order.htm">Shop</a>
- | <a href="https://pcengines.ch/support.htm">Support</a></td></tr></tbody></table>
- <table class="t" width="100%"><tbody><tr><th width="100%">Turbo Pascal 3.0 Compiler / Code Generation Internals</th></tr>
- <tr><td>
- The following is documentation I created after reverse engineering the
- Turbo Pascal 3.01A compiler. While many features, e.g. units and objects,
- have been added, today's compiler is still related to the old code.
- <p>Before you flame me about stuff that has been fixed, remember that this
- is about an OLD version of the compiler.
- </p><p>NEW: This file will generate disassembled, commented source from YOUR
- 3.01A compiler -> <a href="https://pcengines.ch/file/scg.zip">SCG.ZIP</a>. Sorry, I cannot
- provide any support for this dusty deck...
- </p><h2>Compiler Structure</h2>
- Compilers usually consist of the following functional groups:
- <ul>
- <li>Lexical analysis</li>
- <li>Syntactical analysis (parser)</li>
- <li>Optimizer</li>
- <li>Code generator</li>
- <li>Symbol table manager</li>
- <li>Error handler</li></ul>
-
- TURBO Pascal isn't your usual compiler... The parser is interspersed with
- portions of the code generator, and there is no optimizer. Most compilers
- need multiple passes to do their work, but TURBO is a (faster)
- single pass compiler.
- <p>Fortunately, programming languages of the Pascal type are designed with simple
- compilation in one pass in mind. All symbols must be defined before they are
- used. The compiler can easily determine the type of a constant without
- looking ahead:
- </p><ul><li>symbol
- </li><li>$7FFF
- </li><li>12345
- </li><li>12345.5 - This is an exception. TURBO first verifies whether a numeric
- constant is an integer or a real constant. Actually, a standard Pascal
- compiler doesn't have to do this - standard Pascal requires that the integer
- section of a real constant be a valid integer number !
- </li><li>09CFH - This notation (used by many assemblers and languages) is a
- negative example. Compilers - like people - read from left to right. To
- read a number in this notation, it has to be read into a buffer to recognize
- its type, then it can be converted. Guess why TURBO uses $ for hex numbers...
- <i>I wonder why Niklaus Wirth chose this notation for Modula-2.</i>
- </li></ul>
- <h2>Lexical analysis</h2>
- The task of the lexical analysis is to read the source code from memory or
- from an include file, to eliminate comments and to recognize compiler
- directives, symbols and keywords.
- <p>It is called by the parser. On each call a language element (keyword, symbol,
- constant...) is read. The starting position is stored. If an error is
- recognized the editor will be started and the cursor will point to this
- position.
- </p><h2>Parsing Program Structures</h2>
- The task of this part of the compiler is to analyze the structure of the
- program to be compiled and to check the syntax. Like most Pascal compilers,
- TURBO uses a recursive descent parser. The code generation is included in
- the parser.
- <p>The compilation of program structures is quite simple. Usually the syntax is
- described in Backus-Naur form or by a "railway diagram". As an example
- the IF statement will be covered.
- </p><p></p><pre> IF cond THEN stat1 { ELSE stat2 }
- ___________________
- ____ ______ ______ ___________ / \
- ->_IF_->_expr_->_THEN_->_statement_-<> -->
- \______ ___________/
- _ELSE_->_statement__
- </pre>
- <p>After reading an element, the parser takes the applicable track. If there
- isn't any the syntax is incorrect, and an error is reported.
- </p><p>It is possible to have a parser generated automatically by a so called
- compiler-compiler, if the Backus-Naur form of the syntax is given.
- Unfortunately this doesn't help very much: The really difficult parts of
- a compiler - code generation and optimization - must still be written
- manually.
- </p><p>How is the IF statement translated ? (The corresponding section in the
- compiler is at offset 6C12). The statement procedure reads an IF and calls the
- IF procedure. First the condition - actually an arithmetic expression
- of type boolean - is evaluated. This is done by calling the expression
- procedure. The expression is read until an illegal symbol (THEN) is found.
- This terminates the expression, which is checked for type boolean. The
- IF procedure inserts a conditional jump to the end of statement 1 here.
- The displacement is inserted later - it is not yet known. If the expression
- has been terminated by something else than a THEN, an error is reported.
- Now the first statement (stat1) is translated. Actually, this is a RECURSIVE
- call of the statement procedure (That's why this is a recursive descent
- parser). Please note that the syntax definition is recursive, too !
- Because of possible nested IF statements the variables of the IF
- procedure are saved on the stack. After this statement, an ELSE
- may follow. If it does, a jump to the end of stat2 is emitted
- and the jump from the beginning of stat1 is patched, then the second statement
- is translated and the second jump patched.
- </p><p>The code produced looks like this:
- </p><p></p><pre> (IF..THEN) (IF..THEN..ELSE)
- cond cond
- JNZ l1 JNZ l1
- JMP l2 JMP l2
- l1: stat 1 l1: stat 1
- l2: ... JMP l3
- l2: stat 2
- l3: ...
- </pre>
- <p>The long jump at the beginning isn't always necessary. Unfortunately, the
- compiler cannot predict how long the statement will be. To improve this, the
- jump would have to be replaced by a short one and the subsequent code moved,
- which would complicate the compiler quite a bit.
- </p><p>All other program structures are translated in a similar way.
- </p><h2>Parsing Arithmetic Expressions</h2>
- The evaluation of expressions is somewhat more complex, as the precedence of
- the operations has to be taken into account. The solution in TURBO is, however,
- quite simple (code starting at 7A70).
- <p>Expressions are usually translated to reverse polish notation (as used on
- Hewlett-Packard calculators and in the programming language FORTH).
- </p><p>There are five groups of operations:
- </p><ul>
- <li>negation (highest precedence)</li>
- <li>NOT</li>
- <li>multiplication, division, ...</li>
- <li>addition, subtraction, ...</li>
- <li>comparisons, IN (lowest precedence)
- </li></ul>
- <p>This translates into the following program structure:
- </p><p></p><pre> PROCEDURE atom; { element }
- BEGIN
- CASE op OF
- CONST:read constant
- VAR :read variable { indexing -> recursive }
- '(' :read expression { recursive }
- ')' must follow
- func :read parameters { recursive }
- emit function call
- TYPE :'(' must follow { type conversion, e.g. Integer(TRUE) }
- read expression { recursive }
- ')' must follow
- convert type -> type wanted
- ELSE syntax error;
- END;
- END;
- PROCEDURE neg; { negation - }
- VAR negflag:BOOLEAN;
- BEGIN
- negflag:=(op=neg);
- atom;
- IF negflag THEN emit negation;
- END;
- PROCEDURE NOT; { NOT }
- VAR notflag:BOOLEAN;
- BEGIN
- notflag:=(op=NOT);
- neg;
- IF notflag THEN emit NOT;
- END;
- PROCEDURE mult_level; { multiplication ... }
- VAR mult_op:operation;
- BEGIN
- NOT;
- WHILE op IN mult_ops DO BEGIN
- save the result;
- mult_op:=op;
- NOT;
- emit operation(mult_op);
- END;
- END;
- PROCEDURE add_level; { addition ... }
- VAR add_op:operation;
- BEGIN
- mult_level;
- WHILE op IN add_ops DO BEGIN
- save the result;
- add_op:=op;
- mult_level;
- emit operation(add_op);
- END;
- END;
- PROCEDURE expression; { comparisons, IN }
- VAR cmp_op:operation;
- BEGIN
- add_level;
- IF op IN cmp_ops THEN BEGIN
- save the result;
- cmp_op:=op;
- add_level;
- emit operation(cmp_op);
- END;
- END;
- </pre>
- <p><b>Example 1:</b> Translation of (a+b)=c -> RPN = a , b + c =
- </p><p></p><pre> curr. char, stack (active procedure), code produced
- ---
- (:expression add_level mult_level not neg atom
- a:... expression add_level mult_level not neg atom
- +:... expression add_level -> MOV AX,a
- b:... expression add_level mult_level not neg atom
- ):... expression add_level -> ADD AX,b
- ):expression add_level mult_level not neg atom
- =:expression
- c:expression add_level mult_level not neg atom
- :expression -> CMP AX,c
- </pre>
- <p><b>Please note:</b>
- </p><ul>
- <li>The parentheses trigger a recursive call of the expression procedure.
- </li><li>The code production always lags behind the analysis. This improves the
- code produced (e.g. <code>ADD AX,b</code>).
- </li></ul>
- <p><b>Example 2:</b> Translation of a+b*c -> RPN = a , b , c * +
- </p><p></p><pre> curr. char, stack (active procedure), code produced
- ---
- a:expression add_level mult_level not neg atom
- +:expression add_level -> MOV AX,a
- b:expression add_level mult_level not neg atom
- *:expression add_level mult_level -> PUSH AX
- -> MOV AX,b
- c:expression add_level mult_level not neg atom
- :expression add_level mult_level -> IMUL c
- :expression add_level -> POP CX
- -> ADD AX,CX
- </pre>
- <h3>Please note:</h3>
- The content of <code>a</code> must be stacked, as the AX register
- is needed for the multiplication. This is recognized by setting the
- flag <code>push_ax</code>. If subsequent code uses the AX register (destroying
- its content), it has to emit <code>PUSH AX</code>. Finally, if this
- has happened, the register must be restored by <code>POP CX</code>.
- <p>The code produced is rather simple-minded. By transforming the
- expression to b*c+a better code could be produced:
- </p><pre> MOV AX,b
- IMUL c
- ADD AX,a
- </pre>
- During evaluation, type checking and type conversion (Integer -> Real...) is
- also done.
- <p>The 8088 instruction set is often not used well. a:=a+1 yields this code (INC
- a would be better):
- </p><pre> MOV AX,a
- ADD AX,#1
- MOV a,AX
- </pre>
- Expressions usually account for the bulk of the code produced, so their
- translation is very important.
- <h2>Optimization</h2>
- The goal of code optimization is reducing the size and/or execution time of
- the code produced. It is usually impossible to find an optimal solution, as a
- space-time tradeoff has to be made. TURBO Pascal doesn't have an optimizer.
- However, to improve the efficiency of your programs by manual optimizations or
- by add-on optimizers, it is good to know how common optimizations work.
- <p>Optimizations can be local or global: They can cover a single statement or an
- entire program. Global optimization is much more difficult and can cause
- problems. GOTO's and function or procedure calls can keep the optimizer from
- working efficiently.
- </p><p>Side effects can cause errors that are hard to find. Try it - you'll get what
- you deserve... An example:
- </p><pre> FUNCTION funny:INTEGER;
- BEGIN
- side_effect:=side_effect+1;
- funny:=5;
- END;
- ...
- a:=side_effect+funny+side_effect;
- </pre>
- The evaluation sequence and thus the result depends on the compiler used.
- <p>Variables don't necessarily stay constant between assignments. Consider this:
- </p><pre> wait_int:=FALSE;
- REPEAT UNTIL wait_int;
- </pre>
- This might wait for an interrupt procedure to set a flag. An optimizing
- compiler would convert this to an endless loop... <i>Modern C compilers use
- the <code>volatile</code> keyword to avoid this.</i>
- <h3>Use of Register Variables</h3>
- Many load and store operations can be eliminated by using register variables.
- On the 8088 this is rather difficult, as there are few registers, often with
- special uses.
- <h3>Common Subexpressions</h3>
- <pre> c:=(a+b)*d;
- e:=g-(a+b);
- </pre>
- The subexpression (a+b) can be used twice. Expressions of the form a[i]:=a[i]+
- 1 also are a good target for optimizations.
- <h3>Array Indexing</h3>
- References with constant indices (a[5]) or indices with a constant offset
- (a[i+1]) can be optimized. Array indexing in loops can often
- be improved considerably, too.
- <h3>Constant Folding</h3>
- Programs can be more readable if constants expressions can be written in a
- symbolic form. The compiler can evaluate these expressions at compilation time.
- <i>Later versions of the compiler do this.</i>
- <h3>Strength Reduction</h3>
- This means replacing operations by "cheaper" equivalents, e.g. x*0.2 instead
- of x/5 (multiplications are faster than divisions).
- <h3>Loop Optimization</h3>
- <pre> FOR i:=1 TO 100 DO dest[i]:=a+b;
- </pre>
- The subexpression a+b can be evaluated outside the loop, as a and b don't
- change in the loop.
- <h3>Dead Code Elimination</h3>
- <pre> CONST debug=FALSE;
- IF debug THEN writeln('Debug');
- </pre>
- The IF statement can be left out - the condition is never met. The same thing
- can be done with procedures which are never used. There are optimizers that
- eliminate all unused procedures from the run-time library of programs
- translated by TURBO Pascal.
- <i>Later versions of the compiler do this.</i>
- <h3>Evaluation of Boolean Expressions</h3>
- <pre> IF (a=5) AND (b=6) THEN ... can be changed into
- IF (a=5) THEN
- IF (b=6) THEN ...
- </pre>
- The same thing can be done with OR and NOT. Never expect boolean expressions
- to be executed completely ! <i>Later versions of the compiler do this.</i>
- <h3>Variable Alignment</h3>
- Variables in the data segment and on the stack should be aligned to even
- offsets to improve performance on 16 bit PC's.
- <h2>Code Generation</h2>
- The code generator has the difficult task of translating the elements
- recognized by the parser into executable code. If it gets difficult to tell
- whether the code has been generated by a human programmer or by a compiler
- then it is indeed a good one... Don't expect too much of this from TURBO.
- In the following sections the code produced by TURBO will be explained.
- <h3>Program</h3>
- <pre> run-time library, if not chain file
- CALL initmem ;set segments
- W mainflag ;see source code
- W turbocs,turbods
- W cssize,dssize
- w heapsize,maxhpsz
- w maxfile,stdinsz,stdoutsz
- MOV BP,SP ;stack frame
- CALL uncrunch ;expand overlays
- W link,*+2
- definition part
- program part = main program
- XOR AX,AX ;Halt
- CALL progend
- </pre>
- <h3>Definition Part</h3>
- The definition part may contain code, therefore it must be skipped over by:
- <pre> JMP l1
- <overlays |="" procedures="" functions="" structured="" constants="">
- l1:
- </overlays></pre>
- <h3>Structured Constants</h3>
- Structured constants are stored in the same format as normal variables.
- <h3>Overlays</h3>
- The space needed for overlays is not stored in the COM file. It is freed by
- the uncrunch procedure. This means moving up the subsequent code. This is
- executed at the beginning of program execution and after loading an overlay
- procedure.
- <pre> CALL rdover ;read overlay file
- W $ffff ;overlay procedure now in memory = invalid
- B 'over.001' ;name of overlay file
- In the section read from the overlay file:
- CALL uncrunch ;expand overlay
- W link,*+2 ;link for uncrunching
- overlay procedure / function
- W link,* ;for uncrunching
- </pre>
- <h3>Forward Definitions</h3>
- For forward definitions a jump to the final definition is produced. The
- displacement is inserted when the real definition is made.
- <pre> JMP defined_proc
- </pre>
- <h3>External Procedures</h3>
- The code read from an external file is not changed.
-
- <h3>Procedure Definitions</h3>
- Local variables of procedures and functions are always stored on the stack.
- This means that only active procedures take up space on the stack. This also
- enables recursive calls. The transfer of parameters and the allocation of
- stack space can be quite complicated, thus slowing down procedure calls.
- <p>For every procedure a data structure called stack frame or activation record
- is built on the stack. The pointer to the stack frame is always stored in the
- BP register (the 8088 can't use the stack pointer SP as index register). The
- structure of the stack frame is as follows:
- </p><pre> BP+.:function result (space allocated by caller)
- BP+.:first parameter
- BP+4:last parameter
- BP+2:return address
- BP+0:pointer to caller's stack frame
- BP-2:new stack frame
- BP-4:local variables
- BP-.:stack top
- </pre>
- The code for a standard procedure entry looks like this:
- <pre> PUSH BP ;save old pointer
- MOV BP,SP ;set new pointer
- PUSH BP ;save new pointer (for display)
- definition part ;constants, local procedures
- SUB SP,#size ;allocate space for local variables
- ;1..2 bytes: DEC SP
- program part ;the actual procedure
- MOV SP,BP ;forget local variables
- POP BP ;restore old pointer
- RET prmsize ;return, remove parameters from stack
- ;no parameters: RET
- </pre>
- How function results are passed depends on their type. Scalars (integer...)
- are returned in AX, for boolean results the flags are set with OR AX,AX. Reals
- are on the stack anyway. Strings must be moved such that they occupy only
- their effective length:
- <pre> MOV DX,#pos_on_stack
- MOV CL,#max_len
- MOV SP,BP
- POP BP
- JMP retstr ;the normal end is omitted
- </pre>
- Unfortunately, things aren't that simple. Consider nested procedure definitions:
- <pre> PROCEDURE level1;
- VAR
- i:INTEGER;
- PROCEDURE level2;
- BEGIN
- i:=0;
- level2;
- END;
- BEGIN
- level2;
- END;
- </pre>
- The inner procedure level2 uses a local variable of level1, but also calls
- itself recursively. The stack offset of i depends on the calling order. TURBO
- Pascal uses a so called display to resolve this. The display contains pointers
- to the stack frames of calling procedures. Each procedure also adds its own
- pointer to the display. The display is an extension of the stack frame.
- <pre> BP+0:old pointer
- BP-2:display outermost procedure
- BP-.:display
- BP-.:display current procedure
- BP-.:local variables
- BP-.:stack top
- </pre>
- This is maintained by the following code:
- <pre> PUSH BP ;save old pointer
- MOV AX,SP ;set new pointer - keep BP
- PUSH [BP-nest*2] ;build display
- .. ;once for each nesting level
- MOV BP,AX ;set new pointer
- PUSH BP ;add own pointer to display
- definition part
- </pre>
- Newer CPU's (186, 286...) have special commands for these operations (ENTER,
- LEAVE). Please note that referencing variables via the display is slower than
- normal references. If speed is important don't nest procedure definitions.
- <h2>Program Structures</h2>
- <h3>Program Part</h3>
- <pre> statements
- l1: JMP l2 ;jump to the end
- POP AX ;GOTO, EXIT: clean up the stack
- JMP l1
- l2:
- </pre>
- GOTO's and EXIT's aren't really that simple. Sometimes stack variables (FOR,
- WITH) must be removed, which is done at the end of the procedure.
- <h3>Statement</h3>
- If the user interrupt directive is set, an INT 3 is emitted for each statement.
- This calls a routine which checks for user interrupts. This feature can be
- "misused" to trace a program or to profile its execution time. If this isn't
- used anywhere in the program, you can also insert breakpoints as INLINE
- statements for debugging with DEBUG.
- <h3>IF</h3>
- This has been covered above.
- <h3>WHILE</h3>
- <pre>l1: condition ;evaluate condition
- J.. l2 ;:condition met
- JMP l3
- l2: statement
- JMP l1 ;try again
- l3: ;end of loop
- </pre>
- <h3>REPEAT</h3>
- <pre>l1: statement
- condition ;evaluate condition
- J.. l2 ;condition met: end
- JMP l1 ;not met: repeat
- l2:
- </pre>
- REPEAT loops are faster than WHILE loops.
- <h3>FOR</h3>
- The counter (stored on stack) and the control variable are independent:
- assignments to the control variable don't change the number of loop executions.
- <pre> starting value -> AX
- PUSH AX
- ending value -> AX
- POP CX
- XCHG CX,AX
- SUB CX,AX ;calculate difference
- JGE l1 ;(DOWNTO: JNG)
- JMP l3 ;don't execute
- l1: INC CX ;(DEC CX)
- <store beginning="" value="" in="" control="" variable="">
- l2: PUSH CX ;save counter
- <statement>
- POP CX ;restore counter
- DEC CX ;(INC CX)
- JZ l3 ;0: done
- INC loop_var ;(DEC) update control variable
- JMP l2 ;loop
- l3: ;end
- </statement></store></pre>
- <h3>CASE</h3>
- <pre> CASE .. OF
- 2,5 : .. ;
- 7..9: .. ;
- ELSE .. ;
- END;
- <scalar expression=""> ;evaluate selection
- CMP AX,#2 ;compare
- JZ ok1 ;:yes
- CMP AX,#5
- JZ ok1 ;:yes
- JMP test2 ;try next case
- ok1: <statement> ;ok - execute
- JMP endcase ;jump to end
- test2: CMP AX,#7 ;check subrange
- JL test2no ;:no
- CMP AX,#9
- JLE ok2 ;:yes
- test2no: JMP else ;no: execute ELSE part
- ok2: <statement> ;ok - execute
- JMP endcase
- else: <statement> ;ELSE part
- endcase:
- </statement></statement></statement></scalar></pre>
- Complicated CASE statements can exceed the range of short jumps. In this case
- so called "hips" are emitted:
- <pre> JMP hip2 ;skip hip
- hip: JMP ok ;jump to statement part
- hip2: ;normal continuation
- </pre>
- Some compilers also use this technique for IF and other statements.
- <h3>GOTO</h3>
- A jump is emitted. If the GOTO leaves a WITH or a FOR block, the stack must be
- cleaned up. This is recognized and fixed at the end of the program part.
- <h3>WITH</h3>
- The compiler has an internal WITH stack. The pointers for indexed WITH's are
- stored on the stack:
- <pre> set pointer to variable
- PUSH ES ;store pointer on stack
- PUSH DI
- statement
- ADD SP,#4 ;remove pointer from stack
- </pre>
- If the address is known at compilation time this is not necessary.
- <h3>Procedure Calls</h3>
- If the directive K+ is set, a stack check is executed:
- <pre> MOV CX,#space_needed
- CALL xchkstk
- </pre>
- Then parameters are evaluated and passed. Normal parameter:
- <pre> evaluate expression
- optional range check
- PUSH DX ;pointer
- PUSH AX ;scalar and pointer
- </pre>
- String:
- <pre> MOV CL,#max_length;string is extended to maximal length
- CALL xstrparm ;-> on stack like a local variable
- </pre>
- Set:
- <pre> MOV CX,#crunch ;set crunch parameter:
- ;lo = number of bytes
- ;hi = number empty bytes at beginning
- CALL xsetparm ;adapt set
- </pre>
- Real: already on stack
- <p>Structured variable
- </p><pre> set pointer to variable
- MOV CX,#size
- MOV xblkparm ;copy variable onto stack
- </pre>
- VAR parameters: put pointer on stack
- <pre> set pointer to variable
- PUSH ES
- PUSH DI
- </pre>
- For overlay procedures this must be inserted:
- <pre> MOV AX,#length/256
- MOV DX,#pos in overlay file / 256
- </pre>
- Then the procedure is called by
- <pre> CALL proc.
- </pre>
- <h3>Function Call</h3>
- Stack space is allocated for the result (SUB SP,#space_needed), everything
- else is the same. On return the result is on stack (real, string) or in AX
- (integer, scalar). It would be easy to return structured variables - I don't
- understand why this isn't a standard feature. It would make things like
- complex arithmetics much easier.
- <h3>Calling Standard Procedures and Functions</h3>
- Standard procedures like Read and Write can have any number of parameters of
- any type. This means much flexibility is needed. This problem is solved in
- TURBO in a very efficient way: The standard procedure table only contains the
- address of the corresponding translation routine. This routine emits the code
- for reading the parameters (some of them passed in registers !) and for
- calling the run-time library. Some functions (Swap, Hi, Lo) don't call a
- procedure but create inline code instead.
- <h3>Assignments and Expressions</h3>
- Scalar / pointer: ES and DI are saved, if necessary. This is only
- done if the expression does not consist of a constant or a simple variable.
- <p>Normal variable:
- </p><pre> set pointer to variable
- PUSH ES ;save pointer to destination variable
- PUSH DI
- evaluate expression
- type conversion
- store result in destination variable
- </pre>
- Structured variable:
- <pre> pointer to second variable
- MOV CX,#size ;pointer to destination variable on stack
- CALL xmovevar ;copy variable
- </pre>
- Type conversions:
- <pre> CALL xintreal ;Integer -> Real
- MOV AH,AL ;Char -> String: char -> second byte
- MOV AL,01 ;length: 1 char
- PUSH AX ;push as string
- CALL xstrch ;String -> Char
- </pre>
- <h3>Expressions</h3>
- The algorithms for translation of expressions were explained in section 4.
- Arithmetic operations for scalars are emitted by ecalc. For each operation
- there's a parameter block (starting at 973E) controlling the code generation.
- <pre> expr1 - 5 -> SUB AX,#5
- expr1 - var -> SUB AX,var
- expr1 - expr2 -> XCHG CX,AX (first result in CX, second in AX)
- SUB AX,CX
- </pre>
- Expressions of the a:=a+1 type aren't translated well. a:=succ(a) is better,
- but not optimal.
- <h3>Set Expressions</h3>
- Sets are stored in a compressed form and must be expanded to their full size
- (32 bytes) for doing set operations. Because of this set operations tend to be
- slow. Set constructors are handled in an inefficient way. [5,var1..var2] is
- translated like this:
- <pre> CALL sldempty ;store empty set on stack
- MOV AX,#5 ;expression = 5
- CALL setincl ;include element in set
- MOV AX,var1 ;first expression subrange
- PUSH AX ;save
- MOV AX,var2 ;second expression subrange
- CALL setinrng ;include subrange in set
- </pre>
- If the parameters are variable this is all right. For constant sets this is
- disastrous.
- <ul>
- <li><code>IF ch IN ['0'..'9','A'..'Z','a'..'z','_'] THEN ...</code>
- <p>The conventional solution. Very slow, as the set is always built when
- this is executed. Takes much space for complicated sets.
- </p></li><li>
- <pre>CONST setcn:SET OF char = ['0'..'9','A'..'Z','a'..'z','_'];
- IF ch IN setcn THEN ...
- </pre>
- This takes up somewhat more space for this example (set constant takes up
- 32 bytes), but is much faster.
- </li><li>
- <pre>CASE ch OF
- '0'..'9','A'..'Z','a'..'z','_':...;
- END;
- </pre>
- For simple cases this gives the shortest and fastest code.
- </li></ul>
- <h3>Variable References</h3>
- <b>MEM / MEMW</b>
- <pre> expression: segment
- PUSH AX
- expression: offset
- XCHG DI,AX ;pointer -> ES:DI
- POP ES
- </pre>
- Use ABSOLUTE for variables with a constant address.
- <h3>WITH Indexing</h3>
- In a WITH block all variable names must be searched first in the scopes of the
- active records and then in the regular symbol table. This can take quite some
- time. If the base offset is not known at compilation time (WITH rec[var] DO),
- a WITH pointer must be calculated and stored on the stack, otherwise this is
- done at compilation time.
- <h3>Array Indexing, String Indexing</h3>
- If necessary ES and DI must be saved before evaluation. Different code is
- produced depending on the index.
- <p>Constant index: The index is checked at compilation time, multiplied by the
- element size and added to the base offset, no code is emitted.
- </p><p>Variable index with range checking:
- </p><pre> SUB AX,#lower_bound (max be DEC AX / nothing)
- MOV CX,#upper_bound+1
- CALL xindchk ;check index
- </pre>
- Variable index without range checking: The subtraction of the lower bound can
- be omitted, it is multiplied by the element size and then subtracted from the
- base offset.
- <p>The index is multiplied by the element size. This is optimized for some
- important element sizes:
- </p><pre> no code ;size = 1
- SHL AX,1 ;size = 2
- SHL AX,1 ;size = 4
- SHL AX,1
- SHL AX,1 ;size = 6
- MOV CX,AX
- SHL AX,1
- ADD AX,CX
- MOV CX,#size ;other element sizes
- MUL CX
- </pre>
- The index is then stored in DI:
- <pre> XCHG DI,AX
- </pre>
- or added to the existing index:
- <pre> ADD DI,AX
- </pre>
- <h3>Record Indexing</h3>
- This is very simple: The offset of the record variable is added to the memory
- offset of the variable.
- <h3>Pointer indexing</h3>
-
- Pointers are loaded with LES DI,pointer_var.
- <h3>Use of Addressing Modes</h3>
- The procedure <code>einstr</code> emits a command using the correct
- addressing mode. If necessary a segment prefix (CS: or ES:) is inserted.
- not indexed, not on stack:
- <pre> MOV AX,var
- </pre>
- indexed:
- <pre> MOV AX,[DI] ;no offset
- MOV AX,[DI]offs8 ;short offset (-128..127)
- MOV AX,[DI]offs16 ;long offset (0..65535)
- </pre>
- stack, local variables:
- <pre> MOV AX,[BP]offs ;not indexed
- MOV AX,[BP+DI]offs ;indexed
- </pre>
- stack, callers local variables:
- <pre> MOV BX,[BP]-lev*2 ;read display pointer
- SS: ;indexed by BX - need prefix
- MOV AX,[BX]offs ;not indexed
- MOV AX,[BX+DI]offs ;or indexed
- </pre>
- <h3>Calculate Pointer to Variable</h3>
- <pre> indexing
- </pre>
- The offset is read into DI:
- <pre> MOV DI,#offset ;not indexed
- ADD DI,#offset ;indexed: short or long offset
- LEA DI,[BP]offs ;stack: load effective address
- </pre>
- The segment is handed over on the stack:
- <pre> PUSH CS/DS/ES/SS
- </pre>
- <h3>Read Variable</h3>
- Scalar:
- <pre> MOV AX,var ;integer, not indexed, not on stack
- MOVB AL,var ;byte, not indexed, not on stack
- MOV AX,.. ;other integer (emitted by einstr)
- MOVB AL,.. ;other byte
- </pre>
- For byte variables XOR AH,AH is inserted - TURBO always uses integer variables
- internally.
- <p>Pointer:
- </p><pre>
- LES AX,ptr_var ;AX = offset
- MOV DX,ES ;DX = segment
- </pre>
- Real:
- <pre>
- set pointer to variable
- CALL xldreal
- </pre>
- Set:
- <pre>
- set pointer to variable
- MOV CX,#set_crunch
- CALL xldset
- </pre>
- String:
- <pre> set pointer to variable
- CALL strload
- </pre>
- <h3>Store Variable</h3>
- Scalar: If R+ is set, range checking is done:
- <pre> MOV CX,#lower_bound
- MOV DX,#upper_bound
- CALL xrngchk
- </pre>
- If the variable is neither indexed nor on the stack, there's a short form
- again:
- <pre> MOV var,AX ;integer
- MOVB var,AL ;byte
- </pre>
- Otherwise the correct MOV is emitted by <code>einstr</code>.
- <p>Pointer:
- </p><pre> MOV dest,AX ;offset
- MOV dest+2,DX ;segment
- </pre>
- Real:
- <pre> set pointer to variable
- CALL xstoreal
- </pre>
- String:
- <pre> set pointer to variable
- MOV CL,#max_length
- CALL strstore
- </pre>
- Set:
- <pre> set pointer to variable
- MOV CX,#set_crunch
- CALL setsto
- </pre>
- <h2>Symbol Table</h2>
- The symbol table manager has to insert and search symbols of any type. The
- difficulty is that definitions may be of any complexity and names may have any
- length. The structure implemented in TURBO closely represents the definition
- and reference sequence, making it easy to "navigate" through complex types.
- <p>Symbols are searched beginning with the most recent definition. For new
- definitions, the current block (limited by the "fence" set at the beginning of
- a procedure definition) and the keyword table are searched for duplicate
- definitions. Thus it is possible to override old definitions. At the end of a
- procedure local variables may be removed from the symbol table. This reduces
- memory usage and search time.
- </p><h3>Symbol Table Entry Structure</h3>
- The symbol table "symtab" is stored in the stack segment (the actual stack
- doesn't need much space) and grows down. The entries have this basic structure:
- <pre> off :next entry
- --
- off-2:tag word = entry type
- off-4:name length
- off-5:name
- off-.:entry
- 0:offset to next entry
- </pre>
- The symbol table is always searched backwards, looking at the most recent
- entries first. A linear search is used. Sample symbol table entries can be
- found in the compiler tables (starting from 9277).
- <h3>tag = 0100: Label</h3>
- <pre> - 1:procedure nesting (to prevent jumps into or out of procedures)
- - 2:0=ok, FF=not yet defined
- - 4:offset
- </pre>
- <h3>tag = 0200: Constant</h3>
- <pre> - 1:type of constant
- - 3:constant - strings are stored backwards
- </pre>
- Structured constants are initialized variables stored in the code segment and
- are entered as such in the symbol table.
- <h3>tag = 0300: Type</h3>
- <pre> - 2:pointer to type definition
- </pre>
- <h3>tag = 0400: Variable</h3>
- For subvariables of a record the low byte of the tag word is the number of the
- record definition this entry belongs to.
- <pre> - 2:pointer to type definition
- - 4:offset
- - 5:0=normal, FF=indirect (VAR)
- - 6:segment
- FF = DS
- FE = CS
- FD = ES (via pointer)
- .. = SS, the number corresponds to the procedure nesting level
- </pre>
- For ABSOLUTE variables of the form $B800:0 a pointer is stored in the code
- segment, the symbol table entry id CS indirect.
- <h3>tag = 0500: Procedure</h3>
- <h3>tag = 0600: Function</h3>
- <pre> - 2:function only - pointer to result type
- - 4:function only - offset on stack
- - 5:function only - 0 = normal, FF = indirect
- - 6:function only - segment: SS
- - 8:code offset of procedure / function
- - A:position in overlay file / of forward jump
- - C:0=ok, FF=forward definition
- - E:length of overlay procedure
- -10:number of parameter lists
- - 2:type pointer |may be repeated
- - 3:0=normal, FF=VAR parameter |
- - 4:number of parameters of this type |
- - 5:names |
- </pre>
- The parameters are also listed as local variables.
- <h3>Structure of Subentries</h3>
- The normal entries aren't sufficient for the description of complex types.
- Unnamed subentries are used for this. For complex definitions this gives a
- tree structure. ARRAY[1..15] OF INTEGER is an array having the subrange 1..15
- as index type and INTEGER as element type.
- <h3>tag = 0000, 0800: Subentries</h3>
- <pre> - 2:component size
- - 4:lower bound / pointer to type definition
- - 6:upper bound / pointer to index type definition
- - 7:flag / record number
- - 8:component type
- 1 = array
- 2 = record
- 3 = set
- 4 = pointer
- 5 = typed file (FILE OF)
- 6 = text file (TEXT)
- 7 = untyped file (FILE)
- 8 = string
- 9 = real
- A = integer
- B = boolean
- C = char
- . = enumeration types (numbered)
- </pre>
- The compiler often uses this register assignment:
- <pre> CL = component type
- CH = where's the result ?
- 0 = constant
- 1 = variable
- 2 = in AX / on stack
- 3 = flags set (jz/jnz)
- 4 = comparison, use branch opcode brnchop
- </pre>
- The use of the subentry fields depends on the component type:
- <p>Array
- </p><pre> - 2:component size
- - 4:type pointer
- - 6:pointer to index type
- - 8:01
- </pre>
- Record
- <pre> - 2:component size
- - 7:record number
- - 8:02
- </pre>
- Set
- <pre> - 2:component size
- - 4:pointer to index type
- - 8:03
- </pre>
- Pointer
- <pre> - 2:component size = 4
- - 6:pointer to type / type name
- - 7:0=ok, FF=not yet resolved
- - 8:04
- </pre>
- Pointers can be forward defined. In this case the type name is stored as an
- invisible entry. The type pointer is then inserted later.
- <p>String
- </p><pre> - 2:length + 1
- - 8:08
- </pre>
- Text file with nonstandard buffer size
- <pre> - 2:component size
- - 8:06
- </pre>
- Enumeration type, subrange
- <pre> - 2:size: 1 or 2 bytes
- - 4:lower bound
- - 6:upper bound
- - 8:number of enumeration type
- </pre>
- The elements of an enumeration type are stored as constants.
- <h3>Symbol Table Search</h3>
- TURBO uses a linear search to find symbols in the symbol table. This can be
- slow. A worst case program can get compilation speed down to 0.022 lines
- per second...
- <p>There is a better way: Hashing. <i>This is used in later versions of the
- compiler.</i>
- </p><h2>Error Handler</h2>
- Many compilers try to continue compilation after an error has been found. The
- difficulty about this is that this shouldn't trigger an avalanche of
- meaningless error messages.
- <p>TURBO always stops if an error is found. This can also be seen as an
- advantage: The programmer is forced to solve problems one at a time.
- </p><h2>Run-time Library</h2>
- In the run-time library all standard procedures and functions needed for the
- execution of programs are stored. TURBO does this in a rather wasteful (but
- simple) way: it always inserts all procedures. <i>Later versions of the
- compiler only include the procedures that are actually used.</i>
- <h3>Memory Map</h3>
- The segments are allocated as follows:
- <pre> --
- stack (SS) (grows down)
- --
- free
- --
- heap (grows up)
- --
- variables (DS)
- --
- code (CS)
- --
- </pre>
- <h3>Heap</h3>
- Heap storage is allocated in blocks having a size that is a multiple of 8
- bytes. The free blocks are kept track of with this structure:
- <pre> +0: pointer to next free block -> linked list
- +4: length of free block
- +8: free block
- </pre>
- Hpstrt points to the first free block. The last block in the list - the free
- space between stack and heap - is marked by a 0. If there isn't enough space
- between heap and stack an error is reported.
- <h3>Floating Point Arithmetics</h3>
- Floating point numbers are divided into two parts: The exponent gives the
- order of magnitude, the mantissa gives the accuracy needed.
- <pre> number = mantissa * 2 ^ exponent
- </pre>
- The mantissa is a binary fraction with an accuracy of 40 bits. The mantissa
- always represents a number between 0.5 and 1, i.e. it is normalized. This
- means the most significant bit is always 1. Here the sign is stored. The
- examples use decimal numbers for simplicity.
- <p>A floating point addition works as follows:
- </p><pre> 0.95 E+00
- + 0.60 E-01
- 0.95 E+00 The number with the smaller exponent is
- + 0.06 E+00 adjusted (de-normalized) to match the other.
- = 1.01 E+00 The numbers can now be added as usual.
- The result is too big, it must be normalized
- = 0.10 E+01 and rounded up.
- </pre>
- Please note that floating point additions and subtractions can cause large
- round off errors, if the exponents differ too much.
- <p>It is often claimed that BCD arithmetics cause less errors. This is not
- correct - they are not as visible. Some calculators actually do cosmetic
- rounding, if the result is near to an integer number. The best way to avoid
- round off errors is to calculate money amounts in cents and not in dollars.
- BCD multiplications and divisions are slow. However, BCD does have one
- advantage: Conversions to and from ASCII are much faster. As business
- applications usually consist mainly of additions and subtractions, BCD can
- actually be faster.
- </p><p>Floating point multiplication
- </p><pre> 0.13 E+00 The exponents are added (division: subtracted)
- * 0.49 E+03 and the mantissas are multiplied.
- 0.13 E+00
- * 0.49 E+00
- * E+03
- = 0.0637 The additional digits are cut off and the
- * E+03 result is normalized and rounded up.
- = 0.64 E+02
- </pre>
- Square roots are evaluated using Newton's approximation. This is a good
- solution, but not the best: The 8087 does square roots faster than divisions !
- <p>
- Transcendental functions are evaluated using the standard polynomials found in
- math books. I have seen better algorithms to do this. Don't expect much speed
- and precision from TURBO transcendentals.
- </p><h2>Bugs</h2>
- Thanks to the relative simplicity of the algorithms used TURBO Pascal is
- almost bug-free. Well, almost.
- <h3>Set as Procedure Parameter</h3>
- If the last element is in the range 248..255 and the first above 7, this
- doesn't work. Redefine the set or pass it as a VAR parameter.
- <h3>SizeOf</h3>
- Sometimes a redundant load is emitted.
- <h3>UpCase</h3>
- The argument type is not checked. Try UpCase(15).
- <h3>WHILE</h3>
- DO can be omitted.
- <h2>Compiler Speed</h2>
- TURBO may be faster than most other compilers, but there's still a wide margin
- for improvements:
- <ul>
- <li>symbol table: use hashing</li>
- <li>include files: use larger buffer</li>
- <li>don't copy source lines into another buffer</li>
- </ul>
- The editor could be much faster. The screen is re-displayed in an "intelligent"
- way (using DelLine and InsLine). On a terminal this may be faster - with
- memory mapped video this is a nuisance. Search and replace is slow.
- <h2>Write Faster Programs Using TURBO Pascal</h2>
- Avoid the standard string functions.
- <ul>
- <li>Use ord(st[0]) instead of length(st)</li>
- <li>Use move instead of string assignments:
- <pre> move(st1,st2,max_len+1);
- </pre></li></ul>
- Write real constants with decimal point (10.0 instead of 10). This eliminates
- conversions.
- <p>GOTO statement considered harmful. So what... If a GOTO is the best way to
- express a program structure - use it ! TURBO GOTO's are still better than
- BASIC GOTO's: names can be used instead of numbers.
- </p><p>I/O can be improved if larger buffers are used (use multiples of 512 bytes
- for best results). Use BlockRead and BlockWrite. If large blocks are read or
- written the MS-DOS and TURBO overhead doesn't matter as much as for small
- blocks.
- </p></td></tr>
- <tr><td id="copy">© 2002-2021 PC Engines GmbH. All rights reserved.</td></tr>
- </tbody></table>
- <div style="all: unset;"><div style="all: unset;"></div></div></body></html>
|