1
0
mirror of https://gitlab.com/camelot/kickc.git synced 2024-09-08 17:54:40 +00:00

Implemented coalescing for main memory PHI registers.

This commit is contained in:
jespergravgaard 2019-10-19 23:08:30 +02:00
parent 40ade8e60e
commit fcc48de855
11 changed files with 127 additions and 61 deletions

View File

@ -589,14 +589,14 @@ public class Compiler {
}
// Register coalesce on assignment (saving bytes & cycles)
new Pass4ZeroPageCoalesceAssignment(program).coalesce();
new Pass4MemoryCoalesceAssignment(program).coalesce();
// Register coalesce on call graph (saving ZP)
new Pass4ZeroPageCoalesceCallGraph(program).coalesce();
new Pass4MemoryCoalesceCallGraph(program).coalesce();
if(enableZeroPageCoalasce) {
// Register coalesce using exhaustive search (saving even more ZP - but slow)
new Pass4ZeroPageCoalesceExhaustive(program).coalesce();
new Pass4MemoryCoalesceExhaustive(program).coalesce();
}
new Pass4RegistersFinalize(program).allocate(true);
new Pass4AssertZeropageAllocation(program).check();

View File

@ -134,8 +134,10 @@ public class DirectiveParserContext {
this.registerImpliesDirectives = new ArrayList<>();
this.typeDirectives = new HashMap<>();
this.typeDirectives.put(DirectiveType.ARRAY, Arrays.asList(new Directive.Const(), new Directive.MemoryArea(SymbolVariable.MemoryArea.MAIN_MEMORY, null), new Directive.Register(false, null)));
//this.typeDirectives.put(DirectiveType.POINTER, Arrays.asList(new Directive.MemoryArea(SymbolVariable.MemoryArea.ZEROPAGE_MEMORY, null)));
this.scopeDirectives = new HashMap<>();
this.scopeTypeDirectives = new HashMap<>();
//this.scopeDirectives.put(DirectiveScope.GLOBAL, Arrays.asList(new Directive.MemoryArea(SymbolVariable.MemoryArea.MAIN_MEMORY, null)));
}
/**

View File

@ -25,6 +25,8 @@ public class ZeroConstantValues {
zeroValues.put(memberVar.getRef(), zeroValue(memberVar.getType(), programScope));
}
return new ConstantStructValue(typeStruct, zeroValues);
} else if(type.equals(SymbolType.BOOLEAN)) {
return new ConstantBool(false);
} else if(type instanceof SymbolTypeIntegerFixed) {
return new ConstantInteger(0L, type);
} else if(type instanceof SymbolTypePointer) {

View File

@ -6,6 +6,7 @@ import dk.camelot64.kickc.model.LiveRangeEquivalenceClassSet;
import dk.camelot64.kickc.model.Program;
import dk.camelot64.kickc.model.statements.StatementAssignment;
import dk.camelot64.kickc.model.statements.StatementPhiBlock;
import dk.camelot64.kickc.model.symbols.Variable;
import dk.camelot64.kickc.model.types.SymbolType;
import dk.camelot64.kickc.model.values.ConstantValue;
import dk.camelot64.kickc.model.values.VariableRef;
@ -72,10 +73,16 @@ public class Pass3PhiMemCoalesce extends Pass2SsaOptimization {
VariableRef phiRVar = (VariableRef) phiRValue.getrValue();
LiveRangeEquivalenceClass rValEquivalenceClass = phiEquivalenceClasses.getOrCreateEquivalenceClass(phiRVar);
if(!rValEquivalenceClass.equals(equivalenceClass)) {
SymbolType varType = program.getScope().getVariable(variable).getType();
SymbolType rVarType = program.getScope().getVariable(phiRVar).getType();
Variable var = program.getScope().getVariable(variable);
Variable rVar = program.getScope().getVariable(phiRVar);
SymbolType varType = var.getType();
SymbolType rVarType = rVar.getType();
if(varType.getSizeBytes()==rVarType.getSizeBytes()) {
phiEquivalenceClasses.consolidate(equivalenceClass, rValEquivalenceClass);
if(var.getMemoryArea().equals(rVar.getMemoryArea())) {
phiEquivalenceClasses.consolidate(equivalenceClass, rValEquivalenceClass);
} else {
program.getLog().append("Not consolidating phi with different storage strategy "+variable.toString()+" "+phiRVar.toString());
}
} else {
program.getLog().append("Not consolidating phi with different size "+variable.toString()+" "+phiRVar.toString());
}

View File

@ -465,27 +465,46 @@ public class Pass4CodeGeneration {
// Add all memory variables
Collection<Variable> scopeVariables = scope.getAllVariables(false);
for(Variable variable : scopeVariables) {
if(variable.isStorageLoadStore() && variable.isMemoryAreaMain()) {
if(variable.isMemoryAreaMain()) {
// Skip PHI masters
if(variable.isStoragePhiMaster())
continue;
// Skip if already added
String asmName = variable.getAsmName() == null ? variable.getLocalName() : variable.getAsmName();
if(added.contains(asmName)) {
continue;
}
if(variable.getDeclaredMemoryAddress() == null) {
// Generate into the data segment
// Set segment
setCurrentSegment(variable.getDataSegment(), asm);
// Add any comments
generateComments(asm, variable.getComments());
// Add any alignment
Integer declaredAlignment = variable.getDeclaredAlignment();
if(declaredAlignment != null) {
String alignment = AsmFormat.getAsmNumber(declaredAlignment);
asm.addDataAlignment(alignment);
if(variable.isStorageLoadStore() || variable.isStoragePhiVersion() || variable.isStorageIntermediate()){
if(variable.getDeclaredMemoryAddress() == null) {
Registers.Register allocation = variable.getAllocation();
if(!(allocation instanceof Registers.RegisterMainMem)) {
throw new InternalError("Expected main memory allocation "+variable.toString(program));
}
Registers.RegisterMainMem registerMainMem = (Registers.RegisterMainMem) allocation;
if(!((Registers.RegisterMainMem) allocation).getVariableRef().equals(variable.getRef())) {
continue;
}
// Generate into the data segment
// Set segment
setCurrentSegment(variable.getDataSegment(), asm);
// Add any comments
generateComments(asm, variable.getComments());
// Add any alignment
Integer declaredAlignment = variable.getDeclaredAlignment();
if(declaredAlignment != null) {
String alignment = AsmFormat.getAsmNumber(declaredAlignment);
asm.addDataAlignment(alignment);
}
AsmDataChunk asmDataChunk = new AsmDataChunk();
addChunkData(asmDataChunk, ZeroConstantValues.zeroValue(variable.getType(), getScope()), variable.getType(), scopeRef);
asmDataChunk.addToAsm(AsmFormat.asmFix(asmName), asm);
}
AsmDataChunk asmDataChunk = new AsmDataChunk();
addChunkData(asmDataChunk, ZeroConstantValues.zeroValue(variable.getType(), getScope()), variable.getType(), scopeRef);
asmDataChunk.addToAsm(AsmFormat.asmFix(asmName), asm);
} else {
throw new InternalError("Not handled variable storage "+variable.toString());
}
added.add(asmName);
}
@ -632,6 +651,8 @@ public class Pass4CodeGeneration {
dataChunk.addDataNumeric(AsmDataNumeric.Type.DWORD, AsmFormat.getAsmConstant(program, value, 99, scopeRef), getEncoding(value));
} else if(valueType instanceof SymbolTypePointer) {
dataChunk.addDataNumeric(AsmDataNumeric.Type.WORD, AsmFormat.getAsmConstant(program, value, 99, scopeRef), getEncoding(value));
} else if(SymbolType.BOOLEAN.equals(valueType)) {
dataChunk.addDataNumeric(AsmDataNumeric.Type.BYTE, AsmFormat.getAsmConstant(program, value, 99, scopeRef), getEncoding(value));
} else {
throw new InternalError("Unhandled array element type " + valueType.toString() + " value " + value.toString(program));
}
@ -688,6 +709,20 @@ public class Pass4CodeGeneration {
asm.addLabelDecl(AsmFormat.asmFix(asmName), AsmFormat.getAsmNumber(scopeVar.getDeclaredMemoryAddress()));
added.add(asmName);
}
} else if(register instanceof Registers.RegisterMainMem && !((Registers.RegisterMainMem) register).getVariableRef().equals(scopeVar.getRef())) {
// Memory variable is not main var in the equivalence class - add a reference
String asmName = scopeVar.getAsmName();
Registers.RegisterMainMem memAllocation = (Registers.RegisterMainMem) register;
VariableRef memAllocationVarRef = memAllocation.getVariableRef();
Variable memAllocationVar = getScope().getVariable(memAllocationVarRef);
// Add the label declaration
String asmNameMemAllocVar = AsmFormat.getAsmParamName(memAllocationVar, scope);
if(!asmName.equals(asmNameMemAllocVar)) {
// Add any comments
generateComments(asm, scopeVar.getComments());
asm.addLabelDecl(AsmFormat.asmFix(asmName), asmNameMemAllocVar);
added.add(asmName);
}
}
}
}

View File

@ -14,12 +14,12 @@ import java.util.List;
import java.util.Set;
/**
* Coalesces zero page registers where their live ranges do not overlap.
* Coalesces memory registers where their live ranges do not overlap.
* A final step done after all other register optimizations and before ASM generation.
*/
public abstract class Pass4ZeroPageCoalesce extends Pass2Base {
public abstract class Pass4MemoryCoalesce extends Pass2Base {
public Pass4ZeroPageCoalesce(Program program) {
public Pass4MemoryCoalesce(Program program) {
super(program);
}
@ -57,6 +57,7 @@ public abstract class Pass4ZeroPageCoalesce extends Pass2Base {
static boolean canCoalesce(LiveRangeEquivalenceClass ec1, LiveRangeEquivalenceClass ec2, Collection<ScopeRef> threadHeads, Set<String> unknownFragments, Program program) {
return
canCoalesceNotEqual(ec1, ec2) &&
canCoalesceCompatible(ec1, ec2, program) &&
canCoalesceVolatile(ec1, ec2, program) &&
canCoalesceThreads(ec1, ec2, threadHeads, program) &&
canCoalesceClobber(ec1, ec2, unknownFragments, program);
@ -129,6 +130,30 @@ public abstract class Pass4ZeroPageCoalesce extends Pass2Base {
return threads;
}
/**
* Determines if two live range equivalence classes have compatible registers.
* The registers are compatible if they are in memory and have the same type and size.
*
* @param ec1 One equivalence class
* @param ec2 Another equivalence class
* @param program The program
* @return True if the two equivalence classes can be coalesced into one without problems.
*/
private static boolean canCoalesceCompatible(LiveRangeEquivalenceClass ec1, LiveRangeEquivalenceClass ec2, Program program) {
Registers.Register register1 = ec1.getRegister();
Registers.Register register2 = ec2.getRegister();
// Check the both registers are in memory
if(!register1.isMem() || !register2.isMem())
return false;
// Check the both registers have the same type
if(!register1.getType().equals(register2.getType()))
return false;
// Check the both registers have the same size
if(register1.getBytes() != register2.getBytes())
return false;
return true;
}
/**
* Determines if two live range equivalence classes can be coalesced without clobber.
* This is possible if they are both allocated to zero page, have the same size and the resulting ASM has no live range overlaps or clobber issues.
@ -141,15 +166,10 @@ public abstract class Pass4ZeroPageCoalesce extends Pass2Base {
*/
private static boolean canCoalesceClobber(LiveRangeEquivalenceClass ec1, LiveRangeEquivalenceClass ec2, Set<String> unknownFragments, Program program) {
Registers.Register register1 = ec1.getRegister();
Registers.Register register2 = ec2.getRegister();
if(register1.isZp() && register2.isZp() && register1.getBytes()==register2.getBytes()) {
// Both registers are on Zero Page & have the same zero page size
// Try out the coalesce to test if it works
RegisterCombination combination = new RegisterCombination();
combination.setRegister(ec2, register1);
return Pass4RegisterUpliftCombinations.generateCombinationAsm(combination, program, unknownFragments, ScopeRef.ROOT);
}
return false;
// Try out the coalesce to test if it works
RegisterCombination combination = new RegisterCombination();
combination.setRegister(ec2, register1);
return Pass4RegisterUpliftCombinations.generateCombinationAsm(combination, program, unknownFragments, ScopeRef.ROOT);
}
/**
@ -192,7 +212,7 @@ public abstract class Pass4ZeroPageCoalesce extends Pass2Base {
List<LiveRangeEquivalenceClass> equivalenceClasses = liveRangeEquivalenceClassSet.getEquivalenceClasses();
if(equivalenceClasses.contains(candidate.getEc1()) && equivalenceClasses.contains(candidate.getEc2())) {
// Both equivalence classes still exist
if(Pass4ZeroPageCoalesce.canCoalesce(candidate.getEc1(), candidate.getEc2(), threadHeads, unknownFragments, program)) {
if(Pass4MemoryCoalesce.canCoalesce(candidate.getEc1(), candidate.getEc2(), threadHeads, unknownFragments, program)) {
String scoreString = (candidate.getScore() == null) ? "" : (" - score: " + candidate.getScore());
program.getLog().append("Coalescing zero page register [ " + candidate.getEc1() + " ] with [ " + candidate.getEc2() + " ]" + scoreString);
liveRangeEquivalenceClassSet.consolidate(candidate.getEc1(), candidate.getEc2());

View File

@ -8,29 +8,29 @@ import dk.camelot64.kickc.model.statements.Statement;
import java.util.*;
/**
* Coalesces zero page registers where their live ranges do not overlap.
* Coalesces memory registers where their live ranges do not overlap.
* This step tries to coalesce lvalues with rvalues for all assignments - saving both cycles & bytes if successful.
*/
public class Pass4ZeroPageCoalesceAssignment extends Pass2Base {
public class Pass4MemoryCoalesceAssignment extends Pass2Base {
public Pass4ZeroPageCoalesceAssignment(Program program) {
public Pass4MemoryCoalesceAssignment(Program program) {
super(program);
}
public void coalesce() {
CoalesceVarScores coalesceVarScores = new CoalesceVarScores(getProgram());
LinkedHashSet<String> unknownFragments = new LinkedHashSet<>();
Collection<ScopeRef> threadHeads = Pass4ZeroPageCoalesce.getThreadHeads(getProgram());
Collection<ScopeRef> threadHeads = Pass4MemoryCoalesce.getThreadHeads(getProgram());
boolean change;
do {
change = false;
CoalesceLiveRangeEquivalenceClassScores equivalenceClassScores =
new CoalesceLiveRangeEquivalenceClassScores(getProgram(), coalesceVarScores);
List<Pass4ZeroPageCoalesce.LiveRangeEquivalenceClassCoalesceCandidate> coalesceCandidates =
List<Pass4MemoryCoalesce.LiveRangeEquivalenceClassCoalesceCandidate> coalesceCandidates =
equivalenceClassScores.getCoalesceCandidates();
for(Pass4ZeroPageCoalesce.LiveRangeEquivalenceClassCoalesceCandidate candidate : coalesceCandidates) {
change |= Pass4ZeroPageCoalesce.attemptCoalesce(candidate, threadHeads, unknownFragments, getProgram());
for(Pass4MemoryCoalesce.LiveRangeEquivalenceClassCoalesceCandidate candidate : coalesceCandidates) {
change |= Pass4MemoryCoalesce.attemptCoalesce(candidate, threadHeads, unknownFragments, getProgram());
}
} while(change);
@ -147,14 +147,14 @@ public class Pass4ZeroPageCoalesceAssignment extends Pass2Base {
*
* @return candidate pairs of live range equivalence classes for coalescing with a positive score
*/
public List<Pass4ZeroPageCoalesce.LiveRangeEquivalenceClassCoalesceCandidate> getCoalesceCandidates() {
ArrayList<Pass4ZeroPageCoalesce.LiveRangeEquivalenceClassCoalesceCandidate> candidates = new ArrayList<>();
public List<Pass4MemoryCoalesce.LiveRangeEquivalenceClassCoalesceCandidate> getCoalesceCandidates() {
ArrayList<Pass4MemoryCoalesce.LiveRangeEquivalenceClassCoalesceCandidate> candidates = new ArrayList<>();
for(LiveRangeEquivalenceClass ec1 : scores.keySet()) {
Map<LiveRangeEquivalenceClass, Integer> ec1Scores = scores.get(ec1);
for(LiveRangeEquivalenceClass ec2 : ec1Scores.keySet()) {
Integer score = ec1Scores.get(ec2);
Pass4ZeroPageCoalesce.LiveRangeEquivalenceClassCoalesceCandidate candidate =
new Pass4ZeroPageCoalesce.LiveRangeEquivalenceClassCoalesceCandidate(ec1, ec2, score);
Pass4MemoryCoalesce.LiveRangeEquivalenceClassCoalesceCandidate candidate =
new Pass4MemoryCoalesce.LiveRangeEquivalenceClassCoalesceCandidate(ec1, ec2, score);
if(!candidates.contains(candidate)) {
candidates.add(candidate);
}

View File

@ -10,22 +10,22 @@ import dk.camelot64.kickc.model.values.VariableRef;
import java.util.*;
/**
* Use the call graph to coalesce zero page registers.
* Use the call graph to coalesce memory registers.
* For each live range equivalence class:
* - Look up through the call graph and avoid all variables declared in the scopes there
* - Go through already handled live range equivalence classes and if any exist with no scope overlap with the call graph - try to coalesce
* - Add to the list of already handled live range equivalence classes
*/
public class Pass4ZeroPageCoalesceCallGraph extends Pass2Base {
public class Pass4MemoryCoalesceCallGraph extends Pass2Base {
public Pass4ZeroPageCoalesceCallGraph(Program program) {
public Pass4MemoryCoalesceCallGraph(Program program) {
super(program);
}
public void coalesce() {
LinkedHashSet<String> unknownFragments = new LinkedHashSet<>();
LiveRangeEquivalenceClassSet liveRangeEquivalenceClassSet = getProgram().getLiveRangeEquivalenceClassSet();
Collection<ScopeRef> threads = Pass4ZeroPageCoalesce.getThreadHeads(getProgram());
Collection<ScopeRef> threads = Pass4MemoryCoalesce.getThreadHeads(getProgram());
boolean modified;
do {
modified = coalesce(liveRangeEquivalenceClassSet, threads, unknownFragments);
@ -66,8 +66,8 @@ public class Pass4ZeroPageCoalesceCallGraph extends Pass2Base {
if(isScopeOverlap(otherEC, allCallingScopes))
continue;
// No scope overlap - attempt to coalesce
Pass4ZeroPageCoalesce.LiveRangeEquivalenceClassCoalesceCandidate candidate = new Pass4ZeroPageCoalesce.LiveRangeEquivalenceClassCoalesceCandidate(thisEC, otherEC, null);
if(Pass4ZeroPageCoalesce.attemptCoalesce(candidate, threadHeads, unknownFragments, getProgram())) {
Pass4MemoryCoalesce.LiveRangeEquivalenceClassCoalesceCandidate candidate = new Pass4MemoryCoalesce.LiveRangeEquivalenceClassCoalesceCandidate(thisEC, otherEC, null);
if(Pass4MemoryCoalesce.attemptCoalesce(candidate, threadHeads, unknownFragments, getProgram())) {
// Succesfully coalesced with already handled EC - move on to the next one!
coalesced = true;
modified = true;

View File

@ -10,20 +10,20 @@ import java.util.LinkedHashSet;
import java.util.Set;
/**
* Exhaustive attempt to coalesces zero page registers where live ranges do not overlap.
* Exhaustive attempt to coalesces memory registers where live ranges do not overlap.
* An optional final step done after all other register optimizations and before ASM generation.
* Performs an exhaustive search - so it can take a lot of time!
*/
public class Pass4ZeroPageCoalesceExhaustive extends Pass2Base {
public class Pass4MemoryCoalesceExhaustive extends Pass2Base {
public Pass4ZeroPageCoalesceExhaustive(Program program) {
public Pass4MemoryCoalesceExhaustive(Program program) {
super(program);
}
public void coalesce() {
LinkedHashSet<String> unknownFragments = new LinkedHashSet<>();
LiveRangeEquivalenceClassSet liveRangeEquivalenceClassSet = getProgram().getLiveRangeEquivalenceClassSet();
Collection<ScopeRef> threads = Pass4ZeroPageCoalesce.getThreadHeads(getProgram());
Collection<ScopeRef> threads = Pass4MemoryCoalesce.getThreadHeads(getProgram());
boolean change;
do {
change = coalesce(liveRangeEquivalenceClassSet, threads, unknownFragments);
@ -48,8 +48,8 @@ public class Pass4ZeroPageCoalesceExhaustive extends Pass2Base {
private boolean coalesce(LiveRangeEquivalenceClassSet liveRangeEquivalenceClassSet, Collection<ScopeRef> threadHeads, Set<String> unknownFragments) {
for(LiveRangeEquivalenceClass thisEquivalenceClass : liveRangeEquivalenceClassSet.getEquivalenceClasses()) {
for(LiveRangeEquivalenceClass otherEquivalenceClass : liveRangeEquivalenceClassSet.getEquivalenceClasses()) {
Pass4ZeroPageCoalesce.LiveRangeEquivalenceClassCoalesceCandidate candidate = new Pass4ZeroPageCoalesce.LiveRangeEquivalenceClassCoalesceCandidate(thisEquivalenceClass, otherEquivalenceClass, null);
boolean modified = Pass4ZeroPageCoalesce.attemptCoalesce(candidate, threadHeads, unknownFragments, getProgram());
Pass4MemoryCoalesce.LiveRangeEquivalenceClassCoalesceCandidate candidate = new Pass4MemoryCoalesce.LiveRangeEquivalenceClassCoalesceCandidate(thisEquivalenceClass, otherEquivalenceClass, null);
boolean modified = Pass4MemoryCoalesce.attemptCoalesce(candidate, threadHeads, unknownFragments, getProgram());
if(modified) {
return true;
}

View File

@ -77,7 +77,7 @@ public class Pass4RegistersFinalize extends Pass2Base {
if(reallocateZp) {
reallocateZpRegisters(liveRangeEquivalenceClassSet);
reallocateMemRegisters(liveRangeEquivalenceClassSet);
}
liveRangeEquivalenceClassSet.storeRegisterAllocation();
if(reallocateZp) {
@ -160,11 +160,11 @@ public class Pass4RegistersFinalize extends Pass2Base {
}
/**
* Reallocate all ZP registers to minimize ZP usage
* Reallocate all memory registers. Minimizes zeropage usage.
*
* @param liveRangeEquivalenceClassSet The
*/
private void reallocateZpRegisters(LiveRangeEquivalenceClassSet liveRangeEquivalenceClassSet) {
private void reallocateMemRegisters(LiveRangeEquivalenceClassSet liveRangeEquivalenceClassSet) {
for(LiveRangeEquivalenceClass equivalenceClass : liveRangeEquivalenceClassSet.getEquivalenceClasses()) {
Registers.Register register = equivalenceClass.getRegister();
boolean reallocate = true;

View File

@ -3369,7 +3369,7 @@ public class TestPrograms {
tester.testFile(filename, upliftCombinations, null);
}
private void compileAndCompare(String filename, CompileLog log, int upliftCombinations) throws IOException, URISyntaxException {
private void compileAndCompare(String filename, int upliftCombinations, CompileLog log) throws IOException, URISyntaxException {
TestPrograms tester = new TestPrograms();
tester.testFile(filename, upliftCombinations, log);
}