From 5b2e9939941095de374e4ae8e8ab45c540be6076 Mon Sep 17 00:00:00 2001 From: jespergravgaard Date: Sat, 25 Apr 2020 16:51:35 +0200 Subject: [PATCH] Fixed stack-overflow on recursive __stackcall functions. Added abs(). Added printf(). Closes #415 --- .../dk/camelot64/kickc/model/CallGraph.java | 21 +- .../PassNCalcVariableRegisterWeight.java | 16 +- src/main/kc/include/math.h | 0 src/main/kc/include/stdio.h | 3 + src/main/kc/include/stdlib.h | 3 + src/main/kc/lib/math.c | 0 src/main/kc/lib/stdlib.c | 10 +- .../dk/camelot64/kickc/test/TestPrograms.java | 5 + .../kc/procedure-callingconvention-stack-13.c | 18 + .../procedure-callingconvention-stack-13.asm | 46 ++ .../procedure-callingconvention-stack-13.cfg | 38 ++ .../procedure-callingconvention-stack-13.log | 564 ++++++++++++++++++ .../procedure-callingconvention-stack-13.sym | 27 + 13 files changed, 731 insertions(+), 20 deletions(-) create mode 100644 src/main/kc/include/math.h create mode 100644 src/main/kc/include/stdio.h create mode 100644 src/main/kc/lib/math.c create mode 100644 src/test/kc/procedure-callingconvention-stack-13.c create mode 100644 src/test/ref/procedure-callingconvention-stack-13.asm create mode 100644 src/test/ref/procedure-callingconvention-stack-13.cfg create mode 100644 src/test/ref/procedure-callingconvention-stack-13.log create mode 100644 src/test/ref/procedure-callingconvention-stack-13.sym diff --git a/src/main/java/dk/camelot64/kickc/model/CallGraph.java b/src/main/java/dk/camelot64/kickc/model/CallGraph.java index 73d138ec0..7aa3a6bde 100644 --- a/src/main/java/dk/camelot64/kickc/model/CallGraph.java +++ b/src/main/java/dk/camelot64/kickc/model/CallGraph.java @@ -221,21 +221,16 @@ public class CallGraph { } } - public int getCallDepth(ProcedureRef procedureRef) { - final Collection callers = getCallers(procedureRef); - int maxCallDepth = 1; - for(CallBlock.Call caller : callers) { - final ScopeRef callStatementScope = caller.getCallStatementScope(); - if(callStatementScope instanceof ProcedureRef) { - int callerDepth = getCallDepth((ProcedureRef) callStatementScope)+1; - if(callerDepth>maxCallDepth) - maxCallDepth = callerDepth; - } - } - return maxCallDepth; + /** + * Determine if a specific call is part of a recursive procedure call + * @param call to examine + * @return true if the call is part of procedure recursive calls + */ + public boolean isRecursive(CallBlock.Call call) { + final Collection recursiveCalls = getRecursiveCalls(call.callStatementScope); + return recursiveCalls.contains(call.callStatementScope); } - /** * A block in the call graph, matching a scope in the program. */ diff --git a/src/main/java/dk/camelot64/kickc/passes/calcs/PassNCalcVariableRegisterWeight.java b/src/main/java/dk/camelot64/kickc/passes/calcs/PassNCalcVariableRegisterWeight.java index 4102694a1..3b12bc657 100644 --- a/src/main/java/dk/camelot64/kickc/passes/calcs/PassNCalcVariableRegisterWeight.java +++ b/src/main/java/dk/camelot64/kickc/passes/calcs/PassNCalcVariableRegisterWeight.java @@ -79,7 +79,7 @@ public class PassNCalcVariableRegisterWeight extends PassNCalcBase callers = callGraph.getCallers(procedureRef); int maxCallDepth = 0; for(CallGraph.CallBlock.Call caller : callers) { - final Integer callStatementIdx = caller.getCallStatementIdx(); - final ControlFlowBlock callBlock = statementInfos.getBlock(callStatementIdx); - int callDepth = getLoopCallDepth(callBlock.getLabel(), loopSet, callGraph, statementInfos) + 1; - if(callDepth > maxCallDepth) - maxCallDepth = callDepth; + if(callGraph.isRecursive(caller)) { + maxCallDepth = 1; + } else { + final Integer callStatementIdx = caller.getCallStatementIdx(); + final ControlFlowBlock callBlock = statementInfos.getBlock(callStatementIdx); + int callDepth = getLoopCallDepth(callBlock.getLabel(), loopSet, callGraph, statementInfos) + 1; + if(callDepth > maxCallDepth) + maxCallDepth = callDepth; + } } int loopDepth = loopSet.getMaxLoopDepth(block); return maxCallDepth + loopDepth; diff --git a/src/main/kc/include/math.h b/src/main/kc/include/math.h new file mode 100644 index 000000000..e69de29bb diff --git a/src/main/kc/include/stdio.h b/src/main/kc/include/stdio.h new file mode 100644 index 000000000..444542793 --- /dev/null +++ b/src/main/kc/include/stdio.h @@ -0,0 +1,3 @@ +// Functions for performing input and output. + +#include \ No newline at end of file diff --git a/src/main/kc/include/stdlib.h b/src/main/kc/include/stdlib.h index da2f4467d..7e79079e5 100644 --- a/src/main/kc/include/stdlib.h +++ b/src/main/kc/include/stdlib.h @@ -39,3 +39,6 @@ void utoa(unsigned int value, char* buffer, enum RADIX radix); // - radix : The radix to convert the number to (from the enum RADIX) void ultoa(unsigned long value, char* buffer, enum RADIX radix); +// Returns the absolute value of int x. +int abs(int x); + diff --git a/src/main/kc/lib/math.c b/src/main/kc/lib/math.c new file mode 100644 index 000000000..e69de29bb diff --git a/src/main/kc/lib/stdlib.c b/src/main/kc/lib/stdlib.c index 107f813cd..d8c55aec4 100644 --- a/src/main/kc/lib/stdlib.c +++ b/src/main/kc/lib/stdlib.c @@ -247,4 +247,12 @@ unsigned long ultoa_append(char *buffer, unsigned long value, unsigned long sub) while (value >= sub){ digit++; value -= sub; } *buffer = DIGITS[digit]; return value; -} \ No newline at end of file +} + +// Returns the absolute value of int x. +inline int abs(int x) { + if(x<0) + return -x; + else + return x; +} diff --git a/src/test/java/dk/camelot64/kickc/test/TestPrograms.java b/src/test/java/dk/camelot64/kickc/test/TestPrograms.java index 6945670fb..cf4f701b4 100644 --- a/src/test/java/dk/camelot64/kickc/test/TestPrograms.java +++ b/src/test/java/dk/camelot64/kickc/test/TestPrograms.java @@ -517,6 +517,11 @@ public class TestPrograms { compileAndCompare("declared-memory-var-0.c"); } + @Test + public void testProcedureCallingConventionStack13() throws IOException, URISyntaxException { + compileAndCompare("procedure-callingconvention-stack-13.c"); + } + @Test public void testProcedureCallingConventionStack12() throws IOException, URISyntaxException { compileAndCompare("procedure-callingconvention-stack-12.c"); diff --git a/src/test/kc/procedure-callingconvention-stack-13.c b/src/test/kc/procedure-callingconvention-stack-13.c new file mode 100644 index 000000000..634f9dbb7 --- /dev/null +++ b/src/test/kc/procedure-callingconvention-stack-13.c @@ -0,0 +1,18 @@ +// Test a procedure with calling convention stack +// Recursion that works (no local variables) + +char* const SCREEN = 0x0400; + +void main(void) { + *SCREEN = pow2(6); +} + +char __stackcall pow2(char n) { + if (n == 0) + return 1; + else { + char c = pow2(n-1); + return c+c; + } + +} \ No newline at end of file diff --git a/src/test/ref/procedure-callingconvention-stack-13.asm b/src/test/ref/procedure-callingconvention-stack-13.asm new file mode 100644 index 000000000..c7ad7cd0a --- /dev/null +++ b/src/test/ref/procedure-callingconvention-stack-13.asm @@ -0,0 +1,46 @@ +// Test a procedure with calling convention stack +// Recursion that works (no local variables) +.pc = $801 "Basic" +:BasicUpstart(main) +.pc = $80d "Program" + .label SCREEN = $400 + .const STACK_BASE = $103 +main: { + // pow2(6) + lda #6 + pha + jsr pow2 + pla + // *SCREEN = pow2(6) + sta SCREEN + // } + rts +} +// pow2(byte register(A) n) +pow2: { + .const OFFSET_STACK_N = 0 + .const OFFSET_STACK_RETURN = 0 + tsx + lda STACK_BASE+OFFSET_STACK_N,x + // if (n == 0) + cmp #0 + beq __b1 + // n-1 + sec + sbc #1 + // pow2(n-1) + pha + jsr pow2 + // c = pow2(n-1) + pla + // return c+c; + asl + jmp __breturn + __b1: + lda #1 + __breturn: + // } + tsx + sta STACK_BASE+OFFSET_STACK_RETURN,x + rts +} diff --git a/src/test/ref/procedure-callingconvention-stack-13.cfg b/src/test/ref/procedure-callingconvention-stack-13.cfg new file mode 100644 index 000000000..d2be94cf8 --- /dev/null +++ b/src/test/ref/procedure-callingconvention-stack-13.cfg @@ -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() + +(void()) main() +main: scope:[main] from @1 + [4] stackpush(byte) ← (byte) 6 + [5] callexecute pow2 + [6] (byte~) main::$0 ← stackpull(byte) + [7] *((const nomodify byte*) SCREEN) ← (byte~) main::$0 + to:main::@return +main::@return: scope:[main] from main + [8] return + to:@return + +__stackcall (byte()) pow2((byte) pow2::n) +pow2: scope:[pow2] from + [9] (byte) pow2::n#0 ← stackidx(byte,(const byte) pow2::OFFSET_STACK_N) + [10] if((byte) pow2::n#0==(byte) 0) goto pow2::@return + to:pow2::@1 +pow2::@1: scope:[pow2] from pow2 + [11] (byte~) pow2::$1 ← (byte) pow2::n#0 - (byte) 1 + [12] stackpush(byte) ← (byte~) pow2::$1 + [13] callexecute pow2 + [14] (byte) pow2::c#0 ← stackpull(byte) + [15] (byte) pow2::return#1 ← (byte) pow2::c#0 + (byte) pow2::c#0 + to:pow2::@return +pow2::@return: scope:[pow2] from pow2 pow2::@1 + [16] (byte) pow2::return#2 ← phi( pow2/(byte) 1 pow2::@1/(byte) pow2::return#1 ) + [17] stackidx(byte,(const byte) pow2::OFFSET_STACK_RETURN) ← (byte) pow2::return#2 + [18] return + to:@return diff --git a/src/test/ref/procedure-callingconvention-stack-13.log b/src/test/ref/procedure-callingconvention-stack-13.log new file mode 100644 index 000000000..1ae0a7a62 --- /dev/null +++ b/src/test/ref/procedure-callingconvention-stack-13.log @@ -0,0 +1,564 @@ +Culled Empty Block (label) @1 +Culled Empty Block (label) pow2::@4 +Culled Empty Block (label) pow2::@2 +Culled Empty Block (label) pow2::@5 +Culled Empty Block (label) pow2::@6 +Calling convention STACK_CALL adding prepare/execute/finalize for (byte~) main::$0 ← call pow2 (number) 6 +Calling convention STACK_CALL adding prepare/execute/finalize for (byte~) pow2::$2 ← call pow2 (number~) pow2::$1 +Calling convention STACK_CALL replacing param((byte) pow2::n) with stackidx(byte,(const byte) pow2::OFFSET_STACK_N) +Calling convention STACK_CALL adding stack return stackidx(byte,pow2::OFFSET_STACK_RETURN) ← pow2::return +Calling convention STACK_CALL adding stack pull main::$0 ← stackpull(byte) +Calling convention STACK_CALL adding stack pull pow2::$2 ← stackpull(byte) +Calling convention STACK_CALL adding stack push stackpush(byte) ← 6 +Calling convention STACK_CALL adding stack push stackpush(byte) ← pow2::$1 + +CONTROL FLOW GRAPH SSA +@begin: scope:[] from + to:@2 + +(void()) main() +main: scope:[main] from @2 + stackpush(byte) ← (number) 6 + callexecute pow2 + (byte~) main::$0 ← stackpull(byte) + *((const nomodify byte*) SCREEN) ← (byte~) main::$0 + to:main::@return +main::@return: scope:[main] from main + return + to:@return + +__stackcall (byte()) pow2((byte) pow2::n) +pow2: scope:[pow2] from + (byte) pow2::n#0 ← stackidx(byte,(const byte) pow2::OFFSET_STACK_N) + (bool~) pow2::$0 ← (byte) pow2::n#0 == (number) 0 + if((bool~) pow2::$0) goto pow2::@1 + to:pow2::@3 +pow2::@1: scope:[pow2] from pow2 + (byte) pow2::return#0 ← (number) 1 + to:pow2::@return +pow2::@3: scope:[pow2] from pow2 + (byte) pow2::n#1 ← phi( pow2/(byte) pow2::n#0 ) + (number~) pow2::$1 ← (byte) pow2::n#1 - (number) 1 + stackpush(byte) ← (number~) pow2::$1 + callexecute pow2 + (byte~) pow2::$2 ← stackpull(byte) + (byte) pow2::c#0 ← (byte~) pow2::$2 + (byte~) pow2::$3 ← (byte) pow2::c#0 + (byte) pow2::c#0 + (byte) pow2::return#1 ← (byte~) pow2::$3 + to:pow2::@return +pow2::@return: scope:[pow2] from pow2::@1 pow2::@3 + (byte) pow2::return#2 ← phi( pow2::@1/(byte) pow2::return#0 pow2::@3/(byte) pow2::return#1 ) + stackidx(byte,(const byte) pow2::OFFSET_STACK_RETURN) ← (byte) pow2::return#2 + return + to:@return +@2: scope:[] from @begin + call main + to:@3 +@3: scope:[] from @2 + to:@end +@end: scope:[] from @3 + +SYMBOL TABLE SSA +(label) @2 +(label) @3 +(label) @begin +(label) @end +(const nomodify byte*) SCREEN = (byte*)(number) $400 +(const word) STACK_BASE = (word) $103 +(void()) main() +(byte~) main::$0 +(label) main::@return +__stackcall (byte()) pow2((byte) pow2::n) +(bool~) pow2::$0 +(number~) pow2::$1 +(byte~) pow2::$2 +(byte~) pow2::$3 +(label) pow2::@1 +(label) pow2::@3 +(label) pow2::@return +(const byte) pow2::OFFSET_STACK_N = (byte) 0 +(const byte) pow2::OFFSET_STACK_RETURN = (byte) 0 +(byte) pow2::c +(byte) pow2::c#0 +(byte) pow2::n +(byte) pow2::n#0 +(byte) pow2::n#1 +(byte) pow2::return +(byte) pow2::return#0 +(byte) pow2::return#1 +(byte) pow2::return#2 + +Adding number conversion cast (unumber) 6 in stackpush(byte) ← (number) 6 +Adding number conversion cast (unumber) 0 in (bool~) pow2::$0 ← (byte) pow2::n#0 == (number) 0 +Adding number conversion cast (unumber) 1 in (byte) pow2::return#0 ← (number) 1 +Adding number conversion cast (unumber) 1 in (number~) pow2::$1 ← (byte) pow2::n#1 - (number) 1 +Adding number conversion cast (unumber) pow2::$1 in (number~) pow2::$1 ← (byte) pow2::n#1 - (unumber)(number) 1 +Successful SSA optimization PassNAddNumberTypeConversions +Inlining cast stackpush(byte) ← (unumber)(number) 6 +Inlining cast (byte) pow2::return#0 ← (unumber)(number) 1 +Successful SSA optimization Pass2InlineCast +Simplifying constant pointer cast (byte*) 1024 +Simplifying constant integer cast 6 +Simplifying constant integer cast 0 +Simplifying constant integer cast 1 +Simplifying constant integer cast 1 +Successful SSA optimization PassNCastSimplification +Finalized unsigned number type (byte) 6 +Finalized unsigned number type (byte) 0 +Finalized unsigned number type (byte) 1 +Finalized unsigned number type (byte) 1 +Successful SSA optimization PassNFinalizeNumberTypeConversions +Inferred type updated to byte in (unumber~) pow2::$1 ← (byte) pow2::n#1 - (byte) 1 +Alias pow2::n#0 = pow2::n#1 +Alias pow2::c#0 = pow2::$2 +Alias pow2::return#1 = pow2::$3 +Successful SSA optimization Pass2AliasElimination +Simple Condition (bool~) pow2::$0 [7] if((byte) pow2::n#0==(byte) 0) goto pow2::@1 +Successful SSA optimization Pass2ConditionalJumpSimplification +Constant (const byte) pow2::return#0 = 1 +Successful SSA optimization Pass2ConstantIdentification +Inlining constant with var siblings (const byte) pow2::return#0 +Constant inlined pow2::return#0 = (byte) 1 +Successful SSA optimization Pass2ConstantInlining +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 pow2::@1 +CALL GRAPH +Calls in [] to main:2 +Calls in [main] to pow2:6 +Calls in [pow2] to pow2:14 + +Created 1 initial phi equivalence classes +Coalesced [17] pow2::return#3 ← pow2::return#1 +Coalesced down to 1 phi equivalence classes +Culled Empty Block (label) @3 +Culled Empty Block (label) pow2::@1 +Renumbering block @2 to @1 +Renumbering block pow2::@3 to pow2::@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() + +(void()) main() +main: scope:[main] from @1 + [4] stackpush(byte) ← (byte) 6 + [5] callexecute pow2 + [6] (byte~) main::$0 ← stackpull(byte) + [7] *((const nomodify byte*) SCREEN) ← (byte~) main::$0 + to:main::@return +main::@return: scope:[main] from main + [8] return + to:@return + +__stackcall (byte()) pow2((byte) pow2::n) +pow2: scope:[pow2] from + [9] (byte) pow2::n#0 ← stackidx(byte,(const byte) pow2::OFFSET_STACK_N) + [10] if((byte) pow2::n#0==(byte) 0) goto pow2::@return + to:pow2::@1 +pow2::@1: scope:[pow2] from pow2 + [11] (byte~) pow2::$1 ← (byte) pow2::n#0 - (byte) 1 + [12] stackpush(byte) ← (byte~) pow2::$1 + [13] callexecute pow2 + [14] (byte) pow2::c#0 ← stackpull(byte) + [15] (byte) pow2::return#1 ← (byte) pow2::c#0 + (byte) pow2::c#0 + to:pow2::@return +pow2::@return: scope:[pow2] from pow2 pow2::@1 + [16] (byte) pow2::return#2 ← phi( pow2/(byte) 1 pow2::@1/(byte) pow2::return#1 ) + [17] stackidx(byte,(const byte) pow2::OFFSET_STACK_RETURN) ← (byte) pow2::return#2 + [18] return + to:@return + + +VARIABLE REGISTER WEIGHTS +(void()) main() +(byte~) main::$0 22.0 +__stackcall (byte()) pow2((byte) pow2::n) +(byte~) pow2::$1 22.0 +(byte) pow2::c +(byte) pow2::c#0 33.0 +(byte) pow2::n +(byte) pow2::n#0 16.5 +(byte) pow2::return +(byte) pow2::return#1 22.0 +(byte) pow2::return#2 22.0 + +Initial phi equivalence classes +[ pow2::return#2 pow2::return#1 ] +Added variable main::$0 to live range equivalence class [ main::$0 ] +Added variable pow2::n#0 to live range equivalence class [ pow2::n#0 ] +Added variable pow2::$1 to live range equivalence class [ pow2::$1 ] +Added variable pow2::c#0 to live range equivalence class [ pow2::c#0 ] +Complete equivalence classes +[ pow2::return#2 pow2::return#1 ] +[ main::$0 ] +[ pow2::n#0 ] +[ pow2::$1 ] +[ pow2::c#0 ] +Allocated zp[1]:2 [ pow2::return#2 pow2::return#1 ] +Allocated zp[1]:3 [ main::$0 ] +Allocated zp[1]:4 [ pow2::n#0 ] +Allocated zp[1]:5 [ pow2::$1 ] +Allocated zp[1]:6 [ pow2::c#0 ] + +INITIAL ASM +Target platform is c64basic / MOS6502X + // File Comments +// Test a procedure with calling convention stack +// Recursion that works (no local variables) + // Upstart +.pc = $801 "Basic" +:BasicUpstart(__bbegin) +.pc = $80d "Program" + // Global Constants & labels + .label SCREEN = $400 + .const STACK_BASE = $103 + // @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 __0 = 3 + // [4] stackpush(byte) ← (byte) 6 -- _stackpushbyte_=vbuc1 + lda #6 + pha + // [5] callexecute pow2 -- jsr + jsr pow2 + // [6] (byte~) main::$0 ← stackpull(byte) -- vbuz1=_stackpullbyte_ + pla + sta.z __0 + // [7] *((const nomodify byte*) SCREEN) ← (byte~) main::$0 -- _deref_pbuc1=vbuz1 + lda.z __0 + sta SCREEN + jmp __breturn + // main::@return + __breturn: + // [8] return + rts +} + // pow2 +// pow2(byte zp(4) n) +pow2: { + .const OFFSET_STACK_N = 0 + .const OFFSET_STACK_RETURN = 0 + .label __1 = 5 + .label n = 4 + .label c = 6 + .label return = 2 + // [9] (byte) pow2::n#0 ← stackidx(byte,(const byte) pow2::OFFSET_STACK_N) -- vbuz1=_stackidxbyte_vbuc1 + tsx + lda STACK_BASE+OFFSET_STACK_N,x + sta.z n + // [10] if((byte) pow2::n#0==(byte) 0) goto pow2::@return -- vbuz1_eq_0_then_la1 + lda.z n + cmp #0 + beq __breturn_from_pow2 + jmp __b1 + // pow2::@1 + __b1: + // [11] (byte~) pow2::$1 ← (byte) pow2::n#0 - (byte) 1 -- vbuz1=vbuz2_minus_1 + ldx.z n + dex + stx.z __1 + // [12] stackpush(byte) ← (byte~) pow2::$1 -- _stackpushbyte_=vbuz1 + lda.z __1 + pha + // [13] callexecute pow2 -- jsr + jsr pow2 + // [14] (byte) pow2::c#0 ← stackpull(byte) -- vbuz1=_stackpullbyte_ + pla + sta.z c + // [15] (byte) pow2::return#1 ← (byte) pow2::c#0 + (byte) pow2::c#0 -- vbuz1=vbuz2_plus_vbuz2 + lda.z c + asl + sta.z return + // [16] phi from pow2::@1 to pow2::@return [phi:pow2::@1->pow2::@return] + __breturn_from___b1: + // [16] phi (byte) pow2::return#2 = (byte) pow2::return#1 [phi:pow2::@1->pow2::@return#0] -- register_copy + jmp __breturn + // [16] phi from pow2 to pow2::@return [phi:pow2->pow2::@return] + __breturn_from_pow2: + // [16] phi (byte) pow2::return#2 = (byte) 1 [phi:pow2->pow2::@return#0] -- vbuz1=vbuc1 + lda #1 + sta.z return + jmp __breturn + // pow2::@return + __breturn: + // [17] stackidx(byte,(const byte) pow2::OFFSET_STACK_RETURN) ← (byte) pow2::return#2 -- _stackidxbyte_vbuc1=vbuz1 + lda.z return + tsx + sta STACK_BASE+OFFSET_STACK_RETURN,x + // [18] return + rts +} + // File Data + +REGISTER UPLIFT POTENTIAL REGISTERS +Statement [4] stackpush(byte) ← (byte) 6 [ ] ( main:2 [ ] { } ) always clobbers reg byte a +Statement [6] (byte~) main::$0 ← stackpull(byte) [ main::$0 ] ( main:2 [ main::$0 ] { } ) always clobbers reg byte a +Statement [9] (byte) pow2::n#0 ← stackidx(byte,(const byte) pow2::OFFSET_STACK_N) [ pow2::n#0 ] ( main:2::pow2:5 [ pow2::n#0 ] { } ) always clobbers reg byte a reg byte x +Statement [14] (byte) pow2::c#0 ← stackpull(byte) [ pow2::c#0 ] ( main:2::pow2:5 [ pow2::c#0 ] { } ) always clobbers reg byte a +Statement [15] (byte) pow2::return#1 ← (byte) pow2::c#0 + (byte) pow2::c#0 [ pow2::return#1 ] ( main:2::pow2:5 [ pow2::return#1 ] { } ) always clobbers reg byte a +Statement [17] stackidx(byte,(const byte) pow2::OFFSET_STACK_RETURN) ← (byte) pow2::return#2 [ ] ( main:2::pow2:5 [ ] { } ) always clobbers reg byte x +Potential registers zp[1]:2 [ pow2::return#2 pow2::return#1 ] : zp[1]:2 , reg byte a , reg byte x , reg byte y , +Potential registers zp[1]:3 [ main::$0 ] : zp[1]:3 , reg byte a , reg byte x , reg byte y , +Potential registers zp[1]:4 [ pow2::n#0 ] : zp[1]:4 , reg byte a , reg byte x , reg byte y , +Potential registers zp[1]:5 [ pow2::$1 ] : zp[1]:5 , reg byte a , reg byte x , reg byte y , +Potential registers zp[1]:6 [ pow2::c#0 ] : zp[1]:6 , reg byte a , reg byte x , reg byte y , + +REGISTER UPLIFT SCOPES +Uplift Scope [pow2] 44: zp[1]:2 [ pow2::return#2 pow2::return#1 ] 33: zp[1]:6 [ pow2::c#0 ] 22: zp[1]:5 [ pow2::$1 ] 16.5: zp[1]:4 [ pow2::n#0 ] +Uplift Scope [main] 22: zp[1]:3 [ main::$0 ] +Uplift Scope [] + +Uplifting [pow2] best 99 combination reg byte a [ pow2::return#2 pow2::return#1 ] reg byte a [ pow2::c#0 ] reg byte a [ pow2::$1 ] reg byte a [ pow2::n#0 ] +Limited combination testing to 100 combinations of 256 possible. +Uplifting [main] best 93 combination reg byte a [ main::$0 ] +Uplifting [] best 93 combination + +ASSEMBLER BEFORE OPTIMIZATION + // File Comments +// Test a procedure with calling convention stack +// Recursion that works (no local variables) + // Upstart +.pc = $801 "Basic" +:BasicUpstart(__bbegin) +.pc = $80d "Program" + // Global Constants & labels + .label SCREEN = $400 + .const STACK_BASE = $103 + // @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: { + // [4] stackpush(byte) ← (byte) 6 -- _stackpushbyte_=vbuc1 + lda #6 + pha + // [5] callexecute pow2 -- jsr + jsr pow2 + // [6] (byte~) main::$0 ← stackpull(byte) -- vbuaa=_stackpullbyte_ + pla + // [7] *((const nomodify byte*) SCREEN) ← (byte~) main::$0 -- _deref_pbuc1=vbuaa + sta SCREEN + jmp __breturn + // main::@return + __breturn: + // [8] return + rts +} + // pow2 +// pow2(byte register(A) n) +pow2: { + .const OFFSET_STACK_N = 0 + .const OFFSET_STACK_RETURN = 0 + // [9] (byte) pow2::n#0 ← stackidx(byte,(const byte) pow2::OFFSET_STACK_N) -- vbuaa=_stackidxbyte_vbuc1 + tsx + lda STACK_BASE+OFFSET_STACK_N,x + // [10] if((byte) pow2::n#0==(byte) 0) goto pow2::@return -- vbuaa_eq_0_then_la1 + cmp #0 + beq __breturn_from_pow2 + jmp __b1 + // pow2::@1 + __b1: + // [11] (byte~) pow2::$1 ← (byte) pow2::n#0 - (byte) 1 -- vbuaa=vbuaa_minus_1 + sec + sbc #1 + // [12] stackpush(byte) ← (byte~) pow2::$1 -- _stackpushbyte_=vbuaa + pha + // [13] callexecute pow2 -- jsr + jsr pow2 + // [14] (byte) pow2::c#0 ← stackpull(byte) -- vbuaa=_stackpullbyte_ + pla + // [15] (byte) pow2::return#1 ← (byte) pow2::c#0 + (byte) pow2::c#0 -- vbuaa=vbuaa_plus_vbuaa + asl + // [16] phi from pow2::@1 to pow2::@return [phi:pow2::@1->pow2::@return] + __breturn_from___b1: + // [16] phi (byte) pow2::return#2 = (byte) pow2::return#1 [phi:pow2::@1->pow2::@return#0] -- register_copy + jmp __breturn + // [16] phi from pow2 to pow2::@return [phi:pow2->pow2::@return] + __breturn_from_pow2: + // [16] phi (byte) pow2::return#2 = (byte) 1 [phi:pow2->pow2::@return#0] -- vbuaa=vbuc1 + lda #1 + jmp __breturn + // pow2::@return + __breturn: + // [17] stackidx(byte,(const byte) pow2::OFFSET_STACK_RETURN) ← (byte) pow2::return#2 -- _stackidxbyte_vbuc1=vbuaa + tsx + sta STACK_BASE+OFFSET_STACK_RETURN,x + // [18] return + rts +} + // File Data + +ASSEMBLER OPTIMIZATIONS +Removing instruction jmp __b1 +Removing instruction jmp __bend +Removing instruction jmp __breturn +Removing instruction jmp __b1 +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: +Removing instruction __b1: +Removing instruction __breturn_from___b1: +Succesful ASM optimization Pass5UnusedLabelElimination +Updating BasicUpstart to call main directly +Removing instruction jsr main +Succesful ASM optimization Pass5SkipBegin +Relabelling long label __breturn_from_pow2 to __b1 +Succesful ASM optimization Pass5RelabelLongLabels +Removing instruction __bbegin: +Succesful ASM optimization Pass5UnusedLabelElimination + +FINAL SYMBOL TABLE +(label) @1 +(label) @begin +(label) @end +(const nomodify byte*) SCREEN = (byte*) 1024 +(const word) STACK_BASE = (word) $103 +(void()) main() +(byte~) main::$0 reg byte a 22.0 +(label) main::@return +__stackcall (byte()) pow2((byte) pow2::n) +(byte~) pow2::$1 reg byte a 22.0 +(label) pow2::@1 +(label) pow2::@return +(const byte) pow2::OFFSET_STACK_N = (byte) 0 +(const byte) pow2::OFFSET_STACK_RETURN = (byte) 0 +(byte) pow2::c +(byte) pow2::c#0 reg byte a 33.0 +(byte) pow2::n +(byte) pow2::n#0 reg byte a 16.5 +(byte) pow2::return +(byte) pow2::return#1 reg byte a 22.0 +(byte) pow2::return#2 reg byte a 22.0 + +reg byte a [ pow2::return#2 pow2::return#1 ] +reg byte a [ main::$0 ] +reg byte a [ pow2::n#0 ] +reg byte a [ pow2::$1 ] +reg byte a [ pow2::c#0 ] + + +FINAL ASSEMBLER +Score: 72 + + // File Comments +// Test a procedure with calling convention stack +// Recursion that works (no local variables) + // Upstart +.pc = $801 "Basic" +:BasicUpstart(main) +.pc = $80d "Program" + // Global Constants & labels + .label SCREEN = $400 + .const STACK_BASE = $103 + // @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: { + // pow2(6) + // [4] stackpush(byte) ← (byte) 6 -- _stackpushbyte_=vbuc1 + lda #6 + pha + // [5] callexecute pow2 -- jsr + jsr pow2 + // [6] (byte~) main::$0 ← stackpull(byte) -- vbuaa=_stackpullbyte_ + pla + // *SCREEN = pow2(6) + // [7] *((const nomodify byte*) SCREEN) ← (byte~) main::$0 -- _deref_pbuc1=vbuaa + sta SCREEN + // main::@return + // } + // [8] return + rts +} + // pow2 +// pow2(byte register(A) n) +pow2: { + .const OFFSET_STACK_N = 0 + .const OFFSET_STACK_RETURN = 0 + // [9] (byte) pow2::n#0 ← stackidx(byte,(const byte) pow2::OFFSET_STACK_N) -- vbuaa=_stackidxbyte_vbuc1 + tsx + lda STACK_BASE+OFFSET_STACK_N,x + // if (n == 0) + // [10] if((byte) pow2::n#0==(byte) 0) goto pow2::@return -- vbuaa_eq_0_then_la1 + cmp #0 + beq __b1 + // pow2::@1 + // n-1 + // [11] (byte~) pow2::$1 ← (byte) pow2::n#0 - (byte) 1 -- vbuaa=vbuaa_minus_1 + sec + sbc #1 + // pow2(n-1) + // [12] stackpush(byte) ← (byte~) pow2::$1 -- _stackpushbyte_=vbuaa + pha + // [13] callexecute pow2 -- jsr + jsr pow2 + // c = pow2(n-1) + // [14] (byte) pow2::c#0 ← stackpull(byte) -- vbuaa=_stackpullbyte_ + pla + // return c+c; + // [15] (byte) pow2::return#1 ← (byte) pow2::c#0 + (byte) pow2::c#0 -- vbuaa=vbuaa_plus_vbuaa + asl + // [16] phi from pow2::@1 to pow2::@return [phi:pow2::@1->pow2::@return] + // [16] phi (byte) pow2::return#2 = (byte) pow2::return#1 [phi:pow2::@1->pow2::@return#0] -- register_copy + jmp __breturn + // [16] phi from pow2 to pow2::@return [phi:pow2->pow2::@return] + __b1: + // [16] phi (byte) pow2::return#2 = (byte) 1 [phi:pow2->pow2::@return#0] -- vbuaa=vbuc1 + lda #1 + // pow2::@return + __breturn: + // } + // [17] stackidx(byte,(const byte) pow2::OFFSET_STACK_RETURN) ← (byte) pow2::return#2 -- _stackidxbyte_vbuc1=vbuaa + tsx + sta STACK_BASE+OFFSET_STACK_RETURN,x + // [18] return + rts +} + // File Data + diff --git a/src/test/ref/procedure-callingconvention-stack-13.sym b/src/test/ref/procedure-callingconvention-stack-13.sym new file mode 100644 index 000000000..b7e12b0ad --- /dev/null +++ b/src/test/ref/procedure-callingconvention-stack-13.sym @@ -0,0 +1,27 @@ +(label) @1 +(label) @begin +(label) @end +(const nomodify byte*) SCREEN = (byte*) 1024 +(const word) STACK_BASE = (word) $103 +(void()) main() +(byte~) main::$0 reg byte a 22.0 +(label) main::@return +__stackcall (byte()) pow2((byte) pow2::n) +(byte~) pow2::$1 reg byte a 22.0 +(label) pow2::@1 +(label) pow2::@return +(const byte) pow2::OFFSET_STACK_N = (byte) 0 +(const byte) pow2::OFFSET_STACK_RETURN = (byte) 0 +(byte) pow2::c +(byte) pow2::c#0 reg byte a 33.0 +(byte) pow2::n +(byte) pow2::n#0 reg byte a 16.5 +(byte) pow2::return +(byte) pow2::return#1 reg byte a 22.0 +(byte) pow2::return#2 reg byte a 22.0 + +reg byte a [ pow2::return#2 pow2::return#1 ] +reg byte a [ main::$0 ] +reg byte a [ pow2::n#0 ] +reg byte a [ pow2::$1 ] +reg byte a [ pow2::c#0 ]