diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/StackFrameLayout.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/StackFrameLayout.kt index 3db80a8a5..6bc8c0d09 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/StackFrameLayout.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/StackFrameLayout.kt @@ -17,7 +17,8 @@ import prog8.intermediate.* * - stamps frameSize / incomingSize on the IRSubroutine. * * Everything else keeps the existing static path verbatim. A subroutine is frameable when ALL of: - * - it contains no inline assembly chunk, + * - it is not marked %option noframe; inline assembly is allowed and does not by itself prevent + * framing, so asm that manipulates the machine stack or A5 must opt out explicitly, * - it makes no indirect calls and its address is never taken (subptr dispatch tables, interrupt * handler registration, ...): an unknown caller cannot push arguments into a frame, * - it cannot reach a subroutine whose assembly code writes to A5: such a call would overwrite @@ -87,7 +88,7 @@ class StackFrameLayout(private val program: IRProgram, private val errors: IErro val framedSubs = mutableMapOf() for (sub in allSubs) { - val outcome = frameableSubroutine(sub, referencedIn, addressTaken, asmTexts, callSites, dispatchTargets, indirectCallers, reentrant, reachesFramePointerClobber) + val outcome = frameableSubroutine(sub, referencedIn, addressTaken, asmTexts, callSites, dispatchTargets, indirectCallers, reentrant, reachesFramePointerClobber, framePointerClobberers) if (outcome is Frameability.Frameable) framedSubs[sub.label] = outcome.layout } @@ -173,18 +174,15 @@ class StackFrameLayout(private val program: IRProgram, private val errors: IErro return result } - /** a frame-pointer-clobbering subroutine reached from another subroutine, and how it was reached */ - private class ClobberReach(val clobberer: String, val viaIndirect: Boolean) - /** - * Subroutine label -> the frame-pointer-clobbering subroutine it can reach, for every - * subroutine that can reach one. Only real call edges are followed; a subroutine is only - * credited with reaching a clobberer through an indirect call (viaIndirect=true) when it - * can reach a subroutine that makes one, because such a call may dispatch to anything. + * Subroutine labels that can reach a frame-pointer-clobbering subroutine through a real + * call chain, or that can reach an indirect call (which may dispatch to any address-taken + * subroutine including a clobberer). Calls into %option noframe subroutines are a barrier: + * the mark asserts a save/restore discipline that leaves the caller's A5 intact. */ - private fun subsReachingFramePointerClobber(clobberers: Set, indirectCallers: Set): Map { + private fun subsReachingFramePointerClobber(clobberers: Set, indirectCallers: Set): Set { if (clobberers.isEmpty()) - return emptyMap() + return emptySet() // Calls into %option noframe subroutines are a barrier for both propagations below: // the mark asserts a save/restore discipline that leaves the caller's A5 intact when // the call returns (or abandons the caller, as when switching away), so clobber reach @@ -203,16 +201,17 @@ class StackFrameLayout(private val program: IRProgram, private val errors: IErro } } } - val reaching = mutableMapOf() + val reaching = mutableSetOf() var changed = true while (changed) { changed = false for ((label, targets) in callTargets) { if (label in reaching) continue - val hit = targets.filter { it !in noframeSubs }.firstOrNull { it in clobberers || it in reaching } ?: continue - reaching[label] = if (hit in clobberers) ClobberReach(hit, false) else reaching.getValue(hit) - changed = true + if (targets.filter { it !in noframeSubs }.any { it in clobberers || it in reaching }) { + reaching += label + changed = true + } } } // no real call chain reaches a clobberer: if an indirect call is reachable (here or @@ -232,7 +231,7 @@ class StackFrameLayout(private val program: IRProgram, private val errors: IErro } for (label in callTargets.keys) { if (label !in reaching && label in reachesIndirect) - reaching[label] = ClobberReach(clobberers.first(), true) + reaching += label } return reaching } @@ -401,16 +400,14 @@ class StackFrameLayout(private val program: IRProgram, private val errors: IErro dispatchTargets: Set, indirectCallers: Set, reentrant: Set, - reachesFramePointerClobber: Map + reachesFramePointerClobber: Set, + framePointerClobberers: Set ): Frameability { val static = { Frameability.NotFrameable() } // a %option noframe subroutine requires the legacy static convention (it manipulates // the machine stack itself), so it never gets a frame if (sub.noframe) return static() if (sub.hasFrame) return static() - var hasInlineAsm = false - sub.forEachChunk { chunk -> if (chunk is IRInlineAsmChunk) hasInlineAsm = true } - if (hasInlineAsm) return static() if (sub.label in indirectCallers) return static() // An address-taken subroutine with parameters must stay static: an indirect call site // cannot know which convention to pass arguments in. Without parameters there is nothing @@ -419,6 +416,7 @@ class StackFrameLayout(private val program: IRProgram, private val errors: IErro if (sub.label in dispatchTargets && sub.parameters.isNotEmpty()) return static() if (isLabelReferencedInAsm(sub.label, asmTexts)) return static() if (sub.label in reachesFramePointerClobber) return static() + if (sub.label in framePointerClobberers) return static() // every parameter must be able to move: otherwise the caller keeps writing static cells // IRParam.name is the scoped variable name; call sites refer to the same parameter by its diff --git a/compiler/res/prog8lib/amiga1200/prog8_lib.p8 b/compiler/res/prog8lib/amiga1200/prog8_lib.p8 index 55ac5da6a..044989e55 100644 --- a/compiler/res/prog8lib/amiga1200/prog8_lib.p8 +++ b/compiler/res/prog8lib/amiga1200/prog8_lib.p8 @@ -29,6 +29,7 @@ p8_sys_startup { ; } sub init_system() { + %option noframe %asm {{ ; save CLI arguments move.l d0,-(sp) @@ -89,6 +90,7 @@ proc_CLI = 172 } sub init_system_phase2() { + %option noframe %asm {{ moveq #0,d0 moveq #0,d1 @@ -110,6 +112,7 @@ proc_CLI = 172 } sub cleanup_at_exit() { + %option noframe %asm {{ movem.l d0,-(sp) ; keep return code diff --git a/compiler/res/prog8lib/amiga1200/syslib.p8 b/compiler/res/prog8lib/amiga1200/syslib.p8 index cf52f7f3b..cf3bcbb1e 100644 --- a/compiler/res/prog8lib/amiga1200/syslib.p8 +++ b/compiler/res/prog8lib/amiga1200/syslib.p8 @@ -47,6 +47,7 @@ sys { ^^ubyte @shared arguments ; CLI argument string (null-terminated), or NULL if Workbench launch sub reset_system() { + %option noframe %asm {{ move.l 4.w,a6 jmp -726(a6) ; ColdReboot() @@ -71,17 +72,18 @@ sys { }} } - sub exit(ubyte returnvalue) { + asmsub exit(word returnvalue @D0) { ; -- exit the program with a return code in D0. When invoked from Prog8 via sys.exit(), all active defers in the call chain are unwound program-wide (LIFO) before system cleanup. sys.reset_system() does not run defers. %asm {{ - moveq.l #0,d0 - move.b sys.exit.returnvalue,d0 + and.l #$ffff,d0 move.l p8_sys_startup.orig_stackpointer,sp jmp p8_sys_startup.cleanup_at_exit }} } + sub set_carry() { + %option noframe %asm {{ ; set both C (comparison carry) and X (rotate carry) bits moveq #$11,d0 @@ -90,6 +92,7 @@ sys { } sub clear_carry() { + %option noframe %asm {{ ; clear C and X bits moveq #0,d0 @@ -144,7 +147,80 @@ sys { move.l 4.w,a6 move.w 296(a6),d0 andi.w #$008e,d0 + seq d0 + tst.b d0 rts }} } + + sub die(uword code, str message) { + %asm {{ + moveq #0,d0 + move.w 14(a5),d0 + lea .alert(pc),a1 + lea .alert_limit(pc),a3 + move.w #20,(a1)+ + move.b #16,(a1)+ + lea .prefix(pc),a2 +.copy_prefix: + move.b (a2)+,d1 + beq.s .copy_code + move.b d1,(a1)+ + bra.s .copy_prefix +.copy_code: + move.w 14(a5),d2 + moveq #3,d4 +.copy_code_digit: + move.w d2,d3 + lsr.w #8,d3 + lsr.w #4,d3 + andi.w #$000f,d3 + cmpi.w #10,d3 + bcs.s .copy_code_number + addi.w #'A'-10,d3 + bra.s .copy_code_store +.copy_code_number: + addi.w #'0',d3 +.copy_code_store: + move.b d3,(a1)+ + lsl.w #4,d2 + dbra d4,.copy_code_digit + clr.b (a1)+ + move.b #1,(a1)+ + move.w #20,(a1)+ + move.b #32,(a1)+ +.copy_message: + move.l 8(a5),a0 +.copy_message_loop: + cmpa.l a3,a1 + bhi.s .message_full + move.b (a0)+,d1 + move.b d1,(a1)+ + bne.s .copy_message_loop + clr.b (a1) + bra.s .display +.message_full: + subq.l #1,a1 + clr.b (a1) + clr.b 1(a1) +.display: + moveq #48,d1 + movea.l sys.IntuitionBase,a6 + lea .alert(pc),a0 + jsr -90(a6) + moveq #0,d0 + move.w 14(a5),d0 + jmp sys.exit + + even +.alert: + ds.b 158 +.alert_limit: + ds.b 2 +.alert_end: +.prefix: + dc.b "PROGRAM DIED code $",0 + ; !notreached! + }} + } } diff --git a/compiler/res/prog8lib/amiga1200/textio.p8 b/compiler/res/prog8lib/amiga1200/textio.p8 index 4f7e89723..44fe0f59d 100644 --- a/compiler/res/prog8lib/amiga1200/textio.p8 +++ b/compiler/res/prog8lib/amiga1200/textio.p8 @@ -52,50 +52,85 @@ txt { private long @shared fmt_value_l private uword @shared fmt_value_w + private asmsub vprintf(str format @D1, pointer argarray @D2) { + %asm {{ + move.l 4.w,a6 + cmp.w #36,20(a6) ; KS 2.0 = V36 + bcs.s .no_ks20 + move.l sys.DOSBase,a6 + jmp dos.VPrintf(a6) + +.no_ks20: + jmp p8b_txt.p8s_no_ks20_alert + }} + } + + private asmsub no_ks20_alert() { + ; VPrintf number formatting is unavailable before kickstart 2.0 + %asm {{ + move.l #$00070001,d0 + lea _msg(pc),a0 + moveq #40,d1 + move.l sys.IntuitionBase,a6 + jsr -90(a6) ; DisplayAlert + moveq #1,d0 + jmp sys.exit + + even +_msg: + dc.w 20 + dc.b 16 + dc.b "textio/conv number format routines require Kickstart 2.0+",0 + dc.b 0 + ; !notreached! + }} + } + + sub print_ub(ubyte value) { fmt_value_w = value as uword - void dos.VPrintf("%d", &fmt_value_w) + vprintf("%d", &fmt_value_w) } sub print_ub0(ubyte value) { fmt_value_w = value as uword - void dos.VPrintf("%03d", &fmt_value_w) + vprintf("%03d", &fmt_value_w) } sub print_b(byte value) { fmt_value_w = value as uword - void dos.VPrintf("%d", &fmt_value_w) + vprintf("%d", &fmt_value_w) } sub print_uw(uword value) { fmt_value_l = value as long - void dos.VPrintf("%lu", &fmt_value_l) + vprintf("%lu", &fmt_value_l) } sub print_uw0(uword value) { fmt_value_l = value as long - void dos.VPrintf("%05lu", &fmt_value_l) + vprintf("%05lu", &fmt_value_l) } sub print_w(word value) { - void dos.VPrintf("%d", &value) + vprintf("%d", &value) } sub print_l(long value) { - void dos.VPrintf("%ld", &value) + vprintf("%ld", &value) } sub print_ulhex(long value, bool prefix) { - void dos.VPrintf(if prefix then "$%08lx" else "%08lx", &value) + vprintf(if prefix then "$%08lx" else "%08lx", &value) } sub print_uwhex(uword value, bool prefix) { - void dos.VPrintf(if prefix then "$%04x" else "%04x", &value) + vprintf(if prefix then "$%04x" else "%04x", &value) } sub print_ubhex(ubyte value, bool prefix) { fmt_value_w = value as uword - void dos.VPrintf(if prefix then "$%02x" else "%02x", &fmt_value_w) + vprintf(if prefix then "$%02x" else "%02x", &fmt_value_w) } sub print_ubbin(ubyte value, bool prefix) { diff --git a/compiler/res/prog8lib/amiga500/prog8_lib.p8 b/compiler/res/prog8lib/amiga500/prog8_lib.p8 index 489ee22e5..399a97832 100644 --- a/compiler/res/prog8lib/amiga500/prog8_lib.p8 +++ b/compiler/res/prog8lib/amiga500/prog8_lib.p8 @@ -29,6 +29,7 @@ p8_sys_startup { ; } sub init_system() { + %option noframe %asm {{ ; save CLI arguments move.l d0,-(sp) @@ -92,6 +93,7 @@ proc_CLI = 172 } sub init_system_phase2() { + %option noframe %asm {{ moveq #0,d0 moveq #0,d1 @@ -113,6 +115,7 @@ proc_CLI = 172 } sub cleanup_at_exit() { + %option noframe %asm {{ movem.l d0,-(sp) ; keep return code diff --git a/compiler/res/prog8lib/amiga500/syslib.p8 b/compiler/res/prog8lib/amiga500/syslib.p8 index e12dea1a2..91c6d2ed3 100644 --- a/compiler/res/prog8lib/amiga500/syslib.p8 +++ b/compiler/res/prog8lib/amiga500/syslib.p8 @@ -46,6 +46,7 @@ sys { ^^ubyte @shared arguments ; CLI argument string (null-terminated), or NULL if Workbench launch sub reset_system() { + %option noframe %asm {{ move.l 4.w,a6 move.w 20(a6),d0 ; ExecBase version @@ -80,17 +81,17 @@ sys { }} } - sub exit(ubyte returnvalue) { + asmsub exit(word returnvalue @D0) { ; -- exit the program with a return code in D0. When invoked from Prog8 via sys.exit(), all active defers in the call chain are unwound program-wide (LIFO) before system cleanup. sys.reset_system() does not run defers. %asm {{ - moveq.l #0,d0 - move.b sys.exit.returnvalue,d0 + and.l #$ffff,d0 move.l p8_sys_startup.orig_stackpointer,sp jmp p8_sys_startup.cleanup_at_exit }} } sub set_carry() { + %option noframe %asm {{ ; set both C (comparison carry) and X (rotate carry) bits moveq #$11,d0 @@ -99,6 +100,7 @@ sys { } sub clear_carry() { + %option noframe %asm {{ ; clear C and X bits moveq #0,d0 @@ -153,7 +155,80 @@ sys { move.l 4.w,a6 move.w 296(a6),d0 andi.w #$008e,d0 + seq d0 + tst.b d0 rts }} } + + sub die(uword code, str message) { + %asm {{ + moveq #0,d0 + move.w 14(a5),d0 + lea .alert(pc),a1 + lea .alert_limit(pc),a3 + move.w #20,(a1)+ + move.b #16,(a1)+ + lea .prefix(pc),a2 +.copy_prefix: + move.b (a2)+,d1 + beq.s .copy_code + move.b d1,(a1)+ + bra.s .copy_prefix +.copy_code: + move.w 14(a5),d2 + moveq #3,d4 +.copy_code_digit: + move.w d2,d3 + lsr.w #8,d3 + lsr.w #4,d3 + andi.w #$000f,d3 + cmpi.w #10,d3 + bcs.s .copy_code_number + addi.w #'A'-10,d3 + bra.s .copy_code_store +.copy_code_number: + addi.w #'0',d3 +.copy_code_store: + move.b d3,(a1)+ + lsl.w #4,d2 + dbra d4,.copy_code_digit + clr.b (a1)+ + move.b #1,(a1)+ + move.w #20,(a1)+ + move.b #32,(a1)+ +.copy_message: + move.l 8(a5),a0 +.copy_message_loop: + cmpa.l a3,a1 + bhi.s .message_full + move.b (a0)+,d1 + move.b d1,(a1)+ + bne.s .copy_message_loop + clr.b (a1) + bra.s .display +.message_full: + subq.l #1,a1 + clr.b (a1) + clr.b 1(a1) +.display: + moveq #48,d1 + movea.l sys.IntuitionBase,a6 + lea .alert(pc),a0 + jsr -90(a6) + moveq #0,d0 + move.w 14(a5),d0 + jmp sys.exit + + even +.alert: + ds.b 158 +.alert_limit: + ds.b 2 +.alert_end: +.prefix: + dc.b "PROGRAM DIED code $",0 + ; !notreached! + }} + } } diff --git a/compiler/res/prog8lib/amiga500/textio.p8 b/compiler/res/prog8lib/amiga500/textio.p8 index b74854c45..74fecf15f 100644 --- a/compiler/res/prog8lib/amiga500/textio.p8 +++ b/compiler/res/prog8lib/amiga500/textio.p8 @@ -81,50 +81,84 @@ txt { private long @shared fmt_value_l private uword @shared fmt_value_w + private asmsub vprintf(str format @D1, pointer argarray @D2) { + %asm {{ + move.l 4.w,a6 + cmp.w #36,20(a6) ; KS 2.0 = V36 + bcs.s .no_ks20 + move.l sys.DOSBase,a6 + jmp dos.VPrintf(a6) + +.no_ks20: + jmp p8b_txt.p8s_no_ks20_alert + }} + } + + private asmsub no_ks20_alert() { + ; VPrintf number formatting is unavailable before kickstart 2.0 + %asm {{ + move.l #$00070001,d0 + lea _msg(pc),a0 + moveq #40,d1 + move.l sys.IntuitionBase,a6 + jsr -90(a6) ; DisplayAlert + moveq #1,d0 + jmp sys.exit + + even +_msg: + dc.w 20 + dc.b 16 + dc.b "textio/conv number format routines require Kickstart 2.0+",0 + dc.b 0 + ; !notreached! + }} + } + sub print_ub(ubyte value) { fmt_value_w = value as uword - void dos.VPrintf("%d", &fmt_value_w) + vprintf("%d", &fmt_value_w) } sub print_ub0(ubyte value) { fmt_value_w = value as uword - void dos.VPrintf("%03d", &fmt_value_w) + vprintf("%03d", &fmt_value_w) } sub print_b(byte value) { fmt_value_w = value as uword - void dos.VPrintf("%d", &fmt_value_w) + vprintf("%d", &fmt_value_w) } sub print_uw(uword value) { fmt_value_l = value as long - void dos.VPrintf("%lu", &fmt_value_l) + vprintf("%lu", &fmt_value_l) } sub print_uw0(uword value) { fmt_value_l = value as long - void dos.VPrintf("%05lu", &fmt_value_l) + vprintf("%05lu", &fmt_value_l) } sub print_w(word value) { - void dos.VPrintf("%d", &value) + vprintf("%d", &value) } sub print_l(long value) { - void dos.VPrintf("%ld", &value) + vprintf("%ld", &value) } sub print_ulhex(long value, bool prefix) { - void dos.VPrintf(if prefix then "$%08lx" else "%08lx", &value) + vprintf(if prefix then "$%08lx" else "%08lx", &value) } sub print_uwhex(uword value, bool prefix) { - void dos.VPrintf(if prefix then "$%04x" else "%04x", &value) + vprintf(if prefix then "$%04x" else "%04x", &value) } sub print_ubhex(ubyte value, bool prefix) { fmt_value_w = value as uword - void dos.VPrintf(if prefix then "$%02x" else "%02x", &fmt_value_w) + vprintf(if prefix then "$%02x" else "%02x", &fmt_value_w) } sub print_ubbin(ubyte value, bool prefix) { diff --git a/compiler/res/prog8lib/qemu68k/prog8_lib.p8 b/compiler/res/prog8lib/qemu68k/prog8_lib.p8 index b4cb823d9..726fc052f 100644 --- a/compiler/res/prog8lib/qemu68k/prog8_lib.p8 +++ b/compiler/res/prog8lib/qemu68k/prog8_lib.p8 @@ -27,6 +27,7 @@ p8_sys_startup { } sub init_system_phase2() { + %option noframe %asm {{ moveq #0,d0 moveq #0,d1 @@ -48,6 +49,7 @@ p8_sys_startup { } sub cleanup_at_exit() { + %option noframe %asm {{ movea.l #qemu.CTRL_REG_CMD,a1 move.l #qemu.CTRL_CMD_HALT,(a1) diff --git a/compiler/res/prog8lib/qemu68k/syslib.p8 b/compiler/res/prog8lib/qemu68k/syslib.p8 index ae787dba3..d9481447b 100644 --- a/compiler/res/prog8lib/qemu68k/syslib.p8 +++ b/compiler/res/prog8lib/qemu68k/syslib.p8 @@ -310,6 +310,7 @@ sys { sub reset_system() { + %option noframe %asm {{ movea.l #qemu.CTRL_REG_CMD,a1 move.l #qemu.CTRL_CMD_RESET,(a1) @@ -318,6 +319,7 @@ sys { } sub poweroff_system() { + %option noframe %asm {{ movea.l #qemu.CTRL_REG_CMD,a1 move.l #qemu.CTRL_CMD_HALT,(a1) @@ -325,7 +327,8 @@ sys { }} } - sub die(ubyte code, str message) { + sub die(uword code, str message) { + %option noframe ; -- kill the program: print death message to goldfish TTY, then exit. str @shared warning = iso:"\n\nPROGRAM DIED: " %asm {{ @@ -346,7 +349,7 @@ sys { moveq #10,d0 move.l d0,qemu.TTY_PUT_CHAR }} - exit(code) + exit(code as word) } sub wait(uword jiffies) { @@ -355,16 +358,19 @@ sys { } - sub exit(ubyte returnvalue) { + asmsub exit(word returnvalue @D0) { ; -- exit the program with a return code in D0. When invoked from Prog8 via sys.exit(), all active defers in the call chain are unwound program-wide (LIFO) before system cleanup. sys.reset_system()/poweroff_system() do not run defers. %asm {{ - moveq.l #0,d0 - move.b sys.exit.returnvalue,d0 + and.l #$ffff,d0 + movea.l #qemu.CTRL_REG_CMD,a1 + move.l #qemu.CTRL_CMD_HALT,(a1) + stop #0 + ; !notreached! }} - poweroff_system() } sub set_carry() { + %option noframe %asm {{ ; set both C (comparison carry) and X (rotate carry) bits moveq #$11,d0 @@ -373,6 +379,7 @@ sys { } sub clear_carry() { + %option noframe %asm {{ moveq #0,d0 move.w d0,ccr @@ -387,12 +394,14 @@ sys { } sub clear_irqd() { + %option noframe %asm {{ andi.w #$f8ff,sr ; privileged ! }} } sub set_irqd() { + %option noframe %asm {{ ori.w #$0700,sr ; privileged ! }} @@ -475,4 +484,11 @@ sys { rts }} } + + asmsub cpuAtLeast68020() -> bool @Pz { + %asm {{ + moveq #0,d0 ; qemu68k always targets a 68020 or newer + rts + }} + } } diff --git a/compiler/res/prog8lib/shared_m68k_coroutines.p8 b/compiler/res/prog8lib/shared_m68k_coroutines.p8 index f8983f633..cfe872808 100644 --- a/compiler/res/prog8lib/shared_m68k_coroutines.p8 +++ b/compiler/res/prog8lib/shared_m68k_coroutines.p8 @@ -65,6 +65,7 @@ coroutines { pointer tmp_term sub add(pointer taskaddress, pointer userdata) -> ubyte { + %option noframe ; find the next empty slot in the tasklist and stick it there ; returns the task id of the new task, or 255 if there was no space for more tasks. 0 is a valid task id! ; also returns the success in the Carry flag (carry set=success, carry clear = task was not added) diff --git a/compiler/res/prog8lib/shared_m68k_lineclip.p8 b/compiler/res/prog8lib/shared_m68k_lineclip.p8 index d8ba89b5e..743209cbd 100644 --- a/compiler/res/prog8lib/shared_m68k_lineclip.p8 +++ b/compiler/res/prog8lib/shared_m68k_lineclip.p8 @@ -97,6 +97,7 @@ lineclip { ; (int16_t)((int32_t)dist * num / den) ; Uses mulu.w (16x16→32) + divu.w (32/16→16) — works on all 68000-family CPUs sub frac(word dist, word num, word den) -> word { + %option noframe uword adist = abs(dist) as uword uword anum = abs(num) as uword uword aden = abs(den) as uword diff --git a/compiler/res/prog8lib/shared_m68k_math.p8 b/compiler/res/prog8lib/shared_m68k_math.p8 index 96f61768d..78c25ce36 100644 --- a/compiler/res/prog8lib/shared_m68k_math.p8 +++ b/compiler/res/prog8lib/shared_m68k_math.p8 @@ -173,6 +173,7 @@ math { ubyte b1 = $37 sub rndw() -> uword { + %option noframe %asm {{ addq.b #1,math.x1 move.b math.x1,d0 @@ -203,6 +204,7 @@ math { } sub rndseed(uword seed1, uword seed2) { + %option noframe %asm {{ move.b math.rndseed.seed1+1,math.x1 move.b math.rndseed.seed1,math.c1 @@ -217,6 +219,7 @@ math { } sub randrangew(uword n) -> uword { + %option noframe %asm {{ addq.b #1,math.x1 move.b math.x1,d0 diff --git a/compiler/src/prog8/compiler/astprocessing/DeferProcessor.kt b/compiler/src/prog8/compiler/astprocessing/DeferProcessor.kt index c8106519d..cc9ae79f8 100644 --- a/compiler/src/prog8/compiler/astprocessing/DeferProcessor.kt +++ b/compiler/src/prog8/compiler/astprocessing/DeferProcessor.kt @@ -288,7 +288,7 @@ internal object DeferProcessor { overflowIf.add(overflowCond) val overflowTrue = PtNodeGroup() val overflowExit = PtFunctionCall("sys.exit", false, false, emptyArray(), sub.position) - overflowExit.add(PtNumber(BaseDataType.UBYTE, 1.0, sub.position)) + overflowExit.add(PtNumber(spBase, 1.0, sub.position)) overflowTrue.add(overflowExit) overflowIf.add(overflowTrue) overflowIf.add(PtNodeGroup()) diff --git a/compiler/test/TestStackFrameLocals.kt b/compiler/test/TestStackFrameLocals.kt index b41464aaa..645e10ee1 100644 --- a/compiler/test/TestStackFrameLocals.kt +++ b/compiler/test/TestStackFrameLocals.kt @@ -713,6 +713,49 @@ main { rec.any { it.startsWith("move.b p8b_main.p8s_rec.p8v_x,") } shouldBe true } + test("inline assembly does not prevent framing: asm subroutine is framed by default") { + val src = """ +main { + sub leaf(ubyte a) -> ubyte { + %asm {{ + nop + }} + return a + 1 + } + sub start() { + g = leaf(3) + } + ubyte @shared g +} +""" + val (lines, _) = compile(src) + val leaf = subAssembly(lines, "p8b_main.p8s_leaf") + leaf.any { it.startsWith("link") } shouldBe true + leaf.any { it.contains("(a5)") } shouldBe true + } + + test("inline assembly subroutine with %option noframe keeps the static convention") { + val src = """ +main { + sub leaf(ubyte a) -> ubyte { + %option noframe + %asm {{ + nop + }} + return a + 1 + } + sub start() { + g = leaf(3) + } + ubyte @shared g +} +""" + val (lines, _) = compile(src) + val leaf = subAssembly(lines, "p8b_main.p8s_leaf") + leaf.any { it.startsWith("link") } shouldBe false + leaf.any { it.contains("p8b_main.p8s_leaf.p8v_a") } shouldBe true + } + test("slice 5: static-initialized local is framed and initialized at entry") { val src = """ main { diff --git a/docs/source/libraries.rst b/docs/source/libraries.rst index e33506c02..56cd584d1 100644 --- a/docs/source/libraries.rst +++ b/docs/source/libraries.rst @@ -1881,13 +1881,27 @@ miscellaneous Returns true if the CPU in the computer is a 65816, false otherwise (6502 cpu). Note that Prog8 itself has no support yet for this CPU other than detecting its presence. +``cpuAtLeast68020()`` (m68k targets) + Returns true if the machine's 680x0 CPU is a 68020 or newer (68020/68030/68040/68060), + false if it is a plain 68000. + ``disable_caseswitch()`` and ``enable_caseswitch()`` Disable or enable the ability to switch character set case using a keyboard combination. +``exec_version ()`` (m68k targets) + Returns the version number of ``exec.library`` as a uword, which on the Amiga targets is the + Kickstart/ROM version (for example 34 = Kickstart 1.3, 39 = Kickstart 3.0, 40 = Kickstart 3.1). + ``exit (returncode)`` Immediately stops the program and exits it, with the returncode in the A register. + Any active ``defer`` handlers are unwound first on all compiler targets. + On the m68k targets (amiga500, amiga1200, qemu68k) the statuscode is a word passed in the D0 register. Note: custom interrupt handlers remain active unless manually cleared first! +``die (code, message)`` + On m68k targets, displays an Intuition alert containing the code (uword) and message string, then + terminates the program. On 6502 targets, breaks into the debugger or monitor with the code in A and the message pointer in X/Y. + ``exit2 (resultA, resultX, resultY)`` Immediately stops the program and exits it, with the result values in the A, X and Y registers. Note: custom interrupt handlers remain active unless manually cleared first! @@ -2028,6 +2042,10 @@ There is a very specialized function in the sys module here as well: which means the next RTS instruction will jump to that address instead. You cannot use pushw() for this because the bytes pushed by JSR are different +``stack_size ()`` (m68k targets: amiga500, amiga1200, qemu68k) + Returns the amount of CPU stack space still available, in bytes. + Useful to check how much headroom remains before deep recursion or a large local frame could overflow the stack. + textio (txt.*) diff --git a/docs/source/programming.rst b/docs/source/programming.rst index c44217cea..ea8afd233 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -531,6 +531,8 @@ Directives state shared between its overlapping runs. Only use this if you know what you're doing: the programmer is responsible for guaranteeing that no two live runs observe each other's data, and that the caller's frame pointer is intact when the subroutine returns. + A subroutine that contains inline assembly is framed like any other; if that assembly + manipulates the machine stack or the frame pointer itself, mark the subroutine ``noframe``. .. index:: pair: Directives; %output diff --git a/examples/test.p8 b/examples/test.p8 index b7f6ecaf3..76e5e4f88 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,49 +1,8 @@ %import textio -%import coroutines -%zeropage basicsafe - main { sub start() { - void coroutines.add(task1, 0) - void coroutines.add(task2, 0) - void coroutines.add(task3, 0) - txt.print("starting 3 tasks...\n") - coroutines.run(supervisor) - txt.print("\ndone.\n") - } - - ubyte @shared count - - sub supervisor() -> bool { - count++ - if count==200 { - coroutines.killall() - return false - } - return true - } - - sub task1() { - ;%option noframe - repeat { - txt.chrout('a') - void coroutines.yield() - } - } - - sub task2() { - %option noframe - repeat { - txt.chrout('b') - void coroutines.yield() - } - } - - sub task3() { - %option noframe - repeat { - txt.chrout('c') - void coroutines.yield() - } + sys.die($99ea, "ik ga dood") + txt.print_ub(123) + txt.print("\nderp\n") } }