c64 程序员 如何用最少的字节编写 C64 可执行文件

如何写一个尽可能少字节的C64可执行文件?本文作者通过一场比赛详细解读了最佳练习技巧。来拿吧!

条目通过推特回复和私信发布,只包含PRG文件的字节长度和PRG文件的MD5哈希值。
以下是一些参赛者和他们作品的链接:
【c64 程序员 如何用最少的字节编写 C64 可执行文件】菲利普·赫伦
盖尔·斯特劳姆
佩特里·赫克宁
Mathlev Raxenblatz
扬·阿切里尼乌斯
杰米·富勒
大卫·格什曼
简·赫尔斯坦
本文的其余部分将集中讨论竞赛作品中使用的一些组装技术。
基础知识
C64的默认图形模式是40x25字符模式。ram中的fRAMe缓冲区分为两个数组:
0400美元
800美元
要设置字符,您需要在屏幕内存中以00存储一个字节。颜色RAM默认初始化为浅蓝色,正好是线条的颜色,这意味着我们可以忽略颜色RAM。
边框颜色和背景颜色可以通过I/O寄存器控制,映射到内存中的$d020和$d021。
画两条线很简单,因为斜率是固定的,可以直接硬编码。下面是c语言的实现,可以画两条线并显示在stdout上:
上图中使用的字符分别为和$a0。运行后,您应该能够看到以下由两行组成的ASCII图形:
用6502汇编和汇编伪代码,很容易转换成汇编语言:
由这个带下划线的代码获得的PRG文件是286字节。
在讨论优化之前,让我们观察几点:
首先,用ROM例程在C64上运行代码。ROM中有很多例程,可能对我们的小程序有帮助。例如,只需编写JSR $E544来清除屏幕。
其次,在像6502这样的8位CPU上计算地址会非常麻烦,并且会消耗很多字节。CPU中没有乘法器,所以计算y*40+i这样的公式通常需要进行一系列的逻辑移位操作,或者使用查找表,同样会占用很多字节。为了避免乘以40,我们可以逐步增加屏幕指针:
这里,每次我们把直线的斜率加到定点计数器yf上,每次8位累加器设置进位标志时,我们再加40。
下面是通过汇编实现的积累方法:
总共82字节,还是有点大。有几个非常明显的字节是由16位地址计算引起的:
为间接索引寻址设置screenptr值:
在screenptr中添加40,然后继续下一行:
当然,这个代码可以更小,但是如果一开始不能使用16位寻址呢?让我们看看是否可以避免16位寻址。
技巧一:滚动!
我们不画整个屏幕RAM,只画最后的Y=24行,然后通过JSR $E8EA调用“向上滚动”ROM函数将整个屏幕向上滚动!
x周期变成这样:
使用此技术后,线渲染过程如下:

这里使用了两种方法:
堆叠技能;
基本热重置向量技能。
堆叠技术
诀窍是用指向所需入口点的值填充F8处的CPU堆栈。具体来说,创建一个PRG,从16位指针开始,指向我们的代码,然后将PRG加载到F8:
在BASIC LOADer被加载并通过RTS指令返回给调用者后,它不会返回到加载被调用的地方,而是直接返回到我们的PRG。
基本热重置向量技能
通过直接阅读PRG反汇编,这个技巧似乎更容易理解:
注意最后一行。JMP指令从地址01开始,跳转目标地址存储在地址02-03。
与BASIC启动相关的其他技能
Petri发现了另一种BASIC启动技术,它可以将自己的常数注入零页。在这种方法中,您需要手动编写令牌BASIC启动序列,然后将您的常量写入BASIC程序的行号中。BASIC行号在启动时存储在地址-A。非常聪明!
技巧5:非常规控制流程
以下是X循环的简化版本,只画一条线,画完一条线就停止执行:
但是这段代码有一个错误。绘制线条上的最后一个像素时,不应再次滚动。所以我们需要写更多的分支来跳过绘制最后一个像素时的滚动:

推荐阅读