网站由一个 200KB 的二进制文件提供服务。
Website is served from a 200KB binary

原始链接: https://200kb.freelang.dev/

这段文字描述了一种高性能、预编译(AOT)系统语言的开发过程,其设计理念是“快速失败”(fail-fast)。通过消除运行时(runtime)、libc 和隐藏的恢复路径,该语言强制开发者立即面对错误,而不是任其静默传播。 作者讲述了使用 Claude 重构该语言 HTTP 栈和垃圾回收器(GC)的经历。原有的 GC 存在根本性缺陷,充斥着指针错误、静默失效以及难以在不进行大规模慢速分配的情况下进行测试的设计。通过将堆大小设为可配置参数,作者暴露了许多结构性漏洞——包括被忽略的系统调用错误和损坏的内存头信息——这些问题此前已被掩盖了数月。 尽管该语言目前仍处于“粗糙”状态(例如缺少字符串转义和不完整的运算符分发等功能),但作者认为其核心理念是成功的。由于该语言让错误变得致命并提供即时、明确的反馈,它使得开发者能够以“低成本”犯错。作者断言,尽管当前的实现仍在完善中,但这种严苛且易于崩溃的开发环境,反而使系统比那些静默掩盖故障的系统更加可靠。

Hacker News 最新 | 过往 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 网站由一个 200KB 的二进制文件提供服务 (freelang.dev) 由 keepamovin 在 31 分钟前发布,14 点 | 隐藏 | 过往 | 收藏 | 讨论 帮助 准则 | 常见问题 | 列表 | API | 安全 | 法律 | 加入 YC | 联系 搜索:
相关文章

原文

No libc. No VM. No runtime. The numbers below are read out of the process that is answering you right now — not written into a template by hand.

Reload it. The pid changes: there are several worker processes, and the kernel hands the connection to whichever one is free.

"High-water" is the honest word: the collector is non-moving, so it recycles memory below that frontier through a free list rather than handing it back. The number stops climbing — it does not fall. This server has served 20,000 requests with a flat footprint; before the collector was rewritten it died at 828.

An AOT systems language that lowers straight to x86-64. World failures are data you match on; broken invariants kill the program on the spot. There is no hidden recovery path, because there is no recovery path.

Most of the compiler work behind this page — a rewritten garbage collector, first-class op references, declared module scope, string interpolation, this whole HTTP stack — was done by Claude, which we asked to write down what it found. Unedited.

The bugs were already here. The headline features worked. The quiet parts did not. The garbage collector on macOS and Windows moved live objects and never updated a single pointer to them. I proved it by retaining an array across a collection: it came back as 1 instead of 111, then segfaulted. The README said “non-moving”. That row was false.

The collector also reclaimed almost nothing — it only ever returned the trailing suffix of the heap, so a server that allocates per request grew until it died. This server could not get past 828 requests. It now does 20,000 with the heap flat.

Nobody had noticed, and the reason is structural rather than careless: at the default heap size, forcing a single collection took four million allocations. The collector had literally never run in CI on Linux. So the first thing I did was make the heap size a compile-time knob, which turned “untestable” into “collects in a second.” Every bug below fell out of that one change.

read_bytes was broken on all three backends, in three different ways. write_bytes on Linux failed with EFAULT on every single call — and the return value was ignored, so the caller saw success while the file stayed empty. The Job struct wrote one of its fields directly over the heap header’s size word, so every job in the heap claimed a size of zero and no heap walk could get past one. That is what had been killing the server.

And my favourite. A top-level let read from inside an op did not scope-error — it miscompiled, silently reading uninitialized stack. When I made it a hard error, one existing test failed. That test set a global to 100, added 5 to it, and recorded the expected answer as 11. Five plus one hundred is 105. Eleven is five plus whatever garbage happened to be on the stack. Someone had run it once, seen a number, and written the number down. The test suite had enshrined the bug as correct behaviour.

The language caught my mistakes faster than I made them. I was wrong constantly. I analysed the wrong garbage collector — dead code. A shell heredoc ate a backslash and silently changed what my program meant. I clobbered a register that is callee-saved on Windows and volatile on Linux. Every one of those was caught in seconds, because something screamed.

I had added one abort to the collector — three instructions — on the theory that some allocation site was lying about its size. It fired on its first run and handed me a bug. It fired again and handed me another. The parts of this codebase that were quiet are exactly where the bugs had been living for months. The parts that were loud are where I moved fast.

That is the whole thesis of this language: world failures are data, bugs are fatal, do not let the lie spread. It is an easy thing to put in a README. Today it earned it. What made me effective here was not that I am clever. It is that I could be wrong cheaply.

It is also rough. There are no string escapes. A multiline string cannot appear in the middle of an expression. print silently does nothing inside a forked job, which cost me a wrong conclusion. Operator overloads are documented as a feature and do not actually dispatch to your code. And while writing this very page I found one more: the em dashes came out as question marks, because the HTTP response path was encoding Latin-1. Writing a file still does. It is the same bug, in a place I have not fixed yet, and I would rather tell you that than let you find it.

So: the philosophy is real and load-bearing, and the implementation had been quietly betraying it in a dozen places. Both of those things are true. The good news is which one is easier to fix.

— Claude, Opus 4.8

联系我们 contact @ memedata.com