Skip to content

Commit f9e2613

Browse files
补上 time / env / 启动对象 / 线程指针 —— 每一条都是被一次实测逼出来的 (#1)
* deps: 改用 git + 分支跟随 openkal 的开发线 同时把 origin 换成 SSH:它此前指向 HTTPS,而这个账号在那条路径上没有写权限 —— 报出来的是 403,读起来像仓库不存在。 * deps: 例子也要跟着这条线 例子仍用版本号依赖 openkal,而仓库本身改成了 git 依赖 —— 两种形式不能并存, CI 报的是 'requested as both a version dep and a git dep'。 一次改依赖形式,要改的是**图里的每一处**,而例子也在图里。 * feat: 补上 time 与 env —— 而 time 缺席的那条理由是错的 kal.cpp 与 README 都写着 time 是「刻意缺席」,并给了理由: SBI 可以armed一个定时器中断,那是内核的机制,不是程序能读的时钟。 前半句对,后半句不从它推出来。这个架构**另外**把 `rdtime` 直接暴露给程序 —— `time` CSR,由 Zicntr 定义,固件让低特权级可读 —— 它和 SBI 的定时器扩展是两个 不同的设施,第一个不存在说明不了第二个。 实测(2026-08-23,OpenSBI + QEMU virt,S 模式,底下没有内核): t0=333572 t1=381292 ADVANCES ⭐ 结论会被复查,写在它旁边的理由不会。那条理由在注释里待了多久,推翻它所需的 两分钟就一直可用多久。 ## time 单调计数、精确粒度、以及在同一个计数器上自旋的 sleep —— 在一台只有一个执行 上下文、没有调度器可让的机器上,自旋**就是**睡眠而不是它的模拟,所以 KAL_TIME_PROP_SLEEP_PRECISE 是照实设的。 ⚠️ 没有墙钟。SBI 不定义任何取墙钟的设施,而 6.2 把「有没有墙钟」定成属性而不是 接口 —— 所以 kal_time_wall 在场并返回 0,KAL_TIME_PROP_WALL_AVAILABLE 清零。 ⚠️ 一处新的板级事实:rdtime 的频率架构不固定,写在设备树里,而入口约定不转发 设备树指针。所以它是构建输入 OPENKAL_OPENSBI_TIMEBASE_HZ,和已有的堆大小同一 先例 —— 属于机器的数字由知道是哪台机器的工程声明,而不是由不知道的包假设。 ## env 每个答案都是空的,而这是实现不是桩。6.2 禁止的是「在场但总是失败」;这里没有 失败可报:固件把 hart 号和设备树交给镜像,两者都不是命令行,所以这个环境**有** 环境,只是里面没有东西。枚举零个东西的调用者不需要任何特例,这正是它与 6.2 禁止的那种运行期拒绝的区别。 ## 判据 例子里加两条断言,并且都验证过会红: - clock ok —— 两次读数中间 sleep 一毫秒,后者必须大于前者。把 kal_time_monotonic 改成返回常量重跑,打印 clock stuck。**读得出来和会走 是两件事,只有这条断言能分开它们。** - env empty —— 「返回 0」和「根本没链进来」不问就分不出。 * feat: 加上接收控制的那个对象(standalone) openkal-linux 和 openkal-windows 都有一个 start 对象:内核把控制交过来,它找到 参数、建立线程指针、调用程序。这里补上对应的一个,给一个交出的东西少得多的环境。 ⚠️ 固件交出来的和内核交出来的不是一回事。内核启动一个**程序**:栈上有参数、有 线程指针要建、有程序头可读、栈已经在那里了。固件启动一个**镜像**:在 S 模式跳到 装载地址,a0 是 hart 号,a1 是设备树,别的没有。 于是这个文件多做一件、少做一件: - 多:让栈存在。而 __stack_top 由**程序的链接脚本**定义,因为栈放哪里是关于镜像 布局的陈述,而镜像是程序的。本包引用它、不定义它 —— 没有脚本就链接失败,这是 正确的报告。 - 少:不读参数向量,因为没有人可能供给过一个。openkal.env 在这个环境的答案是 「一个都没有」,这里传同样的答案,两边就不会互相矛盾。 __libc_start_main 弱引用,和另一个一样:直接写在 openkal 上的程序没有这个符号, 那时这个文件自己跑 init_array 再调 main。 feature 名沿用 standalone —— 它是关于**程序**的陈述,不是这个实现的精简版本; 知道是哪种安排的消费者才有资格声明它,所以由 openkal-musl 声明,本包不默认打开。 * fix: 弱声明不能有内部链接 初始化数组的四个符号放在匿名 namespace 里了 —— clang 直接拒绝: weak declaration cannot have internal linkage。移到外面。 * fix: 补上 __dso_handle,否则报的是一条读起来像代码模型的错 带析构函数的静态对象都要通过 __cxa_atexit 注册,第三个参数标识对象所属的镜像。 普通链接里这个标识来自 crtbegin。这里没有 crtbegin,链接器就自己合成一个 —— 放在地址 0,因为它没有更好的主意 —— 而镜像在 0x80200000。两者相距 2.05 GiB, 超出 auipc 能达到的范围,即使已经是最宽的 -mcmodel=medany: relocation R_RISCV_PCREL_HI20 out of range: -525086 references '__dso_handle' ⚠️ 没见过这条的人会把它读成代码模型的问题,而代码模型已经是最宽的那个。真正的 问题是这个符号不在镜像里。定义在这里就在镜像里了,取值是它自己的地址 —— 那正是唯一标识一个镜像的东西,也是普通链接产出的值。 * feat: 堆的位置也可以由程序的链接脚本说,理由和栈一样 静态数组意味着大小进镜像,而且不管程序分不分配都要付。64 KiB 对「分配一点点」 的程序是个合理数字;一个带着 C 库和 C++ 标准库的程序**在 main 之前**、在它自己 的初始化里就分配,64 KiB 撑不过去。 ⚠️ 而它显形的方式不是诊断。实测 2026-08-23:裸机 import std 的程序链接成功、 启动、**什么都不打印** —— 分配器在标准库的静态初始化里耗尽,那时还没有任何流 可以报告它。要报告就得用上那个还没起来的设施。 抬高默认值会让不分配的程序也付这份代价。所以大小放到栈的大小已经在的地方: 程序的链接脚本。__heap_start/__heap_end 是弱符号,不定义它们的脚本继续用静态 区域,本来能跑的程序什么都不变。 ⚠️ 定义了这对但顺序写反的脚本会被忽略而不是被信任:end 在 start 下面会让每次 分配看起来都成功、落进不是堆的内存 —— 那正是这整个包在避免的那种失败。 * feat: 线程指针也要有人建立,而它缺席时的报错不提线程指针 openkal-linux 的 start 对象会建立它,理由一样:命名当前上下文线程局部存储的 那个寄存器由创建上下文的人设置,而程序不带加载器时,那个人就是实现。 ⚠️ 它缺席时看起来不像「没有线程局部存储」。实测 2026-08-23:裸机 import std 的程序启动了、打印了,在第一次 throw 时挂掉: fault_load epc = __cxa_throw tval = 0x80048008 cxa_exception.cpp:284 globals->uncaughtExceptions += 1 __cxa_get_globals 读的是一个 thread_local,tp 里是复位时留下的东西,那次读落 进了固件自己的内存。这条消息里没有一个字提到线程指针:它报的是一个异常函数和 一个地址,两样看起来都像内存损坏。 ⚠️ 而 C 库盖不住这件事。openkal-musl 把**它自己的**线程指针放在变量里而不是 寄存器里 —— 那正是它能跑在「寄存器没有意义」的地方的原因 —— 所以 musl 启动 成功完全说明不了**工具链的**线程局部存储能不能用。两套机制,只建立了一套。 ⚠️ 三个尺寸从链接脚本来,不从程序头来:走程序头要 __ehdr_start,那要求 ELF 头 落在某个已加载段里,而那要求它在最低的加载地址上 —— 固件跳的就是最低加载地址, 于是头会被当成指令执行。实测:机器停在 \x7fELF 上。 同时:堆的位置也改成可由链接脚本给(见上一条),以及补上 __dso_handle。 * feat: 板子的内存布局归本包;并补上这个后端唯一缺的那半条款 9 ── 1. board.ld —— 加载地址是这个环境的事实,不是任何程序的 ── 在此之前每个想在 OpenSBI 上跑的程序都自带一份:examples/hello 一份、 openkal-llvm-runtime/examples/same-source 一份,而规范的 conformance 套件会需要 第三份。⭐ 而这个地址是**这段固件把控制权交到哪里**,正是本包存在的理由。 由 build.mcpp 的 link_script 供给,到达使用者的链接线。examples/hello 因此删掉了 自己那份,实测仍然打印四行。 ⚠️ 尺寸是宽裕而不是最小,这是本文件里唯一的判断:打印一个字符串的 C 程序不需要 256 KiB 栈和 16 MiB 堆,而带着 C 库和 C++ 标准库的程序需要,并且它在**自己的初始化 期间**就会耗尽更小的数值 —— 那发生在 main 之前,所以看起来是「程序启动了但什么都 没打印」。两者都在镜像之外,不占镜像空间。 ── 2. conformance 在没有操作系统的机器上跑起来 ── 四个实现里三个跑规范的行为半条款,这一个不跑。而困难不是「套件需要操作系统」: 套件只依赖 openkal 和语言、不 import std,并在自己的清单里写着它是为「在不携带 其它运行时的程序里运行」而写的。 它真正缺的是三样这个环境要供给而 hosted 不必的东西,每一样都是试出来的: 内存布局 → board.ld(上面那条) 控制权交接 → --features standalone,本生态每个实现在「程序下面没有 C 库」时 performing 它的方式 入口符号 → 套件的 main 改成 extern "C"(见 openkal 那侧的提交) ⇒ 实测:observations: 12 held, 0 did not hold, 9 not observed。 ⚠️ 断言同时看输出**和**「确实观察到了东西」——「0 did not hold」也是一次什么都没 检查的运行会报的。 * docs: README 说明 board.ld 与裸机 conformance —— 第二轮 review 的发现 ⚠️ 一个现在向每个使用者供给内存布局的包,而它的 README 一个字都没提。 新增一节说明:board.ld 陈述固件把控制权交到哪里、C++ 程序需要布局包含什么、镜像 预留多少栈和堆,并经本包的构建程序到达使用者的链接线 —— 与 kal_* 定义到达使用者 对象的方式相同。程序因此什么都不用陈述。 ⚠️ 并写明「程序自己也陈述一份会陈述两遍」:这个事实可传递地到达使用者,两份链接 脚本都会被应用,失败形式是输出段重叠,而诊断一个字都没提有两份。 ⚠️ 尺寸是宽裕而非最小,这是文件里唯一的判断,连同理由一起写明。 「Verified」一节补上:conformance 套件在同一段固件下、core 集上跑过,12 条成立、 0 条不成立。 * feat: declare the kernel-abi layer this package supplies mcpp resolves the target side per layer after the dependency graph is known, and reads which layer a package fills from its capabilities. Declaring `mcpp:kernel-abi=openkal` states that this package implements the platform interface a C library sits on, and that it answers to the name `openkal`. The engine knows the three layer names and none of the implementations that fill them, so nothing about openkal appears in mcpp for this to work. Claude-Session: https://claude.ai/code/session_01Q4ucduLuSsETHYJ1RkGVVD * chore: 0.1.1 The manifest states a capability the previous release did not, and the index serves published tarballs rather than branches. A consumer resolving through the index would otherwise get content in which this package declares no layer, and the engine would resolve its target side to the compiler payload. Claude-Session: https://claude.ai/code/session_01Q4ucduLuSsETHYJ1RkGVVD --------- Co-authored-by: speak-agent <x.d2learn.org@gmail.com>
1 parent 01f551b commit f9e2613

13 files changed

Lines changed: 830 additions & 45 deletions

File tree

.github/workflows/ci.yml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ jobs:
1515
MCPP_VERSION: 2026.8.20.2
1616
XLINGS_VERSION: v2026.8.17.2
1717
XLINGS_NON_INTERACTIVE: '1'
18+
# The branch of the specification this backend is verified against. It
19+
# moves with this one; when both are on `main` this becomes `main`.
20+
OPENKAL_BRANCH: feat/openkal-closure
1821
steps:
1922
- uses: actions/checkout@v4
2023

@@ -78,6 +81,59 @@ jobs:
7881
mcpp run 2>&1 | tee run.log
7982
grep -q "hello from openkal over SBI" run.log
8083
grep -q "heap ok" run.log
84+
# ⚠️ `clock ok` is the line that distinguishes a counter that reads
85+
# from a counter that MOVES. The example takes two readings with a
86+
# sleep between them; a stuck clock reads fine and prints
87+
# `clock stuck`. Verified to print it, by returning a constant from
88+
# `kal_time_monotonic` and rerunning.
89+
grep -q "clock ok" run.log
90+
grep -q "env empty" run.log
91+
92+
# ⭐⭐ THE HALF OF CLAUSE 9 THIS BACKEND WAS NOT PERFORMING.
93+
#
94+
# The specification makes behavioural conformance a property of every
95+
# implementation, and three of the four in this ecosystem ran the suite
96+
# while this one did not. The difficulty was real and it was not that the
97+
# suite needs an operating system: the suite depends on openkal and the
98+
# language, does not import std, and states in its own manifest that it is
99+
# written to run in a program that carries no other runtime.
100+
#
101+
# ⚠️ What it needed was three things this environment supplies and a
102+
# hosted one does not have to, and each was found by trying:
103+
#
104+
# the memory map — now `openkal-opensbi/board.ld`, reaching the
105+
# suite through this package's build program,
106+
# because where firmware hands control over is a
107+
# fact about this environment and not about any
108+
# program that runs on it;
109+
# the hand-over — `--features standalone`, which is how every
110+
# implementation here performs it when no C
111+
# library is beneath the program;
112+
# an entry symbol — the suite's `main` is `extern "C"`, because
113+
# `-ffreestanding` makes `main` an ordinary
114+
# function and the startup object refers to it
115+
# by name.
116+
#
117+
# ⚠️ THE ASSERTION IS ON THE OUTPUT AND ON THE EXIT STATUS BOTH. The suite
118+
# exits 2 when it observed nothing, which is the outcome a selection that
119+
# matched no interface would otherwise pass silently — and `core` on a
120+
# machine with no operating system is exactly the selection where that
121+
# could happen.
122+
- name: The conformance suite runs on the machine with no operating system
123+
run: |
124+
set -euo pipefail
125+
git clone --quiet --depth 1 -b "$OPENKAL_BRANCH" \
126+
https://github.com/mcpplibs/openkal "$RUNNER_TEMP/spec"
127+
Q=$(ls -d "$HOME"/.mcpp/registry/data/xpkgs/xim-x-qemu-riscv/*/bin/qemu-system-riscv64 | head -1)
128+
export OPENKAL_CONFORMANCE_RUNNER="$Q -machine virt -nographic -no-reboot -bios default -kernel"
129+
export OPENKAL_CONFORMANCE_IMPL_FEATURES=standalone
130+
cd "$RUNNER_TEMP/spec"
131+
bash tools/run-conformance.sh openkal-opensbi "$GITHUB_WORKSPACE" core \
132+
--target riscv64-none-elf 2>&1 | tee conf.log
133+
grep -q "the implementation conforms in every observation made" conf.log
134+
# ⚠️ And that something WAS observed. "0 did not hold" is also what a
135+
# run that examined nothing reports.
136+
grep -qE "observations: [1-9][0-9]* held" conf.log
81137
82138
# ---------------------------------------------------------------------------
83139
# The cross-compilation is performed FROM three systems, not only from Linux.

README.md

Lines changed: 75 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,45 @@ requires firmware beneath it, a board backend does not.
2323

2424
## What is implemented
2525

26-
`abort`, `stream` and `memory` — openkal's core set. An implementation provides
27-
an interface in whole or not at all, so the absence of `fs`, `process`, `task`,
28-
`env` and `time` is not a deviation; `import openkal.task;` simply does not
29-
resolve.
26+
`abort`, `stream` and `memory` — openkal's core set — plus `time` and `env`. An
27+
implementation provides an interface in whole or not at all, so the absence of
28+
`fs`, `process` and `task` is not a deviation; `import openkal.task;` simply
29+
does not resolve. This machine has no storage, no second image to start and no
30+
scheduler, and clause 6.2 says the remedy for an operation that cannot be
31+
provided is that its absence be expressed by its absence rather than by a
32+
run-time refusal.
33+
34+
⚠️ **`time` used to be on that list, with a reason, and the reason was wrong.**
35+
36+
It read: SBI can arm a timer interrupt, which is a mechanism for a kernel rather
37+
than a clock a program can read. The first half is true. The second does not
38+
follow from it — this architecture also exposes `rdtime` directly to the
39+
program, through the `time` CSR, independently of SBI's timer extension. Two
40+
different facilities, and the absence of the first says nothing about the
41+
second.
42+
43+
Measured 2026-08-23 under OpenSBI on QEMU's `virt`, from supervisor mode with no
44+
kernel beneath:
3045

31-
⚠️ **`time` is absent deliberately, and SBI does have a timer.** SBI can arm a
32-
timer interrupt, which is a mechanism for a kernel rather than a clock a program
33-
can read. Reporting a clock that does not advance would make every timed wait
34-
silently wrong — the specification's own example of a simulation that
35-
disqualifies an interface from being provided at all.
46+
```
47+
t0=333572 t1=381292 ADVANCES
48+
```
49+
50+
⭐ The conclusion got rechecked and the reason beside it did not. The two
51+
minutes that refuted it had been available for as long as the file existed.
52+
53+
`time` is therefore provided: a monotonic count, an exact granularity, and a
54+
sleep that spins on the same counter — which on a machine with one execution
55+
context and nothing to yield to is what sleeping *is*, so
56+
`KAL_TIME_PROP_SLEEP_PRECISE` is set truthfully. **Not** a wall clock: SBI
57+
defines no facility for one, `KAL_TIME_PROP_WALL_AVAILABLE` is clear, and clause
58+
6.2 makes that a property rather than a missing interface.
59+
60+
`env` is provided and every answer is empty. That is an implementation, not a
61+
stub: firmware enters the image with a hart identifier and a device tree, and
62+
neither is a command line, so this environment *has* an environment and it has
63+
nothing in it. A caller enumerating zero things needs no special case, which is
64+
exactly what distinguishes this from the run-time refusal clause 6.2 forbids.
3665

3766
## The heap is a bump allocator, and that is a bound rather than an omission
3867

@@ -47,6 +76,36 @@ The region is 64 KiB by default. A project that needs another figure overrides
4776
allocation pattern needs reuse should place a real allocator above this one;
4877
that is policy, and openkal carries mechanism.
4978

79+
## The memory map is this package's, because it is a fact about this machine
80+
81+
`board.ld` states where the firmware hands control over (`0x80200000`, since
82+
OpenSBI itself occupies the start of RAM), what the layout has to contain for a
83+
C++ program to work — the initialiser arrays, the unwind tables and their bounds,
84+
the thread-local segment — and how much stack and heap the image reserves. It
85+
reaches a consumer's link line through this package's build program, in the same
86+
way its `kal_*` definitions reach the consumer's objects.
87+
88+
A program therefore states nothing. Until 2026-08-23 every program that wanted to
89+
run here carried a copy: `examples/hello` had one, the C++ runtime's
90+
`same-source` example had a second, and the specification's conformance suite
91+
would have needed a third. None of those is a property of a program.
92+
93+
⚠️ **A program that states one as well states it twice.** The fact reaches
94+
consumers transitively, and two linker scripts are both applied — which fails as
95+
overlapping output sections and says nothing about there being two:
96+
97+
```
98+
ld.lld: error: section .eh_frame file range overlaps with .debug_str_offsets
99+
```
100+
101+
The sizes — 256 KiB of stack, 16 MiB of heap — are generous rather than minimal,
102+
and that is the one judgement in the file. A C program printing a string needs
103+
neither; a program carrying a C library and a C++ standard library does, and it
104+
exhausts smaller figures during its own initialisation, before `main`, so what it
105+
looks like is a program that starts and prints nothing. Both regions lie past the
106+
end of the image and cost nothing in it. A board with far less memory states its
107+
own map instead of taking this one.
108+
50109
## Two details that were measured rather than assumed
51110

52111
**DBCN is probed, not assumed.** The Debug Console extension arrived in SBI
@@ -70,7 +129,12 @@ heap ok
70129
```
71130

72131
The image carries no C library (`[target.<triple>].sysroot = ""`) and no board
73-
package. It is loaded at `0x80200000`, because OpenSBI itself occupies the start
74-
of RAM.
132+
package, and it states no memory map of its own: `board.ld` above is this
133+
package's.
134+
135+
The specification's conformance suite also runs here, under the same firmware,
136+
selected to the `core` set — twelve observations held, none failed. Clause 9
137+
makes the behavioural half a property of every implementation, and an
138+
implementation of a machine with no operating system is not exempt from it.
75139

76140
[kal]: https://github.com/mcpplibs/openkal

board.ld

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/* The memory map of a program this implementation starts, and the symbols its
2+
* startup object reads.
3+
*
4+
* ⭐⭐ THIS FILE IS A BOARD FACT AND THEREFORE BELONGS TO THIS PACKAGE.
5+
*
6+
* Until now every program that wanted to run over OpenSBI carried its own copy:
7+
* `examples/hello` had one, `openkal-llvm-runtime/examples/same-source` had a
8+
* second, and the specification's conformance suite would have needed a third.
9+
* The load address is not a property of any of them — it is where this firmware
10+
* hands control over, which is precisely what this package exists to know.
11+
*
12+
* ⚠️ Measured 2026-08-23: the conformance suite BUILDS for `riscv64-none-elf`
13+
* against this implementation and then produces an image whose entry point is
14+
* 0x0, because nothing placed it. The suite is written to run in a program that
15+
* carries no other runtime, and asking it to carry a board's memory map would
16+
* contradict that — clause 9 makes behavioural conformance a property of every
17+
* implementation, including one of a machine with no operating system.
18+
*
19+
* ⇒ Supplied through `build.mcpp`, which reaches the CONSUMER's link line. A
20+
* program adds nothing and gets a layout that works.
21+
*
22+
* ⚠️ THE SIZES ARE GENEROUS RATHER THAN MINIMAL, and that is the one judgement
23+
* in this file. A C program printing a string needs neither 256 KiB of stack
24+
* nor 16 MiB of heap; a program carrying a C library and a C++ standard library
25+
* does, and it exhausts the smaller figures during its own initialisation —
26+
* before `main`, so what it looks like is a program that starts and prints
27+
* nothing. Both are past the end of the image and cost nothing in it, and
28+
* QEMU's `virt` has 128 MiB. A board with far less can state its own.
29+
*
30+
* What follows is what a C++ program needs the layout to contain.
31+
*
32+
* OpenSBI occupies 0x80000000 upward and hands control to the next stage at
33+
* 0x80200000, which is why this address and not the start of RAM.
34+
*
35+
* ⚠️ FIRMWARE JUMPS TO THE LOWEST LOADED ADDRESS AND NOT TO THE ENTRY THE IMAGE
36+
* RECORDS. Measured 2026-08-23, after an attempt to put the ELF header inside
37+
* the first loaded segment so that `__ehdr_start` would be usable: the entry
38+
* moved to 0x80200270, OpenSBI still announced `Next Address 0x80200000`, and
39+
* the machine hung executing the header as instructions. So the first thing at
40+
* the load address has to be the first instruction, and everything that would
41+
* otherwise be read out of the program headers is named here instead.
42+
*
43+
* ⚠️ FOUR THINGS BEYOND THE MINIMUM, AND EACH IS SOMETHING A C++ PROGRAM HAS
44+
* THAT A C ONE DOES NOT.
45+
*
46+
* The initialiser arrays. Every static object with a constructor puts a
47+
* pointer in .init_array, and the C library walks it between symbols the
48+
* LINKER normally provides. A script that does not name them gets an empty
49+
* range and every such object stays unconstructed --- which does not fail,
50+
* it MISBEHAVES, and that is worse.
51+
*
52+
* The unwind tables, and the two symbols that bound them. libunwind built
53+
* with _LIBUNWIND_IS_BAREMETAL looks them up by name instead of walking
54+
* program headers; the fragment below is the one its AddressSpace.hpp
55+
* documents, reproduced rather than invented.
56+
*
57+
* The thread-local segment, and three symbols describing it. The startup
58+
* object in openkal-opensbi builds one context's storage from them. Without
59+
* it the register that names thread-local storage holds whatever reset left,
60+
* and the first `throw` faults inside `__cxa_get_globals` --- a message that
61+
* names an exception function and says nothing about a thread pointer.
62+
*
63+
* A stack big enough for the standard library. 16 KiB is enough for a program
64+
* that prints a string; formatting through <format> and sorting a vector is
65+
* not that program.
66+
*/
67+
ENTRY(_start)
68+
SECTIONS {
69+
. = 0x80200000;
70+
71+
.text : { *(.text.entry) *(.text .text.*) }
72+
73+
.rodata : { *(.rodata .rodata.*) *(.srodata .srodata.*) }
74+
75+
/* Verbatim from libunwind's AddressSpace.hpp, which is where the names come
76+
* from. `--eh-frame-hdr` on the link line is what makes the index exist at
77+
* all; without it the section is empty and the two `_hdr_` symbols are the
78+
* zero the conditional below produces, which libunwind reads as "no index"
79+
* and falls back to scanning. */
80+
.eh_frame : {
81+
__eh_frame_start = .;
82+
KEEP(*(.eh_frame))
83+
__eh_frame_end = .;
84+
}
85+
.eh_frame_hdr : { KEEP(*(.eh_frame_hdr)) }
86+
__eh_frame_hdr_start = SIZEOF(.eh_frame_hdr) > 0 ? ADDR(.eh_frame_hdr) : 0;
87+
__eh_frame_hdr_end = SIZEOF(.eh_frame_hdr) > 0 ? . : 0;
88+
89+
.preinit_array : {
90+
PROVIDE_HIDDEN(__preinit_array_start = .);
91+
KEEP(*(.preinit_array))
92+
PROVIDE_HIDDEN(__preinit_array_end = .);
93+
}
94+
.init_array : {
95+
PROVIDE_HIDDEN(__init_array_start = .);
96+
KEEP(*(SORT_BY_INIT_PRIORITY(.init_array.*)))
97+
KEEP(*(.init_array))
98+
PROVIDE_HIDDEN(__init_array_end = .);
99+
}
100+
.fini_array : {
101+
PROVIDE_HIDDEN(__fini_array_start = .);
102+
KEEP(*(SORT_BY_INIT_PRIORITY(.fini_array.*)))
103+
KEEP(*(.fini_array))
104+
PROVIDE_HIDDEN(__fini_array_end = .);
105+
}
106+
107+
/* The thread-local segment, and its three measurements.
108+
*
109+
* They are named here rather than read from PT_TLS for the reason at the
110+
* top of this file: the program headers are not reachable at run time on
111+
* this target. The startup object takes the initialised bytes from
112+
* `__tls_start`, copies `__tls_filesz` of them, and zeroes out to
113+
* `__tls_memsz` — which is what a loader does. */
114+
.tdata : { __tls_start = .; *(.tdata .tdata.*) }
115+
.tbss : { *(.tbss .tbss.*) *(.tcommon) }
116+
__tls_filesz = SIZEOF(.tdata);
117+
__tls_memsz = SIZEOF(.tdata) + SIZEOF(.tbss);
118+
119+
.data : { *(.data .data.*) *(.sdata .sdata.*) }
120+
121+
.bss : {
122+
__bss_start = .;
123+
*(.sbss .sbss.*) *(.bss .bss.*) *(COMMON)
124+
__bss_end = .;
125+
}
126+
127+
/* 256 KiB of stack. See the note above. */
128+
. = ALIGN(16); . = . + 0x40000; __stack_top = .;
129+
130+
/* ⭐ AND THE HEAP, WHICH FOR THIS PROGRAM CANNOT BE THE IMPLEMENTATION'S
131+
* DEFAULT.
132+
*
133+
* openkal-opensbi carries a 64 KiB static region for a program that
134+
* allocates a little. A program carrying a C library and a C++ standard
135+
* library allocates during its own initialisation, before `main`, and does
136+
* not survive that figure — measured, and what it looks like is a program
137+
* that starts and prints nothing, because the allocator runs out before
138+
* there is a stream to report it on.
139+
*
140+
* So the region is stated here, where the stack's size already is, and it
141+
* costs nothing in the image: it is past the end and QEMU's `virt` has
142+
* 128 MiB. 16 MiB is comfortably more than this program needs and small
143+
* enough to fit a machine with far less. */
144+
. = ALIGN(16); __heap_start = .; . = . + 0x1000000; __heap_end = .;
145+
}

build.mcpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import mcpp;
2+
3+
// ⭐ THE BOARD'S MEMORY MAP, PUT ON THE CONSUMER'S LINK LINE.
4+
//
5+
// `board.ld` records where this firmware hands control over and what a program
6+
// started that way needs its layout to contain. Both are facts about this
7+
// environment rather than about any program, which is why they are here and not
8+
// copied into each one — see the head of that file for what the copies were.
9+
//
10+
// ⚠️ `link_script` reaches the CONSUMER's link line, and a relative path in
11+
// `ldflags` would resolve against the build directory instead of against this
12+
// package. That is the same reason `openkal-macos` supplies its stub through a
13+
// `link_search` rather than a path in a flag.
14+
int main() {
15+
mcpp::link_script("board.ld");
16+
mcpp::rerun_if_changed("board.ld");
17+
return 0;
18+
}

examples/hello/build.mcpp

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import mcpp;
2-
int main() {
3-
// A relative path in `ldflags` would resolve against the build directory;
4-
// `link-script` resolves against the package root.
5-
mcpp::link_script("link.ld");
6-
mcpp::rerun_if_changed("link.ld");
7-
return 0;
8-
}
2+
3+
// Nothing to state. The memory map comes from `openkal-opensbi`, which is the
4+
// package that knows where this firmware hands control over; this example
5+
// carried a copy of it until 2026-08-23.
6+
//
7+
// The file remains because a build program is how this example would say
8+
// anything at all, and its absence would read as "this was never considered".
9+
int main() { return 0; }

examples/hello/link.ld

Lines changed: 0 additions & 12 deletions
This file was deleted.

examples/hello/mcpp.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ runner = ["qemu-system-riscv64", "-machine", "virt", "-nographic",
1313
"-no-reboot", "-bios", "default", "-kernel"]
1414

1515
[dependencies]
16-
openkal = "0.5.1"
16+
openkal = { git = "https://github.com/mcpplibs/openkal", branch = "feat/openkal-closure" }
1717
openkal-opensbi = { path = "../.." }

0 commit comments

Comments
 (0)