1
0
mirror of https://gitlab.com/camelot/kickc.git synced 2024-11-26 12:49:21 +00:00

Merge remote-tracking branch 'origin/master'

This commit is contained in:
jespergravgaard 2020-11-04 09:10:36 +01:00
commit c6d0f41c14
10 changed files with 390 additions and 45 deletions

2
.gitignore vendored
View File

@ -3,9 +3,11 @@
*/*.brk */*.brk
*/*.prg */*.prg
*/*.sym */*.sym
*/.tmpdirs
*/bin/ */bin/
*/workspace.xml */workspace.xml
./target/ ./target/
target/ target/
**/.DS_Store **/.DS_Store
.project .project
.tmpdirs

View File

@ -220,6 +220,9 @@ public class KickC implements Callable<Integer> {
Program program = compiler.getProgram(); Program program = compiler.getProgram();
// Initialize tmp dir manager
TmpDirManager.init(program.getAsmFragmentBaseFolder());
// Initialize the master ASM fragment synthesizer // Initialize the master ASM fragment synthesizer
program.initAsmFragmentMasterSynthesizer(!optimizeNoFragmentCache); program.initAsmFragmentMasterSynthesizer(!optimizeNoFragmentCache);
@ -486,6 +489,9 @@ public class KickC implements Callable<Integer> {
} }
} }
if(TmpDirManager.MANAGER!=null)
TmpDirManager.MANAGER.cleanup();
return CommandLine.ExitCode.OK; return CommandLine.ExitCode.OK;
} }

View File

@ -0,0 +1,136 @@
package dk.camelot64.kickc;
import dk.camelot64.kickc.model.CompileError;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
/**
* Manages temporary folders with files.
* KickAssembler holds open handles to compiled ASM files. Therefore they cannot be deleted immediately after compilation.
* This manager
* - supports creation of new temporary folders
* - attempts deletion on exit
* - if deletion is not successful the folder paths are saved to a file. The folders in this file is attempted deleted on the next invocation.
*/
public class TmpDirManager {
/** Singleton manager. */
public static TmpDirManager MANAGER;
/**
* Initialize the singleton manager
*
* @param baseFolder The base folder where the TXT file containing dirs to delete will be loaded/saved from.
*/
public static void init(Path baseFolder) {
MANAGER = new TmpDirManager(baseFolder);
}
/** The base folder where the TXT file containing dirs to delete will be loaded/saved from. */
private Path baseFolder;
/** Tmp folders that have been created and not deleted. */
private List<Path> tmpDirs;
private TmpDirManager(Path baseFolder) {
this.baseFolder = baseFolder;
this.tmpDirs = new ArrayList<>();
}
/**
* Creates a new temporary directory
*
* @return The new temporary folder
*/
public Path newTmpDir() {
try {
Path tmpDir = Files.createTempDirectory("kickc");
this.tmpDirs.add(tmpDir);
return tmpDir;
} catch(IOException e) {
throw new CompileError("Error creating temporary directory. ", e);
}
}
/**
* Deletes all temporary folders - including any files in the folders.
* If deletion is not successful the absolute paths are saved to a txt-file to tried again later.
* This also loads the txt-file with previous failed deletions and retries them
*/
public void cleanup() {
try {
// Attempt deletion of all temporary folders - including all files in the folders
List<Path> failedDirs = new ArrayList<>();
for(Path tmpDir : tmpDirs) {
boolean success = deleteTmpDir(tmpDir);
if(!success) {
failedDirs.add(tmpDir);
//System.out.println("Cannot delete temporary folder, postponing " + tmpDir);
} else {
//System.out.println("Successfully deleted temporary folder " + tmpDir);
}
}
// Read postponed file and delete any paths
File todoFile = baseFolder.resolve(".tmpdirs").toFile();
if(todoFile.exists()) {
FileReader todoFileReader = new FileReader(todoFile);
BufferedReader todoBufferedReader = new BufferedReader(todoFileReader);
String todoPathAbs = todoBufferedReader.readLine();
while(todoPathAbs != null) {
Path todoPath = new File(todoPathAbs).toPath();
boolean success = deleteTmpDir(todoPath);
if(!success) {
System.err.println("Cannot delete postponed temporary folder - skipping " + todoPathAbs);
} else {
//System.out.println("Successfully deleted postponed temporary folder " + todoPathAbs);
}
todoPathAbs = todoBufferedReader.readLine();
}
todoBufferedReader.close();
todoFileReader.close();
}
// Delete the old postponed file
if(todoFile.exists()) {
if(!todoFile.delete())
System.err.println("Warning! Cannot delete .tmpdir file " + todoFile.getAbsolutePath());
//System.out.println("Deleted old .tmpdir file " + todoFile.getAbsolutePath());
}
// Save any failed paths to new postponed file
if(failedDirs.size() > 0) {
PrintStream todoPrintStream = new PrintStream(todoFile);
for(Path failedDir : failedDirs) {
todoPrintStream.println(failedDir.toAbsolutePath());
}
todoPrintStream.close();
//System.out.println("Saved .tmpdir file with " + failedDirs.size() + " postponed temporary folders " + todoFile.getAbsolutePath());
}
} catch(IOException e) {
throw new CompileError("Error cleaning up temporary files", e);
}
}
private boolean deleteTmpDir(Path tmpDir) {
// Delete the temporary directory with folders
boolean success = true;
String[] entries = tmpDir.toFile().list();
if(entries != null)
for(String s : entries) {
File currentFile = new File(tmpDir.toFile(), s);
if(!currentFile.delete()) {
//System.err.println("Warning! Cannot delete temporary file " + currentFile.getAbsolutePath());
success = false;
break;
}
}
if(!tmpDir.toFile().delete()) {
//System.err.println("Warning! Cannot delete temporary folder " + tmpDir.toAbsolutePath());
success = false;
}
return success;
}
}

View File

@ -2,6 +2,7 @@ package dk.camelot64.kickc.passes;
import dk.camelot64.cpufamily6502.CpuAddressingMode; import dk.camelot64.cpufamily6502.CpuAddressingMode;
import dk.camelot64.cpufamily6502.CpuOpcode; import dk.camelot64.cpufamily6502.CpuOpcode;
import dk.camelot64.kickc.TmpDirManager;
import dk.camelot64.kickc.asm.*; import dk.camelot64.kickc.asm.*;
import dk.camelot64.kickc.model.CompileError; import dk.camelot64.kickc.model.CompileError;
import dk.camelot64.kickc.model.Program; import dk.camelot64.kickc.model.Program;
@ -21,8 +22,6 @@ import java.util.regex.Pattern;
*/ */
public class Pass5FixLongBranches extends Pass5AsmOptimization { public class Pass5FixLongBranches extends Pass5AsmOptimization {
private Path tmpDir;
public Pass5FixLongBranches(Program program) { public Pass5FixLongBranches(Program program) {
super(program); super(program);
} }
@ -54,35 +53,25 @@ public class Pass5FixLongBranches extends Pass5AsmOptimization {
private boolean step() { private boolean step() {
// Reindex ASM lines // Reindex ASM lines
new Pass5ReindexAsmLines(getProgram()).optimize(); new Pass5ReindexAsmLines(getProgram()).optimize();
Path tmpDir = TmpDirManager.MANAGER.newTmpDir();
// Create a temporary directory for the ASM file
try {
tmpDir = Files.createTempDirectory("kickc");
} catch(IOException e) {
throw new CompileError("Error creating temp file.", e);
}
// Generate the ASM file // Generate the ASM file
String outputFileName = getProgram().getPrimaryFileName(); String outputFileName = getProgram().getPrimaryFileName();
try { try {
//getLog().append("ASM"); //getLog().append("ASM");
//getLog().append(getProgram().getAsm().toString(false, true)); //getLog().append(getProgram().getAsm().toString(false, true));
writeOutputFile(tmpDir, outputFileName, ".asm", getProgram().getAsm().toString(new AsmProgram.AsmPrintState(false), null));
writeOutputFile(outputFileName, ".asm", getProgram().getAsm().toString(new AsmProgram.AsmPrintState(false), null));
// Copy Resource Files // Copy Resource Files
for(Path asmResourceFile : getProgram().getAsmResourceFiles()) { for(Path asmResourceFile : getProgram().getAsmResourceFiles()) {
File binFile = getTmpFile(asmResourceFile.getFileName().toString()); File binFile = getTmpFile(tmpDir, asmResourceFile.getFileName().toString());
Files.copy(asmResourceFile, binFile.toPath()); Files.copy(asmResourceFile, binFile.toPath());
} }
} catch(IOException e) { } catch(IOException e) {
throw new CompileError("Error writing ASM temp file.", e); throw new CompileError("Error writing ASM temp file.", e);
} }
// Compile using KickAssembler - catch the output in a String // Compile using KickAssembler - catch the output in a String
File asmFile = getTmpFile(outputFileName, ".asm"); File asmFile = getTmpFile(tmpDir, outputFileName, ".asm");
File binaryFile = getTmpFile(outputFileName, "."+getProgram().getTargetPlatform().getOutFileExtension()); File binaryFile = getTmpFile(tmpDir, outputFileName, "."+getProgram().getTargetPlatform().getOutFileExtension());
ByteArrayOutputStream kickAssOut = new ByteArrayOutputStream(); ByteArrayOutputStream kickAssOut = new ByteArrayOutputStream();
System.setOut(new PrintStream(kickAssOut)); System.setOut(new PrintStream(kickAssOut));
int asmRes = -1; int asmRes = -1;
@ -117,7 +106,6 @@ public class Pass5FixLongBranches extends Pass5AsmOptimization {
// Found line number // Found line number
//getLog().append("Found long branch line number "+contextLineIdx); //getLog().append("Found long branch line number "+contextLineIdx);
if(fixLongBranch(contextLineIdx - 1)) { if(fixLongBranch(contextLineIdx - 1)) {
removeTmpDir();
return true; return true;
} }
} }
@ -125,25 +113,9 @@ public class Pass5FixLongBranches extends Pass5AsmOptimization {
} }
} }
} }
removeTmpDir();
return false; return false;
} }
private void removeTmpDir() {
// Delete the temporary directory with folders
String[]entries = tmpDir.toFile().list();
for(String s: entries){
File currentFile = new File(tmpDir.toFile(),s);
if(!currentFile.delete()) {
System.err.println("Warning! Cannot delete temporary file "+currentFile.getAbsolutePath());
}
}
if(!tmpDir.toFile().delete()) {
System.err.println("Warning! Cannot delete temporary folder "+tmpDir.toAbsolutePath());
}
}
/** /**
* Fix a long branch detected at a specific ASM index * Fix a long branch detected at a specific ASM index
* *
@ -202,9 +174,9 @@ public class Pass5FixLongBranches extends Pass5AsmOptimization {
} }
} }
public File writeOutputFile(String fileName, String extension, String outputString) throws IOException { private File writeOutputFile(Path tmpDir, String fileName, String extension, String outputString) throws IOException {
// Write output file // Write output file
File file = getTmpFile(fileName, extension); File file = getTmpFile(tmpDir, fileName, extension);
FileOutputStream outputStream = new FileOutputStream(file); FileOutputStream outputStream = new FileOutputStream(file);
OutputStreamWriter writer = new OutputStreamWriter(outputStream); OutputStreamWriter writer = new OutputStreamWriter(outputStream);
writer.write(outputString); writer.write(outputString);
@ -214,12 +186,12 @@ public class Pass5FixLongBranches extends Pass5AsmOptimization {
return file; return file;
} }
public File getTmpFile(String fileName, String extension) { private static File getTmpFile(Path tmpDir, String fileName, String extension) {
Path kcPath = FileSystems.getDefault().getPath(fileName); Path kcPath = FileSystems.getDefault().getPath(fileName);
return new File(tmpDir.toFile(), kcPath.getFileName().toString() + extension); return new File(tmpDir.toFile(), kcPath.getFileName().toString() + extension);
} }
public File getTmpFile(String fileName) { private static File getTmpFile(Path tmpDir, String fileName) {
return new File(tmpDir.toFile(), fileName ); return new File(tmpDir.toFile(), fileName );
} }

View File

@ -1,5 +1,6 @@
package dk.camelot64.kickc.test; package dk.camelot64.kickc.test;
import dk.camelot64.kickc.TmpDirManager;
import kickass.KickAssembler65CE02; import kickass.KickAssembler65CE02;
import kickass.nonasm.c64.CharToPetsciiConverter; import kickass.nonasm.c64.CharToPetsciiConverter;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -19,16 +20,22 @@ public class TestKickAssRun {
*/ */
@Test @Test
public void testKickAssRun() throws IOException, URISyntaxException { public void testKickAssRun() throws IOException, URISyntaxException {
TmpDirManager.init(new File("").toPath());
ReferenceHelper asmHelper = new ReferenceHelperFolder("src/test/java/dk/camelot64/kickc/test/"); ReferenceHelper asmHelper = new ReferenceHelperFolder("src/test/java/dk/camelot64/kickc/test/");
URI asmUri = asmHelper.loadReferenceFile("kickasstest", ".asm"); URI asmUri = asmHelper.loadReferenceFile("kickasstest", ".asm");
Path asmPath = Paths.get(asmUri); Path asmPath = Paths.get(asmUri);
File asmPrgFile = getTmpFile("kickasstest", ".prg");
Path tmpDir = TmpDirManager.MANAGER.newTmpDir();
File asmFile = getTmpFile(tmpDir, "kickasstest", ".asm");
File asmPrgFile = getTmpFile(tmpDir, "kickasstest", ".prg");
Files.copy(asmPath, asmFile.toPath());
ByteArrayOutputStream kickAssOut = new ByteArrayOutputStream(); ByteArrayOutputStream kickAssOut = new ByteArrayOutputStream();
System.setOut(new PrintStream(kickAssOut)); System.setOut(new PrintStream(kickAssOut));
try { try {
CharToPetsciiConverter.setCurrentEncoding("screencode_mixed"); CharToPetsciiConverter.setCurrentEncoding("screencode_mixed");
KickAssembler65CE02.main2(new String[]{asmPath.toAbsolutePath().toString(), "-o", asmPrgFile.getAbsolutePath()}); KickAssembler65CE02.main2(new String[]{asmFile.getAbsolutePath(), "-o", asmPrgFile.getAbsolutePath()});
} catch (AssertionError e) { } catch(AssertionError e) {
System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.out))); System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.out)));
String output = kickAssOut.toString(); String output = kickAssOut.toString();
System.out.println(output); System.out.println(output);
@ -38,11 +45,11 @@ public class TestKickAssRun {
} }
String output = kickAssOut.toString(); String output = kickAssOut.toString();
System.out.println(output); System.out.println(output);
TmpDirManager.MANAGER.cleanup();
} }
public static File getTmpFile(Path tmpDir, String fileName, String extension) throws IOException {
public File getTmpFile(String fileName, String extension) throws IOException {
Path tmpDir = Files.createTempDirectory("kickc");
Path kcPath = FileSystems.getDefault().getPath(fileName); Path kcPath = FileSystems.getDefault().getPath(fileName);
return new File(tmpDir.toFile(), kcPath.getFileName().toString() + extension); return new File(tmpDir.toFile(), kcPath.getFileName().toString() + extension);
} }
@ -90,7 +97,7 @@ public class TestKickAssRun {
private void printPetscii(String encoding, char ch, String sCh) { private void printPetscii(String encoding, char ch, String sCh) {
CharToPetsciiConverter.setCurrentEncoding(encoding); CharToPetsciiConverter.setCurrentEncoding(encoding);
Byte petscii = CharToPetsciiConverter.convert(ch); Byte petscii = CharToPetsciiConverter.convert(ch);
System.out.println(encoding+": "+sCh+" > "+(petscii==null?"null":(int)petscii)); System.out.println(encoding + ": " + sCh + " > " + (petscii == null ? "null" : (int) petscii));
} }

View File

@ -3,6 +3,7 @@ package dk.camelot64.kickc.test;
import dk.camelot64.kickc.CompileLog; import dk.camelot64.kickc.CompileLog;
import dk.camelot64.kickc.Compiler; import dk.camelot64.kickc.Compiler;
import dk.camelot64.kickc.SourceLoader; import dk.camelot64.kickc.SourceLoader;
import dk.camelot64.kickc.TmpDirManager;
import dk.camelot64.kickc.asm.AsmProgram; import dk.camelot64.kickc.asm.AsmProgram;
import dk.camelot64.kickc.model.CompileError; import dk.camelot64.kickc.model.CompileError;
import dk.camelot64.kickc.model.Program; import dk.camelot64.kickc.model.Program;
@ -74,6 +75,7 @@ public class TestPrograms {
public void testAtariXlMd5b() throws IOException, URISyntaxException { public void testAtariXlMd5b() throws IOException, URISyntaxException {
compileAndCompare("atarixl-md5b.c"); compileAndCompare("atarixl-md5b.c");
} }
@Test @Test
public void testAtariXlMd5() throws IOException, URISyntaxException { public void testAtariXlMd5() throws IOException, URISyntaxException {
compileAndCompare("atarixl-md5.c"); compileAndCompare("atarixl-md5.c");
@ -4828,10 +4830,13 @@ public class TestPrograms {
@BeforeAll @BeforeAll
public static void setUp() { public static void setUp() {
TmpDirManager.init(new File("").toPath());
} }
@AfterAll @AfterAll
public static void tearDown() { public static void tearDown() {
if(TmpDirManager.MANAGER != null)
TmpDirManager.MANAGER.cleanup();
//AsmFragmentTemplateUsages.logUsages(log, false, false, false, false, false, false); //AsmFragmentTemplateUsages.logUsages(log, false, false, false, false, false, false);
//printGCStats(); //printGCStats();
} }

View File

@ -0,0 +1,26 @@
// Demonstrates problem with inline ASM usages - and early-detect constants
// zp2 should be forced to live at address $fc - but is identified to be constant by Pass1EarlyConstantIdentification
.pc = $801 "Basic"
:BasicUpstart(main)
.pc = $80d "Program"
main: {
.label zp2 = $fc
// zp2 = 0x0400
lda #<$400
sta.z zp2
lda #>$400
sta.z zp2+1
// zp2[1] = '*'
lda #'*'
ldy #1
sta (zp2),y
// asm
lda #$28
sta zp2
// zp2[2] = '*'
lda #'*'
ldy #2
sta (zp2),y
// }
rts
}

View File

@ -0,0 +1,11 @@
void main()
main: scope:[main] from
[0] main::zp2 = (byte*) 1024
[1] main::zp2[1] = '*'
asm { lda#$28 stazp2 }
[3] main::zp2[2] = '*'
to:main::@return
main::@return: scope:[main] from main
[4] return
to:@return

View File

@ -0,0 +1,176 @@
Setting inferred volatile on symbol affected by address-of: main::zp2 in asm { lda#$28 stazp2 }
CONTROL FLOW GRAPH SSA
void main()
main: scope:[main] from __start
main::zp2 = (byte*)$400
main::zp2[1] = '*'
asm { lda#$28 stazp2 }
main::zp2[2] = '*'
to:main::@return
main::@return: scope:[main] from main
return
to:@return
void __start()
__start: scope:[__start] from
call main
to:__start::@1
__start::@1: scope:[__start] from __start
to:__start::@return
__start::@return: scope:[__start] from __start::@1
return
to:@return
SYMBOL TABLE SSA
void __start()
void main()
volatile byte* main::zp2 loadstore !zp[-1]:252
Adding number conversion cast (unumber) 1 in main::zp2[1] = '*'
Adding number conversion cast (unumber) 2 in main::zp2[2] = '*'
Successful SSA optimization PassNAddNumberTypeConversions
Simplifying constant pointer cast (byte*) 1024
Simplifying constant integer cast 1
Simplifying constant integer cast 2
Successful SSA optimization PassNCastSimplification
Finalized unsigned number type 1
Finalized unsigned number type 2
Successful SSA optimization PassNFinalizeNumberTypeConversions
Removing unused procedure __start
Removing unused procedure block __start
Removing unused procedure block __start::@1
Removing unused procedure block __start::@return
Successful SSA optimization PassNEliminateEmptyStart
CALL GRAPH
Created 0 initial phi equivalence classes
Coalesced down to 0 phi equivalence classes
FINAL CONTROL FLOW GRAPH
void main()
main: scope:[main] from
[0] main::zp2 = (byte*) 1024
[1] main::zp2[1] = '*'
asm { lda#$28 stazp2 }
[3] main::zp2[2] = '*'
to:main::@return
main::@return: scope:[main] from main
[4] return
to:@return
VARIABLE REGISTER WEIGHTS
void main()
volatile byte* main::zp2 loadstore !zp[-1]:252 2.0
Initial phi equivalence classes
Added variable main::zp2 to live range equivalence class [ main::zp2 ]
Complete equivalence classes
[ main::zp2 ]
REGISTER UPLIFT POTENTIAL REGISTERS
Statement [0] main::zp2 = (byte*) 1024 [ main::zp2 ] ( [ main::zp2 ] { } ) always clobbers reg byte a
Statement [1] main::zp2[1] = '*' [ main::zp2 ] ( [ main::zp2 ] { } ) always clobbers reg byte a reg byte y
Statement asm { lda#$28 stazp2 } always clobbers reg byte a
Statement [3] main::zp2[2] = '*' [ ] ( [ ] { } ) always clobbers reg byte a reg byte y
Potential registers zp[2]:252 [ main::zp2 ] : zp[2]:252 ,
REGISTER UPLIFT SCOPES
Uplift Scope [main] 2: zp[2]:252 [ main::zp2 ]
Uplift Scope []
Uplifting [main] best 45 combination zp[2]:252 [ main::zp2 ]
Uplifting [] best 45 combination
ASSEMBLER BEFORE OPTIMIZATION
// File Comments
// Demonstrates problem with inline ASM usages - and early-detect constants
// zp2 should be forced to live at address $fc - but is identified to be constant by Pass1EarlyConstantIdentification
// Upstart
.pc = $801 "Basic"
:BasicUpstart(main)
.pc = $80d "Program"
// Global Constants & labels
// main
main: {
.label zp2 = $fc
// [0] main::zp2 = (byte*) 1024 -- pbuz1=pbuc1
lda #<$400
sta.z zp2
lda #>$400
sta.z zp2+1
// [1] main::zp2[1] = '*' -- pbuz1_derefidx_vbuc1=vbuc2
lda #'*'
ldy #1
sta (zp2),y
// asm { lda#$28 stazp2 }
lda #$28
sta zp2
// [3] main::zp2[2] = '*' -- pbuz1_derefidx_vbuc1=vbuc2
lda #'*'
ldy #2
sta (zp2),y
jmp __breturn
// main::@return
__breturn:
// [4] return
rts
}
// File Data
ASSEMBLER OPTIMIZATIONS
Removing instruction jmp __breturn
Succesful ASM optimization Pass5NextJumpElimination
Removing instruction __breturn:
Succesful ASM optimization Pass5UnusedLabelElimination
FINAL SYMBOL TABLE
void main()
volatile byte* main::zp2 loadstore !zp[-1]:252 zp[2]:252 2.0
zp[2]:252 [ main::zp2 ]
FINAL ASSEMBLER
Score: 42
// File Comments
// Demonstrates problem with inline ASM usages - and early-detect constants
// zp2 should be forced to live at address $fc - but is identified to be constant by Pass1EarlyConstantIdentification
// Upstart
.pc = $801 "Basic"
:BasicUpstart(main)
.pc = $80d "Program"
// Global Constants & labels
// main
main: {
.label zp2 = $fc
// zp2 = 0x0400
// [0] main::zp2 = (byte*) 1024 -- pbuz1=pbuc1
lda #<$400
sta.z zp2
lda #>$400
sta.z zp2+1
// zp2[1] = '*'
// [1] main::zp2[1] = '*' -- pbuz1_derefidx_vbuc1=vbuc2
lda #'*'
ldy #1
sta (zp2),y
// asm
// asm { lda#$28 stazp2 }
lda #$28
sta zp2
// zp2[2] = '*'
// [3] main::zp2[2] = '*' -- pbuz1_derefidx_vbuc1=vbuc2
lda #'*'
ldy #2
sta (zp2),y
// main::@return
// }
// [4] return
rts
}
// File Data

View File

@ -0,0 +1,4 @@
void main()
volatile byte* main::zp2 loadstore !zp[-1]:252 zp[2]:252 2.0
zp[2]:252 [ main::zp2 ]