Skip to content

sysl 0.0.161 — a --lib root stands in for its package, and large values stay where they are

Choose a tag to compare

@edadma edadma released this 03 Oct 20:20
· 6 commits to stable since this release

A --lib root stands in for its package, and large values stay where they are

Two packaging changes, two code-generation changes, and three fixes. A --lib root that is a dependency's package now overrides it, so a coordinate that cannot be fetched no longer stops the build. A dependency's own imports are now answered by its own manifest and never by the project's modules. Separately, a large self or by-value parameter is now read where it lies, and a large result is built in the caller's storage. A board program's deepest stack along boot → synth falls from 30,104 B to 13,440 B.

Behaviour changes

Four of the changes alter what an existing build does. Two of them can turn a build that worked into a refused or different one, so they come first.

  • A --lib source root that is a dependency's package now overrides that dependency (a76a3e9). Before, it was refused as a collision. A root stands in for a coordinate when its package.name is the coordinate's repository name: the last path segment, with any /vN suffix set aside. The coordinate is then dropped before selection wherever the graph names it, in the project's manifest or in any dependency's. Nothing is fetched for it and nothing is written to sysl.sum for it. A coordinate that cannot be fetched (unpublished, a bad tag, no network) therefore no longer stops a build that has a --lib copy of it. This works like Cargo's [patch] and Go's replace, written as one flag. A root that merely holds a module a coordinate also offers, without being that package, is still refused, and the refusal now says what would have made it an override. reference/packages.md § A source root stands in for the package it is.
    • Behaviour change: a build that used to be refused for a --lib root colliding with a coordinate now builds against the root.
  • A dependency's own import no longer reaches the project's modules (8b4818c). When a dependency wrote import geom, the import used to be answered by a --lib root's geom, or by the project's own geom/, before the geom its own manifest declared. A program could then build and run the wrong package's code with no warning: the regression case printed 42 where the right answer is 28. The dependency now gets the module its manifest bound. A dependency whose manifest reaches no geom at all is refused the name instead of borrowing the project's. The project's own imports are unchanged. reference/packages.md § Imports are transitive.
    • Behaviour change: a package that relied on reaching one of its consumer's modules now gets undefined name and has to declare what it imports.
  • A large by-value self or parameter is read in place (a0221b5, a9dee9e). It is no longer copied at entry when the function only reads it: no assignment to it or to a part of it, no & of it, no *self call on it, no tail self-call, and nothing outside the program can call the function. reference/declarations.md's "the method gets a copy" still holds. The caller hands over storage that nothing can change while the call runs: a temporary, a local nobody else can name, or a snapshot it stages. A write through an alias in the middle of the call therefore still does not show through self.
    • A caller stages no snapshot at all for a callee that can write nothing. That test (BorrowedParams) now reaches past a leaf: a body may call functions that pass it, recursion included, and may write its own vars.
    • A staged snapshot is released at its address (arc.dispose_at.T(ptr)). It used to be loaded whole and passed to arc.dispose.T by value, which put a second copy of the struct on the stack.
    • A view or a slice written through counts as a write to what it views (a9dee9e). var v = self.table[..]; v[0] = 9 keeps the parameter's own copy. A writable view of a local handed to a call counts as letting that local out, so the local is snapshotted. A write through a slice local (var o = out; o[0] = 7) is no longer counted as the function's own storage. Before this fix, each case changed the caller's value (9 9, 7 7); they now print 9 0 and 0 7.
    • On thumb-freestanding-softfp, musicbox's Synth.render frame goes from 11,128 B to 104 B. A *self method calling a self method over a 4 KB struct goes from 8,224 B to 0.
  • A large result is built where it is going (4fc425a, e52c90e). A local returned on every path is built in the caller's storage. In var s = Synth(…); s.rewind(); Ok(s), s lives in the payload of the caller's Result from its declaration, so the Ok writes the tag and nothing else. This covers s returned alone or as one argument of a variant or struct (Ok(s), Some(s), Held(s, n)), with any return before s exists returning anything.
    • A copy is kept wherever the difference could be seen: another value returned after s exists; another argument of the result mentioning s; s's address going anywhere but straight into a call; a defer; or, where its address does go into a call, a postcondition or a release that could run a destructor between the return and the end of the function.
    • A ? after s exists keeps the copy (e52c90e). Its failure leaves through the same storage, and would write over s with its counts still owed and its destructors never run.
    • A match or if producing a large value builds each branch's value in place, with no merge slot. An arm V(x) -> x over a local copies the payload straight from the matched value, so Result.unwrap and Option.unwrap no longer stage the whole value twice.
    • A large call result read through its address, as a receiver is in synth(…).unwrap(), is written into the slot the read uses and released there. It used to be loaded whole, stored again, and released by value.
    • On thumb-freestanding-softfp, musicbox's synth frame goes from 7,544 B to 1,976 B, Result[Synth, MusicError].unwrap from 5,568 B to 8 B, and a board program's boot calling them from 22,560 B to 11,464 B. The deepest stack along boot → synth goes from 30,104 B to 13,440 B.

Fixes

  • A build-c archive no longer leaves sysl_wall_us undefined (09c6124). Before, any program reaching sysl.time.now(), which includes every sysl.log call, failed at the consumer's link. Wherever the standard library is compiled from source (build-c, --no-std-lib), the library's own supplier of a seam the program calls is now analyzed and kept: sysl.posix.time's sysl_wall_us and sysl_monotonic_us. It is taken only on a target whose operating system has posix, and never where the program or a package supplies the same symbol. Tests: LibrarySupplierCliTests.
  • An #if in a dependency's file no longer drops the module for importers (d290e63, 6144e68). Which package a file belongs to was keyed by the source as collected, but #if gating and literate tangling hand the parser a new source whenever they change the text. A dependency's file with any #if in it, and any .lsysl file, was therefore filed as the program's own, and its module became a second, unprefixed copy that answered imports with none of the package's other files. A prepared source now records what it was prepared from. Tests: DependencyConditionalTests.
  • sysl.fs no longer names libc symbols that Android's Bionic lacks (4fc7352, d1b155c). An aarch64-android program reaching write_text_atomic, or any sysl.fs call that reads errno, failed at its link. On Android, errno is now read through Bionic's __errno rather than glibc's __errno_location, and the pending name's token comes from arc4random_buf rather than getentropy, which Bionic has only from API 28 on. macOS (__error) and Linux (__errno_location, getentropy) are unchanged. Tests: LibraryAndroidLibcCliTests.

Library

  • sysl.log.message_text(r, out) renders a record's message and fields alone, such as underrun frames=512. Fields are quoted exactly as text quotes them, with no time, no level and no newline. It is meant for a sink whose destination stamps its own time and level (logcat, syslog, journald). text now shares its field rendering and writes the same bytes as before (b4be242).

Tests: ReceiverInPlaceTests, ReturnSlotTests, AggregateLoweringTests, PackageBuildTests, and library/sysl/log/tests.sysl.

Verification

  • Full Native gate on 50dbec81 (./run-gate.sh): GATE: GREEN, 12461 succeeded, 0 failed, no retries, nothing timed out. The release is that sha plus two commits: a comment-only pointer fix in TypeChargingTests and the version bump.
  • Warnings census clean (JVM / JS / Native / syslDocJVM / syslDocNative): after clean, two / three / three / one / two. Every warning is named, and none is in this repository: Reader.scala:26:6 comes from scala-parser-combinators, Set.scala:62:15 from the Scala.js scaladoc, and the rest are the build-infra -Xplugin and -classpath lines.
  • check-pointers.py against the site: 2240 pointers, 0 unresolvable.
  • The darwin tarball was extracted to a scratch prefix and checked there: sysl --version reports 0.0.161, sysl run works, and sysl doc dispatches to sysl-doc.