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

Fixed problem with passing dereferenced struct pointers as parameters. Closes #225

This commit is contained in:
jespergravgaard 2019-07-26 13:15:54 +02:00
parent 0c33353bf8
commit a1ef8ad163
15 changed files with 1722 additions and 79 deletions

View File

@ -0,0 +1 @@
and {c1},x

View File

@ -0,0 +1 @@
and {c1},y

View File

@ -1,9 +1,7 @@
package dk.camelot64.kickc.model.types;
import dk.camelot64.kickc.fragment.AsmFragmentInstanceSpec;
import dk.camelot64.kickc.model.CompileError;
import dk.camelot64.kickc.model.InternalError;
import dk.camelot64.kickc.model.Program;
import dk.camelot64.kickc.model.operators.OperatorBinary;
import dk.camelot64.kickc.model.operators.OperatorUnary;
import dk.camelot64.kickc.model.statements.StatementAssignment;

View File

@ -15,7 +15,10 @@ import dk.camelot64.kickc.model.types.SymbolTypePointer;
import dk.camelot64.kickc.model.types.SymbolTypeStruct;
import dk.camelot64.kickc.model.values.*;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.atomic.AtomicBoolean;
/** Convert all struct values that are not used as pointers (address-of used or declared volatile) into variables representing each member */
@ -28,45 +31,36 @@ public class Pass1UnwindStructValues extends Pass1Base {
@Override
public boolean step() {
boolean modified = false;
StructUnwinding structUnwinding = getProgram().getStructUnwinding();
if(structUnwinding == null) {
structUnwinding = new StructUnwinding();
getProgram().setStructUnwinding(structUnwinding);
if(getProgram().getStructUnwinding() == null) {
getProgram().setStructUnwinding(new StructUnwinding());
}
// Iterate through all scopes generating member-variables for each struct
modified |= unwindStructVariables(structUnwinding);
boolean modified = false;
modified |= unwindStructVariables();
// Unwind all procedure declaration parameters
modified |= unwindStructParameters(structUnwinding);
modified |= unwindStructParameters();
// Unwind all usages of struct values
modified |= unwindStructReferences(structUnwinding);
modified |= unwindStructReferences();
// Change all usages of members of struct values
modified |= unwindStructMemberReferences(structUnwinding);
modified |= unwindStructMemberReferences();
return modified;
}
/**
* Unwinds all usages of struct value references (in statements such as assignments.)
*
* @param structUnwinding Information about all unwound struct variables
*/
private boolean unwindStructReferences(StructUnwinding structUnwinding) {
private boolean unwindStructReferences() {
boolean modified = false;
for(ControlFlowBlock block : getGraph().getAllBlocks()) {
ListIterator<Statement> stmtIt = block.getStatements().listIterator();
while(stmtIt.hasNext()) {
Statement statement = stmtIt.next();
if(statement instanceof StatementAssignment) {
StatementAssignment assignment = (StatementAssignment) statement;
SymbolType lValueType = SymbolTypeInference.inferType(getScope(), assignment.getlValue());
if(lValueType instanceof SymbolTypeStruct) {
modified |= unwindAssignment(assignment, (SymbolTypeStruct) lValueType, stmtIt, block, structUnwinding);
}
modified |= unwindAssignment((StatementAssignment) statement, stmtIt, block);
} else if(statement instanceof StatementCall) {
modified |= unwindCall((StatementCall) statement, structUnwinding);
modified |= unwindCall((StatementCall) statement, stmtIt, block);
} else if(statement instanceof StatementReturn) {
modified |= unwindReturn((StatementReturn) statement, structUnwinding);
modified |= unwindReturn((StatementReturn) statement);
}
}
}
@ -75,16 +69,16 @@ public class Pass1UnwindStructValues extends Pass1Base {
/**
* Change all usages of members inside statements to the unwound member variables
*
* @param structUnwinding Information about all unwound struct variables
*/
private boolean unwindStructMemberReferences(StructUnwinding structUnwinding) {
private boolean unwindStructMemberReferences() {
StructUnwinding structUnwinding = getProgram().getStructUnwinding();
AtomicBoolean modified = new AtomicBoolean(false);
ProgramValueIterator.execute(
getProgram(), (programValue, currentStmt, stmtIt, currentBlock) ->
{
if(programValue.get() instanceof StructMemberRef) {
StructMemberRef structMemberRef = (StructMemberRef) programValue.get();
// TODO Extend to handle structs that are not VariableRefs (for instance pointer derefs)
if(structMemberRef.getStruct() instanceof VariableRef) {
Variable structVariable = getScope().getVariable((VariableRef) structMemberRef.getStruct());
StructUnwinding.VariableUnwinding memberVariables = structUnwinding.getVariableUnwinding(structVariable.getRef());
@ -104,12 +98,12 @@ public class Pass1UnwindStructValues extends Pass1Base {
* Unwind any call parameter that is a struct value into the member values
*
* @param call The call to unwind
* @param structUnwinding Information about all unwound struct variables
*/
private boolean unwindCall(StatementCall call, StructUnwinding structUnwinding) {
private boolean unwindCall(StatementCall call, ListIterator<Statement> stmtIt, ControlFlowBlock currentBlock) {
StructUnwinding structUnwinding = getProgram().getStructUnwinding();
// Unwind struct value return value
boolean lvalUnwound = false;
// TODO Extend to handle any struct (not just VariableRefs - but also pointer derefs to structs)
if(call.getlValue() instanceof VariableRef) {
Variable lvalueVar = getScope().getVariable((VariableRef) call.getlValue());
if(lvalueVar.getType() instanceof SymbolTypeStruct) {
@ -132,19 +126,14 @@ public class Pass1UnwindStructValues extends Pass1Base {
boolean anyParameterUnwound = false;
for(RValue parameter : call.getParameters()) {
boolean unwound = false;
if(parameter instanceof VariableRef) {
Variable variable = getScope().getVariable((VariableRef) parameter);
if(variable.getType() instanceof SymbolTypeStruct) {
// Passing a struct variable - convert it to member variables
StructUnwinding.VariableUnwinding variableUnwinding = structUnwinding.getVariableUnwinding((VariableRef) parameter);
if(variableUnwinding != null) {
for(String memberName : variableUnwinding.getMemberNames()) {
unwoundParameters.add(variableUnwinding.getMemberUnwinding(memberName));
}
unwound = true;
anyParameterUnwound = true;
}
StructUnwinding.StructMemberUnwinding parameterUnwinding = getStructMemberUnwinding(parameter, call, stmtIt, currentBlock);
if(parameterUnwinding != null && parameterUnwinding != POSTPONE_UNWINDING) {
// Passing a struct variable - convert it to member variables
for(String memberName : parameterUnwinding.getMemberNames()) {
unwoundParameters.add(parameterUnwinding.getMemberUnwinding(memberName));
}
unwound = true;
anyParameterUnwound = true;
}
if(!unwound) {
unwoundParameters.add(parameter);
@ -162,15 +151,16 @@ public class Pass1UnwindStructValues extends Pass1Base {
* Unwind any return value that is a struct value into the member values
*
* @param statementReturn The return to unwind
* @param structUnwinding Information about all unwound struct variables
*/
private boolean unwindReturn(StatementReturn statementReturn, StructUnwinding structUnwinding) {
private boolean unwindReturn(StatementReturn statementReturn) {
boolean modified = false;
// Unwind struct value return value
// TODO Extend to handle structs that are not variables - eg. pointer derefs
if(statementReturn.getValue() instanceof VariableRef) {
Variable returnValueVar = getScope().getVariable((VariableRef) statementReturn.getValue());
if(returnValueVar.getType() instanceof SymbolTypeStruct) {
StructUnwinding structUnwinding = getProgram().getStructUnwinding();
StructUnwinding.VariableUnwinding returnVarUnwinding = structUnwinding.getVariableUnwinding(returnValueVar.getRef());
if(returnVarUnwinding != null) {
ArrayList<RValue> unwoundMembers = new ArrayList<>();
@ -187,13 +177,10 @@ public class Pass1UnwindStructValues extends Pass1Base {
return modified;
}
/**
* Iterate through all procedures changing parameter lists by unwinding each struct value parameter to the unwound member variables
*
* @param structUnwinding Information about all unwound struct variables (including procedure parameters)
*/
private boolean unwindStructParameters(StructUnwinding structUnwinding) {
private boolean unwindStructParameters() {
boolean modified = false;
// Iterate through all procedures changing parameter lists by unwinding each struct value parameter
for(Procedure procedure : getScope().getAllProcedures(true)) {
@ -201,6 +188,7 @@ public class Pass1UnwindStructValues extends Pass1Base {
boolean procedureUnwound = false;
for(Variable parameter : procedure.getParameters()) {
if(parameter.getType() instanceof SymbolTypeStruct) {
StructUnwinding structUnwinding = getProgram().getStructUnwinding();
StructUnwinding.VariableUnwinding parameterUnwinding = structUnwinding.getVariableUnwinding(parameter.getRef());
for(String memberName : parameterUnwinding.getMemberNames()) {
VariableRef memberUnwinding = (VariableRef) parameterUnwinding.getMemberUnwinding(memberName);
@ -225,11 +213,12 @@ public class Pass1UnwindStructValues extends Pass1Base {
*
* @return Information about all unwound struct variables
*/
private boolean unwindStructVariables(StructUnwinding structUnwinding) {
private boolean unwindStructVariables() {
boolean modified = false;
// Iterate through all scopes generating member-variables for each struct
for(Variable variable : getScope().getAllVariables(true)) {
if(variable.getType() instanceof SymbolTypeStruct) {
StructUnwinding structUnwinding = getProgram().getStructUnwinding();
if(structUnwinding.getVariableUnwinding(variable.getRef()) == null) {
// A non-volatile struct variable
Scope scope = variable.getScope();
@ -263,24 +252,20 @@ public class Pass1UnwindStructValues extends Pass1Base {
* Unwind an assignment to a struct value variable into assignment of each member
*
* @param assignment The assignment statement
* @param structType The struct type being unwound
* @param stmtIt The statement iterator used for adding/removing statements
* @param currentBlock The current code block
* @param structUnwinding Information about unwound struct value variables
*/
private boolean unwindAssignment(StatementAssignment assignment, SymbolTypeStruct structType, ListIterator<Statement> stmtIt, ControlFlowBlock currentBlock, StructUnwinding structUnwinding) {
boolean modified = false;
private boolean unwindAssignment(StatementAssignment assignment, ListIterator<Statement> stmtIt, ControlFlowBlock currentBlock) {
StructUnwinding.StructMemberUnwinding memberUnwinding = getStructMemberUnwinding(assignment.getlValue(), assignment, stmtIt, currentBlock);
StructUnwinding.StructMemberUnwinding memberUnwinding = getStructMemberUnwinding(assignment.getlValue(), structType, structUnwinding, assignment, stmtIt, currentBlock);
if(memberUnwinding==POSTPONE_UNWINDING) {
if(memberUnwinding == null) {
return false;
} else if(memberUnwinding == POSTPONE_UNWINDING) {
return true;
}
if(memberUnwinding == null) {
throw new CompileError("Cannot unwind struct assignment " + assignment.toString(getProgram(), false), assignment);
}
if(assignment.getOperator() == null && assignment.getrValue2() instanceof StructZero && assignment.getlValue() instanceof VariableRef) {
// TODO MAYBE Extend to handle non-variable lvalues
// Zero-initializing a struct - unwind to assigning zero to each member!
List<RValue> membersUnwound = new ArrayList<>();
stmtIt.previous();
@ -294,11 +279,12 @@ public class Pass1UnwindStructValues extends Pass1Base {
}
stmtIt.next();
if(assignment.getlValue() instanceof VariableRef) {
SymbolTypeStruct structType = (SymbolTypeStruct) SymbolTypeInference.inferType(getScope(), assignment.getlValue());
assignment.setrValue2(new StructUnwoundPlaceholder(structType, membersUnwound));
} else {
stmtIt.remove();
}
modified = true;
return true;
} else if(assignment.getOperator() == null && assignment.getrValue2() instanceof ValueList) {
// Initializing struct with a value list - unwind to assigning each member with a value from the list
ValueList valueList = (ValueList) assignment.getrValue2();
@ -317,21 +303,26 @@ public class Pass1UnwindStructValues extends Pass1Base {
}
stmtIt.next();
if(assignment.getlValue() instanceof VariableRef) {
SymbolTypeStruct structType = (SymbolTypeStruct) SymbolTypeInference.inferType(getScope(), assignment.getlValue());
assignment.setrValue2(new StructUnwoundPlaceholder(structType, membersUnwound));
// TODO MAYBE Extend to handle non-variable lvalues
} else {
stmtIt.remove();
}
modified = true;
return true;
} else if(assignment.getOperator() == null) {
if(assignment.getrValue2() instanceof StructUnwoundPlaceholder)
return false;
SymbolTypeStruct structType = (SymbolTypeStruct) SymbolTypeInference.inferType(getScope(), assignment.getlValue());
SymbolType sourceType = SymbolTypeInference.inferType(getScope(), assignment.getrValue2());
if(sourceType.equals(structType)) {
// Copying a struct - unwind to assigning each member!
StructUnwinding.StructMemberUnwinding sourceMemberUnwinding = getStructMemberUnwinding((LValue) assignment.getrValue2(), structType, structUnwinding, assignment, stmtIt, currentBlock);
if(sourceMemberUnwinding==POSTPONE_UNWINDING)
modified = true;
if(sourceMemberUnwinding != null && sourceMemberUnwinding!=POSTPONE_UNWINDING) {
StructUnwinding.StructMemberUnwinding sourceMemberUnwinding = getStructMemberUnwinding((LValue) assignment.getrValue2(), assignment, stmtIt, currentBlock);
if(sourceMemberUnwinding == null) {
throw new CompileError("Incompatible struct assignment " + assignment.toString(getProgram(), false), assignment);
} else if(sourceMemberUnwinding == POSTPONE_UNWINDING) {
return true;
} else {
List<RValue> membersUnwound = new ArrayList<>();
stmtIt.previous();
for(String memberName : memberUnwinding.getMemberNames()) {
@ -344,11 +335,12 @@ public class Pass1UnwindStructValues extends Pass1Base {
}
stmtIt.next();
if(assignment.getlValue() instanceof VariableRef) {
// TODO MAYBE Extend to handle non-variable lvalues
assignment.setrValue2(new StructUnwoundPlaceholder(structType, membersUnwound));
} else {
stmtIt.remove();
}
modified = true;
return true;
}
} else {
throw new CompileError("Incompatible struct assignment " + assignment.toString(getProgram(), false), assignment);
@ -356,25 +348,40 @@ public class Pass1UnwindStructValues extends Pass1Base {
} else {
throw new CompileError("Incompatible struct assignment " + assignment.toString(getProgram(), false), assignment);
}
return modified;
}
private StructUnwinding.StructMemberUnwinding getStructMemberUnwinding(LValue lValue, SymbolTypeStruct lValueType, StructUnwinding structUnwinding, Statement currentStmt, ListIterator<Statement> stmtIt, ControlFlowBlock currentBlock) {
if(lValue instanceof VariableRef) {
return structUnwinding.getVariableUnwinding((VariableRef) lValue);
} else if(lValue instanceof StructMemberRef && ((StructMemberRef) lValue).getStruct() instanceof VariableRef) {
return POSTPONE_UNWINDING;
} else if(lValue instanceof PointerDereferenceSimple) {
return new StructMemberUnwindingPointerDerefSimple((PointerDereferenceSimple) lValue, lValueType.getStructDefinition(getScope()), stmtIt, currentBlock, currentStmt);
} else if(lValue instanceof PointerDereferenceIndexed) {
return new StructMemberUnwindingPointerDerefIndexed((PointerDereferenceIndexed) lValue, lValueType.getStructDefinition(getScope()), stmtIt, currentBlock, currentStmt);
/**
* Examine a value - and if it represents a struct get the unwinding information for the struct members
*
* @param value The struct value
* @param currentStmt Program Context information. Current statement.
* @param stmtIt Program Context information. Statement list iterator.
* @param currentBlock Program Context information. Current block
* @return null if the value is not a struct. Unwinding for the passed value if it is a struct. {@link #POSTPONE_UNWINDING} if the struct is not ready for unwinding yet.
*/
private StructUnwinding.StructMemberUnwinding getStructMemberUnwinding(RValue value, Statement currentStmt, ListIterator<Statement> stmtIt, ControlFlowBlock currentBlock) {
SymbolType lValueType = SymbolTypeInference.inferType(getScope(), value);
if(lValueType instanceof SymbolTypeStruct) {
SymbolTypeStruct structType = (SymbolTypeStruct) lValueType;
if(value instanceof VariableRef) {
StructUnwinding structUnwinding = getProgram().getStructUnwinding();
return structUnwinding.getVariableUnwinding((VariableRef) value);
} else if(value instanceof StructMemberRef && ((StructMemberRef) value).getStruct() instanceof VariableRef) {
return POSTPONE_UNWINDING;
} else if(value instanceof PointerDereferenceSimple) {
return new StructMemberUnwindingPointerDerefSimple((PointerDereferenceSimple) value, structType.getStructDefinition(getScope()), stmtIt, currentBlock, currentStmt);
} else if(value instanceof PointerDereferenceIndexed) {
return new StructMemberUnwindingPointerDerefIndexed((PointerDereferenceIndexed) value, structType.getStructDefinition(getScope()), stmtIt, currentBlock, currentStmt);
} else {
throw new InternalError("Struct unwinding not implemented for " + value.toString(getProgram()));
}
} else {
throw new InternalError("Struct unwinding not implemented for " + lValue.toString(getProgram()));
return null;
}
}
/** Singleton signalling that unwinding should be postponed. */
public static final StructUnwinding.StructMemberUnwinding POSTPONE_UNWINDING = new StructUnwinding.StructMemberUnwinding() {
private static final StructUnwinding.StructMemberUnwinding POSTPONE_UNWINDING = new StructUnwinding.StructMemberUnwinding() {
@Override
public List<String> getMemberNames() {
return null;

View File

@ -35,6 +35,11 @@ public class TestPrograms {
public TestPrograms() {
}
@Test
public void testArray16bitLookup() throws IOException, URISyntaxException {
compileAndCompare("array-16bit-lookup");
}
@Test
public void testZeropageExhausted() throws IOException, URISyntaxException {
assertError("zeropage-exhausted", "Variables used in program do not fit on zeropage", false);
@ -75,12 +80,10 @@ public class TestPrograms {
compileAndCompare("problem-pointer-inside-struct-sizeof-rewriting");
}
/*
@Test
public void testProblemStructPointerParam() throws IOException, URISyntaxException {
compileAndCompare("problem-struct-pointer-param");
}
*/
/*
@Test

View File

@ -0,0 +1,14 @@
// Test KickC performance for 16-bit array lookup function from article "Optimizing C array lookups for the 6502"
// http://8bitworkshop.com/blog/compilers/2019/03/17/cc65-optimization.html
void main() {
unsigned int* SCREEN = 0x0400;
for(unsigned char idx : 0..128)
SCREEN[idx] = getValue(idx);
}
unsigned int[128] arr16;
unsigned int getValue(unsigned int index) {
return (unsigned int)((arr16[index & 0x7f] & 0xff) >> 1);
}

View File

@ -11,6 +11,7 @@ struct Point {
void main() {
struct Point point = { 1, 2 };
struct Point* ptr = &point;
print(point);
print(*ptr);
}

View File

@ -0,0 +1,44 @@
// Test KickC performance for 16-bit array lookup function from article "Optimizing C array lookups for the 6502"
// http://8bitworkshop.com/blog/compilers/2019/03/17/cc65-optimization.html
.pc = $801 "Basic"
:BasicUpstart(main)
.pc = $80d "Program"
main: {
.label SCREEN = $400
.label _0 = 4
ldx #0
b1:
txa
sta getValue.index
lda #0
sta getValue.index+1
jsr getValue
txa
asl
tay
lda _0
sta SCREEN,y
lda _0+1
sta SCREEN+1,y
inx
cpx #$81
bne b1
rts
}
// getValue(word zeropage(2) index)
getValue: {
.label index = 2
.label return = 4
lda index
and #$7f
asl
tay
lda #$ff
and arr16,y
lsr
sta return
lda #0
sta return+1
rts
}
arr16: .fill 2*$80, 0

View File

@ -0,0 +1,38 @@
@begin: scope:[] from
[0] phi()
to:@1
@1: scope:[] from @begin
[1] phi()
[2] call main
to:@end
@end: scope:[] from @1
[3] phi()
main: scope:[main] from @1
[4] phi()
to:main::@1
main::@1: scope:[main] from main main::@2
[5] (byte) main::idx#2 ← phi( main/(byte) 0 main::@2/(byte) main::idx#1 )
[6] (word) getValue::index#0 ← (byte) main::idx#2
[7] call getValue
[8] (word) getValue::return#0 ← (word) getValue::return#1
to:main::@2
main::@2: scope:[main] from main::@1
[9] (word~) main::$0 ← (word) getValue::return#0
[10] (byte~) main::$2 ← (byte) main::idx#2 << (byte) 1
[11] *((const word*) main::SCREEN#0 + (byte~) main::$2) ← (word~) main::$0
[12] (byte) main::idx#1 ← ++ (byte) main::idx#2
[13] if((byte) main::idx#1!=(byte) $81) goto main::@1
to:main::@return
main::@return: scope:[main] from main::@2
[14] return
to:@return
getValue: scope:[getValue] from main::@1
[15] (byte~) getValue::$0 ← (word) getValue::index#0 & (byte) $7f
[16] (byte~) getValue::$4 ← (byte~) getValue::$0 << (byte) 1
[17] (byte~) getValue::$1 ← *((const word[$80]) arr16#0 + (byte~) getValue::$4) & (byte) $ff
[18] (byte~) getValue::$2 ← (byte~) getValue::$1 >> (byte) 1
[19] (word) getValue::return#1 ← (word)(byte~) getValue::$2
to:getValue::@return
getValue::@return: scope:[getValue] from getValue
[20] return
to:@return

View File

@ -0,0 +1,704 @@
Fixing pointer array-indexing *((word*) main::SCREEN + (byte) main::idx)
Fixing pointer array-indexing *((word[$80]) arr16 + (number~) getValue::$0)
Identified constant variable (word*) main::SCREEN
Culled Empty Block (label) main::@2
Culled Empty Block (label) getValue::@1
CONTROL FLOW GRAPH SSA
@begin: scope:[] from
to:@1
main: scope:[main] from @2
(word*) main::SCREEN#0 ← ((word*)) (number) $400
(byte) main::idx#0 ← (byte) 0
to:main::@1
main::@1: scope:[main] from main main::@3
(byte) main::idx#2 ← phi( main/(byte) main::idx#0 main::@3/(byte) main::idx#1 )
(word) getValue::index#0 ← (byte) main::idx#2
call getValue
(word) getValue::return#0 ← (word) getValue::return#2
to:main::@3
main::@3: scope:[main] from main::@1
(byte) main::idx#3 ← phi( main::@1/(byte) main::idx#2 )
(word) getValue::return#3 ← phi( main::@1/(word) getValue::return#0 )
(word~) main::$0 ← (word) getValue::return#3
(byte~) main::$2 ← (byte) main::idx#3 * (const byte) SIZEOF_WORD
*((word*) main::SCREEN#0 + (byte~) main::$2) ← (word~) main::$0
(byte) main::idx#1 ← (byte) main::idx#3 + rangenext(0,$80)
(bool~) main::$1 ← (byte) main::idx#1 != rangelast(0,$80)
if((bool~) main::$1) goto main::@1
to:main::@return
main::@return: scope:[main] from main::@3
return
to:@return
@1: scope:[] from @begin
(word[$80]) arr16#0 ← { fill( $80, 0) }
to:@2
getValue: scope:[getValue] from main::@1
(word) getValue::index#1 ← phi( main::@1/(word) getValue::index#0 )
(number~) getValue::$0 ← (word) getValue::index#1 & (number) $7f
(number~) getValue::$4 ← (number~) getValue::$0 * (const byte) SIZEOF_WORD
(number~) getValue::$1 ← *((word[$80]) arr16#0 + (number~) getValue::$4) & (number) $ff
(number~) getValue::$2 ← (number~) getValue::$1 >> (number) 1
(word~) getValue::$3 ← ((word)) (number~) getValue::$2
(word) getValue::return#1 ← (word~) getValue::$3
to:getValue::@return
getValue::@return: scope:[getValue] from getValue
(word) getValue::return#4 ← phi( getValue/(word) getValue::return#1 )
(word) getValue::return#2 ← (word) getValue::return#4
return
to:@return
@2: scope:[] from @1
call main
to:@3
@3: scope:[] from @2
to:@end
@end: scope:[] from @3
SYMBOL TABLE SSA
(label) @1
(label) @2
(label) @3
(label) @begin
(label) @end
(const byte) SIZEOF_WORD = (byte) 2
(word[$80]) arr16
(word[$80]) arr16#0
(word()) getValue((word) getValue::index)
(number~) getValue::$0
(number~) getValue::$1
(number~) getValue::$2
(word~) getValue::$3
(number~) getValue::$4
(label) getValue::@return
(word) getValue::index
(word) getValue::index#0
(word) getValue::index#1
(word) getValue::return
(word) getValue::return#0
(word) getValue::return#1
(word) getValue::return#2
(word) getValue::return#3
(word) getValue::return#4
(void()) main()
(word~) main::$0
(bool~) main::$1
(byte~) main::$2
(label) main::@1
(label) main::@3
(label) main::@return
(word*) main::SCREEN
(word*) main::SCREEN#0
(byte) main::idx
(byte) main::idx#0
(byte) main::idx#1
(byte) main::idx#2
(byte) main::idx#3
Adding number conversion cast (unumber) $7f in (number~) getValue::$0 ← (word) getValue::index#1 & (number) $7f
Adding number conversion cast (unumber) getValue::$0 in (number~) getValue::$0 ← (word) getValue::index#1 & (unumber)(number) $7f
Adding number conversion cast (unumber) getValue::$4 in (number~) getValue::$4 ← (unumber~) getValue::$0 * (const byte) SIZEOF_WORD
Adding number conversion cast (unumber) $ff in (number~) getValue::$1 ← *((word[$80]) arr16#0 + (unumber~) getValue::$4) & (number) $ff
Adding number conversion cast (unumber) getValue::$1 in (number~) getValue::$1 ← *((word[$80]) arr16#0 + (unumber~) getValue::$4) & (unumber)(number) $ff
Adding number conversion cast (unumber) 1 in (number~) getValue::$2 ← (unumber~) getValue::$1 >> (number) 1
Adding number conversion cast (unumber) getValue::$2 in (number~) getValue::$2 ← (unumber~) getValue::$1 >> (unumber)(number) 1
Successful SSA optimization PassNAddNumberTypeConversions
Inlining cast (word*) main::SCREEN#0 ← (word*)(number) $400
Inlining cast (word~) getValue::$3 ← (word)(unumber~) getValue::$2
Successful SSA optimization Pass2InlineCast
Simplifying constant pointer cast (word*) 1024
Simplifying constant integer cast $7f
Simplifying constant integer cast $ff
Simplifying constant integer cast 1
Successful SSA optimization PassNCastSimplification
Finalized unsigned number type (byte) $7f
Finalized unsigned number type (byte) $ff
Finalized unsigned number type (byte) 1
Successful SSA optimization PassNFinalizeNumberTypeConversions
Inferred type updated to byte in (unumber~) getValue::$0 ← (word) getValue::index#1 & (byte) $7f
Inferred type updated to byte in (unumber~) getValue::$4 ← (byte~) getValue::$0 * (const byte) SIZEOF_WORD
Inferred type updated to byte in (unumber~) getValue::$1 ← *((word[$80]) arr16#0 + (byte~) getValue::$4) & (byte) $ff
Inferred type updated to byte in (unumber~) getValue::$2 ← (byte~) getValue::$1 >> (byte) 1
Alias (word) getValue::return#0 = (word) getValue::return#3
Alias (byte) main::idx#2 = (byte) main::idx#3
Alias (word) getValue::return#1 = (word~) getValue::$3 (word) getValue::return#4 (word) getValue::return#2
Successful SSA optimization Pass2AliasElimination
Identical Phi Values (word) getValue::index#1 (word) getValue::index#0
Successful SSA optimization Pass2IdenticalPhiElimination
Simple Condition (bool~) main::$1 [12] if((byte) main::idx#1!=rangelast(0,$80)) goto main::@1
Successful SSA optimization Pass2ConditionalJumpSimplification
Constant right-side identified [14] (word[$80]) arr16#0 ← { fill( $80, 0) }
Successful SSA optimization Pass2ConstantRValueConsolidation
Constant (const word*) main::SCREEN#0 = (word*) 1024
Constant (const byte) main::idx#0 = 0
Constant (const word[$80]) arr16#0 = { fill( $80, 0) }
Successful SSA optimization Pass2ConstantIdentification
Resolved ranged next value [10] main::idx#1 ← ++ main::idx#2 to ++
Resolved ranged comparison value [12] if(main::idx#1!=rangelast(0,$80)) goto main::@1 to (number) $81
Adding number conversion cast (unumber) $81 in if((byte) main::idx#1!=(number) $81) goto main::@1
Successful SSA optimization PassNAddNumberTypeConversions
Simplifying constant integer cast $81
Successful SSA optimization PassNCastSimplification
Finalized unsigned number type (byte) $81
Successful SSA optimization PassNFinalizeNumberTypeConversions
Rewriting multiplication to use shift [5] (byte~) main::$2 ← (byte) main::idx#2 * (const byte) SIZEOF_WORD
Rewriting multiplication to use shift [11] (byte~) getValue::$4 ← (byte~) getValue::$0 * (const byte) SIZEOF_WORD
Successful SSA optimization Pass2MultiplyToShiftRewriting
Inlining constant with var siblings (const byte) main::idx#0
Constant inlined main::idx#0 = (byte) 0
Successful SSA optimization Pass2ConstantInlining
Eliminating unused constant (const byte) SIZEOF_WORD
Successful SSA optimization PassNEliminateUnusedVars
Added new block during phi lifting main::@4(between main::@3 and main::@1)
Adding NOP phi() at start of @begin
Adding NOP phi() at start of @1
Adding NOP phi() at start of @2
Adding NOP phi() at start of @3
Adding NOP phi() at start of @end
Adding NOP phi() at start of main
CALL GRAPH
Calls in [] to main:3
Calls in [main] to getValue:9
Created 1 initial phi equivalence classes
Coalesced [17] main::idx#4 ← main::idx#1
Coalesced down to 1 phi equivalence classes
Culled Empty Block (label) @1
Culled Empty Block (label) @3
Culled Empty Block (label) main::@4
Renumbering block @2 to @1
Renumbering block main::@3 to main::@2
Adding NOP phi() at start of @begin
Adding NOP phi() at start of @1
Adding NOP phi() at start of @end
Adding NOP phi() at start of main
FINAL CONTROL FLOW GRAPH
@begin: scope:[] from
[0] phi()
to:@1
@1: scope:[] from @begin
[1] phi()
[2] call main
to:@end
@end: scope:[] from @1
[3] phi()
main: scope:[main] from @1
[4] phi()
to:main::@1
main::@1: scope:[main] from main main::@2
[5] (byte) main::idx#2 ← phi( main/(byte) 0 main::@2/(byte) main::idx#1 )
[6] (word) getValue::index#0 ← (byte) main::idx#2
[7] call getValue
[8] (word) getValue::return#0 ← (word) getValue::return#1
to:main::@2
main::@2: scope:[main] from main::@1
[9] (word~) main::$0 ← (word) getValue::return#0
[10] (byte~) main::$2 ← (byte) main::idx#2 << (byte) 1
[11] *((const word*) main::SCREEN#0 + (byte~) main::$2) ← (word~) main::$0
[12] (byte) main::idx#1 ← ++ (byte) main::idx#2
[13] if((byte) main::idx#1!=(byte) $81) goto main::@1
to:main::@return
main::@return: scope:[main] from main::@2
[14] return
to:@return
getValue: scope:[getValue] from main::@1
[15] (byte~) getValue::$0 ← (word) getValue::index#0 & (byte) $7f
[16] (byte~) getValue::$4 ← (byte~) getValue::$0 << (byte) 1
[17] (byte~) getValue::$1 ← *((const word[$80]) arr16#0 + (byte~) getValue::$4) & (byte) $ff
[18] (byte~) getValue::$2 ← (byte~) getValue::$1 >> (byte) 1
[19] (word) getValue::return#1 ← (word)(byte~) getValue::$2
to:getValue::@return
getValue::@return: scope:[getValue] from getValue
[20] return
to:@return
VARIABLE REGISTER WEIGHTS
(word[$80]) arr16
(word()) getValue((word) getValue::index)
(byte~) getValue::$0 4.0
(byte~) getValue::$1 4.0
(byte~) getValue::$2 2.0
(byte~) getValue::$4 4.0
(word) getValue::index
(word) getValue::index#0 13.0
(word) getValue::return
(word) getValue::return#0 22.0
(word) getValue::return#1 4.333333333333333
(void()) main()
(word~) main::$0 11.0
(byte~) main::$2 22.0
(word*) main::SCREEN
(byte) main::idx
(byte) main::idx#1 16.5
(byte) main::idx#2 6.285714285714286
Initial phi equivalence classes
[ main::idx#2 main::idx#1 ]
Added variable getValue::index#0 to zero page equivalence class [ getValue::index#0 ]
Added variable getValue::return#0 to zero page equivalence class [ getValue::return#0 ]
Added variable main::$0 to zero page equivalence class [ main::$0 ]
Added variable main::$2 to zero page equivalence class [ main::$2 ]
Added variable getValue::$0 to zero page equivalence class [ getValue::$0 ]
Added variable getValue::$4 to zero page equivalence class [ getValue::$4 ]
Added variable getValue::$1 to zero page equivalence class [ getValue::$1 ]
Added variable getValue::$2 to zero page equivalence class [ getValue::$2 ]
Added variable getValue::return#1 to zero page equivalence class [ getValue::return#1 ]
Complete equivalence classes
[ main::idx#2 main::idx#1 ]
[ getValue::index#0 ]
[ getValue::return#0 ]
[ main::$0 ]
[ main::$2 ]
[ getValue::$0 ]
[ getValue::$4 ]
[ getValue::$1 ]
[ getValue::$2 ]
[ getValue::return#1 ]
Allocated zp ZP_BYTE:2 [ main::idx#2 main::idx#1 ]
Allocated zp ZP_WORD:3 [ getValue::index#0 ]
Allocated zp ZP_WORD:5 [ getValue::return#0 ]
Allocated zp ZP_WORD:7 [ main::$0 ]
Allocated zp ZP_BYTE:9 [ main::$2 ]
Allocated zp ZP_BYTE:10 [ getValue::$0 ]
Allocated zp ZP_BYTE:11 [ getValue::$4 ]
Allocated zp ZP_BYTE:12 [ getValue::$1 ]
Allocated zp ZP_BYTE:13 [ getValue::$2 ]
Allocated zp ZP_WORD:14 [ getValue::return#1 ]
INITIAL ASM
Target platform is c64basic
// File Comments
// Test KickC performance for 16-bit array lookup function from article "Optimizing C array lookups for the 6502"
// http://8bitworkshop.com/blog/compilers/2019/03/17/cc65-optimization.html
// Upstart
.pc = $801 "Basic"
:BasicUpstart(bbegin)
.pc = $80d "Program"
// Global Constants & labels
// @begin
bbegin:
// [1] phi from @begin to @1 [phi:@begin->@1]
b1_from_bbegin:
jmp b1
// @1
b1:
// [2] call main
// [4] phi from @1 to main [phi:@1->main]
main_from_b1:
jsr main
// [3] phi from @1 to @end [phi:@1->@end]
bend_from_b1:
jmp bend
// @end
bend:
// main
main: {
.label SCREEN = $400
.label _0 = 7
.label _2 = 9
.label idx = 2
// [5] phi from main to main::@1 [phi:main->main::@1]
b1_from_main:
// [5] phi (byte) main::idx#2 = (byte) 0 [phi:main->main::@1#0] -- vbuz1=vbuc1
lda #0
sta idx
jmp b1
// [5] phi from main::@2 to main::@1 [phi:main::@2->main::@1]
b1_from_b2:
// [5] phi (byte) main::idx#2 = (byte) main::idx#1 [phi:main::@2->main::@1#0] -- register_copy
jmp b1
// main::@1
b1:
// [6] (word) getValue::index#0 ← (byte) main::idx#2 -- vwuz1=vbuz2
lda idx
sta getValue.index
lda #0
sta getValue.index+1
// [7] call getValue
jsr getValue
// [8] (word) getValue::return#0 ← (word) getValue::return#1 -- vwuz1=vwuz2
lda getValue.return_1
sta getValue.return
lda getValue.return_1+1
sta getValue.return+1
jmp b2
// main::@2
b2:
// [9] (word~) main::$0 ← (word) getValue::return#0 -- vwuz1=vwuz2
lda getValue.return
sta _0
lda getValue.return+1
sta _0+1
// [10] (byte~) main::$2 ← (byte) main::idx#2 << (byte) 1 -- vbuz1=vbuz2_rol_1
lda idx
asl
sta _2
// [11] *((const word*) main::SCREEN#0 + (byte~) main::$2) ← (word~) main::$0 -- pwuc1_derefidx_vbuz1=vwuz2
ldy _2
lda _0
sta SCREEN,y
lda _0+1
sta SCREEN+1,y
// [12] (byte) main::idx#1 ← ++ (byte) main::idx#2 -- vbuz1=_inc_vbuz1
inc idx
// [13] if((byte) main::idx#1!=(byte) $81) goto main::@1 -- vbuz1_neq_vbuc1_then_la1
lda #$81
cmp idx
bne b1_from_b2
jmp breturn
// main::@return
breturn:
// [14] return
rts
}
// getValue
// getValue(word zeropage(3) index)
getValue: {
.label _0 = $a
.label _1 = $c
.label _2 = $d
.label _4 = $b
.label index = 3
.label return = 5
.label return_1 = $e
// [15] (byte~) getValue::$0 ← (word) getValue::index#0 & (byte) $7f -- vbuz1=vwuz2_band_vbuc1
lda index
and #$7f
sta _0
// [16] (byte~) getValue::$4 ← (byte~) getValue::$0 << (byte) 1 -- vbuz1=vbuz2_rol_1
lda _0
asl
sta _4
// [17] (byte~) getValue::$1 ← *((const word[$80]) arr16#0 + (byte~) getValue::$4) & (byte) $ff -- vbuz1=pwuc1_derefidx_vbuz2_band_vbuc2
lda #$ff
ldy _4
and arr16,y
sta _1
// [18] (byte~) getValue::$2 ← (byte~) getValue::$1 >> (byte) 1 -- vbuz1=vbuz2_ror_1
lda _1
lsr
sta _2
// [19] (word) getValue::return#1 ← (word)(byte~) getValue::$2 -- vwuz1=_word_vbuz2
lda _2
sta return_1
lda #0
sta return_1+1
jmp breturn
// getValue::@return
breturn:
// [20] return
rts
}
// File Data
arr16: .fill 2*$80, 0
REGISTER UPLIFT POTENTIAL REGISTERS
Statement [6] (word) getValue::index#0 ← (byte) main::idx#2 [ main::idx#2 getValue::index#0 ] ( main:2 [ main::idx#2 getValue::index#0 ] ) always clobbers reg byte a
Removing always clobbered register reg byte a as potential for zp ZP_BYTE:2 [ main::idx#2 main::idx#1 ]
Statement [8] (word) getValue::return#0 ← (word) getValue::return#1 [ main::idx#2 getValue::return#0 ] ( main:2 [ main::idx#2 getValue::return#0 ] ) always clobbers reg byte a
Statement [9] (word~) main::$0 ← (word) getValue::return#0 [ main::idx#2 main::$0 ] ( main:2 [ main::idx#2 main::$0 ] ) always clobbers reg byte a
Statement [10] (byte~) main::$2 ← (byte) main::idx#2 << (byte) 1 [ main::idx#2 main::$0 main::$2 ] ( main:2 [ main::idx#2 main::$0 main::$2 ] ) always clobbers reg byte a
Statement [11] *((const word*) main::SCREEN#0 + (byte~) main::$2) ← (word~) main::$0 [ main::idx#2 ] ( main:2 [ main::idx#2 ] ) always clobbers reg byte a
Statement [15] (byte~) getValue::$0 ← (word) getValue::index#0 & (byte) $7f [ getValue::$0 ] ( main:2::getValue:7 [ main::idx#2 getValue::$0 ] ) always clobbers reg byte a
Statement [16] (byte~) getValue::$4 ← (byte~) getValue::$0 << (byte) 1 [ getValue::$4 ] ( main:2::getValue:7 [ main::idx#2 getValue::$4 ] ) always clobbers reg byte a
Statement [17] (byte~) getValue::$1 ← *((const word[$80]) arr16#0 + (byte~) getValue::$4) & (byte) $ff [ getValue::$1 ] ( main:2::getValue:7 [ main::idx#2 getValue::$1 ] ) always clobbers reg byte a
Statement [18] (byte~) getValue::$2 ← (byte~) getValue::$1 >> (byte) 1 [ getValue::$2 ] ( main:2::getValue:7 [ main::idx#2 getValue::$2 ] ) always clobbers reg byte a
Statement [19] (word) getValue::return#1 ← (word)(byte~) getValue::$2 [ getValue::return#1 ] ( main:2::getValue:7 [ main::idx#2 getValue::return#1 ] ) always clobbers reg byte a
Statement [6] (word) getValue::index#0 ← (byte) main::idx#2 [ main::idx#2 getValue::index#0 ] ( main:2 [ main::idx#2 getValue::index#0 ] ) always clobbers reg byte a
Statement [8] (word) getValue::return#0 ← (word) getValue::return#1 [ main::idx#2 getValue::return#0 ] ( main:2 [ main::idx#2 getValue::return#0 ] ) always clobbers reg byte a
Statement [9] (word~) main::$0 ← (word) getValue::return#0 [ main::idx#2 main::$0 ] ( main:2 [ main::idx#2 main::$0 ] ) always clobbers reg byte a
Statement [10] (byte~) main::$2 ← (byte) main::idx#2 << (byte) 1 [ main::idx#2 main::$0 main::$2 ] ( main:2 [ main::idx#2 main::$0 main::$2 ] ) always clobbers reg byte a
Statement [11] *((const word*) main::SCREEN#0 + (byte~) main::$2) ← (word~) main::$0 [ main::idx#2 ] ( main:2 [ main::idx#2 ] ) always clobbers reg byte a
Statement [15] (byte~) getValue::$0 ← (word) getValue::index#0 & (byte) $7f [ getValue::$0 ] ( main:2::getValue:7 [ main::idx#2 getValue::$0 ] ) always clobbers reg byte a
Statement [16] (byte~) getValue::$4 ← (byte~) getValue::$0 << (byte) 1 [ getValue::$4 ] ( main:2::getValue:7 [ main::idx#2 getValue::$4 ] ) always clobbers reg byte a
Statement [17] (byte~) getValue::$1 ← *((const word[$80]) arr16#0 + (byte~) getValue::$4) & (byte) $ff [ getValue::$1 ] ( main:2::getValue:7 [ main::idx#2 getValue::$1 ] ) always clobbers reg byte a
Statement [18] (byte~) getValue::$2 ← (byte~) getValue::$1 >> (byte) 1 [ getValue::$2 ] ( main:2::getValue:7 [ main::idx#2 getValue::$2 ] ) always clobbers reg byte a
Statement [19] (word) getValue::return#1 ← (word)(byte~) getValue::$2 [ getValue::return#1 ] ( main:2::getValue:7 [ main::idx#2 getValue::return#1 ] ) always clobbers reg byte a
Potential registers zp ZP_BYTE:2 [ main::idx#2 main::idx#1 ] : zp ZP_BYTE:2 , reg byte x , reg byte y ,
Potential registers zp ZP_WORD:3 [ getValue::index#0 ] : zp ZP_WORD:3 ,
Potential registers zp ZP_WORD:5 [ getValue::return#0 ] : zp ZP_WORD:5 ,
Potential registers zp ZP_WORD:7 [ main::$0 ] : zp ZP_WORD:7 ,
Potential registers zp ZP_BYTE:9 [ main::$2 ] : zp ZP_BYTE:9 , reg byte a , reg byte x , reg byte y ,
Potential registers zp ZP_BYTE:10 [ getValue::$0 ] : zp ZP_BYTE:10 , reg byte a , reg byte x , reg byte y ,
Potential registers zp ZP_BYTE:11 [ getValue::$4 ] : zp ZP_BYTE:11 , reg byte a , reg byte x , reg byte y ,
Potential registers zp ZP_BYTE:12 [ getValue::$1 ] : zp ZP_BYTE:12 , reg byte a , reg byte x , reg byte y ,
Potential registers zp ZP_BYTE:13 [ getValue::$2 ] : zp ZP_BYTE:13 , reg byte a , reg byte x , reg byte y ,
Potential registers zp ZP_WORD:14 [ getValue::return#1 ] : zp ZP_WORD:14 ,
REGISTER UPLIFT SCOPES
Uplift Scope [main] 22.79: zp ZP_BYTE:2 [ main::idx#2 main::idx#1 ] 22: zp ZP_BYTE:9 [ main::$2 ] 11: zp ZP_WORD:7 [ main::$0 ]
Uplift Scope [getValue] 22: zp ZP_WORD:5 [ getValue::return#0 ] 13: zp ZP_WORD:3 [ getValue::index#0 ] 4.33: zp ZP_WORD:14 [ getValue::return#1 ] 4: zp ZP_BYTE:10 [ getValue::$0 ] 4: zp ZP_BYTE:11 [ getValue::$4 ] 4: zp ZP_BYTE:12 [ getValue::$1 ] 2: zp ZP_BYTE:13 [ getValue::$2 ]
Uplift Scope []
Uplifting [main] best 899 combination reg byte x [ main::idx#2 main::idx#1 ] reg byte a [ main::$2 ] zp ZP_WORD:7 [ main::$0 ]
Uplifting [getValue] best 877 combination zp ZP_WORD:5 [ getValue::return#0 ] zp ZP_WORD:3 [ getValue::index#0 ] zp ZP_WORD:14 [ getValue::return#1 ] reg byte a [ getValue::$0 ] reg byte a [ getValue::$4 ] reg byte a [ getValue::$1 ] reg byte a [ getValue::$2 ]
Limited combination testing to 100 combinations of 256 possible.
Uplifting [] best 877 combination
Coalescing zero page register with common assignment [ zp ZP_WORD:5 [ getValue::return#0 ] ] with [ zp ZP_WORD:7 [ main::$0 ] ] - score: 1
Coalescing zero page register with common assignment [ zp ZP_WORD:5 [ getValue::return#0 main::$0 ] ] with [ zp ZP_WORD:14 [ getValue::return#1 ] ] - score: 1
Allocated (was zp ZP_WORD:3) zp ZP_WORD:2 [ getValue::index#0 ]
Allocated (was zp ZP_WORD:5) zp ZP_WORD:4 [ getValue::return#0 main::$0 getValue::return#1 ]
ASSEMBLER BEFORE OPTIMIZATION
// File Comments
// Test KickC performance for 16-bit array lookup function from article "Optimizing C array lookups for the 6502"
// http://8bitworkshop.com/blog/compilers/2019/03/17/cc65-optimization.html
// Upstart
.pc = $801 "Basic"
:BasicUpstart(bbegin)
.pc = $80d "Program"
// Global Constants & labels
// @begin
bbegin:
// [1] phi from @begin to @1 [phi:@begin->@1]
b1_from_bbegin:
jmp b1
// @1
b1:
// [2] call main
// [4] phi from @1 to main [phi:@1->main]
main_from_b1:
jsr main
// [3] phi from @1 to @end [phi:@1->@end]
bend_from_b1:
jmp bend
// @end
bend:
// main
main: {
.label SCREEN = $400
.label _0 = 4
// [5] phi from main to main::@1 [phi:main->main::@1]
b1_from_main:
// [5] phi (byte) main::idx#2 = (byte) 0 [phi:main->main::@1#0] -- vbuxx=vbuc1
ldx #0
jmp b1
// [5] phi from main::@2 to main::@1 [phi:main::@2->main::@1]
b1_from_b2:
// [5] phi (byte) main::idx#2 = (byte) main::idx#1 [phi:main::@2->main::@1#0] -- register_copy
jmp b1
// main::@1
b1:
// [6] (word) getValue::index#0 ← (byte) main::idx#2 -- vwuz1=vbuxx
txa
sta getValue.index
lda #0
sta getValue.index+1
// [7] call getValue
jsr getValue
// [8] (word) getValue::return#0 ← (word) getValue::return#1
jmp b2
// main::@2
b2:
// [9] (word~) main::$0 ← (word) getValue::return#0
// [10] (byte~) main::$2 ← (byte) main::idx#2 << (byte) 1 -- vbuaa=vbuxx_rol_1
txa
asl
// [11] *((const word*) main::SCREEN#0 + (byte~) main::$2) ← (word~) main::$0 -- pwuc1_derefidx_vbuaa=vwuz1
tay
lda _0
sta SCREEN,y
lda _0+1
sta SCREEN+1,y
// [12] (byte) main::idx#1 ← ++ (byte) main::idx#2 -- vbuxx=_inc_vbuxx
inx
// [13] if((byte) main::idx#1!=(byte) $81) goto main::@1 -- vbuxx_neq_vbuc1_then_la1
cpx #$81
bne b1_from_b2
jmp breturn
// main::@return
breturn:
// [14] return
rts
}
// getValue
// getValue(word zeropage(2) index)
getValue: {
.label index = 2
.label return = 4
// [15] (byte~) getValue::$0 ← (word) getValue::index#0 & (byte) $7f -- vbuaa=vwuz1_band_vbuc1
lda index
and #$7f
// [16] (byte~) getValue::$4 ← (byte~) getValue::$0 << (byte) 1 -- vbuaa=vbuaa_rol_1
asl
// [17] (byte~) getValue::$1 ← *((const word[$80]) arr16#0 + (byte~) getValue::$4) & (byte) $ff -- vbuaa=pwuc1_derefidx_vbuaa_band_vbuc2
tay
lda #$ff
and arr16,y
// [18] (byte~) getValue::$2 ← (byte~) getValue::$1 >> (byte) 1 -- vbuaa=vbuaa_ror_1
lsr
// [19] (word) getValue::return#1 ← (word)(byte~) getValue::$2 -- vwuz1=_word_vbuaa
sta return
lda #0
sta return+1
jmp breturn
// getValue::@return
breturn:
// [20] return
rts
}
// File Data
arr16: .fill 2*$80, 0
ASSEMBLER OPTIMIZATIONS
Removing instruction jmp b1
Removing instruction jmp bend
Removing instruction jmp b1
Removing instruction jmp b2
Removing instruction jmp breturn
Removing instruction jmp breturn
Succesful ASM optimization Pass5NextJumpElimination
Replacing label b1_from_b2 with b1
Removing instruction b1_from_bbegin:
Removing instruction b1:
Removing instruction main_from_b1:
Removing instruction bend_from_b1:
Removing instruction b1_from_b2:
Succesful ASM optimization Pass5RedundantLabelElimination
Removing instruction bend:
Removing instruction b1_from_main:
Removing instruction b2:
Removing instruction breturn:
Removing instruction breturn:
Succesful ASM optimization Pass5UnusedLabelElimination
Updating BasicUpstart to call main directly
Removing instruction jsr main
Succesful ASM optimization Pass5SkipBegin
Removing instruction jmp b1
Succesful ASM optimization Pass5NextJumpElimination
Removing instruction bbegin:
Succesful ASM optimization Pass5UnusedLabelElimination
FINAL SYMBOL TABLE
(label) @1
(label) @begin
(label) @end
(word[$80]) arr16
(const word[$80]) arr16#0 arr16 = { fill( $80, 0) }
(word()) getValue((word) getValue::index)
(byte~) getValue::$0 reg byte a 4.0
(byte~) getValue::$1 reg byte a 4.0
(byte~) getValue::$2 reg byte a 2.0
(byte~) getValue::$4 reg byte a 4.0
(label) getValue::@return
(word) getValue::index
(word) getValue::index#0 index zp ZP_WORD:2 13.0
(word) getValue::return
(word) getValue::return#0 return zp ZP_WORD:4 22.0
(word) getValue::return#1 return zp ZP_WORD:4 4.333333333333333
(void()) main()
(word~) main::$0 $0 zp ZP_WORD:4 11.0
(byte~) main::$2 reg byte a 22.0
(label) main::@1
(label) main::@2
(label) main::@return
(word*) main::SCREEN
(const word*) main::SCREEN#0 SCREEN = (word*) 1024
(byte) main::idx
(byte) main::idx#1 reg byte x 16.5
(byte) main::idx#2 reg byte x 6.285714285714286
reg byte x [ main::idx#2 main::idx#1 ]
zp ZP_WORD:2 [ getValue::index#0 ]
zp ZP_WORD:4 [ getValue::return#0 main::$0 getValue::return#1 ]
reg byte a [ main::$2 ]
reg byte a [ getValue::$0 ]
reg byte a [ getValue::$4 ]
reg byte a [ getValue::$1 ]
reg byte a [ getValue::$2 ]
FINAL ASSEMBLER
Score: 502
// File Comments
// Test KickC performance for 16-bit array lookup function from article "Optimizing C array lookups for the 6502"
// http://8bitworkshop.com/blog/compilers/2019/03/17/cc65-optimization.html
// Upstart
.pc = $801 "Basic"
:BasicUpstart(main)
.pc = $80d "Program"
// Global Constants & labels
// @begin
// [1] phi from @begin to @1 [phi:@begin->@1]
// @1
// [2] call main
// [4] phi from @1 to main [phi:@1->main]
// [3] phi from @1 to @end [phi:@1->@end]
// @end
// main
main: {
.label SCREEN = $400
.label _0 = 4
// [5] phi from main to main::@1 [phi:main->main::@1]
// [5] phi (byte) main::idx#2 = (byte) 0 [phi:main->main::@1#0] -- vbuxx=vbuc1
ldx #0
// [5] phi from main::@2 to main::@1 [phi:main::@2->main::@1]
// [5] phi (byte) main::idx#2 = (byte) main::idx#1 [phi:main::@2->main::@1#0] -- register_copy
// main::@1
b1:
// getValue(idx)
// [6] (word) getValue::index#0 ← (byte) main::idx#2 -- vwuz1=vbuxx
txa
sta getValue.index
lda #0
sta getValue.index+1
// [7] call getValue
jsr getValue
// [8] (word) getValue::return#0 ← (word) getValue::return#1
// main::@2
// [9] (word~) main::$0 ← (word) getValue::return#0
// SCREEN[idx] = getValue(idx)
// [10] (byte~) main::$2 ← (byte) main::idx#2 << (byte) 1 -- vbuaa=vbuxx_rol_1
txa
asl
// [11] *((const word*) main::SCREEN#0 + (byte~) main::$2) ← (word~) main::$0 -- pwuc1_derefidx_vbuaa=vwuz1
tay
lda _0
sta SCREEN,y
lda _0+1
sta SCREEN+1,y
// for(unsigned char idx : 0..128)
// [12] (byte) main::idx#1 ← ++ (byte) main::idx#2 -- vbuxx=_inc_vbuxx
inx
// [13] if((byte) main::idx#1!=(byte) $81) goto main::@1 -- vbuxx_neq_vbuc1_then_la1
cpx #$81
bne b1
// main::@return
// }
// [14] return
rts
}
// getValue
// getValue(word zeropage(2) index)
getValue: {
.label index = 2
.label return = 4
// index & 0x7f
// [15] (byte~) getValue::$0 ← (word) getValue::index#0 & (byte) $7f -- vbuaa=vwuz1_band_vbuc1
lda index
and #$7f
// arr16[index & 0x7f] & 0xff
// [16] (byte~) getValue::$4 ← (byte~) getValue::$0 << (byte) 1 -- vbuaa=vbuaa_rol_1
asl
// [17] (byte~) getValue::$1 ← *((const word[$80]) arr16#0 + (byte~) getValue::$4) & (byte) $ff -- vbuaa=pwuc1_derefidx_vbuaa_band_vbuc2
tay
lda #$ff
and arr16,y
// (arr16[index & 0x7f] & 0xff) >> 1
// [18] (byte~) getValue::$2 ← (byte~) getValue::$1 >> (byte) 1 -- vbuaa=vbuaa_ror_1
lsr
// (unsigned int)((arr16[index & 0x7f] & 0xff) >> 1)
// [19] (word) getValue::return#1 ← (word)(byte~) getValue::$2 -- vwuz1=_word_vbuaa
sta return
lda #0
sta return+1
// getValue::@return
// }
// [20] return
rts
}
// File Data
arr16: .fill 2*$80, 0

View File

@ -0,0 +1,36 @@
(label) @1
(label) @begin
(label) @end
(word[$80]) arr16
(const word[$80]) arr16#0 arr16 = { fill( $80, 0) }
(word()) getValue((word) getValue::index)
(byte~) getValue::$0 reg byte a 4.0
(byte~) getValue::$1 reg byte a 4.0
(byte~) getValue::$2 reg byte a 2.0
(byte~) getValue::$4 reg byte a 4.0
(label) getValue::@return
(word) getValue::index
(word) getValue::index#0 index zp ZP_WORD:2 13.0
(word) getValue::return
(word) getValue::return#0 return zp ZP_WORD:4 22.0
(word) getValue::return#1 return zp ZP_WORD:4 4.333333333333333
(void()) main()
(word~) main::$0 $0 zp ZP_WORD:4 11.0
(byte~) main::$2 reg byte a 22.0
(label) main::@1
(label) main::@2
(label) main::@return
(word*) main::SCREEN
(const word*) main::SCREEN#0 SCREEN = (word*) 1024
(byte) main::idx
(byte) main::idx#1 reg byte x 16.5
(byte) main::idx#2 reg byte x 6.285714285714286
reg byte x [ main::idx#2 main::idx#1 ]
zp ZP_WORD:2 [ getValue::index#0 ]
zp ZP_WORD:4 [ getValue::return#0 main::$0 getValue::return#1 ]
reg byte a [ main::$2 ]
reg byte a [ getValue::$0 ]
reg byte a [ getValue::$4 ]
reg byte a [ getValue::$1 ]
reg byte a [ getValue::$2 ]

View File

@ -0,0 +1,37 @@
// Demonstrates problem with passing struct pointer deref as parameter to call
.pc = $801 "Basic"
:BasicUpstart(main)
.pc = $80d "Program"
.const OFFSET_STRUCT_POINT_Y = 1
.label SCREEN = $400
.label idx = 2
main: {
.label ptr = point_x
.label point_x = 3
.label point_y = 4
lda #1
sta point_x
lda #2
sta point_y
ldy point_x
tax
lda #0
sta idx
jsr print
ldy ptr
ldx ptr+OFFSET_STRUCT_POINT_Y
jsr print
rts
}
// print(byte register(Y) p_x, byte register(X) p_y)
print: {
tya
ldy idx
sta SCREEN,y
iny
txa
sta SCREEN,y
iny
sty idx
rts
}

View File

@ -0,0 +1,36 @@
@begin: scope:[] from
[0] phi()
to:@1
@1: scope:[] from @begin
[1] phi()
[2] call main
to:@end
@end: scope:[] from @1
[3] phi()
main: scope:[main] from @1
[4] (byte) main::point_x#0 ← (byte) 1
[5] (byte) main::point_y#0 ← (byte) 2
[6] (byte) print::p_x#0 ← (byte) main::point_x#0
[7] (byte) print::p_y#0 ← (byte) main::point_y#0
[8] call print
to:main::@1
main::@1: scope:[main] from main
[9] (byte) print::p_x#1 ← *((byte*)(const struct Point*) main::ptr#0)
[10] (byte) print::p_y#1 ← *((byte*)(const struct Point*) main::ptr#0+(const byte) OFFSET_STRUCT_POINT_Y)
[11] call print
to:main::@return
main::@return: scope:[main] from main::@1
[12] return
to:@return
print: scope:[print] from main main::@1
[13] (byte) print::p_y#2 ← phi( main/(byte) print::p_y#0 main::@1/(byte) print::p_y#1 )
[13] (byte) idx#11 ← phi( main/(byte) 0 main::@1/(byte) idx#12 )
[13] (byte) print::p_x#2 ← phi( main/(byte) print::p_x#0 main::@1/(byte) print::p_x#1 )
[14] *((const byte*) SCREEN#0 + (byte) idx#11) ← (byte) print::p_x#2
[15] (byte) idx#4 ← ++ (byte) idx#11
[16] *((const byte*) SCREEN#0 + (byte) idx#4) ← (byte) print::p_y#2
[17] (byte) idx#12 ← ++ (byte) idx#4
to:print::@return
print::@return: scope:[print] from print
[18] return
to:@return

View File

@ -0,0 +1,683 @@
Setting inferred volatile on symbol affected by address-of (struct Point*~) main::$0 ← & (struct Point) main::point
Created struct value member variable (byte) main::point_x
Created struct value member variable (byte) main::point_y
Converted struct value to member variables (struct Point) main::point
Created struct value member variable (byte) print::p_x
Created struct value member variable (byte) print::p_y
Converted struct value to member variables (struct Point) print::p
Converted procedure struct value parameter to member variables (void()) print((byte) print::p_x , (byte) print::p_y)
Adding struct value list initializer (byte) main::point_x ← (number) 1
Adding struct value list initializer (byte) main::point_y ← (number) 2
Converted procedure struct value parameter to member variables in call (void~) main::$1 ← call print (byte) main::point_x (byte) main::point_y
Converted procedure struct value parameter to member variables in call (void~) main::$2 ← call print *((byte*) main::$3) *((byte*) main::$4)
Replacing struct member reference (struct Point) print::p.x with member variable reference (byte) print::p_x
Replacing struct member reference (struct Point) print::p.y with member variable reference (byte) print::p_y
Culled Empty Block (label) @1
Adding versioned struct unwinding for (struct Point) main::point#0
CONTROL FLOW GRAPH SSA
@begin: scope:[] from
(byte*) SCREEN#0 ← ((byte*)) (number) $400
(byte) idx#0 ← (number) 0
to:@2
main: scope:[main] from @2
(byte) idx#14 ← phi( @2/(byte) idx#15 )
(byte) main::point_x#0 ← (number) 1
(byte) main::point_y#0 ← (number) 2
(struct Point) main::point#0 ← struct-unwound {(byte) main::point_x#0, (byte) main::point_y#0}
(struct Point*~) main::$0 ← & (struct Point) main::point#0
(struct Point*) main::ptr#0 ← (struct Point*~) main::$0
(byte) print::p_x#0 ← (byte) main::point_x#0
(byte) print::p_y#0 ← (byte) main::point_y#0
call print
to:main::@1
main::@1: scope:[main] from main
(struct Point*) main::ptr#2 ← phi( main/(struct Point*) main::ptr#0 )
(byte) idx#8 ← phi( main/(byte) idx#6 )
(byte) idx#1 ← (byte) idx#8
(byte) print::p_x#1 ← *((byte*) main::$3)
(byte) print::p_y#1 ← *((byte*) main::$4)
call print
to:main::@2
main::@2: scope:[main] from main::@1
(struct Point*) main::ptr#1 ← phi( main::@1/(struct Point*) main::ptr#2 )
(byte) idx#9 ← phi( main::@1/(byte) idx#6 )
(byte) idx#2 ← (byte) idx#9
(byte*) main::$3 ← (byte*)(struct Point*) main::ptr#1 + (const byte) OFFSET_STRUCT_POINT_X
(byte*) main::$4 ← (byte*)(struct Point*) main::ptr#1 + (const byte) OFFSET_STRUCT_POINT_Y
to:main::@return
main::@return: scope:[main] from main::@2
(byte) idx#10 ← phi( main::@2/(byte) idx#2 )
(byte) idx#3 ← (byte) idx#10
return
to:@return
print: scope:[print] from main main::@1
(byte) print::p_y#2 ← phi( main/(byte) print::p_y#0 main::@1/(byte) print::p_y#1 )
(byte) idx#11 ← phi( main/(byte) idx#14 main::@1/(byte) idx#1 )
(byte) print::p_x#2 ← phi( main/(byte) print::p_x#0 main::@1/(byte) print::p_x#1 )
*((byte*) SCREEN#0 + (byte) idx#11) ← (byte) print::p_x#2
(byte) idx#4 ← ++ (byte) idx#11
*((byte*) SCREEN#0 + (byte) idx#4) ← (byte) print::p_y#2
(byte) idx#5 ← ++ (byte) idx#4
to:print::@return
print::@return: scope:[print] from print
(byte) idx#12 ← phi( print/(byte) idx#5 )
(byte) idx#6 ← (byte) idx#12
return
to:@return
@2: scope:[] from @begin
(byte) idx#15 ← phi( @begin/(byte) idx#0 )
call main
to:@3
@3: scope:[] from @2
(byte) idx#13 ← phi( @2/(byte) idx#3 )
(byte) idx#7 ← (byte) idx#13
to:@end
@end: scope:[] from @3
SYMBOL TABLE SSA
(label) @2
(label) @3
(label) @begin
(label) @end
(const byte) OFFSET_STRUCT_POINT_X = (byte) 0
(const byte) OFFSET_STRUCT_POINT_Y = (byte) 1
(byte) Point::x
(byte) Point::y
(byte*) SCREEN
(byte*) SCREEN#0
(byte) idx
(byte) idx#0
(byte) idx#1
(byte) idx#10
(byte) idx#11
(byte) idx#12
(byte) idx#13
(byte) idx#14
(byte) idx#15
(byte) idx#2
(byte) idx#3
(byte) idx#4
(byte) idx#5
(byte) idx#6
(byte) idx#7
(byte) idx#8
(byte) idx#9
(void()) main()
(struct Point*~) main::$0
(byte*) main::$3
(byte*) main::$4
(label) main::@1
(label) main::@2
(label) main::@return
(struct Point) main::point
(struct Point) main::point#0
(byte) main::point_x
(byte) main::point_x#0
(byte) main::point_y
(byte) main::point_y#0
(struct Point*) main::ptr
(struct Point*) main::ptr#0
(struct Point*) main::ptr#1
(struct Point*) main::ptr#2
(void()) print((byte) print::p_x , (byte) print::p_y)
(label) print::@return
(struct Point) print::p
(byte) print::p_x
(byte) print::p_x#0
(byte) print::p_x#1
(byte) print::p_x#2
(byte) print::p_y
(byte) print::p_y#0
(byte) print::p_y#1
(byte) print::p_y#2
Adding number conversion cast (unumber) 0 in (byte) idx#0 ← (number) 0
Adding number conversion cast (unumber) 1 in (byte) main::point_x#0 ← (number) 1
Adding number conversion cast (unumber) 2 in (byte) main::point_y#0 ← (number) 2
Successful SSA optimization PassNAddNumberTypeConversions
Inlining cast (byte*) SCREEN#0 ← (byte*)(number) $400
Inlining cast (byte) idx#0 ← (unumber)(number) 0
Inlining cast (byte) main::point_x#0 ← (unumber)(number) 1
Inlining cast (byte) main::point_y#0 ← (unumber)(number) 2
Successful SSA optimization Pass2InlineCast
Simplifying constant pointer cast (byte*) 1024
Simplifying constant integer cast 0
Simplifying constant integer cast 1
Simplifying constant integer cast 2
Successful SSA optimization PassNCastSimplification
Finalized unsigned number type (byte) 0
Finalized unsigned number type (byte) 1
Finalized unsigned number type (byte) 2
Successful SSA optimization PassNFinalizeNumberTypeConversions
Alias (struct Point*) main::ptr#0 = (struct Point*~) main::$0 (struct Point*) main::ptr#2 (struct Point*) main::ptr#1
Alias (byte) idx#1 = (byte) idx#8
Alias (byte) idx#10 = (byte) idx#2 (byte) idx#9 (byte) idx#3
Alias (byte) idx#12 = (byte) idx#5 (byte) idx#6
Alias (byte) idx#0 = (byte) idx#15
Alias (byte) idx#13 = (byte) idx#7
Successful SSA optimization Pass2AliasElimination
Identical Phi Values (byte) idx#14 (byte) idx#0
Identical Phi Values (byte) idx#1 (byte) idx#12
Identical Phi Values (byte) idx#10 (byte) idx#12
Identical Phi Values (byte) idx#13 (byte) idx#10
Successful SSA optimization Pass2IdenticalPhiElimination
Rewriting struct address-of to first member [6] (struct Point*) main::ptr#0 ← (struct Point*)&(byte) main::point_x#0
Successful SSA optimization PassNStructAddressOfRewriting
Constant right-side identified [6] (struct Point*) main::ptr#0 ← (struct Point*)&(byte) main::point_x#0
Successful SSA optimization Pass2ConstantRValueConsolidation
Constant (const byte*) SCREEN#0 = (byte*) 1024
Constant (const byte) idx#0 = 0
Constant (const struct Point*) main::ptr#0 = (struct Point*)&main::point_x#0
Successful SSA optimization Pass2ConstantIdentification
Constant value identified (byte*)main::ptr#0 in [18] (byte*) main::$3 ← (byte*)(const struct Point*) main::ptr#0 + (const byte) OFFSET_STRUCT_POINT_X
Constant value identified (byte*)main::ptr#0 in [19] (byte*) main::$4 ← (byte*)(const struct Point*) main::ptr#0 + (const byte) OFFSET_STRUCT_POINT_Y
Successful SSA optimization Pass2ConstantValues
Converting *(pointer+n) to pointer[n] [13] (byte) print::p_x#1 ← *((byte*) main::$3) -- *((byte*)main::ptr#0 + OFFSET_STRUCT_POINT_X)
Converting *(pointer+n) to pointer[n] [14] (byte) print::p_y#1 ← *((byte*) main::$4) -- *((byte*)main::ptr#0 + OFFSET_STRUCT_POINT_Y)
Successful SSA optimization Pass2InlineDerefIdx
Simplifying expression containing zero (byte*)main::ptr#0 in [13] (byte) print::p_x#1 ← *((byte*)(const struct Point*) main::ptr#0 + (const byte) OFFSET_STRUCT_POINT_X)
Simplifying expression containing zero (byte*)main::ptr#0 in [18] (byte*) main::$3 ← (byte*)(const struct Point*) main::ptr#0 + (const byte) OFFSET_STRUCT_POINT_X
Successful SSA optimization PassNSimplifyExpressionWithZero
Eliminating unused variable (struct Point) main::point#0 and assignment [2] (struct Point) main::point#0 ← struct-unwound {(byte) main::point_x#0, (byte) main::point_y#0}
Eliminating unused variable (byte*) main::$3 and assignment [9] (byte*) main::$3 ← (byte*)(const struct Point*) main::ptr#0
Eliminating unused variable (byte*) main::$4 and assignment [10] (byte*) main::$4 ← (byte*)(const struct Point*) main::ptr#0 + (const byte) OFFSET_STRUCT_POINT_Y
Eliminating unused constant (const byte) OFFSET_STRUCT_POINT_X
Successful SSA optimization PassNEliminateUnusedVars
Inlining constant with var siblings (const byte) idx#0
Constant inlined idx#0 = (byte) 0
Successful SSA optimization Pass2ConstantInlining
Consolidated array index constant in *((byte*)main::ptr#0+OFFSET_STRUCT_POINT_Y)
Successful SSA optimization Pass2ConstantAdditionElimination
Adding NOP phi() at start of @begin
Adding NOP phi() at start of @2
Adding NOP phi() at start of @3
Adding NOP phi() at start of @end
Adding NOP phi() at start of main::@2
CALL GRAPH
Calls in [] to main:2
Calls in [main] to print:11 print:17
Created 3 initial phi equivalence classes
Coalesced [9] print::p_x#3 ← print::p_x#0
Coalesced [10] print::p_y#3 ← print::p_y#0
Coalesced [14] print::p_x#4 ← print::p_x#1
Coalesced [15] idx#16 ← idx#12
Coalesced [16] print::p_y#4 ← print::p_y#1
Coalesced down to 3 phi equivalence classes
Culled Empty Block (label) @3
Culled Empty Block (label) main::@2
Renumbering block @2 to @1
Adding NOP phi() at start of @begin
Adding NOP phi() at start of @1
Adding NOP phi() at start of @end
FINAL CONTROL FLOW GRAPH
@begin: scope:[] from
[0] phi()
to:@1
@1: scope:[] from @begin
[1] phi()
[2] call main
to:@end
@end: scope:[] from @1
[3] phi()
main: scope:[main] from @1
[4] (byte) main::point_x#0 ← (byte) 1
[5] (byte) main::point_y#0 ← (byte) 2
[6] (byte) print::p_x#0 ← (byte) main::point_x#0
[7] (byte) print::p_y#0 ← (byte) main::point_y#0
[8] call print
to:main::@1
main::@1: scope:[main] from main
[9] (byte) print::p_x#1 ← *((byte*)(const struct Point*) main::ptr#0)
[10] (byte) print::p_y#1 ← *((byte*)(const struct Point*) main::ptr#0+(const byte) OFFSET_STRUCT_POINT_Y)
[11] call print
to:main::@return
main::@return: scope:[main] from main::@1
[12] return
to:@return
print: scope:[print] from main main::@1
[13] (byte) print::p_y#2 ← phi( main/(byte) print::p_y#0 main::@1/(byte) print::p_y#1 )
[13] (byte) idx#11 ← phi( main/(byte) 0 main::@1/(byte) idx#12 )
[13] (byte) print::p_x#2 ← phi( main/(byte) print::p_x#0 main::@1/(byte) print::p_x#1 )
[14] *((const byte*) SCREEN#0 + (byte) idx#11) ← (byte) print::p_x#2
[15] (byte) idx#4 ← ++ (byte) idx#11
[16] *((const byte*) SCREEN#0 + (byte) idx#4) ← (byte) print::p_y#2
[17] (byte) idx#12 ← ++ (byte) idx#4
to:print::@return
print::@return: scope:[print] from print
[18] return
to:@return
VARIABLE REGISTER WEIGHTS
(byte) Point::x
(byte) Point::y
(byte*) SCREEN
(byte) idx
(byte) idx#11 3.0
(byte) idx#12 0.8
(byte) idx#4 3.0
(void()) main()
(struct Point) main::point
(byte) main::point_x
(byte) main::point_x#0 2.0
(byte) main::point_y
(byte) main::point_y#0 2.0
(struct Point*) main::ptr
(void()) print((byte) print::p_x , (byte) print::p_y)
(struct Point) print::p
(byte) print::p_x
(byte) print::p_x#0 2.0
(byte) print::p_x#1 2.0
(byte) print::p_x#2 6.0
(byte) print::p_y
(byte) print::p_y#0 4.0
(byte) print::p_y#1 4.0
(byte) print::p_y#2 2.0
Initial phi equivalence classes
[ print::p_x#2 print::p_x#0 print::p_x#1 ]
[ idx#11 idx#12 ]
[ print::p_y#2 print::p_y#0 print::p_y#1 ]
Added variable idx#4 to zero page equivalence class [ idx#4 ]
Complete equivalence classes
[ print::p_x#2 print::p_x#0 print::p_x#1 ]
[ idx#11 idx#12 ]
[ print::p_y#2 print::p_y#0 print::p_y#1 ]
[ main::point_x#0 ]
[ main::point_y#0 ]
[ idx#4 ]
Allocated zp ZP_BYTE:2 [ print::p_x#2 print::p_x#0 print::p_x#1 ]
Allocated zp ZP_BYTE:3 [ idx#11 idx#12 ]
Allocated zp ZP_BYTE:4 [ print::p_y#2 print::p_y#0 print::p_y#1 ]
Allocated zp ZP_BYTE:5 [ main::point_x#0 ]
Allocated zp ZP_BYTE:6 [ main::point_y#0 ]
Allocated zp ZP_BYTE:7 [ idx#4 ]
INITIAL ASM
Target platform is c64basic
// File Comments
// Demonstrates problem with passing struct pointer deref as parameter to call
// Upstart
.pc = $801 "Basic"
:BasicUpstart(bbegin)
.pc = $80d "Program"
// Global Constants & labels
.const OFFSET_STRUCT_POINT_Y = 1
.label SCREEN = $400
.label idx = 7
.label idx_11 = 3
.label idx_12 = 3
// @begin
bbegin:
// [1] phi from @begin to @1 [phi:@begin->@1]
b1_from_bbegin:
jmp b1
// @1
b1:
// [2] call main
jsr main
// [3] phi from @1 to @end [phi:@1->@end]
bend_from_b1:
jmp bend
// @end
bend:
// main
main: {
.label ptr = point_x
.label point_x = 5
.label point_y = 6
// [4] (byte) main::point_x#0 ← (byte) 1 -- vbuz1=vbuc1
lda #1
sta point_x
// [5] (byte) main::point_y#0 ← (byte) 2 -- vbuz1=vbuc1
lda #2
sta point_y
// [6] (byte) print::p_x#0 ← (byte) main::point_x#0 -- vbuz1=vbuz2
lda point_x
sta print.p_x
// [7] (byte) print::p_y#0 ← (byte) main::point_y#0 -- vbuz1=vbuz2
lda point_y
sta print.p_y
// [8] call print
// [13] phi from main to print [phi:main->print]
print_from_main:
// [13] phi (byte) print::p_y#2 = (byte) print::p_y#0 [phi:main->print#0] -- register_copy
// [13] phi (byte) idx#11 = (byte) 0 [phi:main->print#1] -- vbuz1=vbuc1
lda #0
sta idx_11
// [13] phi (byte) print::p_x#2 = (byte) print::p_x#0 [phi:main->print#2] -- register_copy
jsr print
jmp b1
// main::@1
b1:
// [9] (byte) print::p_x#1 ← *((byte*)(const struct Point*) main::ptr#0) -- vbuz1=_deref_pbuc1
lda ptr
sta print.p_x
// [10] (byte) print::p_y#1 ← *((byte*)(const struct Point*) main::ptr#0+(const byte) OFFSET_STRUCT_POINT_Y) -- vbuz1=_deref_pbuc1
lda ptr+OFFSET_STRUCT_POINT_Y
sta print.p_y
// [11] call print
// [13] phi from main::@1 to print [phi:main::@1->print]
print_from_b1:
// [13] phi (byte) print::p_y#2 = (byte) print::p_y#1 [phi:main::@1->print#0] -- register_copy
// [13] phi (byte) idx#11 = (byte) idx#12 [phi:main::@1->print#1] -- register_copy
// [13] phi (byte) print::p_x#2 = (byte) print::p_x#1 [phi:main::@1->print#2] -- register_copy
jsr print
jmp breturn
// main::@return
breturn:
// [12] return
rts
}
// print
// print(byte zeropage(2) p_x, byte zeropage(4) p_y)
print: {
.label p_x = 2
.label p_y = 4
// [14] *((const byte*) SCREEN#0 + (byte) idx#11) ← (byte) print::p_x#2 -- pbuc1_derefidx_vbuz1=vbuz2
lda p_x
ldy idx_11
sta SCREEN,y
// [15] (byte) idx#4 ← ++ (byte) idx#11 -- vbuz1=_inc_vbuz2
ldy idx_11
iny
sty idx
// [16] *((const byte*) SCREEN#0 + (byte) idx#4) ← (byte) print::p_y#2 -- pbuc1_derefidx_vbuz1=vbuz2
lda p_y
ldy idx
sta SCREEN,y
// [17] (byte) idx#12 ← ++ (byte) idx#4 -- vbuz1=_inc_vbuz2
ldy idx
iny
sty idx_12
jmp breturn
// print::@return
breturn:
// [18] return
rts
}
// File Data
REGISTER UPLIFT POTENTIAL REGISTERS
Statement [4] (byte) main::point_x#0 ← (byte) 1 [ main::point_x#0 ] ( main:2 [ main::point_x#0 ] ) always clobbers reg byte a
Statement [5] (byte) main::point_y#0 ← (byte) 2 [ main::point_x#0 main::point_y#0 ] ( main:2 [ main::point_x#0 main::point_y#0 ] ) always clobbers reg byte a
Potential registers zp ZP_BYTE:2 [ print::p_x#2 print::p_x#0 print::p_x#1 ] : zp ZP_BYTE:2 , reg byte a , reg byte x , reg byte y ,
Potential registers zp ZP_BYTE:3 [ idx#11 idx#12 ] : zp ZP_BYTE:3 , reg byte a , reg byte x , reg byte y ,
Potential registers zp ZP_BYTE:4 [ print::p_y#2 print::p_y#0 print::p_y#1 ] : zp ZP_BYTE:4 , reg byte a , reg byte x , reg byte y ,
Potential registers zp ZP_BYTE:5 [ main::point_x#0 ] : zp ZP_BYTE:5 ,
Potential registers zp ZP_BYTE:6 [ main::point_y#0 ] : zp ZP_BYTE:6 ,
Potential registers zp ZP_BYTE:7 [ idx#4 ] : zp ZP_BYTE:7 , reg byte a , reg byte x , reg byte y ,
REGISTER UPLIFT SCOPES
Uplift Scope [print] 10: zp ZP_BYTE:2 [ print::p_x#2 print::p_x#0 print::p_x#1 ] 10: zp ZP_BYTE:4 [ print::p_y#2 print::p_y#0 print::p_y#1 ]
Uplift Scope [] 3.8: zp ZP_BYTE:3 [ idx#11 idx#12 ] 3: zp ZP_BYTE:7 [ idx#4 ]
Uplift Scope [main] 2: zp ZP_BYTE:5 [ main::point_x#0 ] 2: zp ZP_BYTE:6 [ main::point_y#0 ]
Uplift Scope [Point]
Uplifting [print] best 110 combination reg byte y [ print::p_x#2 print::p_x#0 print::p_x#1 ] reg byte x [ print::p_y#2 print::p_y#0 print::p_y#1 ]
Uplifting [] best 101 combination zp ZP_BYTE:3 [ idx#11 idx#12 ] reg byte y [ idx#4 ]
Uplifting [main] best 101 combination zp ZP_BYTE:5 [ main::point_x#0 ] zp ZP_BYTE:6 [ main::point_y#0 ]
Uplifting [Point] best 101 combination
Attempting to uplift remaining variables inzp ZP_BYTE:3 [ idx#11 idx#12 ]
Uplifting [] best 101 combination zp ZP_BYTE:3 [ idx#11 idx#12 ]
Attempting to uplift remaining variables inzp ZP_BYTE:5 [ main::point_x#0 ]
Uplifting [main] best 101 combination zp ZP_BYTE:5 [ main::point_x#0 ]
Attempting to uplift remaining variables inzp ZP_BYTE:6 [ main::point_y#0 ]
Uplifting [main] best 101 combination zp ZP_BYTE:6 [ main::point_y#0 ]
Allocated (was zp ZP_BYTE:3) zp ZP_BYTE:2 [ idx#11 idx#12 ]
Allocated (was zp ZP_BYTE:5) zp ZP_BYTE:3 [ main::point_x#0 ]
Allocated (was zp ZP_BYTE:6) zp ZP_BYTE:4 [ main::point_y#0 ]
ASSEMBLER BEFORE OPTIMIZATION
// File Comments
// Demonstrates problem with passing struct pointer deref as parameter to call
// Upstart
.pc = $801 "Basic"
:BasicUpstart(bbegin)
.pc = $80d "Program"
// Global Constants & labels
.const OFFSET_STRUCT_POINT_Y = 1
.label SCREEN = $400
.label idx = 2
// @begin
bbegin:
// [1] phi from @begin to @1 [phi:@begin->@1]
b1_from_bbegin:
jmp b1
// @1
b1:
// [2] call main
jsr main
// [3] phi from @1 to @end [phi:@1->@end]
bend_from_b1:
jmp bend
// @end
bend:
// main
main: {
.label ptr = point_x
.label point_x = 3
.label point_y = 4
// [4] (byte) main::point_x#0 ← (byte) 1 -- vbuz1=vbuc1
lda #1
sta point_x
// [5] (byte) main::point_y#0 ← (byte) 2 -- vbuz1=vbuc1
lda #2
sta point_y
// [6] (byte) print::p_x#0 ← (byte) main::point_x#0 -- vbuyy=vbuz1
ldy point_x
// [7] (byte) print::p_y#0 ← (byte) main::point_y#0 -- vbuxx=vbuz1
ldx point_y
// [8] call print
// [13] phi from main to print [phi:main->print]
print_from_main:
// [13] phi (byte) print::p_y#2 = (byte) print::p_y#0 [phi:main->print#0] -- register_copy
// [13] phi (byte) idx#11 = (byte) 0 [phi:main->print#1] -- vbuz1=vbuc1
lda #0
sta idx
// [13] phi (byte) print::p_x#2 = (byte) print::p_x#0 [phi:main->print#2] -- register_copy
jsr print
jmp b1
// main::@1
b1:
// [9] (byte) print::p_x#1 ← *((byte*)(const struct Point*) main::ptr#0) -- vbuyy=_deref_pbuc1
ldy ptr
// [10] (byte) print::p_y#1 ← *((byte*)(const struct Point*) main::ptr#0+(const byte) OFFSET_STRUCT_POINT_Y) -- vbuxx=_deref_pbuc1
ldx ptr+OFFSET_STRUCT_POINT_Y
// [11] call print
// [13] phi from main::@1 to print [phi:main::@1->print]
print_from_b1:
// [13] phi (byte) print::p_y#2 = (byte) print::p_y#1 [phi:main::@1->print#0] -- register_copy
// [13] phi (byte) idx#11 = (byte) idx#12 [phi:main::@1->print#1] -- register_copy
// [13] phi (byte) print::p_x#2 = (byte) print::p_x#1 [phi:main::@1->print#2] -- register_copy
jsr print
jmp breturn
// main::@return
breturn:
// [12] return
rts
}
// print
// print(byte register(Y) p_x, byte register(X) p_y)
print: {
// [14] *((const byte*) SCREEN#0 + (byte) idx#11) ← (byte) print::p_x#2 -- pbuc1_derefidx_vbuz1=vbuyy
tya
ldy idx
sta SCREEN,y
// [15] (byte) idx#4 ← ++ (byte) idx#11 -- vbuyy=_inc_vbuz1
ldy idx
iny
// [16] *((const byte*) SCREEN#0 + (byte) idx#4) ← (byte) print::p_y#2 -- pbuc1_derefidx_vbuyy=vbuxx
txa
sta SCREEN,y
// [17] (byte) idx#12 ← ++ (byte) idx#4 -- vbuz1=_inc_vbuyy
iny
sty idx
jmp breturn
// print::@return
breturn:
// [18] return
rts
}
// File Data
ASSEMBLER OPTIMIZATIONS
Removing instruction jmp b1
Removing instruction jmp bend
Removing instruction jmp b1
Removing instruction jmp breturn
Removing instruction jmp breturn
Succesful ASM optimization Pass5NextJumpElimination
Replacing instruction ldx point_y with TAX
Removing instruction ldy idx
Succesful ASM optimization Pass5UnnecesaryLoadElimination
Removing instruction b1_from_bbegin:
Removing instruction b1:
Removing instruction bend_from_b1:
Succesful ASM optimization Pass5RedundantLabelElimination
Removing instruction bend:
Removing instruction print_from_main:
Removing instruction b1:
Removing instruction print_from_b1:
Removing instruction breturn:
Removing instruction breturn:
Succesful ASM optimization Pass5UnusedLabelElimination
Updating BasicUpstart to call main directly
Removing instruction jsr main
Succesful ASM optimization Pass5SkipBegin
Removing instruction bbegin:
Succesful ASM optimization Pass5UnusedLabelElimination
FINAL SYMBOL TABLE
(label) @1
(label) @begin
(label) @end
(const byte) OFFSET_STRUCT_POINT_Y OFFSET_STRUCT_POINT_Y = (byte) 1
(byte) Point::x
(byte) Point::y
(byte*) SCREEN
(const byte*) SCREEN#0 SCREEN = (byte*) 1024
(byte) idx
(byte) idx#11 idx zp ZP_BYTE:2 3.0
(byte) idx#12 idx zp ZP_BYTE:2 0.8
(byte) idx#4 reg byte y 3.0
(void()) main()
(label) main::@1
(label) main::@return
(struct Point) main::point
(byte) main::point_x
(byte) main::point_x#0 point_x zp ZP_BYTE:3 2.0
(byte) main::point_y
(byte) main::point_y#0 point_y zp ZP_BYTE:4 2.0
(struct Point*) main::ptr
(const struct Point*) main::ptr#0 ptr = (struct Point*)&(byte) main::point_x#0
(void()) print((byte) print::p_x , (byte) print::p_y)
(label) print::@return
(struct Point) print::p
(byte) print::p_x
(byte) print::p_x#0 reg byte y 2.0
(byte) print::p_x#1 reg byte y 2.0
(byte) print::p_x#2 reg byte y 6.0
(byte) print::p_y
(byte) print::p_y#0 reg byte x 4.0
(byte) print::p_y#1 reg byte x 4.0
(byte) print::p_y#2 reg byte x 2.0
reg byte y [ print::p_x#2 print::p_x#0 print::p_x#1 ]
zp ZP_BYTE:2 [ idx#11 idx#12 ]
reg byte x [ print::p_y#2 print::p_y#0 print::p_y#1 ]
zp ZP_BYTE:3 [ main::point_x#0 ]
zp ZP_BYTE:4 [ main::point_y#0 ]
reg byte y [ idx#4 ]
FINAL ASSEMBLER
Score: 76
// File Comments
// Demonstrates problem with passing struct pointer deref as parameter to call
// Upstart
.pc = $801 "Basic"
:BasicUpstart(main)
.pc = $80d "Program"
// Global Constants & labels
.const OFFSET_STRUCT_POINT_Y = 1
.label SCREEN = $400
.label idx = 2
// @begin
// [1] phi from @begin to @1 [phi:@begin->@1]
// @1
// [2] call main
// [3] phi from @1 to @end [phi:@1->@end]
// @end
// main
main: {
.label ptr = point_x
.label point_x = 3
.label point_y = 4
// point = { 1, 2 }
// [4] (byte) main::point_x#0 ← (byte) 1 -- vbuz1=vbuc1
lda #1
sta point_x
// [5] (byte) main::point_y#0 ← (byte) 2 -- vbuz1=vbuc1
lda #2
sta point_y
// print(point)
// [6] (byte) print::p_x#0 ← (byte) main::point_x#0 -- vbuyy=vbuz1
ldy point_x
// [7] (byte) print::p_y#0 ← (byte) main::point_y#0 -- vbuxx=vbuz1
tax
// [8] call print
// [13] phi from main to print [phi:main->print]
// [13] phi (byte) print::p_y#2 = (byte) print::p_y#0 [phi:main->print#0] -- register_copy
// [13] phi (byte) idx#11 = (byte) 0 [phi:main->print#1] -- vbuz1=vbuc1
lda #0
sta idx
// [13] phi (byte) print::p_x#2 = (byte) print::p_x#0 [phi:main->print#2] -- register_copy
jsr print
// main::@1
// print(*ptr)
// [9] (byte) print::p_x#1 ← *((byte*)(const struct Point*) main::ptr#0) -- vbuyy=_deref_pbuc1
ldy ptr
// [10] (byte) print::p_y#1 ← *((byte*)(const struct Point*) main::ptr#0+(const byte) OFFSET_STRUCT_POINT_Y) -- vbuxx=_deref_pbuc1
ldx ptr+OFFSET_STRUCT_POINT_Y
// [11] call print
// [13] phi from main::@1 to print [phi:main::@1->print]
// [13] phi (byte) print::p_y#2 = (byte) print::p_y#1 [phi:main::@1->print#0] -- register_copy
// [13] phi (byte) idx#11 = (byte) idx#12 [phi:main::@1->print#1] -- register_copy
// [13] phi (byte) print::p_x#2 = (byte) print::p_x#1 [phi:main::@1->print#2] -- register_copy
jsr print
// main::@return
// }
// [12] return
rts
}
// print
// print(byte register(Y) p_x, byte register(X) p_y)
print: {
// SCREEN[idx++] = p.x
// [14] *((const byte*) SCREEN#0 + (byte) idx#11) ← (byte) print::p_x#2 -- pbuc1_derefidx_vbuz1=vbuyy
tya
ldy idx
sta SCREEN,y
// SCREEN[idx++] = p.x;
// [15] (byte) idx#4 ← ++ (byte) idx#11 -- vbuyy=_inc_vbuz1
iny
// SCREEN[idx++] = p.y
// [16] *((const byte*) SCREEN#0 + (byte) idx#4) ← (byte) print::p_y#2 -- pbuc1_derefidx_vbuyy=vbuxx
txa
sta SCREEN,y
// SCREEN[idx++] = p.y;
// [17] (byte) idx#12 ← ++ (byte) idx#4 -- vbuz1=_inc_vbuyy
iny
sty idx
// print::@return
// }
// [18] return
rts
}
// File Data

View File

@ -0,0 +1,40 @@
(label) @1
(label) @begin
(label) @end
(const byte) OFFSET_STRUCT_POINT_Y OFFSET_STRUCT_POINT_Y = (byte) 1
(byte) Point::x
(byte) Point::y
(byte*) SCREEN
(const byte*) SCREEN#0 SCREEN = (byte*) 1024
(byte) idx
(byte) idx#11 idx zp ZP_BYTE:2 3.0
(byte) idx#12 idx zp ZP_BYTE:2 0.8
(byte) idx#4 reg byte y 3.0
(void()) main()
(label) main::@1
(label) main::@return
(struct Point) main::point
(byte) main::point_x
(byte) main::point_x#0 point_x zp ZP_BYTE:3 2.0
(byte) main::point_y
(byte) main::point_y#0 point_y zp ZP_BYTE:4 2.0
(struct Point*) main::ptr
(const struct Point*) main::ptr#0 ptr = (struct Point*)&(byte) main::point_x#0
(void()) print((byte) print::p_x , (byte) print::p_y)
(label) print::@return
(struct Point) print::p
(byte) print::p_x
(byte) print::p_x#0 reg byte y 2.0
(byte) print::p_x#1 reg byte y 2.0
(byte) print::p_x#2 reg byte y 6.0
(byte) print::p_y
(byte) print::p_y#0 reg byte x 4.0
(byte) print::p_y#1 reg byte x 4.0
(byte) print::p_y#2 reg byte x 2.0
reg byte y [ print::p_x#2 print::p_x#0 print::p_x#1 ]
zp ZP_BYTE:2 [ idx#11 idx#12 ]
reg byte x [ print::p_y#2 print::p_y#0 print::p_y#1 ]
zp ZP_BYTE:3 [ main::point_x#0 ]
zp ZP_BYTE:4 [ main::point_y#0 ]
reg byte y [ idx#4 ]