diff --git a/.github/actions/mtest/action.yml b/.github/actions/mtest/action.yml new file mode 100644 index 0000000..e5deb10 --- /dev/null +++ b/.github/actions/mtest/action.yml @@ -0,0 +1,53 @@ +name: mtest +description: > + The mtest engine CI reports with: the release .github/versions.env pins + (MTEST_VERSION), an xlings package (xim:mtest) the index verifies, else the + release bundle checked against MTEST_SHA256. Exports MTEST, the launcher. + +runs: + using: composite + steps: + # By the configured mirror (GLOBAL), then by CN (GitCode first), each with a + # refreshed index; else the release bundle itself: the web address, then + # the API's, checked against the pinned sha256. + - name: mtest + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + . .github/versions.env + launcher="" + for mirror in GLOBAL CN; do + xlings config --mirror "$mirror" > /dev/null 2>&1 || true + xlings update > /dev/null 2>&1 || true + if xlings install "mtest@$MTEST_VERSION" -y -g; then launcher=$(command -v mtest); break; fi + done + # The package's own launcher, not the xvm shim, which on Windows runs + # the alias through cmd. + exe=''; [ "$RUNNER_OS" != Windows ] || exe='.exe' + own="${XLINGS_HOME:-$HOME/.xlings}/data/xpkgs/xim-x-mtest/$MTEST_VERSION/bin/mtest$exe" + if [ -n "$launcher" ] && [ -f "$own" ]; then launcher="$own"; fi + xlings config --mirror GLOBAL > /dev/null 2>&1 || true + if [ -z "$launcher" ]; then + echo "::warning::xlings could not install mtest $MTEST_VERSION; taking the release itself" + archive="mtest-$MTEST_VERSION-openkal-bundle-all.tar.gz" + temp="$RUNNER_TEMP"; python=python3 + if [ "$RUNNER_OS" = Windows ]; then temp=$(cygpath -u "$RUNNER_TEMP"); python=python; fi + if ! curl -fsSL --retry 5 --retry-all-errors --retry-delay 5 -o "$temp/$archive" \ + "https://github.com/mcpp-community/mtest/releases/download/v$MTEST_VERSION/$archive"; then + id=$(curl -fsSL --retry 3 --retry-all-errors -H "Authorization: Bearer $GH_TOKEN" \ + "https://api.github.com/repos/mcpp-community/mtest/releases/tags/v$MTEST_VERSION" \ + | "$python" -c 'import json,sys; print(next(str(a["id"]) for a in json.load(sys.stdin)["assets"] if a["name"] == sys.argv[1]))' "$archive") + curl -fsSL --retry 5 --retry-all-errors --retry-delay 5 -H "Authorization: Bearer $GH_TOKEN" \ + -H "Accept: application/octet-stream" -o "$temp/$archive" \ + "https://api.github.com/repos/mcpp-community/mtest/releases/assets/$id" + fi + actual=$("$python" -c "import hashlib,sys; print(hashlib.sha256(open(sys.argv[1],'rb').read()).hexdigest())" "$temp/$archive") + [ "$actual" = "$MTEST_SHA256" ] || { echo "::error::$archive: sha256 $actual, versions.env pins $MTEST_SHA256"; exit 1; } + mkdir -p "$temp/mtest" && tar xzf "$temp/$archive" -C "$temp/mtest" + launcher="$temp/mtest/mtest-$MTEST_VERSION-openkal-bundle-all/bin/mtest$exe" + fi + "$launcher" --version | grep -qF "$MTEST_VERSION" \ + || { echo "::error::the mtest at $launcher is not $MTEST_VERSION"; exit 1; } + echo "MTEST=$launcher" >> "$GITHUB_ENV" diff --git a/.github/actions/test-report/action.yml b/.github/actions/test-report/action.yml new file mode 100644 index 0000000..752850c --- /dev/null +++ b/.github/actions/test-report/action.yml @@ -0,0 +1,43 @@ +name: test-report +description: > + The runs `mcpp test` kept in the mtest store's form ([test] store, docs/08; + SPEC-017 §6): taken into one store of the job, written as one Markdown report + of this workflow run's runs into the job's summary, and kept as an artifact + for the report over every job of the workflow run (ci.yml `report`). +inputs: + name: + description: the artifact's name, unique in the workflow run (mtest-unit-) + required: true + stores: + description: the stores `mcpp test` kept in this job, relative to the workspace, separated by spaces + required: false + default: ld/target/mtest +runs: + using: composite + steps: + - uses: ./.github/actions/mtest + - shell: bash + env: + STORES: ${{ inputs.stores }} + run: | + set -euo pipefail + job="$(cygpath -u "$RUNNER_TEMP" 2> /dev/null || printf '%s' "$RUNNER_TEMP")/mtest-job" + mkdir -p "$job" + taken=0 + for store in $STORES; do + [ -f "$store/index.ndjson" ] || continue + (cd "$job" && "$MTEST" store import --from "$GITHUB_WORKSPACE/$store") + taken=1 + done + if [ "$taken" = 0 ]; then echo "mcpp test kept no run here"; exit 0; fi + (cd "$job" && "$MTEST" report --format md --group "$GITHUB_RUN_ID") >> "$GITHUB_STEP_SUMMARY" \ + || echo "::warning::no run of this workflow run in the store" + echo "MTEST_JOB_STORE=$(cygpath -w "$job/.mtest" 2> /dev/null || printf '%s' "$job/.mtest")" >> "$GITHUB_ENV" + - uses: actions/upload-artifact@v4 + if: env.MTEST_JOB_STORE != '' + with: + name: ${{ inputs.name }} + path: ${{ env.MTEST_JOB_STORE }} + include-hidden-files: true + if-no-files-found: ignore + retention-days: 7 diff --git a/.github/versions.env b/.github/versions.env new file mode 100644 index 0000000..f670c40 --- /dev/null +++ b/.github/versions.env @@ -0,0 +1,5 @@ +# The engines CI pins, read by the composite actions (.github/actions/mtest). +# mtest: an xlings package (xim:mtest) whose sha256 the index states; the release +# bundle's sha256 checks the fallback download. +MTEST_VERSION=0.5.2 +MTEST_SHA256=be443f8c546b3f4628dcd3f1eb6f45b4d698a7f805232f966349faf8250863c6 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b73baf5..4f19d81 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,9 @@ on: env: XLINGS_VERSION: v2026.10.26.1 XLINGS_NON_INTERACTIVE: '1' - MCPP_VERSION: 2026.11.9.1 + # 2026.11.10.1: openkal-run binds the interfaces its implementation provides + # (MCPP_KERNEL_ABI_INTERFACES). + MCPP_VERSION: 2026.11.10.1 LLVM: llvm@23.1.3 jobs: @@ -54,6 +56,12 @@ jobs: mcpp toolchain default "$LLVM" - name: libokld against the published vectors run: (cd ld && mcpp test) + # The run `mcpp test` kept (ld/target/mtest): this job's report in its + # summary, and the store for the workflow run's report (`report`). + - uses: ./.github/actions/test-report + if: always() + with: + name: mtest-images - name: Debugger object bounds and failed-admission ownership run: python3 .github/scripts/check-debugger-contract.py --cxx "$HOME/.mcpp/registry/data/xpkgs/xim-x-llvm/23.1.3/bin/clang++" - name: okld @@ -260,6 +268,10 @@ jobs: okprof=$(find "$PWD/target" -path '*bin*' \( -name okprof -o -name okprof.exe \) -type f | head -1) [ -n "$okprof" ] || { echo "::error::okprof did not build"; exit 1; } echo "OKPROF=$okprof" >> "$GITHUB_ENV" + - uses: ./.github/actions/test-report + if: always() + with: + name: mtest-run-${{ matrix.host }} - name: okprof forwards environment values and the child's status run: | python=python3; [ "$RUNNER_OS" = Windows ] && python=python @@ -486,3 +498,35 @@ jobs: grep -q "$want" bind.txt || { echo "::error::the binding: $want"; exit 1; } done bash examples/bind/check-refusals.sh + + # EVERY JOB'S TEST RUNS AS ONE REPORT (`mtest ci report`, the workflow run's + # group): libokld's vectors on the image machine and on each system. + report: + name: tests (report) + needs: [images, run] + if: ${{ always() && !cancelled() }} + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + - name: Install xlings + run: | + curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s "$XLINGS_VERSION" + echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH" + - uses: ./.github/actions/mtest + - uses: actions/download-artifact@v4 + with: + pattern: mtest-* + path: stores + - name: Every job's runs, reported together + run: | + set -euo pipefail + found=0 + for store in stores/*/; do + [ -f "$store/index.ndjson" ] || continue + "$MTEST" store import --from "$store" + found=1 + done + [ "$found" = 1 ] || { echo "no job kept a run" >> "$GITHUB_STEP_SUMMARY"; exit 0; } + "$MTEST" ci report --group "$GITHUB_RUN_ID" --out tests.md + cat tests.md >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/release-binaries.yml b/.github/workflows/release-binaries.yml index 61cd462..b820e9a 100644 --- a/.github/workflows/release-binaries.yml +++ b/.github/workflows/release-binaries.yml @@ -33,9 +33,9 @@ env: RELEASE_TAG: ${{ github.event.inputs.tag }} ASSET_REVISION: ${{ github.event.inputs.asset_revision || '' }} MCPP_SOURCE_REF: ${{ github.event.inputs.mcpp_ref || vars.MCPP_SOURCE_REF || '' }} - # 2026.11.9.1: openkal-run states its implementation as a capability with defaults. + # 2026.11.10.1: openkal-run binds the interfaces its implementation provides. # mcpp_ref supplies the candidate until this release is installable. - MCPP_VERSION: 2026.11.9.1 + MCPP_VERSION: 2026.11.10.1 # The platform whose hosted arrangement the programs are built over, named by # the dispatch beside the tag: a tag's tools pin exact implementations, and the # platform is the one whose version group holds them (0.1.4: 0.18.0; 0.1.5: diff --git a/README.md b/README.md index f93a4ff..d9e8dd4 100644 --- a/README.md +++ b/README.md @@ -52,11 +52,22 @@ implementation the graph already has --- the consumer's own, or the one its platform adds, such as openkal-posix on another system --- is the one bound, without a patch of this package. -Version 0.5.5 is built against the openkal 0.25 family: openkal 0.25.0 and, as -the defaults per system, openkal-linux 0.26.0, openkal-macos 0.23.0 and -openkal-windows 0.21.0. The names the runner binds are generated from the -specification's SURFACE.txt, so the names 0.25 adds (`kal_fs_hard_link`, -among others) are bound without a change of the runner's sources. +Version 0.6.0 binds the interfaces the implementation provides, which it reads +when it is built (mcpp 2026.11.10.1, `MCPP_KERNEL_ABI_INTERFACES`): the +implementation's `provides-interfaces`. A name of any other interface is listed +without an address, the runner refers to nothing of that interface, and an image +that requires it is refused at load, naming it (SPEC 12.4). The word of +interfaces an image is told is the implementation's answer intersected with the +interfaces bound, together with the loader's own. An implementation that states +no list is taken to provide every interface. The interfaces the runner calls +itself are stated as `[kernel-abi] requires-interfaces`, so an implementation +without one of them is refused when the graph is resolved. No source file is +chosen per system any more: an implementation that provides part of the surface, +on any system, needs no function it does not have. The names the runner binds are +generated from the specification's SURFACE.txt, so the names 0.25 adds +(`kal_fs_hard_link`, among others) are bound without a change of the runner's +sources. The package follows openkal 0.25 (openkal-linux 0.26, openkal-macos +0.23, openkal-windows 0.21). ## An image diff --git a/bind/mcpp.toml b/bind/mcpp.toml index f967914..0a07aec 100644 --- a/bind/mcpp.toml +++ b/bind/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal-bind" -version = "0.5.5" +version = "0.6.0" # `mcpp::xpkg_request', `mcpp::decision' (protocol 15). mcpp = ">=2026.10.1.3" description = "A build plugin: a library published as an ok image (.okso) and a C header is called from a program of any system, through a binding generated from the header and libokrun." diff --git a/ld/mcpp.toml b/ld/mcpp.toml index 600a1b7..9d77baa 100644 --- a/ld/mcpp.toml +++ b/ld/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal-ld" -version = "0.5.5" +version = "0.6.0" description = "libokld: the ok format (openkal image format) --- its definition, a reader of bytes, a checker, a signer, and a loader. Freestanding: no C library, no exceptions." license = "Apache-2.0" authors = ["mcpplibs"] @@ -10,6 +10,10 @@ repo = "https://github.com/mcpplibs/openkal-image" [targets.okld] kind = "lib" +# The library's tests (tests/), on the mtest library. +[dev-dependencies] +mcpplibs.mtest = "0.5.2" + [build] sources = ["src/*.cppm", "src/*.cpp"] include_dirs = ["include"] diff --git a/ld/tests/crypto_test.cpp b/ld/tests/crypto_test.cpp index 3ca653d..976bcc9 100644 --- a/ld/tests/crypto_test.cpp +++ b/ld/tests/crypto_test.cpp @@ -5,18 +5,21 @@ #include #include +import mcpplibs.mtest; + +using namespace mcpplibs::mtest; + extern "C" void okld_sha512_for_test(const void*, okld_size, unsigned char[64]); extern "C" void okld_ed25519_sign_for_test(const unsigned char[32], const void*, okld_size, unsigned char[64]); namespace { -int failures = 0; void bytes(const char* hex, unsigned char* out) { for (std::size_t i = 0; hex[2 * i]; ++i) { unsigned v; std::sscanf(hex + 2 * i, "%2x", &v); out[i] = static_cast(v); } } -void check(bool held, const char* what) { std::printf(" %s %s\n", held ? "held" : "FAIL", what); if (!held) ++failures; } +void check(bool held, const char* what) { expect(held) << what; } } -int main() { +UTest crypto_test{"crypto", [] { unsigned char h[64], want[64]; okld_sha512_for_test("abc", 3, h); bytes("ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f", want); @@ -42,5 +45,4 @@ int main() { okld_ed25519_sign_for_test(seed, msg, std::strlen(t.msg) / 2, sig); check(std::memcmp(sig, want_sig, 64) == 0, "an Ed25519 signature is RFC 8032's"); } - return failures; -} +}}; diff --git a/ld/tests/okb_test.cpp b/ld/tests/okb_test.cpp index f8e4b32..7e4d727 100644 --- a/ld/tests/okb_test.cpp +++ b/ld/tests/okb_test.cpp @@ -12,9 +12,12 @@ #include #include +import mcpplibs.mtest; + +using namespace mcpplibs::mtest; + namespace { -int failures = 0; -void check(bool held, const char* what) { std::printf(" %s %s\n", held ? "held" : "FAIL", what); if (!held) ++failures; } +void check(bool held, const char* what) { expect(held) << what; } std::string hex(const unsigned char* p, int n) { std::string s; char b[3]; for (int i = 0; i < n; ++i) { std::snprintf(b, sizeof b, "%02x", p[i]); s += b; } @@ -30,7 +33,7 @@ std::vector write(const std::vector& in) { } } // namespace -int main() { +UTest okb_test{"okb", [] { unsigned char d[32]; okld_sha256("abc", 3, d); check(hex(d, 32) == "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", "SHA-256 of \"abc\" (FIPS 180-4)"); @@ -305,5 +308,4 @@ int main() { check(admit(pack(without, big), without, nullptr, &why) == 0, "without chunks, the whole digest is verified"); check(admit(pack(without, changed), without, nullptr, &why) < 0, "and refuses a changed byte"); } - return failures; -} +}}; diff --git a/run/build.mcpp b/run/build.mcpp index 0f083fb..9eea7fc 100644 --- a/run/build.mcpp +++ b/run/build.mcpp @@ -1,12 +1,22 @@ // The names an image may import, read from the specification this package is // built against (openkal's SURFACE.txt), as the lines libokrun binds: // surface.inc (every interface but openkal.space) and surface_space.inc -// (openkal.space, bound where the implementation provides it). Generated at -// every build from the specification itself, so the list cannot fall behind it. +// (openkal.space). Generated at every build from the specification itself, so +// the list cannot fall behind it. +// +// BOUND BY WHAT THE IMPLEMENTATION PROVIDES (0.6, mcpp >= 2026.11.10.1): the +// interfaces the resolved implementation states (`provides-interfaces', read +// from MCPP_KERNEL_ABI_INTERFACES) are bound; a name of any other interface is +// listed without an address, so that the runner refers to nothing an +// implementation does not have, and an image that requires the interface is +// refused at load (SPEC 12.4). An empty list is "unknown": every interface is +// bound, as before. provided.inc states the same set to the runner's sources. +#include #include #include #include #include +#include #include #include #include @@ -33,15 +43,38 @@ int main() { std::set own{ "kal_env_arg_count", "kal_env_arg", "kal_env_image", "kal_env_loader", "kal_interfaces", "kal_exit", "kal_abort" }; const std::set callbacks{ "kal_task_start", "kal_task_start_sized" }; - std::vector main_names, space_names; + std::vector main_names, space_names, groups; + std::map group_of; std::string group, line; while (std::getline(in, line)) { - if (line.rfind("# openkal.", 0) == 0) { group = line.substr(2, line.find_first_of(" \t", 2) - 2); continue; } + if (line.rfind("# openkal.", 0) == 0) { + group = line.substr(2, line.find_first_of(" \t", 2) - 2); + groups.push_back(group); + continue; + } if (line.rfind("kal_", 0) != 0) continue; while (!line.empty() && (line.back() == '\r' || line.back() == ' ')) line.pop_back(); if (group == "openkal.prof" || group == "openkal.debug") own.insert(line); + group_of[line] = group; (group == "openkal.space" ? space_names : main_names).push_back(line); } + + // The interfaces the implementation provides; none stated is every one. + std::set provided; + if (const char* list = std::getenv("MCPP_KERNEL_ABI_INTERFACES"); list && *list) { + std::string item; + for (const char* c = list;; ++c) { + if (*c == ';' || *c == '\0') { if (!item.empty()) provided.insert(item); item.clear(); } + else item += *c; + if (!*c) break; + } + } else { + provided.insert(groups.begin(), groups.end()); + } + // The specification's self-description is the implementation's in every + // case: it has no bit, and every implementation answers it. + provided.insert("openkal.version"); + auto emit = [&](const char* file, const std::vector& names) { std::ofstream o(std::filesystem::path(out) / file); o << "// Generated by openkal-run's build.mcpp from " << surface.generic_string() << ".\n"; @@ -49,12 +82,29 @@ int main() { // of the calls an image makes (okprof, 0.2) keeps its counts. for (std::size_t i = 0; i < names.size(); ++i) { const auto& n = names[i]; - o << (own.contains(n) ? "OKRUN_OWN(" : callbacks.contains(n) ? "OKRUN_CALLBACK(" : "OKRUN_BIND(") + o << (own.contains(n) ? "OKRUN_OWN(" : callbacks.contains(n) ? "OKRUN_CALLBACK(" : + provided.contains(group_of[n]) ? "OKRUN_BIND(" : "OKRUN_ABSENT(") << n << ", " << i << ")\n"; } }; emit("surface.inc", main_names); emit("surface_space.inc", space_names); + { + // OKRUN_HAS_ for each interface with a bit, and the word of + // the provided ones, which the runner intersects with kal_interfaces(). + std::ofstream o(std::filesystem::path(out) / "provided.inc"); + o << "// Generated by openkal-run's build.mcpp: the interfaces the implementation provides.\n"; + std::string word; + for (const auto& g : groups) { + if (g == "openkal.version") continue; + std::string upper; + for (char c : g.substr(sizeof "openkal." - 1)) upper += static_cast(std::toupper(static_cast(c))); + const bool has = provided.contains(g); + o << "#define OKRUN_HAS_" << upper << " " << (has ? 1 : 0) << "\n"; + if (has) word += (word.empty() ? "" : " | ") + std::string("KAL_IFACE_") + upper; + } + o << "#define OKRUN_COMPILED_INTERFACES (" << (word.empty() ? std::string("0") : word) << ")\n"; + } // A PERFECT HASH OF EVERY NAME (0.4): okrun_resolve is asked once for // each name an image imports, and a comparison with every name in turn diff --git a/run/include/okrun.h b/run/include/okrun.h index 6be83a8..2d5d85e 100644 --- a/run/include/okrun.h +++ b/run/include/okrun.h @@ -126,7 +126,9 @@ unsigned long long okrun_cache_list(void (*each)(const char* name, unsigned long * bytes removed. */ unsigned long long okrun_cache_reclaim(int all); -/* The names this runner binds (the specification's surface), `count' of them. */ +/* The specification's surface: every name an image may import, `count' of + * them, whether or not this system's implementation provides its interface + * (okrun_resolve answers which are bound). */ const char* const* okrun_names(unsigned long* count); /* The address an image's import of `name' is bound to, in the image's diff --git a/run/mcpp.toml b/run/mcpp.toml index c973439..87f3a36 100644 --- a/run/mcpp.toml +++ b/run/mcpp.toml @@ -1,13 +1,14 @@ [package] namespace = "mcpplibs" name = "openkal-run" -version = "0.5.5" +version = "0.6.0" description = "libokrun: the runner's core --- an ok image's names bound to this process's one openkal implementation, in the image's calling convention, and its entry entered." license = "Apache-2.0" authors = ["mcpplibs"] repo = "https://github.com/mcpplibs/openkal-image" -# `[defaults]' (mcpp 2026.11.9.1, SPEC-002 §3.7). -mcpp = ">=2026.11.9.1" +# `[defaults]' (mcpp 2026.11.9.1, SPEC-002 §3.7); the implementation's +# interfaces read by build.mcpp (MCPP_KERNEL_ABI_INTERFACES, 2026.11.10.1). +mcpp = ">=2026.11.10.1" # The runner binds an image's names to this process's one implementation of # openkal: whichever the graph has --- the consumer's, or the one its # platform adds --- else the default of the system below. @@ -25,21 +26,22 @@ linux = "openkal-linux@^0.26.0" macos = "openkal-macos@^0.23.0" windows = "openkal-windows@^0.21.0" -# The interfaces each system's implementation provides decide one unit: where -# openkal.space is provided its names are bound, and elsewhere an image that -# requires it is refused at load, naming it. +# THE INTERFACES THE RUNNER ITSELF CALLS (0.6): an implementation that does not +# provide one of them is refused when the graph is resolved. Every other +# interface is bound where the implementation provides it (build.mcpp reads +# its `provides-interfaces'), and an image that requires one it does not is +# refused at load, naming it (SPEC 12.4). An implementation that states no list +# is taken to provide every interface. +[kernel-abi] +requires-interfaces = [ + "openkal.abort", "openkal.stream", "openkal.memory", "openkal.env", "openkal.time", + "openkal.random", "openkal.fs", "openkal.process", "openkal.task", "openkal.exec", + "openkal.map", +] + [build] sources = ["src/okrun.cppm", "src/run.cpp", "src/bindings.cpp", "src/seal.cpp", "src/prof.cpp"] include_dirs = ["include"] flags = [ { glob = "src/*.cpp", cxxflags = ["-fno-exceptions", "-fno-rtti"] }, ] - -[target.linux.build] -sources = ["src/bindings_space.cpp"] - -[target.macos.build] -sources = ["src/bindings_space.cpp"] - -[target.windows.build] -sources = ["src/no_space.cpp"] diff --git a/run/src/bindings.cpp b/run/src/bindings.cpp index e70e750..517a6b3 100644 --- a/run/src/bindings.cpp +++ b/run/src/bindings.cpp @@ -128,43 +128,46 @@ OKRUN_IMAGE_ABI void own_kal_prof_mark(const char* name, kal_uintptr len, int be OKRUN_IMAGE_ABI kal_intptr own_kal_debug_symbolize(kal_uintptr address, char* out, kal_uintptr cap, kal_uintptr* start) { const kal_intptr n = symbolize(address, out, cap, start); - return n >= 0 ? n : kal_debug_symbolize(address, out, cap, start); + return n >= 0 ? n : impl_debug_symbolize(address, out, cap, start); } // WHAT ENDS THE PROGRAM FOR A FAULT, AND WHAT SAMPLES AND TOUCHES, IS THE // IMPLEMENTATION'S (openkal 0.23, SPEC 12.3): a fault, a timer and a page's // first execution are the environment's to see, and this runner forwards. A // measurement of this runner's own that samples or records touches holds them, // and the program asking is told kal_err_again. -OKRUN_IMAGE_ABI kal_uintptr own_kal_debug_props(void) { return kal_debug_props(); } -OKRUN_IMAGE_ABI kal_uintptr own_kal_prof_props(void) { return kal_prof_props(); } +OKRUN_IMAGE_ABI kal_uintptr own_kal_debug_props(void) { return impl_debug_props(); } +OKRUN_IMAGE_ABI kal_uintptr own_kal_prof_props(void) { return impl_prof_props(); } OKRUN_IMAGE_ABI int own_kal_prof_sample_begin(kal_u32 per_second) { if (profile_samples()) return kal_err_again; - return kal_prof_sample_begin(per_second); + return impl_prof_sample_begin(per_second); } OKRUN_IMAGE_ABI kal_intptr own_kal_prof_sample_take(kal_prof_sample* out, kal_uintptr count) { if (profile_samples()) return 0; - return kal_prof_sample_take(out, count); + return impl_prof_sample_take(out, count); } OKRUN_IMAGE_ABI int own_kal_prof_sample_end(void) { if (profile_samples()) return kal_err_invalid; - return kal_prof_sample_end(); + return impl_prof_sample_end(); } OKRUN_IMAGE_ABI int own_kal_prof_touch_begin(const void* at, kal_uintptr size) { if (profile_touches()) return kal_err_again; - return kal_prof_touch_begin(at, size); + return impl_prof_touch_begin(at, size); } OKRUN_IMAGE_ABI kal_intptr own_kal_prof_touch_take(kal_uintptr* out, kal_uintptr count) { if (profile_touches()) return 0; - return kal_prof_touch_take(out, count); + return impl_prof_touch_take(out, count); } OKRUN_IMAGE_ABI int own_kal_prof_touch_end(void) { if (profile_touches()) return kal_err_invalid; - return kal_prof_touch_end(); + return impl_prof_touch_end(); } #define OKRUN_BIND(n, i) { #n, reinterpret_cast(&present<&::n>::call) }, #define OKRUN_OWN(n, i) { #n, reinterpret_cast(&own_##n) }, #define OKRUN_CALLBACK(n, i) { #n, reinterpret_cast(&callback_##n) }, +// A name of an interface the implementation does not provide: listed, so that +// every name keeps its position, and bound to nothing. +#define OKRUN_ABSENT(n, i) { #n, nullptr }, // At namespace scope, so that it is made once at start and no guard is asked // of a C++ run-time this library may be linked without. @@ -179,13 +182,45 @@ const binding g_counted[] = { }; static_assert(sizeof g_table / sizeof g_table[0] <= kProfiledNames, "the call counts hold every name"); +// openkal.space. A started context begins at an address in a copy of the +// space, not by a call, so the entry is passed as it is: the systems that +// provide the interface share the image's convention. +#undef OKRUN_BIND +#undef OKRUN_OWN +#undef OKRUN_CALLBACK +#define OKRUN_BIND(n, i) { #n, reinterpret_cast(&present<&::n>::call) }, +#define OKRUN_OWN(n, i) OKRUN_BIND(n, i) +#define OKRUN_CALLBACK(n, i) OKRUN_BIND(n, i) +const binding g_space[] = { +#include "surface_space.inc" +}; + } // namespace -kal_u64 own_interfaces() { return kal_interfaces() | KAL_IFACE_PROF | KAL_IFACE_DEBUG; } +// WHAT THIS RUNNER PROVIDES (SPEC 12.3): the implementation's answer where this +// runner binds the interface, and the loader's own interfaces. An +// implementation that answers more than its manifest states is not trusted +// with a name the runner never bound. +kal_u64 own_interfaces() { + return (kal_interfaces() & OKRUN_COMPILED_INTERFACES) | KAL_IFACE_PROF | KAL_IFACE_DEBUG; +} const binding* bindings(unsigned long& count) { count = sizeof g_table / sizeof g_table[0]; return profile_calls() ? g_counted : g_table; } +const binding* space_bindings(unsigned long& count) { + count = sizeof g_space / sizeof g_space[0]; + return g_space; +} + +#if OKRUN_HAS_SPACE +kal_uintptr space_props() { return kal_space_props(); } +void* space_props_binding() { return reinterpret_cast(&present<&::kal_space_props>::call); } +#else +kal_uintptr space_props() { return 0; } +void* space_props_binding() { return nullptr; } +#endif + } // namespace okrun diff --git a/run/src/bindings_space.cpp b/run/src/bindings_space.cpp deleted file mode 100644 index f09a4a1..0000000 --- a/run/src/bindings_space.cpp +++ /dev/null @@ -1,32 +0,0 @@ -// openkal.space, bound where the implementation provides it (the manifest -// selects this unit for those systems). A started context begins at an address -// in a copy of the space, not by a call, so the entry is passed as it is: the -// systems that provide the interface share the image's convention. -#include -#include "image_abi.h" -#include "runner.h" - -namespace okrun { - -namespace { -template struct present; -template -struct present { OKRUN_IMAGE_ABI static R call(A... a) { return F(a...); } }; - -#define OKRUN_BIND(n, i) { #n, reinterpret_cast(&present<&::n>::call) }, -#define OKRUN_OWN(n, i) OKRUN_BIND(n, i) -#define OKRUN_CALLBACK(n, i) OKRUN_BIND(n, i) -const binding g_space[] = { -#include "surface_space.inc" -}; -} // namespace - -const binding* space_bindings(unsigned long& count) { - count = sizeof g_space / sizeof g_space[0]; - return g_space; -} - -kal_uintptr space_props() { return kal_space_props(); } -void* space_props_binding() { return reinterpret_cast(&present<&::kal_space_props>::call); } - -} // namespace okrun diff --git a/run/src/no_space.cpp b/run/src/no_space.cpp deleted file mode 100644 index 410823e..0000000 --- a/run/src/no_space.cpp +++ /dev/null @@ -1,10 +0,0 @@ -// openkal.space is not provided here: the manifest selects this unit instead -// of bindings_space.cpp, and an image that requires the interface is refused -// at load, naming it (SPEC 12.4). -#include "runner.h" - -namespace okrun { -const binding* space_bindings(unsigned long& count) { count = 0; return nullptr; } -kal_uintptr space_props() { return 0; } -void* space_props_binding() { return nullptr; } -} // namespace okrun diff --git a/run/src/prof.cpp b/run/src/prof.cpp index 0e7543c..40f72c6 100644 --- a/run/src/prof.cpp +++ b/run/src/prof.cpp @@ -37,6 +37,37 @@ extern "C" void okrun_profile(const char* spec); namespace okrun { + +// THE IMPLEMENTATION'S MEASUREMENT AND NAMES (openkal.prof, openkal.debug), +// referred to only where it provides them (0.6): an implementation without +// them is not asked to define a name it does not have (SPEC 6.1). +#if OKRUN_HAS_PROF +kal_uintptr impl_prof_props() { return kal_prof_props(); } +int impl_prof_sample_begin(kal_u32 per_second) { return kal_prof_sample_begin(per_second); } +kal_intptr impl_prof_sample_take(kal_prof_sample* out, kal_uintptr count) { return kal_prof_sample_take(out, count); } +int impl_prof_sample_end() { return kal_prof_sample_end(); } +int impl_prof_touch_begin(const void* at, kal_uintptr size) { return kal_prof_touch_begin(at, size); } +kal_intptr impl_prof_touch_take(kal_uintptr* out, kal_uintptr count) { return kal_prof_touch_take(out, count); } +int impl_prof_touch_end() { return kal_prof_touch_end(); } +#else +kal_uintptr impl_prof_props() { return 0; } +int impl_prof_sample_begin(kal_u32) { return kal_err_not_supported; } +kal_intptr impl_prof_sample_take(kal_prof_sample*, kal_uintptr) { return 0; } +int impl_prof_sample_end() { return kal_err_invalid; } +int impl_prof_touch_begin(const void*, kal_uintptr) { return kal_err_not_supported; } +kal_intptr impl_prof_touch_take(kal_uintptr*, kal_uintptr) { return 0; } +int impl_prof_touch_end() { return kal_err_invalid; } +#endif +#if OKRUN_HAS_DEBUG +kal_uintptr impl_debug_props() { return kal_debug_props(); } +kal_intptr impl_debug_symbolize(kal_uintptr address, char* out, kal_uintptr cap, kal_uintptr* start) { + return kal_debug_symbolize(address, out, cap, start); +} +#else +kal_uintptr impl_debug_props() { return 0; } +kal_intptr impl_debug_symbolize(kal_uintptr, char*, kal_uintptr, kal_uintptr*) { return -kal_err_not_found; } +#endif + namespace { kal_uintptr length(const char* s) { kal_uintptr n = 0; while (s && s[n]) ++n; return n; } @@ -391,7 +422,7 @@ int touch_module(void* base, const void* phdrs, okld_u32 phnum, const char*, voi for (okld_u32 k = 0; k < phnum; ++k) { if (p[k].type != 1 || !(p[k].flags & 1)) continue; // PT_LOAD, PF_X const kal_u64 lo = p[k].vaddr & ~(g - 1), hi = (p[k].vaddr + p[k].memsz + g - 1) & ~(g - 1); - if (kal_prof_touch_begin(static_cast(base) + lo, hi - lo) == kal_ok) g_touching = true; + if (impl_prof_touch_begin(static_cast(base) + lo, hi - lo) == kal_ok) g_touching = true; } return 0; } @@ -466,7 +497,7 @@ void frame_name(kal_uintptr pc, bool innermost, out_buffer& into) { kal_uintptr start = 0; const kal_uintptr at = innermost ? pc : pc - 1; kal_intptr n = symbolize(at, name, sizeof name, &start); - if (n < 0) n = kal_debug_symbolize(at, name, sizeof name, &start); + if (n < 0) n = impl_debug_symbolize(at, name, sizeof name, &start); if (n > 0) into.put(name, static_cast(n) < sizeof name ? static_cast(n) : sizeof name); else { into.put("0x"); into.number(pc, 16); } } @@ -500,7 +531,7 @@ void profile_phase(const char* name) { void profile_begin_run() { const int m = mode(); - if (m & asked_samples) kal_prof_sample_begin(g_sample_rate); + if (m & asked_samples) impl_prof_sample_begin(g_sample_rate); if (m & asked_touch) okld_iterate(&touch_module, nullptr); } @@ -687,7 +718,7 @@ void profile_finish() { // A TIMELINE beside them: each sample's time, from the start, and its // stack's place in the list --- the input of `okprof report --format=trace'. if (mode() & asked_samples) { - kal_prof_sample_end(); + impl_prof_sample_end(); folded f; static kal_prof_sample batch[256]; struct when { kal_u64 ns; kal_u32 text; kal_u64 context; }; @@ -695,7 +726,7 @@ void profile_finish() { auto* timeline = static_cast(kal_alloc(kTimeline * sizeof(when), 16)); kal_u64 moments = 0; for (;;) { - const kal_intptr got = kal_prof_sample_take(batch, 256); + const kal_intptr got = impl_prof_sample_take(batch, 256); if (got <= 0) break; for (kal_intptr i = 0; i < got; ++i) { out_buffer one; @@ -760,7 +791,7 @@ void profile_finish() { b.put(",\n \"touches\": ["); bool any = false; for (;;) { - const kal_intptr got = kal_prof_touch_take(pages, 4096); + const kal_intptr got = impl_prof_touch_take(pages, 4096); if (got <= 0) break; for (kal_intptr i = 0; i < got; ++i) { const char* image = nullptr; @@ -773,7 +804,7 @@ void profile_finish() { b.put(" }"); } } - kal_prof_touch_end(); + impl_prof_touch_end(); b.put(any ? "\n ]" : "]"); } b.put("\n}\n"); diff --git a/run/src/run.cpp b/run/src/run.cpp index 80b183f..2369d84 100644 --- a/run/src/run.cpp +++ b/run/src/run.cpp @@ -222,10 +222,9 @@ bool admit(const void* bytes, unsigned long long size, const char* name, const o // WHAT THE IMAGE REQUIRES, AGAINST WHAT THIS IMPLEMENTATION PROVIDES: the // loader reports at load what a linker reports at link (SPEC 12.4). // The loader's own interfaces are provided whatever the implementation - // provides (SPEC 12.3, 0.2). - unsigned long long provided = kal_interfaces() | KAL_IFACE_PROF | KAL_IFACE_DEBUG; - unsigned long n = 0; - if (!space_bindings(n) || n == 0) provided &= ~(1ull << 13); + // provides (SPEC 12.3, 0.2); the implementation's are those it answers and + // this runner binds (0.6). + const unsigned long long provided = own_interfaces(); if (const unsigned long long missing = info.interfaces & ~provided) { text t(why, cap); t << name << " requires"; diff --git a/run/src/runner.h b/run/src/runner.h index ee82837..cee0cd1 100644 --- a/run/src/runner.h +++ b/run/src/runner.h @@ -1,6 +1,9 @@ #pragma once #include #include +#include +// OKRUN_HAS_ and OKRUN_COMPILED_INTERFACES (build.mcpp, 0.6). +#include "provided.inc" namespace okrun { @@ -55,14 +58,28 @@ kal_intptr symbolize(kal_uintptr address, char* out, kal_uintptr cap, kal_uintpt // A name as openkal names things: the supplied directory that is the longest // prefix of it, and the rest (src/run.cpp). bool locate_path(const char* path, kal_dir& dir, const char*& rest); +// openkal.space's names, each with a null address where the implementation +// does not provide the interface (src/bindings.cpp). const binding* space_bindings(unsigned long& count); // openkal.space's word of properties and its binding where it is bound; zero -// and null where it is not (src/bindings_space.cpp, src/no_space.cpp). +// and null where it is not. kal_uintptr space_props(); void* space_props_binding(); -// The word of interfaces an image is told: the implementation's, and the -// loader's own (openkal.prof, openkal.debug; SPEC 12.3). +// The word of interfaces an image is told: the implementation's that this +// runner binds, and the loader's own (openkal.prof, openkal.debug; SPEC 12.3). kal_u64 own_interfaces(); +// THE IMPLEMENTATION'S openkal.prof AND openkal.debug, where it provides them +// (src/prof.cpp, 0.6). Elsewhere the properties are empty, a request is +// kal_err_not_supported and an address is named by the images alone. +kal_uintptr impl_prof_props(); +int impl_prof_sample_begin(kal_u32 per_second); +kal_intptr impl_prof_sample_take(kal_prof_sample* out, kal_uintptr count); +int impl_prof_sample_end(); +int impl_prof_touch_begin(const void* at, kal_uintptr size); +kal_intptr impl_prof_touch_take(kal_uintptr* out, kal_uintptr count); +int impl_prof_touch_end(); +kal_uintptr impl_debug_props(); +kal_intptr impl_debug_symbolize(kal_uintptr address, char* out, kal_uintptr cap, kal_uintptr* start); // --- a snapshot of an image's initialisers (0.4, src/seal.cpp) --------------- // diff --git a/run/src/seal.cpp b/run/src/seal.cpp index a3b8378..156b29c 100644 --- a/run/src/seal.cpp +++ b/run/src/seal.cpp @@ -171,6 +171,17 @@ template struct fact; template struct fact { OKRUN_IMAGE_ABI static R call(A... a) { return F(a...); } }; +// A FACT OF AN INTERFACE THIS RUNNER DOES NOT BIND IS ZERO (0.6), as an +// implementation without the property answers, and names nothing of it. +#define OKRUN_FACT_OF(has, type, n) OKRUN_FACT_OF_(has, type, n) +#define OKRUN_FACT_OF_(has, type, n) OKRUN_FACT_##has(type, n) +#define OKRUN_FACT_1(type, n) OKRUN_IMAGE_ABI type fact_##n(void) { return ::n(); } +#define OKRUN_FACT_0(type, n) OKRUN_IMAGE_ABI type fact_##n(void) { return 0; } +OKRUN_FACT_OF(OKRUN_HAS_REGION, kal_uintptr, kal_region_props) +OKRUN_FACT_OF(OKRUN_HAS_TIMEOUT, kal_u64, kal_timeout_granularity) +OKRUN_FACT_OF(OKRUN_HAS_NET, kal_uintptr, kal_net_props) +OKRUN_FACT_OF(OKRUN_HAS_DATAGRAM, kal_uintptr, kal_datagram_props) + // Every other name: the snapshot is refused, naming it. binding g_names[512]; unsigned long g_count = 0; @@ -188,6 +199,7 @@ template OKRUN_IMAGE_ABI void refuse() { asked(g_names[I].name #define OKRUN_BIND(n, i) { #n, reinterpret_cast(&refuse) }, #define OKRUN_OWN(n, i) OKRUN_BIND(n, i) #define OKRUN_CALLBACK(n, i) OKRUN_BIND(n, i) +#define OKRUN_ABSENT(n, i) OKRUN_BIND(n, i) const binding g_main[] = { #include "surface.inc" }; @@ -220,17 +232,17 @@ const binding g_allowed[] = { { "kal_memory_granularity", reinterpret_cast(&fact<&::kal_memory_granularity>::call) }, { "kal_exec_granularity", reinterpret_cast(&fact<&::kal_exec_granularity>::call) }, { "kal_exec_props", reinterpret_cast(&fact<&::kal_exec_props>::call) }, - { "kal_region_props", reinterpret_cast(&fact<&::kal_region_props>::call) }, + { "kal_region_props", reinterpret_cast(&fact_kal_region_props) }, { "kal_time_props", reinterpret_cast(&fact<&::kal_time_props>::call) }, { "kal_time_monotonic_granularity", reinterpret_cast(&fact<&::kal_time_monotonic_granularity>::call) }, - { "kal_timeout_granularity", reinterpret_cast(&fact<&::kal_timeout_granularity>::call) }, + { "kal_timeout_granularity", reinterpret_cast(&fact_kal_timeout_granularity) }, { "kal_random_props", reinterpret_cast(&fact<&::kal_random_props>::call) }, { "kal_process_props", reinterpret_cast(&fact<&::kal_process_props>::call) }, { "kal_task_props", reinterpret_cast(&fact<&::kal_task_props>::call) }, - { "kal_net_props", reinterpret_cast(&fact<&::kal_net_props>::call) }, - { "kal_datagram_props", reinterpret_cast(&fact<&::kal_datagram_props>::call) }, - { "kal_prof_props", reinterpret_cast(&fact<&::kal_prof_props>::call) }, - { "kal_debug_props", reinterpret_cast(&fact<&::kal_debug_props>::call) }, + { "kal_net_props", reinterpret_cast(&fact_kal_net_props) }, + { "kal_datagram_props", reinterpret_cast(&fact_kal_datagram_props) }, + { "kal_prof_props", reinterpret_cast(&fact<&impl_prof_props>::call) }, + { "kal_debug_props", reinterpret_cast(&fact<&impl_debug_props>::call) }, { "kal_fs_max_name", reinterpret_cast(&fact<&::kal_fs_max_name>::call) }, { "kal_env_system", reinterpret_cast(&fact<&::kal_env_system>::call) }, }; @@ -238,8 +250,7 @@ const binding g_allowed[] = { void fill_names() { if (g_count) return; for (const binding& b : g_main) g_names[g_count++] = b; - unsigned long space = 0; - if (space_bindings(space) && space) + if (OKRUN_HAS_SPACE) for (const binding& b : g_space) g_names[g_count++] = b; for (const binding& a : g_allowed) for (unsigned long i = 0; i < g_count; ++i) @@ -345,17 +356,17 @@ kal_u32 environment_facts(kal_u64 asked) { f.word(kal_memory_granularity()); f.word(kal_exec_granularity()); f.word(kal_exec_props()); - f.word(kal_region_props()); + f.word(fact_kal_region_props()); f.word(kal_time_props()); f.word(kal_time_monotonic_granularity()); - f.word(kal_timeout_granularity()); + f.word(fact_kal_timeout_granularity()); f.word(kal_random_props()); f.word(kal_process_props()); f.word(kal_task_props()); - f.word(kal_net_props()); - f.word(kal_datagram_props()); - f.word(kal_prof_props()); - f.word(kal_debug_props()); + f.word(fact_kal_net_props()); + f.word(fact_kal_datagram_props()); + f.word(impl_prof_props()); + f.word(impl_debug_props()); f.word(kal_fs_max_name()); f.word(space_props()); f.word(asked); diff --git a/tools/mcpp.toml b/tools/mcpp.toml index 4502ad0..cafd238 100644 --- a/tools/mcpp.toml +++ b/tools/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal-tools" -version = "0.5.5" +version = "0.6.0" description = "okrun, which runs an ok image on this system, and okld, the format's tool on a build machine (check, note, sign, inspect)." license = "Apache-2.0" authors = ["mcpplibs"]