计算机体系结构

1. 入门

2. 多内核

在一个 SMP 系统上跑多个内核。时不常会有人想做这件事情,我猜最后大家觉得虚拟化足够了就不想折腾了。

3. Bootstrapping

4. 编程语言设计

https://blog.fogus.me/2023/06/02/languages-zoo.html 这个人尝试实现了各种各样特性的语言,也给了相当充实的引用。

4.1. 自举

以前我觉得自举很重要,因为它看上去很完美。但自举显著增加了初始化过程的复杂性。所以如何才算值得呢?

  • Zig 的 自举设计,用一个特化的 wasm 解释器 + zig 编译器自举到 wasm 架构实现

5. 编译原理和框架

5.3. Virtual Machines

案例

5.3.2. 小虚拟机

https://100r.co/site/uxn.html Perma-computing 的想法非常有意思

https://forum.malleable.systems/t/small-virtual-machines/148 如果一个虚拟机足够 小,它是不是更适合用来 bootstrap?更容易理解?组合简单的规则,最终呈现出复杂的系 统,给人一种非常精巧的感觉。

5.3.3. Pointer tagging

https://coredumped.dev/2024/09/09/what-is-the-best-pointer-tagging-method/

结论:这些方案的性能没有太本质的差异,相比较下 cache 命中率要重要得多。文章最后提到还有很多 tagging 的方案和用法,并给了参考文献。

6. OS

7. Coroutines

用 Duff's Device + static variable 实现 Coroutine,用一个隐藏的状态量做 switch, 用行号作为 case 行对应的状态值,每次重入时跳转到上次执行后面的一个 case。需要手动把重入时需要 恢复的变量标记为 static。由此看出 Duff's Device 是把 switch case 当成动态的 goto 使用。

相关: computed goto 可以提升这里的性能:增加 branch prediction point,绕开 switch case 的 boundary检查。

阿里巴巴的 PhotonLibOs 对 stackful coroutine 做了听上去非常合理的优化:减少切换 时要存的寄存器,让编译器在 caller 找出全集,而不是让 callee 真的把所有可能用到的 寄存器存下来; (没看懂的)随机化了每个 coroutine 的栈内存起始地址,防止 cacheline false-sharing;在每个 coroutine yield 的时候做 jumping,有点像 computed goto,使得每个 coroutine 的切换有自己的分支预测点,帮助硬件预测;(没看 懂的)更优的 generator,好像可以减少返回时的 jump 还是什么;用 madvice 做 memory pooling,实现惰性的 coroutine 栈内存分配。

stackful/stackless 并不是唯一的分类标准。比如,usched 是在遇到阻塞的情况下,会把 阻塞协程的栈复制到堆,然后把下一个协程的栈从堆复制到当前的调用栈继续运行。在不经 常阻塞的情况下,因为上下文切换和函数调用相同,所以代价很小。据作者说,微软的 Midori 项目也是类似的做法。

8. Tail Call Optimization

我一直粗浅地理解为,调用函数在发现自己最后一条指令是 return + funcall 的情况下, 需要在调用其它函数前,清理掉自己的调用栈(frame?),再执行 funcall 就行。SICP 的视频里 Abelson 教授似乎说这件事情比很多人想的简单,但是我忘记到底有多简单了。 看下面的讨论,实际的情况不一定有那么简单。

https://ekaitz.elenq.tech/call-me-maybe.html 有很清楚简单的介绍,让我学习到 TCO 原来会导致丢失 stack trace,以及 lua 文档里提到因为 tail call 与 goto 等价,所以 很适合用来实现状态机(上面提到的 computed goto 也是相关的)。他引用了 lua、scala、 clojure 和 emacs lisp 对 TCO 的处理方式,也引用了 python 不做 TCO 的原因(我理解 最主要是不想丢失 stack trace,以及 Guido 不喜欢纯粹的 functional style)。

有各种方法可以缓解丢 stack trace 的问题,比如 MIT Scheme 就是 额外 用个大小可调的 ring buffer 来存。还有 这里 提到可以每个调用都生成 stack trace然后让 gc 去回收不用的 stack trace(我没理解如何组织 stack trace 让它能被找到的同时还能被 gc)。

https://eklitzke.org/how-tail-call-optimization-works 展示了关闭/开启 TCO 后的 tail call/tail recursion 的 x64 反汇编。

Guy Steele Lee 提到 Object-oriented 语言为了完美的封装,必须要支持 TCO。他举的例 子我没有太看懂,好像是为了遍历一个无限集合,必须由 set 们逐个调用下一个 set 的遍 历函数,假如这个遍历函数实现在 set 外面,会泄漏封装。

9. Continuations


Updated: 2026-02-27 Fri 02:43