Mac OS 9上的Rose-8

2020-05-25 15:36:06

我今年的愚人节项目已经过去近两个月了(不是恶作剧!)。在那里我设法让一个SWIFT程序在MacOS9上运行。我仍然为那里的技术成就和博客文章感到自豪。但是当我完成它的时候,…。我不想停下来。

我有几件事想做。首先,所有这些中的一小部分是有用的:为了达到我最低限度可行的C-互操作产品,我砍掉了相当多的SWIFT标准库。对于那些想要在受限环境(如嵌入式代码)中编写SWIFT的人来说,这仍然很有用,我被鼓励在那里写下我的发现。(并计划这样做。)。此外,虽然BitPaint是一个很好的概念验证工具,但它实际上并没有使用那么多Swift。它真的只是C互操作。那么,我该怎么做才能真正让人感觉像是SWIFT项目呢?

那么,我今年的另一个无用的项目,ROSE-8和Color的游戏怎么样?我设计的一个虚构的游戏系统,在我学会编程的操作系统上运行?完美无缺。

Rose-8实际上没有使用太多复杂的SWIFT功能…。但当然,这取决于标准库,特别是SWIFT的普通老式可调整大小的写入时复制阵列(Copy-on-Write Array)。处理数组意味着正确处理。

所有这些都需要实际的运行时支持。对于普通的SWIFT,运行时是用C++编写的,并链接到标准库。虽然我已经让Clang为MacOS9编写了代码,但C++有自己的标准库,我不想让它在MacOS9中运行。(当然,Mac OS 9上提供的C++标准库太旧,无法支持SWIFT运行时所需的许多功能。)。因此,无论如何,我将不得不从头开始实现一些东西,而不是像我为标准库所做的那样,仅仅从真实版本复制。

但是等等,为什么SWIFT运行时是用C++实现的呢?为什么不用SWIFT写呢?

在SWIFT出现的早期就需要运行时,所以它最早的部分不可能是用SWIFT编写的。这在这里是不适用的。

运行库通常需要能够访问平台功能,但是当您执行诸如导入Darwin(或者在本例中是导入MacTypes)之类的操作时,SWIFT有“覆盖”库来增强通常的平台功能。因此,在运行时(通常与stdlib链接)和平台覆盖之间存在循环依赖。但是,如果我总是使用静态链接,链接器可以处理这些循环依赖关系。2个

C++仍然可以做一些SWIFT不能做的事情,最重要的是用特定的名称声明全局变量,并用编译时常量值声明复杂的全局变量。SWIFT编译器希望能够直接引用某些内容,例如基本整数类型的类型元数据。这对我来说仍然是个问题,但也许我可以将我的C++使用限制在这一点上。

我以前避免了对运行时的任何依赖,但这对我来说可能是一次很好的学习经历。即使我在苹果公司从事Swift的工作时,我也主要致力于编译器面向用户的部分,只对运行库和标准库进行了几次短暂的访问。因此,我决定继续使用Swift编写的运行时(主要是)。它可能不是最快或最漂亮的,当然也不会支持真正的运行时所做的一切,但我可以让它工作。对吗?

和上次一样,我决定从一个更简单的目标开始:获得足够的运行时工作来支持BitPaint的单机版。正如我上面所说的,从SWIFT的角度来看,BitPaint实际上并不是那么复杂,因为它主要是推送整数和指针。启用优化后,它内联了所需的所有内容,甚至不需要链接到构建的stdlib。然而,SWIFT中相当多的整数和指针操作是通用实现的,依靠编译器将它们优化为机器提供的操作!所以我马上就得处理一堆泛型模型。

旁白:这篇帖子不是关于SWIFT运行时的讨论--真正的SWIFT运行时或我制作的小运行时。我一直被鼓励在未来也就这一点写一篇帖子,但目前我将坚持讲述我是如何让Color应用程序运行Mac OS“9游戏”的。

我的方法基本上是“一对一编译BitPaint,尝试链接,看看缺少哪些运行时功能”。刚开始的时候是相当多的!3。

#未解析的外部参照:#$sBi16_WV#$sBi32_WV#$sBi64_WV#$sBi8_WV#$sytN#$sytWV#$syXlN#$syycWV#.swft_allocateGenericValueMetadata#.swft_allocObject#.swft_checkMetadataState#.swft_DeallocObject#.swft_getAssociatedConformanceWitanceWitft。.SWIFT_RELEASE#.SWIFT_RETAIN#.SWIFT_SLOW分配#.SWIFT_SLOW取消分配#.truncf#.__divdi3#.__fixdfdi#.__fixunsdfdi#.__fixunssfdi#.__floatdidf#.__floatdisf#.__moddi3#.__mulodi4#.__udivdi3#._。

一些当时的PowerPC中没有实现的低级数值运算。

我最终不得不以不同的方式处理这些问题:

这是我从实际运行时借用最多的一个部分。它是用C++实现的,但对于我回调到SWIFT中的任何复杂代码。

这是大部分工作。运行库所做的很多工作是管理泛型元数据,如果不能优化使用,则必须为每组泛型参数分配和填充这些元数据。也很容易弄错。

如果您不支持无主引用和弱引用,那么对象分配实际上非常简单!但是等等,为什么我需要它来BitPaint,它只做数字和指针呢?原来,SWIFT使用幕后的对象来实现为闭包而存储的Existentials和Capture,此外,我无论如何都需要它们来存储Array。(此外,通过这种方式,我的SWIFT可以使用ARC来管理CF对象。)。

大多数这些带下划线的数值操作都来自LLVM本身。LLVM知道并不是每个平台都实现了所有这些操作,所以它提供了编译器-RT项目来在软件中实现它们。没有太多麻烦,我就能够获得为PPC32构建所需的编译器-RT“内置”。

truncf是一个例外,因为它通常是C标准库的一部分,但MacOS9实际上并没有提供它。为什么?可能是因为C无论如何都会在浮点数和双精度数之间进行转换,如果您往返通过双精度数,操作将会有相同的结果。但是LLVM希望它在那里,试图欺骗它调用双精度中继是不起作用的,所以最后我只是绕道自己实现了它,作为IEEE754格式的更新者。

…。是个挑战。在没有真正的字符串格式化的情况下,我很好地利用了我在Twitter上发布的一个小转储实用程序:

with UnsafePointer(to:v){let p=UnsafeRawPointer($0)var i=0 While I<;MemoryLayout.size(ofValue:v){let nextByte=(p+i).load(as:UInt8.self)putchar(exToASCII(nextByte>;>;4))putchar(heexToASCII(nextByte&;0。

-乔丹·罗斯(@UINT_MIN)2020年5月13日

但这也就到此为止了。当指针指向错误的对象时,或者当某些内存未初始化时,或者当所有内容都从正确地址偏移4时,就没有那么大的帮助了。我的调试技巧从Placemarker调用到Put(“我们走到这一步了吗?”),到试图编译仍然会以同样的方式崩溃的最小程序(我称之为“游乐场”应用程序),到提前退出,甚至故意破坏一些东西,看看它们是否仍然崩溃。这些都是相当标准的调试技术,但是缺少最强大的技术:直接检查内存和单步执行指令,直到发现崩溃或错误行为。在MacOS9上没有回溯和实时调试,至少没有我没有的更专业的工具!4。

最神秘的问题是BitPaint似乎可以工作,但在用户交互几秒钟后就崩溃了。发生了什么事?想猜猜吗?

我花了几天的时间才想到内存耗尽;更糟糕的是,这是在我从朋友那里跳出想法时已经被建议之后。(您知道在MacOS9中,应用程序必须预先指定它将使用的最大内存量吗?)。公平地说,导致问题的代码不应该分配任何内存,并且运行库中唯一分配的元数据是在实例化新泛型类型时分配的。为什么缓存不工作?

事实证明这是一个编译器错误,不过幸运的是,在任何Swift发行版本中都没有出现这个错误。5症状是具有恒定初始值设定项的全局变量被认为永远不会改变,因此每次调用都会从头开始为泛型元数据分配缓存,为空。修复结果是提取编译器的最新更新,并再次合并我的更改。就这样。

最近几天调试:`a!=a`https://bugs.swift.org/browse/SR-12829==对于两个可选选项使用元组https://bugs.swift.org/browse/SR-12829 https://developer.apple.com/documentation/corefoundation/1542375-cfdatasetlength运行时认为元组是其大小的一半它应该是使用相同元数据的所有元组→缓存键是CFData→所有缓存键都是长度为0的→忘记CFData。

-乔丹·罗斯(@UINT_MIN)2020年5月19日。

无论如何,通过大量的试验和错误,“心灵调试”,并重新阅读我猜测的导致问题的代码,我最终得到了一个可以与未经优化的BitPaint一起工作的运行时。不久之后,也有了Array,然后是游戏“by Color”。

总体而言,我花了一个月左右的时间做最初的项目,也许花了一个半月做这个部分。回过头来看,我应该预料到的是:最初的项目主要是将工作正常的部分连接起来,而这个项目是在移植一个中等复杂的项目(Swift运行时)的一大部分,而不能使用测试或调试器。但我从中得到了一些东西:

在Mac OS 9上运行的Color游戏的一个版本。(这是我一直在测试的游戏。)。

在Mac OS 9上运行的SWIFT运行时(部分)和更多标准库的实施。您可以在ppc-swft-project存储库中检出这一点。(彩色来源的游戏也可以在那里买到。)。

SWIFT标准库的功能标志保护版本,我计划在实际的SWIFT论坛上进行讨论,以防嵌入式开发人员感兴趣。

更好地理解SWIFT运行时,我希望在以后的博客文章中介绍这一点。

最后,我将用一张Color在真正的PowerPC Mac上运行的游戏的照片结束,这也是由我的朋友纳丁提供的:

循环依赖通常不只是一个链接问题,而是一个概念性问题:如果发生变化,将重新构建什么?所有东西都重建了吗?是不是所有的东西都会被重建,导致所有的东西永远在一个循环中建造?

无论如何,在这种情况下,如果将接口和实现分开,即使我们对所有事情都使用动态链接,也没有真正的循环依赖:

此逻辑之所以有效,是因为运行库经过优化,并且不依赖于标准库或覆盖本身的任何链接时依赖项。如果没有它,它仍然可以工作,但它不会完全干净。在实践中,运行时总是静态链接到stdlib,即使stdlib是dylib,因此唯一需要严格优化的是覆盖使用。

这是否意味着在实际运行时这样做是值得的呢?可能不会,至少不会马上。实际的运行时要复杂得多,它使用了许多C++特性,并且它的许多逻辑都与调试器共享。尝试在C++和SWIFT之间共享逻辑将是一件痛苦的事情,因此在SWIFT中也很难只编写新代码。而理清构建依赖关系将是一件痛苦的事情。但也许有一天,把一切都转换过来会是值得的。-↩︎。

这甚至不是全部,因为我#如果在标准库中删除了一些我在看到它们有运行时依赖项时就不需要的东西。“↩︎。

苹果公司发布了一个标准的调试器,名为MacBug,但我无法让它在我的仿真器中运行。--↩︎。

我在MASTER-NEXT分支工作,因为我需要ibm在AIX上的最新工作;在下一次LLVM重新分支之后,我将把这个项目绑定到SWIFT5.3或5.4或其他任何位置,并将其留在那里。-↩︎