From 9c40c2f0f2e2f8efa0a48cf520289eee9c4c68e5 Mon Sep 17 00:00:00 2001 From: jespergravgaard Date: Sat, 11 Apr 2020 22:48:33 +0200 Subject: [PATCH] Added initial strncat() implementation. Found error in post-increment when used in a condition. --- .../dk/camelot64/kickc/test/TestPrograms.java | 10 + src/test/kc/post-increment-problem.c | 23 + src/test/kc/strncat-0.c | 37 + src/test/ref/post-increment-problem.asm | 61 + src/test/ref/post-increment-problem.cfg | 36 + src/test/ref/post-increment-problem.log | 616 +++++++++ src/test/ref/post-increment-problem.sym | 22 + src/test/ref/strncat-0.asm | 120 ++ src/test/ref/strncat-0.cfg | 67 + src/test/ref/strncat-0.log | 1181 +++++++++++++++++ src/test/ref/strncat-0.sym | 44 + 11 files changed, 2217 insertions(+) create mode 100644 src/test/kc/post-increment-problem.c create mode 100644 src/test/kc/strncat-0.c create mode 100644 src/test/ref/post-increment-problem.asm create mode 100644 src/test/ref/post-increment-problem.cfg create mode 100644 src/test/ref/post-increment-problem.log create mode 100644 src/test/ref/post-increment-problem.sym create mode 100644 src/test/ref/strncat-0.asm create mode 100644 src/test/ref/strncat-0.cfg create mode 100644 src/test/ref/strncat-0.log create mode 100644 src/test/ref/strncat-0.sym diff --git a/src/test/java/dk/camelot64/kickc/test/TestPrograms.java b/src/test/java/dk/camelot64/kickc/test/TestPrograms.java index c5aadfac9..753130f0f 100644 --- a/src/test/java/dk/camelot64/kickc/test/TestPrograms.java +++ b/src/test/java/dk/camelot64/kickc/test/TestPrograms.java @@ -40,6 +40,16 @@ public class TestPrograms { public TestPrograms() { } + @Test + public void testPostIncrementProblem() throws IOException, URISyntaxException { + compileAndCompare("post-increment-problem.c"); + } + + @Test + public void testStrncat0() throws IOException, URISyntaxException { + compileAndCompare("strncat-0.c"); + } + @Test public void testIncludes3() throws IOException, URISyntaxException { compileAndCompare("complex/includes/includes-3.c"); diff --git a/src/test/kc/post-increment-problem.c b/src/test/kc/post-increment-problem.c new file mode 100644 index 000000000..11deacbd5 --- /dev/null +++ b/src/test/kc/post-increment-problem.c @@ -0,0 +1,23 @@ +// Illustrates a problem with post-incrementing a pointer used in a loop comparison + +char MESSAGE[20] = "camelot"; + +char* const SCREEN = 0x0400; + +void main() { + char* msg = MESSAGE; + // Error! The post-increment in the following loop is turned into a pre-increment by the compiler. + while(*msg++) {} + // Now msg should point right after the zero, since the post increment was executed in the last condition that evaluated to zero. + *--msg = 'x'; + *++msg = 0; + // This should append an x at the end of the message - instead it replaces the last char of the message. + + // Print the resulting message - should be "camelotx" - but the error causes it to be "camelox" + char i=0; + while(MESSAGE[i]) { + SCREEN[i] = MESSAGE[i]; + i++; + } + +} diff --git a/src/test/kc/strncat-0.c b/src/test/kc/strncat-0.c new file mode 100644 index 000000000..52737bfbb --- /dev/null +++ b/src/test/kc/strncat-0.c @@ -0,0 +1,37 @@ +// Test strncat() implementation + +char hello[] = "hello"; +char space[] = " "; +char world[] = "world war 3"; + +char build[40]; + +char* const SCREEN = 0x0400; + +void main() { + strncat(build, hello, 5); + strncat(build, space, 5); + strncat(build, world, 5); + + char i=0; + while(build[i]) { + SCREEN[i] = build[i]; + i++; + } +} + +// Appends the first num characters of source to destination, plus a terminating null-character. +// If the length of the C string in source is less than num, only the content up to the terminating null-character is copied. +char *strncat(char *destination, const char *source, unsigned int num) { + char *dst = destination; + // Skip the existing string at dest + while (*dst) dst++; + // Copy up to num chars from src + while (num && (*dst = *source++)) { + --num; + ++dst; + } + // Add null-character + *dst = 0; + return destination; +} \ No newline at end of file diff --git a/src/test/ref/post-increment-problem.asm b/src/test/ref/post-increment-problem.asm new file mode 100644 index 000000000..08dbd6300 --- /dev/null +++ b/src/test/ref/post-increment-problem.asm @@ -0,0 +1,61 @@ +// Illustrates a problem with post-incrementing a pointer used in a loop comparison +.pc = $801 "Basic" +:BasicUpstart(main) +.pc = $80d "Program" + .label SCREEN = $400 +main: { + .label msg = 2 + lda #MESSAGE + sta.z msg+1 + // Error! The post-increment in the following loop is turned into a pre-increment by the compiler. + __b1: + // while(*msg++) + inc.z msg + bne !+ + inc.z msg+1 + !: + ldy #0 + lda (msg),y + cmp #0 + bne __b1 + // *--msg = 'x'; + lda.z msg + bne !+ + dec.z msg+1 + !: + dec.z msg + // *--msg = 'x' + // Now msg should point right after the zero, since the post increment was executed in the last condition that evaluated to zero. + lda #'x' + ldy #0 + sta (msg),y + // *++msg = 0; + inc.z msg + bne !+ + inc.z msg+1 + !: + // *++msg = 0 + lda #0 + tay + sta (msg),y + tax + __b3: + // while(MESSAGE[i]) + lda MESSAGE,x + cmp #0 + bne __b4 + // } + rts + __b4: + // SCREEN[i] = MESSAGE[i] + lda MESSAGE,x + sta SCREEN,x + // i++; + inx + jmp __b3 +} + MESSAGE: .text "camelot" + .byte 0 + .fill $c, 0 diff --git a/src/test/ref/post-increment-problem.cfg b/src/test/ref/post-increment-problem.cfg new file mode 100644 index 000000000..53aeed5e1 --- /dev/null +++ b/src/test/ref/post-increment-problem.cfg @@ -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() + +(void()) main() +main: scope:[main] from @1 + [4] phi() + to:main::@1 +main::@1: scope:[main] from main main::@1 + [5] (byte*) main::msg#4 ← phi( main/(const byte*) MESSAGE main::@1/(byte*) main::msg#1 ) + [6] (byte*) main::msg#1 ← ++ (byte*) main::msg#4 + [7] if((byte) 0!=*((byte*) main::msg#1)) goto main::@1 + to:main::@2 +main::@2: scope:[main] from main::@1 + [8] (byte*) main::msg#2 ← -- (byte*) main::msg#1 + [9] *((byte*) main::msg#2) ← (byte) 'x' + [10] (byte*) main::msg#3 ← ++ (byte*) main::msg#2 + [11] *((byte*) main::msg#3) ← (byte) 0 + to:main::@3 +main::@3: scope:[main] from main::@2 main::@4 + [12] (byte) main::i#2 ← phi( main::@2/(byte) 0 main::@4/(byte) main::i#1 ) + [13] if((byte) 0!=*((const byte*) MESSAGE + (byte) main::i#2)) goto main::@4 + to:main::@return +main::@return: scope:[main] from main::@3 + [14] return + to:@return +main::@4: scope:[main] from main::@3 + [15] *((const nomodify byte*) SCREEN + (byte) main::i#2) ← *((const byte*) MESSAGE + (byte) main::i#2) + [16] (byte) main::i#1 ← ++ (byte) main::i#2 + to:main::@3 diff --git a/src/test/ref/post-increment-problem.log b/src/test/ref/post-increment-problem.log new file mode 100644 index 000000000..b84d58d09 --- /dev/null +++ b/src/test/ref/post-increment-problem.log @@ -0,0 +1,616 @@ +Warning! Adding boolean cast to non-boolean condition *((byte*) main::msg) +Warning! Adding boolean cast to non-boolean condition *((const byte*) MESSAGE + (byte) main::i) +Culled Empty Block (label) main::@2 +Culled Empty Block (label) main::@4 +Culled Empty Block (label) main::@5 +Culled Empty Block (label) main::@6 +Culled Empty Block (label) main::@10 +Culled Empty Block (label) main::@9 +Culled Empty Block (label) main::@11 +Culled Empty Block (label) main::@12 + +CONTROL FLOW GRAPH SSA +@begin: scope:[] from + to:@1 + +(void()) main() +main: scope:[main] from @1 + (byte*) main::msg#0 ← (const byte*) MESSAGE + to:main::@1 +main::@1: scope:[main] from main main::@1 + (byte*) main::msg#4 ← phi( main/(byte*) main::msg#0 main::@1/(byte*) main::msg#1 ) + (byte*) main::msg#1 ← ++ (byte*) main::msg#4 + (bool~) main::$0 ← (number) 0 != *((byte*) main::msg#1) + if((bool~) main::$0) goto main::@1 + to:main::@3 +main::@3: scope:[main] from main::@1 + (byte*) main::msg#5 ← phi( main::@1/(byte*) main::msg#1 ) + (byte*) main::msg#2 ← -- (byte*) main::msg#5 + *((byte*) main::msg#2) ← (byte) 'x' + (byte*) main::msg#3 ← ++ (byte*) main::msg#2 + *((byte*) main::msg#3) ← (number) 0 + (byte) main::i#0 ← (byte) 0 + to:main::@7 +main::@7: scope:[main] from main::@3 main::@8 + (byte) main::i#2 ← phi( main::@3/(byte) main::i#0 main::@8/(byte) main::i#1 ) + (bool~) main::$1 ← (number) 0 != *((const byte*) MESSAGE + (byte) main::i#2) + if((bool~) main::$1) goto main::@8 + to:main::@return +main::@8: scope:[main] from main::@7 + (byte) main::i#3 ← phi( main::@7/(byte) main::i#2 ) + *((const nomodify byte*) SCREEN + (byte) main::i#3) ← *((const byte*) MESSAGE + (byte) main::i#3) + (byte) main::i#1 ← ++ (byte) main::i#3 + to:main::@7 +main::@return: scope:[main] from main::@7 + 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*) MESSAGE[(number) $14] = (byte*) "camelot" +(const nomodify byte*) SCREEN = (byte*)(number) $400 +(void()) main() +(bool~) main::$0 +(bool~) main::$1 +(label) main::@1 +(label) main::@3 +(label) main::@7 +(label) main::@8 +(label) main::@return +(byte) main::i +(byte) main::i#0 +(byte) main::i#1 +(byte) main::i#2 +(byte) main::i#3 +(byte*) main::msg +(byte*) main::msg#0 +(byte*) main::msg#1 +(byte*) main::msg#2 +(byte*) main::msg#3 +(byte*) main::msg#4 +(byte*) main::msg#5 + +Adding number conversion cast (unumber) 0 in (bool~) main::$0 ← (number) 0 != *((byte*) main::msg#1) +Adding number conversion cast (unumber) 0 in *((byte*) main::msg#3) ← (number) 0 +Adding number conversion cast (unumber) 0 in (bool~) main::$1 ← (number) 0 != *((const byte*) MESSAGE + (byte) main::i#2) +Successful SSA optimization PassNAddNumberTypeConversions +Inlining cast *((byte*) main::msg#3) ← (unumber)(number) 0 +Successful SSA optimization Pass2InlineCast +Simplifying constant pointer cast (byte*) 1024 +Simplifying constant integer cast 0 +Simplifying constant integer cast 0 +Simplifying constant integer cast 0 +Successful SSA optimization PassNCastSimplification +Finalized unsigned number type (byte) 0 +Finalized unsigned number type (byte) 0 +Finalized unsigned number type (byte) 0 +Successful SSA optimization PassNFinalizeNumberTypeConversions +Alias main::msg#1 = main::msg#5 +Alias main::i#2 = main::i#3 +Successful SSA optimization Pass2AliasElimination +Simple Condition (bool~) main::$0 [4] if((byte) 0!=*((byte*) main::msg#1)) goto main::@1 +Simple Condition (bool~) main::$1 [12] if((byte) 0!=*((const byte*) MESSAGE + (byte) main::i#2)) goto main::@8 +Successful SSA optimization Pass2ConditionalJumpSimplification +Constant (const byte*) main::msg#0 = MESSAGE +Constant (const byte) main::i#0 = 0 +Successful SSA optimization Pass2ConstantIdentification +Inlining constant with var siblings (const byte*) main::msg#0 +Inlining constant with var siblings (const byte) main::i#0 +Constant inlined main::i#0 = (byte) 0 +Constant inlined main::msg#0 = (const byte*) MESSAGE +Successful SSA optimization Pass2ConstantInlining +Added new block during phi lifting main::@13(between main::@1 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 @end +Adding NOP phi() at start of main +CALL GRAPH +Calls in [] to main:2 + +Created 2 initial phi equivalence classes +Coalesced [18] main::i#4 ← main::i#1 +Coalesced [19] main::msg#6 ← main::msg#1 +Coalesced down to 2 phi equivalence classes +Culled Empty Block (label) @2 +Culled Empty Block (label) main::@13 +Renumbering block main::@3 to main::@2 +Renumbering block main::@7 to main::@3 +Renumbering block main::@8 to main::@4 +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() + +(void()) main() +main: scope:[main] from @1 + [4] phi() + to:main::@1 +main::@1: scope:[main] from main main::@1 + [5] (byte*) main::msg#4 ← phi( main/(const byte*) MESSAGE main::@1/(byte*) main::msg#1 ) + [6] (byte*) main::msg#1 ← ++ (byte*) main::msg#4 + [7] if((byte) 0!=*((byte*) main::msg#1)) goto main::@1 + to:main::@2 +main::@2: scope:[main] from main::@1 + [8] (byte*) main::msg#2 ← -- (byte*) main::msg#1 + [9] *((byte*) main::msg#2) ← (byte) 'x' + [10] (byte*) main::msg#3 ← ++ (byte*) main::msg#2 + [11] *((byte*) main::msg#3) ← (byte) 0 + to:main::@3 +main::@3: scope:[main] from main::@2 main::@4 + [12] (byte) main::i#2 ← phi( main::@2/(byte) 0 main::@4/(byte) main::i#1 ) + [13] if((byte) 0!=*((const byte*) MESSAGE + (byte) main::i#2)) goto main::@4 + to:main::@return +main::@return: scope:[main] from main::@3 + [14] return + to:@return +main::@4: scope:[main] from main::@3 + [15] *((const nomodify byte*) SCREEN + (byte) main::i#2) ← *((const byte*) MESSAGE + (byte) main::i#2) + [16] (byte) main::i#1 ← ++ (byte) main::i#2 + to:main::@3 + + +VARIABLE REGISTER WEIGHTS +(void()) main() +(byte) main::i +(byte) main::i#1 202.0 +(byte) main::i#2 168.33333333333331 +(byte*) main::msg +(byte*) main::msg#1 157.0 +(byte*) main::msg#2 16.5 +(byte*) main::msg#3 22.0 +(byte*) main::msg#4 202.0 + +Initial phi equivalence classes +[ main::msg#4 main::msg#1 ] +[ main::i#2 main::i#1 ] +Added variable main::msg#2 to live range equivalence class [ main::msg#2 ] +Added variable main::msg#3 to live range equivalence class [ main::msg#3 ] +Complete equivalence classes +[ main::msg#4 main::msg#1 ] +[ main::i#2 main::i#1 ] +[ main::msg#2 ] +[ main::msg#3 ] +Allocated zp[2]:2 [ main::msg#4 main::msg#1 ] +Allocated zp[1]:4 [ main::i#2 main::i#1 ] +Allocated zp[2]:5 [ main::msg#2 ] +Allocated zp[2]:7 [ main::msg#3 ] + +INITIAL ASM +Target platform is c64basic / MOS6502X + // File Comments +// Illustrates a problem with post-incrementing a pointer used in a loop comparison + // Upstart +.pc = $801 "Basic" +:BasicUpstart(__bbegin) +.pc = $80d "Program" + // Global Constants & labels + .label SCREEN = $400 + // @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 msg = 2 + .label msg_1 = 5 + .label msg_2 = 7 + // Print the resulting message - should be "camelotx" - but the error causes it to be "camelox" + .label i = 4 + // [5] phi from main to main::@1 [phi:main->main::@1] + __b1_from_main: + // [5] phi (byte*) main::msg#4 = (const byte*) MESSAGE [phi:main->main::@1#0] -- pbuz1=pbuc1 + lda #MESSAGE + sta.z msg+1 + jmp __b1 + // Error! The post-increment in the following loop is turned into a pre-increment by the compiler. + // [5] phi from main::@1 to main::@1 [phi:main::@1->main::@1] + __b1_from___b1: + // [5] phi (byte*) main::msg#4 = (byte*) main::msg#1 [phi:main::@1->main::@1#0] -- register_copy + jmp __b1 + // main::@1 + __b1: + // [6] (byte*) main::msg#1 ← ++ (byte*) main::msg#4 -- pbuz1=_inc_pbuz1 + inc.z msg + bne !+ + inc.z msg+1 + !: + // [7] if((byte) 0!=*((byte*) main::msg#1)) goto main::@1 -- vbuc1_neq__deref_pbuz1_then_la1 + ldy #0 + lda (msg),y + cmp #0 + bne __b1_from___b1 + jmp __b2 + // main::@2 + __b2: + // [8] (byte*) main::msg#2 ← -- (byte*) main::msg#1 -- pbuz1=_dec_pbuz2 + lda.z msg + sec + sbc #1 + sta.z msg_1 + lda.z msg+1 + sbc #0 + sta.z msg_1+1 + // [9] *((byte*) main::msg#2) ← (byte) 'x' -- _deref_pbuz1=vbuc1 + // Now msg should point right after the zero, since the post increment was executed in the last condition that evaluated to zero. + lda #'x' + ldy #0 + sta (msg_1),y + // [10] (byte*) main::msg#3 ← ++ (byte*) main::msg#2 -- pbuz1=_inc_pbuz2 + lda.z msg_1 + clc + adc #1 + sta.z msg_2 + lda.z msg_1+1 + adc #0 + sta.z msg_2+1 + // [11] *((byte*) main::msg#3) ← (byte) 0 -- _deref_pbuz1=vbuc1 + lda #0 + ldy #0 + sta (msg_2),y + // [12] phi from main::@2 to main::@3 [phi:main::@2->main::@3] + __b3_from___b2: + // [12] phi (byte) main::i#2 = (byte) 0 [phi:main::@2->main::@3#0] -- vbuz1=vbuc1 + lda #0 + sta.z i + jmp __b3 + // main::@3 + __b3: + // [13] if((byte) 0!=*((const byte*) MESSAGE + (byte) main::i#2)) goto main::@4 -- vbuc1_neq_pbuc2_derefidx_vbuz1_then_la1 + lda #0 + ldy.z i + cmp MESSAGE,y + bne __b4 + jmp __breturn + // main::@return + __breturn: + // [14] return + rts + // main::@4 + __b4: + // [15] *((const nomodify byte*) SCREEN + (byte) main::i#2) ← *((const byte*) MESSAGE + (byte) main::i#2) -- pbuc1_derefidx_vbuz1=pbuc2_derefidx_vbuz1 + ldy.z i + lda MESSAGE,y + sta SCREEN,y + // [16] (byte) main::i#1 ← ++ (byte) main::i#2 -- vbuz1=_inc_vbuz1 + inc.z i + // [12] phi from main::@4 to main::@3 [phi:main::@4->main::@3] + __b3_from___b4: + // [12] phi (byte) main::i#2 = (byte) main::i#1 [phi:main::@4->main::@3#0] -- register_copy + jmp __b3 +} + // File Data + MESSAGE: .text "camelot" + .byte 0 + .fill $c, 0 + +REGISTER UPLIFT POTENTIAL REGISTERS +Statement [7] if((byte) 0!=*((byte*) main::msg#1)) goto main::@1 [ main::msg#1 ] ( main:2 [ main::msg#1 ] { } ) always clobbers reg byte a reg byte y +Statement [8] (byte*) main::msg#2 ← -- (byte*) main::msg#1 [ main::msg#2 ] ( main:2 [ main::msg#2 ] { } ) always clobbers reg byte a +Statement [9] *((byte*) main::msg#2) ← (byte) 'x' [ main::msg#2 ] ( main:2 [ main::msg#2 ] { } ) always clobbers reg byte a reg byte y +Statement [10] (byte*) main::msg#3 ← ++ (byte*) main::msg#2 [ main::msg#3 ] ( main:2 [ main::msg#3 ] { } ) always clobbers reg byte a +Statement [11] *((byte*) main::msg#3) ← (byte) 0 [ ] ( main:2 [ ] { } ) always clobbers reg byte a reg byte y +Statement [13] if((byte) 0!=*((const byte*) MESSAGE + (byte) main::i#2)) goto main::@4 [ main::i#2 ] ( main:2 [ main::i#2 ] { } ) always clobbers reg byte a +Removing always clobbered register reg byte a as potential for zp[1]:4 [ main::i#2 main::i#1 ] +Statement [15] *((const nomodify byte*) SCREEN + (byte) main::i#2) ← *((const byte*) MESSAGE + (byte) main::i#2) [ main::i#2 ] ( main:2 [ main::i#2 ] { } ) always clobbers reg byte a +Statement [7] if((byte) 0!=*((byte*) main::msg#1)) goto main::@1 [ main::msg#1 ] ( main:2 [ main::msg#1 ] { } ) always clobbers reg byte a reg byte y +Statement [8] (byte*) main::msg#2 ← -- (byte*) main::msg#1 [ main::msg#2 ] ( main:2 [ main::msg#2 ] { } ) always clobbers reg byte a +Statement [9] *((byte*) main::msg#2) ← (byte) 'x' [ main::msg#2 ] ( main:2 [ main::msg#2 ] { } ) always clobbers reg byte a reg byte y +Statement [10] (byte*) main::msg#3 ← ++ (byte*) main::msg#2 [ main::msg#3 ] ( main:2 [ main::msg#3 ] { } ) always clobbers reg byte a +Statement [11] *((byte*) main::msg#3) ← (byte) 0 [ ] ( main:2 [ ] { } ) always clobbers reg byte a reg byte y +Statement [13] if((byte) 0!=*((const byte*) MESSAGE + (byte) main::i#2)) goto main::@4 [ main::i#2 ] ( main:2 [ main::i#2 ] { } ) always clobbers reg byte a +Statement [15] *((const nomodify byte*) SCREEN + (byte) main::i#2) ← *((const byte*) MESSAGE + (byte) main::i#2) [ main::i#2 ] ( main:2 [ main::i#2 ] { } ) always clobbers reg byte a +Potential registers zp[2]:2 [ main::msg#4 main::msg#1 ] : zp[2]:2 , +Potential registers zp[1]:4 [ main::i#2 main::i#1 ] : zp[1]:4 , reg byte x , reg byte y , +Potential registers zp[2]:5 [ main::msg#2 ] : zp[2]:5 , +Potential registers zp[2]:7 [ main::msg#3 ] : zp[2]:7 , + +REGISTER UPLIFT SCOPES +Uplift Scope [main] 370.33: zp[1]:4 [ main::i#2 main::i#1 ] 359: zp[2]:2 [ main::msg#4 main::msg#1 ] 22: zp[2]:7 [ main::msg#3 ] 16.5: zp[2]:5 [ main::msg#2 ] +Uplift Scope [] + +Uplifting [main] best 824 combination reg byte x [ main::i#2 main::i#1 ] zp[2]:2 [ main::msg#4 main::msg#1 ] zp[2]:7 [ main::msg#3 ] zp[2]:5 [ main::msg#2 ] +Uplifting [] best 824 combination +Coalescing zero page register [ zp[2]:2 [ main::msg#4 main::msg#1 ] ] with [ zp[2]:5 [ main::msg#2 ] ] - score: 1 +Coalescing zero page register [ zp[2]:2 [ main::msg#4 main::msg#1 main::msg#2 ] ] with [ zp[2]:7 [ main::msg#3 ] ] - score: 1 + +ASSEMBLER BEFORE OPTIMIZATION + // File Comments +// Illustrates a problem with post-incrementing a pointer used in a loop comparison + // Upstart +.pc = $801 "Basic" +:BasicUpstart(__bbegin) +.pc = $80d "Program" + // Global Constants & labels + .label SCREEN = $400 + // @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 msg = 2 + // [5] phi from main to main::@1 [phi:main->main::@1] + __b1_from_main: + // [5] phi (byte*) main::msg#4 = (const byte*) MESSAGE [phi:main->main::@1#0] -- pbuz1=pbuc1 + lda #MESSAGE + sta.z msg+1 + jmp __b1 + // Error! The post-increment in the following loop is turned into a pre-increment by the compiler. + // [5] phi from main::@1 to main::@1 [phi:main::@1->main::@1] + __b1_from___b1: + // [5] phi (byte*) main::msg#4 = (byte*) main::msg#1 [phi:main::@1->main::@1#0] -- register_copy + jmp __b1 + // main::@1 + __b1: + // [6] (byte*) main::msg#1 ← ++ (byte*) main::msg#4 -- pbuz1=_inc_pbuz1 + inc.z msg + bne !+ + inc.z msg+1 + !: + // [7] if((byte) 0!=*((byte*) main::msg#1)) goto main::@1 -- vbuc1_neq__deref_pbuz1_then_la1 + ldy #0 + lda (msg),y + cmp #0 + bne __b1_from___b1 + jmp __b2 + // main::@2 + __b2: + // [8] (byte*) main::msg#2 ← -- (byte*) main::msg#1 -- pbuz1=_dec_pbuz1 + lda.z msg + bne !+ + dec.z msg+1 + !: + dec.z msg + // [9] *((byte*) main::msg#2) ← (byte) 'x' -- _deref_pbuz1=vbuc1 + // Now msg should point right after the zero, since the post increment was executed in the last condition that evaluated to zero. + lda #'x' + ldy #0 + sta (msg),y + // [10] (byte*) main::msg#3 ← ++ (byte*) main::msg#2 -- pbuz1=_inc_pbuz1 + inc.z msg + bne !+ + inc.z msg+1 + !: + // [11] *((byte*) main::msg#3) ← (byte) 0 -- _deref_pbuz1=vbuc1 + lda #0 + ldy #0 + sta (msg),y + // [12] phi from main::@2 to main::@3 [phi:main::@2->main::@3] + __b3_from___b2: + // [12] phi (byte) main::i#2 = (byte) 0 [phi:main::@2->main::@3#0] -- vbuxx=vbuc1 + ldx #0 + jmp __b3 + // main::@3 + __b3: + // [13] if((byte) 0!=*((const byte*) MESSAGE + (byte) main::i#2)) goto main::@4 -- vbuc1_neq_pbuc2_derefidx_vbuxx_then_la1 + lda MESSAGE,x + cmp #0 + bne __b4 + jmp __breturn + // main::@return + __breturn: + // [14] return + rts + // main::@4 + __b4: + // [15] *((const nomodify byte*) SCREEN + (byte) main::i#2) ← *((const byte*) MESSAGE + (byte) main::i#2) -- pbuc1_derefidx_vbuxx=pbuc2_derefidx_vbuxx + lda MESSAGE,x + sta SCREEN,x + // [16] (byte) main::i#1 ← ++ (byte) main::i#2 -- vbuxx=_inc_vbuxx + inx + // [12] phi from main::@4 to main::@3 [phi:main::@4->main::@3] + __b3_from___b4: + // [12] phi (byte) main::i#2 = (byte) main::i#1 [phi:main::@4->main::@3#0] -- register_copy + jmp __b3 +} + // File Data + MESSAGE: .text "camelot" + .byte 0 + .fill $c, 0 + +ASSEMBLER OPTIMIZATIONS +Removing instruction jmp __b1 +Removing instruction jmp __bend +Removing instruction jmp __b1 +Removing instruction jmp __b2 +Removing instruction jmp __b3 +Removing instruction jmp __breturn +Succesful ASM optimization Pass5NextJumpElimination +Replacing instruction ldy #0 with TAY +Replacing label __b1_from___b1 with __b1 +Removing instruction __b1_from___bbegin: +Removing instruction __b1: +Removing instruction main_from___b1: +Removing instruction __bend_from___b1: +Removing instruction __b1_from___b1: +Succesful ASM optimization Pass5RedundantLabelElimination +Removing instruction __bend: +Removing instruction __b1_from_main: +Removing instruction __b2: +Removing instruction __b3_from___b2: +Removing instruction __breturn: +Removing instruction __b3_from___b4: +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 +Replacing instruction ldx #0 with TAX +Removing instruction __bbegin: +Succesful ASM optimization Pass5UnusedLabelElimination + +FINAL SYMBOL TABLE +(label) @1 +(label) @begin +(label) @end +(const byte*) MESSAGE[(number) $14] = (byte*) "camelot" +(const nomodify byte*) SCREEN = (byte*) 1024 +(void()) main() +(label) main::@1 +(label) main::@2 +(label) main::@3 +(label) main::@4 +(label) main::@return +(byte) main::i +(byte) main::i#1 reg byte x 202.0 +(byte) main::i#2 reg byte x 168.33333333333331 +(byte*) main::msg +(byte*) main::msg#1 msg zp[2]:2 157.0 +(byte*) main::msg#2 msg zp[2]:2 16.5 +(byte*) main::msg#3 msg zp[2]:2 22.0 +(byte*) main::msg#4 msg zp[2]:2 202.0 + +zp[2]:2 [ main::msg#4 main::msg#1 main::msg#2 main::msg#3 ] +reg byte x [ main::i#2 main::i#1 ] + + +FINAL ASSEMBLER +Score: 653 + + // File Comments +// Illustrates a problem with post-incrementing a pointer used in a loop comparison + // Upstart +.pc = $801 "Basic" +:BasicUpstart(main) +.pc = $80d "Program" + // Global Constants & labels + .label SCREEN = $400 + // @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 msg = 2 + // [5] phi from main to main::@1 [phi:main->main::@1] + // [5] phi (byte*) main::msg#4 = (const byte*) MESSAGE [phi:main->main::@1#0] -- pbuz1=pbuc1 + lda #MESSAGE + sta.z msg+1 + // Error! The post-increment in the following loop is turned into a pre-increment by the compiler. + // [5] phi from main::@1 to main::@1 [phi:main::@1->main::@1] + // [5] phi (byte*) main::msg#4 = (byte*) main::msg#1 [phi:main::@1->main::@1#0] -- register_copy + // main::@1 + __b1: + // while(*msg++) + // [6] (byte*) main::msg#1 ← ++ (byte*) main::msg#4 -- pbuz1=_inc_pbuz1 + inc.z msg + bne !+ + inc.z msg+1 + !: + // [7] if((byte) 0!=*((byte*) main::msg#1)) goto main::@1 -- vbuc1_neq__deref_pbuz1_then_la1 + ldy #0 + lda (msg),y + cmp #0 + bne __b1 + // main::@2 + // *--msg = 'x'; + // [8] (byte*) main::msg#2 ← -- (byte*) main::msg#1 -- pbuz1=_dec_pbuz1 + lda.z msg + bne !+ + dec.z msg+1 + !: + dec.z msg + // *--msg = 'x' + // [9] *((byte*) main::msg#2) ← (byte) 'x' -- _deref_pbuz1=vbuc1 + // Now msg should point right after the zero, since the post increment was executed in the last condition that evaluated to zero. + lda #'x' + ldy #0 + sta (msg),y + // *++msg = 0; + // [10] (byte*) main::msg#3 ← ++ (byte*) main::msg#2 -- pbuz1=_inc_pbuz1 + inc.z msg + bne !+ + inc.z msg+1 + !: + // *++msg = 0 + // [11] *((byte*) main::msg#3) ← (byte) 0 -- _deref_pbuz1=vbuc1 + lda #0 + tay + sta (msg),y + // [12] phi from main::@2 to main::@3 [phi:main::@2->main::@3] + // [12] phi (byte) main::i#2 = (byte) 0 [phi:main::@2->main::@3#0] -- vbuxx=vbuc1 + tax + // main::@3 + __b3: + // while(MESSAGE[i]) + // [13] if((byte) 0!=*((const byte*) MESSAGE + (byte) main::i#2)) goto main::@4 -- vbuc1_neq_pbuc2_derefidx_vbuxx_then_la1 + lda MESSAGE,x + cmp #0 + bne __b4 + // main::@return + // } + // [14] return + rts + // main::@4 + __b4: + // SCREEN[i] = MESSAGE[i] + // [15] *((const nomodify byte*) SCREEN + (byte) main::i#2) ← *((const byte*) MESSAGE + (byte) main::i#2) -- pbuc1_derefidx_vbuxx=pbuc2_derefidx_vbuxx + lda MESSAGE,x + sta SCREEN,x + // i++; + // [16] (byte) main::i#1 ← ++ (byte) main::i#2 -- vbuxx=_inc_vbuxx + inx + // [12] phi from main::@4 to main::@3 [phi:main::@4->main::@3] + // [12] phi (byte) main::i#2 = (byte) main::i#1 [phi:main::@4->main::@3#0] -- register_copy + jmp __b3 +} + // File Data + MESSAGE: .text "camelot" + .byte 0 + .fill $c, 0 + diff --git a/src/test/ref/post-increment-problem.sym b/src/test/ref/post-increment-problem.sym new file mode 100644 index 000000000..c8a14739b --- /dev/null +++ b/src/test/ref/post-increment-problem.sym @@ -0,0 +1,22 @@ +(label) @1 +(label) @begin +(label) @end +(const byte*) MESSAGE[(number) $14] = (byte*) "camelot" +(const nomodify byte*) SCREEN = (byte*) 1024 +(void()) main() +(label) main::@1 +(label) main::@2 +(label) main::@3 +(label) main::@4 +(label) main::@return +(byte) main::i +(byte) main::i#1 reg byte x 202.0 +(byte) main::i#2 reg byte x 168.33333333333331 +(byte*) main::msg +(byte*) main::msg#1 msg zp[2]:2 157.0 +(byte*) main::msg#2 msg zp[2]:2 16.5 +(byte*) main::msg#3 msg zp[2]:2 22.0 +(byte*) main::msg#4 msg zp[2]:2 202.0 + +zp[2]:2 [ main::msg#4 main::msg#1 main::msg#2 main::msg#3 ] +reg byte x [ main::i#2 main::i#1 ] diff --git a/src/test/ref/strncat-0.asm b/src/test/ref/strncat-0.asm new file mode 100644 index 000000000..c670fe24b --- /dev/null +++ b/src/test/ref/strncat-0.asm @@ -0,0 +1,120 @@ +// Test strncat() implementation +.pc = $801 "Basic" +:BasicUpstart(main) +.pc = $80d "Program" + .label SCREEN = $400 +main: { + // strncat(build, hello, 5) + lda #hello + sta.z strncat.source+1 + jsr strncat + // strncat(build, space, 5) + lda #space + sta.z strncat.source+1 + jsr strncat + // strncat(build, world, 5) + lda #world + sta.z strncat.source+1 + jsr strncat + ldx #0 + __b1: + // while(build[i]) + lda build,x + cmp #0 + bne __b2 + // } + rts + __b2: + // SCREEN[i] = build[i] + lda build,x + sta SCREEN,x + // i++; + inx + jmp __b1 +} +// Appends the first num characters of source to destination, plus a terminating null-character. +// If the length of the C string in source is less than num, only the content up to the terminating null-character is copied. +// strncat(byte* zp(2) source, word zp(6) num) +strncat: { + .label dst = 4 + .label source = 2 + .label num = 6 + lda #build + sta.z dst+1 + // Skip the existing string at dest + __b1: + // while (*dst) + ldy #0 + lda (dst),y + cmp #0 + bne __b2 + lda #<5 + sta.z num + tya + sta.z num+1 + // Copy up to num chars from src + __b3: + // *dst = *source++ + ldy #0 + lda (source),y + sta (dst),y + // while (num && (*dst = *source++)) + inc.z source + bne !+ + inc.z source+1 + !: + lda.z num + cmp #<0 + bne !+ + lda.z num+1 + cmp #>0 + beq __b5 + !: + ldy #0 + lda (dst),y + cmp #0 + bne __b4 + __b5: + // *dst = 0 + // Add null-character + lda #0 + tay + sta (dst),y + // } + rts + __b4: + // --num; + lda.z num + bne !+ + dec.z num+1 + !: + dec.z num + // ++dst; + inc.z dst + bne !+ + inc.z dst+1 + !: + jmp __b3 + __b2: + // dst++; + inc.z dst + bne !+ + inc.z dst+1 + !: + jmp __b1 +} + hello: .text "hello" + .byte 0 + space: .text " " + .byte 0 + world: .text "world war 3" + .byte 0 + build: .fill $28, 0 diff --git a/src/test/ref/strncat-0.cfg b/src/test/ref/strncat-0.cfg new file mode 100644 index 000000000..f4f4a061b --- /dev/null +++ b/src/test/ref/strncat-0.cfg @@ -0,0 +1,67 @@ +@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] phi() + [5] call strncat + to:main::@3 +main::@3: scope:[main] from main + [6] phi() + [7] call strncat + to:main::@4 +main::@4: scope:[main] from main::@3 + [8] phi() + [9] call strncat + to:main::@1 +main::@1: scope:[main] from main::@2 main::@4 + [10] (byte) main::i#2 ← phi( main::@2/(byte) main::i#1 main::@4/(byte) 0 ) + [11] if((byte) 0!=*((const byte*) build + (byte) main::i#2)) goto main::@2 + to:main::@return +main::@return: scope:[main] from main::@1 + [12] return + to:@return +main::@2: scope:[main] from main::@1 + [13] *((const nomodify byte*) SCREEN + (byte) main::i#2) ← *((const byte*) build + (byte) main::i#2) + [14] (byte) main::i#1 ← ++ (byte) main::i#2 + to:main::@1 + +(byte*()) strncat((byte*) strncat::destination , (to_nomodify byte*) strncat::source , (word) strncat::num) +strncat: scope:[strncat] from main main::@3 main::@4 + [15] (to_nomodify byte*) strncat::source#7 ← phi( main/(const byte*) hello main::@3/(const byte*) space main::@4/(const byte*) world ) + to:strncat::@1 +strncat::@1: scope:[strncat] from strncat strncat::@2 + [16] (byte*) strncat::dst#3 ← phi( strncat/(const byte*) build strncat::@2/(byte*) strncat::dst#1 ) + [17] if((byte) 0!=*((byte*) strncat::dst#3)) goto strncat::@2 + to:strncat::@3 +strncat::@3: scope:[strncat] from strncat::@1 strncat::@4 + [18] (word) strncat::num#4 ← phi( strncat::@1/(byte) 5 strncat::@4/(word) strncat::num#3 ) + [18] (byte*) strncat::dst#5 ← phi( strncat::@1/(byte*) strncat::dst#3 strncat::@4/(byte*) strncat::dst#2 ) + [18] (to_nomodify byte*) strncat::source#4 ← phi( strncat::@1/(to_nomodify byte*) strncat::source#7 strncat::@4/(to_nomodify byte*) strncat::source#3 ) + [19] *((byte*) strncat::dst#5) ← *((to_nomodify byte*) strncat::source#4) + [20] (to_nomodify byte*) strncat::source#3 ← ++ (to_nomodify byte*) strncat::source#4 + [21] if((byte) 0==(word) strncat::num#4) goto strncat::@5 + to:strncat::@6 +strncat::@6: scope:[strncat] from strncat::@3 + [22] if((byte) 0!=*((byte*) strncat::dst#5)) goto strncat::@4 + to:strncat::@5 +strncat::@5: scope:[strncat] from strncat::@3 strncat::@6 + [23] *((byte*) strncat::dst#5) ← (byte) 0 + to:strncat::@return +strncat::@return: scope:[strncat] from strncat::@5 + [24] return + to:@return +strncat::@4: scope:[strncat] from strncat::@6 + [25] (word) strncat::num#3 ← -- (word) strncat::num#4 + [26] (byte*) strncat::dst#2 ← ++ (byte*) strncat::dst#5 + to:strncat::@3 +strncat::@2: scope:[strncat] from strncat::@1 + [27] (byte*) strncat::dst#1 ← ++ (byte*) strncat::dst#3 + to:strncat::@1 diff --git a/src/test/ref/strncat-0.log b/src/test/ref/strncat-0.log new file mode 100644 index 000000000..3c6ff564a --- /dev/null +++ b/src/test/ref/strncat-0.log @@ -0,0 +1,1181 @@ +Warning! Adding boolean cast to non-boolean condition *((const byte*) build + (byte) main::i) +Warning! Adding boolean cast to non-boolean condition *((byte*) strncat::dst) +Culled Empty Block (label) main::@4 +Culled Empty Block (label) main::@3 +Culled Empty Block (label) main::@5 +Culled Empty Block (label) main::@6 +Culled Empty Block (label) @1 +Culled Empty Block (label) strncat::@4 +Culled Empty Block (label) strncat::@3 +Culled Empty Block (label) strncat::@5 +Culled Empty Block (label) strncat::@6 +Culled Empty Block (label) strncat::@10 +Culled Empty Block (label) strncat::@11 +Culled Empty Block (label) strncat::@12 +Culled Empty Block (label) strncat::@13 + +CONTROL FLOW GRAPH SSA +@begin: scope:[] from + to:@2 + +(void()) main() +main: scope:[main] from @2 + (byte*) strncat::destination#0 ← (const byte*) build + (to_nomodify byte*) strncat::source#0 ← (const byte*) hello + (word) strncat::num#0 ← (number) 5 + call strncat + (byte*) strncat::return#0 ← (byte*) strncat::return#4 + to:main::@7 +main::@7: scope:[main] from main + (byte*) strncat::destination#1 ← (const byte*) build + (to_nomodify byte*) strncat::source#1 ← (const byte*) space + (word) strncat::num#1 ← (number) 5 + call strncat + (byte*) strncat::return#1 ← (byte*) strncat::return#4 + to:main::@8 +main::@8: scope:[main] from main::@7 + (byte*) strncat::destination#2 ← (const byte*) build + (to_nomodify byte*) strncat::source#2 ← (const byte*) world + (word) strncat::num#2 ← (number) 5 + call strncat + (byte*) strncat::return#2 ← (byte*) strncat::return#4 + to:main::@9 +main::@9: scope:[main] from main::@8 + (byte) main::i#0 ← (byte) 0 + to:main::@1 +main::@1: scope:[main] from main::@2 main::@9 + (byte) main::i#2 ← phi( main::@2/(byte) main::i#1 main::@9/(byte) main::i#0 ) + (bool~) main::$3 ← (number) 0 != *((const byte*) build + (byte) main::i#2) + if((bool~) main::$3) goto main::@2 + to:main::@return +main::@2: scope:[main] from main::@1 + (byte) main::i#3 ← phi( main::@1/(byte) main::i#2 ) + *((const nomodify byte*) SCREEN + (byte) main::i#3) ← *((const byte*) build + (byte) main::i#3) + (byte) main::i#1 ← ++ (byte) main::i#3 + to:main::@1 +main::@return: scope:[main] from main::@1 + return + to:@return + +(byte*()) strncat((byte*) strncat::destination , (to_nomodify byte*) strncat::source , (word) strncat::num) +strncat: scope:[strncat] from main main::@7 main::@8 + (word) strncat::num#7 ← phi( main/(word) strncat::num#0 main::@7/(word) strncat::num#1 main::@8/(word) strncat::num#2 ) + (to_nomodify byte*) strncat::source#7 ← phi( main/(to_nomodify byte*) strncat::source#0 main::@7/(to_nomodify byte*) strncat::source#1 main::@8/(to_nomodify byte*) strncat::source#2 ) + (byte*) strncat::destination#3 ← phi( main/(byte*) strncat::destination#0 main::@7/(byte*) strncat::destination#1 main::@8/(byte*) strncat::destination#2 ) + (byte*) strncat::dst#0 ← (byte*) strncat::destination#3 + to:strncat::@1 +strncat::@1: scope:[strncat] from strncat strncat::@2 + (byte*) strncat::destination#6 ← phi( strncat/(byte*) strncat::destination#3 strncat::@2/(byte*) strncat::destination#8 ) + (word) strncat::num#6 ← phi( strncat/(word) strncat::num#7 strncat::@2/(word) strncat::num#8 ) + (to_nomodify byte*) strncat::source#5 ← phi( strncat/(to_nomodify byte*) strncat::source#7 strncat::@2/(to_nomodify byte*) strncat::source#8 ) + (byte*) strncat::dst#3 ← phi( strncat/(byte*) strncat::dst#0 strncat::@2/(byte*) strncat::dst#1 ) + (bool~) strncat::$1 ← (number) 0 != *((byte*) strncat::dst#3) + if((bool~) strncat::$1) goto strncat::@2 + to:strncat::@7 +strncat::@2: scope:[strncat] from strncat::@1 + (byte*) strncat::destination#8 ← phi( strncat::@1/(byte*) strncat::destination#6 ) + (word) strncat::num#8 ← phi( strncat::@1/(word) strncat::num#6 ) + (to_nomodify byte*) strncat::source#8 ← phi( strncat::@1/(to_nomodify byte*) strncat::source#5 ) + (byte*) strncat::dst#4 ← phi( strncat::@1/(byte*) strncat::dst#3 ) + (byte*) strncat::dst#1 ← ++ (byte*) strncat::dst#4 + to:strncat::@1 +strncat::@7: scope:[strncat] from strncat::@1 strncat::@8 + (byte*) strncat::destination#5 ← phi( strncat::@1/(byte*) strncat::destination#6 strncat::@8/(byte*) strncat::destination#7 ) + (word) strncat::num#4 ← phi( strncat::@1/(word) strncat::num#6 strncat::@8/(word) strncat::num#3 ) + (byte*) strncat::dst#5 ← phi( strncat::@1/(byte*) strncat::dst#3 strncat::@8/(byte*) strncat::dst#2 ) + (to_nomodify byte*) strncat::source#4 ← phi( strncat::@1/(to_nomodify byte*) strncat::source#5 strncat::@8/(to_nomodify byte*) strncat::source#6 ) + *((byte*) strncat::dst#5) ← *((to_nomodify byte*) strncat::source#4) + (bool~) strncat::$0 ← (word) strncat::num#4 && *((byte*) strncat::dst#5) + (to_nomodify byte*) strncat::source#3 ← ++ (to_nomodify byte*) strncat::source#4 + if((bool~) strncat::$0) goto strncat::@8 + to:strncat::@9 +strncat::@8: scope:[strncat] from strncat::@7 + (byte*) strncat::destination#7 ← phi( strncat::@7/(byte*) strncat::destination#5 ) + (to_nomodify byte*) strncat::source#6 ← phi( strncat::@7/(to_nomodify byte*) strncat::source#3 ) + (byte*) strncat::dst#6 ← phi( strncat::@7/(byte*) strncat::dst#5 ) + (word) strncat::num#5 ← phi( strncat::@7/(word) strncat::num#4 ) + (word) strncat::num#3 ← -- (word) strncat::num#5 + (byte*) strncat::dst#2 ← ++ (byte*) strncat::dst#6 + to:strncat::@7 +strncat::@9: scope:[strncat] from strncat::@7 + (byte*) strncat::destination#4 ← phi( strncat::@7/(byte*) strncat::destination#5 ) + (byte*) strncat::dst#7 ← phi( strncat::@7/(byte*) strncat::dst#5 ) + *((byte*) strncat::dst#7) ← (number) 0 + (byte*) strncat::return#3 ← (byte*) strncat::destination#4 + to:strncat::@return +strncat::@return: scope:[strncat] from strncat::@9 + (byte*) strncat::return#5 ← phi( strncat::@9/(byte*) strncat::return#3 ) + (byte*) strncat::return#4 ← (byte*) strncat::return#5 + 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 byte*) build[(number) $28] = { fill( $28, 0) } +(const byte*) hello[] = (byte*) "hello" +(void()) main() +(bool~) main::$3 +(label) main::@1 +(label) main::@2 +(label) main::@7 +(label) main::@8 +(label) main::@9 +(label) main::@return +(byte) main::i +(byte) main::i#0 +(byte) main::i#1 +(byte) main::i#2 +(byte) main::i#3 +(const byte*) space[] = (byte*) " " +(byte*()) strncat((byte*) strncat::destination , (to_nomodify byte*) strncat::source , (word) strncat::num) +(bool~) strncat::$0 +(bool~) strncat::$1 +(label) strncat::@1 +(label) strncat::@2 +(label) strncat::@7 +(label) strncat::@8 +(label) strncat::@9 +(label) strncat::@return +(byte*) strncat::destination +(byte*) strncat::destination#0 +(byte*) strncat::destination#1 +(byte*) strncat::destination#2 +(byte*) strncat::destination#3 +(byte*) strncat::destination#4 +(byte*) strncat::destination#5 +(byte*) strncat::destination#6 +(byte*) strncat::destination#7 +(byte*) strncat::destination#8 +(byte*) strncat::dst +(byte*) strncat::dst#0 +(byte*) strncat::dst#1 +(byte*) strncat::dst#2 +(byte*) strncat::dst#3 +(byte*) strncat::dst#4 +(byte*) strncat::dst#5 +(byte*) strncat::dst#6 +(byte*) strncat::dst#7 +(word) strncat::num +(word) strncat::num#0 +(word) strncat::num#1 +(word) strncat::num#2 +(word) strncat::num#3 +(word) strncat::num#4 +(word) strncat::num#5 +(word) strncat::num#6 +(word) strncat::num#7 +(word) strncat::num#8 +(byte*) strncat::return +(byte*) strncat::return#0 +(byte*) strncat::return#1 +(byte*) strncat::return#2 +(byte*) strncat::return#3 +(byte*) strncat::return#4 +(byte*) strncat::return#5 +(to_nomodify byte*) strncat::source +(to_nomodify byte*) strncat::source#0 +(to_nomodify byte*) strncat::source#1 +(to_nomodify byte*) strncat::source#2 +(to_nomodify byte*) strncat::source#3 +(to_nomodify byte*) strncat::source#4 +(to_nomodify byte*) strncat::source#5 +(to_nomodify byte*) strncat::source#6 +(to_nomodify byte*) strncat::source#7 +(to_nomodify byte*) strncat::source#8 +(const byte*) world[] = (byte*) "world war 3" + +Adding number conversion cast (unumber) 5 in (word) strncat::num#0 ← (number) 5 +Adding number conversion cast (unumber) 5 in (word) strncat::num#1 ← (number) 5 +Adding number conversion cast (unumber) 5 in (word) strncat::num#2 ← (number) 5 +Adding number conversion cast (unumber) 0 in (bool~) main::$3 ← (number) 0 != *((const byte*) build + (byte) main::i#2) +Adding number conversion cast (unumber) 0 in (bool~) strncat::$1 ← (number) 0 != *((byte*) strncat::dst#3) +Adding number conversion cast (unumber) 0 in *((byte*) strncat::dst#7) ← (number) 0 +Successful SSA optimization PassNAddNumberTypeConversions +Inlining cast (word) strncat::num#0 ← (unumber)(number) 5 +Inlining cast (word) strncat::num#1 ← (unumber)(number) 5 +Inlining cast (word) strncat::num#2 ← (unumber)(number) 5 +Inlining cast *((byte*) strncat::dst#7) ← (unumber)(number) 0 +Successful SSA optimization Pass2InlineCast +Simplifying constant pointer cast (byte*) 1024 +Simplifying constant integer cast 5 +Simplifying constant integer cast 5 +Simplifying constant integer cast 5 +Simplifying constant integer cast 0 +Simplifying constant integer cast 0 +Simplifying constant integer cast 0 +Successful SSA optimization PassNCastSimplification +Finalized unsigned number type (byte) 5 +Finalized unsigned number type (byte) 5 +Finalized unsigned number type (byte) 5 +Finalized unsigned number type (byte) 0 +Finalized unsigned number type (byte) 0 +Finalized unsigned number type (byte) 0 +Successful SSA optimization PassNFinalizeNumberTypeConversions +Alias main::i#2 = main::i#3 +Alias strncat::dst#0 = strncat::destination#3 +Alias strncat::dst#3 = strncat::dst#4 +Alias strncat::source#5 = strncat::source#8 +Alias strncat::num#6 = strncat::num#8 +Alias strncat::destination#6 = strncat::destination#8 +Alias strncat::num#4 = strncat::num#5 +Alias strncat::dst#5 = strncat::dst#6 strncat::dst#7 +Alias strncat::source#3 = strncat::source#6 +Alias strncat::destination#4 = strncat::destination#7 strncat::destination#5 strncat::return#3 strncat::return#5 strncat::return#4 +Successful SSA optimization Pass2AliasElimination +Identical Phi Values (to_nomodify byte*) strncat::source#5 (to_nomodify byte*) strncat::source#7 +Identical Phi Values (word) strncat::num#6 (word) strncat::num#7 +Identical Phi Values (byte*) strncat::destination#6 (byte*) strncat::dst#0 +Identical Phi Values (byte*) strncat::destination#4 (byte*) strncat::destination#6 +Successful SSA optimization Pass2IdenticalPhiElimination +Simple Condition (bool~) main::$3 [18] if((byte) 0!=*((const byte*) build + (byte) main::i#2)) goto main::@2 +Simple Condition (bool~) strncat::$1 [25] if((byte) 0!=*((byte*) strncat::dst#3)) goto strncat::@2 +Successful SSA optimization Pass2ConditionalJumpSimplification +Rewriting && if()-condition to two if()s [29] (bool~) strncat::$0 ← (word) strncat::num#4 && *((byte*) strncat::dst#5) +Successful SSA optimization Pass2ConditionalAndOrRewriting +Warning! Adding boolean cast to non-boolean condition (word) strncat::num#4 +Warning! Adding boolean cast to non-boolean condition *((byte*) strncat::dst#5) +Constant (const byte*) strncat::destination#0 = build +Constant (const to_nomodify byte*) strncat::source#0 = hello +Constant (const word) strncat::num#0 = 5 +Constant (const byte*) strncat::destination#1 = build +Constant (const to_nomodify byte*) strncat::source#1 = space +Constant (const word) strncat::num#1 = 5 +Constant (const byte*) strncat::destination#2 = build +Constant (const to_nomodify byte*) strncat::source#2 = world +Constant (const word) strncat::num#2 = 5 +Constant (const byte) main::i#0 = 0 +Successful SSA optimization Pass2ConstantIdentification +Eliminating unused variable (byte*) strncat::return#0 and assignment [1] (byte*) strncat::return#0 ← (byte*) strncat::dst#0 +Eliminating unused variable (byte*) strncat::return#1 and assignment [3] (byte*) strncat::return#1 ← (byte*) strncat::dst#0 +Eliminating unused variable (byte*) strncat::return#2 and assignment [5] (byte*) strncat::return#2 ← (byte*) strncat::dst#0 +Successful SSA optimization PassNEliminateUnusedVars +Adding number conversion cast (unumber) 0 in (bool~) strncat::$2 ← (number) 0 != (word) strncat::num#4 +Adding number conversion cast (unumber) 0 in (bool~) strncat::$3 ← (number) 0 != *((byte*) strncat::dst#5) +Successful SSA optimization PassNAddNumberTypeConversions +Simplifying constant integer cast 0 +Simplifying constant integer cast 0 +Successful SSA optimization PassNCastSimplification +Finalized unsigned number type (byte) 0 +Finalized unsigned number type (byte) 0 +Successful SSA optimization PassNFinalizeNumberTypeConversions +Simple Condition (bool~) strncat::$2 [16] if((byte) 0!=(word) strncat::num#4) goto strncat::@14 +Simple Condition (bool~) strncat::$3 [23] if((byte) 0!=*((byte*) strncat::dst#5)) goto strncat::@8 +Successful SSA optimization Pass2ConditionalJumpSimplification +Negating conditional jump and destination [16] if((byte) 0==(word) strncat::num#4) goto strncat::@9 +Successful SSA optimization Pass2ConditionalJumpSequenceImprovement +Inlining constant with var siblings (const byte) main::i#0 +Inlining constant with var siblings (const to_nomodify byte*) strncat::source#0 +Inlining constant with var siblings (const word) strncat::num#0 +Inlining constant with var siblings (const to_nomodify byte*) strncat::source#1 +Inlining constant with var siblings (const word) strncat::num#1 +Inlining constant with var siblings (const to_nomodify byte*) strncat::source#2 +Inlining constant with var siblings (const word) strncat::num#2 +Constant inlined strncat::destination#2 = (const byte*) build +Constant inlined strncat::source#0 = (const byte*) hello +Constant inlined strncat::source#1 = (const byte*) space +Constant inlined strncat::source#2 = (const byte*) world +Constant inlined strncat::destination#0 = (const byte*) build +Constant inlined strncat::destination#1 = (const byte*) build +Constant inlined main::i#0 = (byte) 0 +Constant inlined strncat::num#2 = (byte) 5 +Constant inlined strncat::num#1 = (byte) 5 +Constant inlined strncat::num#0 = (byte) 5 +Successful SSA optimization Pass2ConstantInlining +Identical Phi Values (byte*) strncat::dst#0 (const byte*) build +Identical Phi Values (word) strncat::num#7 (byte) 5 +Successful SSA optimization Pass2IdenticalPhiElimination +Added new block during phi lifting strncat::@15(between strncat::@1 and strncat::@7) +Fixing phi predecessor for strncat::num#4 to new block ( strncat::@1 -> strncat::@15 ) during phi lifting. +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 +Adding NOP phi() at start of main::@7 +Adding NOP phi() at start of main::@8 +Adding NOP phi() at start of main::@9 +CALL GRAPH +Calls in [] to main:2 +Calls in [main] to strncat:6 strncat:8 strncat:10 + +Created 6 initial phi equivalence classes +Coalesced [17] main::i#4 ← main::i#1 +Coalesced [21] strncat::source#9 ← strncat::source#7 +Coalesced [22] strncat::dst#9 ← strncat::dst#3 +Coalesced [32] strncat::source#10 ← strncat::source#3 +Coalesced [33] strncat::dst#10 ← strncat::dst#2 +Coalesced [34] strncat::num#9 ← strncat::num#3 +Coalesced [36] strncat::dst#8 ← strncat::dst#1 +Coalesced down to 4 phi equivalence classes +Culled Empty Block (label) @3 +Culled Empty Block (label) main::@9 +Culled Empty Block (label) strncat::@15 +Renumbering block @2 to @1 +Renumbering block main::@7 to main::@3 +Renumbering block main::@8 to main::@4 +Renumbering block strncat::@7 to strncat::@3 +Renumbering block strncat::@8 to strncat::@4 +Renumbering block strncat::@9 to strncat::@5 +Renumbering block strncat::@14 to strncat::@6 +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 +Adding NOP phi() at start of main::@3 +Adding NOP phi() at start of main::@4 + +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] phi() + [5] call strncat + to:main::@3 +main::@3: scope:[main] from main + [6] phi() + [7] call strncat + to:main::@4 +main::@4: scope:[main] from main::@3 + [8] phi() + [9] call strncat + to:main::@1 +main::@1: scope:[main] from main::@2 main::@4 + [10] (byte) main::i#2 ← phi( main::@2/(byte) main::i#1 main::@4/(byte) 0 ) + [11] if((byte) 0!=*((const byte*) build + (byte) main::i#2)) goto main::@2 + to:main::@return +main::@return: scope:[main] from main::@1 + [12] return + to:@return +main::@2: scope:[main] from main::@1 + [13] *((const nomodify byte*) SCREEN + (byte) main::i#2) ← *((const byte*) build + (byte) main::i#2) + [14] (byte) main::i#1 ← ++ (byte) main::i#2 + to:main::@1 + +(byte*()) strncat((byte*) strncat::destination , (to_nomodify byte*) strncat::source , (word) strncat::num) +strncat: scope:[strncat] from main main::@3 main::@4 + [15] (to_nomodify byte*) strncat::source#7 ← phi( main/(const byte*) hello main::@3/(const byte*) space main::@4/(const byte*) world ) + to:strncat::@1 +strncat::@1: scope:[strncat] from strncat strncat::@2 + [16] (byte*) strncat::dst#3 ← phi( strncat/(const byte*) build strncat::@2/(byte*) strncat::dst#1 ) + [17] if((byte) 0!=*((byte*) strncat::dst#3)) goto strncat::@2 + to:strncat::@3 +strncat::@3: scope:[strncat] from strncat::@1 strncat::@4 + [18] (word) strncat::num#4 ← phi( strncat::@1/(byte) 5 strncat::@4/(word) strncat::num#3 ) + [18] (byte*) strncat::dst#5 ← phi( strncat::@1/(byte*) strncat::dst#3 strncat::@4/(byte*) strncat::dst#2 ) + [18] (to_nomodify byte*) strncat::source#4 ← phi( strncat::@1/(to_nomodify byte*) strncat::source#7 strncat::@4/(to_nomodify byte*) strncat::source#3 ) + [19] *((byte*) strncat::dst#5) ← *((to_nomodify byte*) strncat::source#4) + [20] (to_nomodify byte*) strncat::source#3 ← ++ (to_nomodify byte*) strncat::source#4 + [21] if((byte) 0==(word) strncat::num#4) goto strncat::@5 + to:strncat::@6 +strncat::@6: scope:[strncat] from strncat::@3 + [22] if((byte) 0!=*((byte*) strncat::dst#5)) goto strncat::@4 + to:strncat::@5 +strncat::@5: scope:[strncat] from strncat::@3 strncat::@6 + [23] *((byte*) strncat::dst#5) ← (byte) 0 + to:strncat::@return +strncat::@return: scope:[strncat] from strncat::@5 + [24] return + to:@return +strncat::@4: scope:[strncat] from strncat::@6 + [25] (word) strncat::num#3 ← -- (word) strncat::num#4 + [26] (byte*) strncat::dst#2 ← ++ (byte*) strncat::dst#5 + to:strncat::@3 +strncat::@2: scope:[strncat] from strncat::@1 + [27] (byte*) strncat::dst#1 ← ++ (byte*) strncat::dst#3 + to:strncat::@1 + + +VARIABLE REGISTER WEIGHTS +(void()) main() +(byte) main::i +(byte) main::i#1 202.0 +(byte) main::i#2 168.33333333333331 +(byte*()) strncat((byte*) strncat::destination , (to_nomodify byte*) strncat::source , (word) strncat::num) +(byte*) strncat::destination +(byte*) strncat::dst +(byte*) strncat::dst#1 2002.0 +(byte*) strncat::dst#2 2002.0 +(byte*) strncat::dst#3 2002.0 +(byte*) strncat::dst#5 851.0000000000001 +(word) strncat::num +(word) strncat::num#3 1001.0 +(word) strncat::num#4 600.5999999999999 +(byte*) strncat::return +(to_nomodify byte*) strncat::source +(to_nomodify byte*) strncat::source#3 400.4 +(to_nomodify byte*) strncat::source#4 2002.0 +(to_nomodify byte*) strncat::source#7 250.25 + +Initial phi equivalence classes +[ main::i#2 main::i#1 ] +[ strncat::source#4 strncat::source#7 strncat::source#3 ] +[ strncat::dst#5 strncat::dst#3 strncat::dst#1 strncat::dst#2 ] +[ strncat::num#4 strncat::num#3 ] +Complete equivalence classes +[ main::i#2 main::i#1 ] +[ strncat::source#4 strncat::source#7 strncat::source#3 ] +[ strncat::dst#5 strncat::dst#3 strncat::dst#1 strncat::dst#2 ] +[ strncat::num#4 strncat::num#3 ] +Allocated zp[1]:2 [ main::i#2 main::i#1 ] +Allocated zp[2]:3 [ strncat::source#4 strncat::source#7 strncat::source#3 ] +Allocated zp[2]:5 [ strncat::dst#5 strncat::dst#3 strncat::dst#1 strncat::dst#2 ] +Allocated zp[2]:7 [ strncat::num#4 strncat::num#3 ] + +INITIAL ASM +Target platform is c64basic / MOS6502X + // File Comments +// Test strncat() implementation + // Upstart +.pc = $801 "Basic" +:BasicUpstart(__bbegin) +.pc = $80d "Program" + // Global Constants & labels + .label SCREEN = $400 + // @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 i = 2 + // [5] call strncat + // [15] phi from main to strncat [phi:main->strncat] + strncat_from_main: + // [15] phi (to_nomodify byte*) strncat::source#7 = (const byte*) hello [phi:main->strncat#0] -- pbuz1=pbuc1 + lda #hello + sta.z strncat.source+1 + jsr strncat + // [6] phi from main to main::@3 [phi:main->main::@3] + __b3_from_main: + jmp __b3 + // main::@3 + __b3: + // [7] call strncat + // [15] phi from main::@3 to strncat [phi:main::@3->strncat] + strncat_from___b3: + // [15] phi (to_nomodify byte*) strncat::source#7 = (const byte*) space [phi:main::@3->strncat#0] -- pbuz1=pbuc1 + lda #space + sta.z strncat.source+1 + jsr strncat + // [8] phi from main::@3 to main::@4 [phi:main::@3->main::@4] + __b4_from___b3: + jmp __b4 + // main::@4 + __b4: + // [9] call strncat + // [15] phi from main::@4 to strncat [phi:main::@4->strncat] + strncat_from___b4: + // [15] phi (to_nomodify byte*) strncat::source#7 = (const byte*) world [phi:main::@4->strncat#0] -- pbuz1=pbuc1 + lda #world + sta.z strncat.source+1 + jsr strncat + // [10] phi from main::@4 to main::@1 [phi:main::@4->main::@1] + __b1_from___b4: + // [10] phi (byte) main::i#2 = (byte) 0 [phi:main::@4->main::@1#0] -- vbuz1=vbuc1 + lda #0 + sta.z i + jmp __b1 + // main::@1 + __b1: + // [11] if((byte) 0!=*((const byte*) build + (byte) main::i#2)) goto main::@2 -- vbuc1_neq_pbuc2_derefidx_vbuz1_then_la1 + lda #0 + ldy.z i + cmp build,y + bne __b2 + jmp __breturn + // main::@return + __breturn: + // [12] return + rts + // main::@2 + __b2: + // [13] *((const nomodify byte*) SCREEN + (byte) main::i#2) ← *((const byte*) build + (byte) main::i#2) -- pbuc1_derefidx_vbuz1=pbuc2_derefidx_vbuz1 + ldy.z i + lda build,y + sta SCREEN,y + // [14] (byte) main::i#1 ← ++ (byte) main::i#2 -- vbuz1=_inc_vbuz1 + inc.z i + // [10] phi from main::@2 to main::@1 [phi:main::@2->main::@1] + __b1_from___b2: + // [10] phi (byte) main::i#2 = (byte) main::i#1 [phi:main::@2->main::@1#0] -- register_copy + jmp __b1 +} + // strncat +// Appends the first num characters of source to destination, plus a terminating null-character. +// If the length of the C string in source is less than num, only the content up to the terminating null-character is copied. +// strncat(byte* zp(3) source, word zp(7) num) +strncat: { + .label dst = 5 + .label source = 3 + .label num = 7 + // [16] phi from strncat to strncat::@1 [phi:strncat->strncat::@1] + __b1_from_strncat: + // [16] phi (byte*) strncat::dst#3 = (const byte*) build [phi:strncat->strncat::@1#0] -- pbuz1=pbuc1 + lda #build + sta.z dst+1 + jmp __b1 + // Skip the existing string at dest + // strncat::@1 + __b1: + // [17] if((byte) 0!=*((byte*) strncat::dst#3)) goto strncat::@2 -- vbuc1_neq__deref_pbuz1_then_la1 + ldy #0 + lda (dst),y + cmp #0 + bne __b2 + // [18] phi from strncat::@1 to strncat::@3 [phi:strncat::@1->strncat::@3] + __b3_from___b1: + // [18] phi (word) strncat::num#4 = (byte) 5 [phi:strncat::@1->strncat::@3#0] -- vwuz1=vbuc1 + lda #<5 + sta.z num + lda #>5 + sta.z num+1 + // [18] phi (byte*) strncat::dst#5 = (byte*) strncat::dst#3 [phi:strncat::@1->strncat::@3#1] -- register_copy + // [18] phi (to_nomodify byte*) strncat::source#4 = (to_nomodify byte*) strncat::source#7 [phi:strncat::@1->strncat::@3#2] -- register_copy + jmp __b3 + // Copy up to num chars from src + // strncat::@3 + __b3: + // [19] *((byte*) strncat::dst#5) ← *((to_nomodify byte*) strncat::source#4) -- _deref_pbuz1=_deref_pbuz2 + ldy #0 + lda (source),y + ldy #0 + sta (dst),y + // [20] (to_nomodify byte*) strncat::source#3 ← ++ (to_nomodify byte*) strncat::source#4 -- pbuz1=_inc_pbuz1 + inc.z source + bne !+ + inc.z source+1 + !: + // [21] if((byte) 0==(word) strncat::num#4) goto strncat::@5 -- vwuc1_eq_vwuz1_then_la1 + lda.z num + cmp #<0 + bne !+ + lda.z num+1 + cmp #>0 + beq __b5 + !: + jmp __b6 + // strncat::@6 + __b6: + // [22] if((byte) 0!=*((byte*) strncat::dst#5)) goto strncat::@4 -- vbuc1_neq__deref_pbuz1_then_la1 + ldy #0 + lda (dst),y + cmp #0 + bne __b4 + jmp __b5 + // strncat::@5 + __b5: + // [23] *((byte*) strncat::dst#5) ← (byte) 0 -- _deref_pbuz1=vbuc1 + // Add null-character + lda #0 + ldy #0 + sta (dst),y + jmp __breturn + // strncat::@return + __breturn: + // [24] return + rts + // strncat::@4 + __b4: + // [25] (word) strncat::num#3 ← -- (word) strncat::num#4 -- vwuz1=_dec_vwuz1 + lda.z num + bne !+ + dec.z num+1 + !: + dec.z num + // [26] (byte*) strncat::dst#2 ← ++ (byte*) strncat::dst#5 -- pbuz1=_inc_pbuz1 + inc.z dst + bne !+ + inc.z dst+1 + !: + // [18] phi from strncat::@4 to strncat::@3 [phi:strncat::@4->strncat::@3] + __b3_from___b4: + // [18] phi (word) strncat::num#4 = (word) strncat::num#3 [phi:strncat::@4->strncat::@3#0] -- register_copy + // [18] phi (byte*) strncat::dst#5 = (byte*) strncat::dst#2 [phi:strncat::@4->strncat::@3#1] -- register_copy + // [18] phi (to_nomodify byte*) strncat::source#4 = (to_nomodify byte*) strncat::source#3 [phi:strncat::@4->strncat::@3#2] -- register_copy + jmp __b3 + // strncat::@2 + __b2: + // [27] (byte*) strncat::dst#1 ← ++ (byte*) strncat::dst#3 -- pbuz1=_inc_pbuz1 + inc.z dst + bne !+ + inc.z dst+1 + !: + // [16] phi from strncat::@2 to strncat::@1 [phi:strncat::@2->strncat::@1] + __b1_from___b2: + // [16] phi (byte*) strncat::dst#3 = (byte*) strncat::dst#1 [phi:strncat::@2->strncat::@1#0] -- register_copy + jmp __b1 +} + // File Data + hello: .text "hello" + .byte 0 + space: .text " " + .byte 0 + world: .text "world war 3" + .byte 0 + build: .fill $28, 0 + +REGISTER UPLIFT POTENTIAL REGISTERS +Statement [11] if((byte) 0!=*((const byte*) build + (byte) main::i#2)) goto main::@2 [ main::i#2 ] ( main:2 [ main::i#2 ] { } ) always clobbers reg byte a +Removing always clobbered register reg byte a as potential for zp[1]:2 [ main::i#2 main::i#1 ] +Statement [13] *((const nomodify byte*) SCREEN + (byte) main::i#2) ← *((const byte*) build + (byte) main::i#2) [ main::i#2 ] ( main:2 [ main::i#2 ] { } ) always clobbers reg byte a +Statement [17] if((byte) 0!=*((byte*) strncat::dst#3)) goto strncat::@2 [ strncat::source#7 strncat::dst#3 ] ( main:2::strncat:5 [ strncat::source#7 strncat::dst#3 ] { } main:2::strncat:7 [ strncat::source#7 strncat::dst#3 ] { } main:2::strncat:9 [ strncat::source#7 strncat::dst#3 ] { } ) always clobbers reg byte a reg byte y +Statement [19] *((byte*) strncat::dst#5) ← *((to_nomodify byte*) strncat::source#4) [ strncat::source#4 strncat::dst#5 strncat::num#4 ] ( main:2::strncat:5 [ strncat::source#4 strncat::dst#5 strncat::num#4 ] { } main:2::strncat:7 [ strncat::source#4 strncat::dst#5 strncat::num#4 ] { } main:2::strncat:9 [ strncat::source#4 strncat::dst#5 strncat::num#4 ] { } ) always clobbers reg byte a reg byte y +Statement [21] if((byte) 0==(word) strncat::num#4) goto strncat::@5 [ strncat::dst#5 strncat::num#4 strncat::source#3 ] ( main:2::strncat:5 [ strncat::dst#5 strncat::num#4 strncat::source#3 ] { } main:2::strncat:7 [ strncat::dst#5 strncat::num#4 strncat::source#3 ] { } main:2::strncat:9 [ strncat::dst#5 strncat::num#4 strncat::source#3 ] { } ) always clobbers reg byte a +Statement [22] if((byte) 0!=*((byte*) strncat::dst#5)) goto strncat::@4 [ strncat::dst#5 strncat::num#4 strncat::source#3 ] ( main:2::strncat:5 [ strncat::dst#5 strncat::num#4 strncat::source#3 ] { } main:2::strncat:7 [ strncat::dst#5 strncat::num#4 strncat::source#3 ] { } main:2::strncat:9 [ strncat::dst#5 strncat::num#4 strncat::source#3 ] { } ) always clobbers reg byte a reg byte y +Statement [23] *((byte*) strncat::dst#5) ← (byte) 0 [ ] ( main:2::strncat:5 [ ] { } main:2::strncat:7 [ ] { } main:2::strncat:9 [ ] { } ) always clobbers reg byte a reg byte y +Statement [25] (word) strncat::num#3 ← -- (word) strncat::num#4 [ strncat::dst#5 strncat::source#3 strncat::num#3 ] ( main:2::strncat:5 [ strncat::dst#5 strncat::source#3 strncat::num#3 ] { } main:2::strncat:7 [ strncat::dst#5 strncat::source#3 strncat::num#3 ] { } main:2::strncat:9 [ strncat::dst#5 strncat::source#3 strncat::num#3 ] { } ) always clobbers reg byte a +Statement [11] if((byte) 0!=*((const byte*) build + (byte) main::i#2)) goto main::@2 [ main::i#2 ] ( main:2 [ main::i#2 ] { } ) always clobbers reg byte a +Statement [13] *((const nomodify byte*) SCREEN + (byte) main::i#2) ← *((const byte*) build + (byte) main::i#2) [ main::i#2 ] ( main:2 [ main::i#2 ] { } ) always clobbers reg byte a +Statement [17] if((byte) 0!=*((byte*) strncat::dst#3)) goto strncat::@2 [ strncat::source#7 strncat::dst#3 ] ( main:2::strncat:5 [ strncat::source#7 strncat::dst#3 ] { } main:2::strncat:7 [ strncat::source#7 strncat::dst#3 ] { } main:2::strncat:9 [ strncat::source#7 strncat::dst#3 ] { } ) always clobbers reg byte a reg byte y +Statement [19] *((byte*) strncat::dst#5) ← *((to_nomodify byte*) strncat::source#4) [ strncat::source#4 strncat::dst#5 strncat::num#4 ] ( main:2::strncat:5 [ strncat::source#4 strncat::dst#5 strncat::num#4 ] { } main:2::strncat:7 [ strncat::source#4 strncat::dst#5 strncat::num#4 ] { } main:2::strncat:9 [ strncat::source#4 strncat::dst#5 strncat::num#4 ] { } ) always clobbers reg byte a reg byte y +Statement [21] if((byte) 0==(word) strncat::num#4) goto strncat::@5 [ strncat::dst#5 strncat::num#4 strncat::source#3 ] ( main:2::strncat:5 [ strncat::dst#5 strncat::num#4 strncat::source#3 ] { } main:2::strncat:7 [ strncat::dst#5 strncat::num#4 strncat::source#3 ] { } main:2::strncat:9 [ strncat::dst#5 strncat::num#4 strncat::source#3 ] { } ) always clobbers reg byte a +Statement [22] if((byte) 0!=*((byte*) strncat::dst#5)) goto strncat::@4 [ strncat::dst#5 strncat::num#4 strncat::source#3 ] ( main:2::strncat:5 [ strncat::dst#5 strncat::num#4 strncat::source#3 ] { } main:2::strncat:7 [ strncat::dst#5 strncat::num#4 strncat::source#3 ] { } main:2::strncat:9 [ strncat::dst#5 strncat::num#4 strncat::source#3 ] { } ) always clobbers reg byte a reg byte y +Statement [23] *((byte*) strncat::dst#5) ← (byte) 0 [ ] ( main:2::strncat:5 [ ] { } main:2::strncat:7 [ ] { } main:2::strncat:9 [ ] { } ) always clobbers reg byte a reg byte y +Statement [25] (word) strncat::num#3 ← -- (word) strncat::num#4 [ strncat::dst#5 strncat::source#3 strncat::num#3 ] ( main:2::strncat:5 [ strncat::dst#5 strncat::source#3 strncat::num#3 ] { } main:2::strncat:7 [ strncat::dst#5 strncat::source#3 strncat::num#3 ] { } main:2::strncat:9 [ strncat::dst#5 strncat::source#3 strncat::num#3 ] { } ) always clobbers reg byte a +Potential registers zp[1]:2 [ main::i#2 main::i#1 ] : zp[1]:2 , reg byte x , reg byte y , +Potential registers zp[2]:3 [ strncat::source#4 strncat::source#7 strncat::source#3 ] : zp[2]:3 , +Potential registers zp[2]:5 [ strncat::dst#5 strncat::dst#3 strncat::dst#1 strncat::dst#2 ] : zp[2]:5 , +Potential registers zp[2]:7 [ strncat::num#4 strncat::num#3 ] : zp[2]:7 , + +REGISTER UPLIFT SCOPES +Uplift Scope [strncat] 6,857: zp[2]:5 [ strncat::dst#5 strncat::dst#3 strncat::dst#1 strncat::dst#2 ] 2,652.65: zp[2]:3 [ strncat::source#4 strncat::source#7 strncat::source#3 ] 1,601.6: zp[2]:7 [ strncat::num#4 strncat::num#3 ] +Uplift Scope [main] 370.33: zp[1]:2 [ main::i#2 main::i#1 ] +Uplift Scope [] + +Uplifting [strncat] best 1981 combination zp[2]:5 [ strncat::dst#5 strncat::dst#3 strncat::dst#1 strncat::dst#2 ] zp[2]:3 [ strncat::source#4 strncat::source#7 strncat::source#3 ] zp[2]:7 [ strncat::num#4 strncat::num#3 ] +Uplifting [main] best 1861 combination reg byte x [ main::i#2 main::i#1 ] +Uplifting [] best 1861 combination +Allocated (was zp[2]:3) zp[2]:2 [ strncat::source#4 strncat::source#7 strncat::source#3 ] +Allocated (was zp[2]:5) zp[2]:4 [ strncat::dst#5 strncat::dst#3 strncat::dst#1 strncat::dst#2 ] +Allocated (was zp[2]:7) zp[2]:6 [ strncat::num#4 strncat::num#3 ] + +ASSEMBLER BEFORE OPTIMIZATION + // File Comments +// Test strncat() implementation + // Upstart +.pc = $801 "Basic" +:BasicUpstart(__bbegin) +.pc = $80d "Program" + // Global Constants & labels + .label SCREEN = $400 + // @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: { + // [5] call strncat + // [15] phi from main to strncat [phi:main->strncat] + strncat_from_main: + // [15] phi (to_nomodify byte*) strncat::source#7 = (const byte*) hello [phi:main->strncat#0] -- pbuz1=pbuc1 + lda #hello + sta.z strncat.source+1 + jsr strncat + // [6] phi from main to main::@3 [phi:main->main::@3] + __b3_from_main: + jmp __b3 + // main::@3 + __b3: + // [7] call strncat + // [15] phi from main::@3 to strncat [phi:main::@3->strncat] + strncat_from___b3: + // [15] phi (to_nomodify byte*) strncat::source#7 = (const byte*) space [phi:main::@3->strncat#0] -- pbuz1=pbuc1 + lda #space + sta.z strncat.source+1 + jsr strncat + // [8] phi from main::@3 to main::@4 [phi:main::@3->main::@4] + __b4_from___b3: + jmp __b4 + // main::@4 + __b4: + // [9] call strncat + // [15] phi from main::@4 to strncat [phi:main::@4->strncat] + strncat_from___b4: + // [15] phi (to_nomodify byte*) strncat::source#7 = (const byte*) world [phi:main::@4->strncat#0] -- pbuz1=pbuc1 + lda #world + sta.z strncat.source+1 + jsr strncat + // [10] phi from main::@4 to main::@1 [phi:main::@4->main::@1] + __b1_from___b4: + // [10] phi (byte) main::i#2 = (byte) 0 [phi:main::@4->main::@1#0] -- vbuxx=vbuc1 + ldx #0 + jmp __b1 + // main::@1 + __b1: + // [11] if((byte) 0!=*((const byte*) build + (byte) main::i#2)) goto main::@2 -- vbuc1_neq_pbuc2_derefidx_vbuxx_then_la1 + lda build,x + cmp #0 + bne __b2 + jmp __breturn + // main::@return + __breturn: + // [12] return + rts + // main::@2 + __b2: + // [13] *((const nomodify byte*) SCREEN + (byte) main::i#2) ← *((const byte*) build + (byte) main::i#2) -- pbuc1_derefidx_vbuxx=pbuc2_derefidx_vbuxx + lda build,x + sta SCREEN,x + // [14] (byte) main::i#1 ← ++ (byte) main::i#2 -- vbuxx=_inc_vbuxx + inx + // [10] phi from main::@2 to main::@1 [phi:main::@2->main::@1] + __b1_from___b2: + // [10] phi (byte) main::i#2 = (byte) main::i#1 [phi:main::@2->main::@1#0] -- register_copy + jmp __b1 +} + // strncat +// Appends the first num characters of source to destination, plus a terminating null-character. +// If the length of the C string in source is less than num, only the content up to the terminating null-character is copied. +// strncat(byte* zp(2) source, word zp(6) num) +strncat: { + .label dst = 4 + .label source = 2 + .label num = 6 + // [16] phi from strncat to strncat::@1 [phi:strncat->strncat::@1] + __b1_from_strncat: + // [16] phi (byte*) strncat::dst#3 = (const byte*) build [phi:strncat->strncat::@1#0] -- pbuz1=pbuc1 + lda #build + sta.z dst+1 + jmp __b1 + // Skip the existing string at dest + // strncat::@1 + __b1: + // [17] if((byte) 0!=*((byte*) strncat::dst#3)) goto strncat::@2 -- vbuc1_neq__deref_pbuz1_then_la1 + ldy #0 + lda (dst),y + cmp #0 + bne __b2 + // [18] phi from strncat::@1 to strncat::@3 [phi:strncat::@1->strncat::@3] + __b3_from___b1: + // [18] phi (word) strncat::num#4 = (byte) 5 [phi:strncat::@1->strncat::@3#0] -- vwuz1=vbuc1 + lda #<5 + sta.z num + lda #>5 + sta.z num+1 + // [18] phi (byte*) strncat::dst#5 = (byte*) strncat::dst#3 [phi:strncat::@1->strncat::@3#1] -- register_copy + // [18] phi (to_nomodify byte*) strncat::source#4 = (to_nomodify byte*) strncat::source#7 [phi:strncat::@1->strncat::@3#2] -- register_copy + jmp __b3 + // Copy up to num chars from src + // strncat::@3 + __b3: + // [19] *((byte*) strncat::dst#5) ← *((to_nomodify byte*) strncat::source#4) -- _deref_pbuz1=_deref_pbuz2 + ldy #0 + lda (source),y + ldy #0 + sta (dst),y + // [20] (to_nomodify byte*) strncat::source#3 ← ++ (to_nomodify byte*) strncat::source#4 -- pbuz1=_inc_pbuz1 + inc.z source + bne !+ + inc.z source+1 + !: + // [21] if((byte) 0==(word) strncat::num#4) goto strncat::@5 -- vwuc1_eq_vwuz1_then_la1 + lda.z num + cmp #<0 + bne !+ + lda.z num+1 + cmp #>0 + beq __b5 + !: + jmp __b6 + // strncat::@6 + __b6: + // [22] if((byte) 0!=*((byte*) strncat::dst#5)) goto strncat::@4 -- vbuc1_neq__deref_pbuz1_then_la1 + ldy #0 + lda (dst),y + cmp #0 + bne __b4 + jmp __b5 + // strncat::@5 + __b5: + // [23] *((byte*) strncat::dst#5) ← (byte) 0 -- _deref_pbuz1=vbuc1 + // Add null-character + lda #0 + ldy #0 + sta (dst),y + jmp __breturn + // strncat::@return + __breturn: + // [24] return + rts + // strncat::@4 + __b4: + // [25] (word) strncat::num#3 ← -- (word) strncat::num#4 -- vwuz1=_dec_vwuz1 + lda.z num + bne !+ + dec.z num+1 + !: + dec.z num + // [26] (byte*) strncat::dst#2 ← ++ (byte*) strncat::dst#5 -- pbuz1=_inc_pbuz1 + inc.z dst + bne !+ + inc.z dst+1 + !: + // [18] phi from strncat::@4 to strncat::@3 [phi:strncat::@4->strncat::@3] + __b3_from___b4: + // [18] phi (word) strncat::num#4 = (word) strncat::num#3 [phi:strncat::@4->strncat::@3#0] -- register_copy + // [18] phi (byte*) strncat::dst#5 = (byte*) strncat::dst#2 [phi:strncat::@4->strncat::@3#1] -- register_copy + // [18] phi (to_nomodify byte*) strncat::source#4 = (to_nomodify byte*) strncat::source#3 [phi:strncat::@4->strncat::@3#2] -- register_copy + jmp __b3 + // strncat::@2 + __b2: + // [27] (byte*) strncat::dst#1 ← ++ (byte*) strncat::dst#3 -- pbuz1=_inc_pbuz1 + inc.z dst + bne !+ + inc.z dst+1 + !: + // [16] phi from strncat::@2 to strncat::@1 [phi:strncat::@2->strncat::@1] + __b1_from___b2: + // [16] phi (byte*) strncat::dst#3 = (byte*) strncat::dst#1 [phi:strncat::@2->strncat::@1#0] -- register_copy + jmp __b1 +} + // File Data + hello: .text "hello" + .byte 0 + space: .text " " + .byte 0 + world: .text "world war 3" + .byte 0 + build: .fill $28, 0 + +ASSEMBLER OPTIMIZATIONS +Removing instruction jmp __b1 +Removing instruction jmp __bend +Removing instruction jmp __b3 +Removing instruction jmp __b4 +Removing instruction jmp __b1 +Removing instruction jmp __breturn +Removing instruction jmp __b1 +Removing instruction jmp __b3 +Removing instruction jmp __b6 +Removing instruction jmp __b5 +Removing instruction jmp __breturn +Succesful ASM optimization Pass5NextJumpElimination +Removing instruction ldy #0 +Replacing instruction ldy #0 with TAY +Succesful ASM optimization Pass5UnnecesaryLoadElimination +Removing instruction __b1_from___bbegin: +Removing instruction __b1: +Removing instruction main_from___b1: +Removing instruction __bend_from___b1: +Removing instruction __b3_from_main: +Removing instruction strncat_from___b3: +Removing instruction __b4_from___b3: +Removing instruction strncat_from___b4: +Succesful ASM optimization Pass5RedundantLabelElimination +Removing instruction __bend: +Removing instruction strncat_from_main: +Removing instruction __b3: +Removing instruction __b4: +Removing instruction __b1_from___b4: +Removing instruction __breturn: +Removing instruction __b1_from___b2: +Removing instruction __b1_from_strncat: +Removing instruction __b3_from___b1: +Removing instruction __b6: +Removing instruction __breturn: +Removing instruction __b3_from___b4: +Removing instruction __b1_from___b2: +Succesful ASM optimization Pass5UnusedLabelElimination +Updating BasicUpstart to call main directly +Removing instruction jsr main +Succesful ASM optimization Pass5SkipBegin +Replacing instruction lda #>5 with TYA +Removing instruction __bbegin: +Succesful ASM optimization Pass5UnusedLabelElimination + +FINAL SYMBOL TABLE +(label) @1 +(label) @begin +(label) @end +(const nomodify byte*) SCREEN = (byte*) 1024 +(const byte*) build[(number) $28] = { fill( $28, 0) } +(const byte*) hello[] = (byte*) "hello" +(void()) main() +(label) main::@1 +(label) main::@2 +(label) main::@3 +(label) main::@4 +(label) main::@return +(byte) main::i +(byte) main::i#1 reg byte x 202.0 +(byte) main::i#2 reg byte x 168.33333333333331 +(const byte*) space[] = (byte*) " " +(byte*()) strncat((byte*) strncat::destination , (to_nomodify byte*) strncat::source , (word) strncat::num) +(label) strncat::@1 +(label) strncat::@2 +(label) strncat::@3 +(label) strncat::@4 +(label) strncat::@5 +(label) strncat::@6 +(label) strncat::@return +(byte*) strncat::destination +(byte*) strncat::dst +(byte*) strncat::dst#1 dst zp[2]:4 2002.0 +(byte*) strncat::dst#2 dst zp[2]:4 2002.0 +(byte*) strncat::dst#3 dst zp[2]:4 2002.0 +(byte*) strncat::dst#5 dst zp[2]:4 851.0000000000001 +(word) strncat::num +(word) strncat::num#3 num zp[2]:6 1001.0 +(word) strncat::num#4 num zp[2]:6 600.5999999999999 +(byte*) strncat::return +(to_nomodify byte*) strncat::source +(to_nomodify byte*) strncat::source#3 source zp[2]:2 400.4 +(to_nomodify byte*) strncat::source#4 source zp[2]:2 2002.0 +(to_nomodify byte*) strncat::source#7 source zp[2]:2 250.25 +(const byte*) world[] = (byte*) "world war 3" + +reg byte x [ main::i#2 main::i#1 ] +zp[2]:2 [ strncat::source#4 strncat::source#7 strncat::source#3 ] +zp[2]:4 [ strncat::dst#5 strncat::dst#3 strncat::dst#1 strncat::dst#2 ] +zp[2]:6 [ strncat::num#4 strncat::num#3 ] + + +FINAL ASSEMBLER +Score: 1640 + + // File Comments +// Test strncat() implementation + // Upstart +.pc = $801 "Basic" +:BasicUpstart(main) +.pc = $80d "Program" + // Global Constants & labels + .label SCREEN = $400 + // @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: { + // strncat(build, hello, 5) + // [5] call strncat + // [15] phi from main to strncat [phi:main->strncat] + // [15] phi (to_nomodify byte*) strncat::source#7 = (const byte*) hello [phi:main->strncat#0] -- pbuz1=pbuc1 + lda #hello + sta.z strncat.source+1 + jsr strncat + // [6] phi from main to main::@3 [phi:main->main::@3] + // main::@3 + // strncat(build, space, 5) + // [7] call strncat + // [15] phi from main::@3 to strncat [phi:main::@3->strncat] + // [15] phi (to_nomodify byte*) strncat::source#7 = (const byte*) space [phi:main::@3->strncat#0] -- pbuz1=pbuc1 + lda #space + sta.z strncat.source+1 + jsr strncat + // [8] phi from main::@3 to main::@4 [phi:main::@3->main::@4] + // main::@4 + // strncat(build, world, 5) + // [9] call strncat + // [15] phi from main::@4 to strncat [phi:main::@4->strncat] + // [15] phi (to_nomodify byte*) strncat::source#7 = (const byte*) world [phi:main::@4->strncat#0] -- pbuz1=pbuc1 + lda #world + sta.z strncat.source+1 + jsr strncat + // [10] phi from main::@4 to main::@1 [phi:main::@4->main::@1] + // [10] phi (byte) main::i#2 = (byte) 0 [phi:main::@4->main::@1#0] -- vbuxx=vbuc1 + ldx #0 + // main::@1 + __b1: + // while(build[i]) + // [11] if((byte) 0!=*((const byte*) build + (byte) main::i#2)) goto main::@2 -- vbuc1_neq_pbuc2_derefidx_vbuxx_then_la1 + lda build,x + cmp #0 + bne __b2 + // main::@return + // } + // [12] return + rts + // main::@2 + __b2: + // SCREEN[i] = build[i] + // [13] *((const nomodify byte*) SCREEN + (byte) main::i#2) ← *((const byte*) build + (byte) main::i#2) -- pbuc1_derefidx_vbuxx=pbuc2_derefidx_vbuxx + lda build,x + sta SCREEN,x + // i++; + // [14] (byte) main::i#1 ← ++ (byte) main::i#2 -- vbuxx=_inc_vbuxx + inx + // [10] phi from main::@2 to main::@1 [phi:main::@2->main::@1] + // [10] phi (byte) main::i#2 = (byte) main::i#1 [phi:main::@2->main::@1#0] -- register_copy + jmp __b1 +} + // strncat +// Appends the first num characters of source to destination, plus a terminating null-character. +// If the length of the C string in source is less than num, only the content up to the terminating null-character is copied. +// strncat(byte* zp(2) source, word zp(6) num) +strncat: { + .label dst = 4 + .label source = 2 + .label num = 6 + // [16] phi from strncat to strncat::@1 [phi:strncat->strncat::@1] + // [16] phi (byte*) strncat::dst#3 = (const byte*) build [phi:strncat->strncat::@1#0] -- pbuz1=pbuc1 + lda #build + sta.z dst+1 + // Skip the existing string at dest + // strncat::@1 + __b1: + // while (*dst) + // [17] if((byte) 0!=*((byte*) strncat::dst#3)) goto strncat::@2 -- vbuc1_neq__deref_pbuz1_then_la1 + ldy #0 + lda (dst),y + cmp #0 + bne __b2 + // [18] phi from strncat::@1 to strncat::@3 [phi:strncat::@1->strncat::@3] + // [18] phi (word) strncat::num#4 = (byte) 5 [phi:strncat::@1->strncat::@3#0] -- vwuz1=vbuc1 + lda #<5 + sta.z num + tya + sta.z num+1 + // [18] phi (byte*) strncat::dst#5 = (byte*) strncat::dst#3 [phi:strncat::@1->strncat::@3#1] -- register_copy + // [18] phi (to_nomodify byte*) strncat::source#4 = (to_nomodify byte*) strncat::source#7 [phi:strncat::@1->strncat::@3#2] -- register_copy + // Copy up to num chars from src + // strncat::@3 + __b3: + // *dst = *source++ + // [19] *((byte*) strncat::dst#5) ← *((to_nomodify byte*) strncat::source#4) -- _deref_pbuz1=_deref_pbuz2 + ldy #0 + lda (source),y + sta (dst),y + // while (num && (*dst = *source++)) + // [20] (to_nomodify byte*) strncat::source#3 ← ++ (to_nomodify byte*) strncat::source#4 -- pbuz1=_inc_pbuz1 + inc.z source + bne !+ + inc.z source+1 + !: + // [21] if((byte) 0==(word) strncat::num#4) goto strncat::@5 -- vwuc1_eq_vwuz1_then_la1 + lda.z num + cmp #<0 + bne !+ + lda.z num+1 + cmp #>0 + beq __b5 + !: + // strncat::@6 + // [22] if((byte) 0!=*((byte*) strncat::dst#5)) goto strncat::@4 -- vbuc1_neq__deref_pbuz1_then_la1 + ldy #0 + lda (dst),y + cmp #0 + bne __b4 + // strncat::@5 + __b5: + // *dst = 0 + // [23] *((byte*) strncat::dst#5) ← (byte) 0 -- _deref_pbuz1=vbuc1 + // Add null-character + lda #0 + tay + sta (dst),y + // strncat::@return + // } + // [24] return + rts + // strncat::@4 + __b4: + // --num; + // [25] (word) strncat::num#3 ← -- (word) strncat::num#4 -- vwuz1=_dec_vwuz1 + lda.z num + bne !+ + dec.z num+1 + !: + dec.z num + // ++dst; + // [26] (byte*) strncat::dst#2 ← ++ (byte*) strncat::dst#5 -- pbuz1=_inc_pbuz1 + inc.z dst + bne !+ + inc.z dst+1 + !: + // [18] phi from strncat::@4 to strncat::@3 [phi:strncat::@4->strncat::@3] + // [18] phi (word) strncat::num#4 = (word) strncat::num#3 [phi:strncat::@4->strncat::@3#0] -- register_copy + // [18] phi (byte*) strncat::dst#5 = (byte*) strncat::dst#2 [phi:strncat::@4->strncat::@3#1] -- register_copy + // [18] phi (to_nomodify byte*) strncat::source#4 = (to_nomodify byte*) strncat::source#3 [phi:strncat::@4->strncat::@3#2] -- register_copy + jmp __b3 + // strncat::@2 + __b2: + // dst++; + // [27] (byte*) strncat::dst#1 ← ++ (byte*) strncat::dst#3 -- pbuz1=_inc_pbuz1 + inc.z dst + bne !+ + inc.z dst+1 + !: + // [16] phi from strncat::@2 to strncat::@1 [phi:strncat::@2->strncat::@1] + // [16] phi (byte*) strncat::dst#3 = (byte*) strncat::dst#1 [phi:strncat::@2->strncat::@1#0] -- register_copy + jmp __b1 +} + // File Data + hello: .text "hello" + .byte 0 + space: .text " " + .byte 0 + world: .text "world war 3" + .byte 0 + build: .fill $28, 0 + diff --git a/src/test/ref/strncat-0.sym b/src/test/ref/strncat-0.sym new file mode 100644 index 000000000..58451c889 --- /dev/null +++ b/src/test/ref/strncat-0.sym @@ -0,0 +1,44 @@ +(label) @1 +(label) @begin +(label) @end +(const nomodify byte*) SCREEN = (byte*) 1024 +(const byte*) build[(number) $28] = { fill( $28, 0) } +(const byte*) hello[] = (byte*) "hello" +(void()) main() +(label) main::@1 +(label) main::@2 +(label) main::@3 +(label) main::@4 +(label) main::@return +(byte) main::i +(byte) main::i#1 reg byte x 202.0 +(byte) main::i#2 reg byte x 168.33333333333331 +(const byte*) space[] = (byte*) " " +(byte*()) strncat((byte*) strncat::destination , (to_nomodify byte*) strncat::source , (word) strncat::num) +(label) strncat::@1 +(label) strncat::@2 +(label) strncat::@3 +(label) strncat::@4 +(label) strncat::@5 +(label) strncat::@6 +(label) strncat::@return +(byte*) strncat::destination +(byte*) strncat::dst +(byte*) strncat::dst#1 dst zp[2]:4 2002.0 +(byte*) strncat::dst#2 dst zp[2]:4 2002.0 +(byte*) strncat::dst#3 dst zp[2]:4 2002.0 +(byte*) strncat::dst#5 dst zp[2]:4 851.0000000000001 +(word) strncat::num +(word) strncat::num#3 num zp[2]:6 1001.0 +(word) strncat::num#4 num zp[2]:6 600.5999999999999 +(byte*) strncat::return +(to_nomodify byte*) strncat::source +(to_nomodify byte*) strncat::source#3 source zp[2]:2 400.4 +(to_nomodify byte*) strncat::source#4 source zp[2]:2 2002.0 +(to_nomodify byte*) strncat::source#7 source zp[2]:2 250.25 +(const byte*) world[] = (byte*) "world war 3" + +reg byte x [ main::i#2 main::i#1 ] +zp[2]:2 [ strncat::source#4 strncat::source#7 strncat::source#3 ] +zp[2]:4 [ strncat::dst#5 strncat::dst#3 strncat::dst#1 strncat::dst#2 ] +zp[2]:6 [ strncat::num#4 strncat::num#3 ]