Skip to content

Direct backend rejects record locals/fields from a call result (BLD004) #317

Description

@PeterXMR

Reproducer

type Point
  x i32

fn make_point Point
  let p Point . x 1
  ret p

pub fn main Void world World !
  let p make_point()
  if == p.x 1
    check world.out.write "ok\n"

bin/zero check passes silently. bin/zero build --target darwin-arm64 --out /tmp/p:

BLD004: direct backend call return type is unsupported
  expected: direct AArch64 Mach-O object subset
  actual: Point
  help: choose a supported direct target or reduce the program to supported AArch64 direct-backend constructs
  explain: zero explain BLD004

Same failure across all 8 targets listed in bin/zero --version --json:

  • darwin-arm64, darwin-x64
  • linux-musl-x64, linux-musl-arm64, linux-x64, linux-arm64
  • win32-x64.exe, win32-arm64.exe

Why it matters

Source-backed stdlib (and user code) can't return any user-defined type from a function and have it lower to the direct backends. Built-in record types — Maybe<String>, Maybe<Span<u8>>, etc. — work fine via specialized backend lowering. User-defined records don't have an equivalent path.

This blocks:

  • Source-backed stdlib helpers that want to return typed handles (e.g., a hypothetical Iterator<T>, Decoder, or fallibility wrapper)
  • User code that wants to return small value types from functions
  • The assertDirectRuntimeOrUnsupported conformance path silently swallows this — so existing fixtures touching user records don't actually exercise runtime behavior; they only typecheck

I hit this while prototyping std.decimal (PR #308, now closed). The workaround there used a per-module DecimalResult { has Bool, value i64 } shape which type-checked cleanly but couldn't run on any backend.

Related but distinct gap

mutref<UserRecord> parameters hit a sibling restriction:

BLD004: direct backend call parameter type is unsupported
  expected: direct AArch64 Mach-O object subset
  actual: mutref<Point>

Listed here for completeness; may or may not share the fix.

Possible fix paths

  1. Generalize the existing record-lowering infrastructure used for Maybe<X> and Span<X> to handle user-defined type declarations.
  2. Restrict to plain-by-value records under a size budget (e.g., ≤ 16 bytes / 2 registers) — covers the common cases without ABI complexity.

Both are non-trivial; this is the kind of language work that's clearly maintainer-scope, not contributor-scope.

Compiler version

v0.1.4 (bin/zero --version --json).

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