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
84 changes: 65 additions & 19 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ env:
# `em++`'s C compiler is `emcc` and not `em` (a C translation unit in the
# conformance suite does not compile without that), and the wasm target rows
# resolve the emsdk payload.
MCPP_VERSION: 2026.9.11.4
MCPP_VERSION: 2026.9.12.2
XLINGS_VERSION: v2026.8.17.2
XLINGS_NON_INTERACTIVE: '1'

Expand Down Expand Up @@ -167,8 +167,9 @@ jobs:
# The set is named rather than `full`: clause 6.1 says an implementation
# provides an interface in whole or not at all, and a group this platform
# does not have is an absence rather than a deviation. `task` is not in
# the set -- see the task-gate step below for why it needs a link this
# suite cannot currently be given.
# the set: the suite's consumer manifest is generated by the
# specification's runner, which does not state the ABI switch threads
# need. The task-gate step below builds and runs a task program with it.
- name: Every interface this platform has, examined
run: |
OPENKAL_CONFORMANCE_RUNNER="$NODE_BIN" \
Expand Down Expand Up @@ -288,20 +289,24 @@ jobs:
done
test "$fail" -eq 0

# THE TASK GATE, IN BOTH DIRECTIONS.
# THE TASK GATE, IN THREE DIRECTIONS.
#
# `openkal.task` needs `-pthread`, which selects a different C library
# build and a different memory model -- a property of the whole link. So
# the interface is carried by a feature: without it the translation unit
# is empty and the eight symbols do not exist, which is the same
# treatment the three absent interfaces get and for the same reason.
# build and a different memory model -- a property of the whole link, and
# therefore of the consumer's root manifest (`[target.<selector>.abi]
# threads = true`, mcpp 2026.9.12.2). The `threads` feature states that it
# needs the switch. Asserted:
#
# Both directions are asserted, because a gate is only a gate if the
# other side of it differs. The conformance suite cannot yet be built
# with this switch -- it would have to reach the specification package's
# modules too, and mcpp has no whole-graph flag channel -- so what is
# asserted here is the gating, and the README records the rest.
- name: The task interface exists in a -pthread link and in no other
# 1. without the feature and the switch, no task symbol exists;
# 2. the feature without the switch is refused before anything compiles,
# naming the feature;
# 3. with both, the eight symbols exist and a program that starts a task
# and joins it exits 0 under node.
#
# The consumer names `openkal` by path, as the implementation's manifest
# now does (the step above): mcpp refuses one package reached both by path
# and by version.
- name: The task interface needs the ABI switch, is refused without it, and runs with it
run: |
set -euo pipefail
rm -rf target
Expand All @@ -311,14 +316,55 @@ jobs:
test "$n" -eq 0 || { echo "::error::$n kal_task symbols without the feature"; exit 1; }
echo " ok no task symbol without the feature"

rm -rf target
mcpp build --target wasm32-emscripten --features threads
objs=$(find target -path '*/obj/*' -name '*.o' ! -name '*.m.o' | tr '\n' ' ')
work="$RUNNER_TEMP/tasks"
impl="$(pwd)"
rm -rf "$work"; mkdir -p "$work/src"
cat > "$work/src/main.cpp" << 'CPP'
import openkal.task;
static void entry(void* arg) { *static_cast<int*>(arg) = 1; }
int main() {
// KAL_TASK_PROP_PARALLEL is bit 1 (openkal/task.h); a macro does not
// cross a module.
if ((kal_task_props() & 2u) == 0) return 2;
int ran = 0;
kal_task t{};
if (kal_task_start(&entry, &ran, &t) != 0) return 3;
if (kal_task_join(t) != 0) return 4;
return ran == 1 ? 0 : 5;
}
CPP
manifest() {
cat > "$work/mcpp.toml" << TOML
[package]
name = "tasks"
version = "0.1.0"

[dependencies]
openkal = { path = "$impl/.spec" }
openkal-emscripten = { path = "$impl", features = ["threads"] }
$1
TOML
}

manifest ""
if ( cd "$work" && mcpp build --target wasm32-emscripten ) > refused.log 2>&1; then
cat refused.log; echo "::error::the threads feature built without the ABI switch"; exit 1
fi
grep -q "requires the artefact's ABI to have threads (feature \`threads\`)" refused.log \
|| { cat refused.log; echo "::error::the refusal does not name the feature"; exit 1; }
echo " ok the feature without the switch is refused, naming the feature"

manifest "$(printf '\n[target.%s.abi]\nthreads = true\n' "'cfg(os = \"emscripten\")'")"
( cd "$work" && mcpp build --target wasm32-emscripten )
objs=$(find "$work/target" -path '*/obj/*' -name '*.o' ! -name '*.m.o' | tr '\n' ' ')
n=$("$NM" --defined-only $objs 2>/dev/null \
| awk '$2=="T"||$2=="W"{print $3}' | sed 's/^_//' \
| grep -c '^kal_task' || true)
test "$n" -eq 8 || { echo "::error::expected 8 kal_task symbols with the feature, found $n"; exit 1; }
echo " ok eight task symbols with the feature"
test "$n" -eq 8 || { echo "::error::expected 8 kal_task symbols with the switch, found $n"; exit 1; }
echo " ok eight task symbols with the switch"
( cd "$work" && timeout 300 mcpp run --target wasm32-emscripten ) \
|| { echo "::error::the task program did not exit 0 under node"; exit 1; }
echo " ok a task starts, runs and joins under node"

- name: Restore the manifest
if: always()
Expand Down
44 changes: 31 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,30 +61,48 @@ So the interface is carried by a feature:

```toml
[dependencies]
openkal-emscripten = { version = "0.1.0", features = ["threads"] }
openkal-emscripten = { version = "0.1.1", features = ["threads"] }
```

Without it, `src/threads/task.cpp` compiles to nothing, the eight `kal_task_*`
symbols do not exist, and `kal_interfaces()` does not claim the interface --
the same treatment the three absent interfaces get, for the same reason.

**This feature is not yet usable end to end, and the limitation is the build
tool's rather than this package's.** `-pthread` changes the module
configuration of *every* translation unit in the link, including the
specification package's, and mcpp has no channel for a flag that applies to a
whole dependency graph: a feature contributes sources, defines and per-glob
compile flags, and `[build] ldflags` reaches the root package only. Measured
2026-09-11 with the feature active and `-pthread` on the consumer:
**The switch belongs to the artefact, and the root manifest states it.**
`-pthread` changes the module configuration of *every* translation unit in the
link, including the specification package's, so it cannot be a flag of this
package's own units. With mcpp 2026.9.12.2 or later, the consumer writes

```toml
[target.'cfg(os = "emscripten")'.abi]
threads = true
```

and the switch reaches the standard library module, every translation unit of
every package, and the link. The `threads` feature states that it needs the
switch (`requires_abi = { threads = true }`), so a consumer that activates the
feature without the table is refused before anything compiles, naming the
feature and the table. Measured 2026-09-12 with emsdk 6.0.9 and a development
build of mcpp 2026.9.12.2: with the table, a program that starts a task and
joins it exits 0 under node, and without it the build stops at

```
error: `openkal-emscripten` requires the artefact's ABI to have threads (feature `threads`), and this build does not state it.
```

An earlier version of this section, measured 2026-09-11, concluded that mcpp had
no channel for a flag that applies to a whole dependency graph. What had been
measured was `-pthread` in the consumer's per-package `cxxflags`:

```
error: POSIX thread support was disabled in precompiled file
'.../pcm.cache/openkal.types.pcm' but is currently enabled
```

which is the specification package's module, compiled without the switch. The
interface word is already correct for a link that manages it; closing the gap
is an engine change of the same shape as the existing whole-graph runtime
flags, and it is recorded rather than worked around.
That channel does not reach the specification package's module. A graph-wide
channel did exist (`[build] dialect_cxxflags`); what was missing was a way to
scope it to one target and to let this feature state its requirement, which the
typed table provides.

## Conformance

Expand Down Expand Up @@ -128,7 +146,7 @@ implementation in its source:
openkal = "0.12.0"

[target.'cfg(os = "emscripten")'.dependencies]
openkal-emscripten = "0.1.0"
openkal-emscripten = "0.1.1"
```

## Licence
Expand Down
43 changes: 19 additions & 24 deletions mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
namespace = "mcpplibs"
name = "openkal-emscripten"
version = "0.1.0"
version = "0.1.1"
description = "An implementation of openkal for Emscripten, written ABOVE a C library rather than beneath one, because on this platform there is no kernel to issue calls to."
license = "Apache-2.0"

Expand Down Expand Up @@ -59,29 +59,24 @@ default = []
# capability is not a property of the machine this runs on, and it is not
# discoverable at run time by anything this package can ask.
#
# Declaring it as a feature puts the decision where the rest of the link is
# decided, and `kal_task_props` reports the consequence: with this feature the
# word carries KAL_TASK_PROP_PARALLEL, and without it the interface is still
# provided and `kal_task_start` reports kal_err_not_supported -- which is
# clause 6.2's third time, a capability word read before use, rather than the
# shape the specification forbids.
# THE SWITCH IS THE ARTEFACT'S, SO THE ROOT MANIFEST STATES IT. `-pthread`
# changes the module configuration of every translation unit in the link, the
# specification package's included, so it cannot be a flag of this package's
# own units. mcpp 2026.9.12.2 carries it as a typed member of the root's target
# table,
#
# `-pthread` ON THIS PACKAGE'S OWN UNITS, AND NOT A DEFINE OF ITS OWN. Passing
# the switch is what makes Emscripten define `__EMSCRIPTEN_PTHREADS__`, so the
# code keys on that rather than on a name this manifest invented: one
# mechanism, and it is the compiler's own answer to "was this compiled with
# threads" instead of a second word that could disagree with it.
# [target.'cfg(os = "emscripten")'.abi]
# threads = true
#
# THE LINK HALF IS THE CONSUMER'S, AND THAT IS A LIMITATION WORTH NAMING. A
# feature contributes build INPUTS -- sources, defines, flags -- and mcpp's
# feature axis has no `ldflags`, so this cannot put `-pthread` on the link
# line. A project that activates this feature must also write
# which reaches the standard library module, every translation unit and the
# link. This feature states that it needs the switch: a build that activates it
# without the table is refused before anything compiles, naming the feature and
# the table.
#
# [build]
# ldflags = ["-pthread"]
#
# and a project that forgets fails at LINK naming the pthread symbols, which
# is a loud failure rather than a silent single-threaded program.
threads = { flags = [
{ glob = "src/**", cxxflags = ["-pthread"] },
] }
# `kal_task_props` reports the consequence: with the switch the word carries
# KAL_TASK_PROP_PARALLEL, and without it `kal_task_start` reports
# kal_err_not_supported -- clause 6.2's third time, a capability word read
# before use. The code keys on `__EMSCRIPTEN_PTHREADS__`, which the compiler
# defines when `-pthread` is passed: one mechanism, and it is the compiler's own
# answer to "was this compiled with threads".
threads = { requires_abi = { threads = true } }
Loading