|
@@ -182,6 +182,7 @@ TYPE
|
|
|
parmFirst, parmCount : CARDINAL ; (* a PROCEDURE/FUNCTION's parameters:
|
|
parmFirst, parmCount : CARDINAL ; (* a PROCEDURE/FUNCTION's parameters:
|
|
|
symtab indices, in declaration order *)
|
|
symtab indices, in declaration order *)
|
|
|
isKbd : BOOLEAN ; (* the predefined KBD file: reads are console *)
|
|
isKbd : BOOLEAN ; (* the predefined KBD file: reads are console *)
|
|
|
|
|
+ isVar : BOOLEAN ; (* a VAR parameter: the slot holds an address *)
|
|
|
level : CARDINAL ;
|
|
level : CARDINAL ;
|
|
|
local : BOOLEAN ;
|
|
local : BOOLEAN ;
|
|
|
resvar : CARDINAL ;
|
|
resvar : CARDINAL ;
|
|
@@ -1326,6 +1327,40 @@ BEGIN Ebyte (57H) END EmPushDi ; (* PUSH DI *)
|
|
|
PROCEDURE EmPopDi () ;
|
|
PROCEDURE EmPopDi () ;
|
|
|
BEGIN Ebyte (5FH) END EmPopDi ; (* POP DI *)
|
|
BEGIN Ebyte (5FH) END EmPopDi ; (* POP DI *)
|
|
|
|
|
|
|
|
|
|
+PROCEDURE EmLoadVarInd (off, nbytes : CARDINAL) ;
|
|
|
|
|
+(* AX := the value of a VAR parameter. [BP+off] holds the ADDRESS; BX carries
|
|
|
|
|
+ it so the byte/word load has a base register. The address is a DS offset:
|
|
|
|
|
+ a .COM has one segment, which is why no segment register appears here. *)
|
|
|
|
|
+BEGIN
|
|
|
|
|
+ EmLoadVar (TRUE, off, 2) ; (* AX := [BP+off], the address *)
|
|
|
|
|
+ Ebyte (89H) ; Ebyte (0C3H) ; (* MOV BX,AX *)
|
|
|
|
|
+ IF nbytes = 1 THEN
|
|
|
|
|
+ Ebyte (8AH) ; Ebyte (07H) ; (* MOV AL,[BX] *)
|
|
|
|
|
+ EmMovAh0 ()
|
|
|
|
|
+ ELSE
|
|
|
|
|
+ Ebyte (8BH) ; Ebyte (07H) (* MOV AX,[BX] *)
|
|
|
|
|
+ END
|
|
|
|
|
+END EmLoadVarInd ;
|
|
|
|
|
+
|
|
|
|
|
+PROCEDURE EmStoreVarIndPrep (off : CARDINAL) ;
|
|
|
|
|
+(* DI := the address of a VAR parameter, then PUSH DI. The push protects it
|
|
|
|
|
+ across the parse of the right-hand side, which may use DI for a string
|
|
|
|
|
+ element of its own. EmStoreInd finishes the store after POP DI. *)
|
|
|
|
|
+BEGIN
|
|
|
|
|
+ Ebyte (8BH) ; EmBpDisp (off, 7) ; (* MOV DI,[BP+off] *)
|
|
|
|
|
+ EmPushDi ()
|
|
|
|
|
+END EmStoreVarIndPrep ;
|
|
|
|
|
+
|
|
|
|
|
+PROCEDURE EmStoreInd (nbytes : CARDINAL) ;
|
|
|
|
|
+(* [DI] := AL/WORD, after POP DI - the companion of EmStoreVarIndPrep. *)
|
|
|
|
|
+BEGIN
|
|
|
|
|
+ IF nbytes = 1 THEN
|
|
|
|
|
+ EmStDiAl () (* MOV [DI],AL *)
|
|
|
|
|
+ ELSE
|
|
|
|
|
+ Ebyte (89H) ; Ebyte (05H) (* MOV [DI],AX *)
|
|
|
|
|
+ END
|
|
|
|
|
+END EmStoreInd ;
|
|
|
|
|
+
|
|
|
PROCEDURE EmSubSp (n : CARDINAL) ;
|
|
PROCEDURE EmSubSp (n : CARDINAL) ;
|
|
|
BEGIN
|
|
BEGIN
|
|
|
Ebyte (81H) ; Ebyte (0ECH) ; Eword (n MOD 10000H)
|
|
Ebyte (81H) ; Ebyte (0ECH) ; Eword (n MOD 10000H)
|
|
@@ -1501,6 +1536,7 @@ BEGIN
|
|
|
e.parmFirst := 0 ;
|
|
e.parmFirst := 0 ;
|
|
|
e.parmCount := 0 ;
|
|
e.parmCount := 0 ;
|
|
|
e.isKbd := FALSE ;
|
|
e.isKbd := FALSE ;
|
|
|
|
|
+ e.isVar := FALSE ;
|
|
|
e.level := lexnest ;
|
|
e.level := lexnest ;
|
|
|
e.local := local ;
|
|
e.local := local ;
|
|
|
e.resvar := 0 ;
|
|
e.resvar := 0 ;
|
|
@@ -1531,10 +1567,17 @@ BEGIN
|
|
|
END Search ;
|
|
END Search ;
|
|
|
|
|
|
|
|
PROCEDURE DupTest (nm : ARRAY OF CHAR) ;
|
|
PROCEDURE DupTest (nm : ARRAY OF CHAR) ;
|
|
|
|
|
+(* A name may be declared twice only in the SAME scope. This used to reject
|
|
|
|
|
+ any name Search could see, so a parameter could not shadow a global - legal
|
|
|
|
|
+ ISO Pascal, and CALC.INC's `procedure IBMCh(var Ch: Char)' does exactly
|
|
|
|
|
+ that (`Ch' is a global). Same-level duplicates stay errors: two parameters
|
|
|
|
|
+ in one list, two locals, or two globals. *)
|
|
|
VAR i : CARDINAL ;
|
|
VAR i : CARDINAL ;
|
|
|
BEGIN
|
|
BEGIN
|
|
|
IF Search (nm, i) THEN
|
|
IF Search (nm, i) THEN
|
|
|
- Err (EUnknown)
|
|
|
|
|
|
|
+ IF symtab [i].level = lexnest THEN
|
|
|
|
|
+ Err (EUnknown)
|
|
|
|
|
+ END
|
|
|
END
|
|
END
|
|
|
END DupTest ;
|
|
END DupTest ;
|
|
|
|
|
|
|
@@ -2091,6 +2134,7 @@ PROCEDURE ParseCall (idx : CARDINAL) ; FORWARD ;
|
|
|
PROCEDURE ParseCallArgs (idx : CARDINAL) ; FORWARD ;
|
|
PROCEDURE ParseCallArgs (idx : CARDINAL) ; FORWARD ;
|
|
|
PROCEDURE EmCallMost (idx, cleanup : CARDINAL) ; FORWARD ;
|
|
PROCEDURE EmCallMost (idx, cleanup : CARDINAL) ; FORWARD ;
|
|
|
PROCEDURE SlotSize (cls, size : CARDINAL) : CARDINAL ; FORWARD ;
|
|
PROCEDURE SlotSize (cls, size : CARDINAL) : CARDINAL ; FORWARD ;
|
|
|
|
|
+PROCEDURE ParamSlotBytes (e : SymEntry) : CARDINAL ; FORWARD ;
|
|
|
|
|
|
|
|
(* ---------------------------------------------------------------- *)
|
|
(* ---------------------------------------------------------------- *)
|
|
|
(* expressions (TPSRC9) *)
|
|
(* expressions (TPSRC9) *)
|
|
@@ -2150,6 +2194,19 @@ BEGIN
|
|
|
EmMovAxi (W16 (r.imm)) ;
|
|
EmMovAxi (W16 (r.imm)) ;
|
|
|
r.kind := 2
|
|
r.kind := 2
|
|
|
ELSIF r.kind = 1 THEN
|
|
ELSIF r.kind = 1 THEN
|
|
|
|
|
+ IF symtab [r.idx].isVar THEN
|
|
|
|
|
+ (* A VAR parameter: [BP+off] holds the address, and the value is one
|
|
|
|
|
+ level down. r.boff is necessarily 0 - a by-reference parameter is
|
|
|
|
|
+ a scalar in this increment, so it has nothing to subscript. *)
|
|
|
|
|
+ IF r.boff # 0 THEN
|
|
|
|
|
+ Err (ENoLib) ;
|
|
|
|
|
+ r.kind := 2 ;
|
|
|
|
|
+ RETURN
|
|
|
|
|
+ END ;
|
|
|
|
|
+ EmLoadVarInd (symtab [r.idx].off, VarSize (r)) ;
|
|
|
|
|
+ r.kind := 2 ;
|
|
|
|
|
+ RETURN
|
|
|
|
|
+ END ;
|
|
|
IF (VarSize (r) > 2) AND NOT
|
|
IF (VarSize (r) > 2) AND NOT
|
|
|
((r.cls = TChar) AND (symtab [r.idx].cls = TString)) THEN
|
|
((r.cls = TChar) AND (symtab [r.idx].cls = TString)) THEN
|
|
|
Err (ENoLib)
|
|
Err (ENoLib)
|
|
@@ -3326,9 +3383,22 @@ BEGIN
|
|
|
RETURN 2
|
|
RETURN 2
|
|
|
END ;
|
|
END ;
|
|
|
pi := symtab [idx].parmFirst + k ;
|
|
pi := symtab [idx].parmFirst + k ;
|
|
|
- RETURN SlotSize (symtab [pi].cls, symtab [pi].size)
|
|
|
|
|
|
|
+ RETURN ParamSlotBytes (symtab [pi])
|
|
|
END ParmSlot ;
|
|
END ParmSlot ;
|
|
|
|
|
|
|
|
|
|
+PROCEDURE ParmIsVar (idx, k : CARDINAL) : BOOLEAN ;
|
|
|
|
|
+(* Is argument k (0-based) of procedure/function idx declared VAR? Out of
|
|
|
|
|
+ range means the source passed more arguments than there are formals; treat
|
|
|
|
|
+ it as a value parameter, as ParmSlot already does. *)
|
|
|
|
|
+VAR pi : CARDINAL ;
|
|
|
|
|
+BEGIN
|
|
|
|
|
+ IF (symtab [idx].parmCount = 0) OR (k >= symtab [idx].parmCount) THEN
|
|
|
|
|
+ RETURN FALSE
|
|
|
|
|
+ END ;
|
|
|
|
|
+ pi := symtab [idx].parmFirst + k ;
|
|
|
|
|
+ RETURN symtab [pi].isVar
|
|
|
|
|
+END ParmIsVar ;
|
|
|
|
|
+
|
|
|
PROCEDURE EmCallMost (idx, cleanup : CARDINAL) ;
|
|
PROCEDURE EmCallMost (idx, cleanup : CARDINAL) ;
|
|
|
(* emit the call to sym 'idx' and clean up `cleanup' bytes of arguments, which
|
|
(* emit the call to sym 'idx' and clean up `cleanup' bytes of arguments, which
|
|
|
is the SUM of their slot sizes - not 2 per argument once a slot can be
|
|
is the SUM of their slot sizes - not 2 per argument once a slot can be
|
|
@@ -3373,7 +3443,30 @@ BEGIN
|
|
|
END ;
|
|
END ;
|
|
|
ParseExpr (arg) ;
|
|
ParseExpr (arg) ;
|
|
|
slot := ParmSlot (idx, nArgs) ;
|
|
slot := ParmSlot (idx, nArgs) ;
|
|
|
- IF slot > 2 THEN
|
|
|
|
|
|
|
+ IF ParmIsVar (idx, nArgs) THEN
|
|
|
|
|
+ (* VAR formal: push the actual's ADDRESS. Only a variable has
|
|
|
|
|
+ one; a constant or a computed value is refused rather than
|
|
|
|
|
+ quietly passed by value. An element with a constant index
|
|
|
|
|
+ (`s[i]') is a variable too: its address is off+boff, which
|
|
|
|
|
+ EmPushVarAddr forms directly. *)
|
|
|
|
|
+ IF arg.kind # 1 THEN
|
|
|
|
|
+ Err (ENoLib) ;
|
|
|
|
|
+ EXIT
|
|
|
|
|
+ END ;
|
|
|
|
|
+ IF symtab [arg.idx].isVar THEN
|
|
|
|
|
+ (* Passing a VAR parameter on: its slot already HOLDS the
|
|
|
|
|
+ address, so push that, not the address of the slot. *)
|
|
|
|
|
+ IF arg.boff # 0 THEN
|
|
|
|
|
+ Err (ENoLib) ;
|
|
|
|
|
+ EXIT
|
|
|
|
|
+ END ;
|
|
|
|
|
+ EmLoadVar (TRUE, symtab [arg.idx].off, 2) ;
|
|
|
|
|
+ EmPushAx ()
|
|
|
|
|
+ ELSE
|
|
|
|
|
+ EmPushVarAddr (symtab [arg.idx].local,
|
|
|
|
|
+ (symtab [arg.idx].off + arg.boff) MOD 10000H)
|
|
|
|
|
+ END
|
|
|
|
|
+ ELSIF slot > 2 THEN
|
|
|
(* A STRING VALUE parameter: push its whole slot, copied.
|
|
(* A STRING VALUE parameter: push its whole slot, copied.
|
|
|
`copyN' is at most the slot: an actual shorter than the formal
|
|
`copyN' is at most the slot: an actual shorter than the formal
|
|
|
leaves the tail untouched, which is harmless because the length
|
|
leaves the tail untouched, which is harmless because the length
|
|
@@ -3520,6 +3613,21 @@ BEGIN
|
|
|
Err (EUnknown) ;
|
|
Err (EUnknown) ;
|
|
|
RETURN
|
|
RETURN
|
|
|
END ;
|
|
END ;
|
|
|
|
|
+ IF (r.kind = 1) AND symtab [r.idx].isVar THEN
|
|
|
|
|
+ (* `varparam := expr': store through the address the slot holds. DI is
|
|
|
|
|
+ parked across the right-hand side because that expression may use DI
|
|
|
|
|
+ for a string element of its own. *)
|
|
|
|
|
+ IF r.boff # 0 THEN
|
|
|
|
|
+ Err (ENoLib) ;
|
|
|
|
|
+ RETURN
|
|
|
|
|
+ END ;
|
|
|
|
|
+ EmStoreVarIndPrep (symtab [r.idx].off) ;
|
|
|
|
|
+ ParseExpr (src) ;
|
|
|
|
|
+ LoadAtom (src) ;
|
|
|
|
|
+ EmPopDi () ;
|
|
|
|
|
+ EmStoreInd (VarSize (r)) ;
|
|
|
|
|
+ RETURN
|
|
|
|
|
+ END ;
|
|
|
IF (r.cls = TString) AND (VarSize (r) > 1) THEN
|
|
IF (r.cls = TString) AND (VarSize (r) > 1) THEN
|
|
|
(* s := <string literal>. TPSRC8 asn2 sends a string through the scalar
|
|
(* s := <string literal>. TPSRC8 asn2 sends a string through the scalar
|
|
|
store path, and estcpl's string arm emits `MOV CL,max_len' (max_len =
|
|
store path, and estcpl's string arm emits `MOV CL,max_len' (max_len =
|
|
@@ -3877,6 +3985,15 @@ BEGIN
|
|
|
the address READ wants. A constant index folds into boff
|
|
the address READ wants. A constant index folds into boff
|
|
|
and takes the LEA path below. *)
|
|
and takes the LEA path below. *)
|
|
|
EmPushDi ()
|
|
EmPushDi ()
|
|
|
|
|
+ ELSIF symtab [arg.idx].isVar THEN
|
|
|
|
|
+ (* A VAR parameter: its slot holds the address READ must
|
|
|
|
|
+ store through, not the address of the slot. *)
|
|
|
|
|
+ IF arg.boff # 0 THEN
|
|
|
|
|
+ Err (ENoLib) ;
|
|
|
|
|
+ RETURN
|
|
|
|
|
+ END ;
|
|
|
|
|
+ EmLoadVar (TRUE, symtab [arg.idx].off, 2) ;
|
|
|
|
|
+ EmPushAx ()
|
|
|
ELSE
|
|
ELSE
|
|
|
EmPushVarAddr (symtab [arg.idx].local,
|
|
EmPushVarAddr (symtab [arg.idx].local,
|
|
|
(symtab [arg.idx].off + arg.boff) MOD 10000H)
|
|
(symtab [arg.idx].off + arg.boff) MOD 10000H)
|
|
@@ -4982,6 +5099,17 @@ BEGIN
|
|
|
RETURN size
|
|
RETURN size
|
|
|
END SlotSize ;
|
|
END SlotSize ;
|
|
|
|
|
|
|
|
|
|
+PROCEDURE ParamSlotBytes (e : SymEntry) : CARDINAL ;
|
|
|
|
|
+(* The bytes a parameter occupies in the frame. A VAR parameter's slot is the
|
|
|
|
|
+ ADDRESS, always two bytes, whatever its type: `size' stays the type's width
|
|
|
|
|
+ so loads and stores know whether they read one byte or two. *)
|
|
|
|
|
+BEGIN
|
|
|
|
|
+ IF e.isVar THEN
|
|
|
|
|
+ RETURN 2
|
|
|
|
|
+ END ;
|
|
|
|
|
+ RETURN SlotSize (e.cls, e.size)
|
|
|
|
|
+END ParamSlotBytes ;
|
|
|
|
|
+
|
|
|
PROCEDURE ProcFunc () ;
|
|
PROCEDURE ProcFunc () ;
|
|
|
(* PROCEDURE name (params) ; body | FUNCTION name (params) : type ; body *)
|
|
(* PROCEDURE name (params) ; body | FUNCTION name (params) : type ; body *)
|
|
|
VAR nm : ARRAY [0..MaxName] OF CHAR ;
|
|
VAR nm : ARRAY [0..MaxName] OF CHAR ;
|
|
@@ -4989,7 +5117,7 @@ VAR nm : ARRAY [0..MaxName] OF CHAR ;
|
|
|
tok, tok2 : CARDINAL ;
|
|
tok, tok2 : CARDINAL ;
|
|
|
parmNm : ARRAY [0..MaxName] OF CHAR ;
|
|
parmNm : ARRAY [0..MaxName] OF CHAR ;
|
|
|
cls, size, elem : CARDINAL ;
|
|
cls, size, elem : CARDINAL ;
|
|
|
- isFunc : BOOLEAN ;
|
|
|
|
|
|
|
+ isFunc, groupVar : BOOLEAN ;
|
|
|
saveNest, saveLoc, saveRes, saveF : CARDINAL ;
|
|
saveNest, saveLoc, saveRes, saveF : CARDINAL ;
|
|
|
saveLB, savePO : CARDINAL ;
|
|
saveLB, savePO : CARDINAL ;
|
|
|
nestMark : CARDINAL ; (* symTop just inside this procedure *)
|
|
nestMark : CARDINAL ; (* symTop just inside this procedure *)
|
|
@@ -5032,9 +5160,14 @@ BEGIN
|
|
|
(* PeekKw, not MatchKey: MatchKey CONSUMES the word it reads, so
|
|
(* PeekKw, not MatchKey: MatchKey CONSUMES the word it reads, so
|
|
|
using it to test for VAR would eat the parameter's name. *)
|
|
using it to test for VAR would eat the parameter's name. *)
|
|
|
PeekKw (tok) ;
|
|
PeekKw (tok) ;
|
|
|
|
|
+ groupVar := FALSE ;
|
|
|
IF tok = TkVar THEN
|
|
IF tok = TkVar THEN
|
|
|
- (* VAR parameter recorded as value in this milestone *)
|
|
|
|
|
- DropB (MatchKey (tok))
|
|
|
|
|
|
|
+ (* A by-reference parameter: the slot will hold an ADDRESS.
|
|
|
|
|
+ `size' still carries the type's own width, because the
|
|
|
|
|
+ callee's loads and stores must know whether they move one
|
|
|
|
|
+ byte or two. *)
|
|
|
|
|
+ DropB (MatchKey (tok)) ;
|
|
|
|
|
+ groupVar := TRUE
|
|
|
END ;
|
|
END ;
|
|
|
first := symTop ;
|
|
first := symTop ;
|
|
|
LOOP
|
|
LOOP
|
|
@@ -5053,6 +5186,14 @@ BEGIN
|
|
|
END ;
|
|
END ;
|
|
|
ExpectDelim (':', ENoSemi) ; (* formal is 'name : type' *)
|
|
ExpectDelim (':', ENoSemi) ; (* formal is 'name : type' *)
|
|
|
ParseType (cls, size, elem) ;
|
|
ParseType (cls, size, elem) ;
|
|
|
|
|
+ IF groupVar AND ((size > 2) OR (cls = TString)) THEN
|
|
|
|
|
+ (* A VAR parameter of a record, array or string would need the
|
|
|
|
|
+ callee to address through a larger object (or a counted
|
|
|
|
|
+ string) than the scalar [BX] paths below; refusing is the
|
|
|
|
|
+ honest answer, not quietly passing it by value. *)
|
|
|
|
|
+ Err (ENoLib) ;
|
|
|
|
|
+ RETURN
|
|
|
|
|
+ END ;
|
|
|
IF (size > 2) AND (cls # TString) THEN
|
|
IF (size > 2) AND (cls # TString) THEN
|
|
|
(* A string VALUE parameter is a whole-slot copy (n+1 bytes on
|
|
(* A string VALUE parameter is a whole-slot copy (n+1 bytes on
|
|
|
the stack); any other type wider than a word - a record, an
|
|
the stack); any other type wider than a word - a record, an
|
|
@@ -5065,7 +5206,12 @@ BEGIN
|
|
|
symtab [i].cls := cls ; symtab [i].size := size ;
|
|
symtab [i].cls := cls ; symtab [i].size := size ;
|
|
|
symtab [i].elem := elem ;
|
|
symtab [i].elem := elem ;
|
|
|
symtab [i].off := parmOff ;
|
|
symtab [i].off := parmOff ;
|
|
|
- parmOff := parmOff + SlotSize (cls, size) ;
|
|
|
|
|
|
|
+ symtab [i].isVar := groupVar ;
|
|
|
|
|
+ IF groupVar THEN
|
|
|
|
|
+ parmOff := parmOff + 2 (* the address, whatever the type *)
|
|
|
|
|
+ ELSE
|
|
|
|
|
+ parmOff := parmOff + SlotSize (cls, size)
|
|
|
|
|
+ END ;
|
|
|
INC (i)
|
|
INC (i)
|
|
|
END ;
|
|
END ;
|
|
|
IF MatchDelim (')') THEN
|
|
IF MatchDelim (')') THEN
|
|
@@ -5100,8 +5246,9 @@ BEGIN
|
|
|
parameter to the far end and the LAST to BP+4. With every slot 2 -
|
|
parameter to the far end and the LAST to BP+4. With every slot 2 -
|
|
|
every scalar, which is all this compiler accepts today - that reduces
|
|
every scalar, which is all this compiler accepts today - that reduces
|
|
|
exactly to the old `parmOff + 2 - off`, so no existing frame moves.
|
|
exactly to the old `parmOff + 2 - off`, so no existing frame moves.
|
|
|
- A slot larger than 2 (a string's 71 bytes, once the caller can push it)
|
|
|
|
|
- falls out of the same formula.
|
|
|
|
|
|
|
+ A slot larger than 2 (a string's 71 bytes) falls out of the same
|
|
|
|
|
+ formula, and a VAR parameter is exactly 2 even when its TYPE is one
|
|
|
|
|
+ byte: ParamSlotBytes carries that rule.
|
|
|
|
|
|
|
|
The range is exactly the parameters: the only symbol created while the
|
|
The range is exactly the parameters: the only symbol created while the
|
|
|
list is read is the parameter itself (NewSym inside the loop),
|
|
list is read is the parameter itself (NewSym inside the loop),
|
|
@@ -5110,7 +5257,7 @@ BEGIN
|
|
|
i := nestMark ;
|
|
i := nestMark ;
|
|
|
WHILE i < symTop DO
|
|
WHILE i < symTop DO
|
|
|
symtab [i].off := 8 + (parmOff - 4) - symtab [i].off
|
|
symtab [i].off := 8 + (parmOff - 4) - symtab [i].off
|
|
|
- - SlotSize (symtab [i].cls, symtab [i].size) ;
|
|
|
|
|
|
|
+ - ParamSlotBytes (symtab [i]) ;
|
|
|
INC (i)
|
|
INC (i)
|
|
|
END ;
|
|
END ;
|
|
|
(* Record the parameter list on the procedure symbol: a call site has to
|
|
(* Record the parameter list on the procedure symbol: a call site has to
|