diff --git a/docs/design-notes.md b/docs/design-notes.md index 80e8a47c..61cb9e36 100644 --- a/docs/design-notes.md +++ b/docs/design-notes.md @@ -784,42 +784,29 @@ outside this capability because they require their own complete element/projecti ## The lambda philosophy -Lambdas (`fn x { ... }`) are not a separate paradigm bolted onto the data-processing core — they -**are** how you pass behavior to `map` / `where` / `reduce` / `par_map`. A lambda and a named -function are the same thing; the lambda just spares you a top-level declaration for a one-off. - -The load-bearing decision is **how capture works**. A lambda that captures an enclosing variable -does *not* allocate a hidden closure environment: it is lifted to an ordinary function whose -captured values become extra parameters, passed at the call site. So a captured pipeline lambda -fuses into the same counted loop as a named function and carries zero allocation — the capture is -just a loop-invariant argument. The compiler snapshots each stage capture once when that written -stage is formed, evaluates later terminal arguments, then snapshots terminal/reducer captures; the -fused loop reuses those SSA operands. It never delegates capture timing to an optimizer by emitting -an enclosing-local reload per iteration. A view snapshot keeps its owner dependency across the -intervening arguments, so an ownership-changing `init` is rejected rather than leaving a dangling -callback argument. This keeps lambdas inside the existing -guarantees rather than introducing a new cost class: **Nothing hidden** (no silent heap -environment), **Predictable performance** (a lambda is never secretly slower than the named -equivalent), and **Compiler-friendly** (the optimizer sees a direct call, not an indirect one -through a closure object). - -**Escape decides the representation.** A lambda that *escapes* — stored in a variable, returned, -or handed to `task_group`'s `spawn` to run later — outlives the locals it captures, so it cannot -borrow them; it needs a **closure environment** holding the captured values. The compiler's escape -analysis (the same one that governs views and arenas) chooses: a non-escaping lambda (every -pipeline `map`/`where`/`reduce`/`par_map`) is inlined with captures-as-parameters — zero -allocation, SIMD/GPU-friendly; an escaping lambda gets an environment. That environment is not a -new hidden cost class: it is **owned by the enclosing region** — the `task_group {}` / `arena {}` -scope it escapes into — and freed with that region, exactly like every other region allocation. -The task-group region is reserved for spawned environments and result slots; it does not silently -turn unrelated allocations in the block into arena allocations. So -it stays inside the one region-based allocation model, and the *visible* act of escaping (and the -*visible* enclosing scope) is the allocation boundary — consistent with **Nothing hidden** (no -silent free-floating `malloc`). This is the load-bearing design point: it lets first-class function -values and `task_group` exist *without* eroding the inlined, offload-ready pipeline path, and the -two paths are distinguished by escape, not by two different lambda syntaxes. (The allocation model -for a closure that escapes *every* region — e.g. one returned to an unbounded caller — is part of -the deferred first-class-closure design; the `task_group` consumer is scope-bounded and clean.) +Lambdas (`fn x { ... }`) pass behavior to pipelines and other functions without +requiring a top-level declaration for each callback. + +An inline pipeline lambda is lifted to a function with extra parameters for its Copy +captures. The compiler snapshots each stage's captures at its written position, then +evaluates terminal arguments and snapshots terminal captures. The fused loop reuses those +values. A captured view retains its owner dependency across intervening arguments, so an +ownership-changing `init` cannot leave the callback with a dangling view. No separate +closure environment is needed on this path. + +A stored or passed capturing function value instead uses a frame-local environment. +It may be called while that frame and its borrowed captures remain live. Binding a lambda +therefore does not imply heap allocation or permission to return it. Returning function +values and carrying a capturing environment beyond its frame remain deferred; wrapping +the binding in an `arena` does not extend that environment's lifetime. + +A spawned literal needs a fresh environment for each task. A frame-local slot reused by +a loop iteration would let concurrent tasks observe overwritten captures. The visible +`task_group` scope owns these environments and result slots until every task has joined. +Other values allocated in its body retain their ordinary ownership, and arena-only +operations still need an explicit `arena`. The same lambda syntax thus supports direct +pipeline calls, frame-local function values and structured tasks with distinct storage +lifetimes. Plan259 adds owned inputs only to the task's single invocation, as described below. The **Side Effect Rule** completes the picture: a `par_map` lambda must be Pure (it may read captured values but not mutate external state), and every worker capture must independently be diff --git a/docs/guide/07-strings-and-text.md b/docs/guide/07-strings-and-text.md index a37f8b33..5a8cda79 100644 --- a/docs/guide/07-strings-and-text.md +++ b/docs/guide/07-strings-and-text.md @@ -22,7 +22,7 @@ fn main() -> i32 { } ``` -The default in signatures is `str`: take views, return views when the data already exists, and return `string` only when the function genuinely creates new text. +Use `str` when a function reads text or returns a view whose source remains alive. Return `string` when the caller needs ownership, whether the function creates the text or transfers an existing owned value. ## Literals, escapes, bytes @@ -119,6 +119,13 @@ fn main() -> i32 { The builder uses one growable buffer and produces a final `string`. Buffer growth is amortized across appends. `write` takes a `str` (or an owned `string`); `write_int` formats an integer directly into the buffer without a temporary string. The compiler can combine adjacent writes of literals and integers into one runtime call. +`b.len()` reports the initialized byte count without finishing the builder. +`b.bytes()` borrows those same bytes as a read-only `slice`, without a copy +or allocation. This lets a byte-oriented consumer read the current text without +first calling `to_string()`. Finish using that view before any `write*` call, +including an empty write, or before finishing, moving or dropping the builder. +Obtain a fresh view after a write; reserved capacity is never part of the view. + ## Template strings For one-shot formatting, `template` interpolates full expressions: diff --git a/docs/guide/10-closures-and-parallelism.md b/docs/guide/10-closures-and-parallelism.md index db4cdb90..afa4fd58 100644 --- a/docs/guide/10-closures-and-parallelism.md +++ b/docs/guide/10-closures-and-parallelism.md @@ -58,7 +58,7 @@ ys := [1, 2].par_map(fn x { show(x) }) // for an accumulation ``` -Sequential `map` may call `show`: it prints in input order. Changing that call to `par_map` is not valid, because the observable order would depend on worker scheduling. Parallel callables must satisfy both purity and capture restrictions. Move captures are rejected, and a `region` allocation capability cannot be sent to a worker even though its type is Copy. These checks are inferred; no `Send` or `Sync` annotation is written. +Sequential `map` may call `show`: it prints in input order. Changing that call to `par_map` is not valid, because the observable order would depend on worker scheduling. `par_map` callables must satisfy both purity and capture restrictions. They cannot capture Move values, and a `region` allocation capability cannot be sent to a worker even though its type is Copy. These checks are inferred; no `Send` or `Sync` annotation is written. ## `par_map` — data parallelism @@ -103,7 +103,7 @@ fn main() -> Result<(), Error> { The task is eligible to run before `spawn` returns, even when it is the only task. A busy worker pool can delay its start. A captured view shares its original bytes: keep that backing unchanged and alive until `wait`. Dropping or reassigning the task handle does not end this protection. -Tasks may read shared views and modify storage they create themselves. +Tasks may read shared views and modify storage they create or receive by ownership transfer, as shown below. Unlike `par_map`, spawned tasks may perform I/O and other impure work. `wait()` also helps execute its group's pending tasks, allowing nested groups to progress even when every worker is occupied. It cannot resolve a blocking cycle created by the program. Task results may be self-contained primitive values, strings, buffers, files, ordinary dynamic arrays, records, tuples or sums, optionally through `Result`. They may contain owned data but cannot contain views, pointers, callables or native/resource handles other than `file`. After a successful wait, Copy `get()` may repeat; owned `get()` consumes the handle and transfers the result once. Results can outlive the group. Group exit joins all tasks and drops untaken results, including after a failed wait. Shared captures remain read-only. For contiguous output, create one owned band per task and collect the named handles in band order. @@ -119,7 +119,7 @@ An already-open `file` can move into a task without reopening its path: import std.fs fn read_window(pack: file) -> Result { - mut window := buffer(4096) + mut window := buffer.try_new(4096)? pack.pread_into(window, 0, 4096, 0)? Ok(window) } @@ -136,6 +136,10 @@ fn main() -> Result<(), Error> { } ``` +`buffer.try_new(4096)?` reserves the requested window or propagates an allocation +error before the read. `pread_into` fills at most that window; a short read +produces a shorter result, so this example reads one window rather than an entire file. + The same file owner may be returned directly or in a record alongside a filled buffer, using the ordinary consuming `get()`. This permits bounded window reuse while retaining file identity. GPU resources and signal subscriptions remain confined to their owning thread; diff --git a/docs/guide/16-toolchain.md b/docs/guide/16-toolchain.md index e92cba2c..3288a7fa 100644 --- a/docs/guide/16-toolchain.md +++ b/docs/guide/16-toolchain.md @@ -101,7 +101,7 @@ alignc explain-opt file.align --verbose alignc size file.align --profile tiny ``` -`emit-mir` is the semantic lens. Raw LLVM IR shows lowering before optimization; optimized IR shows the code LLVM actually shaped. `explain-opt` reports the compiler's current storage and explicit-parallel execution choices, then translates vectorization and other LLVM optimization remarks back to source lines. `size` builds the same artifact as `build` under the selected profile and reports where its bytes went. For standalone objects or IR, repeat `--export name` to keep selected entry-unit functions externally visible. +`emit-mir` prints the compiler's mid-level representation. Raw LLVM IR shows lowering before optimization; optimized IR shows the result of LLVM's optimization passes. `explain-opt` reports the compiler's current storage and explicit-parallel execution choices, then translates vectorization and other LLVM optimization remarks back to source lines. `size` builds the same artifact as `build` under the selected profile and reports where its bytes went. For standalone objects or IR, repeat `--export name` to keep selected entry-unit functions externally visible. Roots for linking an executable and roots for inspecting a unit are different questions. A build keeps `main` plus whatever `--export` names, and `emit-obj` keeps the same set because its output is linked, not read. The inspection verbs report the unit you pointed them at: when that file defines no `main` and you pass no `--export`, every `pub` function it defines is a root, so `emit-llvm --stage optimized` emits the unit's own bodies and `explain-opt` reports its own optimizer decisions instead of an empty module. The seeded set is noted on stderr, so a redirected IR or report stream is unchanged. `--export` is accepted by `emit-obj`, `emit-llvm` and `explain-opt`, and narrows that set exactly as before. `emit-mir` needs no rule: it prints every lowered function. @@ -123,11 +123,16 @@ runtime LTO off; it does not describe a linked ThinLTO executable. --thin-lto # cross-unit ThinLTO ``` -On an Apple target the deployment target is part of the resolved target identity, exactly as the CPU is: one resolution point, one stated precedence, and one string that the objects, the module IR, the link and the cache key all derive from. It resolves to the first of `--deployment-target`, the platform's `MACOSX_DEPLOYMENT_TARGET` (or `IPHONEOS_`/`WATCHOS_`/`TVOS_`/`XROS_`) environment variable, the host's `sw_vers -productVersion`, and a documented per-platform floor. Every layer is read at `major.minor` precision, and a malformed value at any layer is an error naming that layer rather than a fall-through to the next: an unset environment variable moves on, but a variable that is set to an empty or unusable value is a supplied value and is rejected. `--deployment-target` on a non-Apple host is an error rather than a silently ignored flag. Because the version is part of the identity and the OS patch level is not, a patch-level OS update does not invalidate the build cache, and changing the deployment target does. +On an Apple target, the deployment target specifies the minimum OS version for generated artifacts and participates in the build cache identity. It resolves to the first of `--deployment-target`, the platform's `MACOSX_DEPLOYMENT_TARGET` (or `IPHONEOS_`/`WATCHOS_`/`TVOS_`/`XROS_`) environment variable, the host's `sw_vers -productVersion`, and a documented per-platform floor. Every layer is read at `major.minor` precision, and a malformed value at any layer is an error naming that layer rather than a fall-through to the next: an unset environment variable moves on, but a variable that is set to an empty or unusable value is a supplied value and is rejected. `--deployment-target` on a non-Apple host is an error rather than a silently ignored flag. Because the version is part of the identity and the OS patch level is not, a patch-level OS update does not invalidate the build cache, and changing the deployment target does. `--sdk-version` separately records explicit SDK provenance in Apple objects' `LC_BUILD_VERSION`. It accepts `major`, `major.minor`, or `major.minor.patch`; omission leaves the object SDK unspecified (`sdk n/a`) exactly as before. It does not select an SDK or sysroot and has no ambient `SDKROOT`/`xcrun` fallback. The selected canonical version participates in codegen and ThinLTO cache identities but not the frontend cache. An invalid value, or any value on a non-Apple target, is an error before source or artifact work. -The default is portable `baseline` plus `release`. `native` is for the current machine; a named LLVM CPU such as `x86-64-v3` is useful for a known deployment fleet. Runtime LTO is **on by default** under the optimizing `release`/`fast` profiles (measured 2-3× on string-predicate pipelines, non-regressing elsewhere, +1-2ms compile) and off under `dev`/`small`/`tiny`; `--no-rt-lto` / `--rt-lto` force either direction. `--thin-lto` stays explicit because it changes compile cost and optimization scope; it requires `release` or `fast`, applies to linked `build`/`run`/`size` operations, is parallel and cached, and composes with runtime LTO. +The default is portable `baseline` plus `release`. `native` is for the current machine; a named LLVM CPU such as `x86-64-v3` is useful for a known deployment fleet. Runtime LTO is **on by default** under the optimizing `release`/`fast` profiles and off under `dev`/`small`/`tiny`; `--no-rt-lto` / `--rt-lto` force either direction. `--thin-lto` stays explicit because it changes compile cost and optimization scope; it requires `release` or `fast`, applies to linked `build`/`run`/`size` operations, is parallel and cached, and composes with runtime LTO. + +Runtime LTO lets the optimizer see runtime helper bodies, which can remove calls from +string predicates. The [recorded benchmark](../../bench/rt_lto/README.md) shows +one workload and target; measure your program with both settings before drawing +a performance conclusion. For a representative production workload, instrumented PGO is available: @@ -245,8 +250,8 @@ spliced in, the whole program is recompiled through the same driver calls `align the resulting native binary is executed. Behavior is therefore identical to a production compile — the same profile, the same `rt-lto` default, the same object. -Re-execution is the model, not an implementation detail. Because the whole program runs again every -entry, output you have already seen is elided and only the new lines are printed: +The whole program runs again after every entry, including earlier I/O and other side +effects. The REPL hides unchanged output and prints only the new lines: ```text align> x := 5 diff --git a/docs/guide/17-the-align-way.md b/docs/guide/17-the-align-way.md index 99a0bb5b..b9e3b6cd 100644 --- a/docs/guide/17-the-align-way.md +++ b/docs/guide/17-the-align-way.md @@ -31,7 +31,7 @@ Consider `soa` when a hot path scans a few fields across many records. `to_so ## Choose between data parallelism and tasks -Use `par_map` when the per-element function is expensive; measure first, because a vectorized sequential pipeline may be faster. Its callable must be Pure and cannot mutate captured backing. Use `task_group`/`spawn` for independent jobs, including I/O: tasks may be Impure, but captured views remain read-only until the group joins. Use `wait()` when no task returns `Result`, and `wait()?` to propagate fallible task results. After a successful wait, `.get()` reads a Copy result or transfers an owned result once. Both forms enforce capture and lifetime rules without user-written purity annotations. +Use `par_map` when the per-element function is expensive; measure first, because a vectorized sequential pipeline may be faster. Its callable must be Pure and cannot mutate captured backing. Use `task_group`/`spawn` for independent jobs, including I/O: tasks may be Impure, but captured views remain read-only until the group joins. Use `wait()` when no task returns `Result`, and `wait()?` to propagate fallible task results. A literal `spawn(fn { ... })` can move self-contained inputs, including buffers and files, into a task; ordinary stored closures and pipeline lambdas still capture Copy values only. After a successful wait, `.get()` reads a Copy result or transfers an owned result once. Both forms enforce capture and lifetime rules without user-written purity annotations. ## Let the compiler see the shape diff --git a/docs/guide/18-std-services.md b/docs/guide/18-std-services.md index a614cf1a..fb539da2 100644 --- a/docs/guide/18-std-services.md +++ b/docs/guide/18-std-services.md @@ -66,6 +66,46 @@ pub fn main(args: array) -> Result<(), Error> { HTTP status is data: a 404 is a successful HTTP response, not an `Err`. Transport, TLS, and malformed-message failures are errors. `cl.get_many(urls, degree)` performs bounded blocking-I/O overlap while preserving input order. Server primitives are deliberately below framework level: `http.serve`, `accept`, request views, `http.response`, and `respond`. For SSE or another streaming body, `respond_stream` yields an `http_stream` while the request context stays readable (borrowed, spent); call `send` for each chunk and `finish` for the sole clean terminator — or, before the first `send`, `reject(rb)` to answer with a normal error response instead. +### Keep the listener owned and capture a request view + +The listener returned by `http.serve` has the Move type `http_server`. A helper +can borrow it for `accept`, while the caller keeps ownership. `ctx.view()` gives +a Copy `http_request_view` for reading the request or capturing it in a callback: + +```align +import std.http + +fn receive(borrow server: http_server) -> Result = server.accept() +fn checkpoint(read: fn() -> Result) -> Result = read() + +fn main() -> Result<(), Error> { + server := http.serve("127.0.0.1", 8080)? + ctx := receive(server)? + request := ctx.view() + print(request.path()) + print(checkpoint(fn { request.peer_eof() })?) + return ctx.respond(http.response(200)) +} +``` + +This program accepts one request, prints its path and the EOF observation, and +sends an empty 200 response. The callback captures the view, leaving `ctx` +available to respond after the last view use. Creating the view allocates no +storage and copies no request bytes. It supports `method`, `path`, `body`, +`headers`, `upgrade_ready`, and `peer_eof`; it cannot send a response or own the +connection. + +`peer_eof()` makes one nonblocking, nonconsuming observation. `true` includes a +peer that closed only its sending half, which may still expect a response; +`false` does not guarantee the peer is alive. The application decides whether +an observation should cancel work. The example propagates observation errors +with `?` and responds regardless of the Boolean result. + +A request view must not outlive its context. If a spawned task captures it, +join that task before responding or starting a response/upgrade stream. +See [the HTTP contract](../impl/std-design/http.md) for the full lifetime and +response rules. + ### Receive binary bodies without retaining the whole response The runnable [http_fetch example](../../examples/http_fetch.align) composes diff --git a/docs/guide/27-multimodal-reference.md b/docs/guide/27-multimodal-reference.md index aea554e8..deea89fc 100644 --- a/docs/guide/27-multimodal-reference.md +++ b/docs/guide/27-multimodal-reference.md @@ -1,5 +1,7 @@ # A bounded text, audio and media pipeline +> 🌐 **English** · [Japanese](./ja/27-multimodal-reference.md) + [`examples/multimodal/`](../../examples/multimodal/) is a runnable application written in ordinary Align. It combines HTTP, owned JSON, structured tasks, retained directories and `std.process.child_scope`. Its exact contract is @@ -12,7 +14,7 @@ phoneme alignment, GPU fit and GPU release require real engine adapters. ## Run on Linux or WSL2 -Install LLVM22 and FFmpeg/ffprobe. WSL2 must pass the existing child_scope kernel +Install LLVM 22 and FFmpeg/ffprobe. WSL2 must pass the existing child_scope kernel feature probe. The full managed-process reference refuses unsupported kernels and macOS before starting its workers. Common source/model tests run on macOS. @@ -28,7 +30,7 @@ root=$(mktemp -d) "$(pwd)/main" serve "$root" 8080 8081 8082 /usr/bin/ffmpeg ``` -ROOT must be an existing empty absolute directory with mode0700. Use an absolute +ROOT must be an existing empty absolute directory with mode 0700. Use an absolute executable path. The process remains running until a shutdown request. In another terminal, submit a pipeline job: @@ -63,25 +65,29 @@ One controller owns eight scalar SoA job slots, at most four queued jobs and one managed phase. The tasks exchange bounded authenticated HTTP snapshots; no mutable job table is shared. Every response has an explicit write budget. -Output uses fixed4KiB scratch and at most1MiB reserved per job. Completed +Output uses a fixed 4 KiB scratch buffer and reserves at most 1 MiB per job. Completed artifacts stay file-backed. Three terminal jobs remain publicly retained; -older jobs retire. An existing reader lease keeps retired files and their -reservation alive until exact-id release. Disconnecting events does not cancel +older jobs retire. A reader that already holds a file can finish reading it +after the job retires. +The file and its reserved storage remain until that reader releases its lease +with the matching ID. Disconnecting events does not cancel a job. None-valued JSON fields are omitted, including unavailable percentages and unsupported alignment. The mock reports only its known silence interval in sample frames; it invents no word or phoneme times. Each phase drains captured pipes, observes native process state and explicitly releases its child_scope before publishing or launching the next phase. A -failed release retains Faulted ownership and blocks the next launch. This -process absence witness does not prove a real engine released device memory. +failed release retains Faulted ownership and blocks the next launch. +A successful release confirms that the managed processes have ended; it does +not confirm that a real engine has released device memory. Clean shutdown stops admission, releases managed work, joins observers, removes only recorded owned artifacts and removes `controller.lock` last. After an -unclean exit the fence remains: reconcile remaining native/device ownership -before manually removing it. Root exit and elapsed time are insufficient. +unclean exit the lock file remains. Check for surviving processes and device +resources before removing it manually; the main process exiting does not prove +that its descendants and device work have ended. -The provider regression is `scripts/cargo.sh test -p align_driver --test -multimodal_reference`. Run its Linux composition owner with FFmpeg and ffprobe -installed; it checks real muxing, cancellation, retained readers, stale releases, +Run `scripts/cargo.sh test -p align_driver --test multimodal_reference` on +Linux with FFmpeg and ffprobe installed; it checks real muxing, cancellation, +retained readers, stale releases, stalled control replies and abnormal producers. No GPU inference is qualified. diff --git a/docs/guide/api-by-task.md b/docs/guide/api-by-task.md index 37933208..6841fdec 100644 --- a/docs/guide/api-by-task.md +++ b/docs/guide/api-by-task.md @@ -36,7 +36,9 @@ name, such as `pkg.csv.decode`, after `import pkg.csv`. Follow the complete Choose an explicit materialization terminal when you need stored output. Borrowed views must stay within their owner's lifetime. `par_map` requires a Pure callable. Spawned tasks may perform I/O, but shared captures remain read-only -until the group joins. +until the group joins. A literal `spawn(fn { ... })` can also take ownership of +self-contained inputs such as a buffer or an open file; the original owner +and its borrowed views become unavailable. See the task chapter for supported types. ## Handle text and data formats @@ -44,6 +46,7 @@ until the group joins. |---|---|---|---| | Keep text after its source is gone | `str.clone()` → owned `string` | [Strings and text](07-strings-and-text.md) | [Strings and builders](../impl/core-design/string.md) | | Assemble text without repeated concatenation | `builder`, `.write`, `.write_int`, `.to_string` | [Builder examples](07-strings-and-text.md#the-builder) | [Strings and builders](../impl/core-design/string.md) | +| Inspect text before finishing its builder | `builder.len`, `.bytes` | [Builder views](07-strings-and-text.md#the-builder) | [Strings and builders](../impl/core-design/string.md#unfinished-builder-bytes) | | Decode JSON with independently owned fields | `json.decode`, record fields of `string` / `array` | [Complete owned JSON example](../../examples/json_owned.align) | [JSON types and ownership](../impl/core-design/json.md) | | Read JSON through borrowed text or columns | `json.decode`, `str`, `soa`, `arena` | [JSON](08-json.md) | [JSON types and ownership](../impl/core-design/json.md) | | Inspect or forward JSON whose schema is not known | `json.doc`, `.get`, `.at`, `json.encode_options` | [Document views](08-json.md#when-the-schema-is-not-known) | [JSON types and ownership](../impl/core-design/json.md) | @@ -81,9 +84,10 @@ has been written. | I want to… | API or language feature | Start here | Exact contract | |---|---|---|---| | Make an HTTP request | `http.client`, `.get`, `.request` | [HTTP](18-std-services.md#stdhttp) | [HTTP](../impl/std-design/http.md) | +| Pass an HTTP listener to a helper or inspect a request in a callback | `http_server`, `borrow`, `ctx.view`, `http_request_view` | [Listener and request view](18-std-services.md#keep-the-listener-owned-and-capture-a-request-view) | [HTTP](../impl/std-design/http.md) | | Download binary HTTP data with an explicit limit | `.request_stream`, stream `.read` | [Complete fetch example](../../examples/http_fetch.align) | [Streaming receive](../impl/std-design/http.md#client-streaming-receive-post-pkgdb-convergence-item-1--implemented-2026-08-30) | | Connect a TCP stream or resolve a hostname | `tcp.connect`, `dns.resolve` via `import std.net` | [Networking](18-std-services.md#stdnet) | [Networking](../impl/std-design/net.md) | -| Run a child program and inspect its output | `process.command`, `.run`, `.code`, `.stdout` | [Captured output example](../impl/std-design/process.md#surface) | [Processes](../impl/std-design/process.md) | +| Run a child program and inspect its output | `process.command`, `.run`, `.status`, `.stdout` | [Process status and output](../impl/std-design/process.md#current-status-and-output-api) | [Processes](../impl/std-design/process.md) | | Compress bytes or hash a file incrementally | `compress.gzip_compress`, `crypto.sha256_stream` | [Services and hashing examples](18-std-services.md) | [Compression](../impl/std-design/compress.md), [cryptography](../impl/std-design/crypto.md) | | Write logs through an explicit writer | `log.new`, `.line`, `.flush` | [Logging](18-std-services.md#stdlog) | [Logging](../impl/std-design/log.md) | diff --git a/docs/guide/ja/07-strings-and-text.md b/docs/guide/ja/07-strings-and-text.md index e6f373a4..8e480578 100644 --- a/docs/guide/ja/07-strings-and-text.md +++ b/docs/guide/ja/07-strings-and-text.md @@ -22,7 +22,7 @@ fn main() -> i32 { } ``` -関数のシグネチャでは、デフォルトで `str` を使用してください。引数としてビューを受け取り、既存のデータの一部を返す場合もビューを返します。関数が本当に新しいテキストデータを生成して返す必要がある場合にのみ、`string` を返すように設計すべきです。 +テキストを読む関数や、元のデータが有効な間だけ使うビューを返す関数では `str` を使います。呼び出し元へ所有権を渡す場合は `string` を返します。新しく作ったテキストだけでなく、既存の所有値を移す場合も同じです。 ## リテラル、エスケープ、バイト @@ -118,6 +118,13 @@ fn main() -> i32 { builder は1つの拡張可能なバッファを使い、最後に `string` を返します。バッファを拡張するコストは、複数回の追記に分散されます。`write` は `str` または所有権を持つ `string` を受け取ります。`write_int` は、一時文字列を作らずに整数をバッファへ直接書き込みます。また、コンパイラは連続するリテラルや整数の書き込みを、1回の実行時呼び出しにまとめられます。 +`b.len()` は builder を完成させずに、書き込み済みのバイト数を返します。 +`b.bytes()` は同じバイト列を読み取り専用の `slice` として借用し、コピーも +領域確保もしません。`to_string()` を呼ぶ前でも、バイト列を受け取る処理へ渡せます。 +空の書き込みを含む `write*` 呼び出しや、builder の完成・ムーブ・解放より前に、 +そのビューを使い終えてください。書き込み後は新しいビューを取得します。 +未使用の予約容量はビューに含まれません。 + ## テンプレート文字列 変数の値を埋め込むような1回限りの文字列整形には、`template` キーワードを使用した補間(インターポレーション)を利用します。 diff --git a/docs/guide/ja/10-closures-and-parallelism.md b/docs/guide/ja/10-closures-and-parallelism.md index 67452186..9212cc26 100644 --- a/docs/guide/ja/10-closures-and-parallelism.md +++ b/docs/guide/ja/10-closures-and-parallelism.md @@ -58,7 +58,7 @@ ys := [1, 2].par_map(fn x { show(x) }) // for an accumulation ``` -逐次の `map` なら `show` を呼べます。表示は入力順に行われます。それを `par_map` に変えると、出力順がワーカーの実行順に依存するため拒否されます。並列に渡す関数には、純粋性とキャプチャの両方の制限があります。Move 値のキャプチャは拒否され、確保先を指定する `region` も、Copy 型ですがワーカーへ渡せません。これらはコンパイラが検査し、`Send` や `Sync` の注釈は書きません。 +逐次の `map` なら `show` を呼べます。表示は入力順に行われます。それを `par_map` に変えると、出力順がワーカーの実行順に依存するため拒否されます。`par_map` に渡す関数には、純粋性とキャプチャの両方の制限があります。Move 値はキャプチャできず、確保先を指定する `region` も、Copy 型ですがワーカーへ渡せません。これらはコンパイラが検査し、`Send` や `Sync` の注釈は書きません。 ## `par_map` ― データ並列 @@ -100,7 +100,7 @@ fn main() -> Result<(), Error> { `spawn(fn { ... })` はタスクを開始してハンドルを返し、`wait()` はグループの完了を待ちます。`.get()` は join の成功後に結果を読みます。タスクは `task_group` より長生きできず、早期リターンを含めスコープを離れるときには残りのタスクを join します。 -タスクが1つだけでも、`spawn` が戻る前にワーカーが実行できる状態になります。ただし、ワーカーが使用中なら開始は遅れます。キャプチャしたビューは元のデータを共有するため、`wait` まで元のデータを変更・解放できません。タスクハンドルを捨てたり再代入したりしても、この保護は続きます。タスクは共有ビューを読み、自分で作ったデータを変更できます。 +タスクが1つだけでも、`spawn` が戻る前にワーカーが実行できる状態になります。ただし、ワーカーが使用中なら開始は遅れます。キャプチャしたビューは元のデータを共有するため、`wait` まで元のデータを変更・解放できません。タスクハンドルを捨てたり再代入したりしても、この保護は続きます。タスクは共有ビューを読み、自分で作ったデータや、後述の所有権移転で受け取ったデータを変更できます。 `par_map` と異なり、spawn したタスクは I/O などの非純粋な処理を実行できます。`wait()` も自分のグループの未実行タスクを処理するため、すべてのワーカーが使用中でも入れ子のグループが進行できます。ただし、プログラムが作った循環した待ち合わせを解消するものではありません。タスクの結果には、外部の記憶域を借用しないプリミティブ値、string、buffer、file、通常の動的配列、レコード、タプル、sum 型を使えます。`Result` で包むこともできます。所有データを含められますが、ビュー、ポインタ、関数値、file 以外のネイティブハンドルや resource は含められません。wait の成功後、Copy 値の `get()` は繰り返し呼べます。所有値の `get()` はハンドルを消費し、結果を一度だけ移します。結果はグループ終了後も使えます。グループ終了時には全タスクを待ち、wait が失敗した場合も未取得の結果を解放します。共有キャプチャは引き続き読み取り専用です。連続した出力を並列生成するには、各タスクで区間ごとの所有値を作り、名前を付けたハンドルを区間順に取得します。 @@ -112,7 +112,7 @@ fn main() -> Result<(), Error> { import std.fs fn read_window(pack: file) -> Result { - mut window := buffer(4096) + mut window := buffer.try_new(4096)? pack.pread_into(window, 0, 4096, 0)? Ok(window) } @@ -129,6 +129,10 @@ fn main() -> Result<(), Error> { } ``` +`buffer.try_new(4096)?` は指定した容量を確保し、失敗すれば読み取り前に確保エラーを伝播します。 +`pread_into` はその範囲までを読み込み、短い読み取りでは結果も短くなります。 +この例が読むのは一つの区間であり、ファイル全体ではありません。 + 同じファイル所有者を直接返すことも、読み込んだ buffer と一緒にレコードで返すこともできます。通常の所有値と同じく、`get()` で一度だけ受け取ります。ファイルの同一性を保ちながら、サイズを制限した読み取りバッファを再利用できます。GPU resource や signal subscription は引き続き所有スレッドに限定され、排他的借用のキャプチャには対応しません。 失敗する可能性があるタスクは `Result` を返します。グループのエラーを伝播させるには `wait()?` と書きます。 diff --git a/docs/guide/ja/16-toolchain.md b/docs/guide/ja/16-toolchain.md index b55705b9..227d963a 100644 --- a/docs/guide/ja/16-toolchain.md +++ b/docs/guide/ja/16-toolchain.md @@ -117,12 +117,14 @@ CPU名の欠落、空文字、埋め込みNUL、未知の名前、別アーキ --thin-lto # cross-unit ThinLTO ``` -Apple ターゲットでは、deployment target は CPU と同じくターゲット識別子の一部です。解決箇所は 1 か所、優先順位は 1 つで、オブジェクト、モジュール IR、リンク、キャッシュキーはすべて同じ文字列から導かれます。値は `--deployment-target`、プラットフォームの `MACOSX_DEPLOYMENT_TARGET`(`IPHONEOS_`/`WATCHOS_`/`TVOS_`/`XROS_`)環境変数、ホストの `sw_vers -productVersion`、文書化されたプラットフォームごとの下限値の順に、最初に値を与えた段階が採用されます。各段階の値は `major.minor` の精度で読み取り、不正な値はその段階を明示したエラーになります(次の段階へは進みません)。環境変数が未設定の場合は次の段階に進みますが、空文字などの使用できない値が設定されている場合は「値が与えられた」とみなしてエラーになります。Apple 以外のホストで `--deployment-target` を指定した場合も、黙って無視せずエラーになります。バージョンは識別子に含まれ OS のパッチレベルは含まれないため、OS のパッチ更新だけではビルドキャッシュは無効化されず、deployment target を変更すると無効化されます。 +Apple ターゲットでは、deployment target は生成する成果物の最低 OS バージョンを指定し、ビルドキャッシュの識別子にも含まれます。値は `--deployment-target`、プラットフォームの `MACOSX_DEPLOYMENT_TARGET`(`IPHONEOS_`/`WATCHOS_`/`TVOS_`/`XROS_`)環境変数、ホストの `sw_vers -productVersion`、文書化されたプラットフォームごとの下限値の順に、最初に値を与えた段階が採用されます。各段階の値は `major.minor` の精度で読み取り、不正な値はその段階を明示したエラーになります(次の段階へは進みません)。環境変数が未設定の場合は次の段階に進みますが、空文字などの使用できない値が設定されている場合は「値が与えられた」とみなしてエラーになります。Apple 以外のホストで `--deployment-target` を指定した場合も、黙って無視せずエラーになります。バージョンは識別子に含まれ OS のパッチレベルは含まれないため、OS のパッチ更新だけではビルドキャッシュは無効化されず、deployment target を変更すると無効化されます。 `--sdk-version` はこれとは別に、Apple オブジェクトの `LC_BUILD_VERSION` へ明示的な SDK の来歴を記録します。`major`、`major.minor`、`major.minor.patch` を受け付け、省略時は従来どおりオブジェクトの SDK を未指定(`sdk n/a`)のままにします。SDK や sysroot を選択するオプションではなく、`SDKROOT` や `xcrun` から暗黙に補いません。正規化した値は codegen と ThinLTO のキャッシュ識別子に含めますが、フロントエンドキャッシュには含めません。不正な値と Apple 以外のターゲットでの指定は、ソースや成果物を処理する前にエラーになります。 既定は移植性のある `baseline` ターゲットと `release` プロファイルです。`native` は現在のマシン向け、`x86-64-v3` などの LLVM CPU 名は配布先のハードウェアが決まっている場合に使えます。 -ランタイム LTO は `release` / `fast` で**既定で有効**です。文字列述語のパイプラインで実測2〜3倍の速度改善があり、その他の測定では性能低下はなく、コンパイル時間の増加は1〜2msでした。`dev` / `small` / `tiny` では無効で、`--no-rt-lto` / `--rt-lto` で切り替えられます。`--thin-lto` はコンパイル時間と最適化の範囲を変えるため、明示的に指定します。`release` / `fast` のリンクを伴う `build` / `run` / `size` に適用され、並列化とキャッシュに対応し、ランタイム LTO と組み合わせられます。 +ランタイム LTO は `release` / `fast` で**既定で有効**です。`dev` / `small` / `tiny` では無効で、`--no-rt-lto` / `--rt-lto` で切り替えられます。`--thin-lto` はコンパイル時間と最適化の範囲を変えるため、明示的に指定します。`release` / `fast` のリンクを伴う `build` / `run` / `size` に適用され、並列化とキャッシュに対応し、ランタイム LTO と組み合わせられます。 + +ランタイム LTO はランタイムの補助関数の本体を最適化対象に含め、文字列の条件判定などから関数呼び出しを取り除けるようにします。[計測記録](../../../bench/rt_lto/README.md)は特定の処理とターゲットでの結果です。自分のプログラムでも、有効・無効の両方を測って判断してください。 本番を代表する処理を使って、実行時の計測結果に基づく PGO も利用できます。 @@ -231,7 +233,7 @@ align> 1 + 2 セッションは**少しずつ書き足していく1つの Align プログラム**です。入力のたびに `alignc build` と同じドライバで全体を再コンパイルし、生成されたネイティブバイナリを実行します。インタプリタや JIT は使いません。プロファイル、ランタイム LTO の既定値、生成するオブジェクトは通常のコンパイルと同じです。 -入力のたびにプログラム全体を実行し直します。すでに表示した出力は省き、新しい行だけを表示します。 +入力のたびにプログラム全体を実行し直すので、以前の I/O やその他の副作用も再び起きます。REPL は変わらなかった出力を省き、新しい行だけを表示します。 ```text align> x := 5 diff --git a/docs/guide/ja/17-the-align-way.md b/docs/guide/ja/17-the-align-way.md index bc0fce18..d1f8f3da 100644 --- a/docs/guide/ja/17-the-align-way.md +++ b/docs/guide/ja/17-the-align-way.md @@ -33,7 +33,7 @@ total := xs.map(f).where(p).sum() ## データ並列とタスクを使い分ける -要素ごとの計算が重い場合は `par_map` を検討します。ベクトル化された逐次パイプラインのほうが速い場合もあるので、計測してください。関数には Pure が要求され、キャプチャした参照先は変更できません。I/O を含む独立した仕事には `task_group` / `spawn` を使います。タスクは非純粋でも構いませんが、キャプチャしたビューは join まで読み取り専用です。どのタスクも `Result` を返さなければ `wait()`、失敗するタスクの結果を伝播するには `wait()?` を使います。wait の成功後、`.get()` で Copy の結果を読み取るか、所有結果を一度だけ受け取ります。どちらも、ユーザーが純粋性の注釈を書かずにキャプチャとライフタイムを検査します。 +要素ごとの計算が重い場合は `par_map` を検討します。ベクトル化された逐次パイプラインのほうが速い場合もあるので、計測してください。関数には Pure が要求され、キャプチャした参照先は変更できません。I/O を含む独立した仕事には `task_group` / `spawn` を使います。タスクは非純粋でも構いませんが、キャプチャしたビューは join まで読み取り専用です。どのタスクも `Result` を返さなければ `wait()`、失敗するタスクの結果を伝播するには `wait()?` を使います。リテラルの `spawn(fn { ... })` には、buffer や file など外部の記憶域を借用しない所有値を移せます。通常の関数値やパイプラインのラムダがキャプチャできるのは、引き続き Copy 値だけです。wait の成功後、`.get()` で Copy の結果を読み取るか、所有結果を一度だけ受け取ります。どちらも、ユーザーが純粋性の注釈を書かずにキャプチャとライフタイムを検査します。 ## コンパイラが解析しやすい形で書く diff --git a/docs/guide/ja/18-std-services.md b/docs/guide/ja/18-std-services.md index 9f465782..318b535a 100644 --- a/docs/guide/ja/18-std-services.md +++ b/docs/guide/ja/18-std-services.md @@ -70,6 +70,43 @@ pub fn main(args: array) -> Result<(), Error> { `cl.get_many(urls, degree)` は、入力順を保ちつつ、指定した上限内で複数のブロッキング I/O を並行して進めます。サーバー側には `http.serve`、`accept`、リクエストビュー、`http.response`、`respond` などの基本操作があります。SSE などでボディをストリーミングする場合は `respond_stream` が `http_stream` を返します。リクエストコンテキストは借用された spent 状態になり、内容のビューは引き続き参照できます。チャンクを `send` で送り、正常に完了するには `finish` を呼びます。最初の `send` より前なら、`reject(rb)` で通常のエラーレスポンスに切り替えることもできます。 +### リスナーを所有し、リクエストビューをキャプチャする + +`http.serve` が返すリスナーは Move 型の `http_server` です。呼び出し元が所有したまま、 +補助関数が借用して `accept` できます。`ctx.view()` は Copy 型の +`http_request_view` を返し、リクエストの読み取りやコールバックへのキャプチャに使えます。 + +```align +import std.http + +fn receive(borrow server: http_server) -> Result = server.accept() +fn checkpoint(read: fn() -> Result) -> Result = read() + +fn main() -> Result<(), Error> { + server := http.serve("127.0.0.1", 8080)? + ctx := receive(server)? + request := ctx.view() + print(request.path()) + print(checkpoint(fn { request.peer_eof() })?) + return ctx.respond(http.response(200)) +} +``` + +このプログラムはリクエストを一つ受け付け、パスと EOF の観測結果を表示し、 +本文が空の 200 レスポンスを返します。コールバックがキャプチャするのはビューなので、 +最後にビューを使った後で `ctx` から応答できます。ビューの生成では領域を確保せず、 +リクエストのバイト列もコピーしません。`method`、`path`、`body`、`headers`、 +`upgrade_ready`、`peer_eof` を使えますが、応答の送信や接続の所有はできません。 + +`peer_eof()` は、ブロックせず、受信データを消費せず、一度だけ状態を観測します。 +`true` には送信側だけを閉じた相手も含まれ、その相手が応答を待っている場合もあります。 +`false` も相手の生存を保証しません。観測結果によって処理を中止するかは、 +アプリケーションが決めます。この例は観測エラーを `?` で伝播し、真偽値にかかわらず応答します。 + +リクエストビューはコンテキストより長く使えません。spawn したタスクにキャプチャした場合は、 +応答の送信やレスポンス・アップグレードのストリーム開始前にタスクを join します。 +ライフタイムと応答の詳しい規則は [HTTP の仕様](../../impl/std-design/ja/http.md)を参照してください。 + ### 本文全体を保持せずバイナリを受信する 実行できる [http_fetch の例](../../../examples/http_fetch.align) は、 diff --git a/docs/guide/ja/27-multimodal-reference.md b/docs/guide/ja/27-multimodal-reference.md index d466335a..f33cf56c 100644 --- a/docs/guide/ja/27-multimodal-reference.md +++ b/docs/guide/ja/27-multimodal-reference.md @@ -1,5 +1,7 @@ # 上限を明示したテキスト・音声・映像パイプライン +> 🌐 [English](../27-multimodal-reference.md) · **日本語** + [`examples/multimodal/`](../../../examples/multimodal/) は、通常の Align で 書いた実行可能なアプリケーションです。HTTP、所有する JSON、構造化タスク、 保持したディレクトリ、`std.process.child_scope` を組み合わせます。 @@ -12,7 +14,7 @@ ## Linux または WSL2 で実行する -LLVM22 と FFmpeg/ffprobe を用意します。WSL2 は既存の child_scope の +LLVM 22 と FFmpeg/ffprobe を用意します。WSL2 は既存の child_scope の カーネル機能検査を通る必要があります。管理プロセスを含む参照実装は、非対応の カーネルと macOS ではワーカー開始前に拒否します。共通のソース・モデル検査は macOS でも動作します。 @@ -29,7 +31,7 @@ root=$(mktemp -d) "$(pwd)/main" serve "$root" 8080 8081 8082 /usr/bin/ffmpeg ``` -ROOT は絶対パスで指定する、空の既存ディレクトリ(モード0700)です。 +ROOT は絶対パスで指定する、空の既存ディレクトリ(モード 0700)です。 実行ファイルも絶対パスで起動します。終了要求までプロセスは動き続けます。 別の端末からパイプラインを投入します。 @@ -66,23 +68,24 @@ curl -sS http://127.0.0.1:8080/v1/jobs/pipeline \ HTTP スナップショットを交換します。可変のジョブ表は共有しません。 全応答に明示的な書き込み時間制限があります。 -出力は固定4KiB の作業領域と、ジョブ当たり最大1MiB の予約を使います。 +出力は固定 4 KiB の作業バッファを使い、ジョブごとに最大 1 MiB を予約します。 成果物はファイルのまま保持します。完了済みジョブは3件を公開し、古いものを -退役させます。既存の読み手のリースがあれば、正確な ID の解放まで退役した -ファイルと予約を保持します。イベント切断はジョブをキャンセルしません。 +退役させます。すでにファイルを読んでいる読み手は、ジョブの公開が終わった後も読み続けられます。 +その読み手が対応する ID のリースを解放するまで、ファイルと予約領域を保持します。イベント切断はジョブをキャンセルしません。 JSON の None フィールドは省略し、不明な進捗率・未対応のアライメントも 省略します。模擬音声は既知の無音区間のみをサンプルフレームで報告し、 単語・音素の時刻を作りません。 各フェーズはパイプを読み、ネイティブのプロセス状態を確認し、child_scope を 明示的に解放してから公開・次フェーズ開始を行います。解放に失敗すると -Faulted の所有を保持し、次の起動を止めます。このプロセス不在の証明だけで -実エンジンのデバイスメモリ解放を証明することはできません。 +Faulted の所有を保持し、次の起動を止めます。解放の成功は管理対象のプロセスが終了したことを示しますが、 +実エンジンのデバイスメモリが解放されたことまでは確認できません。 正常終了は投入を止め、管理処理を解放し、配信タスクを join してから記録された 所有ファイルだけを削除し、最後に `controller.lock` を削除します。 -異常終了ではフェンスを残します。残るプロセス・デバイスの所有を確認してから -手動で削除してください。ルートの終了や経過時間だけでは不十分です。 +異常終了ではロックファイルを残します。生存するプロセスやデバイス資源を確認してから、 +手動で削除してください。メインプロセスが終了しても、その子孫やデバイス上の処理が +終わったとは限りません。 回帰検査は `scripts/cargo.sh test -p align_driver --test multimodal_reference` です。Linux の統合検査は FFmpeg と ffprobe をインストールして実行します。 diff --git a/docs/guide/ja/api-by-task.md b/docs/guide/ja/api-by-task.md index 1a76523c..84907e75 100644 --- a/docs/guide/ja/api-by-task.md +++ b/docs/guide/ja/api-by-task.md @@ -35,7 +35,9 @@ 結果を保存したい場合は、明示的に実体化する終端操作を選びます。借用ビューは所有者の 寿命の範囲内で使います。`par_map` に渡す関数には Pure が必要です。 spawn したタスクは I/O を実行できますが、共有キャプチャはグループの join まで -読み取り専用です。 +読み取り専用です。リテラルの `spawn(fn { ... })` には、buffer や開いた file など、 +外部の記憶域を借用しない所有値も渡せます。移転後は元の所有者とそのビューを使えません。 +対応する型はタスクの章を参照してください。 ## 文字列やデータ形式を扱う @@ -43,6 +45,7 @@ spawn したタスクは I/O を実行できますが、共有キャプチャは |---|---|---|---| | 元のデータがなくなった後も文字列を保持する | `str.clone()` → 所有文字列 `string` | [文字列とテキスト](07-strings-and-text.md) | [文字列とbuilder](../../impl/core-design/ja/string.md) | | 連結を繰り返さずに文字列を組み立てる | `builder`, `.write`, `.write_int`, `.to_string` | [builderの例](07-strings-and-text.md) | [文字列とbuilder](../../impl/core-design/ja/string.md) | +| 組み立て中のテキストを読む | `builder.len`, `.bytes` | [builder のビュー](07-strings-and-text.md#builder) | [文字列と builder](../../impl/core-design/ja/string.md) | | JSONから独立した所有フィールドを読み出す | `json.decode`, `string` / `array` のレコードフィールド | [所有JSONの完全な例](../../../examples/json_owned.align) | [JSONの型と所有権](../../impl/core-design/ja/json.md) | | JSONを借用文字列や列として読む | `json.decode`, `str`, `soa`, `arena` | [JSON](08-json.md) | [JSONの型と所有権](../../impl/core-design/ja/json.md) | | スキーマが未知の JSON を調べる・転送する | `json.doc`, `.get`, `.at`, `json.encode_options` | [ドキュメントビュー](08-json.md#スキーマが未知の場合) | [JSONの型と所有権](../../impl/core-design/ja/json.md) | @@ -79,9 +82,10 @@ UTF-8が必要な操作に渡すまでは、バイナリデータをバイト列 | やりたいこと | API・言語機能 | 使用例・解説 | 詳しい契約 | |---|---|---|---| | HTTPリクエストを送る | `http.client`, `.get`, `.request` | [HTTP](18-std-services.md) | [HTTP](../../impl/std-design/ja/http.md) | +| HTTPリスナーを補助関数へ渡す・コールバックでリクエストを調べる | `http_server`, `borrow`, `ctx.view`, `http_request_view` | [リスナーとリクエストビュー](18-std-services.md#リスナーを所有しリクエストビューをキャプチャする) | [HTTP](../../impl/std-design/ja/http.md) | | 上限を指定してHTTPのバイナリをダウンロードする | `.request_stream`, ストリームの `.read` | [取得処理の完全な例](../../../examples/http_fetch.align) | [HTTPストリーム受信](../../impl/std-design/ja/http.md) | | TCP接続やホスト名の解決を行う | `import std.net` の `tcp.connect`, `dns.resolve` | [ネットワーク](18-std-services.md) | [ネットワーク](../../impl/std-design/ja/net.md) | -| 子プログラムを動かし、その出力を調べる | `process.command`, `.run`, `.code`, `.stdout` | [出力キャプチャの例](../../impl/std-design/ja/process.md) | [プロセス](../../impl/std-design/ja/process.md) | +| 子プログラムを動かし、その出力を調べる | `process.command`, `.run`, `.status`, `.stdout` | [終了状態と出力](../../impl/std-design/ja/process.md#現在の終了状態出力-api) | [プロセス](../../impl/std-design/ja/process.md) | | バイト列を圧縮する・ファイルを逐次ハッシュする | `compress.gzip_compress`, `crypto.sha256_stream` | [サービスとハッシュの例](18-std-services.md) | [圧縮](../../impl/std-design/ja/compress.md)、[暗号](../../impl/std-design/ja/crypto.md) | | 明示したwriterへログを書く | `log.new`, `.line`, `.flush` | [ログ](18-std-services.md) | [ログ](../../impl/std-design/ja/log.md) | diff --git a/docs/impl/00-overview.md b/docs/impl/00-overview.md index 40cb79b9..22772b7b 100644 --- a/docs/impl/00-overview.md +++ b/docs/impl/00-overview.md @@ -14,7 +14,9 @@ Approach Fix the whole design first / drive a vertical skeleton ### Implementation language: Rust -The standard choice for compiler implementation. Lexer/parser generators, LLVM bindings (`inkwell`), and strong types + ownership for self-checking are all available. It also serves as the foundation for later writing Align in Align (self-hosting). +Rust provides LLVM bindings (`inkwell`) and types that help enforce compiler invariants. +Align uses a handwritten lexer and parser. The compiler remains implemented in Rust; +self-hosting is not a prerequisite for language or library work. ### Backend: straight to LLVM, but through MIR @@ -75,6 +77,8 @@ alignc/ workspace root align_fmt/ token-preserving source formatter align_driver/ CLI, import DAG, object cache, parallel codegen, link (end-to-end tests live under this crate) + align_watch/ filesystem observation and rebuild scheduling for build --watch + align_repl/ interactive frontend, persistent session state, native execution ``` Stage responsibilities and IR-boundary details in `01-pipeline.md`. @@ -145,4 +149,6 @@ The design invariants in `draft.md` / `docs/design-notes.md` remain binding at t - allocation / error / side effects / parallelism / unsafe must be **traceable even in generated code** (nothing hidden). - Restrictions are an information source for compiler inference. Infer no-alias / non-null / arena lifetime / cold error path without exposing lifetimes in source (`03-types.md`). -- Achieve, in MIR, the lowering by which `map` / `reduce` / `scan` / `filter` / `mask` vectorize naturally (`04-mir.md`). +- Preserve the information needed to fuse and vectorize `map` / `where` / `reduce` / `scan` + pipelines and mask operations in MIR. Effects, traps, layout and target cost can prevent + vectorization; the source spelling alone does not guarantee SIMD (`04-mir.md`). diff --git a/docs/impl/06-runtime-std.md b/docs/impl/06-runtime-std.md index 771a233c..8bc48080 100644 --- a/docs/impl/06-runtime-std.md +++ b/docs/impl/06-runtime-std.md @@ -194,13 +194,17 @@ void* align_rt_tg_wait(TaskGroup*) // lowest-index error slot, or null void align_rt_tg_end(TaskGroup*) ``` -I/O-wait concurrency (`draft.md` §11). Spawn snapshots captures into the task-group region; `wait` +I/O-wait concurrency (`draft.md` §11). Spawn stores evaluated captures in the task-group region; `wait` joins registered tasks using the persistent shared pool and reports the failure from the lowest spawn index. The caller also claims work, so nested groups can progress without a free worker. Owned result slots supply a cleanup thunk and an initialized flag: `get()` transfers one owned result, and group exit drops untaken results after joining. Cleanup remains -registered across waits. Copy results allow repeated reads. The result and capture -domains are fixed by [Plan228](228-task-owned-results.md). There is no async/await. +registered across waits. Copy results allow repeated reads. +[Plan228](228-task-owned-results.md) defines the owned-result lifecycle; +[Plan259](259-task-owned-captures.md) adds owned inputs and file-bearing results. +A literal task moves admitted self-contained owners into ordinary worker parameters, +which drop unreturned inputs. Shared Copy views remain read-only until join. +Ordinary stored and pipeline closures retain Copy-only captures. There is no async/await. --- diff --git a/docs/impl/07-roadmap.md b/docs/impl/07-roadmap.md index 6653cabc..3ded0b4f 100644 --- a/docs/impl/07-roadmap.md +++ b/docs/impl/07-roadmap.md @@ -267,7 +267,7 @@ every item below has since completed as recorded in the per-milestone sections, first-class-closures → `task_group` arc (slices ①–④c) is COMPLETE**: closures as values / captures / higher-order arguments, and a real parallel `task_group` with structured join, sound `get`-before-`wait`, and a `wait()?` error boundary. - **Successful-Wait flow correction pending am-w (2026-07-31):** the settled proof follows an + **Successful-Wait flow correction implemented:** the proof follows an exact group/generation/Wait id through bare Result locals, copy/reassignment, `map_err`, and value-producing control, is consumed on the Ok continuation of `?`/Result `match`/Result `else`, and requires every earlier Wait for that drained generation to resolve Ok. A later empty Wait cannot @@ -280,14 +280,15 @@ every item below has since completed as recorded in the per-milestone sections, it also invalidates covered Tasks, while after completion one later Wait can reauthorize old and new handles. Calls/returns/captures/imports/aggregate - reconstruction do not transport it. The current traversal does not implement the complete - stored-result, match, and loop joins and can admit `get()` on an uninitialized slot. Each Move + reconstruction do not transport it. `align_sema::task_wait` implements the stored-result, + match and loop joins, rejecting reads without successful completion. Each Move Task handle also carries its originating group through transparent local/control transfer; `get()` checks that group rather than the innermost active group. Nested entry retains outer facts, inner Wait cannot authorize an outer Task, and handling an outer Wait Result inside the inner group updates the outer fact. Exit clears proofs naming the exited group, including one on its Result block value, without clearing outer proofs. Current primitive `get()` is - non-consuming and repeatable. Am-w owns the producer correction and scale matrix. + non-consuming and repeatable. Owned results and captures follow the later Plan228/Plan259 + lifecycle; this paragraph records the successful-Wait proof rather than its result domain. 4. **Language-spec stock-take (2026-06-24) → the "big three" expressiveness gaps.** Before `std`, the language itself needs three interlocked features to be expressively complete (model any domain, handle errors well). Validated against an external review pass (Codex/Gemini); these are diff --git a/docs/impl/core-design/array-slice-pipeline.md b/docs/impl/core-design/array-slice-pipeline.md index 49541764..20a80c4c 100644 --- a/docs/impl/core-design/array-slice-pipeline.md +++ b/docs/impl/core-design/array-slice-pipeline.md @@ -1,6 +1,3 @@ -This directory records the per-area design of the `core` library: signatures, -ownership and effect classification, error policy, pitfalls, and related tests. - # core — array / slice / the pipeline > 🌐 **English** · [Japanese](./ja/array-slice-pipeline.md) @@ -131,18 +128,18 @@ array field. The complete capability shipped on 2026-08-14. fields for primitive Copy scalar P, including zero. The field adds no allocation or Drop. A completely zero-sized heap element retains its logical initialized count without payload storage. Read a complete fixed field as an ordinary Copy value (`shape := values[i].shape`), -then index or slice that local. Other fixed element families, broader borrowed payloads -and direct indexed-parent fixed-field views remain separate; Plan232 adds independent value indexing. +then index or slice that local. Plan232 adds direct independent value indexing; +Plan242 adds the read-only indexed-field ranges described above. Other fixed +element families and broader borrowed payloads remain outside this extension. R78's request for payload-free enum fields remains deferred/PROPOSED; the closed enum-field exclusion above is unchanged. [Plan 56](../56-r77-r83-composition-plan.md) records the assessment and plan 23's unmet reopening prerequisite. No enum-field admission, layout, ownership or cleanup change is selected for this batch. -The other six composition requests are implemented on the provider branch. Their -indexed Move-field call place is dynamic-indexed for slice/AoS views and -integer-literal-indexed for source-formed fixed `StructArray` views; the provider -owner and consumer-pending boundary are recorded in plan 56. +Plan56 also records the implemented shared-call forms for indexed Move fields: +runtime indices for slice/AoS views and integer-literal indices for source-formed +fixed `StructArray` views. Region form (required L6, **shipped**): diff --git a/docs/impl/core-design/hash.md b/docs/impl/core-design/hash.md index 438f31a8..bfefafbe 100644 --- a/docs/impl/core-design/hash.md +++ b/docs/impl/core-design/hash.md @@ -1,6 +1,3 @@ -This directory records the per-area design of the `core` library: signatures, -ownership and effect classification, error policy, pitfalls, and related tests. - # core — hash (+ scalar math) > 🌐 **English** · [Japanese](./ja/hash.md) diff --git a/docs/impl/core-design/ja/array-slice-pipeline.md b/docs/impl/core-design/ja/array-slice-pipeline.md index bf12e60a..7507eafa 100644 --- a/docs/impl/core-design/ja/array-slice-pipeline.md +++ b/docs/impl/core-design/ja/array-slice-pipeline.md @@ -1,6 +1,3 @@ -このディレクトリは `core` ライブラリの各領域の設計仕様をまとめる。 -シグネチャ、所有権と effect の分類、エラー方針、注意点、関連テストを記載する。 - # core — array / slice / パイプライン > 🌐 [English](../array-slice-pipeline.md) · **日本語** @@ -124,17 +121,18 @@ Option や array を要素とする array は対象外である。正確な ledg `[P; N]` record field を、長さ0も含めて受け入れる。field 自体の allocation と Drop は増えない。 record 全体が0バイトでも、heap payload を確保せず初期化済み要素数を保持する。 `shape := values[i].shape` で field 全体を通常の Copy 値として読み、その local を -index・slice 操作できる。他の固定配列要素型、borrowed payload の拡張、 -indexed parent から直接 fixed field の view を作る構文は別の対応範囲である。独立した値の添字読み出しは Plan232 が扱う。 +index・slice 操作できる。Plan232 は独立した値の直接の添字読み出しを、Plan242 は +前述の読み取り専用のフィールド範囲を扱う。他の固定長配列要素型と、借用ペイロードの +拡張はこの対応に含まない。 R78 の payload-free enum field 対応依頼は保留/PROPOSED のままであり、既存の 閉じた enum-field 除外規則は変わらない。[Plan 56](../../56-r77-r83-composition-plan.md) に 調査結果と plan 23 の未充足の再検討条件を記録する。この batch では enum field の 受理、layout、所有権、cleanup の変更を選択していない。 -残る6件の composition request は provider branch に実装済みである。indexed Move field の -shared-call place は slice/AoS view では runtime 添字、source-formed な固定 `StructArray` view -では整数リテラル添字を使う。provider owner と consumer 側の未完了境界は plan 56 に記録する。 +Plan56 は、添字付き Move フィールドを共有借用の関数へ渡す実装済みの形式も記録する。 +slice/AoS ビューでは実行時の添字を、ソースで構築した固定 `StructArray` ビューでは +整数リテラルの添字を使う。 region 形式(必須L6、**実装済み**): diff --git a/docs/impl/core-design/ja/hash.md b/docs/impl/core-design/ja/hash.md index 22141325..b2bc6b80 100644 --- a/docs/impl/core-design/ja/hash.md +++ b/docs/impl/core-design/ja/hash.md @@ -1,6 +1,3 @@ -このディレクトリは `core` ライブラリの各領域の設計仕様をまとめる。 -シグネチャ、所有権と effect の分類、エラー方針、注意点、関連テストを記載する。 - # core — hash (+ scalar math) > 🌐 [English](../hash.md) · **日本語** @@ -53,7 +50,7 @@ Pure。ハッシュ結果は、与えられた入力に対して **1 つのビ ## 提供状況と制限 - **契約には含まれない事項:** *ビルド間やバージョン間* でのハッシュ出力の安定性 — 固定シードを持つ wyhash は現状たまたま安定しているが、§18.1 ではこれを明示的に「オンディスクやワイヤーフォーマットとして安定したものではない」と定めている。テストやユーザー側で、特定の正確なハッシュ値を API の仕様として固定(pin)することを許してはならない(ドライバのテストで固定するのは、決定性、レーンの等価性、ビューの受理といった *プロパティ* のみである)。 -- DoS 耐性はなく、暗号用途でもない — 実装済みの [`std.crypto`](../../std-design/crypto.md) が暗号操作を提供する。セキュリティが要求される場面で「手軽に hash64 で済ませる」という方法は拒否すること。 +- DoS 耐性はなく、暗号用途でもない — 実装済みの [`std.crypto`](../../std-design/ja/crypto.md) が暗号操作を提供する。セキュリティが要求される場面で「手軽に hash64 で済ませる」という方法は拒否すること。 - 初等関数 `exp`/`exp2`/`log`/`log2`/`log10` は plan 75 に従って実装済み。LLVM のスカラー/ベクタ intrinsic へ直接変換する。全入力に対する ULP 上限やターゲット間のビット一致は保証しない。命令選択前のスカラー化は診断に表示するが、ベクタ IR が残っていても後段でスカラー化され得る。最終的な SIMD 命令は出力オブジェクトを検査して確認する。 - `expm1`/`log1p` は各値の幅に一致するスカラー libm 関数を呼び、ベクタでは各レーンに適用する([Plan234](../../234-cancellation-sensitive-math.md))。`sin`/`cos`/`atan2` は LLVM のスカラー/ベクタ intrinsic を使う([Plan235](../../235-trigonometric-math.md))。すべて Pure で Align の領域を確保せず、精度とベクタ化には上記の制限がある。`atan2` の両辺は同じ型で、スカラーのブロードキャストは行わない。結果は [-pi, pi] のラジアン値になる。 - `core.bitset`(§18.1 に隣接する要素) — 未実装。ビット操作は整数演算子で行う。そのメモリレイアウトの問題は packed-bool な soa カラムと密接に関連しているため、一緒に決着させること。 diff --git a/docs/impl/core-design/ja/soa-groupby.md b/docs/impl/core-design/ja/soa-groupby.md index a2c769bf..ba9ffec7 100644 --- a/docs/impl/core-design/ja/soa-groupby.md +++ b/docs/impl/core-design/ja/soa-groupby.md @@ -1,6 +1,3 @@ -このディレクトリは `core` ライブラリの各領域の設計仕様をまとめる。 -シグネチャ、所有権と effect の分類、エラー方針、注意点、関連テストを記載する。 - # core — soa / group_by / dict_encode > 🌐 [English](../soa-groupby.md) · **日本語** diff --git a/docs/impl/core-design/ja/test.md b/docs/impl/core-design/ja/test.md index 135726af..694a4bfe 100644 --- a/docs/impl/core-design/ja/test.md +++ b/docs/impl/core-design/ja/test.md @@ -1,6 +1,3 @@ -このディレクトリには `../std-design/` と同じ深さで `core` ライブラリ各領域の正式な -設計文書(シグネチャ、ownership/effect 分類、error policy、pitfall、test anchor)を置く。 - # core.test — 言語内テスト > [English](../test.md) · **日本語** diff --git a/docs/impl/core-design/ja/vec-mask.md b/docs/impl/core-design/ja/vec-mask.md index c52e0fea..4f5b9efe 100644 --- a/docs/impl/core-design/ja/vec-mask.md +++ b/docs/impl/core-design/ja/vec-mask.md @@ -1,6 +1,3 @@ -このディレクトリは `core` ライブラリの各領域の設計仕様をまとめる。 -シグネチャ、所有権と effect の分類、エラー方針、注意点、関連テストを記載する。 - # core — vecN / maskN / align(N) > 🌐 [English](../vec-mask.md) · **日本語** diff --git a/docs/impl/core-design/soa-groupby.md b/docs/impl/core-design/soa-groupby.md index e05b2c4d..cb3ed543 100644 --- a/docs/impl/core-design/soa-groupby.md +++ b/docs/impl/core-design/soa-groupby.md @@ -1,6 +1,3 @@ -This directory records the per-area design of the `core` library: signatures, -ownership and effect classification, error policy, pitfalls, and related tests. - # core — soa / group_by / dict_encode > 🌐 **English** · [Japanese](./ja/soa-groupby.md) diff --git a/docs/impl/core-design/test.md b/docs/impl/core-design/test.md index 621d179d..1642ec1c 100644 --- a/docs/impl/core-design/test.md +++ b/docs/impl/core-design/test.md @@ -1,7 +1,3 @@ -This directory holds the authoritative per-area design docs for the `core` library, at the same -depth as `../std-design/` (signatures, ownership/effect classification, error policy, pitfalls, -and test anchors). - # core.test — in-language tests > 🌐 **English** · [Japanese](./ja/test.md) diff --git a/docs/impl/core-design/vec-mask.md b/docs/impl/core-design/vec-mask.md index cee11737..3e736960 100644 --- a/docs/impl/core-design/vec-mask.md +++ b/docs/impl/core-design/vec-mask.md @@ -1,6 +1,3 @@ -This directory records the per-area design of the `core` library: signatures, -ownership and effect classification, error policy, pitfalls, and related tests. - # core — vecN / maskN / align(N) > 🌐 **English** · [Japanese](./ja/vec-mask.md) diff --git a/docs/impl/pkg-design/ja/web.md b/docs/impl/pkg-design/ja/web.md index 60591a8b..40db330c 100644 --- a/docs/impl/pkg-design/ja/web.md +++ b/docs/impl/pkg-design/ja/web.md @@ -252,14 +252,15 @@ web.serve Impure。テーブル借用。セットアップ Err まで リクエスト毎: 不正リクエスト → 400、不一致 → 404/405、ハンドラ `Err` → 500 — 固定最小 JSON、 ループ継続。アプリのエラー語彙(OpenAI error object 等)は `web.status_json` で構築するアプリ ポリシー。unary handler と stream pump の全 `Err` は request method/path と組み込みエラー値全体 -(`NotFound`、`Invalid`、`Denied`、`Code(n)`)を best-effort の stderr 1 行に残し、ログ失敗で +(`NotFound`、`Invalid`、`Denied`、`Timeout`、`Code(n)`)を best-effort の stderr 1 行に残し、ログ失敗で ループを殺さない。リクエスト由来の panic はゼロ。 ## Middleware(2026-07-22 出荷済み) -Fiber の `c.Next()` チェーンはキャプチャクロージャ(延期)を要する。framework がハンドルを所有する -モデルでは、v1 の形は元の Move 連鎖設計より単純になる: `Ctx` は Copy なので、pre-handler は -それを消費も返却もしない。判定には三つの結果を持つ sum 型を使う。 +フレームワークがリクエストのハンドルを所有し、ミドルウェアは Copy の `Ctx` ビューを +受け取る。戻り値の `Middleware` は、続行・応答・失敗の三つを直接表す。 +クロージャの環境を生成元のフレームより長く保持する状態付きミドルウェアは未対応だが、 +通常のローカルなキャプチャ付きクロージャは使える。 ```text Middleware { Proceed, Respond(response_builder), Failed(Error) } diff --git a/docs/impl/pkg-design/web.md b/docs/impl/pkg-design/web.md index 45a6d002..817b0d6e 100644 --- a/docs/impl/pkg-design/web.md +++ b/docs/impl/pkg-design/web.md @@ -263,17 +263,15 @@ programmer error). Per-request: framework maps malformed requests to 400, unmatc handler `Err` to 500 — fixed minimal JSON bodies, loop continues. Application error vocabularies (e.g. the OpenAI error object) are app policy via `web.status_json`. Every unary-handler and stream- pump `Err` emits one best-effort stderr line with the request method/path and the complete builtin -error value (`NotFound`, `Invalid`, `Denied`, or `Code(n)`); a logging failure never kills the loop. +error value (`NotFound`, `Invalid`, `Denied`, `Timeout`, or `Code(n)`); a logging failure never kills the loop. Nothing request-derived can panic; everything is `Result` or a view. ## Middleware (shipped 2026-07-22) -Fiber's `c.Next()` chain needs capturing closures (deferred). The framework-owns-the-handle model -makes the v1 shape simpler than the original Move-threading design: `Ctx` is Copy, so a -pre-handler neither consumes nor returns it. The verdict is an or-kind rather than the originally -sketched nested `Result, Error>`: the current payload model deliberately -does not nest aggregates, and a single tagged value gives the same three outcomes with one clear -drop boundary — +The framework owns the request handle; middleware receives a Copy `Ctx` view and +returns one of three outcomes. `Middleware` names those outcomes directly: +continue, respond, or fail. Stateful middleware that retains an escaping closure +environment remains deferred; ordinary local capturing closures are supported. ```text Middleware { Proceed, Respond(response_builder), Failed(Error) } diff --git a/docs/impl/std-design/cli.md b/docs/impl/std-design/cli.md index 958dee04..9b0c7280 100644 --- a/docs/impl/std-design/cli.md +++ b/docs/impl/std-design/cli.md @@ -1,6 +1,3 @@ -This directory records the detailed `std` module designs that supplement the roadmap. -These specifications define the contract for each module's implementation. - # std.cli — implementation design (M10 Slice 3) > 🌐 **English** · [Japanese](./ja/cli.md) diff --git a/docs/impl/std-design/compress.md b/docs/impl/std-design/compress.md index 8f852f8b..8b88ba6a 100644 --- a/docs/impl/std-design/compress.md +++ b/docs/impl/std-design/compress.md @@ -1,6 +1,3 @@ -This directory records the detailed `std` module designs that supplement the roadmap. -These specifications define the contract for each module's implementation. - # std.compress — implementation design (M11) > 🌐 **English** · [Japanese](./ja/compress.md) diff --git a/docs/impl/std-design/crypto.md b/docs/impl/std-design/crypto.md index f5865c52..5220c1b5 100644 --- a/docs/impl/std-design/crypto.md +++ b/docs/impl/std-design/crypto.md @@ -1,6 +1,3 @@ -This directory records the detailed `std` module designs that supplement the roadmap. -These specifications define the contract for each module's implementation. - # std.crypto — implementation design (M11) **Implemented addition:** diff --git a/docs/impl/std-design/fs.md b/docs/impl/std-design/fs.md index c2318ca6..2a1cecce 100644 --- a/docs/impl/std-design/fs.md +++ b/docs/impl/std-design/fs.md @@ -195,7 +195,14 @@ and the existing handle shell allocate; no read buffer or mmap is allocated. The result retains no path lifetime. The result is the same Move file, owning one descriptor that Drop closes, with -existing bound-receiver, aggregate and single-threaded restrictions. Pread uses +existing bound-receiver and aggregate rules. A literal `spawn(fn { ... })` may +consume the file, and the task may return it directly or in an admitted owning +record. The same descriptor moves between caller and worker; there is no reopen, +duplication or new allocation for the file transfer. Caller aliases expire at +transfer, and borrowed files cannot be moved into tasks. Ordinary stored closures, +`par_map` captures and other native resource owners retain their restrictions. +[Plan259](../259-task-owned-captures.md) defines the complete transfer and cleanup +contract. Pread uses the caller's mutable buffer window, returns actual/short counts and zero EOF; len is live descriptor metadata. Negative pread/pwrite offsets abort before permission checks. Pwrite queries kernel-owned descriptor mode before reading source bytes and diff --git a/docs/impl/std-design/http.md b/docs/impl/std-design/http.md index ec7d439b..bb8abca8 100644 --- a/docs/impl/std-design/http.md +++ b/docs/impl/std-design/http.md @@ -1,6 +1,3 @@ -This directory records the detailed `std` module designs that supplement the roadmap. -These specifications define the contract for each module's implementation. - # std.http — implementation design (M11) > 🌐 **English** · [Japanese](./ja/http.md) diff --git a/docs/impl/std-design/ja/cli.md b/docs/impl/std-design/ja/cli.md index 9743aca8..2092d7a0 100644 --- a/docs/impl/std-design/ja/cli.md +++ b/docs/impl/std-design/ja/cli.md @@ -1,6 +1,3 @@ -このディレクトリは、ロードマップを補う `std` モジュールの詳細な設計仕様をまとめる。 -各モジュールの実装は、この仕様に従う。 - # std.cli — implementation design (M10 Slice 3) > 🌐 [English](../cli.md) · **日本語** diff --git a/docs/impl/std-design/ja/compress.md b/docs/impl/std-design/ja/compress.md index 4143b350..7f46e680 100644 --- a/docs/impl/std-design/ja/compress.md +++ b/docs/impl/std-design/ja/compress.md @@ -1,6 +1,3 @@ -このディレクトリは、ロードマップを補う `std` モジュールの詳細な設計仕様をまとめる。 -各モジュールの実装は、この仕様に従う。 - # std.compress — implementation design (M11) > 🌐 [English](../compress.md) · **日本語** diff --git a/docs/impl/std-design/ja/crypto.md b/docs/impl/std-design/ja/crypto.md index b6fa99ed..28759999 100644 --- a/docs/impl/std-design/ja/crypto.md +++ b/docs/impl/std-design/ja/crypto.md @@ -1,6 +1,3 @@ -このディレクトリは、ロードマップを補う `std` モジュールの詳細な設計仕様をまとめる。 -各モジュールの実装は、この仕様に従う。 - # std.crypto — implementation design (M11) **実装済みの追加機能:** diff --git a/docs/impl/std-design/ja/fs.md b/docs/impl/std-design/ja/fs.md index 2accaa2b..c49d2857 100644 --- a/docs/impl/std-design/ja/fs.md +++ b/docs/impl/std-design/ja/fs.md @@ -175,8 +175,14 @@ NUL は Invalid、空パスは通常の OS エラー変換となる。不在は 含め、パスは呼び出し中だけ借用する。ネイティブパス変換と既存ハンドル本体は割り当てを 伴うが、読み取りバッファや mmap は割り当てない。結果はパスの寿命を保持しない。 -結果は同じ Move の file で、所有する descriptor を Drop が閉じる。束縛済み receiver、 -集約への格納、単一スレッドの制限も同じ。pread は呼び出し側の mutable buffer の窓を +結果は同じ Move の file で、所有するディスクリプタを Drop が閉じる。束縛済みレシーバーと +集約への格納には既存の規則を使う。リテラルの `spawn(fn { ... })` は file を消費でき、 +タスクはそれを直接、または対応する所有レコードに入れて返せる。同じディスクリプタを +呼び出し側とワーカーの間で移動し、再オープン・複製・ファイル移転のための新たな確保は +行わない。移転時に呼び出し側の別名は無効になり、借用した file はタスクへ移動できない。 +通常の保存するクロージャ、`par_map` のキャプチャ、その他のネイティブ資源には既存の +制限が残る。[Plan259](../../259-task-owned-captures.md) が移転と解放の全規則を定める。 +pread は呼び出し側の mutable buffer の窓を 使い、実際の読み取り数、短い読み取り、EOF の 0 を返す。len は descriptor の最新の メタデータを取得する。pread/pwrite の負のオフセットは権限検査より先に abort する。pwrite は入力 バイトを読む前にカーネルが保持する descriptor のモードを調べ、読み取り専用の場合は diff --git a/docs/impl/std-design/ja/http-client-composition.md b/docs/impl/std-design/ja/http-client-composition.md index 564780f1..eae906bb 100644 --- a/docs/impl/std-design/ja/http-client-composition.md +++ b/docs/impl/std-design/ja/http-client-composition.md @@ -144,7 +144,7 @@ HIR・runtime ABI台帳は規範的契約が変わる場合のみ更新します キャリア監査では「既存規則」の範囲も確定しました。タプルには3種類のHTTP葉だけを追加し、 入れ子タプルや集約要素の規則は変えません。固定長の所有レコード配列からの添字経由Move葉の -取り出しも追加しません。所有フィールドの直接置換は[メモリのガイド](../../../guide/05-memory.md#replacing-owned-fields-and-shortening-arrays)にある +取り出しも追加しません。所有フィールドの直接置換は[メモリのガイド](../../../guide/ja/05-memory.md#所有フィールドの置換と配列の短縮)にある 可変placeと割り当てモードの一般規則に従い、O1は所有値全体の置換・再初期化を検証します。`array` は既存バッチ所有値をヘルパー型として 記述するもので、配列リテラルの構築は追加しません。H1は各境界の成功・拒否を固定します。 diff --git a/docs/impl/std-design/ja/http.md b/docs/impl/std-design/ja/http.md index 6dd73f2d..2e8cec5e 100644 --- a/docs/impl/std-design/ja/http.md +++ b/docs/impl/std-design/ja/http.md @@ -1,6 +1,3 @@ -このディレクトリは、ロードマップを補う `std` モジュールの詳細な設計仕様をまとめる。 -各モジュールの実装は、この仕様に従う。 - # std.http — implementation design (M11) > 🌐 [English](../http.md) · **日本語** diff --git a/docs/impl/std-design/ja/net.md b/docs/impl/std-design/ja/net.md index be56b2dd..d7ffc71e 100644 --- a/docs/impl/std-design/ja/net.md +++ b/docs/impl/std-design/ja/net.md @@ -1,6 +1,3 @@ -このディレクトリは、ロードマップを補う `std` モジュールの詳細な設計仕様をまとめる。 -各モジュールの実装は、この仕様に従う。 - # std.net — implementation design (M11) > 🌐 [English](../net.md) · **日本語** diff --git a/docs/impl/std-design/ja/process.md b/docs/impl/std-design/ja/process.md index 6f18923f..58d24644 100644 --- a/docs/impl/std-design/ja/process.md +++ b/docs/impl/std-design/ja/process.md @@ -1,15 +1,10 @@ -このディレクトリは、ロードマップを補う `std` モジュールの詳細な設計仕様をまとめる。 -各モジュールの実装は、この仕様に従う。 - -# std.process — implementation design (M11) +# std.process — プロセスの実行と観測 > 🌐 [English](../process.md) · **日本語** -> **ステータス:** M11 のコアは完了済みです(exit/abort、spawn/wait/kill、exec は実装済み)。**拡張は -> 2026-07-24 に設計済み**(align-llm Request 1)です。`process.command` ビルダ + `run_output` ハンドルに -> よる、出力キャプチャ + cwd / env / timeout — 本ファイル末尾の「Extension」を参照してください。**スライス -> 4〜6 は実装済み**(`process.command`/`c.cwd`/`c.run` のキャプチャ、`c.timeout_ns` とコアの `Error.Timeout` -> バリアント、`c.env`/`c.env_clear`)。align-llm Request 11 の有界 text/bytes 拡張も**実装済み**です。 +> **実装済み。** 現在の API は型付きの終了状態、実行中の出力読み取り、明示的な +> シグナル購読を提供し、プラットフォームの対応範囲は plan 49/50 に従う。 +> 以下の日付付き M11 スライスは実装の履歴であり、置き換え済みの数値ステータス API は使えない。 ## R65 の契約 @@ -24,12 +19,41 @@ 取得契約を明記します。監視方針はアプリケーションが持ち、追加サービス、専用ユーザー、 VM、維持用プロセスは要求しません。 -## Overview +## 現在の終了状態・出力 API + +すべての操作には `import std.process` が必要である。所有する値を束縛してから +メソッドを呼ぶ。子プロセスの状態を変える操作には排他的アクセスが必要となる。 + +```text +ch.status() -> Result, Error> // 回収せずに観測する +ch.try_wait() -> Result, Error> // ノンブロッキングで回収する +ch.wait() -> Result // 回収、または保存済みの結果を返す +out.status() -> process.wait_result // run_output と run_bytes +``` + +`process.termination` は `Exited(i64)` と `Signaled(i64)` を区別する。 +`process.wait_result` は `termination` と `max_rss_bytes: Option` を持つ。 +RSS は直接の子プロセスの観測値であり、メモリ上限でもプロセスツリーの合計でもない。 +削除済みの `out.code()` と数値を返す `wait()` に互換別名はない。 + +`command.start()` は実行中の子プロセスを返す。キャプチャした stdout/stderr は +`read_stdout`/`read_stderr` で読み、`poll` で必要なイベントを待つ。子が実行中で、 +まだ EOF に達していないキャプチャがある場合、ブロッキングの `wait()` は +`Error.Invalid` を返す。出力を暗黙に読み捨てることはない。回収に成功すると +結果全体を保存し、以後の待機は同じ値を返す。準備や実行ファイルの起動失敗は `Err`、 +実際に起動したプログラムの終了コード 127 は `Exited(127)` となる。 + +完全なシグネチャ、検証順序、ネイティブ資源の寿命は plan 49/50 に従う。 +現在の使用例は[プロセスのガイド](../../../guide/ja/18-std-services.md)にある。 +以下の M11 セクションは当初の実装記録であり、変更されたシグネチャと起動規則は +plan 49/50 の契約が優先する。 + +## 当初の M11 の概要(履歴) spawn / exec / exit(draft §18.2)。fork/exec/waitpid と、子プロセスを表す child Move ハンドルで構成する。 **`process.exit` の Drop セマンティクスをめぐる Open question(open-questions)は、このモジュールで確定させる。** -## Signatures +## 当初の M11 シグネチャ(履歴) ```text ch := process.spawn(cmd: str, args: array) -> Result // fork+exec, child owns pid @@ -213,7 +237,11 @@ exit は単なるランタイム呼び出しではなく、先に関数(理想 - CLOEXEC が子プロセスへの fd リークを防ぐ(P3) - import が必須であること -# Extension — captured output + cwd / env / timeout (align-llm Request 1) +# 過去の拡張 — 出力キャプチャと cwd / env / timeout(align-llm Request 1) + +この節は、当時の使用例とネイティブ起動処理を含む最初のキャプチャ設計の記録である。 +数値ステータス、起動エラー、ディスクリプタ継承の規則は plan 49/50 に置き換えられた。 +現在のコードには冒頭の API を使う。 > **ステータス: 出荷済み(Slice 4〜6、#630/#631/#632、2026-07-24)。** `process.command` + `cwd` + > `timeout_ns` + `env`/`env_clear` + `run` キャプチャがエンドツーエンドで構築済み。bytes 層と明示 cap は @@ -221,14 +249,12 @@ exit は単なるランタイム呼び出しではなく、先に関数(理想 > コマンドを走らせ、その**出力をパースする**)が動機である。出典: > `../align-llm/docs/align-requests.md` の Request 1(優先度: critical — このループを塞いでいた)。 -## Why this is genuinely new +## 当初の動機(2026年7月) -上記のスライス 1–3 は `spawn`/`wait`/`kill`/`exec` を出荷しているが、`spawn` は素の `fork` + `execvp` を -**パイプも `dup2` も無し**で行う。子は親の fd を継承し、その出力はそのまま端末へ流れる。`child` ハンドルは -`{ pid, reaped }` だけである。したがって `stdout`/`stderr` を文字列としてキャプチャすること、指定した作業 -ディレクトリで実行すること、子ごとの環境を渡すこと、タイムアウトで実行を打ち切ることは、いずれも**今日は -不可能**であり、そのどれも本モジュールの記録済み設計空間には無い(過去の先送りは `detach()` と `Never` 型 -だけだった)。これは実ワークロードの要件であって、計画済みのギャップではない。 +この拡張より前の `spawn` は親のディスクリプタを継承して端末へ直接出力し、 +`child` は `{ pid, reaped }` だけを持っていた。align-llm のビルド・テスト・lint の +実行には、stdout/stderr の取得、作業ディレクトリの指定、子ごとの環境設定、 +タイムアウトが必要だった。Request 1 がこの用途を示し、スライス 4〜6 で実装した。 ## Shared prerequisite — the `Error.Timeout` variant (canonical definition; `std.http`/`std.net` reuse it) @@ -491,14 +517,20 @@ owner は不変。 `EINTR` remainder recomputation、時間が残る early zero poll result、次の poll 前の exhausted remainder、 no early expiry。 -# 拡張 — 有界 text/byte キャプチャ(align-llm Request 11) +# 過去の拡張 — 有界 text/byte キャプチャ(align-llm Request 11) + +以下の台帳・ABI・レビュー記録は、最初の有界キャプチャの設計を示す。 +バイト数上限と出力の所有権の規則は維持するが、`code()`、数値の終了状態、起動エラーと +ネイティブ起動処理は plan 49/50 に置き換えられた。旧使用例とステータスの行は履歴として +残しており、別の対応 API を示すものではない。 > **ステータス: 実装済み(2026-08-14)。** この拡張は上限を選択した呼び出しについて P12 を > 閉じ、先送りしていたバイナリ出力層を出荷する。出典: `../align-llm/docs/align-requests.md` Request 11。 ## 公開契約 ledger -次の ledger が本拡張の正本である。本文と実装は独立に範囲を広げてはならない。 +以下は当初の拡張の正本である。変更されていないキャプチャの規則にも、冒頭の現在の +プロセス契約を適用する。 | Surface | 厳密な契約 | |---|---| diff --git a/docs/impl/std-design/net.md b/docs/impl/std-design/net.md index e6992e73..316441ca 100644 --- a/docs/impl/std-design/net.md +++ b/docs/impl/std-design/net.md @@ -1,6 +1,3 @@ -This directory records the detailed `std` module designs that supplement the roadmap. -These specifications define the contract for each module's implementation. - # std.net — implementation design (M11) > 🌐 **English** · [Japanese](./ja/net.md) diff --git a/docs/impl/std-design/process.md b/docs/impl/std-design/process.md index 5fd422f7..4a7b0ec7 100644 --- a/docs/impl/std-design/process.md +++ b/docs/impl/std-design/process.md @@ -1,15 +1,11 @@ -This directory records the detailed `std` module designs that supplement the roadmap. -These specifications define the contract for each module's implementation. - -# std.process — implementation design (M11) +# std.process — process execution and observation > 🌐 **English** · [Japanese](./ja/process.md) -> **Status:** M11 core complete (exit/abort, spawn/wait/kill, exec shipped). **Extension DESIGNED -> 2026-07-24** (align-llm Request 1): captured output + cwd / env / timeout via a `process.command` -> builder + `run_output` handle — see "Extension" at the end of this file. **Slices 4–6 SHIPPED** -> (`process.command`/`c.cwd`/`c.run` capture; `c.timeout_ns` + the core `Error.Timeout` variant; -> `c.env`/`c.env_clear`). The bounded text/bytes extension is **SHIPPED** for align-llm Request 11. +> **Status:** implemented. The current API uses typed process status, live capture, +> explicit signal subscriptions and the platform boundaries in plans 49/50. +> The dated M11 slices below preserve implementation history; their replaced +> numeric status APIs are not available. ## R65 contract @@ -26,12 +22,41 @@ Linux/macOS. Verified sealed-image selection, namespace inheritance and exclusiv child_scope retain explicit Linux-only acquisition contracts. Application code owns supervision; no additional service, dedicated user, VM or keeper is required. -## Overview +## Current status and output API + +All operations require `import std.process`. Bind owners before calling methods; +stateful child operations require exclusive access. + +```text +ch.status() -> Result, Error> // observe without reaping +ch.try_wait() -> Result, Error> // nonblocking reap +ch.wait() -> Result // reap or return cached result +out.status() -> process.wait_result // run_output and run_bytes +``` + +`process.termination` distinguishes `Exited(i64)` from `Signaled(i64)`. +`process.wait_result` contains `termination` and `max_rss_bytes: Option`; +RSS is an observation for the direct child, not a memory limit or process-tree total. +The removed `out.code()` accessor and numeric `wait()` result have no aliases. + +`command.start()` returns a live child. Drain captured stdout/stderr with +`read_stdout`/`read_stderr` and use `poll` to wait for selected readiness. A pending +child with a capture that has not reached EOF makes blocking `wait()` return +`Error.Invalid`; it does not silently drain or discard output. Successful waits +cache the complete result. Setup and executable-launch failures return `Err`; +a program that starts and exits with code 127 returns `Exited(127)`. + +Plans 49/50 define the complete signatures, validation order and native lifecycle. +The [process guide](../../guide/18-std-services.md) shows current usage. The following +M11 sections record the original implementation and are superseded where those +plans replace a signature or launch rule. + +## Original M11 overview (historical) spawn, exec, exit (draft §18.2). Fork/exec/waitpid + a child Move handle. **This module settles the `process.exit` Drop-semantics Open question** (open-questions). -## Signatures +## Original M11 signatures (historical) ```text ch := process.spawn(cmd: str, args: array) -> Result // fork+exec, child owns pid @@ -217,7 +242,11 @@ current-frame + global flush. (Record the gap honestly.) --- -# Extension — captured output + cwd / env / timeout (align-llm Request 1) +# Historical extension — captured output + cwd / env / timeout (align-llm Request 1) + +This section records the original capture design, including its then-current +examples and native launch mechanism. Plans 49/50 supersede numeric status, +launch-error and inherited-descriptor rules; use the current API above. > **Status: SHIPPED (Slices 4–6, #630/#631/#632, 2026-07-24).** `process.command` + `cwd` + > `timeout_ns` + `env`/`env_clear` + `run` capture are built end-to-end. The bytes tier and explicit @@ -225,15 +254,13 @@ current-frame + global flush. (Record the gap honestly.) > whose core loop runs build/test/lint commands and **parses their output**. Source: > `../align-llm/docs/align-requests.md` Request 1 (priority: critical — it blocked that loop). -## Why this is genuinely new +## Original motivation (July 2026) -Slices 1–3 above ship `spawn`/`wait`/`kill`/`exec`, but `spawn` does a bare `fork` + `execvp` with -**no pipes and no `dup2`**: the child inherits the parent's fds and its output goes straight to the -terminal. The `child` handle is `{ pid, reaped }` only. So capturing `stdout`/`stderr` as strings, -running in a chosen working directory, passing a per-child environment, and bounding the run with a -timeout are all **impossible today** — and none of them are in this module's recorded design space -(the only prior deferrals were `detach()` and a `Never` type). This is a real-workload requirement, -not a planned gap. +Before this extension, `spawn` inherited the parent's descriptors and wrote directly +to the terminal; `child` held only `{ pid, reaped }`. The align-llm build/test/lint +loop needed captured stdout/stderr, an explicit working directory, per-child +environment settings and a timeout. Request 1 supplied that use case; these +operations shipped in slices 4–6. ## Shared prerequisite — the `Error.Timeout` variant (canonical definition; `std.http`/`std.net` reuse it) @@ -510,7 +537,13 @@ cleanup, and UTF-8 owners remain unchanged. budgets; `EINTR` remainder recomputation; an early zero poll result with time remaining; exhausted remainder before another poll; and no early expiry. -# Extension — bounded text and byte capture (align-llm Request 11) +# Historical extension — bounded text and byte capture (align-llm Request 11) + +The following ledger, ABI and review records describe the original bounded-capture +capability. Its byte-limit and output-ownership rules remain; plans 49/50 replace +`code()`, decoded numeric termination, launch-error handling and native launch +mechanisms. The old examples and status rows are retained as history, not as a +second supported API. > **Status: SHIPPED (2026-08-14).** This extension closes P12 for > callers that select a bound and ships the previously deferred binary-output tier. Source: @@ -518,8 +551,8 @@ cleanup, and UTF-8 owners remain unchanged. ## Public-contract ledger -The ledger below is authoritative for the extension. The prose and implementation must not broaden -it independently. +This was the authoritative ledger for the original extension. Its unchanged +capture rules remain subject to the current process contract above. | Surface | Exact contract | |---|---| diff --git a/docs/language-spec.md b/docs/language-spec.md index 9cd6487c..4ac3abe7 100644 --- a/docs/language-spec.md +++ b/docs/language-spec.md @@ -808,9 +808,12 @@ NaN and carries no minimum/maximum semantics — so it is not the vectorizable s (`draft.md` §8.) Stages and reducers take a named function or an inline lambda `fn x { ... }` (parameter types -inferred). A lambda may capture enclosing variables by value — with no hidden closure -environment (it compiles like a named function, captures passed as arguments). `where(.active)` -is shorthand for a one-field lambda. +inferred). An inline pipeline lambda passes captured Copy values as extra arguments, without +an allocated closure environment. `where(.active)` is shorthand for a one-field lambda. +Stored or passed capturing function values instead use a frame-local environment; they cannot +outlive it or their borrowed captures. Returning function values remains unsupported, and an +enclosing arena does not extend a closure environment's lifetime. A literal consumed by `spawn` +uses fresh task-group storage and admits the owned inputs described under Concurrency. `source.scan(init, f) -> array` materializes the running state after each surviving element, in source order; `init: A` is evaluated once and never emitted, diff --git a/docs/little-aligner/02-do-it-again.md b/docs/little-aligner/02-do-it-again.md index a7740c1a..bb69b883 100644 --- a/docs/little-aligner/02-do-it-again.md +++ b/docs/little-aligner/02-do-it-again.md @@ -139,7 +139,7 @@ factor := 3 scores.map(fn s { s + 10 }).to_array() ``` -`.to_array()`. We need to keep the transformed scores as an array. +We need to keep the transformed scores as an array. --- @@ -151,7 +151,7 @@ scores.map(fn s { s + 10 }).to_array() scores.map(fn s { s + 10 }).sum() ``` -`.sum()`. We need only the total, so there is no need to store the transformed scores in an array. +We need only the total, so there is no need to store the transformed scores in an array. --- diff --git a/docs/little-aligner/08-whose-is-it.md b/docs/little-aligner/08-whose-is-it.md index 9c62e81b..a4c8feac 100644 --- a/docs/little-aligner/08-whose-is-it.md +++ b/docs/little-aligner/08-whose-is-it.md @@ -170,7 +170,7 @@ Which names may still be used? fn count_bytes(s: str) -> i64 = s.len() ``` -Pass a view while retaining the owner. Passing a `string` by value transfers ownership; passing a `str` lets the function borrow the text. +Passing a `string` by value transfers ownership; passing a `str` lets the function borrow the text. --- @@ -249,10 +249,6 @@ The elements live inside the record. `Table` is Copy because its elements are Co --- -To see the ownership changes as diagrams, read the guide's [memory chapter](../guide/05-memory.md). It follows Move, borrowing, and a string copied out of an arena. - ---- - **Q26.** May this function change the first byte? ```align @@ -261,6 +257,8 @@ fn first(bytes: slice) -> u8 = bytes[0] **A26.** It may read it. A value slice parameter is read-only. +--- + **Q27.** Then how do we ask a function to change it? **A27.** Say so in the parameter: @@ -274,17 +272,25 @@ fn mark(borrow mut bytes: slice) { Give `mark` a mutable view of owned storage. It writes a byte and leaves the view in place. You may use the same view for the next call. +--- + **Q28.** What if a function returns that view? May we write through the result? **A28.** No. Returned views are read-only. The caller's original writable view still works. A returned owner, such as an `array`, owns its storage and may be changed. +--- + **Q29.** Could we hide the view in a record to make it writable? **A29.** No. A view inside a parameter or result follows the same rule. Pass the selected view itself to `mark`. We need a mutable parameter. +--- + +To see the ownership changes as diagrams, read the guide's [memory chapter](../guide/05-memory.md). It follows Move, borrowing, and a string copied out of an arena. + > **The Eighth Commandment** > > *One owner at a time. Group values with the same lifetime in an arena. When you need another copy of a string, write `.clone()`.* diff --git a/docs/little-aligner/11-do-it-until.md b/docs/little-aligner/11-do-it-until.md index c7ce281f..32b9a50d 100644 --- a/docs/little-aligner/11-do-it-until.md +++ b/docs/little-aligner/11-do-it-until.md @@ -20,16 +20,19 @@ ```align fn pump(r: reader, w: writer) -> Result<(), Error> { - mut buf := buffer(4096) + mut buf := buffer.try_new(4096)? loop { n := r.read(buf)? - if n == 0 { break Ok(()) } + if n == 0 { + w.flush()? + break Ok(()) + } w.write(buf.bytes())? } } ``` -`loop` repeats its block until a `break` runs. Until empty — then out. +`loop` repeats its block until a `break` runs. `try_new` obtains the read window first; `?` returns an allocation error if it cannot. A zero-byte read then means EOF. Before reporting success, `flush()` delivers any buffered output and lets us handle a final write error. Automatic cleanup alone cannot report that error. --- @@ -44,7 +47,7 @@ fn pump(r: reader, w: writer) -> Result<(), Error> { **A5.** ```align -mut buf := buffer(4096) +mut buf := buffer.try_new(4096)? mut total := 0 sum := loop { n := r.read(buf)? diff --git a/docs/little-aligner/12-do-it-apart.md b/docs/little-aligner/12-do-it-apart.md index 77215f9d..1ba5877f 100755 --- a/docs/little-aligner/12-do-it-apart.md +++ b/docs/little-aligner/12-do-it-apart.md @@ -152,7 +152,7 @@ task_group { - the same expensive Pure function over many independent items - three different independent operations -**A18.** Sequential pipeline; `par_map`; `task_group`. SIMD lanes, data-parallel workers, task-parallel workers — three scales, three visible choices. +**A18.** Sequential pipeline; `par_map`; `task_group`. --- @@ -191,6 +191,42 @@ It prints `ready`. The task creates an owned `string`; after the join, `get()` t --- +**Q23.** The string already exists. Must the task make another copy? + +**A23.** No. A lambda written directly inside `spawn` can take ownership: + +```align +text := "ready".clone() +message := task_group { + task := spawn(fn { text }) + wait() + task.get() +} +print(message) +``` + +Again, `ready`. This time the string moves into the task at `spawn`, then back to the caller through `get()`. The bytes are not copied at either boundary. The task may work on storage it owns; a shared view still permits only reading. + +--- + +**Q24.** After `wait()`, may we use the old name `text` again? + +**A24.** No. Waiting completes the task; it does not undo the move. Use `message`, the new owner. A view borrowed from `text` before the spawn cannot be used afterward either. Moving the owner to a worker must not leave another task reading the same storage while the worker changes or frees it. + +--- + +**Q25.** Can we first store that lambda in `work`, then give `work` to `spawn`? + +**A25.** No. An ordinary stored lambda still captures only Copy values. The owning form is the literal directly inside `spawn(fn { ... })`: it runs once. The same distinction prevents a repeatedly called pipeline lambda from consuming one captured owner many times. + +--- + +**Q26.** Could we move an already-open file and a read buffer to a task, then get them back for another read? + +**A26.** Yes, using the `file` owner and an independent `buffer`, for example as fields of one owned record. The task receives the same open file; it does not reopen its pathname. After a successful wait, `get()` can return the record for reuse. A `reader`, an arena allocation, or a record containing a borrowed view cannot be transferred this way. The [parallelism guide](../guide/10-closures-and-parallelism.md) shows a task that reads from an owned file. + +--- + > **The Twelfth Commandment** > > *Give expensive Pure work to `par_map`, and different independent work to a `task_group`. Give every task a scope, and every failure a join.* diff --git a/docs/little-aligner/README.md b/docs/little-aligner/README.md index 5ce68b41..2a8e2e1d 100644 --- a/docs/little-aligner/README.md +++ b/docs/little-aligner/README.md @@ -51,7 +51,7 @@ For examples that show a complete program with `fn main`, save the code to a fil 9. [Turn It Sideways](09-turn-it-sideways.md) — `soa`: data as columns 10. [Count Me by Name](10-count-me-by-name.md) — `group_by`, `agg`, `dict_encode` 11. [Do It Until](11-do-it-until.md) — the `loop` expression, when a pipeline can't say it -12. [Do It Apart](12-do-it-apart.md) — Pure work, `par_map`, and structured tasks +12. [Do It Apart](12-do-it-apart.md) — Pure work, `par_map`, and ownership across structured tasks 13. [Four at a Time](13-four-at-a-time.md) — explicit SIMD, vectors, and masks 14. [The Big Crunch](14-the-big-crunch.md) — mmap, zero-copy pipelines, and putting it all together 15. [Read It Four Ways](15-read-it-four-ways.md) — answer, flow, lifetime, and work diff --git a/docs/little-aligner/ja/08-whose-is-it.md b/docs/little-aligner/ja/08-whose-is-it.md index 048430c4..78047ee5 100644 --- a/docs/little-aligner/ja/08-whose-is-it.md +++ b/docs/little-aligner/ja/08-whose-is-it.md @@ -249,10 +249,6 @@ fn main() -> i32 { --- -図で確かめたいときは、ガイドの[メモリの章](../../guide/ja/05-memory.md)を参照してください。Move、借用、arena の文字列をコピーして外へ返す例を図で追えます。 - ---- - **Q26.** この関数は先頭のバイトを書き換えてもよいですか? ```align @@ -261,6 +257,8 @@ fn first(bytes: slice) -> u8 = bytes[0] **A26.** 読むことはできます。値として受け取ったスライスは読み取り専用です。 +--- + **Q27.** では、関数に書き換えてもらうには? **A27.** パラメータにそう書きます。 @@ -273,14 +271,22 @@ fn mark(borrow mut bytes: slice) { 所有する領域の可変ビューを `mark` に渡します。バイトを書き換えても、ビューの参照先はそのままです。次の呼び出しにも同じビューを使えます。 +--- + **Q28.** 関数がそのビューを返したら、戻り値を通して書き込めますか? **A28.** いいえ。返されたビューは読み取り専用です。呼び出し元の元の可変ビューは引き続き使えます。返されたものが `array` のような所有者なら、自分の領域を書き換えられます。 +--- + **Q29.** ビューをレコードに隠せば、書き込めるようになりますか? **A29.** いいえ。パラメータや戻り値の中のビューにも同じ規則が適用されます。選んだビュー自体を `mark` に渡しましょう。必要なのは可変パラメータです。 +--- + +図で確かめたいときは、ガイドの[メモリの章](../../guide/ja/05-memory.md)を参照してください。Move、借用、arena の文字列をコピーして外へ返す例を図で追えます。 + > **第八の戒律** > > *所有者は一度に一つ。同時に使い終わる値は `arena` にまとめよ。文字列の複製が必要なら、`.clone()` と書け。* diff --git a/docs/little-aligner/ja/11-do-it-until.md b/docs/little-aligner/ja/11-do-it-until.md index e96f9413..9dc9b4aa 100644 --- a/docs/little-aligner/ja/11-do-it-until.md +++ b/docs/little-aligner/ja/11-do-it-until.md @@ -20,16 +20,19 @@ ```align fn pump(r: reader, w: writer) -> Result<(), Error> { - mut buf := buffer(4096) + mut buf := buffer.try_new(4096)? loop { n := r.read(buf)? - if n == 0 { break Ok(()) } + if n == 0 { + w.flush()? + break Ok(()) + } w.write(buf.bytes())? } } ``` -`loop` は `break` が実行されるまでブロックを繰り返します。ここでは読み込みが0バイトになったら終了します。 +`loop` は `break` が実行されるまでブロックを繰り返します。`try_new` で読み込み用の領域を確保してから始め、確保に失敗したら `?` でエラーを返します。0バイト読んだら EOF です。最後に `flush()` で書き込みの完了を確認してから成功を返します。終了時の自動処理だけでは、書き込みエラーを返せません。 --- @@ -44,7 +47,7 @@ fn pump(r: reader, w: writer) -> Result<(), Error> { **A5.** ```align -mut buf := buffer(4096) +mut buf := buffer.try_new(4096)? mut total := 0 sum := loop { n := r.read(buf)? diff --git a/docs/little-aligner/ja/12-do-it-apart.md b/docs/little-aligner/ja/12-do-it-apart.md index cf62a407..6ec4ce1b 100755 --- a/docs/little-aligner/ja/12-do-it-apart.md +++ b/docs/little-aligner/ja/12-do-it-apart.md @@ -152,7 +152,7 @@ task_group { - 多数の独立した要素に、同じ計算量の大きい Pure な関数を適用する - 互いに独立した3つの異なる処理を行う -**A18.** 逐次のパイプライン、`par_map`、`task_group`。SIMD のレーンを使う計算、同じ処理の分担、異なるタスクの並行実行という、3つの使い分けです。 +**A18.** 逐次のパイプライン、`par_map`、`task_group`。 --- @@ -191,6 +191,42 @@ print(message) --- +**Q23.** 文字列がすでにあるなら、タスク用にもう一つコピーが必要ですか? + +**A23.** いいえ。`spawn` に直接書くラムダには、所有権を移せます。 + +```align +text := "ready".clone() +message := task_group { + task := spawn(fn { text }) + wait() + task.get() +} +print(message) +``` + +これも `ready` と表示します。今度は `spawn` で文字列がタスクに移り、`get()` で呼び出し元へ戻ります。どちらでも文字列のバイト列はコピーしません。タスクが所有する領域は変更できますが、共有したビューは引き続き読み取り専用です。 + +--- + +**Q24.** `wait()` の後なら、元の名前 `text` をまた使えますか? + +**A24.** いいえ。待つことでタスクは完了しますが、ムーブが取り消されるわけではありません。新しい所有者の `message` を使います。`spawn` の前に `text` から借りたビューも、その後には使えません。ワーカーが領域を変更・解放できる間に、別のタスクが同じ領域を読み続けられては困ります。 + +--- + +**Q25.** 先にラムダを `work` に保存して、それを `spawn` に渡してもよいですか? + +**A25.** いいえ。通常の変数に保存するラムダが取り込めるのは、引き続き Copy の値だけです。所有権を受け取れるのは、`spawn(fn { ... })` に直接書く、一度だけ実行されるラムダです。繰り返し呼ばれるパイプラインのラムダも、一つの所有者を何度も消費することはできません。 + +--- + +**Q26.** 開いてあるファイルと読み込み用バッファをタスクに渡し、次の読み込みのために受け取れますか? + +**A26.** はい。所有する `file` と独立した `buffer` を、たとえば一つの所有するレコードのフィールドにまとめて渡せます。タスクが受け取るのは同じ開かれたファイルです。パスから開き直しません。正常に完了を待った後、`get()` でレコードを取り出し、再利用できます。`reader`、arena に確保した値、借用ビューを含むレコードは、この方法では移せません。[並列処理のガイド](../../guide/ja/10-closures-and-parallelism.md)に、所有するファイルをタスクで読む例があります。 + +--- + > **第十二の戒律** > > *計算量の大きい Pure な処理は `par_map`、異なる独立した処理は `task_group`。各タスクの寿命をスコープで区切り、失敗してもすべての完了を待て。* diff --git a/docs/little-aligner/ja/README.md b/docs/little-aligner/ja/README.md index 06fcfae7..356301cf 100644 --- a/docs/little-aligner/ja/README.md +++ b/docs/little-aligner/ja/README.md @@ -51,7 +51,7 @@ align> 1 + 2 9. [Turn It Sideways](09-turn-it-sideways.md) — `soa`: 列としてのデータ 10. [Count Me by Name](10-count-me-by-name.md) — `group_by`、`agg`、`dict_encode` 11. [Do It Until](11-do-it-until.md) — `loop` 式、パイプラインで言い表せないとき -12. [Do It Apart](12-do-it-apart.md) — Pure な仕事、`par_map`、スコープに属するタスク +12. [Do It Apart](12-do-it-apart.md) — Pure な仕事、`par_map`、スコープに属するタスクと所有権 13. [Four at a Time](13-four-at-a-time.md) — 明示的 SIMD、ベクトル、マスク 14. [The Big Crunch](14-the-big-crunch.md) — mmap、ゼロコピーのパイプライン、そしてすべてを統合する 15. [Read It Four Ways](15-read-it-four-ways.md) — 答え、流れ、寿命、仕事量 diff --git a/docs/open-questions.md b/docs/open-questions.md index a1c62bfc..a6be6d7c 100644 --- a/docs/open-questions.md +++ b/docs/open-questions.md @@ -1444,14 +1444,28 @@ supplies owned input transfer and file results. Exclusive-borrow captures remain > **Current status:** first-class closures ①–③ and `task_group` ④a–④c are shipped, including real > threads, scoped capture environments, `spawn`/`wait()`/`get()`, and fallible `wait()?`. The plan -> below is retained as the implementation record. The exact stored-Result/control-flow -> successful-Wait proof described below is pending the am-w correction after its 2026-07-31 audit. +> below is retained as the implementation record. The stored-Result/control-flow +> successful-Wait proof described below is implemented. > Returning function values, and environment-bearing or capturing function values carried out of > every enclosing region through a struct field or array element, remain deferred pending a > heap-owned environment and Drop model with a real consumer. A `Static`/noncapturing named function > value may already occupy a locally used struct field or array element; aggregate placement alone > is not a full escape. -**Decision (2026-06-23): escape decides a lambda's representation; `spawn` takes a lambda; `task_group` is a structured scope.** The ideal form, chosen on merit (not legacy): a lambda that **escapes** (stored in a variable, returned, or handed to `spawn`) gets a **closure environment** holding its captured values; a non-escaping lambda (every pipeline stage/reducer) stays inlined with captures-as-parameters (zero allocation, SIMD/GPU-friendly). The compiler's **escape analysis** picks the representation — the same syntax, two representations — so first-class function values and `task_group` exist without eroding the offload-ready pipeline path. The environment is **owned by the enclosing region** (the `task_group {}` / `arena {}` scope) and freed with it — a region allocation, not a hidden `malloc`, so the visible scope is the boundary (consistent with *Nothing hidden*). (The model for a closure that escapes *every* region is part of this deferred design; the `task_group` consumer is scope-bounded.) `task_group` (`draft.md` §11) is a **structured** scope like `arena {}`: `spawn(fn { … })` takes a lambda (the deferral is then visible — *Nothing hidden* — and it is the one lambda mechanism, not a bare-call special form), returns a `Task` handle; `wait()?` is the single error boundary (joins all, propagates the lowest-index `Err`); `a.get()` reads a result after the join. A spawned task **may be impure** (it does I/O — unlike a Pure `par_map`); safety combines by-value snapshots with shared-backing protection until join. Rejected alternative: a bare-call special form `spawn(fs.read_file(p))` — it hides the deferral (against *Nothing hidden*) and is a second deferral mechanism (against *One way*); it was only attractive as a way to dodge the closure-environment work, which escape analysis handles cleanly. **Build order:** first-class closures (escape-driven) as the foundation, then `task_group` as a consumer. Rationale: [The lambda philosophy](design-notes.md#the-lambda-philosophy). +**Decision (2026-06-23): capture storage follows use; `spawn` takes a lambda; +`task_group` is a structured scope.** Inline pipeline lambdas pass Copy captures as extra +arguments. Stored or passed capturing function values use a frame-local environment and +cannot outlive it or their borrowed captures. Returning function values remains deferred; +an enclosing arena does not extend a frame-local environment's lifetime. A spawned literal +gets fresh task-group storage for each invocation, released after all tasks join. Plan259 +also allows that single invocation to consume admitted owned inputs; reusable function +values retain Copy-only captures. + +`spawn(fn { … })` makes the separate work visible and returns a `Task` handle. +`wait()?` joins every task and propagates the lowest-spawn-index error; `get()` reads a result +after a successful join. A spawned task may perform I/O, while `par_map` callbacks must be +Pure. Shared captured backing remains read-only and protected until join. A bare-call form +such as `spawn(fs.read_file(p))` was rejected because it obscures deferred evaluation. +Rationale: [The lambda philosophy](design-notes.md#the-lambda-philosophy). By-value capture still requires worker-sendability: the same general non-Send rule recursively rejects a `region` capability in any reachable callable environment before either a spawned environment or a `par_map` range is published. diff --git a/draft.md b/draft.md index 26c66332..c61a8466 100644 --- a/draft.md +++ b/draft.md @@ -1572,11 +1572,9 @@ doubled := xs.map(fn x { x * 2 }) big := xs.where(fn x { x > limit }) // captures `limit` ``` -A lambda may **capture** enclosing variables (`limit` above). Capture is by value, and there is -**no hidden closure environment**: a lambda compiles exactly like a named function — captured -values are passed as ordinary arguments — so it fuses into the same loop and adds no allocation. -(Consistent with *Nothing hidden*: a lambda that escaped and outlived its captures would need a -visible heap environment; the in-loop case never does.) +An inline pipeline lambda may **capture** enclosing Copy variables (`limit` above). +Captured values become extra function arguments, so capture adds no separate environment +allocation. Stored function values and spawned tasks use the representations described below. `source.scan(init, f) -> array` materializes the running state after each surviving element, in source order; `init: A` is evaluated once and never emitted, @@ -1629,11 +1627,13 @@ selected worker-transfer parameter roots, and imported declarations carry the sa set, so checking resumes against completed actual arguments at the caller. The inferred effect and provenance summaries remain unwritten. -A passed closure's captured environment lives in the caller's frame for the duration of the call, so -no heap allocation is needed. A function value that *escapes* — returned from a function, stored -beyond the frame, or handed to `spawn` (§11) — needs a region-owned environment, owned by the -enclosing `arena {}` / `task_group {}` scope (never a hidden `malloc`); escape analysis chooses the -representation. +A stored or passed capturing function value uses a frame-local environment and may be +called only while that environment and its borrowed captures remain live. It needs no heap +allocation. Returning function values or carrying a capturing environment beyond its frame +remains unsupported; an enclosing `arena` does not make that escape legal. A literal consumed +by `spawn` (§11) instead receives a fresh environment in the task-group region, which remains +live until all tasks join. Ordinary function values retain Copy-only captures; the task's +single invocation also admits the owned inputs specified in §11. A trusted compile-time library producer may instead require one exact named or non-capturing lifted function and compile it into a nominal static descriptor plus a producer-owned C-ABI trampoline.