diff --git a/.gitignore b/.gitignore index ffd89384..d9f921e3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,6 @@ # Compilation related files -temp.s -temp.sym - assembler/KickAss* build/* diff --git a/Makefile b/Makefile index 5080e7ed..66226086 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/bin/REEADME.txt b/bin/README.txt similarity index 100% rename from bin/REEADME.txt rename to bin/README.txt diff --git a/bin/basic_generic.rom b/bin/basic_generic.rom index 71a7c830..966d794a 100644 Binary files a/bin/basic_generic.rom and b/bin/basic_generic.rom differ diff --git a/bin/basic_mega65.rom b/bin/basic_mega65.rom index 17939d89..20dc20c0 100644 Binary files a/bin/basic_mega65.rom and b/bin/basic_mega65.rom differ diff --git a/bin/basic_ultimate64.rom b/bin/basic_ultimate64.rom index 0d1f0cef..482ff397 100644 Binary files a/bin/basic_ultimate64.rom and b/bin/basic_ultimate64.rom differ diff --git a/bin/kernal_generic.rom b/bin/kernal_generic.rom index 8085094a..32bd602e 100644 Binary files a/bin/kernal_generic.rom and b/bin/kernal_generic.rom differ diff --git a/bin/kernal_mega65.rom b/bin/kernal_mega65.rom index 8085094a..f6b0fd85 100644 Binary files a/bin/kernal_mega65.rom and b/bin/kernal_mega65.rom differ diff --git a/bin/kernal_ultimate64.rom b/bin/kernal_ultimate64.rom index 8085094a..a52e636e 100644 Binary files a/bin/kernal_ultimate64.rom and b/bin/kernal_ultimate64.rom differ diff --git a/src/build_segment.cc b/src/build_segment.cc new file mode 100644 index 00000000..3d658171 --- /dev/null +++ b/src/build_segment.cc @@ -0,0 +1,983 @@ +// +// Utility to build a segment from the provided assembler +// source files, placing routines where they need to live +// + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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 ] [-d ] [-t ]" << std::endl << + " [-l ] [-h ]" << std::endl << + " [-s ] [-i ] " << + 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 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 &routines); + void performObviousSteps(); + void removeUselessGaps(); + void sortFloatingRoutinesBySize(); + + std::map fixedRoutines; // routines with location already fixed + std::map gaps; // gaps by address + std::vector 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 &partialSolution); + +private: + + BinningProblem &problem; +}; + +// +// Global variables +// + +std::list GLOBAL_sourceFiles; +std::list 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 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 &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 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 &routines, + std::vector> &cacheV, // value cache + std::vector>> &cacheS, // partial solution cache + int n, int C, + std::list &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 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 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 &partialSolution) +{ + // First filter out available routines, take only the ones not larger than our gap + + std::vector 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> cacheV; + cacheV.resize(routines.size()); + for (auto &cacheRow : cacheV) cacheRow.resize(gapSize, -1); + + // Prepare partial solution cache + std::vector>> cacheS; + cacheS.resize(routines.size()); + for (auto &cacheRow : cacheS) cacheRow.resize(gapSize); + + // Calculate solution + + std::list 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; +} diff --git a/src/compress_text.c b/src/compress_text.c index ff0cc4eb..cf7e4751 100644 --- a/src/compress_text.c +++ b/src/compress_text.c @@ -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); diff --git a/src/basic005.c b/src/outdated/basic005.c similarity index 100% rename from src/basic005.c rename to src/outdated/basic005.c diff --git a/src/prepare_startup.c b/src/outdated/prepare_startup.c similarity index 100% rename from src/prepare_startup.c rename to src/outdated/prepare_startup.c diff --git a/src/preprocess.c b/src/outdated/preprocess.c similarity index 100% rename from src/preprocess.c rename to src/outdated/preprocess.c