gigatron/rom/Contrib/at67/validater.cpp
2025-01-28 19:17:01 +03:00

478 lines
22 KiB
C++

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string>
#include <vector>
#include <algorithm>
#include "memory.h"
#include "cpu.h"
#include "assembler.h"
#include "compiler.h"
#include "validater.h"
namespace Validater
{
bool initialise(void)
{
return true;
}
void adjustLabelAddresses(uint16_t address, int offset)
{
// Adjust addresses for any non page jump labels with addresses higher than start label, (labels can be stored out of order)
for(int i=0; i<int(Compiler::getLabels().size()); i++)
{
if(!Compiler::getLabels()[i]._pageJump && Compiler::getLabels()[i]._address >= address)
{
Compiler::getLabels()[i]._address += int16_t(offset);
}
}
for(int i=0; i<int(Compiler::getInternalLabels().size()); i++)
{
if(Compiler::getInternalLabels()[i]._address >= address)
{
Compiler::getInternalLabels()[i]._address += int16_t(offset);
}
}
}
void adjustVasmAddresses(int codeLineIndex, uint16_t address, int offset)
{
for(int i=codeLineIndex; i<int(Compiler::getCodeLines().size()); i++)
{
for(int j=0; j<int(Compiler::getCodeLines()[i]._vasm.size()); j++)
{
// Don't adjust page jump's
if(!Compiler::getCodeLines()[i]._vasm[j]._pageJump && Compiler::getCodeLines()[i]._vasm[j]._address >= address)
{
Compiler::getCodeLines()[i]._vasm[j]._address += int16_t(offset);
}
}
}
}
auto insertPageJumpInstruction(const std::vector<Compiler::CodeLine>::iterator& itCode, const std::vector<Compiler::VasmLine>::iterator& itVasm,
const std::string& opcode, const std::string& operand, const std::string& code, uint16_t address, int vasmSize)
{
if(itVasm >= itCode->_vasm.end())
{
fprintf(stderr, "Validater::insertPageJumpInstruction() : Trying to insert a PAGE JUMP into lala land, in '%s'", itCode->_code.c_str());
_EXIT_(EXIT_FAILURE);
}
Memory::takeFreeRAM(address, vasmSize, true);
return itCode->_vasm.insert(itVasm, {address, opcode, operand, code, "", true, vasmSize});
}
// TODO: make this more flexible, (e.g. sound channels off etc)
bool checkForRelocation(const std::string& opcode, uint16_t vPC, uint16_t& nextPC, uint16_t& numThunks, uint16_t& totalThunkSize, bool& print)
{
#define CALL_PAGE_JUMP_SIZE 7
#define CALLI_PAGE_JUMP_SIZE 3
#define CALL_PAGE_JUMP_OFFSET 2
#define CALLI_PAGE_JUMP_OFFSET 0
int opcodeSize = 0;
if(opcode.size())
{
// Macro
if(opcode[0] == '%')
{
std::string macro = opcode;
macro.erase(0, 1);
if(Compiler::getMacroIndexEntries().find(macro) != Compiler::getMacroIndexEntries().end())
{
opcodeSize = Compiler::getMacroIndexEntries()[macro]._byteSize;
}
}
// VASM
else
{
opcodeSize = Assembler::getAsmOpcodeSize(opcode);
}
if(opcodeSize)
{
// Increase opcodeSize by size of page jump prologue
int opSize = (Compiler::getCodeRomType() >= Cpu::ROMv5a) ? opcodeSize + CALLI_PAGE_JUMP_SIZE : opcodeSize + CALL_PAGE_JUMP_SIZE;
// Code can't straddle page boundaries
if(HI_BYTE(vPC) == HI_BYTE(vPC + opSize) && Memory::isFreeRAM(vPC, opSize))
{
// Code relocation is not required if requested RAM address is free
Memory::takeFreeRAM(vPC, opcodeSize, true);
return false;
}
// Get next free code address after page jump prologue and relocate code, (return true)
if(!Memory::getNextCodeAddress(Memory::FitAscending, vPC, opSize, nextPC))
{
fprintf(stderr, "Validater::checkForRelocation(): Memory alloc at 0x%0x4 of size %d failed\n", vPC, opSize);
return false;
}
if(!print)
{
print = true;
fprintf(stderr, "\n*******************************************************\n");
fprintf(stderr, "* Relocating \n");
fprintf(stderr, "*******************************************************\n");
fprintf(stderr, "* Opcode : Address : Size : New \n");
fprintf(stderr, "*******************************************************\n");
}
numThunks++;
uint16_t newPC = nextPC;
if(Compiler::getCodeRomType() >= Cpu::ROMv5a)
{
newPC += CALLI_PAGE_JUMP_OFFSET;
totalThunkSize += CALLI_PAGE_JUMP_SIZE + CALLI_PAGE_JUMP_OFFSET;
}
else
{
newPC += CALL_PAGE_JUMP_OFFSET;
totalThunkSize += CALL_PAGE_JUMP_SIZE + CALL_PAGE_JUMP_OFFSET;
}
fprintf(stderr, "* %-20s : 0x%04x : %2d bytes : 0x%04x\n", opcode.c_str(), vPC, opcodeSize, newPC);
return true;
}
}
return false;
}
bool checkForRelocations(void)
{
std::string line;
bool print = false;
uint16_t numThunks = 0, totalThunkSize = 0;
for(auto itCode=Compiler::getCodeLines().begin(); itCode!=Compiler::getCodeLines().end();)
{
if(itCode->_vasm.size() == 0)
{
itCode++;
continue;
}
int codeLineIndex = int(itCode - Compiler::getCodeLines().begin());
for(auto itVasm=itCode->_vasm.begin(); itVasm!=itCode->_vasm.end();)
{
uint16_t nextPC;
bool excluded = checkForRelocation(itVasm->_opcode, itVasm->_address, nextPC, numThunks, totalThunkSize, print);
if(!itVasm->_pageJump && excluded)
{
std::vector<std::string> tokens;
uint16_t currPC = itVasm->_address;
// Insert PAGE JUMP
int restoreOffset = 0;
std::string nextPClabel = "_page_" + Expression::wordToHexString(nextPC);
if(Compiler::getCodeRomType() >= Cpu::ROMv5a)
{
// CALLI PAGE JUMP
std::string codeCALLI;
int sizeCALLI = Compiler::createVcpuAsm("CALLI", nextPClabel, codeLineIndex, codeCALLI);
itVasm = insertPageJumpInstruction(itCode, itVasm, "CALLI", nextPClabel, codeCALLI, uint16_t(currPC), sizeCALLI);
}
else
{
// ROMS that don't have CALLI save and restore vAC
std::string codeSTW, codeLDWI, codeCALL, codeLDW;
#define VAC_SAVE_STACK
#ifdef VAC_SAVE_STACK
int sizeSTW = Compiler::createVcpuAsm("STLW", "0xFE", codeLineIndex, codeSTW);
int sizeLDWI = Compiler::createVcpuAsm("LDWI", nextPClabel, codeLineIndex, codeLDWI);
int sizeCALL = Compiler::createVcpuAsm("CALL", "giga_vAC", codeLineIndex, codeCALL);
int sizeLDW = Compiler::createVcpuAsm("LDLW", "0xFE", codeLineIndex, codeLDW);
itVasm = insertPageJumpInstruction(itCode, itVasm + 0, "STLW", "0xFE", codeSTW, uint16_t(currPC), sizeSTW);
itVasm = insertPageJumpInstruction(itCode, itVasm + 1, "LDWI", nextPClabel, codeLDWI, uint16_t(currPC + sizeSTW), sizeLDWI);
itVasm = insertPageJumpInstruction(itCode, itVasm + 1, "CALL", "giga_vAC", codeCALL, uint16_t(currPC + sizeSTW + sizeLDWI), sizeCALL);
itVasm = insertPageJumpInstruction(itCode, itVasm + 1, "LDLW", "0xFE", codeLDW, uint16_t(nextPC), sizeLDW);
#else
#define VAC_SAVE_START 0x00D6
int sizeSTW = Compiler::createVcpuAsm("STW", Expression::byteToHexString(VAC_SAVE_START), codeLineIndex, codeSTW);
int sizeLDWI = Compiler::createVcpuAsm("LDWI", nextPClabel, codeLineIndex, codeLDWI);
int sizeCALL = Compiler::createVcpuAsm("CALL", "giga_vAC", codeLineIndex, codeCALL);
int sizeLDW = Compiler::createVcpuAsm("LDW", Expression::byteToHexString(VAC_SAVE_START), codeLineIndex, codeLDW);
itVasm = insertPageJumpInstruction(itCode, itVasm + 0, "STW", Expression::byteToHexString(VAC_SAVE_START), codeSTW, uint16_t(currPC), sizeSTW);
itVasm = insertPageJumpInstruction(itCode, itVasm + 1, "LDWI", nextPClabel, codeLDWI, uint16_t(currPC + sizeSTW), sizeLDWI);
itVasm = insertPageJumpInstruction(itCode, itVasm + 1, "CALL", "giga_vAC", codeCALL, uint16_t(currPC + sizeSTW + sizeLDWI), sizeCALL);
itVasm = insertPageJumpInstruction(itCode, itVasm + 1, "LDW", Expression::byteToHexString(VAC_SAVE_START), codeLDW, uint16_t(nextPC), sizeLDW);
#endif
// New page address is offset by size of vAC restore
restoreOffset = sizeLDW;
}
// Fix labels and addresses
int offset = nextPC + restoreOffset - currPC;
adjustLabelAddresses(currPC, offset);
adjustVasmAddresses(codeLineIndex, currPC, offset);
// Check for existing label, (after label adjustments)
int labelIndex = -1;
std::string labelName;
Compiler::VasmLine* vasmCurr = &itCode->_vasm[itVasm - itCode->_vasm.begin()]; // points to CALLI and LDW
Compiler::VasmLine* vasmNext = &itCode->_vasm[itVasm + 1 - itCode->_vasm.begin()]; // points to instruction after CALLI and after LDW
if(Compiler::findLabel(nextPC) >= 0)
{
labelIndex = Compiler::findLabel(nextPC);
labelName = Compiler::getLabels()[labelIndex]._name;
}
if(labelIndex == -1)
{
// Create CALLI page jump label, (created later in outputCode())
if(Compiler::getCodeRomType() >= Cpu::ROMv5a)
{
// Code referencing these labels must be fixed later in outputLabels, (discarded label addresses must be updated if they match page jump address)
if(vasmNext->_internalLabel.size())
{
Compiler::getDiscardedLabels().push_back({vasmNext->_address, vasmNext->_internalLabel});
Compiler::adjustDiscardedLabels(vasmNext->_internalLabel, vasmNext->_address);
}
vasmNext->_internalLabel = nextPClabel;
}
// Create pre-CALLI page jump label, (created later in outputCode())
else
{
// Code referencing these labels must be fixed later in outputLabels, (discarded label addresses must be updated if they match page jump address)
if(vasmCurr->_internalLabel.size())
{
Compiler::getDiscardedLabels().push_back({vasmCurr->_address, vasmCurr->_internalLabel});
Compiler::adjustDiscardedLabels(vasmCurr->_internalLabel, vasmCurr->_address);
}
vasmCurr->_internalLabel = nextPClabel;
}
}
// Existing label at the PAGE JUMP address, so use it
else
{
// Update CALLI page jump label
if(Compiler::getCodeRomType() >= Cpu::ROMv5a)
{
// Macro labels are underscored by default
vasmCurr->_code = (labelName[0] == '_') ? "CALLI" + std::string(OPCODE_TRUNC_SIZE - 4, ' ') + labelName : "CALLI" + std::string(OPCODE_TRUNC_SIZE - 4, ' ') + "_" + labelName;
}
// Update pre-CALLI page jump label
else
{
// Macro labels are underscored by default
Compiler::VasmLine* vasm = &itCode->_vasm[itVasm - 2 - itCode->_vasm.begin()]; // points to LDWI
vasm->_code = (labelName[0] == '_') ? "LDWI" + std::string(OPCODE_TRUNC_SIZE - 4, ' ') + labelName : "LDWI" + std::string(OPCODE_TRUNC_SIZE - 4, ' ') + "_" + labelName;
}
}
}
itVasm++;
}
itCode++;
}
if(print)
{
fprintf(stderr, "*******************************************************\n");
fprintf(stderr, "* Number of page jumps = %4d, RAM used = %5d bytes *\n", numThunks, totalThunkSize);
fprintf(stderr, "*******************************************************\n");
}
return true;
}
bool opcodeHasBranch(const std::string& opcode)
{
if(opcode == "BRA") return true;
if(opcode == "BEQ") return true;
if(opcode == "BNE") return true;
if(opcode == "BGE") return true;
if(opcode == "BLE") return true;
if(opcode == "BGT") return true;
if(opcode == "BLT") return true;
if(opcode == "%ForNextInc") return true;
if(opcode == "%ForNextDec") return true;
if(opcode == "%ForNextDecZero") return true;
if(opcode == "%ForNextAdd") return true;
if(opcode == "%ForNextSub") return true;
if(opcode == "%ForNextVarAdd") return true;
if(opcode == "%ForNextVarSub") return true;
return false;
}
bool checkBranchLabels(void)
{
for(int i=0; i<int(Compiler::getCodeLines().size()); i++)
{
// Line number taking into account modules
int codeLineStart = Compiler::getCodeLineStart(i);
for(int j=0; j<int(Compiler::getCodeLines()[i]._vasm.size()); j++)
{
uint16_t opcAddr = Compiler::getCodeLines()[i]._vasm[j]._address;
std::string opcode = Compiler::getCodeLines()[i]._vasm[j]._opcode;
const std::string& code = Compiler::getCodeLines()[i]._vasm[j]._code;
const std::string& basic = Compiler::getCodeLines()[i]._text;
Expression::stripWhitespace(opcode);
if(opcodeHasBranch(opcode))
{
std::vector<std::string> tokens = Expression::tokenise(code, ' ', false);
if(tokens.size() < 2) continue;
// Normal branch
std::string operand;
if(tokens.size() == 2)
{
Expression::stripWhitespace(tokens[1]);
operand = tokens[1];
}
// Branch embedded in a FOR NEXT macro
else if(tokens.size() > 2)
{
Expression::stripWhitespace(tokens[2]);
operand = tokens[2];
}
// Remove underscores from BASIC labels for matching
if(operand.size() > 1 && operand[0] == '_') operand.erase(0, 1);
// Is operand a label?
int labelIndex = Compiler::findLabel(operand);
if(labelIndex >= 0)
{
uint16_t labAddr = Compiler::getLabels()[labelIndex]._address;
if(HI_MASK(opcAddr) != HI_MASK(labAddr))
{
fprintf(stderr, "\nValidater::checkBranchLabels() : *** Error ***, %s is branching from 0x%04x to 0x%04x, for '%s' on line %d\n\n", opcode.c_str(), opcAddr, labAddr, basic.c_str(), codeLineStart);
return false;
}
}
// Check internal label
else
{
// Internal labels always have underscores, so put it back
operand.insert(0, 1, '_');
labelIndex = Compiler::findInternalLabel(operand);
if(labelIndex >= 0)
{
uint16_t labAddr = Compiler::getInternalLabels()[labelIndex]._address;
if(HI_MASK(opcAddr) != HI_MASK(labAddr))
{
fprintf(stderr, "\nValidater::checkBranchLabels() : *** Error ***, %s is branching from 0x%04x to 0x%04x, for '%s' on line %d\n\n", opcode.c_str(), opcAddr, labAddr, basic.c_str(), codeLineStart);
return false;
}
}
}
}
}
}
return true;
}
bool checkStatementBlocks(void)
{
bool success = true;
// Check FOR NEXT blocks
while(!Compiler::getForNextDataStack().empty())
{
success = false;
Compiler::ForNextData forNextData = Compiler::getForNextDataStack().top();
int codeLineIndex = forNextData._codeLineIndex;
const std::string& code = Compiler::getCodeLines()[codeLineIndex]._code;
fprintf(stderr, "Validater::checkStatementBlocks() : Syntax error, missing NEXT statement, for '%s' on line %d\n", code.c_str(), codeLineIndex);
Compiler::getForNextDataStack().pop();
}
// Check ELSE ELSEIF blocks
while(!Compiler::getElseIfDataStack().empty())
{
success = false;
Compiler::ElseIfData elseIfData = Compiler::getElseIfDataStack().top();
int codeLineIndex = elseIfData._codeLineIndex;
const std::string& code = Compiler::getCodeLines()[codeLineIndex]._code;
fprintf(stderr, "Validater::checkStatementBlocks() : Syntax error, missing ELSE or ELSEIF statement, for '%s' on line %d\n", code.c_str(), codeLineIndex);
Compiler::getElseIfDataStack().pop();
}
// Check WHILE WEND blocks
while(!Compiler::getWhileWendDataStack().empty())
{
success = false;
Compiler::WhileWendData whileWendData = Compiler::getWhileWendDataStack().top();
int codeLineIndex = whileWendData._codeLineIndex;
const std::string& code = Compiler::getCodeLines()[codeLineIndex]._code;
fprintf(stderr, "Validater::checkStatementBlocks() : Syntax error, missing WEND statement, for '%s' on line %d\n", code.c_str(), codeLineIndex);
Compiler::getWhileWendDataStack().pop();
}
// Check DO UNTIL blocks
while(!Compiler::getRepeatUntilDataStack().empty())
{
success = false;
Compiler::RepeatUntilData repeatUntilData = Compiler::getRepeatUntilDataStack().top();
int codeLineIndex = repeatUntilData._codeLineIndex;
const std::string& code = Compiler::getCodeLines()[codeLineIndex]._code;
fprintf(stderr, "Validater::checkStatementBlocks() : Syntax error, missing UNTIL statement, for '%s' on line %d\n", code.c_str(), codeLineIndex);
Compiler::getRepeatUntilDataStack().pop();
}
return success;
}
bool checkCallProcFuncData(void)
{
bool success = true;
// Check PROC's corresponding CALL's
for(auto it=Compiler::getCallDataMap().begin(); it!=Compiler::getCallDataMap().end(); ++it)
{
int numParams = it->second._numParams;
int codeLineIndex = it->second._codeLineIndex;
const std::string& procName = it->second._name;
const std::string& code = Compiler::getCodeLines()[codeLineIndex]._code;
if(Compiler::getProcDataMap().find(procName) == Compiler::getProcDataMap().end())
{
fprintf(stderr, "Validator::checkCallProcFuncData() : Syntax error, 'CALL <NAME>' cannot find a corresponding 'PROC <NAME>', in '%s' on line %d\n", code.c_str(), codeLineIndex);
success = false;
continue;
}
if(Compiler::getProcDataMap()[procName]._numParams != numParams)
{
fprintf(stderr, "Validator::checkCallProcFuncData() : Syntax error, 'CALL <NAME>' has incorrect number of parameters compared to 'PROC <NAME>', in '%s' on line %d\n", code.c_str(), codeLineIndex);
success = false;
continue;
}
}
return success;
}
bool checkRuntimeVersion(void)
{
int16_t runtimeVersion = Assembler::getRuntimeVersion();
if(runtimeVersion != RUNTIME_VERSION)
{
fprintf(stderr, "\n*************************************************************************************************\n");
fprintf(stderr, "* Expected runtime version %04d : Found runtime version %04d\n", RUNTIME_VERSION, runtimeVersion);
fprintf(stderr, "*************************************************************************************************\n\n");
return false;
}
return true;
}
}