Skip to content

Commit 74afb88

Browse files
committed
0.4.0 --- the region a context stands on, published by the platform
`kal_task_stack` reports the stack of the CALLING context, and this platform publishes it per context: the runtime that starts a thread calls `emscripten_stack_set_limits` for it before its entry runs, so `emscripten_stack_get_base` and `emscripten_stack_get_end` answer about the context that asks and no record is kept here. The stack grows down, so the end is the low address and the base the high one, and the region is the pair. THE END IS THE END OF THE STACK AND NOT OF THE MEMORY. Below it is the guard Emscripten places for a thread that overflowed, so the answer begins at the end rather than at the bottom of what the thread was given --- which is what a caller that walked to the boundary would otherwise walk into. The continuous integration gate that already builds a task program under `-pthread` now asks both of its contexts for their own region and asserts that each contains a local of that context, and the count it asserts moves from eight `kal_task_*` symbols to nine. Measured here before it was pushed: the gate's program builds for `wasm32-emscripten`, the objects carry the nine names, and it exits 0 under node --- so both the main thread and a pthread are told a region that contains the context that asked.
1 parent ff3b56e commit 74afb88

4 files changed

Lines changed: 86 additions & 15 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ name: CI
1212
# does not have fails at LINK naming the symbol, which is
1313
# clause 6.2's second time and is the mechanism rather than a
1414
# defect
15-
# task gating the eight kal_task_* symbols exist in a `-pthread` link and
15+
# task gating the nine kal_task_* symbols exist in a `-pthread` link and
1616
# in no other, and the interfaces word follows the link
1717
#
1818
# ONE TOOLCHAIN, NOT TWO, AND THAT IS A PROPERTY OF THE TARGET RATHER THAN A
@@ -308,8 +308,9 @@ jobs:
308308
# 1. without the feature and the switch, no task symbol exists;
309309
# 2. the feature without the switch is refused before anything compiles,
310310
# naming the feature;
311-
# 3. with both, the eight symbols exist and a program that starts a task
312-
# and joins it exits 0 under node.
311+
# 3. with both, the nine symbols exist and a program that starts a task,
312+
# joins it, and asks each context for its own stack, exits 0 under
313+
# node.
313314
#
314315
# The consumer names `openkal` by path, as the implementation's manifest
315316
# now does (the step above): mcpp refuses one package reached both by path
@@ -329,16 +330,42 @@ jobs:
329330
rm -rf "$work"; mkdir -p "$work/src"
330331
cat > "$work/src/main.cpp" << 'CPP'
331332
import openkal.task;
332-
static void entry(void* arg) { *static_cast<int*>(arg) = 1; }
333+
import openkal.types;
334+
335+
// THE REGION EACH CONTEXT STANDS ON, ASKED FROM INSIDE IT. Containment
336+
// is the observation rather than the shape of the numbers: a range
337+
// that does not contain a local of the asking context describes
338+
// something other than that context.
339+
static bool holds(kal_uintptr at, int e, void* base, kal_uintptr size) {
340+
const kal_uintptr b = reinterpret_cast<kal_uintptr>(base);
341+
return e == 0 && size != 0 && b + size > b && at >= b && at - b < size;
342+
}
343+
344+
static int inside = 0;
345+
static void entry(void* arg) {
346+
*static_cast<int*>(arg) = 1;
347+
char here = 0;
348+
void* base = nullptr;
349+
kal_uintptr size = 0;
350+
inside = holds(reinterpret_cast<kal_uintptr>(&here),
351+
kal_task_stack(&base, &size), base, size) ? 1 : 0;
352+
}
353+
333354
int main() {
334355
// KAL_TASK_PROP_PARALLEL is bit 1 (openkal/task.h); a macro does not
335356
// cross a module.
336357
if ((kal_task_props() & 2u) == 0) return 2;
358+
char here = 0;
359+
void* base = nullptr;
360+
kal_uintptr size = 0;
361+
if (!holds(reinterpret_cast<kal_uintptr>(&here),
362+
kal_task_stack(&base, &size), base, size)) return 6;
337363
int ran = 0;
338364
kal_task t{};
339365
if (kal_task_start(&entry, &ran, &t) != 0) return 3;
340366
if (kal_task_join(t) != 0) return 4;
341-
return ran == 1 ? 0 : 5;
367+
if (ran != 1) return 5;
368+
return inside == 1 ? 0 : 7;
342369
}
343370
CPP
344371
manifest() {
@@ -368,11 +395,11 @@ jobs:
368395
n=$("$NM" --defined-only $objs 2>/dev/null \
369396
| awk '$2=="T"||$2=="W"{print $3}' | sed 's/^_//' \
370397
| grep -c '^kal_task' || true)
371-
test "$n" -eq 8 || { echo "::error::expected 8 kal_task symbols with the switch, found $n"; exit 1; }
372-
echo " ok eight task symbols with the switch"
398+
test "$n" -eq 9 || { echo "::error::expected 9 kal_task symbols with the switch, found $n"; exit 1; }
399+
echo " ok nine task symbols with the switch"
373400
( cd "$work" && timeout 300 mcpp run --target wasm32-emscripten ) \
374401
|| { echo "::error::the task program did not exit 0 under node"; exit 1; }
375-
echo " ok a task starts, runs and joins under node"
402+
echo " ok a task starts, runs and joins under node, and both contexts are told their own stack"
376403
377404
- name: Restore the manifest
378405
if: always()

‎README.md‎

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,10 @@ So the interface is carried by a feature:
6161

6262
```toml
6363
[dependencies]
64-
openkal-emscripten = { version = "0.3.1", features = ["threads"] }
64+
openkal-emscripten = { version = "0.4.0", features = ["threads"] }
6565
```
6666

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

@@ -104,6 +104,24 @@ channel did exist (`[build] dialect_cxxflags`); what was missing was a way to
104104
scope it to one target and to let this feature state its requirement, which the
105105
typed table provides.
106106

107+
## The region a context stands on
108+
109+
`kal_task_stack` reports the stack of the calling context, and this platform
110+
publishes it per context: the runtime that starts a thread calls
111+
`emscripten_stack_set_limits` for it before its entry runs, so
112+
`emscripten_stack_get_base` and `emscripten_stack_get_end` answer about the
113+
context that asks, and no record is kept here.
114+
115+
The stack grows down, so the end is the low address and the base the high one,
116+
and the region is the pair. The end is the end of the *stack* and not of the
117+
memory: below it is the guard Emscripten places for a thread that overflowed,
118+
which is why the answer begins at the end rather than at the bottom of what the
119+
thread was given.
120+
121+
The continuous integration gate that already builds a task program under
122+
`-pthread` also asks each of its two contexts for its own region and asserts
123+
that the region contains a local of that context.
124+
107125
## Conformance
108126

109127
Measured 2026-09-11, `emsdk 6.0.9`, run under node:
@@ -143,10 +161,10 @@ implementation in its source:
143161

144162
```toml
145163
[dependencies]
146-
openkal = "0.14.1"
164+
openkal = "0.15.0"
147165

148166
[target.'cfg(os = "emscripten")'.dependencies]
149-
openkal-emscripten = "0.3.1"
167+
openkal-emscripten = "0.4.0"
150168
```
151169

152170
## Licence

‎mcpp.toml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
namespace = "mcpplibs"
33
name = "openkal-emscripten"
4-
version = "0.3.1"
4+
version = "0.4.0"
55
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."
66
license = "Apache-2.0"
77

@@ -18,7 +18,7 @@ authors = ["mcpplibs"]
1818
repo = "https://github.com/mcpplibs/openkal-emscripten"
1919

2020
[dependencies]
21-
openkal = "0.14.1"
21+
openkal = { git = "https://github.com/mcpplibs/openkal.git", branch = "openkal-0.15.0" }
2222

2323
[build]
2424
# THE DIALECT FLAGS ARE THE SAME AS EVERY OTHER IMPLEMENTATION'S AND THE REASON

‎src/threads/task.cpp‎

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
// package-invented `OKE_THREADS` would have: a manifest that set the define
1616
// and not the flag, or the reverse, and a capability word that then lied.
1717
#include <emscripten/threading.h>
18+
// The stack introspection this platform publishes, which is per thread: the
19+
// pthread runtime calls `emscripten_stack_set_limits` for each context it
20+
// starts, so the answer below is the asking context's own.
21+
#include <emscripten/stack.h>
1822

1923
// THREADS ARE A LINK-TIME DECISION ON THIS PLATFORM, WHICH NO OTHER
2024
// IMPLEMENTATION HAS TO SAY.
@@ -53,7 +57,7 @@ extern "C" {
5357
// DID NOT HOLD contexts that ran at the same time have identities distinct
5458
//
5559
// The suite is right. So without the switch this translation unit is empty,
56-
// the eight `kal_task_*` symbols do not exist, a program that uses one fails
60+
// the nine `kal_task_*` symbols do not exist, a program that uses one fails
5761
// at LINK naming it, and `kal_interfaces()` -- keyed on the same
5862
// `__EMSCRIPTEN_PTHREADS__` -- does not claim the interface. That is clause
5963
// 6.2's second time, and it is the treatment `openkal.process`,
@@ -124,6 +128,28 @@ kal_uintptr kal_task_current(void) {
124128
return reinterpret_cast<kal_uintptr>(::pthread_self());
125129
}
126130

131+
// The stack the calling context runs on. Version 0.15.
132+
//
133+
// THE PLATFORM PUBLISHES IT, PER CONTEXT, AND THE STACK GROWS DOWN. The base is
134+
// where the stack pointer stands when nothing is in use --- the HIGH address ---
135+
// and the end is where it stops being the stack, so the usable region is
136+
// [end, base) and that is what this answers. Both are the asking context's:
137+
// the runtime that starts a pthread sets that thread's pair before its entry
138+
// runs, which is why no record is kept here and nothing is measured.
139+
//
140+
// The end is the end of the stack and not of the memory: a region below it is
141+
// the guard Emscripten places for a thread that overflowed, and a caller that
142+
// treats it as usable would be writing past what this context has.
143+
int kal_task_stack(void** base, kal_uintptr* size) {
144+
if (base == nullptr || size == nullptr) return kal_err_invalid;
145+
const uintptr_t low = ::emscripten_stack_get_end();
146+
const uintptr_t high = ::emscripten_stack_get_base();
147+
if (high <= low) return kal_err_io;
148+
*base = reinterpret_cast<void*>(low);
149+
*size = static_cast<kal_uintptr>(high - low);
150+
return kal_ok;
151+
}
152+
127153
// SUSPENSION ON A WORD, AND ON THIS PLATFORM IT IS THE MACHINE'S OWN
128154
// INSTRUCTION RATHER THAN A LIBRARY CALL.
129155
//

0 commit comments

Comments
 (0)