diff --git a/src/main/java/dk/camelot64/kickc/Compiler.java b/src/main/java/dk/camelot64/kickc/Compiler.java index 0521db3fd..10e7b3480 100644 --- a/src/main/java/dk/camelot64/kickc/Compiler.java +++ b/src/main/java/dk/camelot64/kickc/Compiler.java @@ -274,6 +274,7 @@ public class Compiler { optimizations.add(new Pass2ConditionalAndOrRewriting(program)); optimizations.add(new PassNAddBooleanCasts(program)); optimizations.add(new PassNStructPointerRewriting(program)); + optimizations.add(new PassNStructAddressOfRewriting(program)); optimizations.add(new Pass2ConditionalJumpSequenceImprovement(program)); optimizations.add(new Pass2ConstantRValueConsolidation(program)); optimizations.add(new Pass2ConstantIdentification(program)); @@ -387,7 +388,6 @@ public class Compiler { } private void pass3Analysis() { - new PassNEliminateStructUnwoundPlaceholder(program).step(); new Pass3AssertNoTypeId(program).check(); new Pass3AssertRValues(program).check(); new Pass3AssertNoNumbers(program).check(); diff --git a/src/main/java/dk/camelot64/kickc/model/LiveRangeEquivalenceClass.java b/src/main/java/dk/camelot64/kickc/model/LiveRangeEquivalenceClass.java index 56b428132..c637ac29c 100644 --- a/src/main/java/dk/camelot64/kickc/model/LiveRangeEquivalenceClass.java +++ b/src/main/java/dk/camelot64/kickc/model/LiveRangeEquivalenceClass.java @@ -49,10 +49,12 @@ public class LiveRangeEquivalenceClass { } LiveRangeVariables liveRanges = set.getProgram().getLiveRangeVariables(); LiveRange varLiveRange = liveRanges.getLiveRange(variable); - if(liveRange.overlaps(varLiveRange)) { - throw new RuntimeException("Compilation error! Variable live range overlaps live range equivalence class live range. " + variable); + if(varLiveRange!=null) { + if(liveRange.overlaps(varLiveRange)) { + throw new RuntimeException("Compilation error! Variable live range overlaps live range equivalence class live range. " + variable); + } + liveRange.add(varLiveRange); } - liveRange.add(varLiveRange); variables.add(variable); set.setVarClass(variable, this); } diff --git a/src/main/java/dk/camelot64/kickc/model/iterator/ProgramValue.java b/src/main/java/dk/camelot64/kickc/model/iterator/ProgramValue.java index b5b67751d..3c4238394 100644 --- a/src/main/java/dk/camelot64/kickc/model/iterator/ProgramValue.java +++ b/src/main/java/dk/camelot64/kickc/model/iterator/ProgramValue.java @@ -770,6 +770,28 @@ public interface ProgramValue { } + class ProgramValueStructUnwoundPlaceholderMember implements ProgramValue { + private StructUnwoundPlaceholder placeholder; + private int idx; + + public ProgramValueStructUnwoundPlaceholderMember(StructUnwoundPlaceholder placeholder, int idx) { + this.placeholder = placeholder; + this.idx = idx; + } + + @Override + public Value get() { + return placeholder.getUnwoundMembers().get(idx); + } + + @Override + public void set(Value value) { + placeholder.getUnwoundMembers().set(idx, (RValue) value); + } + + } + + /** * Pointer index inside a indexed pointer dererence value. */ diff --git a/src/main/java/dk/camelot64/kickc/model/iterator/ProgramValueIterator.java b/src/main/java/dk/camelot64/kickc/model/iterator/ProgramValueIterator.java index e3e3e36d8..c0c80a06f 100644 --- a/src/main/java/dk/camelot64/kickc/model/iterator/ProgramValueIterator.java +++ b/src/main/java/dk/camelot64/kickc/model/iterator/ProgramValueIterator.java @@ -188,6 +188,11 @@ public class ProgramValueIterator { subValues.add(new ProgramValue.ProgramValuePointer((PointerDereference) value)); } else if(value instanceof StructMemberRef) { subValues.add(new ProgramValue.ProgramValueStruct((StructMemberRef) value)); + } else if(value instanceof StructUnwoundPlaceholder) { + int size = ((StructUnwoundPlaceholder) value).getUnwoundMembers().size(); + for(int i = 0; i < size; i++) { + subValues.add(new ProgramValue.ProgramValueStructUnwoundPlaceholderMember((StructUnwoundPlaceholder) value, i)); + } } else if(value instanceof ValueList) { ValueList valueList = (ValueList) value; int size = valueList.getList().size(); @@ -226,7 +231,6 @@ public class ProgramValueIterator { value instanceof ConstantLiteral || value instanceof ConstantRef || value instanceof StructZero || - value instanceof StructUnwoundPlaceholder || value instanceof LabelRef ) { // No sub values diff --git a/src/main/java/dk/camelot64/kickc/model/values/StructUnwoundPlaceholder.java b/src/main/java/dk/camelot64/kickc/model/values/StructUnwoundPlaceholder.java index 65efbc169..891c633f0 100644 --- a/src/main/java/dk/camelot64/kickc/model/values/StructUnwoundPlaceholder.java +++ b/src/main/java/dk/camelot64/kickc/model/values/StructUnwoundPlaceholder.java @@ -3,23 +3,45 @@ package dk.camelot64.kickc.model.values; import dk.camelot64.kickc.model.Program; import dk.camelot64.kickc.model.types.SymbolTypeStruct; +import java.util.List; + /** Used as a placeholder value, where a struct has been unwound. */ public class StructUnwoundPlaceholder implements RValue { - public StructUnwoundPlaceholder(SymbolTypeStruct typeStruct) { + public StructUnwoundPlaceholder(SymbolTypeStruct typeStruct, List unwoundMembers) { this.typeStruct = typeStruct; + this.unwoundMembers = unwoundMembers; } /** The type of the struct. */ private SymbolTypeStruct typeStruct; + /** The unwound struct members. */ + private List unwoundMembers; + public SymbolTypeStruct getTypeStruct() { return typeStruct; } + public List getUnwoundMembers() { + return unwoundMembers; + } + @Override public String toString(Program program) { - return "struct-unwound"; + StringBuffer str = new StringBuffer(); + str.append("struct-unwound {"); + boolean first = true; + for(RValue unwoundMember : unwoundMembers) { + if(first) { + first = false; + } else { + str.append(", "); + } + str.append(unwoundMember.toString(program)); + } + str.append("}"); + return str.toString(); } } diff --git a/src/main/java/dk/camelot64/kickc/passes/Pass1UnwindStructValues.java b/src/main/java/dk/camelot64/kickc/passes/Pass1UnwindStructValues.java index 007735924..7d4cc515d 100644 --- a/src/main/java/dk/camelot64/kickc/passes/Pass1UnwindStructValues.java +++ b/src/main/java/dk/camelot64/kickc/passes/Pass1UnwindStructValues.java @@ -276,9 +276,11 @@ public class Pass1UnwindStructValues extends Pass1Base { if(assignment.getOperator() == null && assignment.getrValue2() instanceof StructZero && assignment.getlValue() instanceof VariableRef) { // Zero-initializing a struct - unwind to assigning zero to each member! + List membersUnwound = new ArrayList<>(); stmtIt.previous(); for(String memberName : memberUnwinding.getMemberNames()) { VariableRef memberVarRef = (VariableRef) memberUnwinding.getMemberUnwinding(memberName); + membersUnwound.add(memberVarRef); Variable memberVar = getScope().getVariable(memberVarRef); Statement initStmt = Pass0GenerateStatementSequence.createDefaultInitializationStatement(memberVarRef, memberVar.getType(), assignment.getSource(), Comment.NO_COMMENTS); stmtIt.add(initStmt); @@ -286,7 +288,7 @@ public class Pass1UnwindStructValues extends Pass1Base { } stmtIt.next(); if(assignment.getlValue() instanceof VariableRef) { - assignment.setrValue2(new StructUnwoundPlaceholder(structType)); + assignment.setrValue2(new StructUnwoundPlaceholder(structType, membersUnwound)); } else { stmtIt.remove(); } @@ -298,16 +300,18 @@ public class Pass1UnwindStructValues extends Pass1Base { throw new CompileError("Struct initialization list has wrong size. Need " + memberUnwinding.getMemberNames().size() + " got " + valueList.getList().size(), assignment); } stmtIt.previous(); + List membersUnwound = new ArrayList<>(); int idx = 0; for(String memberName : memberUnwinding.getMemberNames()) { LValue memberLvalue = memberUnwinding.getMemberUnwinding(memberName); + membersUnwound.add(memberLvalue); Statement initStmt = new StatementAssignment(memberLvalue, valueList.getList().get(idx++), assignment.getSource(), Comment.NO_COMMENTS); stmtIt.add(initStmt); getLog().append("Adding struct value list initializer " + initStmt.toString(getProgram(), false)); } stmtIt.next(); if(assignment.getlValue() instanceof VariableRef) { - assignment.setrValue2(new StructUnwoundPlaceholder(structType)); + assignment.setrValue2(new StructUnwoundPlaceholder(structType, membersUnwound)); } else { stmtIt.remove(); } @@ -320,17 +324,19 @@ public class Pass1UnwindStructValues extends Pass1Base { // Copying a struct - unwind to assigning each member! StructMemberUnwinding sourceMemberUnwinding = getStructMemberUnwinding((LValue) assignment.getrValue2(), structType, structUnwinding, assignment, stmtIt, currentBlock); if(sourceMemberUnwinding != null) { + List membersUnwound = new ArrayList<>(); stmtIt.previous(); for(String memberName : memberUnwinding.getMemberNames()) { LValue assignedMemberVarRef = memberUnwinding.getMemberUnwinding(memberName); LValue sourceMemberVarRef = sourceMemberUnwinding.getMemberUnwinding(memberName); + membersUnwound.add(assignedMemberVarRef); Statement copyStmt = new StatementAssignment(assignedMemberVarRef, sourceMemberVarRef, assignment.getSource(), Comment.NO_COMMENTS); stmtIt.add(copyStmt); getLog().append("Adding struct value member variable copy " + copyStmt.toString(getProgram(), false)); } stmtIt.next(); if(assignment.getlValue() instanceof VariableRef) { - assignment.setrValue2(new StructUnwoundPlaceholder(structType)); + assignment.setrValue2(new StructUnwoundPlaceholder(structType, membersUnwound)); } else { stmtIt.remove(); } diff --git a/src/main/java/dk/camelot64/kickc/passes/Pass2ConstantIdentification.java b/src/main/java/dk/camelot64/kickc/passes/Pass2ConstantIdentification.java index 7d97935fe..325e78275 100644 --- a/src/main/java/dk/camelot64/kickc/passes/Pass2ConstantIdentification.java +++ b/src/main/java/dk/camelot64/kickc/passes/Pass2ConstantIdentification.java @@ -1,6 +1,7 @@ package dk.camelot64.kickc.passes; import dk.camelot64.kickc.model.CompileError; +import dk.camelot64.kickc.model.ConstantNotLiteral; import dk.camelot64.kickc.model.ControlFlowBlock; import dk.camelot64.kickc.model.Program; import dk.camelot64.kickc.model.iterator.ProgramValueIterator; @@ -80,11 +81,18 @@ public class Pass2ConstantIdentification extends Pass2SsaOptimization { } if(!SymbolTypeConversion.assignmentTypeMatch(variableType, valueType)) { - throw new CompileError( - "Constant variable has a non-matching type \n variable: " + variable.toString(getProgram()) + - "\n value: (" + valueType.toString() + ") " + constVal.calculateLiteral(getScope()) + - "\n value definition: " + constVal.toString(getProgram()) - ); + ConstantLiteral constantLiteral = null; + try { + constantLiteral = constVal.calculateLiteral(getScope()); + } catch(ConstantNotLiteral e) { + // ignore + } + String literalStr = (constantLiteral == null) ? "null" : constantLiteral.toString(getProgram()); + throw new CompileError( + "Constant variable has a non-matching type \n variable: " + variable.toString(getProgram()) + + "\n value: (" + valueType.toString() + ") " + literalStr + + "\n value definition: " + constVal.toString(getProgram()) + ); } ConstantVar constantVar = new ConstantVar( @@ -197,7 +205,7 @@ public class Pass2ConstantIdentification extends Pass2SsaOptimization { // Volatile variables cannot be constant return; } - if(assignment.getrValue1()==null && assignment.getOperator()==null && assignment.getrValue2() instanceof ConstantValue) { + if(assignment.getrValue1() == null && assignment.getOperator() == null && assignment.getrValue2() instanceof ConstantValue) { constants.put(variable, new ConstantVariableValue(variable, (ConstantValue) assignment.getrValue2(), assignment)); } } @@ -324,7 +332,7 @@ public class Pass2ConstantIdentification extends Pass2SsaOptimization { // If the symbol is part of an unwound struct - look at the struct itself Pass1UnwindStructValues.StructUnwinding structUnwinding = program.getStructUnwinding(); VariableRef structVarRef = structUnwinding.getContainingStructVariable(symbolRef); - if(structVarRef!=null) { + if(structVarRef != null) { return isAddressOfUsed(structVarRef, program); } diff --git a/src/main/java/dk/camelot64/kickc/passes/PassNEliminateStructUnwoundPlaceholder.java b/src/main/java/dk/camelot64/kickc/passes/PassNEliminateStructUnwoundPlaceholder.java deleted file mode 100644 index 0c9084b42..000000000 --- a/src/main/java/dk/camelot64/kickc/passes/PassNEliminateStructUnwoundPlaceholder.java +++ /dev/null @@ -1,37 +0,0 @@ -package dk.camelot64.kickc.passes; - -import dk.camelot64.kickc.model.ControlFlowBlock; -import dk.camelot64.kickc.model.Program; -import dk.camelot64.kickc.model.statements.Statement; -import dk.camelot64.kickc.model.statements.StatementAssignment; -import dk.camelot64.kickc.model.values.StructUnwoundPlaceholder; - -import java.util.ListIterator; - -/** Remove any assignments with {@link dk.camelot64.kickc.model.values.StructUnwoundPlaceholder} as RValue */ -public class PassNEliminateStructUnwoundPlaceholder extends Pass2SsaOptimization { - - public PassNEliminateStructUnwoundPlaceholder(Program program) { - super(program); - } - - @Override - public boolean step() { - for(ControlFlowBlock block : getGraph().getAllBlocks()) { - ListIterator stmtIt = block.getStatements().listIterator(); - while(stmtIt.hasNext()) { - Statement stmt = stmtIt.next(); - if(stmt instanceof StatementAssignment) { - StatementAssignment assignment = (StatementAssignment) stmt; - if(assignment.getOperator() == null && assignment.getrValue2() instanceof StructUnwoundPlaceholder) { - getLog().append("Eliminating struct unwound placeholder "+stmt.toString(getProgram(), false)); - stmtIt.remove(); - } - } - } - } - return false; - } - - -} diff --git a/src/main/java/dk/camelot64/kickc/passes/PassNEliminateUnusedVars.java b/src/main/java/dk/camelot64/kickc/passes/PassNEliminateUnusedVars.java index 57dd76775..e71c36a2f 100644 --- a/src/main/java/dk/camelot64/kickc/passes/PassNEliminateUnusedVars.java +++ b/src/main/java/dk/camelot64/kickc/passes/PassNEliminateUnusedVars.java @@ -6,8 +6,8 @@ import dk.camelot64.kickc.model.VariableReferenceInfos; import dk.camelot64.kickc.model.statements.*; import dk.camelot64.kickc.model.symbols.ConstantVar; import dk.camelot64.kickc.model.symbols.Procedure; -import dk.camelot64.kickc.model.symbols.Scope; import dk.camelot64.kickc.model.symbols.Variable; +import dk.camelot64.kickc.model.types.SymbolTypeStruct; import dk.camelot64.kickc.model.values.LValue; import dk.camelot64.kickc.model.values.VariableRef; @@ -41,9 +41,27 @@ public class PassNEliminateUnusedVars extends Pass2SsaOptimization { LValue lValue = assignment.getlValue(); if(lValue instanceof VariableRef && referenceInfos.isUnused((VariableRef) lValue) && !Pass2ConstantIdentification.isAddressOfUsed((VariableRef) lValue, getProgram())) { Variable variable = getScope().getVariable((VariableRef) lValue); - if(variable==null || !variable.isDeclaredVolatile()) { + boolean eliminate = false; + if(variable == null) { + // Already deleted + eliminate = true; + } else if(!variable.isDeclaredVolatile()) { + // Not volatile + eliminate = true; + } else if(variable.isDeclaredVolatile() && variable.getType() instanceof SymbolTypeStruct) { + // If an unwound volatile struct - eliminate it + if(variable.getRef().isVersion()) { + String fullNameUnversioned = variable.getRef().getFullNameUnversioned(); + VariableRef unversionedRef = new VariableRef(fullNameUnversioned); + Pass1UnwindStructValues.StructUnwinding.VariableUnwinding variableUnwinding = getProgram().getStructUnwinding().getVariableUnwinding(unversionedRef); + if(variableUnwinding != null) { + eliminate = true; + } + } + } + if(eliminate) { if(!pass2 && isReturnValue(variable)) { - // Do not eliminate reutn variables in pass 1 + // Do not eliminate return variables in pass 1 continue; } if(pass2 || getLog().isVerbosePass1CreateSsa()) { @@ -124,11 +142,12 @@ public class PassNEliminateUnusedVars extends Pass2SsaOptimization { /** * Determines if a variable is the return value for a procedure + * * @param variable The variable * @return true if this is the return variable for a function */ private boolean isReturnValue(Variable variable) { - if(variable==null) return false; + if(variable == null) return false; return variable.getScope() instanceof Procedure && variable.getLocalName().equals("return"); } diff --git a/src/main/java/dk/camelot64/kickc/passes/PassNStructAddressOfRewriting.java b/src/main/java/dk/camelot64/kickc/passes/PassNStructAddressOfRewriting.java new file mode 100644 index 000000000..ed94f0587 --- /dev/null +++ b/src/main/java/dk/camelot64/kickc/passes/PassNStructAddressOfRewriting.java @@ -0,0 +1,86 @@ +package dk.camelot64.kickc.passes; + +import dk.camelot64.kickc.model.ControlFlowBlock; +import dk.camelot64.kickc.model.Program; +import dk.camelot64.kickc.model.iterator.ProgramValueIterator; +import dk.camelot64.kickc.model.operators.Operators; +import dk.camelot64.kickc.model.statements.Statement; +import dk.camelot64.kickc.model.statements.StatementAssignment; +import dk.camelot64.kickc.model.statements.StatementLValue; +import dk.camelot64.kickc.model.symbols.Symbol; +import dk.camelot64.kickc.model.types.SymbolTypePointer; +import dk.camelot64.kickc.model.types.SymbolTypeStruct; +import dk.camelot64.kickc.model.values.*; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Rewrite struct address-of to use the first member if the struct is unwound + */ +public class PassNStructAddressOfRewriting extends Pass2SsaOptimization { + + public PassNStructAddressOfRewriting(Program program) { + super(program); + } + + @Override + public boolean step() { + AtomicBoolean modified = new AtomicBoolean(false); + ProgramValueIterator.execute(getProgram(), (programValue, currentStmt, stmtIt, currentBlock) -> { + Value value = programValue.get(); + if(value instanceof ConstantSymbolPointer) { + ConstantSymbolPointer constantSymbolPointer = (ConstantSymbolPointer) value; + SymbolRef toSymbolRef = constantSymbolPointer.getToSymbol(); + Symbol toSymbol = getScope().getSymbol(toSymbolRef); + if(toSymbol.getType() instanceof SymbolTypeStruct) { + RValue rewrite = rewriteStructAddressOf((VariableRef) toSymbol); + if(rewrite!=null) { + programValue.set(rewrite); + } + getLog().append("Rewriting struct address-of to first member "+value.toString(getProgram())); + modified.set(true); + } + } + }); + + // Examine all statements + for(ControlFlowBlock block : getProgram().getGraph().getAllBlocks()) { + for(Statement statement : block.getStatements()) { + if(statement instanceof StatementAssignment) { + StatementAssignment assignment = (StatementAssignment) statement; + if(Operators.ADDRESS_OF.equals(assignment.getOperator()) ) { + RValue rValue = assignment.getrValue2(); + if(rValue instanceof SymbolVariableRef) { + Symbol toSymbol = getScope().getSymbol((SymbolVariableRef) rValue); + if(toSymbol.getType() instanceof SymbolTypeStruct) { + RValue rewrite = rewriteStructAddressOf((VariableRef) toSymbol.getRef()); + if(rewrite!=null) { + assignment.setOperator(null); + assignment.setrValue2(rewrite); + getLog().append("Rewriting struct address-of to first member "+assignment.toString(getProgram(), false)); + } + modified.set(true); + } + } + } + } + } + } + return modified.get(); + } + + private RValue rewriteStructAddressOf(VariableRef toSymbol) { + StatementLValue toSymbolAssignment = getGraph().getAssignment(toSymbol); + if(toSymbolAssignment instanceof StatementAssignment) { + StatementAssignment assignment = (StatementAssignment) toSymbolAssignment; + if(assignment.getrValue2() instanceof StructUnwoundPlaceholder) { + // Found placeholder assignment! + StructUnwoundPlaceholder placeholder = (StructUnwoundPlaceholder) assignment.getrValue2(); + SymbolRef firstMember = (SymbolRef) placeholder.getUnwoundMembers().get(0); + return new CastValue(new SymbolTypePointer(placeholder.getTypeStruct()), new ConstantSymbolPointer(firstMember)); + } + } + return null; + } + +} diff --git a/src/test/java/dk/camelot64/kickc/test/TestPrograms.java b/src/test/java/dk/camelot64/kickc/test/TestPrograms.java index 08e7365f5..edd1f35fa 100644 --- a/src/test/java/dk/camelot64/kickc/test/TestPrograms.java +++ b/src/test/java/dk/camelot64/kickc/test/TestPrograms.java @@ -75,15 +75,13 @@ public class TestPrograms { @Test public void testStructPtr12Ref() throws IOException, URISyntaxException { - compileAndCompare("struct-ptr-12-ref", log()); + compileAndCompare("struct-ptr-12-ref"); } - /* @Test public void testStructPtr12() throws IOException, URISyntaxException { - compileAndCompare("struct-ptr-12", log().verboseCreateSsa().verboseParse().verboseStatementSequence()); + compileAndCompare("struct-ptr-12"); } - */ @Test public void testStructPtr11() throws IOException, URISyntaxException { diff --git a/src/test/kc/struct-ptr-12.kc b/src/test/kc/struct-ptr-12.kc index b9dc1e6b2..ef48015dc 100644 --- a/src/test/kc/struct-ptr-12.kc +++ b/src/test/kc/struct-ptr-12.kc @@ -5,7 +5,7 @@ struct Point { }; void main() { - struct Point p = { 2, 3 }; + volatile struct Point p = { 2, 3 }; struct Point *q = &p; const byte* SCREEN = 0x0400; SCREEN[0] = q->x; diff --git a/src/test/ref/complex/clearscreen/clearscreen.log b/src/test/ref/complex/clearscreen/clearscreen.log index 1581537ff..58a5babf8 100644 --- a/src/test/ref/complex/clearscreen/clearscreen.log +++ b/src/test/ref/complex/clearscreen/clearscreen.log @@ -473,7 +473,7 @@ getCharToProcess::@1: scope:[getCharToProcess] from getCharToProcess::@10 getCh (byte) getCharToProcess::return_x#1 ← (byte) getCharToProcess::closest_x#2 (byte) getCharToProcess::return_y#1 ← (byte) getCharToProcess::closest_y#2 (word) getCharToProcess::return_dist#1 ← (word) getCharToProcess::closest_dist#4 - (struct ProcessingChar) getCharToProcess::return#0 ← struct-unwound + (struct ProcessingChar) getCharToProcess::return#0 ← struct-unwound {(byte) getCharToProcess::return_x#1, (byte) getCharToProcess::return_y#1, (word) getCharToProcess::return_dist#1} to:getCharToProcess::@return getCharToProcess::@11: scope:[getCharToProcess] from getCharToProcess::@10 (word) getCharToProcess::closest_dist#7 ← phi( getCharToProcess::@10/(word) getCharToProcess::closest_dist#3 ) @@ -492,7 +492,7 @@ getCharToProcess::@return: scope:[getCharToProcess] from getCharToProcess::@1 (byte) getCharToProcess::return_x#2 ← (byte) getCharToProcess::return_x#4 (byte) getCharToProcess::return_y#2 ← (byte) getCharToProcess::return_y#4 (word) getCharToProcess::return_dist#2 ← (word) getCharToProcess::return_dist#4 - (struct ProcessingChar) getCharToProcess::return#1 ← struct-unwound + (struct ProcessingChar) getCharToProcess::return#1 ← struct-unwound {(byte) getCharToProcess::return_x#2, (byte) getCharToProcess::return_y#2, (word) getCharToProcess::return_dist#2} return to:@return startProcessing: scope:[startProcessing] from main::@6 @@ -2597,9 +2597,9 @@ Simplifying expression containing zero (word*)processChars::processing#0 in [371 Simplifying expression containing zero (word*)processChars::processing#0 in [371] *((word*)(struct ProcessingSprite*) processChars::processing#0 + (const byte) OFFSET_STRUCT_PROCESSINGSPRITE_X) ← *((word*)(struct ProcessingSprite*) processChars::processing#0) + *((word*)(struct ProcessingSprite*) processChars::processing#0 + (const byte) OFFSET_STRUCT_PROCESSINGSPRITE_VX) Successful SSA optimization PassNSimplifyExpressionWithZero Eliminating unused variable (word) startProcessing::center_dist#0 and assignment [51] (word) startProcessing::center_dist#0 ← (word) main::center_dist#0 -Eliminating unused variable (struct ProcessingChar) getCharToProcess::return#0 and assignment [72] (struct ProcessingChar) getCharToProcess::return#0 ← struct-unwound +Eliminating unused variable (struct ProcessingChar) getCharToProcess::return#0 and assignment [72] (struct ProcessingChar) getCharToProcess::return#0 ← struct-unwound {(byte) getCharToProcess::return_x#1, (byte) getCharToProcess::return_y#1, (word) getCharToProcess::return_dist#1} Eliminating unused variable (byte*~) getCharToProcess::$12 and assignment [76] (byte*~) getCharToProcess::$12 ← (byte*~) getCharToProcess::$11 + (byte) getCharToProcess::return_x#1 -Eliminating unused variable (struct ProcessingChar) getCharToProcess::return#1 and assignment [78] (struct ProcessingChar) getCharToProcess::return#1 ← struct-unwound +Eliminating unused variable (struct ProcessingChar) getCharToProcess::return#1 and assignment [78] (struct ProcessingChar) getCharToProcess::return#1 ← struct-unwound {(byte) getCharToProcess::return_x#1, (byte) getCharToProcess::return_y#1, (word) getCharToProcess::return_dist#1} Eliminating unused variable (byte*) processChars::$40 and assignment [157] (byte*) processChars::$40 ← (byte*)(struct ProcessingSprite*) processChars::processing#0 + (const byte) OFFSET_STRUCT_PROCESSINGSPRITE_ID Eliminating unused variable (byte*) processChars::$41 and assignment [159] (byte*) processChars::$41 ← (byte*)(struct ProcessingSprite*) processChars::processing#0 + (const byte) OFFSET_STRUCT_PROCESSINGSPRITE_STATUS Eliminating unused variable (byte*) processChars::$42 and assignment [164] (byte*) processChars::$42 ← (byte*)(struct ProcessingSprite*) processChars::processing#0 + (const byte) OFFSET_STRUCT_PROCESSINGSPRITE_STATUS diff --git a/src/test/ref/struct-5.log b/src/test/ref/struct-5.log index f441f8884..bcc58221a 100644 --- a/src/test/ref/struct-5.log +++ b/src/test/ref/struct-5.log @@ -61,14 +61,14 @@ point: scope:[point] from main (byte) point::p_y#0 ← (number) 3 (byte) point::return_x#1 ← (byte) point::p_x#0 (byte) point::return_y#1 ← (byte) point::p_y#0 - (struct Point) point::return#0 ← struct-unwound + (struct Point) point::return#0 ← struct-unwound {(byte) point::return_x#1, (byte) point::return_y#1} to:point::@return point::@return: scope:[point] from point (byte) point::return_y#4 ← phi( point/(byte) point::return_y#1 ) (byte) point::return_x#4 ← phi( point/(byte) point::return_x#1 ) (byte) point::return_x#2 ← (byte) point::return_x#4 (byte) point::return_y#2 ← (byte) point::return_y#4 - (struct Point) point::return#1 ← struct-unwound + (struct Point) point::return#1 ← struct-unwound {(byte) point::return_x#2, (byte) point::return_y#2} return to:@return @2: scope:[] from @begin @@ -162,8 +162,8 @@ Constant (const byte) main::q_y#1 = point::return_y#0 Successful SSA optimization Pass2ConstantIdentification Simplifying expression containing zero main::SCREEN#0 in [11] *((const byte*) main::SCREEN#0 + (byte) 0) ← (const byte) main::q_x#1 Successful SSA optimization PassNSimplifyExpressionWithZero -Eliminating unused variable (struct Point) point::return#0 and assignment [4] (struct Point) point::return#0 ← struct-unwound -Eliminating unused variable (struct Point) point::return#1 and assignment [5] (struct Point) point::return#1 ← struct-unwound +Eliminating unused variable (struct Point) point::return#0 and assignment [4] (struct Point) point::return#0 ← struct-unwound {(const byte) point::p_x#0, (const byte) point::p_y#0} +Eliminating unused variable (struct Point) point::return#1 and assignment [5] (struct Point) point::return#1 ← struct-unwound {(const byte) point::p_x#0, (const byte) point::p_y#0} Eliminating unused constant (const byte) main::q_x#0 Eliminating unused constant (const byte) main::q_y#0 Successful SSA optimization PassNEliminateUnusedVars diff --git a/src/test/ref/struct-ptr-12.asm b/src/test/ref/struct-ptr-12.asm new file mode 100644 index 000000000..d441c3e66 --- /dev/null +++ b/src/test/ref/struct-ptr-12.asm @@ -0,0 +1,20 @@ +// Minimal struct - using address-of +.pc = $801 "Basic" +:BasicUpstart(main) +.pc = $80d "Program" + .const OFFSET_STRUCT_POINT_Y = 1 +main: { + .label q = p_x + .label SCREEN = $400 + .label p_x = 2 + .label p_y = 3 + lda #2 + sta p_x + lda #3 + sta p_y + lda q + sta SCREEN + lda q+OFFSET_STRUCT_POINT_Y + sta SCREEN+1 + rts +} diff --git a/src/test/ref/struct-ptr-12.cfg b/src/test/ref/struct-ptr-12.cfg new file mode 100644 index 000000000..a6942fa86 --- /dev/null +++ b/src/test/ref/struct-ptr-12.cfg @@ -0,0 +1,18 @@ +@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::p_x#0 ← (byte) 2 + [5] (byte) main::p_y#0 ← (byte) 3 + [6] *((const byte*) main::SCREEN#0) ← *((byte*)(const struct Point*) main::q#0) + [7] *((const byte*) main::SCREEN#0+(byte) 1) ← *((byte*)(const struct Point*) main::q#0+(const byte) OFFSET_STRUCT_POINT_Y) + to:main::@return +main::@return: scope:[main] from main + [8] return + to:@return diff --git a/src/test/ref/struct-ptr-12.log b/src/test/ref/struct-ptr-12.log new file mode 100644 index 000000000..fd305fe89 --- /dev/null +++ b/src/test/ref/struct-ptr-12.log @@ -0,0 +1,358 @@ +Created struct value member variable (byte) main::p_x +Created struct value member variable (byte) main::p_y +Converted struct value to member variables (struct Point) main::p +Adding struct value list initializer (byte) main::p_x ← (number) 2 +Adding struct value list initializer (byte) main::p_y ← (number) 3 +Rewriting struct pointer member access *((struct Point*) main::q).x +Rewriting struct pointer member access *((struct Point*) main::q).y +Adding pointer type conversion cast (byte*) main::SCREEN in (byte*) main::SCREEN ← (number) $400 + +CONTROL FLOW GRAPH SSA +@begin: scope:[] from + to:@1 +main: scope:[main] from @1 + (byte) main::p_x#0 ← (number) 2 + (byte) main::p_y#0 ← (number) 3 + (struct Point) main::p#0 ← struct-unwound {(byte) main::p_x#0, (byte) main::p_y#0} + (struct Point*~) main::$0 ← & (struct Point) main::p#0 + (struct Point*) main::q#0 ← (struct Point*~) main::$0 + (byte*) main::SCREEN#0 ← ((byte*)) (number) $400 + (byte*) main::$1 ← (byte*)(struct Point*) main::q#0 + (const byte) OFFSET_STRUCT_POINT_X + *((byte*) main::SCREEN#0 + (number) 0) ← *((byte*) main::$1) + (byte*) main::$2 ← (byte*)(struct Point*) main::q#0 + (const byte) OFFSET_STRUCT_POINT_Y + *((byte*) main::SCREEN#0 + (number) 1) ← *((byte*) main::$2) + to:main::@return +main::@return: scope:[main] from main + return + to:@return +@1: scope:[] from @begin + call main + to:@2 +@2: scope:[] from @1 + to:@end +@end: scope:[] from @2 + +SYMBOL TABLE SSA +(label) @1 +(label) @2 +(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 +(void()) main() +(struct Point*~) main::$0 +(byte*) main::$1 +(byte*) main::$2 +(label) main::@return +(byte*) main::SCREEN +(byte*) main::SCREEN#0 +(struct Point) main::p +(struct Point) main::p#0 +(byte) main::p_x +(byte) main::p_x#0 +(byte) main::p_y +(byte) main::p_y#0 +(struct Point*) main::q +(struct Point*) main::q#0 + +Adding number conversion cast (unumber) 2 in (byte) main::p_x#0 ← (number) 2 +Adding number conversion cast (unumber) 3 in (byte) main::p_y#0 ← (number) 3 +Adding number conversion cast (unumber) 0 in *((byte*) main::SCREEN#0 + (number) 0) ← *((byte*) main::$1) +Adding number conversion cast (unumber) 1 in *((byte*) main::SCREEN#0 + (number) 1) ← *((byte*) main::$2) +Successful SSA optimization PassNAddNumberTypeConversions +Inlining cast (byte) main::p_x#0 ← (unumber)(number) 2 +Inlining cast (byte) main::p_y#0 ← (unumber)(number) 3 +Inlining cast (byte*) main::SCREEN#0 ← (byte*)(number) $400 +Successful SSA optimization Pass2InlineCast +Simplifying constant integer cast 2 +Simplifying constant integer cast 3 +Simplifying constant pointer cast (byte*) 1024 +Simplifying constant integer cast 0 +Simplifying constant integer cast 1 +Successful SSA optimization PassNCastSimplification +Finalized unsigned number type (byte) 2 +Finalized unsigned number type (byte) 3 +Finalized unsigned number type (byte) 0 +Finalized unsigned number type (byte) 1 +Successful SSA optimization PassNFinalizeNumberTypeConversions +Alias (struct Point*) main::q#0 = (struct Point*~) main::$0 +Successful SSA optimization Pass2AliasElimination +Rewriting struct address-of to first member [3] (struct Point*) main::q#0 ← (struct Point*)&(byte) main::p_x#0 +Successful SSA optimization PassNStructAddressOfRewriting +Constant right-side identified [3] (struct Point*) main::q#0 ← (struct Point*)&(byte) main::p_x#0 +Successful SSA optimization Pass2ConstantRValueConsolidation +Constant (const struct Point*) main::q#0 = (struct Point*)&main::p_x#0 +Constant (const byte*) main::SCREEN#0 = (byte*) 1024 +Successful SSA optimization Pass2ConstantIdentification +Constant value identified (byte*)main::q#0 in [6] (byte*) main::$1 ← (byte*)(const struct Point*) main::q#0 + (const byte) OFFSET_STRUCT_POINT_X +Constant value identified (byte*)main::q#0 in [8] (byte*) main::$2 ← (byte*)(const struct Point*) main::q#0 + (const byte) OFFSET_STRUCT_POINT_Y +Successful SSA optimization Pass2ConstantValues +Converting *(pointer+n) to pointer[n] [7] *((const byte*) main::SCREEN#0 + (byte) 0) ← *((byte*) main::$1) -- *((byte*)main::q#0 + OFFSET_STRUCT_POINT_X) +Converting *(pointer+n) to pointer[n] [9] *((const byte*) main::SCREEN#0 + (byte) 1) ← *((byte*) main::$2) -- *((byte*)main::q#0 + OFFSET_STRUCT_POINT_Y) +Successful SSA optimization Pass2InlineDerefIdx +Simplifying expression containing zero (byte*)main::q#0 in [6] (byte*) main::$1 ← (byte*)(const struct Point*) main::q#0 + (const byte) OFFSET_STRUCT_POINT_X +Simplifying expression containing zero (byte*)main::q#0 in [7] *((const byte*) main::SCREEN#0 + (byte) 0) ← *((byte*)(const struct Point*) main::q#0 + (const byte) OFFSET_STRUCT_POINT_X) +Simplifying expression containing zero main::SCREEN#0 in [7] *((const byte*) main::SCREEN#0 + (byte) 0) ← *((byte*)(const struct Point*) main::q#0) +Successful SSA optimization PassNSimplifyExpressionWithZero +Eliminating unused variable (struct Point) main::p#0 and assignment [2] (struct Point) main::p#0 ← struct-unwound {(byte) main::p_x#0, (byte) main::p_y#0} +Eliminating unused variable (byte*) main::$1 and assignment [3] (byte*) main::$1 ← (byte*)(const struct Point*) main::q#0 +Eliminating unused variable (byte*) main::$2 and assignment [5] (byte*) main::$2 ← (byte*)(const struct Point*) main::q#0 + (const byte) OFFSET_STRUCT_POINT_Y +Eliminating unused constant (const byte) OFFSET_STRUCT_POINT_X +Successful SSA optimization PassNEliminateUnusedVars +Consolidated array index constant in *((byte*)main::q#0+OFFSET_STRUCT_POINT_Y) +Consolidated array index constant in *(main::SCREEN#0+1) +Successful SSA optimization Pass2ConstantAdditionElimination +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 @end +CALL GRAPH +Calls in [] to main:2 + +Created 0 initial phi equivalence classes +Coalesced down to 0 phi equivalence classes +Culled Empty Block (label) @2 +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::p_x#0 ← (byte) 2 + [5] (byte) main::p_y#0 ← (byte) 3 + [6] *((const byte*) main::SCREEN#0) ← *((byte*)(const struct Point*) main::q#0) + [7] *((const byte*) main::SCREEN#0+(byte) 1) ← *((byte*)(const struct Point*) main::q#0+(const byte) OFFSET_STRUCT_POINT_Y) + to:main::@return +main::@return: scope:[main] from main + [8] return + to:@return + + +VARIABLE REGISTER WEIGHTS +(byte) Point::x +(byte) Point::y +(void()) main() +(byte*) main::SCREEN +(struct Point) main::p +(byte) main::p_x +(byte) main::p_x#0 20.0 +(byte) main::p_y +(byte) main::p_y#0 20.0 +(struct Point*) main::q + +Initial phi equivalence classes +Complete equivalence classes +[ main::p_x#0 ] +[ main::p_y#0 ] +Allocated zp ZP_BYTE:2 [ main::p_x#0 ] +Allocated zp ZP_BYTE:3 [ main::p_y#0 ] + +INITIAL ASM +//SEG0 File Comments +// Minimal struct - using address-of +//SEG1 Basic Upstart +.pc = $801 "Basic" +:BasicUpstart(bbegin) +.pc = $80d "Program" +//SEG2 Global Constants & labels + .const OFFSET_STRUCT_POINT_Y = 1 +//SEG3 @begin +bbegin: +//SEG4 [1] phi from @begin to @1 [phi:@begin->@1] +b1_from_bbegin: + jmp b1 +//SEG5 @1 +b1: +//SEG6 [2] call main + jsr main +//SEG7 [3] phi from @1 to @end [phi:@1->@end] +bend_from_b1: + jmp bend +//SEG8 @end +bend: +//SEG9 main +main: { + .label q = p_x + .label SCREEN = $400 + .label p_x = 2 + .label p_y = 3 + //SEG10 [4] (byte) main::p_x#0 ← (byte) 2 -- vbuz1=vbuc1 + lda #2 + sta p_x + //SEG11 [5] (byte) main::p_y#0 ← (byte) 3 -- vbuz1=vbuc1 + lda #3 + sta p_y + //SEG12 [6] *((const byte*) main::SCREEN#0) ← *((byte*)(const struct Point*) main::q#0) -- _deref_pbuc1=_deref_pbuc2 + lda q + sta SCREEN + //SEG13 [7] *((const byte*) main::SCREEN#0+(byte) 1) ← *((byte*)(const struct Point*) main::q#0+(const byte) OFFSET_STRUCT_POINT_Y) -- _deref_pbuc1=_deref_pbuc2 + lda q+OFFSET_STRUCT_POINT_Y + sta SCREEN+1 + jmp breturn + //SEG14 main::@return + breturn: + //SEG15 [8] return + rts +} + +REGISTER UPLIFT POTENTIAL REGISTERS +Statement [4] (byte) main::p_x#0 ← (byte) 2 [ ] ( main:2 [ ] ) always clobbers reg byte a +Statement [5] (byte) main::p_y#0 ← (byte) 3 [ ] ( main:2 [ ] ) always clobbers reg byte a +Statement [6] *((const byte*) main::SCREEN#0) ← *((byte*)(const struct Point*) main::q#0) [ ] ( main:2 [ ] ) always clobbers reg byte a +Statement [7] *((const byte*) main::SCREEN#0+(byte) 1) ← *((byte*)(const struct Point*) main::q#0+(const byte) OFFSET_STRUCT_POINT_Y) [ ] ( main:2 [ ] ) always clobbers reg byte a +Potential registers zp ZP_BYTE:2 [ main::p_x#0 ] : zp ZP_BYTE:2 , +Potential registers zp ZP_BYTE:3 [ main::p_y#0 ] : zp ZP_BYTE:3 , + +REGISTER UPLIFT SCOPES +Uplift Scope [main] 20: zp ZP_BYTE:2 [ main::p_x#0 ] 20: zp ZP_BYTE:3 [ main::p_y#0 ] +Uplift Scope [Point] +Uplift Scope [] + +Uplifting [main] best 47 combination zp ZP_BYTE:2 [ main::p_x#0 ] zp ZP_BYTE:3 [ main::p_y#0 ] +Uplifting [Point] best 47 combination +Uplifting [] best 47 combination +Attempting to uplift remaining variables inzp ZP_BYTE:2 [ main::p_x#0 ] +Uplifting [main] best 47 combination zp ZP_BYTE:2 [ main::p_x#0 ] +Attempting to uplift remaining variables inzp ZP_BYTE:3 [ main::p_y#0 ] +Uplifting [main] best 47 combination zp ZP_BYTE:3 [ main::p_y#0 ] + +ASSEMBLER BEFORE OPTIMIZATION +//SEG0 File Comments +// Minimal struct - using address-of +//SEG1 Basic Upstart +.pc = $801 "Basic" +:BasicUpstart(bbegin) +.pc = $80d "Program" +//SEG2 Global Constants & labels + .const OFFSET_STRUCT_POINT_Y = 1 +//SEG3 @begin +bbegin: +//SEG4 [1] phi from @begin to @1 [phi:@begin->@1] +b1_from_bbegin: + jmp b1 +//SEG5 @1 +b1: +//SEG6 [2] call main + jsr main +//SEG7 [3] phi from @1 to @end [phi:@1->@end] +bend_from_b1: + jmp bend +//SEG8 @end +bend: +//SEG9 main +main: { + .label q = p_x + .label SCREEN = $400 + .label p_x = 2 + .label p_y = 3 + //SEG10 [4] (byte) main::p_x#0 ← (byte) 2 -- vbuz1=vbuc1 + lda #2 + sta p_x + //SEG11 [5] (byte) main::p_y#0 ← (byte) 3 -- vbuz1=vbuc1 + lda #3 + sta p_y + //SEG12 [6] *((const byte*) main::SCREEN#0) ← *((byte*)(const struct Point*) main::q#0) -- _deref_pbuc1=_deref_pbuc2 + lda q + sta SCREEN + //SEG13 [7] *((const byte*) main::SCREEN#0+(byte) 1) ← *((byte*)(const struct Point*) main::q#0+(const byte) OFFSET_STRUCT_POINT_Y) -- _deref_pbuc1=_deref_pbuc2 + lda q+OFFSET_STRUCT_POINT_Y + sta SCREEN+1 + jmp breturn + //SEG14 main::@return + breturn: + //SEG15 [8] return + rts +} + +ASSEMBLER OPTIMIZATIONS +Removing instruction jmp b1 +Removing instruction jmp bend +Removing instruction jmp breturn +Succesful ASM optimization Pass5NextJumpElimination +Removing instruction b1_from_bbegin: +Removing instruction b1: +Removing instruction bend_from_b1: +Succesful ASM optimization Pass5RedundantLabelElimination +Removing instruction bend: +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 +(void()) main() +(label) main::@return +(byte*) main::SCREEN +(const byte*) main::SCREEN#0 SCREEN = (byte*) 1024 +(struct Point) main::p +(byte) main::p_x +(byte) main::p_x#0 p_x zp ZP_BYTE:2 20.0 +(byte) main::p_y +(byte) main::p_y#0 p_y zp ZP_BYTE:3 20.0 +(struct Point*) main::q +(const struct Point*) main::q#0 q = (struct Point*)&(byte) main::p_x#0 + +zp ZP_BYTE:2 [ main::p_x#0 ] +zp ZP_BYTE:3 [ main::p_y#0 ] + + +FINAL ASSEMBLER +Score: 32 + +//SEG0 File Comments +// Minimal struct - using address-of +//SEG1 Basic Upstart +.pc = $801 "Basic" +:BasicUpstart(main) +.pc = $80d "Program" +//SEG2 Global Constants & labels + .const OFFSET_STRUCT_POINT_Y = 1 +//SEG3 @begin +//SEG4 [1] phi from @begin to @1 [phi:@begin->@1] +//SEG5 @1 +//SEG6 [2] call main +//SEG7 [3] phi from @1 to @end [phi:@1->@end] +//SEG8 @end +//SEG9 main +main: { + .label q = p_x + .label SCREEN = $400 + .label p_x = 2 + .label p_y = 3 + //SEG10 [4] (byte) main::p_x#0 ← (byte) 2 -- vbuz1=vbuc1 + lda #2 + sta p_x + //SEG11 [5] (byte) main::p_y#0 ← (byte) 3 -- vbuz1=vbuc1 + lda #3 + sta p_y + //SEG12 [6] *((const byte*) main::SCREEN#0) ← *((byte*)(const struct Point*) main::q#0) -- _deref_pbuc1=_deref_pbuc2 + lda q + sta SCREEN + //SEG13 [7] *((const byte*) main::SCREEN#0+(byte) 1) ← *((byte*)(const struct Point*) main::q#0+(const byte) OFFSET_STRUCT_POINT_Y) -- _deref_pbuc1=_deref_pbuc2 + lda q+OFFSET_STRUCT_POINT_Y + sta SCREEN+1 + //SEG14 main::@return + //SEG15 [8] return + rts +} + diff --git a/src/test/ref/struct-ptr-12.sym b/src/test/ref/struct-ptr-12.sym new file mode 100644 index 000000000..768169aa9 --- /dev/null +++ b/src/test/ref/struct-ptr-12.sym @@ -0,0 +1,20 @@ +(label) @1 +(label) @begin +(label) @end +(const byte) OFFSET_STRUCT_POINT_Y OFFSET_STRUCT_POINT_Y = (byte) 1 +(byte) Point::x +(byte) Point::y +(void()) main() +(label) main::@return +(byte*) main::SCREEN +(const byte*) main::SCREEN#0 SCREEN = (byte*) 1024 +(struct Point) main::p +(byte) main::p_x +(byte) main::p_x#0 p_x zp ZP_BYTE:2 20.0 +(byte) main::p_y +(byte) main::p_y#0 p_y zp ZP_BYTE:3 20.0 +(struct Point*) main::q +(const struct Point*) main::q#0 q = (struct Point*)&(byte) main::p_x#0 + +zp ZP_BYTE:2 [ main::p_x#0 ] +zp ZP_BYTE:3 [ main::p_y#0 ]