Skip to content

Commit 416e2fb

Browse files
authored
build: detect page overflows (#132)
Issue a build-time warning when generated code overflows into another page. Signed-off-by: Aleksey Gurtovoy <aleks@wildbitscomputing.com>
1 parent 9bdd763 commit 416e2fb

10 files changed

Lines changed: 222 additions & 122 deletions

File tree

.vscode/settings.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"cSpell.words": [
3+
"endlogical"
4+
]
5+
}

modules/_scripts/makebuild.py

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,24 @@
1111

1212
import os
1313
import re
14-
import sys
1514
from pathlib import Path
1615
from typing import TextIO
1716

1817
# regex for exported labels
1918
export_re = re.compile(r"^\s*Export_(\w+)\s*:\s*(;.*)?$")
2019

2120

21+
PAGE2_BEGIN = """
22+
.section page2
23+
.logical * + $2000
24+
"""
25+
26+
PAGE2_END = """
27+
.endlogical
28+
.send page2
29+
"""
30+
31+
2232
def main(*, module_name: str, build_dir: Path, page: int = 1) -> None:
2333
"""Generate a single assembly build file for the specified module."""
2434
if not build_dir.exists():
@@ -59,26 +69,28 @@ def process_source(path: Path, out: TextIO, *, page: int = 1) -> list[str]:
5969
if page == 2:
6070
normalized = " ".join(line.split())
6171
if normalized == ".section code":
62-
out.write("\t\t.section page2\n")
63-
out.write("\t\t.logical * + $2000\n")
72+
out.write(PAGE2_BEGIN)
6473
continue
6574
elif normalized == ".send code":
66-
out.write("\t\t.here\n")
67-
out.write("\t\t.send page2\n")
75+
out.write(PAGE2_END)
6876
continue
6977

7078
out.write(f"{line.rstrip()}\n")
7179

7280
return exports
7381

7482

75-
def dump_exports(build_dir: Path, module_name: str, exports: list[str], *, page: int = 1) -> None:
83+
def dump_exports(
84+
build_dir: Path, module_name: str, exports: list[str], *, page: int = 1
85+
) -> None:
7686
"""Save module exports to the corresponding `.exports` file."""
7787
if not exports:
7888
return
7989

8090
suffix = "_p2" if page == 2 else ""
81-
with open(build_dir / (module_name + suffix + ".exports"), "w", encoding="utf-8") as out:
91+
with open(
92+
build_dir / (module_name + suffix + ".exports"), "w", encoding="utf-8"
93+
) as out:
8294
for export in exports:
8395
out.write(f"{export}\n")
8496

@@ -109,8 +121,12 @@ def collect_sources(module_name: str) -> list[Path]:
109121

110122
parser = argparse.ArgumentParser(description="Build module assembly file")
111123
parser.add_argument("module_name", help="Name of the module to build")
112-
parser.add_argument("build_dir", nargs="?", default=".build", help="Build output directory")
113-
parser.add_argument("--page", type=int, default=1, choices=[1, 2], help="Module page (1 or 2)")
124+
parser.add_argument(
125+
"build_dir", nargs="?", default=".build", help="Build output directory"
126+
)
127+
parser.add_argument(
128+
"--page", type=int, default=1, choices=[1, 2], help="Module page (1 or 2)"
129+
)
114130
args = parser.parse_args()
115131

116132
main(

modules/_scripts/makeexport.py

Lines changed: 27 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -22,48 +22,7 @@ def main(*, build_dir: Path) -> None:
2222
print(f"PagingEnabled = {1 if paging else 0}")
2323

2424
has_page2 = any(exports[m]["page"] == 2 for m in exports)
25-
26-
if paging and has_page2:
27-
# These routines are included inside an existing .section code block
28-
# in 00start.asm, so no .section/.send wrappers are needed here.
29-
# Slot3ModulePage/Slot3Depth/Slot3Saved are declared in 04data.inc
30-
# (placed before numberBuffer/decimalBuffer to survive buffer overflows).
31-
print("")
32-
print("; --- Slot 3 module bank switching ---")
33-
print("")
34-
print("Slot3Init:")
35-
print("\tstz Slot3Depth")
36-
print("\tlda 8+4")
37-
print("\tclc")
38-
print("\tadc #3")
39-
print("\tsta Slot3ModulePage")
40-
print("\trts")
41-
print("")
42-
print("Slot3BankIn:")
43-
print("\tphy")
44-
print("\tpha")
45-
print("\tinc Slot3Depth")
46-
print("\tlda Slot3Depth")
47-
print("\tcmp #1")
48-
print("\tbne +")
49-
print("\tldy 8+3")
50-
print("\tsty Slot3Saved")
51-
print("\tldy Slot3ModulePage")
52-
print("\tsty 8+3")
53-
print("+\tpla")
54-
print("\tply")
55-
print("\trts")
56-
print("")
57-
print("Slot3BankOut:")
58-
print("\tpha")
59-
print("\tphy")
60-
print("\tdec Slot3Depth")
61-
print("\tbne +")
62-
print("\tldy Slot3Saved")
63-
print("\tsty 8+3")
64-
print("+\tply")
65-
print("\tpla")
66-
print("\trts")
25+
print(f"HasPage2 = {1 if has_page2 else 0}")
6726

6827
for module in exports:
6928
page = exports[module]["page"]
@@ -74,25 +33,39 @@ def main(*, build_dir: Path) -> None:
7433
print(f"{routine}:")
7534
if paging:
7635
if page == 1:
77-
print("\tinc 8+5")
78-
print(f"\tjsr\tExport_{routine}")
79-
print("\tphp")
80-
print("\tdec 8+5")
81-
print("\tplp")
82-
print("\trts")
36+
print(page1_thunk(routine))
8337
elif page == 2:
84-
print("\tjsr Slot3BankIn")
85-
print(f"\tjsr\tExport_{routine}")
86-
print("\tphp")
87-
print("\tjsr Slot3BankOut")
88-
print("\tplp")
89-
print("\trts")
38+
print(page2_thunk(routine))
39+
else:
40+
raise RuntimeError(f"Unknown page #{page}")
9041
else:
9142
print(f"\tjmp\tExport_{routine}")
9243

9344
print("\t.endif")
9445

9546

47+
def page1_thunk(routine: str) -> str:
48+
return f"""
49+
inc 8+5
50+
jsr Export_{routine}
51+
php
52+
dec 8+5
53+
plp
54+
rts
55+
"""
56+
57+
58+
def page2_thunk(routine: str) -> str:
59+
return f"""
60+
jsr Slot3BankIn
61+
jsr Export_{routine}
62+
php
63+
jsr Slot3BankOut
64+
plp
65+
rts
66+
"""
67+
68+
9669
def read_exports(build_dir: Path) -> dict[str, dict]:
9770
"""Read all `.exports` files from the build directory.
9871

source/_basic.asm

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,16 +159,25 @@
159159

160160

161161
.section code
162+
; issuing a warning instead of an error so one can examine output listing for details
163+
.cwarn * > $C000, "BROKEN BUILD: non-paged code overflows into kernel-reserved I/O space ($C000-$DFFF) by ", * - $C000," bytes"
164+
162165
StartModuleCode:
163-
.if PagingEnabled==1
164-
* = $A000
165-
.offs $2000
166-
.endif
166+
.if PagingEnabled==1
167+
* = $A000
168+
.offs $2000
169+
.endif
167170
.send code
171+
168172
.include "../modules/.build/hardware.module.asm"
169173
.include "../modules/.build/tokeniser.module.asm"
170174
.include "../modules/.build/graphics.module.asm"
171175

176+
.section code
177+
; issuing a warning instead of an error so one can examine output listing for details
178+
.cwarn * > $C000, "BROKEN BUILD: page 1 code overflows into kernel-reserved I/O space ($C000-$DFFF) by ", * - $C000," bytes"
179+
.send code
180+
172181
; --- Startup banner data in boot section ($6000-$7FFF) ---
173182
.include "../modules/hardware/startup/.build/banner.dat"
174183

@@ -180,3 +189,10 @@ StartModuleCode:
180189
.include "./common/generated/_errortext_p2.asm"
181190
.include "../modules/.build/kernel_p2.module.asm"
182191
.include "../modules/.build/sound_p2.module.asm"
192+
193+
.section page2
194+
.logical * + $2000
195+
; issuing a warning instead of an error so one can examine output listing for details
196+
.cwarn * > $8000, "BROKEN BUILD: page 2 code overflows into SuperBASIC ROM space ($8000-$BFFF) by ", * - $8000," bytes"
197+
.endlogical
198+
.send page2

source/common/aa.system/00start.asm

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ KernelHeader:
3333
;;
3434
Boot: jmp Start
3535
.include "../../../modules/.build/_exports.module.asm"
36+
.include "./_slot3banking.asm"
3637

3738
Start: ldx #$FF ; stack reset
3839
txs
@@ -45,11 +46,12 @@ Start: ldx #$FF ; stack reset
4546

4647
jsr EXTInitialize ; hardware initialization
4748
jsr EXTShowStartupBanner ; reads banner data from slot 3
48-
jsr DisplayBannerText ; print hardware, ROM & build info
4949

5050
lda #3 ; remap slot 3 to RAM page 3
5151
sta $0008+3 ; (frees $6000-$7FFF for arrays/temp buffers)
5252

53+
jsr DisplayBannerText ; print hardware, ROM & build info
54+
5355
lda 0 ; turn on editing of MMU LUT
5456
ora #$80
5557
sta 0
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
;;
2+
; Slot 3 module bank switching
3+
;
4+
; Note that slot 3 (page 2) banking is about 4x slower than slot 5 (page 1),
5+
; ~29 cycles vs ~112 cycles per thunk.
6+
;
7+
; `Slot3ModulePage`, `Slot3Depth`, and `Slot3Saved` are declared in
8+
; `04data.inc` (placed before `numberBuffer`/`decimalBuffer` to survive
9+
; buffer overflows).
10+
;;
11+
12+
.if PagingEnabled==1 && HasPage2==1
13+
14+
Slot3Init:
15+
stz Slot3Depth
16+
lda 8+4
17+
clc
18+
adc #3
19+
sta Slot3ModulePage
20+
rts
21+
22+
Slot3BankIn:
23+
phy
24+
pha
25+
inc Slot3Depth
26+
lda Slot3Depth
27+
cmp #1
28+
bne +
29+
ldy 8+3
30+
sty Slot3Saved
31+
ldy Slot3ModulePage
32+
sty 8+3
33+
+ pla
34+
ply
35+
rts
36+
37+
Slot3BankOut:
38+
pha
39+
phy
40+
dec Slot3Depth
41+
bne +
42+
ldy Slot3Saved
43+
sty 8+3
44+
+ ply
45+
pla
46+
rts
47+
48+
.endif

source/common/generated/_errortext.asm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
;
22
; This is automatically generated.
33
;
4+
45
.section code
56
ErrorText:
67
.text "Break",0

source/common/generated/_errortext_p2.asm

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
;
22
; This is automatically generated.
33
;
4-
.section page2
5-
.logical * + $2000
4+
5+
.section page2
6+
.logical * + $2000
67
ErrorText:
78
.text "Break",0
89
.text "Syntax error",0
@@ -35,5 +36,5 @@ ErrorText:
3536
.text "Too many parameters",0
3637
.text "Formula too complex",0
3738
.text "Initialization error",0
38-
.here
39-
.send page2
39+
.endlogical
40+
.send page2

0 commit comments

Comments
 (0)