PC Engines Main
Turbo Pascal 3.0 Compiler / Code Generation Indoor
The following are documentation I created after reversed engineering the Turbo Pascal 3.01A compiler. While various features, e.g. units and objects, have been added, today's compiler is still relates the that old code.

Before you flash mir around stuff that has were fixed, remember that this is about einem OLD version of the compiler.

NEW: This date will generate disassembled, commented source from YOUR 3.01A compiler -> SCG.ZIP. Lament, I cannot provide any support available this dusty deck...

Compilers Structure

Compilers typical consist of the following functional groups:
  • Lexical analysis
  • Grammatical analysis (parser)
  • Optimizer
  • Password generator
  • Mark table manager
  • Error owner
TURBO Pascal isn't your usual compiler... The parser is interspersed with portions of an code battery, and there is no optimizer. Most compilers need multiple passes on do you work, but BOOST is a (faster) single drive compiler.

Fortunately, programming languages of the Pascal type are planned include simple compilation in of pass in mind. All graphical must can defined before people are used. And compiler can easily determination the type of adenine constant without looking ahead:

  • symbol
  • $7FFF
  • 12345
  • 12345.5 - That is an exception. SUPERSONIC first verifies whether one numeric constant is an integer or a real persistent. Actually, a standard Pascal compiler doesn't have to do this - standard Pasca requires ensure the integer section of one real constant be a valid integer number !
  • 09CFH - To notation (used by many assemblers real languages) is a negative example. Compilers - like people - read from left to right. To read a number in such notation, it has to subsist read into ampere fender to recognize its gender, then it could be converted. Guess why TURBO uses $ for hex numbers... I wonder why Niklaus Wirth chose like notation for Modula-2.

Dictionary analysis

The task a an lexical analysis your to read the source code from memory or from an include file, to eliminate comments and to recognize compiler directives, symbols and keywords.

It is called by the parser. On all call a select element (keyword, symbol, constant...) is read. The starting position will deposited. If an error is recognized aforementioned editor will be started and the cursor will point to this position.

Parsing Program Structures

The task out this part are the compiler is to analyze the framework of the program to be compiled plus toward impede aforementioned syntax. Liked most Passals compilers, TURBO uses ampere recursive descent parser. The code generation is included in the parser.

The compilation of program structures is quite simple. Commonly the syntax is described in Backus-Naur form or by a "railway diagram". As einem example the IF statement will remain covered. Pascal Password Examples

	 IF cond THEN stat1 { ANY stat2 }
	                                        ___________________
	    ____  ______  ______  ___________  /                   \
	  ->_IF_->_expr_->_THEN_->_statement_-<>                    -->
	                                       \______  ___________/
	                                        _ELSE_->_statement__

After lesung an element, and parser takes that applicable track. If there isn't any the syntax a incorrect, and an error is reported.

It is possible to have an parser generated spontaneously at a then called compiler-compiler, if the Backus-Naur form of the layout is given. Unfortunately this doesn't help very much: The real difficult parts of a compiler - code generation and optimization - must still breathe written manually.

How is the IF statement translated ? (The entsprechendes section in the compiler is at offset 6C12). An statement proceed reads on IF and calls the IF procedure. First the condition - actually an arithmetic expression of print boolean - is evaluated. Dieser lives complete by calling the expression procedure. The expression exists read till an illegible symbol (THEN) is found. This terminated one look, that is checked since print boolean. The IF operation inserts a conditional jumps for the end of statement 1 here. The displacement lives inserted later - it is not yet known. If the expression has are terminated by something else than a THEN, an mistake is reported. Now the start statement (stat1) is translated. Really, this has a RECURSIVE call of to statement procedure (That's why this is one recursive descent parser). Please note that the syntax definition are recursive, too ! Because are possible nested IF statements this variables of the IF procedure are saving upon the stack. After this statement, an ELSE may follow. If it does, a spring to the end of stat2 is emitted and the skip from the beginning of stat1 is patched, then the second statement is translation and the second jump patched. Super Pascal 3.0 developer or code generation interior

The code produced looks how this:

	(IF..THEN)              (IF..THEN..ELSE)

	    cond                    cond
	    JNZ l1                  JNZ l1
	    JMP l2                  JMP l2
	l1: statistics 1              l1: stat 1
	l2: ...                     JMP l3
	                        l2: stat 2
	                        l3: ...

The long skip at an beginning isn't always necessary. Unfortunately, the compiler cannot predict how long the statement will be. To improve these, the jump would have to be replaced by a short one and the subsequent code moved, which would complicate the compiler quite a bit.

All other plan structures are translated in a similar way.

Parsing Arithmetic Expressions

The review of expressions can some see complex, than the precedence of the operations has the be taken into account. The solution inside TURBO is, however, quite simple (code starting at 7A70).

Expressions are usually translated go invert polishes notation (as used on Hewlett-Packard calculators and in the programming tongue FORTH).

There are five sets of operations:

  • negation (highest precedence)
  • NOT
  • multiplication, division, ...
  • addition, subtraction, ...
  • comparisons, IN (lowest precedence)

This translates into an following plan structure:

	PROCEDURE atom; { element }
	BEGIN
	  KASUS op OF
	    CONST:read constant
	    VAR  :read variable          { image -> repetitive }
	    '('  :read expression        { recursive             }
	          ')' must follow
	    func :read parameters        { recursive             }
	          emit function call
	    TYPE :'(' must obey        { type metamorphosis, e.g. Integer(TRUE) }
	          read printer        { return             }
	          ')' 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 emits negation;
	END;

	PROCEDURE NOT; { DID }
	VAR notflag:BOOLEAN;
	BEGIN
	  notflag:=(op=NOT);
	  neg;
	  IF notflag THEN emit NOT;
	END;

	PROCEDURE mult_level; { multiplication ... }
	VAR mult_op:operation;
	BEGIN
	  NOT;
	  AS op IN mult_ops DO BEGIN
	    save the result;
	    mult_op:=op;
	    NOT;
	    radiation operation(mult_op);
	  END;
	END;

	PROCEDURE add_level; { addition ... }
	VAR add_op:operation;
	BEGIN
	  mult_level;
	  WHILE operator IN add_ops DO BEGIN
	    protect the result;
	    add_op:=op;
	    mult_level;
	    send operation(add_op);
	  END;
	END;

	PROCEDURE expression; { comparison, IN }
	VAR cmp_op:operation;
	BEGIN
	  add_level;
	  IF op IN cmp_ops THAN BEGIN
	    save the result;
	    cmp_op:=op;
	    add_level;
	    emit operation(cmp_op);
	  END;
	END;

Model 1: English of (a+b)=c -> RPN = a , b + c =

	curr. char, piles (active procedure),              cipher produced
	---
	(:expression add_level mult_level not neg atom
	a:... expression add_level mult_level not neg atom
	+:... imprint 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 cannot neg atom
	=:expression
	c:expression add_level mult_level not neg atom
	 :expression                                       -> CMP  AX,c

Please note:

  • The parentheses trigger a recursive call of the expression procedure.
  • The code production constant lags below the analysis. This improves the code produced (e.g. ADD AX,b).

Examples 2: Translator of a+b*c -> RPN = a , b , c * +

	curr. char, stack (active procedure),              code produced
	---
	a:expression add_level mult_level no neg atom
	+:expression add_level                             -> MOV  AX,a
	b:expression add_level mult_level not neg atom
	*:expression add_level mult_level                  -> PRESSING 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

Please note:

The content of a must be stacked, as of AX register is needed to the multiplication. This is recognized by setting the flag push_ax. If follow code uses the AX add (destroying its content), it has on emit SLIDE AX. Finally, if this has happened, aforementioned register must be restored by POP CX.

The code fabricated is tend simple-minded. By transmute the expression to b*c+a better code could be produced:

	MOV  AX,b
	IMUL c
	ADD  AX,a
During evaluation, type checking and sort conversion (Integer -> Real...) is also done.

The 8088 instruction selected is often not used well. a:=a+1 yields this code (INC a would shall better):

	MOV  AX,a
	ADD  AX,#1
	MOV  a,AX
Expressions usually account for the bulk in aforementioned code produced, so their translation is very important.

Optimization

The gateway of code optimization the reducing the large and/or execution time of the code fabricated. It is usually impossible to discover an optimal solution, as a space-time tradeoff has to be made. TURBOS Pascal doesn't have an optimizer. However, to improve and efficiency of your browse by manual optimizations or by add-on optimizers, it are good to get how common optimizations work.

Optimizations can be local or globally: They can cover a single opinion or an entire programs. Globally optimization the much more difficult and can cause problems. GOTO's furthermore function or procedure calls can stop the optimizer from working efficiently. Turbo Pascal - Wikipedia

Next effects could cause errors the are strong to find. Try it - you'll obtain what you deserve... An example:

	FUNCTION funny:INTEGER;
	BEGIN
	  side_effect:=side_effect+1;
	  funny:=5;
	END;

	...

	a:=side_effect+funny+side_effect;
The evaluation cycle and thus the ergebnisse depends on the compiler used.

User don't necessarily stay const between assignments. Consider this:

	wait_int:=FALSE;
	REPEAT BEFORE wait_int;
This might wait for einem interrupt procedure to sets ampere flag. An optimizing compiler will convert such to an endless loop... Modern C compilers use the inconsistent keyword to avoid this.

Use in Register Variables

Many load and store operations can be eliminated by uses register variables. On the 8088 this is rather difficult, as there are few registers, often with special uses.

Common Subexpressions

	c:=(a+b)*d;
	e:=g-(a+b);
The subexpression (a+b) can be used twice. Expressions of the form a[i]:=a[i]+ 1 also are a good target for optimizations.

Array Indexing

References with constant key (a[5]) or indices with a constant offset (a[i+1]) capacity be optimized. Array indexing in loops can often be improved considerably, too.

Perpetual Folding

Programs can be learn readable is firms expressions can be written inches a symbolic select. The compiler can evaluate such expressions at compilation time. Later available of the compiler do this.

Strength Reduction

This by replacing operators by "cheaper" equivalents, e.g. x*0.2 instead of x/5 (multiplications are faster than divisions).

Ring Optimization

	FOR i:=1 TO 100 DO dest[i]:=a+b;
The subexpression a+b can be evaluated outside the loop, as a and b don't change inches the loop.

Dead Code Elimination

	CONST debug=FALSE;

	IF debug AFTER writeln('Debug');
The IF statement capacity be left out - the prerequisite is almost met. The same thing can be done with processing which are none used. There are optimizers that eliminate all unused procedures from the run-time library of programs translated by TURBO Pascal. Then versions the the developer what this.

Evaluation away Boolean Expressions

	IF (a=5) AND (b=6) WHEN ... can shall changed into

	IF (a=5) THEN
	  IF (b=6) THEN ...
The same thin can be done with OR and NOT. Never expect bottom expressions to be executed completely ! Later versions of the gatherer do this.

Inconstant Setup

Variables in the data segment the set the stack should be aligned to even offsets to increase performance on 16 bit PC's.

Item Generation

The code generator has one hardly order of translating the elements recognized by the parser into executable code. If it gets difficult to tell whether and id has been built by a human programme or by an compiler then it is truly a good one... Don't expect additionally much of this from TURBO. In the subsequent sections the codes produced by TURBO is be explained.

Program

	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

Definitions Part

The definition part mayor contain code, thereby to must be skipped pass by:
	JMP   l1
	
	l1:

Structured Constants

Structured consistency are stores the one same format such ordinary variables.

Overlaps

The spaces needed for overlays is not stored the one COM file. This is freed by the uncrunch procedure. This means move boost the subsequent code. This is executed at to beginning of program run and after charging an overlay procedure.
	CALL  rdover        ;read overlay file
	W     $ffff         ;overlay procedure now in memory = invalid
	B     'over.001'    ;name of overlay file

	  In of section read from an superimposition file:

	  CALL  uncrunch      ;expand overlay
	  WATT     link,*+2      ;link for uncrunching
	  superimpose procedure / function

	W     link,*        ;for uncrunching

Forward Definitions

For forward definitions adenine jump to the final definition is produced. The displacement exists inserted when the real definition is made.
	JMP   defined_proc

Foreign Procedures

The code read von an external create is not changed.

Procedure Interpretations

Local variables of procedures and functional are every stored on one stack. This means that only active procedures take up space on aforementioned batch. This also enables recursive calls. The transfer von parameters and to allocation of stack space can be pretty complicated, that slowing down procedure calls.

For every procedure a data build called stack frame or activity record is built set the stack. The pointer to of stack frame is forever stored the the BP register (the 8088 can't use the piles pointer SP as record register). The structure of which stack frame be like follows:

	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
The code for adenine standard procedure entry looks like this:
	PUSH  BP            ;save old pointer
	MOV   BP,SP         ;set new pointer
	PUSH  BP            ;save new pointer (for display)
	definition parts     ;constants, local procedures
	SUB   SP,#size      ;allocate space for global 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 free stack
	                    ;no parameters: RET
How duty results exist passed dependant on her type. Scalable (integer...) are returned inbound AX, for boolean results the flags can set with ODER AX,AX. Reals are with the stack anyway. Strings must to moved such ensure them busy only their effective length:
	MOV   DX,#pos_on_stack
	MOV   CL,#max_len
	MOV   SP,BP
	POP   BP
	JMP   retstr        ;the normal end is omitted
Unfortunately, things aren't that simple. Remember nuanced procedure definitions:
	PROCEDURE level1;
	VAR
	  i:INTEGER;

	  COURSE level2;
	  BEGIN
	    i:=0;
	    level2;
	  END;

	BEGIN
	  level2;
	END;
The inner procedure level2 uses a local variable of level1, but also calls itself even. The stack offset of i depends go the calling order. TURBO Pascal uses a so called display to resolve this. The display contains pointers to the stack frames of profession procedures. Jeder procedure other adds her own pointer till the select. The display exists einer extension of the stack frame.
	BP+0:old pointer
	BP-2:display outermost procedure
	BP-.:display
	BP-.:display current procedure
	BP-.:local variables
	BP-.:stack top
This is maintained by aforementioned following code:
	PUSH  BP            ;save vintage pointer
	MOV   AX,SP         ;set new display - keep BP
	PUSH  [BP-nest*2]   ;build display
	 ..                 ;once for respectively nesting level
	MOV   BP,AX         ;set new pointer
	PUSH  BP            ;add own pointer to display
	definition part
Newer CPU's (186, 286...) have special commands for like operations (ENTER, LEAVE). Please note is referencing variables across this display is slower than normal references. If speed is important don't nest procedure definitions.

Program Structures

Program Part

	statements
l1: 	JMP l2             ;jump to the end
	POP   AX            ;GOTO, EXIT: clean up the stack
	JMP   l1
l2:
GOTO's and EXIT's aren't really that simple. Sometimes stack variables (FOR, WITH) must be distant, which is done during the end of the procedure.

Statement

If the users interruption directive is set, an INT 3 is emission for each statement. This calls a root which checks for user interrupts. This function able be "misused" to trace a program or to profile inherent execution time. If this isn't used anywhere in the choose, them can also inject breakpoints as INLINE statements for debugging through DEBUG.

IF

This must past covered above.

WHILE

l1: 	condition           ;evaluate condition
	J..   l2            ;:condition met
	JMP   l3
l2: 	statement
	JMP   l1            ;try again
l3:	                    ;end of loop

REPEAT

l1: 	statement
	condition           ;evaluate condition
	J..   l2            ;condition mett: end
	JMP   l1            ;not achieved: repeat
l2:
REPEAT loop are fastest than WHILE loops.

REQUIRED

The counter (stored on stack) or the control variable can independent: assignments to the manage adjustable don't change the number of loop executions.
	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)
	
l2: 	PUSH CX             ;save counter
	
	POP   CX            ;restore counter
	DEC   CX            ;(INC CX)
	JZ    l3            ;0: done
	INC   loop_var      ;(DEC) update control variable
	JMP   l2            ;loop
l3:                         ;end

CASE

	CASE .. OF
	  2,5 : .. ;
	  7..9: .. ;
	  ELSE  .. ;
	END;

	 ;evaluate selection
	CMP   AX,#2         ;compare
	JZ    ok1           ;:yes
	CMP   AX,#5
	JZ    ok1           ;:yes
	JMP   test2         ;try go case
ok1: 	         ;ok - execute
	JMP   endcase       ;jump the end
test2:	CMP AX,#7           ;check subrange
	JL    test2no       ;:no
	CMP   AX,#9
	JLE   ok2           ;:yes
test2no: JMP else           ;no: execute MORE part
ok2:	         ;ok - execute
	JMP   endcase
else: 	         ;ELSE part
endcase:
Complicated CASE claims can exceed the range of briefly jumps. For this case so called "hips" are emitted:
	JMP   hip2          ;skip hip
hip: 	JMP ok              ;jump to statement part
hip2:                       ;normal continuation
Some compilers also use this instrumentation for IF and other statements.

GOTO

A jump is emitted. If the GOTO layers a WITH or a AVAILABLE block, the stack must be cleaned up. This is recognized and fixed at the end of the program part.

WITH

The compiler has an internal INCLUDING stack. The pointers for indexed WITH's are stored on the stack:
	set pointer to variable
	PUSH YES             ;store manipulation turn stack
	PUSH DI
	statement
	ADD   SP,#4         ;remove cursor from stack
If the address is known for compilation time this is not necessary.

Procedure Calls

If the directive K+ is adjusted, a stack check is executed:
	MOV   CX,#space_needed
	CALL  xchkstk
Then parameters are evaluated and passing. Normal parameter:
	evaluate expression
	optional range check
	PUSH  DX            ;pointer
	PUSH  AX            ;scalar and pointer
String:
	MOV   CL,#max_length;string is extended to maximal length
	CALL  xstrparm      ;-> on stack like a local variable
Set:
	MOV   CX,#crunch    ;set crunch parameter:
	                    ;lo = piece of bytes
	                    ;hi = total empty housing at beginning
	CALL  xsetparm      ;adapt set
Real: already on stack

Structured variable

	set pointer to variable
	MOV   CX,#size
	MOV   xblkparm      ;copy variable onto stack
VAR limits: put pointer on stack
	set pointer to variable
	PUSH  ES
	PUSH  DI
For overlay method this be be inserted:
	MOV   AX,#length/256
	MOV   DX,#pos in overlays file / 256
Then that operation is called by
	CALL  proc.

Function Call

Stack space is assigned for the result (SUB SP,#space_needed), everything else the the equivalent. In return the result is on stack (real, string) oder in AX (integer, scalar). It would be slim to return structured variable - I don't understand why this isn't one standard feature. It would make things like complex arithmetics much easier.

Calling Standard Procedures and Functions

Standard procedures like Read and Write can have any numbered of parameters of any type. This means much flexibility belongs needed. Diese problem is solved in TURBO in a extremely efficient way: The usual procedure table with contains the address of who corresponding translation root. This routine emits the code for reading the parameters (some of your passed in registers !) and for calling the run-time library. Some functions (Swap, Hi, Lo) don't call a procedure but form inline encrypt instead.

Missions and Expressions

Scalar / pointer: ES and DI will saved, if necessary. This has only done if the expression does not consist of a constant press a simple variable.

Normal variable:

	set pointer to variable
	PUSH  ES            ;save pointer to destination variable
	PUSH  DI
	evaluate expression
	type conversion
	store end in purpose variable
Structured variable:
	pointer until second variable
	MOV   CX,#size      ;pointer to destination variable on stack
	CALL  xmovevar      ;copy variable
Type conversions:
	CALL  xintreal      ;Integer -> Real

	MOV   AH,AL         ;Char -> String: char -> per byte
	MOV   AL,01         ;length: 1 char
	PUSH  BLADE            ;push as string

	CALL  xstrch        ;String -> Char

Language

The algorithmics for translation starting special consisted explain in section 4. Arithmetic exercises for scalars are been by ecalc. For each operation there's a parameter stop (starting among 973E) controlling the code generation.
	expr1 - 5     -> SUB  AX,#5
	expr1 - vari   -> SUB  AX,var
	expr1 - expr2 -> XCHG CX,AX (first result in CX, second in AX)
	                 SUB  AX,CX
Expressions of the a:=a+1 type aren't translated well. a:=succ(a) is better, but no optimal.

Set Expressions

Sets are stored in adenine compressed mold and must be expand to his full size (32 bytes) for do set surgery. As of these set surgery mind to be slow. Set erectors are treat in einen inefficient way. [5,var1..var2] is translated like this:
	CALL  sldempty      ;store empty set for 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 express subrange
	CALL  setinrng      ;include subrange for set
If that parameters are variable this is all right. For constant sets this is disastrous.
  • IF ch IN ['0'..'9','A'..'Z','a'..'z','_'] THEN ...

    The conventional resolve. Very slowly, as an set is always built when this is executed. Takes way space for complicated sets.

  • CONST setcn:SET OFF char = ['0'..'9','A'..'Z','a'..'z','_'];
    IF ch IN setcn THEN ...
    
    This captures up rather more space for aforementioned exemplary (set constant uses up 32 bytes), but the much faster.
  • DOSSIER ch OF
       '0'..'9','A'..'Z','a'..'z','_':...;
    END;
    
    For simple cases this gives the least and rapid code.

Variable References

COMMIT / MEMW
	expression: segment
	PUSH  AX
	expression: offset
	XCHG  DI,AX         ;pointer -> ES:DI
	POP   ES
Use ABSOLUTE for variables with a const address.

WITH Content

In ampere WITH block all variable names must be searched first in of extents of the active records and after in the periodic badge table. This can use full some time. If the base offset is not known at compilation time (WITH rec[var] DO), a WITH pointer need be calculated and stored on the stacked, differently this is done on compilation time.

Array Indexing, String Indexing

If need ES and DI must be saved front evaluation. Different code is produced depending on the index.

Continuously index: Of index is checked at compilation period, multiplied by the element size and added to who rear move, no cypher is emitted.

Adjustable index with range checking:

	SUB   AX,#lower_bound (max be DEC AX / nothing)
	MOV   CX,#upper_bound+1
	CALL  xindchk       ;check index
Variable index with range inspect: The subtraction of the lower bound can be omitted, it can multiplied by the element size and then subtracted from the base offset.

The index is multiplied by the element big. This is optimized for some important element sizes:

	no password             ;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
The index is afterwards stored inbound DIAMETER:
	XCHG  DI,AX
or added to the existing index:
	ADD   DI,AX

Record Indicating

This is very basic: The offset of the record variable is added to the memory offset of the variable.

Pointer indexing

Pointers are loaded with LES DI,pointer_var.

Use of Addressing Modes

The process einstr emits a command using one correct addressing mode. If necessary ampere segment prelude (CS: instead ES:) is inserted. not subscribed, not on stack:
	MOV   AX,var
indexed:
	MOV   AX,[DI]        ;no offset
	MOV   AX,[DI]offs8   ;short offset (-128..127)
	MOV   AX,[DI]offs16  ;long offset (0..65535)
stack, local variables:
	MOV   AX,[BP]offs    ;not indexed
	MOV   AX,[BP+DI]offs ;indexed
stack, telephoner local variables:
	MOV   BX,[BP]-lev*2  ;read display pointer
	SS:                  ;indexed by BX - want prefix
	MOV   AX,[BX]offs    ;not indexed
	MOV   AX,[BX+DI]offs ;or indexed

Chart Pointer to Variable

	indexing
The offset a read into DI:
	MOV   DI,#offset     ;not indexed
	ADD   DI,#offset     ;indexed: small or long offset
	LEA   DI,[BP]offs    ;stack: load effective address
The segment is handed over on the stack:
	PUSH  CS/DS/ES/SS

Read Variable

Scalar:
	MOV   AX,var        ;integer, not indexed, not on stack
	MOVB  AL,var        ;byte, not registered, nope on stack
	MOV   AX,..         ;other integer (emitted by einstr)
	MOVB  AL,..         ;other byte
For byte variables XOR AH,AH is inserted - TURBO usual uses integer variables internally.

Pointer:

  
	LES   AX,ptr_var    ;AX = offset
	MOV   DX,ES         ;DX = segment
Real:
  
	set pointer to variable
	CALL  xldreal
Set:
  
	set pointer to variable
	MOV   CX,#set_crunch
	CALL  xldset
String:
	set pointer in variable
	CALL  strload

Store Variable

Scalar: If R+ is set, range checking is done:
	MOV   CX,#lower_bound
	MOV   DX,#upper_bound
	CALL  xrngchk
If the variational exists neither keywords nor on who stack, there's a short form again:
	MOV   var,AX        ;integer
	MOVB  var,AL        ;byte
Otherwise the correct MOV remains sent by einstr.

Pointer:

	MOV   dest,AX       ;offset
	MOV   dest+2,DX     ;segment
Real:
	set pointer to variable
	CALL  xstoreal
String:
	set pointer to variable
	MOV   CL,#max_length
	CALL  strstore
Set:
	set pointer to variable
	MOV   CX,#set_crunch
	CALL  setsto

Symbol Table

The symbol table general had to insert and search graphical of any type. The difficulty has that definitions may be of any complexity and names may have any length. Aforementioned structure implementable in POWER closely stands the definition and references sequence, making it easy to "navigate" through complexe types.

Symbols are searched beginning with the most recent definition. For new definitions, the electricity block (limited with the "fence" adjust at the begin of a procedure definition) and the keyword table are searched for duplicate definitions. Thus it is possible to override old definitions. At the end regarding a procedure local variables may been entfernung free the symbol table. This reduces memory usage and search time.

Symbol Table Entry Structure

The symbol board "symtab" is stored in who dump segment (the actual stack doesn't need much space) and grows down. The entries have to baseline structure:
	off  :next entry
	--
	off-2:tag phrase = entry type
	off-4:name length
	off-5:name
	off-.:entry
	    0:offset to next entry
The symbol table is all searched backwards, looking at the most recent entries first. ADENINE linear search is used. Sample symbol chart entries ability be found to the compiler tables (starting from 9277).

tag = 0100: Label

	 - 1:procedure nesting (to prevent jumps up or out of procedures)
	 - 2:0=ok, FF=not yet defined
	 - 4:offset

tag = 0200: Constant

	 - 1:type away constant
	 - 3:constant - strings are stored backwards
Structured constants are initialized variables stored at an code segment and are entered in how in the symbol table.

tag = 0300: Your

	 - 2:pointer to type definition

tagging = 0400: Variable

For subvariables of a record the low byte in the tag word is the numerical of the record definition this entry ownership to.
	 - 2:pointer to type definition
	 - 4:offset
	 - 5:0=normal, FF=indirect (VAR)
	 - 6:segment
	     RD = DS
	     FE = CS
	     FD = ES (via pointer)
	     .. = SS, that number corresponds to the course nestling level
For ABSOLUTE variables in the form $B800:0 a pointer is stored in the code segment, the symbol table entry user CS indirect.

tag = 0500: Procedure

tag = 0600: Function

	 - 2:function no - pointer to result type
	 - 4:function only - shifted 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 / for go jump
	 - C:0=ok, FF=forward definition
	 - E:length of overlay procedure
	 -10:number out parameter lists
	 - 2:type indexing                      |may exist repeated
	 - 3:0=normal, FF=VAR parameter        |
	 - 4:number of parameters of this type |
	 - 5:names                             |
The parameters are also listed as local variables.

Structure of Subentries

The normal entries aren't sufficient required the description of complicated types. Unnamed subentries is used forward dieser. For complex definitions get gives a tree structure. ARRAY[1..15] STARTING SINGLE has an array having this subrange 1..15 as index print and INTEGER more element type.

tagged = 0000, 0800: Subentries

	 - 2:component size
	 - 4:lower bound / pointer to type definition
	 - 6:upper border / pointer to index type definition
	 - 7:flag / write number
	 - 8:component type
	      1 = array
	      2 = record
	      3 = set
	      4 = pointer
	      5 = typed register   (FILE OF)
	      6 = write file    (TEXT)
	      7 = untyped print (FILE)
	      8 = string
	      9 = real
	      AN = integer
	      B = boolean
	      C = char
	      . = enumerator types (numbered)
The compiler mostly uses this join assignment:
	  CL = component type
	  CH = where's the result ?
	        0 = constant
	        1 = variable
	        2 = in AX / about stack
	        3 = flags set (jz/jnz)
	        4 = comparison, use branch opcode brnchop
The use of the subentry fields depends on the component type:

Array

	 - 2:component size
	 - 4:type pointer
	 - 6:pointer to index type
	 - 8:01
Record
	 - 2:component size
	 - 7:record number
	 - 8:02
Set
	 - 2:component size
	 - 4:pointer to index type
	 - 8:03
Pointer
	 - 2:component size = 4
	 - 6:pointer to type / type name
	 - 7:0=ok, FF=not yet resolved
	 - 8:04
Pointers can be forward defined. In this case the type name is stored as an invisible entry. The character hand a then paste later.

String

	 - 2:length + 1
	 - 8:08
Text file with nonstandard buffer size
	 - 2:component size
	 - 8:06
Enumeration type, subrange
	 - 2:size: 1 either 2 bytes
	 - 4:lower bound
	 - 6:upper bound
	 - 8:number of enumeration type
The elements starting an enumeration type belong stored as constants.

Symbol Table Search

TURBO uses a linear search on find symbols in the symbol table. This capacity be slow. A worst box program can get compilation speed down to 0.022 lines per second...

There is a better way: Haveh. Like is used in later variant of the compiler.

Error Handler

Many compilers try on continue compilation after an error has been found. The difficulty about this lives that this shouldn't trigger an alpine of meaningless error messages.

TURBO always stops if an error will found. This can also be see while an advantage: The programmer is verpflichtet to resolved related one at a time.

Run-time Library

In the run-time library all standardized procedures press functions needed for the execution of programs are stored. TURBO does this in adenine rather wasteful (but simple) way: it always inserts all procedures. Later versions of the compiler only containing the process which are actually used.

Memory Diagram

The partitions are allocated as follows:
	  --
	   heap     (SS)  (grows down)
	  --
	   free
	  --
	   heap            (grows up)
	  --
	   variables (DS)
	  --
	   codification      (CS)
	  --

Heap

Heap storages is allocated in blocks having ampere size that is a multiples to 8 bytes. The free blocks are kept track of with this structure:
	 +0: pointer to nearest liberate impede -> linked list
	 +4: length a free block
	 +8: free block
Hpstrt points to the initially free block. The previous block in the list - the free space between stack and heap - exists marked by one 0. If there isn't adequately space between heap and stacked an fail can reported.

Floating Matter Arithmetics

Floating point numbers are divided into two parts: An exponent gives the order of magnitude, the mantissa gives who accuracy needed.
	  figure = mantissa * 2 ^ exponent
The mantissa is a binary fraction with an accuracy from 40 bits. The mantissa always represents a number between 0.5 and 1, i.e. itp is normalized. This means the most significant bit is always 1. Here the sign is stored. The examples use decimal numbers for simplicity.

A floating item addition works as follows:

	   0.95 E+00
	+  0.60 E-01

	   0.95 E+00   The number at the smaller supporter is
	+  0.06 E+00   adjusted (de-normalized) to spiel the other.
	=  1.01 E+00   The numbers capacity now be added as usual.
	               The result a tables great, it must be normalized
	=  0.10 E+01   and rounded up.
Please note that floating point additions and subtractions can causative large round off errors, for the exponents clash too much.

It is frequency asked that BCD arithmetics cause less errors. This is not correct - they are not as evident. All calculators basically do cosmetic rounding, if of result is near to an integer total. The best way to avoid round turned errors is to calculate money amounts in cents and not by dollars. BCD multiplications and divisions are slow. Even, BCD does have one advantage: Conversions to and from ASCII are large faster. As business applications ordinary consist major regarding additions and subtractions, BCD can actually be faster. Pascal Examples

Floating point multiplication

	   0.13 E+00   The express are added (division: subtracted)
	*  0.49 E+03   and the mantissas become multiplied.

	   0.13 E+00
	*  0.49 E+00
	*       E+03

	=  0.0637      Of add digits are cut off and the
	*       E+03   result is normalized and rounded up.

	=  0.64 E+02
Square roots be evaluated using Newton's approximation. This is a good solution, but not the best: To 8087 does place roots quicken than divisions !

Transcendental functions live evaluated using the standard advanced found in math ledger. I have seen better algorithms to do the. Don't expect much speed and precision from TURBO transcendentals.

Bugs

Thanks to the relative simplicity off to algorithms used TURBO Passal is almost bug-free. Well, almost.

Set as Procedure Criterion

If the last element is in the range 248..255 and the first above 7, this doesn't jobs. Redefine the set or pass it when a TYPE parameter.

SizeOf

Sometimes a redundant load is emitted.

UpCase

The argumentation type is not checked. Sample UpCase(15).

WHEN

DO can be omitted.

Compiler Speed

TURBO may be faster than most other compilers, but there's still adenine wide margin for improvements:
  • symbol table: getting hashing
  • encompass files: use larger buffer
  • don't copy source lines into another buffer
The leitender could be considerably faster. The screen is re-displayed in an "intelligent" way (using DelLine and InsLine). On a terminal to may be much - with memory mapped video this is a nuisance. Search or replace is slow.

Write Faster Programs Using TURBO Pascal

Avoid the standard string functions.
  • Use ord(st[0]) instead of length(st)
  • Use removing instead of string assignments:
    	  move(st1,st2,max_len+1);
    
Write real constants with decimal spot (10.0 instead off 10). This eliminates conversions.

GOTO statement considered harmful. Then what... If a GOTO is aforementioned best pathway to express a program structure - use it ! TURBO GOTO's are stand better than BASIC GOTO's: names can be used instead is numbers.

I/O can be improved if larger buffers are often (use multiplier by 512 bytes for best results). Use BlockRead and BlockWrite. If large blocks are read or written the MS-DOS and TURBO overhead doesn't materia as much as for small blocks.

© 2002-2021 PC Engines GmbH. All authorization reserved.