Skip to content
Closed
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
7 changes: 5 additions & 2 deletions docs/abi-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ library). A newer minor whose duties it knows is read.
**The runtime ABI.** `AETHER_RUNTIME_ABI_ID` (`runtime/aether_abi.h`) is
the Aether version, `/abi` and `AETHER_RUNTIME_ABI_REV`, and the flags
that change layouts (`+debug-mailbox`). A self-contained library carries
its own runtime, so this ABI never crosses its edge. A `--shared-runtime`
its own runtime, so this ABI never crosses its edge: on ELF `ae` links it
`-Bsymbolic`, so its runtime calls bind to its own copy, not to the one a
program linked `-rdynamic` exports, as macOS and Windows keep them apart by
themselves (#3481). A `--shared-runtime`
library, and C emitted with `--emit=csrc` or `--emit=obj`, must run on the
runtime it was made for. The shared runtime reports its ID
(`aether_runtime_abi()`), and the catalog of a shared-runtime library
Expand Down Expand Up @@ -87,7 +90,7 @@ catalogs and libraries compiled for a runtime of another ABI.
| Combination | Supported |
|---|---|
| A C host and a self-contained library of any version | Yes, using the functions of the library's level (read `host_abi`); a host built with older headers uses its own level's |
| An Aether program importing a self-contained library | Yes, when the importer knows every duty the catalog requires; a library of an older version is read by its minor |
| An Aether program importing a self-contained library | Yes, when the importer knows every duty the catalog requires; a library of an older version is read by its minor. On ELF, a library of another runtime ABI linked before #3481 (without `-Bsymbolic`, so it would run on the program's runtime) is refused at import, naming the rebuild |
| An importer older than schema 1.11 and a newer library | Calls that succeed work; it cannot read `requires`, so it does not check outcomes, and a failed call reads as its zero value, as with 0.856's own libraries |
| Anything on `--shared-runtime` | Only on the runtime it was made for (one runtime ABI ID) |
| Two self-contained libraries of different versions in one process | Yes; each runs on its own runtime, and the shared facilities are refused across layouts |
Expand Down
9 changes: 5 additions & 4 deletions docs/design/ownership-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -901,10 +901,11 @@ struct holding a string has no such copy: by value, the program's drop
releases the library's strings, and through a typed pointer the program's
stores do. A builder that uses its config fills, grows or frees a map the
program made, and a factory in the library would not help, since the
trailing block fills the config with the program's runtime. So where the
copies stay apart (macOS, Windows: no ELF interposition) `ae` refuses to
import a static-runtime library whose exports pass such a struct or whose
builders use their config, and names the rebuild with `--shared-runtime`.
trailing block fills the config with the program's runtime. So, since the
copies stay apart on every platform (#3481: a self-contained library binds
to its own runtime on ELF too), `ae` refuses to import a static-runtime
library whose exports pass such a struct or whose builders use their
config, and names the rebuild with `--shared-runtime`.
Closures cross either way: an environment carries its own release, which
runs in the library that made it.

Expand Down
19 changes: 10 additions & 9 deletions docs/emit-lib.md
Original file line number Diff line number Diff line change
Expand Up @@ -1254,11 +1254,13 @@ Scope of the current implementation:
A library built the default way carries its own copy of the runtime, linked
in statically: one scheduler, one set of panic frames, one config per image.
That keeps a library self-contained for a C or Python host, but a program
that imports Aether libraries in binary form then runs on several runtimes.
On Linux and FreeBSD ELF symbol interposition happens to bind a library's
runtime calls to the program's copy; macOS's two-level namespace and Windows'
PE keep them apart, so a panic raised inside a library never reaches the
program's `catch` there.
that imports Aether libraries in binary form then runs on several runtimes,
on every platform: a panic raised inside a library never reaches the
program's `catch`. macOS's two-level namespace and Windows' PE keep the
copies apart by themselves; on ELF (Linux, FreeBSD) `ae` links a
self-contained library with `-Bsymbolic`, so its runtime calls bind to its
own copy, not to the one a program linked `-rdynamic` exports (#3481: they
did, and a library made for one release then ran on another's runtime).

`--shared-runtime` links against the runtime as a shared library instead,
`aether.dll` (with `libaether.dll.a`), `libaether.dylib` or `libaether.so`,
Expand Down Expand Up @@ -1324,10 +1326,9 @@ ae build app.ae -o app # has none
way out, `main()` drains the scheduler and joins it, so a message the
library sent fire-and-forget is still delivered.
- **Draining needs one runtime.** The drain reaches the library's actors
where the program and the library share one runtime: `--shared-runtime`
everywhere, or ELF interposition with the default static runtime (Linux,
FreeBSD). On macOS and Windows a static library's actors run on that
library's own scheduler. `ae` warns that messages still in flight when the
where the program and the library share one runtime: `--shared-runtime`.
A static library's actors run on that library's own scheduler, on every
platform (#3481). `ae` warns that messages still in flight when the
program exits are not delivered, and names the library to rebuild with
`--shared-runtime`.
- **A C host** calls `scheduler_shutdown()` before it exits to deliver what
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
- **A library with a runtime of its own keeps it on Linux and FreeBSD
(#3481).** An `ae build` program is linked `-rdynamic`, so it exports
its runtime, and on ELF a library's calls into the runtime it carries
bound to the program's instead: its code ran on a runtime it was not
made for. Across versions its runtime ABI check refused, and the program
ended on the panic ("the library was made for Aether runtime 0.860.0/abi1
and runs on 0.863.0/abi1"), which kept the cross-version ABI matrix red
on Linux since 0.861.0. `ae` now links such a library `-Bsymbolic`, as
macOS and Windows keep the copies apart by themselves, so on every
platform a static-runtime library runs on its own runtime: `ae` refuses
an import that needs one runtime (a struct holding strings, a builder
using its config) and warns that a library's actors are not drained at
exit, naming `--shared-runtime`, as it did on macOS and Windows. A
library of another runtime ABI linked before this is refused at import
on ELF, naming the rebuild, instead of ending the program.
48 changes: 44 additions & 4 deletions tests/integration/abi_matrix/test_abi_matrix.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@
# a panic in one, an account of the new one suspended while the old one
# runs and resumed, both unloaded
# Aether consumers (app.ae, a binary import):
# this importer, the old library; the old importer, this library
# this importer, the old library; the old importer, this library. On
# ELF this ae links a library -Bsymbolic, so it keeps its own runtime in
# a program that exports one (#3481); an old library linked without it,
# of another version, is refused at import, naming the rebuild.
# The shared runtime:
# this host, this library on this shared runtime the baseline
# this host, the old library on the old shared runtime
Expand Down Expand Up @@ -159,10 +162,47 @@ run_app() {
bad "$1: app.ae did not build" "$2/app_build.log"
return
fi
expect "$1" "$(cd "$2" && PATH="$2:$PATH" LD_LIBRARY_PATH="$2${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" \
DYLD_LIBRARY_PATH="$2${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}" ./app$exe 2>"$2/app.err" | tr -d '\r')" "$app_want"
got="$(cd "$2" && PATH="$2:$PATH" LD_LIBRARY_PATH="$2${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" \
DYLD_LIBRARY_PATH="$2${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}" ./app$exe 2>"$2/app.err" | tr -d '\r')"
expect "$1" "$got" "$app_want"
# What the program said on stderr: an abort prints nothing on stdout.
if [ "$got" != "$app_want" ] && [ -s "$2/app.err" ]; then
echo " stderr:"; tail -12 "$2/app.err" | sed 's/^/ /'
fi
}
# A library with a runtime of its own linked before #3481 on ELF binds that
# runtime to the program's (an `ae build` program exports its runtime), so it
# would run on this one, of another version: this importer refuses it at
# build, naming the rebuild, instead of the program ending on its first call.
refuse_app() {
cp "$SCRIPT_DIR/app.ae" "$2/app.ae"
if (cd "$2" && AETHER_HOME="$3" "$4" build app.ae -o app > app_build.log 2>&1); then
bad "$1: app.ae built; the import was not refused" "$2/app_build.log"
elif ! grep -q "carries the Aether runtime" "$2/app_build.log" ||
! grep -q -- "--shared-runtime" "$2/app_build.log"; then
bad "$1: the refusal does not say why and name the rebuild" "$2/app_build.log"
fi
}
run_app "this importer, the $old_version library" "$TMP/old" "$ROOT" "$AE"
this_version="$(tr -d '\r\n' < "$ROOT/VERSION" 2>/dev/null)"
# This ae links a library with a runtime of its own -Bsymbolic on ELF, so it
# keeps that runtime in any program (#3481).
if [ "$ext" = so ] && command -v readelf >/dev/null 2>&1 &&
! readelf -d "$TMP/new/libmlib.$ext" 2>/dev/null | grep -q SYMBOLIC; then
bad "this ae linked libmlib.$ext without -Bsymbolic: it would run on an importing program's runtime"
fi
old_binds_own=1
if [ "$ext" = so ] && [ "$old_version" != "$this_version" ]; then
if ! command -v readelf >/dev/null 2>&1; then
old_binds_own=unknown
elif ! readelf -d "$TMP/old/libmlib.$ext" 2>/dev/null | grep -q SYMBOLIC; then
old_binds_own=0
fi
fi
case "$old_binds_own" in
0) refuse_app "this importer, the $old_version library (linked before #3481)" "$TMP/old" "$ROOT" "$AE" ;;
unknown) echo " [NOTE] abi_matrix: no readelf, so this importer with the $old_version library is not run" ;;
*) run_app "this importer, the $old_version library" "$TMP/old" "$ROOT" "$AE" ;;
esac
run_app "the $old_version importer, this library" "$TMP/new" "$OLD_HOME" "$OLD_AE"

# The shared runtime. $1 = case dir, $2 = library dir, $3 = runtime dir,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@
# way out: crowd's slow fire-and-forget message is still delivered.
#
# Two passes over the same sources:
# static runtime each library carries its own libaether. The actors run
# wherever the importer's calls land; the exit drain reaches
# them only where ELF interposition merges the copies
# (Linux, FreeBSD). Elsewhere `ae` warns that it cannot.
# static runtime each library carries its own libaether and runs its
# actors on its own scheduler, which the program's exit
# does not drain, on every platform (#3481); `ae` warns.
# shared runtime `--shared-runtime`: one runtime, one scheduler, and the
# program's exit drains it on every platform.
set -e
Expand All @@ -25,10 +24,9 @@ AE="$ROOT/build/ae"
[ -x "$AE" ] || [ -x "$AE.exe" ] || { echo " [SKIP] binary_import_actors: ae not built"; exit 0; }

case "$(uname -s)" in
MINGW*|MSYS*|CYGWIN*|Windows_NT) SO_EXT=".dll"; ELF=0; EXE=".exe" ;;
Darwin) SO_EXT=".dylib"; ELF=0; EXE="" ;;
Linux|FreeBSD) SO_EXT=".so"; ELF=1; EXE="" ;;
*) SO_EXT=".so"; ELF=0; EXE="" ;;
MINGW*|MSYS*|CYGWIN*|Windows_NT) SO_EXT=".dll"; EXE=".exe" ;;
Darwin) SO_EXT=".dylib"; EXE="" ;;
*) SO_EXT=".so"; EXE="" ;;
esac

fail() {
Expand Down Expand Up @@ -65,7 +63,7 @@ run_pass() {
echo "$OUT" | grep -q "^OK" || { echo "$OUT"; fail "$label: the actors did not count to 300"; }
if [ "$drains" = 1 ]; then
echo "$OUT" | grep -q "^late 7" || { echo "$OUT"; fail "$label: the exit did not drain the library's in-flight message" run.log; }
elif [ "$ELF" = 0 ]; then
else
grep -q "runs actors on its own static runtime" run.log \
|| fail "$label: no warning that the static runtime's messages are not drained" run.log
fi
Expand All @@ -80,7 +78,7 @@ run_pass() {
}

# ---- static runtime ----
run_pass "$WORK/static" "" "static runtime" "$ELF"
run_pass "$WORK/static" "" "static runtime" 0

# ---- shared runtime (where this toolchain built one) ----
if [ -d "$ROOT/build/shared" ]; then
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
# finds nothing on macOS.
# 3. A struct holding a string, returned by value, and a builder that frees
# its config. Neither can be copied between two copies of the runtime, so
# they need one: the shared runtime, or the static one where ELF
# interposition merges the copies (Linux, FreeBSD). There they run and
# grow 0; elsewhere the import is refused, naming the rebuild.
# they need one, the shared runtime: with the static one each library
# keeps its own copy (on every platform since #3481), and the import is
# refused, naming the rebuild. On the shared runtime they run and grow 0.

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
Expand All @@ -22,10 +22,9 @@ AE="$ROOT/build/ae"
CC="${CC:-cc}"

case "$(uname -s)" in
MINGW*|MSYS*|CYGWIN*|Windows_NT) SO_EXT=".dll"; ELF=0; EXE=".exe" ;;
Darwin) SO_EXT=".dylib"; ELF=0; EXE="" ;;
Linux|FreeBSD) SO_EXT=".so"; ELF=1; EXE="" ;;
*) SO_EXT=".so"; ELF=0; EXE="" ;;
MINGW*|MSYS*|CYGWIN*|Windows_NT) SO_EXT=".dll"; EXE=".exe" ;;
Darwin) SO_EXT=".dylib"; EXE="" ;;
*) SO_EXT=".so"; EXE="" ;;
esac
LEAKS=0
if [ "$(uname -s)" = Darwin ] && command -v leaks >/dev/null 2>&1; then LEAKS=1; fi
Expand Down Expand Up @@ -102,7 +101,7 @@ run_pass() {
cd "$WORK"
}

run_pass "$WORK/static" "" "static runtime" "$ELF"
run_pass "$WORK/static" "" "static runtime" 0

# The C host, against the static library.
cd "$WORK/static"
Expand Down
8 changes: 4 additions & 4 deletions tests/integration/binary_import_structs/app_panic.ae
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// A panic inside a binary-imported library reaches the host's catch
// (#2297). Holds where the host and the library share one runtime: on ELF
// (Linux, FreeBSD) the library's runtime symbols bind to the host's by
// interposition. The test script runs this only there; macOS and Windows
// keep a runtime per image until #2297's shared-runtime work lands.
// (#2297). Holds where the host and the library share one runtime: a
// library built with --shared-runtime, which is how the test script runs
// it. A library with a runtime of its own keeps it on every platform
// (#3481), so its panic stays in it.

import engine

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,11 @@
# and a function taking or returning a struct was not exported at all.
#
# Two passes over the same sources:
# static runtime each library carries its own copy of libaether. Where
# ELF interposition merges the copies (Linux, FreeBSD), a
# second binary library (script) importing the engine, and
# the host importing both, share the engine's one module
# state, and a panic in the engine reaches the host's
# catch. Elsewhere the copies stay apart and neither may
# free the other's strings, so importing the engine, whose
# *Model holds one, is refused, naming the rebuild (#2931).
# static runtime each library carries its own copy of libaether, and on
# every platform (#3481) the copies stay apart: neither
# may free the other's strings, so importing the engine,
# whose *Model holds one, is refused, naming the rebuild
# (#2931).
# shared runtime `--shared-runtime`: the libraries link aether.dll /
# libaether.so / libaether.dylib, their catalogs say so
# (schema 1.5), and the host follows. One runtime, so the
Expand All @@ -30,10 +27,9 @@ AE="$ROOT/build/ae"
[ -x "$AE" ] || [ -x "$AE.exe" ] || { echo " [SKIP] binary_import_structs: ae not built"; exit 0; }

case "$(uname -s)" in
MINGW*|MSYS*|CYGWIN*|Windows_NT) SO_EXT=".dll"; ELF=0; EXE=".exe" ;;
Darwin) SO_EXT=".dylib"; ELF=0; EXE="" ;;
Linux|FreeBSD) SO_EXT=".so"; ELF=1; EXE="" ;;
*) SO_EXT=".so"; ELF=0; EXE="" ;;
MINGW*|MSYS*|CYGWIN*|Windows_NT) SO_EXT=".dll"; EXE=".exe" ;;
Darwin) SO_EXT=".dylib"; EXE="" ;;
*) SO_EXT=".so"; EXE="" ;;
esac

fail() {
Expand Down Expand Up @@ -88,25 +84,20 @@ panic_check() {
}

# ---- static runtime ----
if [ "$ELF" = 1 ]; then
run_pass "$WORK/static" "" "static runtime"
panic_check "$WORK/static" "static runtime"
else
mkdir -p "$WORK/static"
cp "$SCRIPT_DIR/engine.ae" "$SCRIPT_DIR/app.ae" "$WORK/static/"
cd "$WORK/static"
AETHER_HOME="$ROOT" "$AE" build --emit=lib engine.ae -o "libengine$SO_EXT" >engine.log 2>&1 \
|| fail "static runtime: ae build --emit=lib engine.ae" engine.log
rm -f engine.ae
if AETHER_HOME="$ROOT" "$AE" run app.ae >run.log 2>&1; then
fail "static runtime: importing a library whose *Model holds a string was not refused" run.log
fi
grep -q "model_new\` passes \*Model, which holds strings" run.log \
|| fail "static runtime: the refusal does not name model_new and *Model" run.log
grep -q "ae build --emit=lib --shared-runtime" run.log \
|| fail "static runtime: the refusal does not name the rebuild" run.log
cd "$WORK"
mkdir -p "$WORK/static"
cp "$SCRIPT_DIR/engine.ae" "$SCRIPT_DIR/app.ae" "$WORK/static/"
cd "$WORK/static"
AETHER_HOME="$ROOT" "$AE" build --emit=lib engine.ae -o "libengine$SO_EXT" >engine.log 2>&1 \
|| fail "static runtime: ae build --emit=lib engine.ae" engine.log
rm -f engine.ae
if AETHER_HOME="$ROOT" "$AE" run app.ae >run.log 2>&1; then
fail "static runtime: importing a library whose *Model holds a string was not refused" run.log
fi
grep -q "model_new\` passes \*Model, which holds strings" run.log \
|| fail "static runtime: the refusal does not name model_new and *Model" run.log
grep -q "ae build --emit=lib --shared-runtime" run.log \
|| fail "static runtime: the refusal does not name the rebuild" run.log
cd "$WORK"

# ---- shared runtime (where this toolchain built one) ----
if [ -d "$ROOT/build/shared" ]; then
Expand Down
Loading