diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cda4011..7964d2b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -122,7 +122,21 @@ jobs: # fails to compile and the diagnostic names it. list="$(grep -vE '^[[:space:]]*(#|$)' .spec/SURFACE.txt | sort -u)" { - for m in types abort stream memory env time fs process task; do + # ⚠️ THE MODULE LIST IS DERIVED, NOT WRITTEN OUT. + # + # It used to be written out, and it fell out of step the first time + # the specification gained an interface: SURFACE.txt listed four + # `kal_exec_*' names, no `import openkal.exec' was emitted, and the + # generated test failed with four undeclared identifiers --- which + # names the symptom (a name is missing) and not the cause (a list + # here was not updated). + # + # SURFACE.txt groups its names under `# openkal.' + # headings, so the list it already carries is the list this needs. + # `types' is not a group there --- it declares no kal_ name --- and + # is therefore named separately. + echo "import openkal.types;" + for m in $(grep -oE '^# openkal\.[a-z]+' .spec/SURFACE.txt | cut -d. -f2); do echo "import openkal.$m;" done echo 'const void *const surface[] = {' diff --git a/mcpp.toml b/mcpp.toml index 0402a01..ea03d8b 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -4,11 +4,21 @@ name = "openkal-linux" version = "0.5.1" description = "The reference implementation of openkal for Linux, written on the kernel's own system-call interface so that it can be placed beneath a C library as well as above one." license = "Apache-2.0" + +# The layer this package supplies, in the vocabulary the engine resolves. +# +# `mcpp:kernel-abi` names the platform interface a C library sits on. On a +# traditional stack that seam is unnamed — a C library issues system calls or +# calls the platform's own entry points directly — and naming it is what lets +# one C library sit above several platforms. `=openkal` is the interface this +# package answers to; several packages answer to it and the engine knows none +# of them by name. +provides = ["mcpp:kernel-abi=openkal"] authors = ["mcpplibs"] repo = "https://github.com/mcpplibs/openkal-linux" [dependencies] -openkal = "0.5.2" +openkal = { git = "https://github.com/mcpplibs/openkal", branch = "feat/openkal-closure" } # The package contributes definitions and no modules. The interface it # implements is declared by the specification package, which this package diff --git a/src/exec.cpp b/src/exec.cpp new file mode 100644 index 0000000..bef827d --- /dev/null +++ b/src/exec.cpp @@ -0,0 +1,61 @@ +#include "sys.h" +#include + +// openkal.exec on this kernel. +// +// A mapping obtained writable and made executable afterwards. The kernel places +// no condition upon either step, so this implementation provides the interface +// unconditionally --- unlike one of the other systems, where the same interface +// is available only to a program produced in a particular way, which clause 6.5 +// covers and which is not this system's case. +// +// The reservation is not made executable at the outset even though this kernel +// would permit it. Two of the three environments the specification targets +// refuse a mapping that is both, so an implementation that returned one here +// would be offering a program a shape it could not use elsewhere --- and the +// program would discover that only on the other system. The interface states +// the narrower contract and this implementation keeps to it. + +namespace { + +constexpr okl_uptr kPage = 4096; + +okl_uptr round_up(okl_uptr n, okl_uptr to) { return (n + to - 1) & ~(to - 1); } + +} // namespace + +extern "C" { + +void* kal_exec_alloc(kal_uintptr size) { + if (size == 0) return nullptr; + const okl_uptr bytes = round_up(static_cast(size), kPage); + const okl_long r = okl::sys(okl::nr_mmap, 0, static_cast(bytes), + okl::prot_read | okl::prot_write, + okl::map_private | okl::map_anonymous, -1, 0); + if (okl::failed(r)) return nullptr; + return reinterpret_cast(r); +} + +int kal_exec_publish(void* p, kal_uintptr size) { + if (p == nullptr || size == 0) return kal_err_invalid; + const okl_uptr bytes = round_up(static_cast(size), kPage); + const okl_long r = okl::sys(okl::nr_mprotect, reinterpret_cast(p), + static_cast(bytes), + okl::prot_read | okl::prot_exec); + if (okl::failed(r)) return okl::translate(r); + return kal_ok; +} + +void kal_exec_free(void* p, kal_uintptr size) { + if (p == nullptr || size == 0) return; + const okl_uptr bytes = round_up(static_cast(size), kPage); + okl::sys(okl::nr_munmap, reinterpret_cast(p), + static_cast(bytes)); +} + +// A published region may be reserved for writing again: this kernel's +// protection call is not one-way. The position is set accordingly, and a +// caller that must change published bytes need not abandon the region. +const kal_uintptr kal_exec_props = KAL_EXEC_PROP_REPUBLISH; + +} // extern "C" diff --git a/src/sys.h b/src/sys.h index 9aed92d..6352116 100644 --- a/src/sys.h +++ b/src/sys.h @@ -197,7 +197,7 @@ enum : okl_long { o_creat = 0100, o_excl = 0200, o_trunc = 01000, o_append = 02000, o_directory = 0200000, o_cloexec = 02000000, o_nofollow = 0400000, at_fdcwd = -100, at_removedir = 0x200, at_symlink_nofollow = 0x100, - prot_read = 1, prot_write = 2, prot_none = 0, + prot_read = 1, prot_write = 2, prot_exec = 4, prot_none = 0, map_private = 2, map_anonymous = 0x20, map_stack = 0x20000, clock_monotonic = 1, clock_realtime = 0, futex_wait = 0, futex_wake = 1, futex_private = 128,