new routine binning tool - modified dynamic knapsack solving algorithm

This commit is contained in:
FeralChild64 committed 2019-09-08 22:17:49 +02:00
1 parent 5c27e8fbac
commit b9e3cb89bf
14 files changed
+1084 -101

No files matched your search

-3
View File
@@ -1,9 +1,6 @@
# Compilation related files
temp.s
temp.sym
assembler/KickAss*
build/*
+100 -97
View File
@@ -1,27 +1,41 @@
# Source files
SRC_COMMON = $(wildcard c64/aliases/*.s)
SRC_BASIC_COMMON = $(SRC_COMMON) $(wildcard c64/basic/*.s)
SRC_KERNAL_COMMON = $(SRC_COMMON) $(wildcard c64/kernal/*.s c64/kernal/jumptable/*.s c64/kernal/iec/*.s c64/kernal/interrupts/*.s)
SRCDIR_COMMON = c64/aliases
SRCDIR_BASIC_COMMON = $(SRCDIR_COMMON) c64/basic
SRCDIR_KERNAL_COMMON = $(SRCDIR_COMMON) c64/kernal c64/kernal/jumptable c64/kernal/iec c64/kernal/interrupts
SRC_BASIC_generic = $(SRC_BASIC_COMMON) $(wildcard c64/basic/,target_generic/*.s)
SRC_BASIC_mega65 = $(SRC_BASIC_COMMON) $(wildcard c64/basic/,target_mega65/*.s)
SRC_BASIC_ultimate64 = $(SRC_BASIC_COMMON) $(wildcard c64/basic/,target_ultimate64/*.s)
SRCDIR_BASIC_generic = $(SRCDIR_BASIC_COMMON) c64/basic/,target_generic
SRCDIR_BASIC_mega65 = $(SRCDIR_BASIC_COMMON) c64/basic/,target_mega65
SRCDIR_BASIC_ultimate64 = $(SRCDIR_BASIC_COMMON) c64/basic/,target_ultimate64
SRC_KERNAL_generic = $(SRC_KERNAL_COMMON) $(wildcard c64/kernal/,target_generic/*.s)
SRC_KERNAL_mega65 = $(SRC_KERNAL_COMMON) $(wildcard c64/kernal/,target_mega65/*.s)
SRC_KERNAL_ultimate64 = $(SRC_KERNAL_COMMON) $(wildcard c64/kernal/,target_ultimate64/*.s)
SRCDIR_KERNAL_generic = $(SRCDIR_KERNAL_COMMON) c64/kernal/,target_generic
SRCDIR_KERNAL_mega65 = $(SRCDIR_KERNAL_COMMON) c64/kernal/,target_mega65
SRCDIR_KERNAL_ultimate64 = $(SRCDIR_KERNAL_COMMON) c64/kernal/,target_ultimate64
SRC_TOOLS = $(wildcard src/tools/*.c,src/tools/*.cc)
# List of tools
TOOL_COLLECT_DATA = build/tools/collect_data
TOOL_COMPRESS_TEXT = build/tools/compress_text
TOOL_PNGPREPARE = build/tools/pngprepare
TOOL_PREPROCESS = build/tools/preprocess
TOOL_SIMILARITY = build/tools/similarity
TOOL_COLLECT_DATA = build/tools/collect_data
TOOL_COMPRESS_TEXT = build/tools/compress_text
TOOL_PNGPREPARE = build/tools/pngprepare
TOOL_BUILD_SEGMENT = build/tools/build_segment
TOOL_SIMILARITY = build/tools/similarity
TOOL_ASSEMBLER = assembler/KickAss.jar
TOOLS_LIST = $(TOOL_COLLECT_DATA) $(TOOL_COMPRESS_TEXT) $(TOOL_PNGPREPARE) $(TOOL_PREPROCESS) $(TOOL_SIMILARITY)
TOOLS_LIST = $(pathsubst src/tools/%,build/tools/%,$(basename $(SRC_TOOLS)))
# List of targets
STD_TARGET_LIST = build/kernal_generic.rom build/basic_generic.rom \
build/kernal_mega65.rom build/basic_mega65.rom \
build/kernal_ultimate64.rom build/basic_ultimate64.rom \
build/chargen.rom
EXT_TARGET_LIST = build/newc65.rom
REL_TARGET_LIST = $(pathsubst build/%,bin/%, $(STD_TARGET_LIST))
# GIT commit
@@ -31,126 +45,115 @@ GIT_COMMIT:= $(shell git log -1 --pretty='%h' | tr '[:lower:]' '[:upper:]')
.PHONY: all clean updatebin
all: build/chargen build/newkern_generic build/newbasic_generic build/newc65 build/newkern_ultimate64 build/newbasic_ultimate64
all: $(STD_TARGET_LIST) $(EXT_TARGET_LIST)
clean:
@rm -rf build
@rm -f temp.s
@rm -f temp.sym
updatebin: build/chargen build/newbasic_generic build/newkern_generic build/newbasic_mega65 build/newkern_mega65 build/newbasic_ultimate64 build/newkern_ultimate64
cp build/chargen bin/chargen.rom
cp build/newbasic_generic bin/basic_generic.rom
cp build/newkern_generic bin/kernal_generic.rom
cp build/newbasic_mega65 bin/basic_mega65.rom
cp build/newkern_mega65 bin/kernal_mega65.rom
cp build/newbasic_ultimate64 bin/basic_ultimate64.rom
cp build/newkern_ultimate64 bin/kernal_ultimate64.rom
updatebin: $(STD_TARGET_LIST)
cp build/chargen.rom bin/chargen.rom
cp build/basic_generic.rom bin/basic_generic.rom
cp build/kernal_generic.rom bin/kernal_generic.rom
cp build/basic_mega65.rom bin/basic_mega65.rom
cp build/kernal_mega65.rom bin/kernal_mega65.rom
cp build/basic_ultimate64.rom bin/basic_ultimate64.rom
cp build/kernal_ultimate64.rom bin/kernal_ultimate64.rom
# Rules - tools
$(TOOL_COLLECT_DATA): src/collect_data.c Makefile
$(TOOL_PNGPREPARE): src/pngprepare.c Makefile
@mkdir -p build/tools
gcc -Wall -o $(TOOL_COLLECT_DATA) src/collect_data.c
$(CC) -O2 -Wall -I/usr/local/include -L/usr/local/lib -o $@ $< -lpng
$(TOOL_COMPRESS_TEXT): src/compress_text.c Makefile
@mkdir -p build/tools
gcc -g -Wall -o $(TOOL_COMPRESS_TEXT) src/compress_text.c -lm
$(CC) -O2 -Wall -I/usr/local/include -L/usr/local/lib -o $@ $< -lm
$(TOOL_PNGPREPARE): src/pngprepare.c Makefile
build/tools/%: src/%.c Makefile
@mkdir -p build/tools
$(CC) $(COPT) -I/usr/local/include -L/usr/local/lib -o $(TOOL_PNGPREPARE) src/pngprepare.c -lpng
$(CC) -O2 -Wall -o $@ $<
$(TOOL_PREPROCESS): src/preprocess.c Makefile
build/tools/%: src/%.cc Makefile
@mkdir -p build/tools
gcc -g -Wall -o $(TOOL_PREPROCESS) src/preprocess.c
$(TOOL_SIMILARITY): src/similarity.c Makefile
@mkdir -p build/tools
gcc -g -Wall -o $(TOOL_SIMILARITY) src/similarity.c
$(CXX) -O2 -Wall -o $@ $<
# Rules - CHARGEN
build/chargen: $(TOOL_PNGPREPARE) assets/8x8font.png
$(TOOL_PNGPREPARE) charrom assets/8x8font.png build/chargen
build/chargen.rom: $(TOOL_PNGPREPARE) assets/8x8font.png
$(TOOL_PNGPREPARE) charrom assets/8x8font.png build/chargen.rom
# Dependencies
# Dependencies - BASIC and KERNAL
build/basic_generic/OUT.BIN: $(TOOLS_LIST) $(SRC_BASIC_generic) build/basic/packed_messages.s
build/kernal_generic/OUT.BIN: $(TOOLS_LIST) $(SRC_KERNAL_generic)
build/newrom_generic: build/basic_generic/OUT.BIN build/kernal_generic/OUT.BIN
build/newkern_generic: build/newrom_generic Makefile
build/newbasic_generic: build/newrom_generic Makefile
build/target_generic/OUTB.BIN: $(TOOL_ASSEMBLER) $(TOOL_BUILD_SEGMENT) $(SRCDIR_BASIC_generic) $(foreach dir,$(SRCDIR_BASIC_generic),$(wildcard $(dir)/*.s)) build/,generated/gen_packed_messages.s
build/target_mega65/OUTB.BIN: $(TOOL_ASSEMBLER) $(TOOL_BUILD_SEGMENT) $(SRCDIR_BASIC_mega65) $(foreach dir,$(SRCDIR_BASIC_mega65),$(wildcard $(dir)/*.s)) build/,generated/gen_packed_messages.s
build/target_ultimate64/OUTB.BIN: $(TOOL_ASSEMBLER) $(TOOL_BUILD_SEGMENT) $(SRCDIR_BASIC_ultimate64) $(foreach dir,$(SRCDIR_BASIC_ultimate64),$(wildcard $(dir)/*.s)) build/,generated/gen_packed_messages.s
build/basic_mega65/OUT.BIN: $(TOOLS_LIST) $(SRC_BASIC_mega65) build/basic/packed_messages.s
build/kernal_mega65/OUT.BIN: $(TOOLS_LIST) $(SRC_KERNAL_mega65)
build/newrom_mega65: build/basic_mega65/OUT.BIN build/kernal_mega65/OUT.BIN
build/newkern_mega65: build/newrom_mega65 Makefile
build/newbasic_mega65: build/newrom_mega65 Makefile
build/target_generic/OUTK.BIN: $(TOOL_ASSEMBLER) $(TOOL_BUILD_SEGMENT) $(SRCDIR_KERNAL_generic) $(foreach dir,$(SRCDIR_KERNAL_generic),$(wildcard $(dir)/*.s))
build/target_mega65/OUTK.BIN: $(TOOL_ASSEMBLER) $(TOOL_BUILD_SEGMENT) $(SRCDIR_KERNAL_mega65) $(foreach dir,$(SRCDIR_KERNAL_mega65),$(wildcard $(dir)/*.s))
build/target_ultimate64/OUTK.BIN: $(TOOL_ASSEMBLER) $(TOOL_BUILD_SEGMENT) $(SRCDIR_KERNAL_ultimate64) $(foreach dir,$(SRCDIR_KERNAL_ultimate64),$(wildcard $(dir)/*.s))
build/basic_ultimate64/OUT.BIN: $(TOOLS_LIST) $(SRC_BASIC_ultimate64) build/basic/packed_messages.s
build/kernal_ultimate64/OUT.BIN: $(TOOLS_LIST) $(SRC_KERNAL_ultimate64)
build/newrom_ultimate64: build/basic_ultimate64/OUT.BIN build/kernal_ultimate64/OUT.BIN
build/newkern_ultimate64: build/newrom_ultimate64 Makefile
build/newbasic_ultimate64: build/newrom_ultimate64 Makefile
build/target_generic/newrom: build/target_generic/OUTB.BIN build/target_generic/OUTK.BIN
build/target_mega65/newrom: build/target_mega65/OUTB.BIN build/target_mega65/OUTK.BIN
build/target_ultimate64/newrom: build/target_ultimate64/OUTB.BIN build/target_ultimate64/OUTK.BIN
build/kernal_generic.rom: build/target_generic/newrom
build/kernal_mega65.rom: build/target_mega65/newrom
build/kernal_ultimate64.rom: build/target_ultimate64/newrom
build/basic_generic.rom: build/target_generic/newrom
build/basic_mega65.rom: build/target_mega65/newrom
build/basic_ultimate64.rom: build/target_ultimate64/newrom
# Rules - BASIC and KERNAL
build/basic/packed_messages.s: $(TOOL_COMPRESS_TEXT)
@mkdir -p build/basic
$(TOOL_COMPRESS_TEXT) > build/basic/packed_messages.s
build/,generated/gen_packed_messages.s: $(TOOL_COMPRESS_TEXT)
@mkdir -p build/,generated
$(TOOL_COMPRESS_TEXT) > build/,generated/gen_packed_messages.s
.PRECIOUS: build/basic_%/OUT.BIN
build/basic_%/OUT.BIN: $(TOOLS_LIST) $(SRC_BASIC_$*) build/basic/packed_messages.s
@mkdir -p build/basic_$*
@rm -f build/basic_$*/*
@for SRC in $(SRC_BASIC_$*); do \
ln -s ../../$$SRC build/basic_$*/$$(basename $$SRC); \
done
@ln -s ../../build/basic/packed_messages.s build/basic_$*/packed_messages.s
$(TOOL_PREPROCESS) -d build/basic_$* -l a000 -h e4d2
.PRECIOUS: build/target_%/OUTB.BIN
build/target_%/OUTB.BIN:
@mkdir -p build/target_$*
@rm -f $@* build/target_$*/BASIC*
@$(TOOL_BUILD_SEGMENT) -s BASIC -i BASIC-$* -o OUTB.BIN -t build/target_$* -l a000 -h e4d2 $(SRCDIR_BASIC_$*) build/,generated
.PRECIOUS: build/kernal_%/OUT.BIN
build/kernal_%/OUT.BIN: $(TOOLS_LIST) $(SRC_KERNAL_$*)
@mkdir -p build/kernal_$*
@rm -f build/kernal_$*/*
@for SRC in $(SRC_KERNAL_$*); do \
ln -s ../../$$SRC build/kernal_$*/$$(basename $$SRC); \
done
$(TOOL_PREPROCESS) -d build/kernal_$* -l e4d3 -h ffff
.PRECIOUS: build/target_%/OUTK.BIN
build/target_%/OUTK.BIN:
@mkdir -p build/target_$*
@rm -f $@* build/target_$*/KERNAL*
@$(TOOL_BUILD_SEGMENT) -s KERNAL -i KERNAL-$* -o OUTK.BIN -t build/target_$* -l e4d3 -h ffff $(SRCDIR_KERNAL_$*)
.PRECIOUS: build/newrom_%
build/newrom_%: build/basic_%/OUT.BIN build/kernal_%/OUT.BIN
cat build/basic_$*/OUT.BIN build/kernal_$*/OUT.BIN > build/newrom_$*
.PRECIOUS: build/target_%/newrom
build/target_%/newrom:
cat build/target_$*/OUTB.BIN build/target_$*/OUTK.BIN > $@
.PRECIOUS: build/newkern_%
build/newkern_%: build/newrom_% Makefile
dd if=build/newrom_$* bs=8192 count=1 skip=2 of=build/newkern_$*
.PRECIOUS: build/kernal_%.rom
build/kernal_%.rom:
dd if=build/target_$*/newrom bs=8192 count=1 skip=2 of=$@
.PRECIOUS: build/newbasic_%
build/newbasic_%: build/newrom_% Makefile
dd if=build/newrom_$* bs=8192 count=1 skip=0 of=build/newbasic_$*
.PRECIOUS: build/basic_%.rom
build/basic_%.rom:
dd if=build/target_$*/newrom bs=8192 count=1 skip=0 of=$@
# Rules - platform 'Mega 65' specific
build/newc65: build/newkern_mega65 build/newbasic_mega65 build/chargen Makefile
dd if=/dev/zero bs=4096 count=10 of=build/newc65
cat build/newbasic_mega65 build/chargen build/chargen build/newkern_mega65 >> build/newc65
dd if=/dev/zero bs=65536 count=1 of=build/basic/padding
cat build/basic/padding >> build/newc65
build/newc65.rom: build/kernal_mega65.rom build/basic_mega65.rom build/chargen.rom Makefile
dd if=/dev/zero bs=4096 count=10 of=build/newc65.rom
cat build/basic_mega65.rom build/chargen.rom build/chargen.rom build/kernal_mega65.rom >> build/newc65.rom
dd if=/dev/zero bs=65536 count=1 of=build/padding
cat build/padding >> build/newc65.rom
rm -f build/padding
# Rules - tests
test: build/newkern_generic build/newbasic_generic
x64 -kernal build/newkern_generic -basic build/newbasic_generic -8 empty.d64
test: build/kernal_generic.rom build/basic_generic.rom
x64 -kernal build/kernal_generic.rom -basic build/basic_generic.rom -8 empty.d64
testremote: build/newkern_generic build/newbasic_generic
x64 -kernal build/newkern_generic -basic build/newbasic_generic -remotemonitor
testremote: build/kernal_generic.rom build/basic_generic.rom
x64 -kernal build/kernal_generic.rom -basic build/basic_generic.rom -remotemonitor
testm65: build/newc65
m65 -b ../mega65-core/bin/mega65r1.bit -k ../mega65-core/bin/KICKUP.M65 -R build/newc65 -4
m65 -b ../mega65-core/bin/mega65r1.bit -k ../mega65-core/bin/KICKUP.M65 -R build/newc65.rom -4
testsimilarity: build/newrom_generic $(TOOL_SIMILARITY)
$(TOOL_SIMILARITY) kernal build/newrom_generic
$(TOOL_SIMILARITY) basic build/newrom_generic
testsimilarity: build/target_generic/newrom $(TOOL_SIMILARITY)
$(TOOL_SIMILARITY) kernal build/target_generic/newrom
$(TOOL_SIMILARITY) basic build/target_generic/newrom
View File
File renamed without changes.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+983
View File
@@ -0,0 +1,983 @@
//
// Utility to build a segment from the provided assembler
// source files, placing routines where they need to live
//
#include <dirent.h>
#include <unistd.h>
#include <sys/types.h>
#include <algorithm>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <string>
#include <list>
#include <map>
#include <set>
#include <vector>
#if defined(WIN32) || defined(_WIN32)
#define DIR_SEPARATOR "\\"
#else
#define DIR_SEPARATOR "/"
#endif
const std::string ASM_CMD = "java -jar assembler/KickAss.jar ";
const std::string LAB_OUT_START = "__routine_START_";
const std::string LAB_OUT_END = "__routine_END_";
const std::string LAB_IN_START = ".label __routine_START_";
const std::string LAB_IN_END = ".label __routine_END_";
const std::string BANNER_LINE = "//-------------------------------------------------------------------------------------------";
//
// Command line settings
//
std::string CMD_outFile = "OUT.BIN";
std::string CMD_tmpDir = "./out";
std::string CMD_segName = "MAIN";
std::string CMD_segInfo = "(unnamed)";
int CMD_loAddress = 0xC000;
int CMD_hiAddress = 0xCFFF;
std::list<std::string> CMD_dirList;
//
// Common helper functions
//
void bailOut()
{
exit(-1);
}
void bailOut(const std::string &message)
{
std::cout << std::endl << "FATAL: " << message << std::endl << std::endl;
exit(-1);
}
void printUsage()
{
std::cout << std::endl <<
"usage: build_segment [-o <out file>] [-d <out dir>] [-t <temp dir>]" << std::endl <<
" [-l <start/low address>] [-h <end/high address>]" << std::endl <<
" [-s <segment name>] [-i <segment display info>] <input dir list>" <<
std::endl << std::endl;
}
void printBannerLineTop()
{
std::cout << std::endl << std::endl << std::endl << BANNER_LINE << std::endl;
}
void printBannerLineBottom()
{
std::cout << BANNER_LINE << std::endl << std::endl;
}
void printBannerCollectAnalyse()
{
printBannerLineTop();
std::cout << "// Segment '" << CMD_segInfo << "' - collecting and analysing routines" << std::endl;
printBannerLineBottom();
}
void printBannerBinCompile()
{
printBannerLineTop();
std::cout << "// Segment '" << CMD_segInfo << "' - binning and compiling the assembly" << std::endl;
printBannerLineBottom();
}
//
// Class definitions
//
class SourceFile
{
public:
SourceFile(const std::string &fileName, const std::string &dirName);
std::string fileName;
std::string dirName;
bool floating;
std::vector<char> content;
std::string label;
int startAddr; // for fixed (non-floating) routines only
int codeLength; // for both fixed and floating routines
int testAddrStart; // start address during test run
int testAddrEnd; // end address during test run
};
class BinningProblem
{
public:
BinningProblem() {};
BinningProblem(int loAddress, int hiAddress);
bool isSolved() const;
void addToProblem(SourceFile *routine);
void fillGap(int gapAddress, const std::list<SourceFile *> &routines);
void performObviousSteps();
void removeUselessGaps();
void sortFloatingRoutinesBySize();
std::map<int, SourceFile *> fixedRoutines; // routines with location already fixed
std::map<int, int> gaps; // gaps by address
std::vector<SourceFile *> floatingRoutines; // routines not allocated to any address yet; always keep them sorted!
};
class Solver
{
public:
Solver(BinningProblem &problem) : problem(problem) {}
void run();
int selectGapToFill();
void findPartialSolution(int gapSize, std::list<SourceFile *> &partialSolution);
private:
BinningProblem &problem;
};
//
// Global variables
//
std::list<SourceFile> GLOBAL_sourceFiles;
std::list<SourceFile> GLOBAL_sourceFiles_noCode;
size_t GLOBAL_maxFileNameLen = 0;
size_t GLOBAL_totalRoutinesSize = 0;
BinningProblem GLOBAL_binningProblem;
//
// Top-level functions
//
void parseCommandLine(int argc, char **argv)
{
int opt;
// Retrieve command line options
while ((opt = getopt(argc, argv, "o:d:t:l:h:s:i:")) != -1)
{
switch(opt)
{
case 'o': CMD_outFile = optarg; break;
case 't': CMD_tmpDir = optarg; break;
case 's': CMD_segName = optarg; break;
case 'i': CMD_segInfo = optarg; break;
case 'l': CMD_loAddress = strtol(optarg, nullptr ,16); break;
case 'h': CMD_hiAddress = strtol(optarg, nullptr ,16); break;
default: printUsage(); bailOut();
}
}
// Retrieve directory list
for (int idx = optind; idx < argc; idx++)
{
CMD_dirList.push_back(argv[idx]);
}
if (CMD_dirList.empty()) { printUsage(); bailOut("empty directory list"); }
}
void readSourceFiles()
{
for (const auto &dirName : CMD_dirList)
{
DIR *dirHandle = opendir(dirName.c_str());
if (!dirHandle) bailOut(std::string("unable to open directory '") + dirName + "'");
struct dirent *dirEntry;
while ((dirEntry = readdir(dirHandle)) != nullptr)
{
const std::string fileName = dirEntry->d_name;
// Filter-out files which are not assembler files, temporary, etc.
if (fileName.length() < 3) continue;
if (fileName.front() == '#') continue;
if (fileName.front() == '~') continue;
if (fileName.substr(fileName.length() - 2) != ".s") continue;
// Add file to the global list
GLOBAL_sourceFiles.push_back(SourceFile(fileName, dirName));
GLOBAL_maxFileNameLen = std::max(GLOBAL_maxFileNameLen, fileName.length());
}
closedir(dirHandle);
}
if (GLOBAL_sourceFiles.empty()) bailOut("no source files found");
// Sort the file list by name, to provide deterministic results
auto compare = [](const SourceFile &a, const SourceFile &b) -> bool { return a.fileName.compare(b.fileName) < 0; };
GLOBAL_sourceFiles.sort(compare);
}
void checkInputFileLabels()
{
std::set<std::string> usedLabels;
for (const auto &sourceFile : GLOBAL_sourceFiles)
{
if (usedLabels.count(sourceFile.label) != 0)
{
bailOut(std::string("input file '") + sourceFile.fileName + "' has a name too similar to another one");
}
usedLabels.insert(sourceFile.label);
}
}
void calcRoutineSizes()
{
const std::string nameBase = CMD_tmpDir + DIR_SEPARATOR + CMD_segName + "_sizetest";
const std::string outFileName = nameBase + ".s";
const std::string symFileName = nameBase + ".sym";
// Remove old files
unlink(outFileName.c_str());
unlink(symFileName.c_str());
// Write test file to determine routine sizes
std::ofstream outFile(outFileName, std::fstream::out | std::fstream::trunc);
if (!outFile.good()) bailOut(std::string("can't open temporary file '") + outFileName + "'");
// Start at $100, so that no local data gets accesses using ZP addressing modes
// during this pass, which would otherwise upset things later
outFile << std::endl << ".segment " << CMD_segName << " [start=$100, min=$100, max=$FFFF]" << std::endl;
for (const auto &sourceFile : GLOBAL_sourceFiles)
{
outFile << std::endl << std::endl << std::endl << std::endl;
outFile << "// Source file: " << sourceFile.fileName << std::endl << std::endl;
outFile << LAB_OUT_START << sourceFile.label << ":" << std::endl << std::endl;
outFile << std::string(sourceFile.content.begin(), sourceFile.content.end());
outFile << std::endl << std::endl;
outFile << LAB_OUT_END << sourceFile.label << ":" << std::endl;
}
if (!outFile.good()) bailOut(std::string("error writing temporary file '") + outFileName + "'");
outFile.close();
// All written - now launch the assembler
const std::string cmd = ASM_CMD + outFileName + " -symbolfile -o /dev/null";
std::cout << std::flush;
if (0 != system(cmd.c_str()))
{
bailOut("assembler running failed");
}
// Read addresses
std::ifstream symFile;
symFile.open(symFileName);
if (!symFile.good()) bailOut(std::string("unable to open results file '") + symFileName + "'");
std::string line;
while (std::getline(symFile, line))
{
bool startLabel;
if (0 == line.compare(0, LAB_IN_START.size(), LAB_IN_START))
{
line.erase(0, LAB_IN_START.length());
startLabel = true;
}
else if (0 == line.compare(0, LAB_IN_END.size(), LAB_IN_END))
{
line.erase(0, LAB_IN_END.length());
startLabel = false;
}
else continue;
// Decode address, write it into the object
auto eqPos = line.rfind('=');
auto address = strtol(line.substr(eqPos + 2).c_str(), nullptr ,16);
std::string refLabel = line.substr(0, eqPos);
for (auto &sourceFile : GLOBAL_sourceFiles)
{
if (refLabel.compare(sourceFile.label) != 0) continue;
if (startLabel)
{
sourceFile.testAddrStart = address;
}
else
{
sourceFile.testAddrEnd = address;
}
}
}
symFile.close();
// Calculate size of each and every routine
for (auto &sourceFile : GLOBAL_sourceFiles)
{
if (sourceFile.testAddrStart <= 0 || sourceFile.testAddrEnd <= 0 ||
sourceFile.testAddrStart > sourceFile.testAddrEnd)
{
bailOut(std::string("unable to determine code length in '") + sourceFile.fileName + "'");
}
sourceFile.codeLength = sourceFile.testAddrEnd - sourceFile.testAddrStart;
GLOBAL_totalRoutinesSize += sourceFile.codeLength;
}
// Check whether total code size is sane
if (0 == GLOBAL_totalRoutinesSize) bailOut("total code size is 0");
if (signed(GLOBAL_totalRoutinesSize) > CMD_hiAddress - CMD_loAddress)
{
bailOut(std::string("total code size is ") + std::to_string(GLOBAL_totalRoutinesSize) + ", too much for this segment!");
}
// Sort the file list by code size, starting from the smallest
auto compare = [](const SourceFile &a, const SourceFile &b) -> bool { return a.codeLength < b.codeLength; };
GLOBAL_sourceFiles.sort(compare);
// Move zero-sized element to a separate list
while (GLOBAL_sourceFiles.front().codeLength == 0)
{
GLOBAL_sourceFiles_noCode.push_back(GLOBAL_sourceFiles.front());
GLOBAL_sourceFiles.pop_front();
}
}
void prepareBinningProblem()
{
// Prepare the log file
const std::string logFileName = CMD_tmpDir + DIR_SEPARATOR + CMD_segName + "_binproblem.log";
unlink(logFileName.c_str());
std::ofstream logFile(logFileName, std::fstream::out | std::fstream::trunc);
std::string logLine;
// Print out code length information
for (const auto &sourceFile : GLOBAL_sourceFiles)
{
std::string spacing;
spacing.resize(GLOBAL_maxFileNameLen + 4 - sourceFile.fileName.length(), ' ');
std::string floating = sourceFile.floating ? "(floating) " : " ";
logLine = "file: " + floating + sourceFile.fileName + spacing + "size: " + std::to_string(sourceFile.codeLength);
logFile << logLine << std::endl;
std::cout << logLine << std::endl;
}
// Create the binning problem
GLOBAL_binningProblem = BinningProblem(CMD_loAddress, CMD_hiAddress);
for (auto &sourceFile : GLOBAL_sourceFiles)
{
GLOBAL_binningProblem.addToProblem(&sourceFile);
}
// Print out some more statistics
logLine = "free space (after floating routines are placed): " +
std::to_string(CMD_hiAddress - CMD_loAddress - GLOBAL_totalRoutinesSize) ;
logFile << std::endl << logLine << std::endl;
std::cout << std::endl << logLine << std::endl;
logLine = "number of floating routines: " +
std::to_string(GLOBAL_binningProblem.floatingRoutines.size());
logFile << logLine << std::endl;
std::cout << logLine << std::endl;
logLine = "number of gaps for the floating routines: " +
std::to_string(GLOBAL_binningProblem.gaps.size());
logFile << logLine << std::endl << std::endl;
std::cout << logLine << std::endl << std::endl;
// Print out the available gaps
logLine = "gap address: $";
for (auto& gap : GLOBAL_binningProblem.gaps)
{
std::string gapSize = std::string(" size: ") + std::to_string(gap.second);
logFile << logLine << std::uppercase << std::hex << gap.first << gapSize << std::endl;
std::cout << logLine << std::uppercase << std::hex << gap.first << gapSize << std::endl;
}
std::cout << std::endl;
// Close the log file
if (!logFile.good()) bailOut(std::string("error writing log file '") + logFileName + "'");
logFile.close();
}
void solveBinningProblem()
{
std::cout << "trying to solve the routine binning problem" << std::endl << std::endl;
// Do some routine actions on the binning problem object
Solver solver(GLOBAL_binningProblem);
solver.run();
if (!GLOBAL_binningProblem.isSolved())
{
bailOut("unable to solve the routine binning problem");
}
std::cout << std::endl;
}
void compileSegment()
{
// First combine everything into one assembler file
const std::string outFileName = CMD_tmpDir + DIR_SEPARATOR + CMD_segName + "_combined.s";
unlink(outFileName.c_str());
std::ofstream outFile(outFileName, std::fstream::out | std::fstream::trunc);
if (!outFile.good()) bailOut(std::string("can't open temporary file '") + outFileName + "'");
// Write the header
outFile << std::endl << ".segment " << CMD_segName <<
" [start=$" << std::hex << CMD_loAddress <<
", min=$" << std::hex << CMD_loAddress <<
", max=$" << std::hex << CMD_hiAddress <<
", outBin=\"" << CMD_outFile << "\", fill]" <<
std::endl;
// Write files which only contain definitions (no routines)
for (const auto &sourceFile : GLOBAL_sourceFiles_noCode)
{
outFile << std::endl << std::endl << std::endl << std::endl;
outFile << "// Source file: " << sourceFile.fileName << std::endl << std::endl;
outFile << std::string(sourceFile.content.begin(), sourceFile.content.end());
outFile << std::endl;
}
// Write remaining files, these should be placed under their proper locations
for (const auto &routine : GLOBAL_binningProblem.fixedRoutines)
{
outFile << std::endl << std::endl << std::endl << std::endl;
outFile << "// Source file: " << routine.second->fileName << std::endl << std::endl;
outFile << "\t* = $" << std::hex << routine.first << std::endl << std::endl;
outFile << std::string(routine.second->content.begin(), routine.second->content.end());
outFile << std::endl;
}
if (!outFile.good()) bailOut(std::string("error writing temporary file '") + outFileName + "'");
outFile.close();
// All written - now launch the assembler
const std::string cmd = ASM_CMD + outFileName + " -symbolfile -o " + CMD_outFile;
std::cout << std::flush;
if (0 != system(cmd.c_str()))
{
bailOut("assembler running failed");
}
}
//
// Main function
//
int main(int argc, char **argv)
{
parseCommandLine(argc, argv);
printBannerCollectAnalyse();
readSourceFiles();
checkInputFileLabels();
calcRoutineSizes();
printBannerBinCompile();
prepareBinningProblem();
solveBinningProblem();
compileSegment();
return 0;
}
//
// Class 'SourceFile'
//
SourceFile::SourceFile(const std::string &fileName, const std::string &dirName) :
fileName(fileName),
dirName(dirName),
floating(false),
startAddr(-1),
codeLength(-1),
testAddrStart(-1),
testAddrEnd(-1)
{
const std::string fileNameWithPath = dirName + DIR_SEPARATOR + fileName;
std::cout << "reading file: " << fileNameWithPath << std::endl;
// Open the file
std::ifstream inFile;
inFile.open(fileNameWithPath);
if (!inFile.good()) bailOut("unable to open file");
// Read the content
inFile.seekg(0, inFile.end);
auto fileLength = inFile.tellg();
if (fileLength == 0) bailOut("file is empty");
content.resize(fileLength);
inFile.seekg(0);
inFile.read(&content[0], fileLength);
if (!inFile.good()) bailOut("error reading file content");
inFile.close();
// Determine if the file content is floating or fixed position one,
// retrieve start address
auto isHexDigit = [](char digit) -> bool
{
return (digit >= '0' && digit <= '9') ||
(digit >= 'a' && digit <= 'f') ||
(digit >= 'A' && digit <= 'F');;
};
floating = !(fileName.length() >= 6 && fileName[4] == '.' &&
isHexDigit(fileName[0]) &&
isHexDigit(fileName[1]) &&
isHexDigit(fileName[2]) &&
isHexDigit(fileName[3]));
if (!floating)
{
startAddr = strtol(fileName.substr(0, 4).c_str(), nullptr ,16);
}
// Generate KickAss compatible label from the file name
label = fileName.substr(0, fileName.length() - 2);
std::replace(label.begin(), label.end(), '.', '_');
std::replace(label.begin(), label.end(), ',', '_');
}
//
// Class 'BinningProblem'
//
BinningProblem::BinningProblem(int loAddress, int hiAddress)
{
if (hiAddress < loAddress || hiAddress < 0 || loAddress < 0 || hiAddress > 0xFFFF || loAddress > 0xFFFF)
{
bailOut("invalid lo/hi address");
}
// Create one large gap (initial) to represent the assembly
gaps[loAddress] = hiAddress - loAddress + 1;
}
bool BinningProblem::isSolved() const
{
return floatingRoutines.empty();
}
void BinningProblem::addToProblem(SourceFile *routine)
{
if (routine->floating)
{
floatingRoutines.push_back(routine);
}
else
{
// For the fixed-address routines, we have to find the matching place
bool gapFound = false;
for (auto &gap: gaps)
{
if (gap.first > routine->startAddr || gap.first + gap.second - 1 < routine->startAddr)
{
continue; // not a suitable gap
}
// We have to put the routine into the current gap
if (gap.first + gap.second < routine->startAddr + routine->codeLength)
{
bailOut(std::string("fixed address file '") + routine->fileName + "' won't fit in the available gap");
}
gapFound = true;
// Put the routine into the gap, possibly removing it or splitting into two
fixedRoutines[routine->startAddr] = routine;
// Calculate possible new gap after the routine
int newGapSize = (gap.first + gap.second) - (routine->startAddr + routine->codeLength);
int newGapStart = (newGapSize <= 0) ? -1 : routine->startAddr + routine->codeLength;
// Remove or shrink the current gap
if (gap.first == routine->startAddr)
{
gaps.erase(gap.first);
}
else
{
gap.second = routine->startAddr - gap.first;
}
// Add a new gap
if (newGapStart > 0) gaps[newGapStart] = newGapSize;
break; // iterator is not valid enymore, we have to stop the llop
}
if (!gapFound)
{
bailOut(std::string("start address of fixed address file '") + routine->fileName + "' already occupied");
}
}
}
void BinningProblem::fillGap(int gapAddress, const std::list<SourceFile *> &routines)
{
int offset = 0;
std::string spacing;
for (auto &routine : routines)
{
int targetAddr = gapAddress + offset;
spacing.resize(GLOBAL_maxFileNameLen + 4 - routine->fileName.length(), ' ');
std::cout << " $" << std::hex << targetAddr << std::dec << ": " <<
routine->fileName << spacing << "size: " << routine->codeLength << std::endl;
fixedRoutines[targetAddr] = routine;
offset += routine->codeLength;
if (offset > gaps[gapAddress]) bailOut(std::string("internal error line ") + std::to_string(__LINE__));
floatingRoutines.erase(std::remove(floatingRoutines.begin(), floatingRoutines.end(), routine), floatingRoutines.end());
}
// Get rid of the gap, it's useless now
if (offset == gaps[gapAddress])
{
std::cout << "filled to the last byte" << std::endl;
}
else if (!isSolved())
{
std::cout << "filled in - dropped bytes: " << gaps[gapAddress] << std::endl;
}
else
{
std::cout << "out of routines" << std::endl;
}
gaps.erase(gapAddress);
}
void BinningProblem::performObviousSteps()
{
// Get the size of the biggest routine; if there is just one gap which
// can handle it - put the roputine exactly there
std::string spacing;
bool repeat = true;
while (repeat && !floatingRoutines.empty() && !gaps.empty())
{
repeat = false;
int routineSize = floatingRoutines.back()->codeLength;
int gapAddress;
int matchingGaps = 0;
for (auto &gap : gaps)
{
if (gap.second >= routineSize)
{
matchingGaps++;
gapAddress = gap.first;
}
}
if (matchingGaps == 1)
{
// Routine can only be placed in this particular gap - do so
gaps[gapAddress] -= routineSize;
int targetAddr = gapAddress + gaps[gapAddress];
fixedRoutines[targetAddr] = floatingRoutines.back();
std::cout << "forced reducing gap $" << std::hex << gapAddress << std::dec << " to size " <<
gaps[gapAddress] << std::endl;
spacing.resize(GLOBAL_maxFileNameLen + 4 - floatingRoutines.back()->fileName.length(), ' ');
std::cout << " $" << std::hex << targetAddr << std::dec << ": " <<
floatingRoutines.back()->fileName << spacing << "size: " <<
floatingRoutines.back()->codeLength << std::endl;
floatingRoutines.pop_back();
if (gaps[gapAddress] == 0) gaps.erase(gapAddress);
repeat = true;
}
}
}
void BinningProblem::removeUselessGaps()
{
// Get the size of the smallest floating routine,
// remove all the gaps which are smaller in size
int minUsefulSize = floatingRoutines[0]->codeLength;
bool repeat = true;
while (repeat)
{
repeat = false;
for (auto &gap : gaps)
{
if (gap.second < minUsefulSize)
{
std::cout << "dropping gap: $" << std::hex << gap.first << std::dec << " (size: " << gap.second << ")" << std::endl;
gaps.erase(gap.first);
repeat = true;
break;
}
}
}
}
void BinningProblem::sortFloatingRoutinesBySize()
{
// Sort the unallocated routines by size, starting from the smallest one
auto compare = [](const SourceFile *a, const SourceFile *b) -> bool { return a->codeLength < b->codeLength; };
std::sort (floatingRoutines.begin(), floatingRoutines.end(), compare);
}
//
// Class 'Solver'
//
void Solver::run()
{
problem.sortFloatingRoutinesBySize(); // just to be extra sure
while (!problem.gaps.empty() && !problem.floatingRoutines.empty())
{
problem.performObviousSteps();
problem.removeUselessGaps();
if (problem.gaps.empty() || problem.floatingRoutines.empty()) break;
int gapAddr = selectGapToFill();
std::cout << "selected gap: " << "$" << std::hex << gapAddr << std::dec << " (size: " << problem.gaps[gapAddr] << ")" << std::endl;
std::list<SourceFile *> partialSolution;
findPartialSolution(problem.gaps[gapAddr], partialSolution);
problem.fillGap(gapAddr, partialSolution);
}
if (problem.isSolved())
{
std::cout << std::endl << "all the routines sucessfully placed" << std::endl << std::endl;
}
}
int Solver::selectGapToFill()
{
// Find the smallest gap to fill-in
int gapAddr = -1;
int gapSize = -1;
for (auto &gap : problem.gaps)
{
if (gapSize < 0 || gap.second < gapSize)
{
gapAddr = gap.first;
gapSize = gap.second;
}
}
return gapAddr;
}
int KS(const std::vector<SourceFile *> &routines,
std::vector<std::vector<int>> &cacheV, // value cache
std::vector<std::vector<std::list<bool>>> &cacheS, // partial solution cache
int n, int C,
std::list<bool> &solution) // sequence of decisions for each routine, has to be empty when calling
{
// This routine solves a knapsack problem using a dynamic programming
// XXX Possible efficiency improvement for the future: try to consider more than one gap at once,
// this will need a serious rework of the dynamic cache handling and will probably hurt the performance
if (n == 0)
{
// No more routines to take the decision...
return 0;
}
if (C == 0)
{
// We have no capacity left - decisions for all the routines left should be not to take them
solution.resize(n, false);
return 0;
}
auto &cachedV = cacheV[n - 1][C - 1];
auto &cachedS = cacheS[n - 1][C - 1];
if (cachedV >= 0)
{
// We already hold the solution in out cache - return it
solution = cachedS;
return cachedV;
}
auto &codeSize = routines[n - 1]->codeLength;
if (codeSize > C)
{
// We can't take this routine, it's too large
cachedV = KS(routines, cacheV, cacheS, n - 1, C, solution);
solution.push_back(false);
cachedS = solution;
return cachedV;
}
// Note: it it seems equally good to take this routine, or not - take it!
// Evaluation starts from the largest routines, and we should prefer to leave
// a larger number of smaller routines - as this gives more possibilities
// while optimizing the usage of further gaps
std::list<bool> solution1;
int val1 = KS(routines, cacheV, cacheS, n - 1, C - codeSize, solution1) + codeSize; // take
if (val1 == C)
{
// By taking this routine we are filling all the space - contrary to the classic
// knapsack problem, this is an optimal solution for us, don't waste time calculating
// the others
cachedV = val1;
solution = solution1;
solution.push_back(true);
cachedS = solution;
return cachedV;
}
std::list<bool> solution2;
int val2 = KS(routines, cacheV, cacheS, n - 1, C, solution2); // don't take
if (val1 >= val2)
{
// It's better to take this particular routine
cachedV = val1;
solution = solution1;
solution.push_back(true);
}
else
{
// It's better not to take this particular routine
cachedV = val2;
solution = solution2;
solution.push_back(false);
}
cachedS = solution;
return cachedV;
};
void Solver::findPartialSolution(int gapSize, std::list<SourceFile *> &partialSolution)
{
// First filter out available routines, take only the ones not larger than our gap
std::vector<SourceFile *> routines;
routines.clear();
for (auto &routine : problem.floatingRoutines)
{
if (routine->codeLength <= gapSize)
{
routines.push_back(routine);
}
else break; // 'floatingRoutines' should be kept sorted
}
// Prepare value cache
std::vector<std::vector<int>> cacheV;
cacheV.resize(routines.size());
for (auto &cacheRow : cacheV) cacheRow.resize(gapSize, -1);
// Prepare partial solution cache
std::vector<std::vector<std::list<bool>>> cacheS;
cacheS.resize(routines.size());
for (auto &cacheRow : cacheS) cacheRow.resize(gapSize);
// Calculate solution
std::list<bool> solution;
KS(routines, cacheV, cacheS, routines.size(), gapSize, solution);
if (routines.size() != solution.size()) bailOut(std::string("internal error line ") + std::to_string(__LINE__));
// Return the solution upstream
while (!routines.empty())
{
if (solution.back() != false)
{
partialSolution.push_front(routines.back());
}
routines.pop_back();
solution.pop_back();
}
return;
}
+1 -1
View File
@@ -479,7 +479,7 @@ int main(void)
} else
printf("\t.byte $%02x\n",packed_keywords[i]);
}
printf("\t.const packed_keyword_table_len = $%X\n",packedkey_len);
printf("\t.label packed_keyword_table_len = $%X\n",packedkey_len);
fprintf(stderr,"Error list takes %d bytes raw.\n",raw_size);
fprintf(stderr,"%d token bytes used to encode all messages.\n",token_count);
File renamed without changes.
File renamed without changes.
File renamed without changes.