1
0
mirror of https://gitlab.com/camelot/kickc.git synced 2024-10-15 09:23:47 +00:00

Isolated KickAss assembly during test. Fixed long branches. Closes #62

This commit is contained in:
jespergravgaard 2018-02-13 03:34:01 +01:00
parent 0d6ce93e93
commit 2db042801b
15 changed files with 1183 additions and 237 deletions

View File

@ -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));

View File

@ -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 <var> or sta <var> 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_xlo[x]+plot_ylo[y]; >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 | <yoffs;
+ (callconstparam.kc) Constant parameters to multiple calls are mixed together because their versioned names (x0#0, X0#1) are shortened to not include the version and only one version is output in the ASM.
+ (callconstparam.kc) Constant call parameters are stored as values in the called function - not at the call. The reference from the call to the constant does not include the function name, so the result is an unknown symbol error when compiling the ASM.
+ Inline constants that are only call parameters used once (eg. x0#0 and x0#1 in callconstparam.kc)
+ Ensured that assignment to variables with ALU potential does not affect potential registers through clobbering.
+ (const-identification.kc) Constants are not identified correctly. Some times constant pointers are treated as variables Eg. byte* SCREEN = $0400 is adressed through zero-page even though it can be adressed directly.
+ Add support for inline asm
+ Syntax: asm { asm... } - using real ASM syntax inline!
+ Support ASM syntax check etc.
+ Also perform ASM clobber analysis when assigning registers.
+ Move the main code into a main() function. The main function per default has no parameters and exits with RTS.
+ (incd020.kc) Increment/decrement of value pointed to by pointer does not work. eg. byte* BGCOL = $d020; (*BGCOL)++;
+ All pointer derefs *ptr should per default be inlined as rValues/lValues in early passes (PointerDereference).
+ Alternatively: Selectively inline some pointer derefs that can be optimized in ASM
+ Change pointer derefs ASM signature to _deref_xxx (from _star_xxx)

View File

@ -3,13 +3,13 @@ package dk.camelot64.kickc.asm;
/** A line of 6502 assembler code */
public interface AsmLine {
public int getLineBytes();
int getLineBytes();
public double getLineCycles();
double getLineCycles();
public String getAsm();
String getAsm();
public int getIndex();
int getIndex();
void setIndex(int index);
}

View File

@ -175,9 +175,14 @@ public class AsmProgram {
}
public String toString(boolean comments) {
return toString(new AsmPrintState(comments));
return toString(new AsmPrintState(comments, false));
}
public String toString(boolean comments, boolean lineIdx) {
return toString(new AsmPrintState(comments, lineIdx));
}
public String toString(AsmPrintState printState) {
StringBuilder out = new StringBuilder();
for(AsmSegment segment : segments) {
@ -191,12 +196,38 @@ public class AsmProgram {
return toString(true);
}
/**
* Set the index of the next line
* @param nextIndex The index of the next line
*/
public void setNextLineIndex(int nextIndex) {
this.nextLineIndex = nextIndex;
}
/**
* Get ASM segment by line index
* @param idx The index of the line to get the segment for
* @return The segment with the line that has the passed index. Null if not found
*/
public AsmSegment getAsmSegment(int idx) {
for(AsmSegment segment : segments) {
for(AsmLine asmLine : segment.getLines()) {
if(asmLine.getIndex()==idx) {
return segment;
}
}
}
return null;
}
static class AsmPrintState {
boolean comments;
boolean lineIdx;
String indent;
public AsmPrintState(boolean comments) {
public AsmPrintState(boolean comments, boolean lineIdx) {
this.comments = comments;
this.lineIdx = lineIdx;
this.indent = "";
}
@ -218,6 +249,9 @@ public class AsmProgram {
return indent;
}
public boolean getLineIdx() {
return lineIdx;
}
}

View File

@ -4,6 +4,8 @@ import dk.camelot64.kickc.model.PhiTransitions;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;
/**
* A segment of an ASM program. The segment has a number of methods/attributes that describe the lines of the segment.
@ -57,6 +59,23 @@ public class AsmSegment {
lines.add(line);
}
/**
* Add a new line just after another line
* @param line The line to look for. If it is not found an Exception is thrown
* @param add The line to add
*/
public void addLineAfter(AsmLine line, AsmLine add) {
ListIterator<AsmLine> 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));
}
}

View File

@ -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<String> 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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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:
!:
lda #$37
sta PROCPORT

View File

@ -2173,6 +2173,8 @@ Succesful ASM optimization Pass5RelabelLongLabels
Removing instruction jmp b1
Removing instruction jmp b6
Succesful ASM optimization Pass5NextJumpElimination
Fixing long branch [128] bcc b1 to bcs
Fixing long branch [134] bcc b1 to bcs
FINAL SYMBOL TABLE
(label) @1
@ -2289,7 +2291,7 @@ reg byte a [ main::bits_gen#7 ]
FINAL ASSEMBLER
Score: 3054
Score: 3114
//SEG0 Basic Upstart
.pc = $801 "Basic"
@ -2496,11 +2498,15 @@ main: {
//SEG76 [51] if((byte*) main::chargen#1<(const byte*) CHARGEN#0+(word/signed word/dword/signed dword) 2048) goto main::@1 [ main::chargen#1 main::charset4#1 ] ( main:2 [ main::chargen#1 main::charset4#1 ] ) -- pbuz1_lt_pbuc1_then_la1
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:
!:
//SEG77 main::@11
//SEG78 [52] *((const byte*) PROCPORT#0) ← (byte/signed byte/word/signed word/dword/signed dword) 55 [ ] ( main:2 [ ] ) -- _deref_pbuc1=vbuc2

View File

@ -76,7 +76,9 @@ test_16u: {
adc i
sta i
cmp #$c
bne b1
beq !b+
jmp b1
!b:
rts
str: .text " / @"
str1: .text " = @"

View File

@ -5420,6 +5420,7 @@ Removing instruction jmp b1
Removing instruction jmp b1
Removing instruction jmp b1
Succesful ASM optimization Pass5NextJumpElimination
Fixing long branch [78] bne b1 to beq
FINAL SYMBOL TABLE
(label) @13
@ -5650,7 +5651,7 @@ reg byte a [ div8u::$1 ]
FINAL ASSEMBLER
Score: 30902
Score: 30932
//SEG0 Basic Upstart
.pc = $801 "Basic"
@ -5819,7 +5820,9 @@ test_16u: {
sta i
//SEG84 [38] if((byte) test_16u::i#1!=(byte/signed byte/word/signed word/dword/signed dword) 12) goto test_16u::@1 [ test_16u::i#1 div16u::rem#8 line_cursor#1 ] ( main:2::test_16u:9 [ test_16u::i#1 div16u::rem#8 line_cursor#1 ] ) -- vbuz1_neq_vbuc1_then_la1
cmp #$c
bne b1
beq !b+
jmp b1
!b:
//SEG85 test_16u::@return
//SEG86 [39] return [ ] ( main:2::test_16u:9 [ ] )
rts