diff --git a/src/main/java/dk/camelot64/kickc/Compiler.java b/src/main/java/dk/camelot64/kickc/Compiler.java index d74ab24a4..5a82b0492 100644 --- a/src/main/java/dk/camelot64/kickc/Compiler.java +++ b/src/main/java/dk/camelot64/kickc/Compiler.java @@ -81,6 +81,7 @@ public class Compiler { } public Program compile(String fileName) throws IOException { + program.setFileName(fileName); try { Pass0GenerateStatementSequence pass0GenerateStatementSequence = new Pass0GenerateStatementSequence(program); loadAndParseFile(fileName, program, pass0GenerateStatementSequence); @@ -356,6 +357,9 @@ public class Compiler { } } + new Pass5ReindexAsmLines(program).optimize(); + new Pass5FixLongBranches(program).optimize(); + getLog().append("\nFINAL SYMBOL TABLE"); getLog().append(program.getScope().getSymbolTableContents(program)); diff --git a/src/main/java/dk/camelot64/kickc/TODO.txt b/src/main/java/dk/camelot64/kickc/TODO.txt deleted file mode 100644 index c3409160d..000000000 --- a/src/main/java/dk/camelot64/kickc/TODO.txt +++ /dev/null @@ -1,211 +0,0 @@ -Known Problems -- (inlinearrayproblem.kc) Arrays / strings allocated inline destroy functions (because they are allocated where the call enters. -- (forincrementassign.kc) Classic for() does not allow assignment as increment, eg. for(byte i=0;i<25;i=i+2) {} -- (forrangesymbolic.kc) Range-based for does not recognize symbolic constants. The following gives a ParseTreeConstantEvaluator$NotConstantException - const byte* BITMAP = $2000; for(byte* b : BITMAP..BITMAP+$2000) { *b = 0; } -- (wordexpr.kc) Expressions based on bytes but resulting in words are erronously infered as bytes - eg. yoffs = yoffs + 40*8; -- (useuninitialized.kc) Using an uninitialized variable fails in the optimizer phase. It should fail much earlier. -- (inline-asm-param.kc) Inline ASM does not attempt to handle parameters (variables). - -Features -- Improve locality of block sequence for if(cond) { stmt1; } else { stmt2; } to if(!cond) goto @else stmt1; jmp @end; @else: stmt2; @end: -- Optimize if/else by swapping if & else if cond is easier to evaluate than !cond. -- Let { stmt } and for() {} introduce a new anonymous scope. (But not when optimizing) -- Implement inline compilation of functions (and a mechanism for choosing which methods / calls to inline) -- Handle long branches -- Optimize loops by unrolling them somewhat -- Optimize loops with Strength reduction (https://en.wikipedia.org/wiki/Strength_reduction) -- Add syntax for encoded chars (eg. PETSCII instead of SCREEN) - -Syntax Checking -- Disallow code outside functions. -- Check that declared constants are never assigned. -- Graceful error when using undeclared variable. -- Graceful error when using uninitialized variable. - -New language Features -- Add signed bytes / words -- Add Fixed Point number types fixed[8.8], fixed[16.8] - maybe even fixed[24.4] -- Add structs - -Pointers -- Support calculated pointers eg. *(ptr+i) -- Use a rule-based approach to de-inline most pointer derefs again just before register allocation. ( *a = *b + *c => $1=*b (reg a), $2=*c (alu), $3=$1+$2 (reg a), *a = $3 - but *a=*a+const should stay inlined as it has efficient ASM) -- VariableReplacer, GraphReplacer, Pass1GenerateSSA#versionAllUses() has a pretty ugly handling of pointers. Abstract prettier code. -- AsmFragmentManager only handles _neq_ pointer comparison. Abstract a better replacer and handle the rest. -- Consider if array-indexing should be handled in the same way as unary * when generating statements (put into inline Pointers instead of temporary variables.) This will fix a[2]++, which probably does not work atm. - -Compiletime (Preprocessing) -- Add preprocessing / find a way to allow some functions to run at compile time. This will always be to initialize memory to a certain state. - - Syntax: A compiletime keyword usable before initializers or global statements blocks? (compiletime is a better word as it is not a preprocessor in the C-sense of the word) - - byte[$100] sins = compiletime { [x] = sin(2.0*PI*x/$100) }; - - byte[$100] sins; compiletime { for(byte x : 0 $ff) { sins[x] = sin(2.0*PI*x/$100)}}; - -Imports -- Remove unused functions & symbols. -- Add imports -- Add an export keyword ensuring that a function is generated even if it is never called. Maybe export can be handled by generating a call that can be used as stub. - -Constants -- Do not create multiple versions & phi-statements for declared constants. -- Perform static constant analysis determining if a var is ever assigned. Mark as constant before creating SSA/versions. -- Limit number of versions & phi-blocks by create a generative algorithm for creating initial phi-blocks & variable versions. - - When an assignment is encountered create a new version. - - Backtrack the necessary new phi-blocks and changes into the code? - -Word Operations / Math -- Syntax for composing a word from two bytes. word w = { lo, hi } --> ICL: w = lo _word_ hi; -- Support Word table lookup (through 2 byte-tables) -- Support word math operations (addition, subtraction) -- Implement a word-ALU - - Usage: word[] plotter_x; word[] plotter_y; byte* plotter = plotter_x[x] + plotter_y[y]; -> lda plotter_x_lo,x; clc; adc plotter_y_lo,y; sta plotter; lda plotter_x_hi,x; adc plotter_y_hi,y; sta plotter+1 -- Add support for casting. Specifically casting a byte* to a word and the other way (eg. to enable calculating D018-value from pointer to bitmap & screen.) - *D018 = (byte*)((word)SCREEN/$40)|((word)BITMAP/$400); - - Consider whether autocasting word & byte* is possible ? - -Arrays / Strings / Inline data -- New semantic: Arrays are always allocated inline (and must therefore have a size). Pointers are never. -- Allow complex array expressions in lValues eg. (SCREEN+$100)[idx] -- Add string / static array length operation. -- Add array initializer filling with a specific value / (pattern?). -- Add support for empty / filled byte data arrays. -- Add support for expressions in data initializers. -- Create a fill-like array initialization - byte[$100] plot_xlo = { [x] = x&$f8 }; -- Support word-arrays as two underlying byte-arrays in memory: word[$100] plot_x; -> ASM { plot_x_lo .fill $100,0 plot_x_hi .fill $100,0; } - - Support syntax for initializing the address of the two byte-arrays underlying a word array. word[] plot_x = { lo=$1000, hi=$1000 }; - - Support syntax for initializing the address of a byte-array. byte[] plot_x = { addr=$1000 }; -- current syntax as a short hand byte[] plot_x = $1000; - - Syntax for accessing the low/high byte-array that underlies the word-array. word[$100] plot_x; byte* plot_x_lo = plot_x.lo; plot_x_lo[0] = 0; - -ASM Integration / Inline ASM -- Allow inline ASM parameter expansion - eg lda line (where line is a variable) -- Allow lda or sta to just perform a register copy if possible. -- Add syntax for ensuring specific values are in A/X/Y when entering ASM - eg. asm( A: i, X: bits+1 ) { sta {SCREEN},x }; -- Add syntax for specifying that ASM does not clobber specific registers (if the values are used but preserved through pha/pla) - eg. asm( A: noclob, X: i ) { pha lda #0 sta {SCREEN},x pla}; -- Add ability to call external ASM code from KC. -- Add ability to call KC code from ASM. (Maybe declare some functions external to ensure their interface is well defined. Maybe generate ASM call stubs.) -- A syntax for supporting custom ASM fragments? Allowing the adding of a new operator / function call that is compiled into custom written ASM-fragments. Currently there seems to be a quite short way when adding a new expression operator - maybe give the programmer the same freedom. - -Assembler Improvements -- Relabel blocks eliminating long labels such as b3_from_b11 -- Eliminate chained JMP's (replace by direct JMP) - example: loopsplit.asm -- Better ASM static value analysis - - Not just constants - but also other values (value of var, value of var1[var2], value of var+4 etc.) - - Also analyze back/forward through block entry/exit - - Also analyze value of flags (eg. N=N(var1)) -- Eliminate CPX from DEX, CPX #0, BNE la1 (using ASM static value analysis for N flag - it might be worth annotating CPX with the fact that only the N-flag is relevant - or creating a special instruction for setting the N-flag) -- Eliminate LDA from DEC $2, LDA $2, BNE la1 (using ASM static value analysis for N flag - it might be worth annotating LDA $2 with the fact that only the N-flag is relevant - or creating a special instruction for setting the N-flag) -- Eliminate LDA from STA $11, LDA $11 (using ASM static value analysis of A-register) -- Optimize by allowing resequencing of ASM (statements and phi assignments) in a final phase. -- ASM loop analysis: If all usages of a register has the same value move the assignment out of the loop (if the assignment is simple enough). - -Register Allocation -- Allow user to limit number of combinations tested -- Safe-Copy based SSA deconstruction - - Optimize by finding optimal sequence for multiple phi assignments in entry-segments. - - Avoid clobbering alive vars -- Maybe support several fragments for the same operation with different cost and clobbering profiles. -- Add memory registers (if we need to free some ZP). -- Improve combination testing by finding live range overlaps and not creating combinations that would create an overlap conflict. - - Combinations should be created in a tree-structure instead of just doing all combinations - - Example: For equivalence classes a, b, c if a&c have overlapping live ranges they can never have the same register. Therefore the combination iterator should not create combinations where C has the same register as a. -- Optimize registers in local windows - creating smaller sets of combinations. Currently some programs easily has millions of possible combinations. -- Optimize registers in a tree search. - - Initially allocate all variables to ZP registers - - Handle each scope prioritized - - Handle each variable prioritized - - For each variable try each potential register. - - Examine if current variables overlap with other variable assigned to the same register. If an overlap is found - the branch is dead - - Generate ASM & examine clobber. If the potential register allocation results in clobber - the branch is dead. - - Handle each remaining allocation possibility in order of their score - highest score first. - - Assign the variable to the selected register & Recurse down to the next variable. -- Add UpliftRemains support for attempting to uplift potentials to ALU (requires modifying two registers: 1. the ALU potential to ALU - the one added to the ALU potential to A.) -- Add ability to store/restore registers to free up registers for allocation in outer scopes. Requires marking registers as non-clobbered in certain cases. - -Process/Code Structure Improvement -- Eliminate copy visitor (using ListIterator option for adding elements) -- Refactor Expression Operator Implementation & Evaluation into one class per operator -- Improve error messages to give better context -- Offer to compile resulting ASM with KickAssembler - -Testing -- Test single passes by configuring compiler to only log ICL before & after the tested step! -- Implement ICL syntax (printer & parser). Drop the JSON. -- Test that the parse tree for specific KC syntax is as expected. Use a print function for the parse tree to generate output for comparison. -- Test that the parse tree for specific KC syntax is as expected. Use a print function for the parse tree to generate output for comparison. -- Emulate/Run the ASM for a specific KC program compiled. Compare the emulated ASM output to output calculated by executing directly on the KC tree. -- Add assert statements to the language. Create KC programs that test the compiler by compiling, running and testing assertions. - -Usages -- Implement library for memory allocation in main memory -- Implement library for output on the screen (using basic functions) - -Real Usage -- Implement library for fast multiply (mul.asm) -- Implement spline library (spline.asm) -- Implement polygon filler for complex polygons. -- Implement a true type font renderer. - -Done -+ Test the ASM program output resulting from compiling specific KC program input. -+ Create a proper main function for the compiler -+ Add ++/-- incrementing/decrementing operators. -+ Optimize if's without else if(expr) { stmt; } -> $1=!expr; if($1) goto @1; stmt; @1: -+ Add a for loop for(init;condition;increment) {stmt} -> { init; do { stmt; increment } while (condition) } -+ Add for loop for(byte i: 1..100) { } and for(byte i : 100..0) {} (plus maybe .+. and .-. to make the inc/dec unambiguous) -+ Eliminate unnecessary labels in ASM -+ Make generated ASM human readable. - + Use hex-numbers - + add labels for constants and zp-variables. -+ Procedures that modify variables outside their scope does not work. Outer vars must be transfered in/out like parameters/return values to get it working. -+ Alive vars are propagated backward through procedures (correctly). However they are then propagated back through ALL calls to the procedure incorrectly. They should only be alive at calls where they are alive after the call. In summin.kc s1#0 is incirrectly backpropagated through the first call, where it is not alive. -+ Reuse phi-transitions that are identical -+ Optimize phi transitions by ensuring that identical phi-transitions with regards to register allocation are collected into a single transition. -+ Limit number of combinations tested -+ Equivalences not tested through combinaitons should be tested individually afterwards. -+ Matrix Phi operation (instead of separate statements) -+ Phi Lifting -+ PhiLifting & PhiMemCoalesce (http://compilers.cs.ucla.edu/fernando/projects/soc/reports/short_tech.pdf) -+ Interval Analysis (variable liveness intervals) -+ ComputeLoopNestDepth(b) - Assign loop nesting levels to blocks. -+ ComputeRegisterPreference(v), ComputeWeight(v) -+ CalculateClobbering(i) -+ Register types: A, X, Y, ZP, (memory). -+ Implement a register allocation (coloring) algorithm using by liveness intervals, preferences, weights & clobbering information. - + Optimize register allocation by combining with knowledge of ASM program cost (bytes/cycles) and different ASM fragments with different clobbering. -+ Live range overlap analysis of register combinations inside methods must also look at registers alive at all calls. -+ Examine why temp-vars are used in flipper. -+ Examine why flipper is plotted in a wrong position on the screen. -+ Implement constants into the symbol table and support them in code. -+ Implement new constant consolidation steps. -+ Reintroduce constant addition optimization -+ Add asmName for ConstantVar to remove subscripts -+ Inline constants for "single" versions of vars - such as .const i#0 = 0 -+ Look at optimizing liverange.kc's ASM further - atm. there are to many copy-operations into unnecesary registers. -+ In summin.asm the result of the 2nd sum() is clobbered twice during call to sum(). jsr sum, txa, lda #$d, ldx #$9, jsr sum - + Fix by introducing "effective alive vars" which includes alive vars at all calls to containing methods and using that during clobber check. -+ In loopnest.asm x&y are used in both loops - the outer x&y are clobbered by the inner loop. -+ In voronoi.asm in render() x is clobbered during call to findcol(). -+ Optimize getAliveEffective() to improve speed -+ Add possibility of declaring in-program data - just like .byte/.fill in KickAss. -+ In immemarray.kc the if(++j==8) is broken because the optimizer culls the empty block main::@3 (because the value is constant). In the end the phi()-function in main::@2 has two handlers for min::@1 phi( main::@1/(byte) main::j#1 main::@1/(const byte) main::j#2 ) -+ Add literal char syntax ( 'c' == (byte) 3 ) -+ Add string constants -+ Clobber can occur in PHI-statements. Example: scroll-clobber.kc. c#1/c#2 (A-register) is clobbered by //SEG18 [6] phi (byte*) main::nxt#4 = (const byte[]) TEXT#0 -- zpptrby1=cowo1 -+ Implement fast combination elimination based on live ranges (skipping the ASM-code.) -+ Implemenent Assertions for the output of different phases (ensuring that the result of the phase is consistent) -+ Make each phase return a separate object graph (allowing for keeeping the history in memory & performing rollbacks) -+ Remove "string" keyword (if it exists). -+ Support hi/lo-byte lvalues - plotter = plot_xhi[x]+plot_yhi[y]; -+ Add ALU support for lo/hi operators on pointers - to avoid unnecessary zp-variable holding for cases like byte v = y&$7 | it = lines.listIterator(); + while(it.hasNext()) { + AsmLine asmLine = it.next(); + if(line.equals(asmLine)) { + it.add(add); + return; + } + } + throw new NoSuchElementException("Item not found "+line); + } + public int getIndex() { return index; } @@ -136,6 +155,21 @@ public class AsmSegment { return clobber; } + /** + * Get ASM line by index + * @param idx The index of the line to get + * @return The line with the passed index. Null if not found inside the segment. + */ + public AsmLine getAsmLine(int idx) { + for(AsmLine asmLine : getLines()) { + if(asmLine.getIndex() == idx) { + return asmLine; + } + } + return null; + } + + public String toString(AsmProgram.AsmPrintState printState) { StringBuffer out = new StringBuffer(); if(printState.isComments()) { @@ -165,6 +199,9 @@ public class AsmSegment { if(line instanceof AsmScopeEnd) { printState.decIndent(); } + if(printState.getLineIdx()) { + out.append("["+line.getIndex()+"]"); + } out.append(printState.getIndent()); if(line instanceof AsmComment || line instanceof AsmInstruction || line instanceof AsmLabelDecl || line instanceof AsmConstant || line instanceof AsmDataNumeric || line instanceof AsmDataFill || line instanceof AsmDataString || line instanceof AsmDataAlignment) { out.append(" "); @@ -179,7 +216,7 @@ public class AsmSegment { @Override public String toString() { - return toString(new AsmProgram.AsmPrintState(true)); + return toString(new AsmProgram.AsmPrintState(true, false)); } } diff --git a/src/main/java/dk/camelot64/kickc/model/Program.java b/src/main/java/dk/camelot64/kickc/model/Program.java index 5b577bf66..91b054fa4 100644 --- a/src/main/java/dk/camelot64/kickc/model/Program.java +++ b/src/main/java/dk/camelot64/kickc/model/Program.java @@ -9,6 +9,8 @@ import java.util.List; /** A KickC Intermediate Compiler Language (ICL) Program */ public class Program { + /** The name of the file being compiled. */ + private String fileName; /** Paths used for importing files. */ private List importPaths; /** Imported files. */ @@ -233,4 +235,11 @@ public class Program { return result; } + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public String getFileName() { + return fileName; + } } diff --git a/src/main/java/dk/camelot64/kickc/passes/Pass5FixLongBranches.java b/src/main/java/dk/camelot64/kickc/passes/Pass5FixLongBranches.java new file mode 100644 index 000000000..212121e59 --- /dev/null +++ b/src/main/java/dk/camelot64/kickc/passes/Pass5FixLongBranches.java @@ -0,0 +1,180 @@ +package dk.camelot64.kickc.passes; + +import dk.camelot64.kickc.asm.*; +import dk.camelot64.kickc.model.CompileError; +import dk.camelot64.kickc.model.Program; +import kickass.KickAssembler; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Identify and fix long branches (over 128 bytes). + * Requires KickAssembler in CLASS path + */ +public class Pass5FixLongBranches extends Pass5AsmOptimization { + + private Path tmpDir; + + public Pass5FixLongBranches(Program program) { + super(program); + } + + public boolean optimize() { + + // Make sure KickAssembler is present + try { + Class.forName("kickass.KickAssembler", false, getClass().getClassLoader()); + } catch(ClassNotFoundException e) { + getLog().append("Warning! KickAssembler not found in java CLASSPATH. Cannot eliminate long branches."); + } + + // Perform repeated optimization until no more branches are too long + boolean anyOptimized = false; + boolean stepOptimized = true; + while(stepOptimized) { + stepOptimized = step(); + anyOptimized = true; + } + return anyOptimized; + } + + /** + * Perform + * @return + */ + private boolean step() { + // Reindex ASM lines + new Pass5ReindexAsmLines(getProgram()).optimize(); + + + // 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 + String fileName = getProgram().getFileName(); + try { + //getLog().append("ASM"); + //getLog().append(getProgram().getAsm().toString(false, true)); + writeOutputFile(fileName, ".asm", getProgram().getAsm().toString(false)); + } catch(IOException e) { + throw new CompileError("Error writing ASM temp file.", e); + } + + + // Compile using KickAssembler - catch the output in a String + File asmFile = getTmpFile(fileName, ".asm"); + File asmPrgFile = getTmpFile(fileName, ".prg"); + ByteArrayOutputStream kickAssOut = new ByteArrayOutputStream(); + System.setOut(new PrintStream(kickAssOut)); + int asmRes = KickAssembler.main2(new String[]{asmFile.getAbsolutePath(), "-o", asmPrgFile.getAbsolutePath()}); + System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.out))); + String output = kickAssOut.toString(); + + // Look for a long branch error + if(asmRes != 0) { + String outputLines[] = output.split("\\r?\\n"); + for(int i = 0; i < outputLines.length; i++) { + String outputLine = outputLines[i]; + if(outputLine.contains("Error: relative address is illegal (jump distance is too far).")) { + // Found a long branch! + String contextLine = outputLines[i + 1]; + Pattern contextPattern = Pattern.compile("at line ([0-9]*),.*"); + Matcher matcher = contextPattern.matcher(contextLine); + //getLog().append("Found long branch "+contextLine); + if(matcher.matches()) { + String contextLineIdxStr = matcher.group(1); + int contextLineIdx = Integer.parseInt(contextLineIdxStr); + // Found line number + //getLog().append("Found long branch line number "+contextLineIdx); + if(fixLongBranch(contextLineIdx - 1)) { + return true; + } + } + getLog().append("Warning! Failed to fix long branch at "+contextLine); + } + } + } + return false; + } + + /** + * Fix a long branch detected at a specific ASM index + * @param idx The index of the ASM line with the long branch + * @return True if the branch was fixed + */ + private boolean fixLongBranch(int idx) { + AsmProgram asm = getProgram().getAsm(); + AsmSegment asmSegment = asm.getAsmSegment(idx); + if(asmSegment != null) { + //getLog().append("Found ASM segment "+asmSegment); + AsmLine asmLine = asmSegment.getAsmLine(idx); + if(asmLine != null && asmLine instanceof AsmInstruction) { + //getLog().append("Found ASM line "+asmLine); + AsmInstruction asmInstruction = (AsmInstruction) asmLine; + AsmInstructionType asmInstructionType = asmInstruction.getType(); + AsmInstructionType inverseType = invertBranch(asmInstructionType); + if(inverseType != null) { + //getLog().append("Inversed branch instruction "+asmInstructionType.getMnemnonic()+" -> "+inverseType.getMnemnonic()); + getLog().append("Fixing long branch ["+idx+"] "+asmLine.toString() + " to "+inverseType.getMnemnonic()); + String branchDest = asmInstruction.getParameter(); + asmInstruction.setType(inverseType); + asmInstruction.setParameter("!b+"); + AsmInstructionType jmpType = AsmInstructionSet.getInstructionType("jmp", AsmAddressingMode.ABS, false); + AsmInstruction jmpInstruction = new AsmInstruction(jmpType, branchDest); + asmSegment.addLineAfter(asmInstruction, jmpInstruction); + asmSegment.addLineAfter(jmpInstruction, new AsmLabel("!b")); + return true; + } + } + } + return false; + } + + private AsmInstructionType invertBranch(AsmInstructionType type) { + switch(type.getMnemnonic()) { + case "bcc": + return AsmInstructionSet.getInstructionType("bcs", AsmAddressingMode.REL, false); + case "bcs": + return AsmInstructionSet.getInstructionType("bcc", AsmAddressingMode.REL, false); + case "beq": + return AsmInstructionSet.getInstructionType("bne", AsmAddressingMode.REL, false); + case "bne": + return AsmInstructionSet.getInstructionType("beq", AsmAddressingMode.REL, false); + case "bpl": + return AsmInstructionSet.getInstructionType("bmi", AsmAddressingMode.REL, false); + case "bmi": + return AsmInstructionSet.getInstructionType("bpl", AsmAddressingMode.REL, false); + case "bvs": + return AsmInstructionSet.getInstructionType("bvc", AsmAddressingMode.REL, false); + case "bvc": + return AsmInstructionSet.getInstructionType("bvs", AsmAddressingMode.REL, false); + default: + return null; + } + } + + public File writeOutputFile(String fileName, String extension, String outputString) throws IOException { + // Write output file + File file = getTmpFile(fileName, extension); + FileOutputStream outputStream = new FileOutputStream(file); + OutputStreamWriter writer = new OutputStreamWriter(outputStream); + writer.write(outputString); + writer.close(); + outputStream.close(); + //System.out.println("ASM generated to " + file.getAbsolutePath()); + return file; + } + + public File getTmpFile(String fileName, String extension) { + return new File(tmpDir.toFile(), fileName + extension); + } + +} diff --git a/src/main/java/dk/camelot64/kickc/passes/Pass5ReindexAsmLines.java b/src/main/java/dk/camelot64/kickc/passes/Pass5ReindexAsmLines.java new file mode 100644 index 000000000..a62fcdcd7 --- /dev/null +++ b/src/main/java/dk/camelot64/kickc/passes/Pass5ReindexAsmLines.java @@ -0,0 +1,31 @@ +package dk.camelot64.kickc.passes; + +import dk.camelot64.kickc.asm.AsmComment; +import dk.camelot64.kickc.asm.AsmLine; +import dk.camelot64.kickc.asm.AsmSegment; +import dk.camelot64.kickc.model.Program; + +/** + * Reindex ASM lines + */ +public class Pass5ReindexAsmLines extends Pass5AsmOptimization { + + public Pass5ReindexAsmLines(Program program) { + super(program); + } + + public boolean optimize() { + int nextIndex =0; + for(AsmSegment asmSegment : getAsmProgram().getSegments()) { + for(AsmLine asmLine : asmSegment.getLines()) { + if((asmLine instanceof AsmComment)) { + asmLine.setIndex(-1); + } else { + asmLine.setIndex(nextIndex++); + } + } + } + getAsmProgram().setNextLineIndex(nextIndex); + return false; + } +} diff --git a/src/test/java/dk/camelot64/kickc/test/TestPrograms.java b/src/test/java/dk/camelot64/kickc/test/TestPrograms.java index 71e2ba934..14dc82475 100644 --- a/src/test/java/dk/camelot64/kickc/test/TestPrograms.java +++ b/src/test/java/dk/camelot64/kickc/test/TestPrograms.java @@ -12,9 +12,9 @@ import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; -import java.io.File; -import java.io.IOException; +import java.io.*; import java.net.URISyntaxException; +import java.nio.file.Path; import static junit.framework.TestCase.fail; import static org.junit.Assert.assertTrue; @@ -45,6 +45,16 @@ public class TestPrograms { AsmFragmentTemplateUsages.logUsages(log, false, false, false, false, false, false); } + @Test + public void testLongJump2() throws IOException, URISyntaxException { + compileAndCompare("longjump2"); + } + + @Test + public void testLongJump() throws IOException, URISyntaxException { + compileAndCompare("longjump"); + } + @Test public void testAddressOfParam() throws IOException, URISyntaxException { compileAndCompare("test-address-of-param"); @@ -554,15 +564,7 @@ public class TestPrograms { compiler.addImportPath(testPath); Program program = compiler.compile(fileName); - helper.writeOutputFile(fileName, ".asm", program.getAsm().toString(false)); - File asmFile = helper.getTmpFile(fileName, ".asm"); - File asmPrgFile = helper.getTmpFile(fileName, ".prg"); - File asmLogFile = helper.getTmpFile(fileName, ".klog"); - - int asmRes = KickAssembler.main2(new String[]{asmFile.getAbsolutePath(), "-log", asmLogFile.getAbsolutePath(), "-o", asmPrgFile.getAbsolutePath(), "-vicesymbols", "-showmem"}); - if(asmRes!=0) { - fail("KickAssembling file failed!"); - } + compileAsm(fileName, program); boolean success = true; success &= helper.testOutput(fileName, ".asm", program.getAsm().toString(false)); @@ -576,5 +578,46 @@ public class TestPrograms { } } + private void compileAsm(String fileName, Program program) throws IOException { + writeBinFile(fileName, ".asm", program.getAsm().toString(false)); + File asmFile = getBinFile(fileName, ".asm"); + File asmPrgFile = getBinFile(fileName, ".prg"); + File asmLogFile = getBinFile(fileName, ".log"); + ByteArrayOutputStream kickAssOut = new ByteArrayOutputStream(); + System.setOut(new PrintStream(kickAssOut)); + int asmRes = KickAssembler.main2(new String[]{asmFile.getAbsolutePath(), "-log", asmLogFile.getAbsolutePath(), "-o", asmPrgFile.getAbsolutePath(), "-vicesymbols", "-showmem"}); + System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.out))); + if(asmRes!=0) { + fail("KickAssembling file failed!"); + } + } + + public File writeBinFile(String fileName, String extension, String outputString) throws IOException { + // Write output file + File file = getBinFile(fileName, extension); + FileOutputStream outputStream = new FileOutputStream(file); + OutputStreamWriter writer = new OutputStreamWriter(outputStream); + writer.write(outputString); + writer.close(); + outputStream.close(); + System.out.println("ASM written to " + file.getAbsolutePath()); + return file; + } + + public File getBinFile(String fileName, String extension) { + return new File(getBinDir(), fileName + extension); + } + + private File getBinDir() { + Path tempDir = helper.getTempDir(); + File binDir = new File(tempDir.toFile(), "bin"); + if(!binDir.exists()) { + binDir.mkdir(); + + } + return binDir; + } + + } \ No newline at end of file diff --git a/src/test/java/dk/camelot64/kickc/test/kc/longjump.kc b/src/test/java/dk/camelot64/kickc/test/kc/longjump.kc new file mode 100644 index 000000000..ec3482221 --- /dev/null +++ b/src/test/java/dk/camelot64/kickc/test/kc/longjump.kc @@ -0,0 +1,266 @@ +// Minimal example program generating a long jump + +void main() { + byte* SCREEN = $0400; + for(byte i : 0..10) { + asm { + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + } + SCREEN[i] = i; + } +} diff --git a/src/test/java/dk/camelot64/kickc/test/kc/longjump2.kc b/src/test/java/dk/camelot64/kickc/test/kc/longjump2.kc new file mode 100644 index 000000000..462c959e0 --- /dev/null +++ b/src/test/java/dk/camelot64/kickc/test/kc/longjump2.kc @@ -0,0 +1,538 @@ +// Minimal example program generating two long jumps + +void main() { + long1(); + long2(); +} + + +void long1() { + byte* SCREEN = $0400; + for(byte i : 0..10) { + asm { + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + } + SCREEN[i] = i; + } +} + +void long2() { + byte* SCREEN = $0400; + for(byte i : 0..10) { + asm { + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + } + SCREEN[i] = i; + } + +} \ No newline at end of file diff --git a/src/test/java/dk/camelot64/kickc/test/ref/halfscii.asm b/src/test/java/dk/camelot64/kickc/test/ref/halfscii.asm index 8a0358c44..460555425 100644 --- a/src/test/java/dk/camelot64/kickc/test/ref/halfscii.asm +++ b/src/test/java/dk/camelot64/kickc/test/ref/halfscii.asm @@ -126,11 +126,15 @@ main: { !: lda chargen+1 cmp #>CHARGEN+$800 - bcc b1 + bcs !b+ + jmp b1 + !b: bne !+ lda chargen cmp #CHARGEN+$800 - bcc b1 + bcs !b+ + jmp b1 + !b: bne !+ lda chargen cmp #