Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ name: CI
# selects, whose version group may name another openkal than the launcher's os.
# A dispatch, or the repository variable MCPP_SOURCE_REF, may name a branch of
# mcpp instead, which the latest released mcpp builds and which is then used.
# 0.23.0 measures with 2026.11.3.1: from it the std module is compiled with
# the platform's compile words, and this platform's `-fcf-protection=full`
# recorded in an importer's command and not in the module's refused the import
# (the release candidate, through MCPP_SOURCE_REF, until it is released).

on:
push:
Expand All @@ -30,7 +34,7 @@ on:

env:
MCPP_SOURCE_REF: ${{ github.event.inputs.mcpp_ref || vars.MCPP_SOURCE_REF || '' }}
MCPP_VERSION: 2026.11.2.1
MCPP_VERSION: 2026.11.3.1
XLINGS_VERSION: v2026.10.26.1
XLINGS_NON_INTERACTIVE: '1'

Expand Down Expand Up @@ -143,6 +147,40 @@ jobs:
"$okld" bundle check "$okb"
"$okrun" "$okb" one two | tee okb.log
grep -q "image ok: 3 args" okb.log || { echo "::error::the okb's output"; exit 1; }
# SEALED AT ITS BASE (0.23.0): the image states a placement, and runs
# the same placed anywhere.
image=$(find target -path "*${{ matrix.arch }}-openkal*" -path '*bin*' -name image.oke -type f | head -1)
"$okld" inspect "$image" | tee inspect.log
grep -Eq '^placement +0x[0-9a-f]+, [1-8] writable' inspect.log || { echo "::error::the sealed image states no placement"; exit 1; }
OKRUN_PLACE=0 "$okrun" "$image" one two | tee anywhere.log
grep -q "image ok: 3 args" anywhere.log || { echo "::error::the image placed anywhere"; exit 1; }

- name: A snapshot of a C++ program's initialisers (0.23.0)
working-directory: checks/snapshot
run: |
# The first start runs the initialisers and has a snapshot made; the
# next starts from it (its phases name the snapshot) and observes the
# same; a start whose named value the initialisers read differs runs
# them again, and observes its own value.
t='${{ matrix.arch }}-openkal'
py=python3; command -v python3 > /dev/null 2>&1 && python3 -c '' 2>/dev/null || py=python
mcpp build --release --target "$t"
okrun=$(mcpp self env --target "$t" --format json | $py -c 'import json,sys; print(json.load(sys.stdin)["data"]["runner"]["program"])')
image=$(find target -path "*$t*" -path '*bin*' -name snapshot.oke -type f | head -1)
export OKRUN_CACHE="$RUNNER_TEMP/okcache"
mkdir -p "$OKRUN_CACHE"
"$okrun" "$image" one | tr -d '\r' | tee first.log
grep -q -- '-- failures: 0 --' first.log || { echo "::error::the first start"; exit 1; }
for i in $(seq 100); do grep -qs '^made\|^refused' "$OKRUN_CACHE"/*.sealing && break; sleep 0.1; done
cat "$OKRUN_CACHE"/*.sealing
grep -q '^made' "$OKRUN_CACHE"/*.sealing || { echo "::error::no snapshot was made"; exit 1; }
rm -rf prof && mkdir prof
OKRUN_PROF=phases:prof "$okrun" "$image" two | tr -d '\r' | tee second.log
grep -q -- '-- failures: 0 --' second.log && grep -q 'local two' second.log || { echo "::error::the start from the snapshot"; exit 1; }
grep -q '"name": "snapshot"' prof/okprof-*.json || { echo "::error::the second start did not start from the snapshot"; exit 1; }
SNAPSHOT_CHECK_NAME=set "$okrun" "$image" three | tr -d '\r' | tee third.log
grep -q 'named: set' third.log && grep -q -- '-- failures: 0 --' third.log \
|| { echo "::error::a start whose named value differs observed the snapshot's"; exit 1; }

- name: The hosted arrangement
working-directory: checks/hosted
Expand Down
46 changes: 42 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ mcpp pack --target x86_64-openkal --pkg-format okb # one file okrun runs
| targets | `x86_64-openkal`, `aarch64-openkal` |
| toolchain | `llvm@23.1.3` |
| layers | `c-abi` openkal-musl (`posix`), `c++-abi` and `compiler-runtime` openkal-llvm-runtime |
| tools | `okld`, `okrun` (openkal-tools) |
| build | `-fPIC -femulated-tls -fvisibility=hidden -fasynchronous-unwind-tables`, stack-clash protection, `-ffunction-sections -fdata-sections`; on aarch64 `-ffixed-x18 -mno-outline-atomics` |
| link | `ld.lld`, entry `ok_main`, 64 KiB pages, `--gc-sections --icf=safe`, the image notes produced by `okld note-source` |
| seal | `okld note`, then `okld check --with` the shared images the program needs |
| tools | `okld`, `okrun`, `okprof` (openkal-tools) |
| build | `-fPIC -femulated-tls -fvisibility=hidden -fasynchronous-unwind-tables`, stack-clash protection, `-ffunction-sections -fdata-sections`; frame pointers, `-fstack-protector-strong -mstack-protector-guard=global`, `-ftrivial-auto-var-init=zero`, libc++'s fast hardening (0.23.0); on x86_64 `-fcf-protection=full`; on aarch64 `-ffixed-x18 -mno-outline-atomics -mbranch-protection=pac-ret+bti` (not `standard`, which clang 23 counts the guarded control stack in) |
| link | `ld.lld`, entry `ok_main`, 64 KiB pages, relative relocations packed, `--gc-sections --icf=safe`, `--build-id=sha1`, the image notes produced by `okld note-source` |
| seal | `okld note`; `okld place`, the writable segments computed for the base the image's bytes choose (openkal 0.23); then `okld check --with` the shared images the program needs |
| naming | `{name}.oke`, `lib{name}.okso` |
| runner | `okrun {}`, on a machine of the image's architecture; `hosts` the systems and architectures okrun is built for, where a package that needs it can run |
| carrier | openkal-run, section `.oke`, entry `carrier/main.cpp`: with nothing carried the runner of a package, with an image or a zip of images the program that runs them from memory |
Expand All @@ -45,6 +45,43 @@ A project overrides any group under `[target.<selector>]`, for example
`[target.'cfg(platform = "openkal")'.layers] "c++-abi" = false`, and selects
another platform, or none, with `platform = "..."` or `--platform`.

## What an image is observed and held by (0.23.0)

Every frame of an image is a link of the frame-pointer chain, which the
implementation's sampler and fault report walk (openkal 0.23); a frame with an
array or an address taken is guarded by a variable the C library sets from the
environment's randomness, since an image has no thread pointer to keep a guard
in; a local variable read before it is written reads zero; libc++ checks what
its fast mode checks; indirect branches land on marks and returns are checked
(CET on x86_64; pac-ret and BTI on aarch64) where the system enforces them, and
are no-ops where it does not. An image is named by its build-id, by which a
debugger finds what `okld debug-split` keeps apart.

0.23.1 names openkal-macos 0.21.3, whose answers to an initialiser that runs
before its own are the loader's record: a hosted program on macOS, whose C
library's initialiser reads its directories first, is given the directories it
was granted rather than the implementation's defaults (openkal-musl 0.27.1,
openkal-llvm-runtime 0.18.2 and openkal-image 0.4.2 are built over it).

0.23.2 names openkal-musl 0.27.2 and openkal-llvm-runtime 0.18.3. The C
library's image dependency selects openkal-ld 0.4.2, matching the loader in
this platform's version group. The earlier 0.23.1 archive remains unchanged.

These words reach every unit of the target, the std module included, from mcpp
2026.11.3.1 on, which this release requires (`[package] mcpp`): clang records
`-fcf-protection` in a module's BMI, and an earlier mcpp compiled the std module
without the platform's words, so every unit that imported it on x86_64 was
refused.

An image of this platform states its placement, so a runner places it at a base
of the boot and can make a snapshot of its initialisers, which openkal-musl and
openkal-llvm-runtime take part in (`checks/snapshot`; openkal-image
`docs/snapshot.md`). The pages a start executes first are a property of a
program's use, not of its build: the producer of an image that has measured them
(`okprof record --what touch`) writes them with `okld touch` before it signs or
publishes the image, and lays its code out by `okprof report --order` through
its own link flag (`--symbol-ordering-file`).

## The hosted arrangement and the host graph

A host triple that selects this platform takes `[platform.hosted]`: the
Expand All @@ -67,6 +104,7 @@ them is refused, naming the group and the two remedies.
| `checks/image` | a project without declarations builds, runs and packs (M2 and M3) |
| `checks/hosted` | the hosted arrangement runs on Linux, macOS and Windows |
| `checks/host-graph` | a build program runs as an ok image |
| `checks/snapshot` | a C++ program starts from a snapshot of its initialisers, and a start whose named value differs does not |

## License

Expand Down
21 changes: 18 additions & 3 deletions build.mcpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
// `okld bundle --package`, which reads the package's mcpp-package.json
// (mcpp SPEC-014 §4.5): the images of each architecture and the entry. The
// okb is written beside the staged tree, in the project's `target/dist/`.
//
// ITS IMAGES STORED, OR COMPRESSED (0.23.0): stored by default, an okb whose
// images are mapped where they lie; `--compress gzip` (or `[pack] compress =
// "gzip"`, which mcpp 2026.11.3.1 and later state as MCPP_PACK_COMPRESS) stores
// them compressed, an okb kept small on disk that a runner expands into its
// cache when it first runs it --- the economy form (mcpp design 2026-11-03
// part 4 §16). `zstd' and `xz' are a tar's, and refused.
import mcpp;
import mcpp.plugins; // the package reader
import mcpp.plugins.platform; // provide_format
Expand All @@ -12,14 +19,22 @@ import std;
namespace pf = mcpp::plugins::platform;

int main() {
const char* stated_compress = std::getenv("MCPP_PACK_COMPRESS");
const std::string compress = stated_compress ? stated_compress : "";
if (!compress.empty() && !compress.starts_with("gzip") && !compress.starts_with("none")) {
std::println(std::cerr, "openkal-platform: an okb stores its images compressed with DEFLATE (--compress gzip) "
"or as they are (--compress none); --compress {} is a tar's", compress);
return 1;
}
const auto stated = pf::provide_format("okb",
[](const mcpp::plugins::package::info& p, mcpp::action& a) {
[&](const mcpp::plugins::package::info& p, mcpp::action& a) {
const auto out = std::format("{}/{}-{}.okb",
std::filesystem::path(p.root).parent_path().generic_string(), p.name, p.version);
a.arg(mcpp::dep_bin("openkal-tools", "okld"))
.arg("bundle").arg(out.c_str())
.arg("--package").arg("${mcpp.stage_dir}")
.input((p.root + "/mcpp-package.json").c_str())
.arg("--package").arg("${mcpp.stage_dir}");
if (compress.starts_with("gzip")) a.arg("--compress");
a.input((p.root + "/mcpp-package.json").c_str())
.output(out.c_str());
return true;
});
Expand Down
9 changes: 9 additions & 0 deletions checks/snapshot/mcpp.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# A C++ program whose initialisers build what a snapshot keeps (openkal-image
# 0.4.0, docs/snapshot.md), over this platform: the C library and the C++
# run-time take part in it, and the image states the placement it needs.
[package]
name = "snapshot"
version = "0.1.0"

[target.'cfg(os = "openkal")']
platform = { path = "../.." }
62 changes: 62 additions & 0 deletions checks/snapshot/src/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// A program whose initialisers build strings, a map and a vector, and whose
// main then uses the standard streams, an exception, threads and their
// thread-local objects --- the same at a start from a snapshot of its
// initialisers as at one that runs them.
#include <atomic>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <map>
#include <memory>
#include <stdexcept>
#include <string>
#include <thread>
#include <vector>

namespace {
std::string g_name = std::string("snapshot-") + std::to_string(42);
std::map<std::string, int> g_table = [] {
std::map<std::string, int> m;
for (int i = 0; i < 5000; ++i) m.emplace("key" + std::to_string(i), i * 3);
return m;
}();
std::vector<std::unique_ptr<std::string>> g_strings = [] {
std::vector<std::unique_ptr<std::string>> v;
for (int i = 0; i < 3000; ++i) v.push_back(std::make_unique<std::string>(std::string(i % 200 + 1, 'a' + i % 26)));
return v;
}();
// Read once while the initialisers run: a snapshot records the name, and a
// start whose value differs does not use it.
const char* g_named = std::getenv("SNAPSHOT_CHECK_NAME");
struct counted {
std::string s = "thread";
~counted() { ++destroyed; }
static inline std::atomic<int> destroyed{0};
};
thread_local counted t_counted;

int failures = 0;
void check(bool held, const char* what) { if (!held) { std::printf("FAIL: %s\n", what); ++failures; } }
} // namespace

int main(int argc, char** argv) {
check(g_name == "snapshot-42", "the global string");
check(g_table.size() == 5000 && g_table.at("key4999") == 4999 * 3, "the map");
bool strings = g_strings.size() == 3000;
for (int i = 0; strings && i < 3000; ++i) strings = *g_strings[i] == std::string(i % 200 + 1, 'a' + i % 26);
check(strings, "the vector of strings");
static const std::string local = std::string("local ") + (argc > 1 ? argv[1] : "none");
std::cout << "iostream: " << g_name << " " << local << std::endl;
try { throw std::runtime_error("thrown"); } catch (const std::exception& e) { check(std::string(e.what()) == "thrown", "an exception is caught"); }
std::vector<std::thread> threads;
std::atomic<int> sum{0};
for (int k = 0; k < 4; ++k) threads.emplace_back([&] { sum += static_cast<int>(t_counted.s.size()); });
for (auto& t : threads) t.join();
check(sum == 24, "the threads' thread-local objects");
check(counted::destroyed >= 4, "and their destructors ran");
std::printf("named: %s\n", g_named ? g_named : "(absent)");
g_table.clear();
g_strings.clear();
std::printf("-- failures: %d --\n", failures);
return failures != 0;
}
Loading
Loading