Tridora-CPU/pcomp/sasm.pas

2653 lines
62 KiB
ObjectPascal

(* Copyright 2021-2024 Sebastian Lederer. See the file LICENSE.md for details *)
{$MODE objfpc}
{$H600}
{$S4}
program sasm;
{$!}{$ifdef FPC}uses math,crt;{$endif}
{$R+}
type TokenType = (
PlusToken, MinusToken, AsteriskToken, SlashToken,
SemicolonToken, EOFToken, EOLToken,
NumberToken, KeywordToken, LabelToken, DirectiveToken,
StringLitToken, CharLitToken, MetaKeywordToken,
CommaToken, DotToken, ColonToken, PercentToken, TildeToken,
AndToken, OrToken, XorToken,
AtToken, UnknownToken
);
IdentString = string[120];
KeywordString = string[255];
InsString = string[24];
AddrString = string[8];
Token = record
tokenText: string[255];
tokenKind: TokenType;
end;
OperandType = (
NoOprnd,
U13WOprnd, (* unsigned 13 bit word-aligned operand e.g. LOAD and STORE *)
S13Oprnd, (* signed 13 bit operand e.g. LOADC *)
RelWOprnd, (* signed 13 bit word-aligned operand, PC-relative, e.g. BRANCH/CBRANCH *)
S10Oprnd, (* signed 10 bit operand, e.g. FPADJ *)
U4Oprnd, (* unsigned 4 bit operand, would used for modifiers but is actually unused *)
CmpOprnd, (* comparison operand, unsigned 4 bit, actually unused *)
RegOprnd, (* register id operand for LOADREG, 4 bit unsigned *)
RelU10Oprnd, (* PC-relative 10 bit unsigned, for LOADREL *)
OptOprnd (* optional 4 bit operand for ALU *)
);
EncodingEntry = record
mask: integer;
value: integer;
end;
InstructionWord = integer;
MachineWord = integer;
OutputWord = record
intvalue:integer;
end;
ModifierEntry = record
keyword: string[24];
encoding: EncodingEntry;
next: ^ModifierEntry;
end;
OpcodeData = record (* the key is stored in the tree as metadata *)
encoding: EncodingEntry;
modifiers: ^ModifierEntry;
operand: OperandType;
id: integer; (* unique id for this opcode or alias *)
end;
SymbolType = (ConstSymbol, LabelSymbol, SpecialSymbol, SkippedSymbol);
Symbol = record
value:integer;
typ:SymbolType;
aligned:boolean;
padded:boolean;
exported:boolean;
end;
SymbolRef = ^Symbol;
CPoolEntry = record
labelname: string[40];
value: KeywordString;
offset: integer;
symbolic: boolean;
sym: SymbolRef;
next: ^CPoolEntry;
prev: ^CPoolEntry;
end;
CPoolRef = ^CPoolEntry;
LabelList = record
name: IdentString;
next: ^LabelList;
prev: ^LabelList;
end;
LabelListRef = ^LabelList;
UnresolvedBranch = record
target:IdentString;
origin:integer;
maxDistance:integer;
shrinkage:integer;
labels:LabelListRef;
next:^UnresolvedBranch;
end;
UnresBranchRef = ^UnresolvedBranch;
{$I 'platform-types+.pas'} (* defines OutputFileType, InputFileType, SymFileType *)
InputFileState = record
name: string;
filevar: InputFileType;
line: integer;
end;
TreeDataType = (TDString, TDInteger, TDSymbol, TDOpcode);
Treedata = record
case typ:Treedatatype of
TDString:(stringdata:string);
TDInteger:(intdata:integer);
TDSymbol:(symboldata:Symbol);
TDOpcode:(opcodedata:OpcodeData);
end;
{$I 'treetypes.pas'}
const insSize = 2;
wordSize = 4;
wordSizeMask = 3;
MaxUShortOffset = 8191;
MaxShortOffset = 4095;
MaxShorterOffset = 511;
MaxTinyOffset = 15;
Unresolved = 2147483647; (* max integer - 1 *)
MaxIntegerDigits = 24;
wordBits = 32;
MaxIncludes = 4;
FilenameSuffix = '.s';
OutfileSuffix = '.prog';
SymFileSuffix = '.sym';
AsciifileSuffix = '.mem';
progressSteps = 511;
shortcutChar = '`';
firstShCChar = 'A';
lastShCChar = 'i';
var
curToken, nextToken, lastToken: Token;
infileOpened: boolean;
outputEnabled: boolean;
asciiOutput: boolean;
lastOpcode: TreeDataRef;
bufferedChar: char;
buffered: boolean;
infile: InputFileType;
outfile: OutputFileType;
filename: string;
outfilename: string;
editOnError, runOnSuccess: boolean;
lineno: integer;
prevFiles: array[1..MaxIncludes] of InputFileState;
includeLevel: integer;
paramPos: integer;
pc: integer;
pass:integer;
bytesCount:integer;
symbolTable: TreeRef;
opcodeTable: TreeRef;
nextOpcodeId: integer;
constantPool: CPoolRef;
cPoolCount: integer;
nextConstId: integer;
firstUnresBranch: ^UnresolvedBranch;
LOADCPId: integer;
outputPrefix: string;
includePrefix: string;
shortcuts: array[firstShCChar..lastShCChar] of OpcodeData;
procedure errorExit2(message1, message2: string); forward;
{$I 'platform+.pas'}
{$I 'treeimpl.pas'}
procedure verifyTree(node:TreeRef); forward;
procedure dumpSymbolTable; forward;
procedure cleanup;
begin
if infileOpened then
close(infile);
if outputEnabled then
close(outfile);
end;
procedure errorExit;
begin
cleanup;
(* dumpSymbolTable; *)
halt;
end;
procedure errorLine(line:integer);
begin
if curToken.tokenKind = EOLToken then
lineno := lineno - 1;
writeln('at line ',lineno, ' in ', filename);
end;
procedure errorExit2(message1, message2: string);
var errormsg:string[128];
begin
errormsg := message1 + ' ' + message2;
writeln;
writeln('Error: ', errormsg);
errorLine(lineno);
cleanup;
if editOnError then
ExecEditor(filename, lineno, errormsg)
else
halt;
end;
function descToken(kind:tokenType):string;
begin
case kind of
PlusToken, MinusToken, AsteriskToken, SlashToken, AndToken, OrToken, XorToken:
descToken := 'one of + - * / & | ^';
SemicolonToken:
descToken := '";"';
EOFToken:
descToken := 'end-of-file';
EOLToken:
descToken := 'end-of-line';
NumberToken:
descToken := 'number';
KeywordToken:
descToken := 'keyword';
LabelToken:
descToken := 'label';
DirectiveToken:
descToken := 'directive';
StringLitToken:
descToken := 'string literal';
CharLitToken:
descToken := 'char literal';
MetaKeywordToken:
descToken := 'meta directive';
CommaToken:
descToken := '","';
DotToken:
descToken := '"."';
ColonToken:
descToken := '":"';
PercentToken:
descToken := '"."';
AtToken:
descToken := '"@"'
else
descToken := '<unknown>';
end;
end;
procedure makeCPoolLabel(var labelname:IdentString);
var digits:string[16];
begin
str(nextConstId, digits);
labelname := '_CP_' + digits;
end;
procedure hexstr(value:integer;var output:string); forward;
procedure putCPoolEntry(var constant:KeywordString; offset:integer; symbolic:boolean;var labelname:IdentString);
var newEntry:CPoolRef;
begin
makeCPoolLabel(labelname);
new(newEntry);
newEntry^.labelname := labelname;
newEntry^.value := constant;
newEntry^.offset := offset;
newEntry^.symbolic := symbolic;
newEntry^.next := constantPool;
newEntry^.prev := nil;
if constantPool <> nil then
constantPool^.prev := newEntry;
constantPool := newEntry;
nextConstId := nextConstId + 1;
end;
function isNumber(var s:string):boolean;
begin
if isdigit(s[1]) then
isNumber := true
else
isNumber := s[1] in [ '-', '$', '%' ];
end;
function convertNumber(digits:KeywordString):integer; forward;
function findSymbol(var keyword:KeywordString):TreeDataRef; forward;
procedure addCPoolEntry(var constant:KeywordString; offset:integer; var labelname:IdentString);
begin
putCPoolEntry(constant, offset, not isNumber(constant), labelname);
cPoolCount := cPoolCount + 1;
end;
function getSymbolValue(var keyword:KeywordString):integer; forward;
(*
Get the address of a cpool entry for a constant value.
If the pool does not contain the constant, create
a new entry.
The constant needs to be a string because it might
be a symbol. In pass 1, the value of the symbol is not
known, so to reuse a value we need to use the symbolic name.
The offset is an optional numerical value or symbol that gets
added to the constant before it is put into the pool.
This is used by the compiler for global variables
(arrays, record fields). Can be zero.
*)
function getCPoolAddr(var constant:KeywordString;offset:integer):integer;
var labelname:IdentString;
current:CPoolRef;
found:CPoolRef;
begin
getCPoolAddr := pc;
found := nil;
current := constantPool;
while (current <> nil) and (found = nil) do
begin
if (constant = current^.value) and (offset = current^.offset) then
found := current;
current := current^.next;
end;
if found <> nil then
begin
(* value already exists in pool *)
labelname := found^.labelname;
end
else
(* value not found, add it to the pool and
set the label name for the new entry *)
addCPoolEntry(constant,offset,labelname);
(* writeln(' [P', pass, ' cpool ', constant, ' -> ', labelname, '] '); *)
getCPoolAddr := getSymbolValue(labelname);
end;
procedure printPassNo;
begin
write('P', pass, ' ');
end;
procedure printCurrentLineno;
begin
write(#13);
printPassNo;
write(filename, ' ', lineno);
ClrEol;
end;
procedure printLastLineno;
begin
printCurrentLineno;
end;
procedure beginInclude(var newname: string);
var newfile: InputFileType;
begin
if includeLevel = MaxIncludes then
errorExit2('Too many nested includes', '');
includeLevel := includeLevel + 1;
prevFiles[includeLevel].filevar := infile;
prevFiles[includeLevel].name := filename;
prevFiles[includeLevel].line := lineno;
openFileWithDefault(newfile, newname);
infile := newfile;
filename := newname;
lineno := 1;
buffered := false;
end;
procedure endInclude;
begin
if includeLevel = 0 then
errorExit2('Internal error in', 'endInclude');
close(infile);
infile := prevFiles[includeLevel].filevar;
filename := prevFiles[includeLevel].name;
lineno := prevFiles[includeLevel].line;
buffered := false;
includeLevel := includeLevel - 1;
end;
function includeIsActive:boolean;
begin
includeIsActive := includeLevel > 0;
end;
function nextChar: char;
var ch: char;
begin
if buffered then
begin
ch := bufferedChar;
buffered := false;
end
else
begin
if not eof(infile) then
begin
read(infile, ch);
end
else
begin
(* we reached end-of-file, was this
the end of an include file? *)
if includeIsActive then
begin
(* if yes, switch back to previous file *)
endInclude;
ch := ' '; (* return a space which will get skipped *)
end
else
(* no, return null character which becomes an EOFToken *)
ch := #0;
end
end;
if ch = #10 then lineno := lineno + 1;
nextChar := ch;
end;
function peekChar: char;
var tmpChar: char;
begin
if buffered then
begin
peekChar := bufferedChar;
end
else
begin
if not eof(infile) then
begin
read(infile, tmpChar);
peekChar := tmpChar;
bufferedChar := tmpChar;
buffered := true;
end
else
begin
(* at the eof of an include,
just return an extra space and let nextChar
do the work *)
if includeIsActive then
begin
peekChar := ' ';
buffered := false; (* force nextChar to do real I/O *)
end
else
peekChar := #0;
end
end
end;
procedure skipWhitespace;
var c:char;
begin
while peekChar() in [ #13, #32, #9 ] do
c := nextChar;
end;
function integerFromString(digits:KeywordString):integer;
var value,error:integer;
begin
val(digits, value, error);
if error <> 0 then
errorExit2('Invalid integer value', digits);
integerFromString := value;
end;
function convertHex(var digits:KeywordString):integer;
var i,v,len:integer;
c:char;
begin
len := length(digits);
i := 2;
convertHex := 0;
while i <= len do
begin
convertHex := convertHex shl 4;
c := digits[i];
if (c >= 'A') and (c <= 'F') then
v := ord(c) - ord('A') + 10
else
if (c >= '0') and (c <= '9') then
v := ord(c) - ord('0')
else
errorExit2('Invalid number',digits);
convertHex := convertHex + v;
i := i + 1;
end;
end;
function convertBin(var digits:KeywordString):integer;
var i,v,len:integer;
c:char;
begin
len := length(digits);
i := 2;
convertBin := 0;
while i <= len do
begin
c := digits[i];
if c <> '_' then (* ignore '_' for a syntax like 0000_0001 *)
begin
convertBin := convertBin shl 1;
if (c >= '0') and (c <= '1') then
v := ord(c) - ord('0')
else
errorExit2('Invalid number',digits);
convertBin := convertBin + v;
end;
i := i + 1;
end;
end;
function convertChar(digits:KeywordString):integer;
begin
convertChar := ord(digits[1]);
end;
function convertNumber(digits:KeywordString):integer;
var negate:boolean;
begin
negate := digits[1] = '-';
(* we need to keep the sign for decimals
because we cannot represent abs(-maxint)
as a signed 32-bit integer and integerFromString
uses val() *)
if negate then
if (digits[2] in [ '$', '%' ]) then
delete(digits,1,1);
if digits[1] = '$' then
convertNumber := convertHex(digits)
else
if digits[1] = '%' then
convertNumber := convertBin(digits)
else
begin
negate := false;
convertNumber := integerFromString(digits);
end;
if negate then
convertNumber := -convertNumber;
end;
function getCharLitValue(tokenText:string):integer;
begin
(* is is a one-character-string-literal like 'A' ? *)
if length(tokenText) = 1 then
getCharLitValue := ord(tokenText[1])
else
errorExit2('Cannot use string as char here', tokenText);
end;
(* scan for an integer number. the first digit is already in curChar.
digits are written to keyword. *)
procedure getDigits(curChar: char; var keyword: KeywordString);
begin
keyword := keyword + curChar;
while peekChar in [ '0'..'9' ] do
begin
keyword := keyword + nextChar;
end;
end;
procedure hexstr(value:integer;var output:string);
var i:integer;
nibble:integer;
c:char;
begin
output := '00000000';
for i := 8 downto 1 do
begin
nibble := value and $F;
if nibble > 9 then
c := chr( ord('A') + nibble - 10)
else
c := chr( ord('0') + nibble);
output[i] := c;
value := value shr 4;
if value = 0 then break;
end;
end;
(* Scan for an integer number in hexadecimal format.
The hex marker '$' is already in curChar.
Digits are written to keyword. *)
procedure getHexDigits(curChar: char; var keyword: KeywordString);
begin
keyword := keyword + curChar;
while peekChar in [ '0'..'9', 'A'..'F' ] do
begin
keyword := keyword + nextChar;
end;
end;
procedure getToken(var tokenReturn:Token;stringTokens:boolean);
var curChar,pkChar: char;
keyword: KeywordString;
startLine: string[12];
function isKeywordChar(ch:char):boolean;
begin
isKeywordChar := (ch >= 'A') and (ch <= 'Z') or
(ch >= 'a') and (ch <= 'z') or
(ch >= '0') and (ch <= '9') or
(ch = '_') or (ch = '.');
end;
function isAlpha(ch:char):boolean;
begin
isAlpha := ((ch >= 'A') and (ch <= 'Z')) or
((ch >= 'a') and (ch <= 'z'));
end;
function isKeywordStart(ch:char):boolean;
begin
isKeywordStart := ((ch >= 'A') and (ch <= 'Z')) or
((ch >= 'a') and (ch <= 'z')) or
(ch = '_');
end;
begin
curChar := nextChar;
tokenReturn.tokenText := curChar;
if curChar = shortcutChar then (* two character instruction shortcut *)
begin
keyword := curChar + nextChar;
(* shortcuts can have modifiers *)
while isKeywordChar(peekChar) do
begin
curChar := Upcase(nextChar);
if (length(keyword) < 80) and (curChar <> #0) then keyword := keyword + curChar;
end;
tokenReturn.tokenKind := KeywordToken;
tokenReturn.tokenText := keyword;
end
else
if curChar = #0 then
tokenReturn.tokenKind := EOFToken
else
if curChar = #10 then
tokenReturn.tokenKind := EOLToken
else
if curChar = '+' then
tokenReturn.tokenKind := PlusToken
else
if curChar = '-' then
tokenReturn.tokenKind := MinusToken
else
if curChar = '*' then
tokenReturn.tokenKind := AsteriskToken
else
if curChar = '/' then
tokenReturn.tokenKind := SlashToken
else
if curChar = '~' then
tokenReturn.tokenKind := TildeToken
else
if curChar = '@' then
tokenReturn.tokenKind := AtToken
else
if curChar = ',' then
tokenReturn.tokenKind := CommaToken
else
if curChar = '&' then
tokenReturn.tokenKind := AndToken
else
if curChar = '|' then
tokenReturn.tokenKind := OrToken
else
if curChar = '^' then
tokenReturn.tokenKind := XorToken
else
if curChar = '.' then
begin
pkChar := peekChar;
if isAlpha(pkChar) then
begin
keyword := Upcase(curChar);
while isKeywordChar(peekChar) do
begin
curChar := Upcase(nextChar);
if (length(keyword) < 80) and (curChar <> #0) then keyword := keyword + curChar;
end;
tokenReturn.tokenText := keyword;
tokenReturn.tokenKind := DirectiveToken;
end
else
tokenReturn.tokenKind := DotToken;
end
else
if curChar = '%' then
(* percent sign can be the start of a binary number or
an include directive *)
begin
pkChar := peekChar;
if pkChar in ['A'..'Z', 'a'..'z' ] then (* is it a meta directive? *)
begin
keyword := Upcase(curChar);
while peekChar in ['A'..'Z', 'a'..'z', '0'..'9', '_'] do
begin
curChar := Upcase(nextChar);
if (length(keyword) < 80) and (curChar <> #0) then keyword := keyword + curChar;
end;
tokenReturn.tokenText := keyword;
tokenReturn.tokenKind := MetaKeywordToken;
end
else
if pkChar in ['0'..'1' ] then (* is it a binary number? *)
begin
keyword := curChar;
while peekChar in ['0','1','_' ] do
begin
curChar := nextChar;
if (length(keyword) < 80) and (curChar <> #0) then keyword := keyword + curChar;
end;
tokenReturn.tokenText := keyword;
tokenReturn.tokenKind := NumberToken;
end
else
tokenReturn.tokenKind := PercentToken; (* this is most likely unusable *)
end
else
if curChar = ';' then
tokenReturn.tokenKind := SemicolonToken
else
if isKeywordStart(curChar) then
begin
keyword := Upcase(curChar);
while isKeywordChar(peekChar) do
begin
curChar := Upcase(nextChar);
if (length(keyword) < 80) and (curChar <> #0) then keyword := keyword + curChar;
end;
tokenReturn.tokenText := keyword;
if peekChar = ':' then
begin
tokenReturn.tokenKind := LabelToken;
curChar := nextChar;
end
else
tokenReturn.tokenKind := KeywordToken;
end
else
if isdigit(curChar) then
begin
keyword := '';
getDigits(curChar, keyword);
tokenReturn.tokenText := keyword;
tokenReturn.tokenKind := NumberToken;
end
else
if curChar = '$' then
begin
keyword := '';
getHexDigits(curChar, keyword);
tokenReturn.tokenText := keyword;
tokenReturn.tokenKind := NumberToken;
end
else
if (curChar = '''') and stringTokens then
begin
keyword := nextChar;
curChar := nextChar;
tokenReturn.tokenKind := CharLitToken;
tokenReturn.tokenText := keyword;
if curChar <> '''' then
errorExit2('Invalid character literal, missing ''', keyword);
end
else
if (curChar = '"') and stringTokens then
begin
str(lineno, startLine);
keyword := '';
curChar := nextChar;
(* add characters as long as the current char is not '
(or if it is a double ') and not EOF *)
while (not ((curChar = '"') and (peekChar <> '"'))) and (curChar <> #0 ) do
begin
if (curChar = '"') and (peekChar = '"') then
begin
keyword := keyword + curChar;
curChar := nextChar;
end
else
keyword := keyword + curChar;
curChar := nextChar;
end;
if curChar = #0 then
errorExit2('Unterminated string constant starting at line', startLine);
tokenReturn.tokenText := keyword;
(* string literals with a length of 1 are char literals
which may be converted into string constants later *)
if length(keyword) = 1 then
tokenReturn.tokenKind := CharLitToken
else
tokenReturn.tokenKind := StringLitToken;
end
else
tokenReturn.tokenKind := UnknownToken;
end;
(* check for (and do not consume) a specific token, returns true on match *)
function checkToken(kind: TokenType): boolean;
begin
checkToken := curToken.tokenKind = kind;
end;
(* move to next token without any processing.
sets curToken global variable. *)
procedure skipToNextToken;
begin
getToken(nextToken, true);
curToken := nextToken;
end;
(* read the next token into the global variable curToken.
skips whitespace and comments.
*)
procedure readNextToken;
var c:char;
begin
skipWhitespace;
lastToken := curToken;
getToken(nextToken, true);
curToken := nextToken;
if curToken.tokenKind = SemicolonToken then
begin
repeat
c := nextChar;
until c = #10;
curToken.tokenKind := EOLToken;
end;
end;
(* match (and consume) a token or exit with error *)
procedure matchToken(kind: TokenType);
begin
if curToken.tokenKind <> kind then
errorExit2('Expected ' + descToken(kind) + ', found', curToken.tokenText);
readNextToken;
end;
(* match (and consume) a token, returning true, or if no match, do not
consume token and return false *)
function matchTokenOrNot(wantedToken: TokenType): boolean;
begin
if checkToken(wantedToken) then
begin
matchTokenOrNot := true;
readNextToken;
end
else
matchTokenOrNot := false;
end;
procedure emitInstructionWord(value:InstructionWord); forward;
procedure fixupLabel(oldPc, newPc:integer); forward;
procedure alignOutput(amount:integer);
var mask,o:integer;
oldPc:integer;
begin
oldPc := pc;
mask := amount - 1;
o := pc and mask;
if o = 2 then
begin
emitInstructionWord(0);
(* if there was a label, we need to fix the address *)
fixupLabel(oldPc, pc);
end
else
if o = 0 then
begin (* do nothing *) end
else
errorExit2('internal error: bad alignment', '');
end;
procedure emitBinByte(value:integer);
var c:char;
begin
c := chr(value and $FF);
write(outfile,c);
end;
procedure emitBin16(value:InstructionWord);
var hi,lo:integer;
begin
hi := value and $FF00 shr 8;
lo := value and $00FF;
emitBinByte(hi);
emitBinByte(lo);
end;
procedure emitBin32(value:MachineWord);
var b3,b2,b1,b0:integer;
begin
b0 := value and $FF;
value := value shr 8;
b1 := value and $FF;
value := value shr 8;
b2 := value and $FF;
value := value shr 8;
b3 := value and $FF;
emitBinByte(b3);
emitBinByte(b2);
emitBinByte(b1);
emitBinByte(b0);
end;
procedure emitAsciiBin(encoded:InstructionWord);
var i:integer;
digit:char;
begin
for i := 1 to 16 do
begin
if (encoded and $8000) <> 0 then
digit := '1'
else
digit := '0';
write(outfile, digit);
encoded := encoded shl 1;
end;
if((pc and 3) = 2) then
writeln(outfile);
end;
(* assumes 32-bit alignment *)
procedure emitAsciiBin32(value:MachineWord);
var i:integer;
digit:char;
begin
for i := 1 to 32 do
begin
if (value and $80000000) <> 0 then
digit := '1'
else
digit := '0';
write(outfile, digit);
value := value shl 1;
end;
writeln(outfile);
end;
procedure emitAsciiByte(value:integer);
var i:integer;
digit:char;
begin
for i := 1 to 8 do
begin
if (value and $80) <> 0 then
digit := '1'
else
digit := '0';
write(outfile, digit);
value := value shl 1;
end;
if (pc and wordSizeMask) = 3 then
writeln(outfile);
end;
(*
Emit a single byte.
Make sure to do proper alignment afterwards.
Mainly used for .BYTE directive.
*)
procedure emitByte(value:integer);
begin
if outputEnabled then
begin
if asciiOutput then
emitAsciiByte(value)
else
emitBinByte(value);
end;
bytesCount := bytesCount + 1;
pc := pc + 1;
end;
(* assumes aligned output *)
procedure emitWord(value:MachineWord);
begin
if outputEnabled then
begin
if asciiOutput then
emitAsciiBin32(value)
else
emitBin32(value);
end;
bytesCount := bytesCount + 4;
pc := pc + 4;
end;
procedure emitInstructionWord(value:InstructionWord);
begin
if outputEnabled then
begin
if asciiOutput then
emitAsciiBin(value)
else
emitBin16(value);
end;
bytesCount := bytesCount + 2;
pc := pc + 2;
end;
procedure emitInstruction(var opcode:OpcodeData; encoded:InstructionWord);
begin
emitInstructionWord(encoded);
end;
procedure emitBlock(count:integer; value:integer);
var i:integer;
begin
alignOutput(wordSize);
for i := 1 to count do
emitWord(value);
end;
procedure encodeOperand(value:integer; var op:OpcodeData; var encoded:InstructionWord); forward;
procedure getBaseAndModifiers(var ins:KeywordString; var opcode:OpcodeData; var encoded:InstructionWord); forward;
procedure encodeInstruction(ins:string; operand:integer; var encoded:InstructionWord);
var opcode:OpcodeData;
begin
getBaseAndModifiers(ins, opcode, encoded);
if opcode.id = LOADCPId then
errorExit2('internal error in encodeInstruction', curToken.tokenText);
encodeOperand(operand, opcode, encoded);
end;
procedure ClearTree(var root:TreeRef);
begin
while root <> nil do
begin
(* delete subtrees first to reduce reshuffling of nodes *)
if root^.left <> nil then TreeDelete(root^.left, root^.left^.key^);
if root^.right <> nil then TreeDelete(root^.right, root^.right^.key^);
TreeDelete(root, root^.key^);
end;
verifyTree(root);
end;
procedure ClearCPool;
var current:CPoolRef;
next:CPoolRef;
begin
current := constantPool;
while current <> nil do
begin
next := current^.next;
dispose(current);
current := next;
end;
constantPool := nil;
end;
procedure createSymbol(var keyword:IdentString; typ:SymbolType; value:integer; aligned,padded:boolean);
forward;
procedure dumpCPool; forward;
procedure emitConstantPool(branch:boolean);
var current:CPoolRef;
labelname:IdentString;
value:KeywordString;
intValue:integer;
size:integer;
encoded:InstructionWord;
padded:boolean;
begin
(* writeln('*** emitConstantPool at ', pc, ' count ', cPoolCount); *)
if branch then
begin
(* calculate size of all constants in bytes *)
size := cPoolCount * wordSize;
(*
Add padding if alignment is needed.
This happens when the pc is at a word boundary (pc and wordSize = 0).
Then after the branch instruction, the pc is at a half-word boundary
and we need a padding half-word to get the correct alignment for the
word-sized constants.
*)
if (pc and wordSizeMask) = 0 then size := size + insSize;
(* encode the instruction, adjust operand for the size of the branch instruction*)
encodeInstruction('BRANCH', pc + size + insSize, encoded);
emitInstructionWord(encoded);
end;
current := constantPool;
padded := false;
(* if the constant pool is empty, do no alignment *)
if current <> nil then
begin
(* remember if we needed alignment padding *)
padded := (pc and wordSizeMask) = insSize;
alignOutput(wordSize);
end;
(* the cpool list has the latest entries at the front,
so go to the tail first and then go backward *)
if current <> nil then
while current^.next <> nil do
current := current^.next;
while current <> nil do
begin
labelname := current^.labelname;
value := current^.value;
createSymbol(labelname, LabelSymbol, pc, true, padded);
(* only the first entry is marked as padded *)
if padded then padded := false;
if current^.symbolic then
intValue := getSymbolValue(value)
else
intValue := convertNumber(value);
if intValue <> Unresolved then
intValue := intValue + current^.offset;
emitWord(intValue);
current := current^.prev;
end;
(* writeln('*** emitConstantPool new pc ', pc); *)
(* dumpCPool; *)
ClearCPool;
cPoolCount := 0;
end;
procedure encodeOperand(value:integer; var op:OpcodeData; var encoded:InstructionWord);
var mask, negativeMask:integer;
valueStr:string;
isSigned:boolean;
min, max: integer;
begin
case op.operand of
NoOprnd: begin mask := 0; negativeMask := 0; end;
U13WOprnd: begin mask := $1FFE; negativeMask := $0000; end;
S13Oprnd: begin mask := $1FFF; negativeMask := $1000; end;
RelWOprnd: begin mask := $1FFE; negativeMask := $1000; value := value - pc; end;
S10Oprnd: begin mask := $03FF; negativeMask := $0200; end;
U4Oprnd: begin mask := $000F; negativeMask := $0000; end;
CmpOprnd: begin mask := $000F; negativeMask := $0000; end;
RegOprnd: begin mask := $000F; negativeMask := $0000; end;
RelU10Oprnd: begin mask := $03FF; negativeMask := $0000; value := value - pc; end;
OptOprnd: begin mask := $000F; negativeMask := $0000; end;
end;
isSigned := negativeMask <> 0;
if isSigned then
begin
min := -negativeMask;
max := -min + 1;
end
else
begin
min := 0;
max := mask;
end;
if (value < min) or (value > max) then
begin
(* if not on the last pass (generating output),
we ignore values which are out of range,
because they might change after the 1st pass *)
if not outputEnabled then
value := 0
else
begin
if op.operand = RelU10Oprnd then
begin
writeln;
writeln('bad RelU10Oprnd:', value, ' pc:', pc, ' pass:', pass);
dumpCPool;
end;
str(value, valueStr);
errorExit2('Invalid operand value', valueStr);
end;
end;
encoded := (encoded and not mask) or (value and mask);
end;
function getSymbol(var keyword:KeywordString):TreeDataRef;
begin
getSymbol := TreeSearch(symbolTable, keyword);
if (getSymbol = nil) and (pass > 1) then
errorExit2('Undeclared symbol', keyword);
end;
function getLabelAddr(var keyword:KeywordString):integer;
var sym:TreeDataRef;
begin
sym := getSymbol(curToken.tokenText);
if sym = nil then
getLabelAddr := Unresolved
else
getLabelAddr := sym^.symboldata.value;
end;
function findSymbol(var keyword:KeywordString):TreeDataRef;
begin
findSymbol := TreeSearch(symbolTable, keyword);
end;
function getSymbolValue(var keyword:KeywordString):integer;
var data:TreeDataRef;
begin
data := TreeSearch(symbolTable, keyword);
if (data = nil) then
begin
if pass = 1 then
(* in pass 1, we do not care about undefined symbols *)
getSymbolValue := Unresolved
else
errorExit2('Undeclared symbol', keyword);
end
else
getSymbolValue := data^.symboldata.value;
end;
procedure addUncertainLabel(var name:IdentString);
var current:UnresBranchRef;
newLListEntry:LabelListRef;
begin
current := firstUnresBranch;
while (current <> nil) do
begin
(* writeln('**addUncertainLabel ',name, ' for unresBranch ', current^.target); *)
(* put new label list entry at head of the list *)
new(newLListEntry);
newLListEntry^.next := current^.labels;
newLListEntry^.prev := nil;
newLListEntry^.name := name;
if current^.labels <> nil then
current^.labels^.prev := newLListEntry;
current^.labels := newLListEntry;
current := current^.next;
end;
end;
procedure addUnresBranch(var name:IdentString; origin:integer; maxDistance,shrink:integer);
var newUnresBranch:^UnresolvedBranch;
begin
new(newUnresBranch);
newUnresBranch^.target := name;
newUnresBranch^.origin := origin;
newUnresBranch^.maxDistance := maxDistance;
newUnresBranch^.shrinkage := shrink;
newUnresBranch^.labels := nil;
newUnresBranch^.next := firstUnresBranch;
firstUnresBranch := newUnresBranch;
(* writeln('** addUnresBranch ', name, ' at ', origin); *)
end;
(* Delete unresolved branch entry with target *name*.
Will delete multiple occurrences of the same name. *)
procedure deleteUnresBranch(var name:IdentString);
var last,current,temp:^UnresolvedBranch;
begin
current := firstUnresBranch;
last := nil;
while (current <> nil) do
begin
if current^.target = name then
begin
if last = nil then
firstUnresBranch := current^.next
else
last^.next := current^.next;
temp := current^.next;
dispose(current);
current := temp;
end
else
begin
last := current;
current := current^.next;
end;
end;
end;
procedure addUnresBranch2(var name:IdentString; o:integer; max,shrink:integer);
var newUnresBranch:^UnresolvedBranch;
begin
new(newUnresBranch);
with newUnresBranch^ do
begin
target := name;
origin := o;
maxDistance := max;
shrinkage := shrink;
labels := nil;
next := firstUnresBranch;
end;
firstUnresBranch := newUnresBranch;
end;
(* Check if a label declaration resolves an unresolved branch
(.LBRANCH/.LCBRANCH). If it does, apply the code size correction
to all labels we encountered since the branch. *)
procedure checkUnresBranches(var name:IdentString);
var r:UnresBranchRef;
current,last,next:LabelListRef;
sym:TreeDataRef;
distance:integer;
shrink:boolean;
adjustment:integer;
begin
r := firstUnresBranch;
while r <> nil do
begin
if r^.target = name then
begin
distance := pc - r^.origin;
shrink := distance <= r^.maxDistance;
{ writeln('** checkUnresBranches found ', name, ' at ', pc, ' distance ', pc - r^.origin, ' line ', lineno);
writeln(' ', r^.origin, ' ', r^.maxDistance, ' ', r^.shrinkage); }
current := r^.labels;
last := nil;
if shrink then
pc := pc - r^.shrinkage;
(* writeln(' short:', shrink); *)
while current <> nil do
begin
(* go through all labels we encountered since the LBRANCH/LCBRANCH,
process and dispose of the list entries *)
(* writeln(' ', current^.name); *)
if shrink then
begin
sym := findSymbol(current^.name);
with sym^.symboldata do
begin
(* writeln(' ', value, ' -', r^.shrinkage, ' ', aligned, ' ', padded); *)
value := value - r^.shrinkage;
(* writeln(' ', value); *)
end;
end;
last := current;
current := current^.next;
end;
(* walk through list backwards to check alignment,
that is in order of occurrence *)
adjustment := 0;
current := last;
while current <> nil do
begin
if shrink then
begin
sym := findSymbol(current^.name);
with sym^.symboldata do
begin
if aligned then
begin
(* if this label needs alignment, shift all labels
after this one by one instruction word (2 bytes) *)
if ((value + adjustment) and wordSizeMask) = insSize then
begin
(* if a constant pool entry was already padded,
do not add second padding but remove first
padding instead *)
if not padded then
begin
padded := true;
pc := pc + insSize;
adjustment := adjustment + insSize;
end
else
begin
padded := false;
pc := pc - insSize;
adjustment := adjustment - insSize;
end;
end;
end;
value := value + adjustment;
(* if adjustment <> 0 then
writeln(' ', current^.name, ' adjusted to ', value, ' by ', adjustment); *)
end;
end;
{$ifndef FPC}
{writeln(' disposing ', current^.name, ' ', current);}
{$endif}
next := current^.prev;
dispose(current);
current := next;
end;
r^.labels := nil;
end;
r := r^.next;
end;
deleteUnresBranch(name);
end;
(* Create a symbol table entry.
On passes other than 1, symbol must exist and the value is updated. *)
procedure createSymbol(var keyword:IdentString; typ:SymbolType; value:integer; aligned, padded:boolean);
var d:TreeData;
dref:TreeDataRef;
begin
dref := TreeSearch(symbolTable, keyword);
if pass = 1 then
begin
if dref <> nil then
errorExit2('Duplicate label', keyword);
d.typ := TDSymbol;
d.symboldata.typ := typ;
d.symboldata.value := value;
d.symboldata.aligned := aligned;
d.symboldata.padded := padded;
d.symboldata.exported := false;
TreeInsert(symbolTable, keyword, d);
addUncertainLabel(keyword);
checkUnresBranches(keyword);
end
else
begin
if dref = nil then
begin
dumpCPool;
ErrorExit2('internal error in createSymbol', keyword);
end;
if dref^.symboldata.value <> value then
(* writeln('////// label changed value ', keyword, ' ',
dref^.symboldata.value, ' -> ', value); *)
dref^.symboldata.value := value;
end;
end;
(* Change the address of a label after it has been
created. This happens if an alignment is required
e.g. for a .WORD directive which has a label *)
procedure fixupLabel(oldPc, newPc:integer);
var current:TreeRef;
walkState:TreeWalkState;
begin
TreeWalkFirst(symbolTable, walkState, current);
while current <> nil do
begin
if current^.data^.symboldata.value = oldPc then
begin
current^.data^.symboldata.aligned := true;
current^.data^.symboldata.value := newPc;
current^.data^.symboldata.padded := (newPc <> oldPc);
(* if newPc <> oldPc then
writeln(' aligning ', current^.key^, ' ', oldPc, ' -> ', newPc); *)
end;
TreeWalkNext(walkState, current);
end;
end;
function parsePrimary:integer;
var applyNot:boolean;
negate:boolean;
value:integer;
begin
if checkToken(TildeToken) then
begin
readNextToken;
applyNot := true;
end
else
applyNot := false;
if checkToken(MinusToken) then
begin
readNextToken;
negate := true;
end
else
negate := false;
if checkToken(NumberToken) then
begin
(* let convertNumber handle negative numbers
because the statement
"value := -value" below would
not work for -maxint.
abs(-maxint) cannot be represented
as a signed 32-bit integer *)
if negate then
begin
value := convertNumber('-' + curToken.tokenText);
negate := false;
end
else
value := convertNumber(curToken.tokenText);
end
else
if checkToken(KeywordToken) then
value := getSymbolValue(curToken.tokenText)
else
if checkToken(CharLitToken) then
value := convertChar(curToken.tokenText)
else
if checkToken(AtToken) then
value := pc
else
errorExit2('number or symbol expected, got', descToken(curToken.tokenKind));
if applyNot then
value := not value;
if negate then
value := - value;
readNextToken;
parsePrimary := value;
end;
function parseExpression:integer;
var value:integer;
function parseNextPrimary:integer;
begin
readNextToken;
parseNextPrimary := parsePrimary();
end;
begin
value := parsePrimary;
while not (curToken.tokenKind in [ EOLToken, CommaToken ]) do
begin
if checkToken(PlusToken) then
value := value + parseNextPrimary
else
if checkToken(MinusToken) then
value := value - parseNextPrimary
else
if checkToken(AsteriskToken) then
value := value * parseNextPrimary
else
if checkToken(AndToken) then
value := value and parseNextPrimary
else
if checkToken(OrToken) then
value := value or parseNextPrimary
else
if checkToken(XorToken) then
value := value xor parseNextPrimary
else
if checkToken(SlashToken) then
value := value div parseNextPrimary
else
errorExit2('Expected one of + - * / & | ^ but got', curToken.tokenText);
end;
parseExpression := value;
end;
procedure parseLabel;
begin
(* writeln(':::: parseLabel ', curToken.tokenText, ' at ', pc); *)
createSymbol(curToken.tokenText, LabelSymbol, pc, false, false);
readNextToken;
end;
procedure parseOneWordArg;
var operandValue:integer;
begin
operandValue := parseExpression;
emitWord(operandValue);
end;
procedure parseWordArgs;
begin
alignOutput(wordSize);
parseOneWordArg;
while checkToken(CommaToken) do
begin
readNextToken;
(* if there is a comma at the end of the line,
continue to the next line *)
if checkToken(EOLToken) then
readNextToken;
parseOneWordArg;
end;
end;
procedure parseOneByteArg;
var bytevalue:integer;
c:char;
begin
if checkToken(StringLitToken) then
begin
for c in curToken.tokenText do
emitByte(ord(c));
readNextToken;
end
else
begin
bytevalue := parseExpression;
emitByte(byteValue);
end;
end;
procedure parseByteArgs;
begin
alignOutput(wordSize);
parseOneByteArg;
while checkToken(CommaToken) do
begin
readNextToken;
(* if there is a comma at the end of the line,
continue to the next line *)
if checkToken(EOLToken) then
readNextToken;
parseOneByteArg;
end;
(* align to word *)
while (pc and wordsizeMask) <> 0 do
emitByte(0);
end;
procedure dumpOpcodeTable;
var walkState:TreeWalkState;
walkRes:TreeRef;
begin
writeln('Opcode Table:');
TreeWalkStart(opcodeTable, walkState);
repeat
TreeWalkNext(walkState, walkRes);
if walkRes <> nil then
writeln(walkRes^.key^);
until walkRes = nil;
end;
function changeFileSuffix(filename: string; suffix:string): string; forward;
procedure writeSymbolTable;
var walkState:TreeWalkState;
walkRes:TreeRef;
h:string;
f:SymFileType;
fname:string;
c:char;
begin
fname := changeFileSuffix(filename, SymFileSuffix);
overwriteFile(f, fname);
TreeWalkStart(symbolTable, walkState);
repeat
TreeWalkNext(walkState, walkRes);
if walkRes <> nil then
begin
if walkRes^.data^.symboldata.typ in [ LabelSymbol, ConstSymbol ] then
begin
hexstr(walkRes^.data^.symboldata.value, h);
if walkRes^.data^.symboldata.typ = ConstSymbol then
c := '='
else
if walkRes^.data^.symboldata.exported then
c := '!'
else
c := ' ';
writeln(f, h, ' ', c, walkRes^.key^);
end;
end;
until walkRes = nil;
close(f);
end;
procedure dumpSymbolTable;
var walkState:TreeWalkState;
walkRes:TreeRef;
h:string;
begin
writeln('Symbol Table:');
TreeWalkStart(symbolTable, walkState);
repeat
TreeWalkNext(walkState, walkRes);
if walkRes <> nil then
begin
if walkRes^.data^.symboldata.typ = LabelSymbol then
begin
hexstr(walkRes^.data^.symboldata.value, h);
writeln(h, ' ', walkRes^.key^);
end;
end;
until walkRes = nil;
end;
procedure dumpCPool;
var current:CPoolRef;
begin
writeln('dump constant pool ', cPoolCount, ' ', nextConstId);
current := constantPool;
while current <> nil do
begin
writeln(current^.labelname, ': ', current^.value, ' ',
getSymbolValue(current^.labelname), ' ');
current := current^.next;
end;
end;
procedure emitLoadcp(var operand:KeywordString; numOffset:integer);
var cpooladdr:integer;
encoded:InstructionWord;
begin
cpooladdr := getCPoolAddr(operand, numOffset);
(* writeln('** emitLoadcp for ',operand, '/', numOffset, ' ', cpooladdr); *)
encodeInstruction('LOADREL', cpooladdr, encoded);
emitInstructionWord(encoded);
end;
procedure parseLoadcp;
var opstr:KeywordString;
data:TreeDataRef;
numOffset:integer;
begin
if not (curToken.tokenKind in [ KeywordToken, NumberToken, MinusToken ]) then
errorExit2('Identifier or number expected, got', curToken.tokenText);
if checkToken(MinusToken) then
begin
opstr := '-';
readNextToken;
if not checkToken(NumberToken) then
errorExit2('Invalid number', curToken.tokenText);
opstr := opstr + curToken.tokenText;
end
else
opstr := curToken.tokenText;
readNextToken;
(* check for optional offset *)
if checkToken(CommaToken) then
begin
readNextToken;
(* offset can be either a number literal *)
if checkToken(NumberToken) then
numOffset := convertNumber(curToken.tokenText)
else
(* or a symbol *)
if checkToken(KeywordToken) then
begin
data := findSymbol(curToken.tokenText);
if data = nil then
errorExit2('Cannot use unresolved symbol for LOADCP offset:',
curToken.tokenText);
numOffset := data^.symboldata.value;
end
else
errorExit2('Number or symbol required, got', curToken.tokenText);
readNextToken;
end
else
numOffset := 0;
emitLoadcp(opstr, numOffset);
end;
procedure parseLbranch;
var value:integer;
distance:integer;
offset,shrinkage:integer;
pad:boolean;
encoded:InstructionWord;
begin
if not checkToken(KeywordToken) then
errorExit2('identifier expected, got', curToken.tokenText);
value := getLabelAddr(curToken.tokenText);
distance := value - pc;
if (value = Unresolved) or (distance > 4095) or (distance < -4096) then
begin
if (pc and 3) = 0 then (* no padding *)
begin
pad := false;
(* total size 8 bytes *)
offset := 4; (* offset for LOADREL *)
shrinkage := 6; (* difference to short form size *)
end
else
begin
pad := true;
(* total size 10 bytes *)
offset := 6; (* offset for LOADREL with padding *)
shrinkage := 8; (* difference to short form size *)
end;
if value = Unresolved then
addUnresBranch(curToken.tokenText, pc, 4095, shrinkage);
encodeInstruction('LOADREL', pc + offset, encoded);
emitInstructionWord(encoded);
encodeInstruction('JUMP', 0, encoded);
emitInstructionWord(encoded);
if pad then
emitInstructionWord(0);
emitWord(value);
end
else
begin
encodeInstruction('BRANCH', value, encoded);
emitInstructionWord(encoded);
end;
(*
if value = Unresolved then
writeln('** parseLbranch ', curToken.tokenText, ' unresolved')
else
if (distance > 4095) then
writeln('** parseLbranch ', curToken.tokenText, ' long ', distance)
else
writeln('** parseLbranch ', curToken.tokenText, ' short ', distance);
*)
readNextToken;
end;
procedure parseLcbranch(negate:boolean);
var value:integer;
relValue:integer;
offset,shrinkage:integer;
encoded:InstructionWord;
modifier:string[4];
pad:boolean;
begin
if not checkToken(KeywordToken) then
errorExit2('identifier expected, got', curToken.tokenText);
value := getLabelAddr(curToken.tokenText);
relValue := value - pc;
modifier := '';
if (value = Unresolved) or (relValue > 4095) or (relValue < -4096) then
begin
if (pc and 3) = 2 then (* no padding *)
begin
pad := false;
(* total size 10 bytes *)
offset := 4; (* offset for LOADREL *)
shrinkage := 8; (* difference to short form size *)
end
else
begin
pad := true;
(* total size 12 bytes *)
offset := 6; (* offset for LOADREL with padding *)
shrinkage := 10; (* difference to short form size *)
end;
if value = Unresolved then
addUnresBranch(curToken.tokenText, pc, 4095, shrinkage);
(* writeln('*** long cbranch triggered:', value, ' ', relValue, ' pass:',pass);
writeln('*** pc ', pc, ' offset ', offset, ' insSize ', insSize);
writeln('*** ', pc + offset + insSize); *)
if not negate then
modifier := '.Z';
(* branch over CBRANCH, LOADREL, padding and literal value *)
encodeInstruction('CBRANCH' + modifier, pc + insSize + offset + wordSize, encoded);
emitInstructionWord(encoded);
encodeInstruction('LOADREL', pc + offset, encoded);
emitInstructionWord(encoded);
encodeInstruction('JUMP', 0, encoded);
emitInstructionWord(encoded);
if pad then
emitInstructionWord(0);
emitWord(value);
end
else
begin
if negate then
modifier := '.Z';
encodeInstruction('CBRANCH' + modifier, value, encoded);
emitInstructionWord(encoded);
end;
readNextToken;
end;
procedure parseDirective;
var operandValue:integer;
count:integer;
name:IdentString;
oldsym:TreeDataRef;
begin
readNextToken;
if lastToken.tokenText = '.LBRANCH' then
parseLbranch
else
if lastToken.tokenText = '.LCBRANCH' then
parseLcbranch(false)
else
if lastToken.tokenText = '.LCBRANCHZ' then
parseLcbranch(true)
else
if lastToken.tokenText = '.ORG' then
begin
operandValue := parseExpression;
pc := operandValue;
end
else
if lastToken.tokenText = '.EQU' then
begin
matchToken(KeywordToken);
name := lastToken.tokenText;
operandValue := parseExpression;
oldsym := findSymbol(name);
if oldsym <> nil then
begin
if oldsym^.symboldata.value <> operandValue then
errorExit2('Symbol already declared:', name);
end
else
createSymbol(name, ConstSymbol, operandValue, false, false);
end
else
if lastToken.tokenText = '.WORD' then
parseWordArgs
else
if lastToken.tokenText = '.BYTE' then
parseByteArgs
else
if lastToken.tokenText = '.CPOOL' then
emitConstantPool(false)
else
if lastToken.tokenText = '.CPOOLNOP' then
emitConstantPool(true)
else
if lastToken.tokenText = '.BLOCK' then
begin
count := parseExpression;
if matchTokenOrNot(CommaToken) then
operandValue := parseExpression
else
operandValue := 0;
emitBlock(count, operandValue);
end
else
errorExit2('Unrecognized directive', lastToken.tokenText);
end;
procedure parseMetaDirective;
var filename:string;
sym:TreeDataRef;
begin
readNextToken;
if lastToken.tokenText = '%INCLUDE' then
begin
if curToken.tokenKind in [StringLitToken, KeywordToken] then
begin
filename := curToken.tokenText;
readNextToken;
beginInclude(filename);
end
else
errorExit2('Filename expected', '');
end
else
if lastToken.tokenText = '%EXPORT' then
begin
if curToken.tokenKind = KeywordToken then
begin
sym := findSymbol(curToken.tokenText);
if sym = nil then
errorExit2('Undeclared symbol', curToken.tokenText);
sym^.symboldata.exported := true;
end
else
errorExit2('Symbol expected', '');
readNextToken;
end
else
errorExit2('Invalid meta directive', lastToken.tokenText);
end;
procedure encode(var entry:EncodingEntry; var value:InstructionWord);
begin
value := (value and not entry.mask) or entry.value;
end;
procedure getModifier(var m:KeywordString; var opcode:OpcodeData; var encoded:InstructionWord);
var cur:^ModifierEntry;
begin
if opcode.id <> 0 then
begin
cur := opcode.modifiers;
while cur <> nil do
begin
if m = cur^.keyword then
begin
encode(cur^.encoding, encoded);
break;
end;
cur := cur^.next;
end;
if cur = nil then
errorExit2('Invalid modifier', m);
end;
end;
procedure getMnemonic(var m:KeywordString; var opcode:OpcodeData; var encoded:InstructionWord);
var data:TreeDataRef;
ch:char;
begin
if m[1] = shortcutChar then
begin
ch := m[2];
if (ch < firstShCChar) or (ch > lastShCChar) then
errorExit2('invalid shortcut', m);
opcode := shortcuts[ch];
if opcode.id = -1 then
errorExit2('invalid shortcut', m);
encode(opcode.encoding, encoded);
end
else
begin
data := TreeSearch(opcodeTable, m);
if data = nil then
begin
errorExit2('Unrecognized instruction', m);
opcode.id := 0;
end
else
begin
opcode := data^.opcodedata;
encode(opcode.encoding, encoded);
end;
end;
end;
procedure getBaseAndModifiers(var ins:KeywordString; var opcode:OpcodeData; var encoded:InstructionWord);
var i:integer;
insLength:integer;
slice:string;
startPos:integer;
function scanchar(c:char; curPos:integer):integer;
begin
scanchar := 0;
while curPos <= insLength do
begin
if ins[curPos] = c then
begin
scanchar := curPos;
break;
end
else
curPos := curPos + 1;
end;
end;
begin
encoded := 0;
insLength := length(ins);
i := pos('.',ins);
{ writeln('** getModifiers ',i); }
if i > 1 then
begin
slice := copy(ins,1,i-1);
getMnemonic(slice, opcode, encoded);
repeat
startPos := i + 1;
i := scanchar('.', startPos);
if i > 0 then
begin
slice := copy(ins,startPos,i-startPos);
getModifier(slice, opcode, encoded);
end;
until i < 1;
(* last slice *)
slice := copy(ins, startPos, insLength-startPos+1);
getModifier(slice, opcode, encoded);
end
else
getMnemonic(ins, opcode, encoded);
end;
procedure parseInstruction;
var operandValue:integer;
opcode:OpcodeData;
encodedIns:InstructionWord;
begin
getBaseAndModifiers(curToken.tokenText, opcode, encodedIns);
readNextToken;
if opcode.id = LOADCPId then
parseLoadcp
else
begin
if opcode.operand <> NoOprnd then
begin
if not checkToken(EOLToken) then
operandValue := parseExpression
else
begin
if opcode.operand = OptOprnd then
operandValue := 0
else
errorExit2('Missing operand', lastToken.tokenText)
end
end
else
operandValue := 0;
encodeOperand(operandValue, opcode, encodedIns);
emitInstruction(opcode, encodedIns);
end;
end;
procedure parseLine;
begin
(* writeln('## P', pass, ' line ', lineno:4, ' pc ', pc:8); *)
if checkToken(LabelToken) then
parseLabel;
if checkToken(DirectiveToken) then
parseDirective
else
if checkToken(KeywordToken) then
parseInstruction
else
if checkToken(MetaKeywordToken) then
parseMetaDirective
else
if checkToken(EOLToken) then
begin end (* empty line *)
else
begin
(* writeln(curToken.tokenKind); *)
errorExit2('Invalid syntax', curToken.tokenText);
end;
matchToken(EOLToken);
end;
procedure parseFile;
begin
readNextToken;
repeat
parseLine;
if (lineno and progressSteps) = 0 then
printCurrentLineno;
until checkToken(EOFToken);
emitConstantPool(false);
end;
(* Add an instruction to the opcodeTable (which is a tree).
mask specifies the bits which are used by the opcode.
*)
procedure addOpcode(mnemonic:InsString; value, mask:integer; oprnd: OperandType);
var data:TreeData;
begin
data.typ := TDOpcode;
data.opcodedata.encoding.value := value;
data.opcodedata.encoding.mask := mask;
data.opcodedata.modifiers := nil;
data.opcodedata.operand := oprnd;
data.opcodedata.id := nextOpcodeId;
nextOpcodeId := nextOpcodeId + 1;
TreeInsert(opcodeTable, mnemonic, data);
lastOpcode := TreeSearch(opcodeTable, mnemonic);
end;
(* Add a modifier to the instruction that was last added by addOpcode.
mask specifies the bits used by the modifier.
*)
procedure addModifier(key:InsString; value, mask:integer);
var newModifier:^ModifierEntry;
cur:^ModifierEntry;
begin
if lastOpcode = nil then
errorExit2('internal error in addModifier', key);
new(newModifier);
newModifier^.keyword := key;
newModifier^.encoding.value := value;
newModifier^.encoding.mask := mask;
newModifier^.next := nil;
cur := lastOpcode^.opcodedata.modifiers;
if cur = nil then
begin
lastOpcode^.opcodedata.modifiers := newModifier
end
else
begin
while cur^.next <> nil do cur := cur^.next;
cur^.next := newModifier;
end;
end;
procedure addAlias(key:InsString; dest:InsString);
var opcode:OpcodeData;
encoded:InstructionWord;
begin
(* encode the instruction and modifiers *)
getBaseAndModifiers(dest, opcode, encoded);
(* add a new opcode entry with the alias name and
the values we just calculated *)
addOpcode(key, encoded, opcode.encoding.mask, opcode.operand);
(* copy the modifier list (list of valid modifiers)
from the original instruction *)
lastOpcode^.opcodedata.modifiers := opcode.modifiers;
end;
procedure addShortcut(ch:char; dest:InsString);
var opcode:OpcodeData;
encoded:InstructionWord;
begin
if shortcuts[ch].id <> -1 then
errorExit2('internal error in addShortcut for', dest);
getBaseAndModifiers(dest, opcode, encoded);
shortcuts[ch] := opcode;
end;
procedure addSpecialOperand(key:IdentString; value:integer);
begin
createSymbol(key, SpecialSymbol, value, false, false)
end;
procedure initSpecialOperands;
begin
pass := 1; (* createSymbol only creates symbols on pass 1 *)
(* create special operand symbols *)
addSpecialOperand('FP', 0);
addSpecialOperand('BP', 1);
addSpecialOperand('RP', 2);
addSpecialOperand('IV', 3);
addSpecialOperand('IR', 4);
addSpecialOperand('ESP', 5);
addSpecialOperand('EQ', 2);
addSpecialOperand('LT', 1);
addSpecialOperand('NE', 6);
addSpecialOperand('LE', 3);
addSpecialOperand('GE', 5);
addSpecialOperand('GT', 7);
end;
procedure initOpcodes;
begin
addOpcode('BRANCH', $0000, $E000, RelWOprnd);
addOpcode('LOADC', $C000, $E000, S13Oprnd);
addOpcode('LOAD', $8000, $E000, U13WOprnd);
addModifier('B', $0001, $0001);
addOpcode('STORE', $4000, $E000, U13WOprnd);
addModifier('B', $0001, $0001);
addOpcode('CBRANCH',$A001, $E001, RelWOprnd);
addModifier('N', $0001, $0001);
addModifier('NZ', $0001, $0001);
addModifier('Z', $0000, $0001);
addOpcode('XFER', $6000, $E000, NoOprnd);
addModifier('RSM1', $0300, $0300);
addModifier('RS0', $0000, $0300);
addModifier('RS1', $0100, $0300);
addModifier('R2P', $0080, $0080);
addModifier('P2R', $0040, $0040);
addModifier('SM1', $0030, $0030);
addModifier('S0', $0000, $0030);
addModifier('S1', $0010, $0030);
addModifier('X2P', $0001, $0001);
addOpcode('ALU', $2000, $E000, OptOprnd);
addModifier('SM1', $0030, $0030);
addModifier('S0', $0000, $0030);
addModifier('S1', $0010, $0030);
addModifier('X2Y', $0040, $0040);
addModifier('NX2Y', $0000, $0040);
addModifier('XT', $0080, $0080);
addModifier('ADD', $0000, $1e00);
addModifier('SUB', $0200, $1e00);
addModifier('NOT', $0400, $1e00);
addModifier('AND', $0600, $1e00);
addModifier('OR', $0800, $1e00);
addModifier('XOR', $0a00, $1e00);
addModifier('CMP', $0c00, $1e00);
addModifier('Y', $0e00, $1e00);
addModifier('SHR', $1000, $1e00);
addModifier('SHL', $1200, $1e00);
addModifier('INC', $1400, $1e00);
addModifier('DEC', $1600, $1e00);
addModifier('BPLC', $1a00, $1e00);
addModifier('BROT', $1c00, $1e00);
addModifier('BSEL', $1e00, $1e00);
addModifier('CMPU', $1800, $1e00);
(* addOpcode('EXT', $E000, $E000); *)
addOpcode('MEM', $E400, $FFF0, OptOprnd);
addModifier('W', $0200, $0200);
addModifier('SM1', $0030, $0030);
addModifier('S0', $0000, $0030);
addModifier('S1', $0010, $0030);
addModifier('X2Y', $0040, $0040);
addModifier('NX2Y', $0000, $0040);
addOpcode('LOADREL',$F400, $FC00, RelU10Oprnd);
LOADCPId := nextOpcodeId;
addOpcode('LOADCP', $F400, $FC00, RelU10Oprnd);
addOpcode('REG', $E000, $FFF0, RegOprnd);
addModifier('W', $0200, $0200);
addOpcode('FPADJ', $EC00, $FC00, S10Oprnd);
addAlias('JUMP', 'XFER.SM1.X2P');
addAlias('CALL', 'XFER.RS1.SM1.P2R.X2P');
addAlias('RET', 'XFER.RSM1.R2P');
addAlias('ADD', 'ALU.ADD.SM1');
addAlias('SUB', 'ALU.SUB.SM1');
addAlias('NOT', 'ALU.NOT.S0');
addAlias('AND', 'ALU.AND.SM1');
addAlias('OR', 'ALU.OR.SM1');
addAlias('XOR', 'ALU.XOR.SM1');
addAlias('CMP', 'ALU.CMP.SM1');
addAlias('SHR', 'ALU.SHR.S0');
addAlias('SHL', 'ALU.SHL.S0');
addAlias('DUP', 'ALU.INC.S1.X2Y');
addAlias('NIP', 'ALU.INC.SM1');
addAlias('INC', 'ALU.INC.S0');
addAlias('DEC', 'ALU.DEC.S0');
addAlias('CMPU', 'ALU.CMPU.SM1');
addAlias('BPLC', 'ALU.BPLC.SM1');
addAlias('BROT', 'ALU.BROT.S0');
addAlias('BSEL', 'ALU.BSEL.SM1');
addAlias('Y', 'ALU.Y.S1.X2Y');
addAlias('DROP', 'ALU.Y.SM1');
addAlias('SWAP', 'ALU.Y.S0.X2Y');
addAlias('OVER', 'ALU.Y.S1.X2Y');
addAlias('LOADI', 'MEM');
addAlias('STOREI', 'MEM.W.SM1');
addAlias('LOADREG', 'REG');
addAlias('STOREREG', 'REG.W');
end;
procedure initShortcuts;
var ch:char;
begin
for ch := firstShCChar to lastShCChar do
shortcuts[ch].id := -1;
addShortcut('A', 'ADD');
addShortcut('B', 'BRANCH');
addShortcut('C', 'CALL');
addShortcut('D', 'DUP');
addShortcut('E', 'LOADREL');
addShortcut('F', 'LOAD');
addShortcut('G', 'LOADREG');
addShortcut('H', 'SHL');
addShortcut('I', 'LOADI');
addShortcut('J', 'JUMP');
addShortcut('K', 'LOADC');
addShortcut('L', 'LOADCP');
addShortcut('M', 'STORE');
addShortcut('N', 'NIP');
addShortcut('O', 'OR');
addShortcut('P', 'DROP');
(* Q is unused *)
addShortcut('R', 'RET');
addShortcut('S', 'STOREI');
addShortcut('T', 'NOT');
addShortcut('U', 'CMPU');
addShortcut('V', 'OVER');
addShortcut('W', 'SWAP');
addShortcut('X', 'XOR');
(* Y is ununsed *)
addShortcut('Z', 'SUB');
addShortcut('a', 'AND');
addShortcut('b', 'CBRANCH');
addShortcut('c', 'CMP');
addShortcut('d', 'DEC');
(* e is unused *)
addShortcut('f', 'FPADJ');
addShortcut('g', 'STOREREG');
addShortcut('h', 'SHR');
addShortcut('i', 'INC');
end;
function changeFileSuffix(filename: string; suffix:string): string;
var suffixPos:integer;
begin
suffixPos := pos(filenameSuffix, filename);
if suffixPos > 0 then
setlength(filename, suffixPos-1);
filename := filename + suffix;
changeFileSuffix := filename;
end;
procedure performPass(passNo:integer);
begin
lineno := 1;
nextConstId := 0;
cPoolCount := 0;
pc := 0;
bytesCount := 0;
pass := passNo;
outputEnabled := pass = 2;
openFileWithDefault(infile, filename);
infileOpened := true;
if outputEnabled then
overwriteFile(outfile, outfilename);
parseFile;
printLastLineno;
close(infile);
if outputEnabled then
close(outfile);
(* dumpSymbolTable; *)
end;
procedure verifyNodeKey(node:TreeRef);
var c:integer;
begin
if node = nil then
errorExit2('verifyNodeKey FAIL node is nil', '');
if node^.key = nil then
errorExit2('verifyNodeKey FAIL key is nil', '');
if length(node^.key^) < 1 then
errorExit2('verifyNodeKey FAIL key has zero length', '');
c := ord(node^.key^[1]);
if not (
((c >= ord('0')) and (c <= ord('9')))
or
((c >= ord('A')) and (c <= ord('F')))
) then
begin
writeln('verifyNodeKey FAIL at ', node^.key^, ' ', c);
if node^.parent <> nil then
writeln(' parent:', node^.parent^.key^);
errorExit;
end;
end;
procedure verifyTree(node:TreeRef);
begin
if node <> nil then
begin
verifyNodeKey(node);
if node^.right <> nil then
begin
if node^.right^.parent <> node then
errorExit2('verifyTree FAIL parent check right at', node^.key^);
verifyTree(node^.right);
end;
if node^.left <> nil then
begin
if node^.left^.parent <> node then
errorExit2('verifyTree FAIL parent check left at', node^.key^);
verifyTree(node^.left);
end;
end;
end;
begin
infileOpened := false;
outputEnabled := false;
asciiOutput := false;
editOnError := false;
runOnSuccess := false;
buffered := false;
includeLevel := 0;
symbolTable := nil;
opcodeTable := nil;
lastOpcode := nil;
firstUnresBranch := nil;
nextOpcodeId := 1;
if ParamCount < 1 then halt;
paramPos := 1;
filename := '';
outfilename := '';
while paramPos <= ParamCount do
begin
if paramStr(paramPos) = '-e' then
editOnError := true
else
if paramStr(paramPos) = '-R' then
runOnSuccess := true
else
if paramStr(paramPos) = '-A' then
asciiOutput := true
else
begin
if length(filename) = 0 then
filename := ParamStr(paramPos)
else
outfilename := ParamStr(paramPos);
end;
paramPos := paramPos + 1;
end;
initPlatform;
initOpcodes;
initSpecialOperands;
initShortcuts;
if length(outfilename) = 0 then
begin
if asciiOutput then
outfilename := changeFileSuffix(filename, asciifileSuffix)
else
outfilename := changeFileSuffix(filename, outfileSuffix)
end;
writeln('Assembling ', filename, ' to ', outfilename);
performPass(1);
performPass(2);
writeln(#13, lineno - 1, ' lines, program size ', bytesCount, ' bytes.');
(* dumpOpcodeTable; *)
(* dumpSymbolTable; *)
writeSymbolTable;
if runOnSuccess then
ExecProgram(outfilename);
end.