Skip to content

synth-wcet-v1 names functions from the export section only, so the --wcet-hints seam has no key for internal functions — 7 of our 13 loop declines are unaddressable #1063

Description

@avrabe

[fathom (gale) — found while building the scry oracle-seam fixture]

--wcet-hints is a better-designed seam than the one I was about to ask scry for: hints untrusted, re-derived over the final instruction stream, rejected with hint-below-derived-trip / hint-unverifiable-induction rather than silently trusted. The gate tests in crates/synth-cli/tests/wcet_bound_gate.rs are the reason I believe it works.

But on a real dissolved object, the seam has no key for the functions that need hints most.

Measured

The E2 dissolved gust:os composite — meld 0.48.0 -> loom 1.2.0 -> synth 0.57.0, --target cortex-m3 --all-exports --relocatable --native-pointer-abi --emit-wcet. 31 functions compiled, 3 bounded / 28 declined:

wcet fn names: 31   named = 23   anonymous (func_<N>) = 8
module exports: 26
named wcet fns that ARE exports: 23 / 23        <- all of them
anonymous wcet fns that are exports:  0 / 8     <- none of them

So synth-wcet-v1 takes its name from the export section, and falls back to func_<index> for every internal function. Exactly.

That matters because of where the declines are:

loop declines (13):
  gust:os/log@0.1.0#line            cabi_realloc_wit_bindgen_0_52_0
  exec_admit                        gust:sched/tasks@0.1.0#state
  exec_poll_round                   exec_state
  func_22  func_24  func_25  func_26  func_27  func_28  func_32

7 of 13 are index-named. A --wcet-hints file keys on the function name, so those seven cannot be hinted at all — and any hint written against func_22 silently retargets the moment an edit adds or removes a function earlier in the index space. That is worse than not being hintable: it is a wrong hint that still parses.

Since loop is 13 of 28 declines with 11 callee-unbounded cascades behind it, this is most of the leverage the hints seam exists to unlock.

The names already exist and are not consulted

I assumed this was self-inflicted — our MC/DC harness passes meld fuse --preserve-names and documents that trap; our dissolve script did not. That was not the cause. Rebuilding the whole chain with --preserve-names produces a byte-identical .wcet.json, while populating the name section from 0 to 59 of 68 functions.

So the module carries names for the internal functions, unused by the sidecar:

_RNvCs942N1ctoMYm_18gust_exec_provider..exports..gust..os..exec..__export_admit_cabi..
_RNvCs4dFUS4ljfv3_18gust_time_provider..___link_custom_section_describing_imports

Request

When the name section is present, use it for functions with no export name in synth-wcet-v1 (and, symmetrically, accept it as a --wcet-hints key). Keep func_<index> only as the last resort when there is no name at all.

A caveat I would rather state than have you discover: those names are v2 Rust mangling carrying a crate disambiguator (Cs942N1ctoMYm_), which is not content-derived — scry measured 43-45% of their own function identities churning per build for exactly this reason (scry#123, two-tier fix in scry#137: strip the disambiguator where the result is unique in the module, and flag it id_build_local where it is not).

So the name section is better than an index but still not stable, and I do not think gale should be the one to pick the canonical key. What would help most:

  1. Use the name section for internal functions, so a key exists at all.
  2. Consider emitting, per function, the key --wcet-hints will actually match on — so scry and gale join against what synth accepts rather than reconstructing it. A build_local: true/false flag alongside it, in scry's sense, would let a consumer tell a durable key from a per-build one without parsing mangled symbols.

Point 2 is a suggestion, not a request; point 1 is the blocker.

Kill-criterion

A hints file keyed on the name-section name of one of those seven functions converts its loop decline into hint-verified (or is rejected with a named reason, which is equally fine) — rather than being ignored because the key never matches.

What I am not claiming

  • I have not shown any of the seven is a counted loop that a correct hint would bound. That is the measurement scry#144 is set up to do, and it needs a key first.
  • The unmodeled-op (2) and call (2) declines are unrelated to this. call on gust:os/time@0.1.0#now and poll_task is correct — both reach the native seam and an intra-procedural bound should stop at an import.
  • Fixture (loom.wasm, .o, .wcet.json) is being committed to gale; happy to attach it here if that is easier than a link.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions