Skip to content

A declined export exits 0 with the symbol simply absent — the decline is honest to stderr but invisible to the build system #952

Description

@avrabe

Raised by gale on #929 while verifying the v0.56.0 fix, and reproduced here on
v0.56.0. Filing separately as they offered, because it is a different defect from
#929 and outlives it.

Measured (synth 0.56.0)

(module
  (func $g (param i32 i64) (result i32) (local.get 0))
  (func (export "f") (result i32) (call $g (i32.const 7) (i64.const 9))))
$ synth compile ctrl.wat -b arm -t cortex-m3 --all-exports --relocatable -o ctrl.o
warning: skipping function 'f': ... #929: call arg 1 is 64-bit ... Declining rather
         than emitting a silent miscompile
warning: 1 of 2 functions were skipped (not in output): f
$ echo $?
0
$ llvm-objdump -t ctrl.o | grep 'F .text'
00000000 l     F .text 00000004 func_0        # $g only — the EXPORT 'f' is absent

Exit 0. Object written. The requested export is not in it.

Why this matters more than it used to

The decline itself is right and its wording is good — it names the mechanism, not
a generic "unsupported". The problem is where the honesty stops.

A build that gates on $? — which is every build — accepts this object and ships
it. The absence surfaces only in the symbol table, or downstream as a link
failure, or at run time as a missing entry point. So a LOUD decline at the
compiler becomes a SILENT one at the build-system boundary, which is the exact
class v0.56.0 spent its whole scope closing (#911, #924, #928, #945: gates that
report something other than what they check).

It also got sharper in v0.56.0 rather than milder. Declining is now the DESIGNED
outcome for a whole class of ordinary signatures — every call passing a 64-bit
value in a register, including fn(ptr, u64) — and #931 added another
(br whose carried-value count cannot be trusted across frames). As the decline
frontier grows deliberately, the cost of it being invisible to $? grows with it.

Suggested shape (gale's, and I agree)

A nonzero exit when a REQUESTED export is skipped, with an opt-in
--allow-skipped-exports for callers who genuinely want partial objects (the
--all-exports sweep over a corpus, where skipping is expected and counted).

Note the asymmetry worth preserving: skipping a non-exported helper is routine;
dropping a function the user explicitly asked to export is not. The exit code
should distinguish those, which the current warning already does textually
(1 of 2 functions were skipped (not in output): f) — the information exists,
it just does not reach the caller.

Related

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