Skip to content

riscv64: duplicate __cxa* symbols linking wasm-opt (vendored libcxxabi vs toolchain libc++.a) #24

Closed

Description

@jwinarske

riscv64 fails in Build Debug linking wasm-opt. Nine __cxa* symbols are
defined twice, once by our vendored libcxxabi and once by the libc++ that ships
with the pinned clang toolchain:

FAILED: exe.unstripped/wasm-opt wasm-opt
clang++ --target=riscv64-unknown-linux-gnu ...
ld.lld: error: duplicate symbol: __cxxabiv1::__setExceptionClass(_Unwind_Exception*, unsigned long)
>>> defined in obj/flutter/third_party/libcxxabi/src/libcxxabi.cxa_noexception.o
>>> defined in buildtools/linux-x64/clang/lib/riscv64-unknown-linux-gnu/libc++.a(cxa_exception.cpp.o)

The other eight are __getExceptionClass, __isOurExceptionClass,
__cxa_decrement_exception_refcount, __cxa_increment_exception_refcount,
__cxa_current_primary_exception, __cxa_rethrow_primary_exception,
__cxa_uncaught_exception, __cxa_uncaught_exceptions.

Both objects end up on the link line: ours built with exceptions off
(cxa_noexception.o), theirs the normal cxa_exception.cpp.o out of the
prebuilt libc++.a. Only riscv64 ships that per-target lib directory in
buildtools, which is why x86_64, arm64 and armv7hf link clean from the same
dispatch.

Not new and not tied to a version bump. Same failure at both revisions I have
runs for:

srcrev version run
5d531788 3.47.1 33021152268
a804b261 3.47.2 33679768178

Two directions, no strong opinion on which:

  • link with -nostdlib++ for this target so the toolchain's libc++.a is not
    pulled in, or
  • skip the vendored libcxxabi on riscv64 and use the toolchain's

Worth checking whether patches/0001-clang-toolchain.patch is what puts that lib
directory on the search path in the first place.

Consequence today: there are no published riscv64 engine artifacts for any
recent release. meta-flutter builds the engine from source so its CI is
unaffected, but anyone consuming the prebuilt tarballs has nothing for riscv64.

Activity

  1. jwinarske commented on Sep 2, 2026

    @jwinarske
    ContributorAuthor

    Correction to the above: the per-target lib directory is not the difference. All
    four targets ship one and all four contain libc++.a:

    x86_64-unknown-linux-gnu          libc++.a
    aarch64-unknown-linux-gnu         libc++.a
    armv7-unknown-linux-gnueabihf     libc++.a
    riscv64-unknown-linux-gnu         libc++.a
    

    The actual difference is that riscv64 is the only build that compiles binaryen at
    all:

    arch binaryen objects ninja targets
    x86_64 6 9255
    armv7hf 6 7585
    riscv64 282 14380

    x86_64 and armv7hf never link wasm-opt; there is no mention of it in either
    log. So this is not "riscv64 links libc++ differently", it is "riscv64 builds a
    tool the other targets do not build", and that tool is what hits the collision.

    wasm-opt is only reachable from dart2wasm_benchmark in
    third_party/dart/BUILD.gn, which nothing in an embedder build should want. The
    gn invocations in the three workflows are identical apart from the arch
    variables, so whatever pulls it in is inside the engine's own gn logic keyed on
    target_cpu.

    Open question, and the command that answers it:

    gn refs out/linux_debug_riscv64 //flutter/third_party/dart/third_party/binaryen:wasm-opt --all
    

    I could not run it: the riscv64 out directory is gone and the workspace is busy
    with another build.

    That reframes the fix. -nostdlib++ would get the link to pass, but if wasm-opt
    has no business being built for the target then not building it is the better
    answer and drops ~7000 ninja targets with it.

  2. jwinarske commented on Sep 2, 2026

    @jwinarske
    ContributorAuthor

    Traced it to upstream flutter/tools/gn, which already knows this target does
    not build:

      if args.prebuilt_dart_sdk:
        if can_use_prebuilt_dart(args):
          gn_args['flutter_prebuilt_dart_sdk'] = True
          ...
        elif is_host_build(args):
          print('Tried to download prebuilt Dart SDK but ...')
      elif is_host_build(args):
        # If we are building the dart sdk in-tree, exclude the wasm-opt target, as
        # it doesn't build properly with our gn configuration.
        gn_args['dart_include_wasm_opt'] = False

    "It doesn't build properly with our gn configuration" is their comment, not
    mine. The exclusion is only ever applied when is_host_build() is true, and
    that is:

      if args.target_os is None:                                   return True
      if args.target_os == 'linux' and args.linux_cpu == 'arm64':  return True
      if args.target_os == 'mac':                                  return True
      return False

    linux/riscv64 is none of those, so the exclusion never fires for us and
    dart_include_wasm_opt keeps its default of is_clang || !is_win, i.e. true.
    sdk/BUILD.gn then pulls :copy_wasm_opt into create_most_sdk and wasm-opt
    gets built with the custom toolchain, where it hits the libc++ collision.

    Why x86_64 and arm64 never see it: flutter/prebuilts/ ships linux-x64 and
    linux-arm64 only, so can_use_prebuilt_dart() is true for both and no Dart SDK
    is built in-tree at all.

    One thing I cannot account for yet. armv7hf has no prebuilt either, so by the
    same reading it should build the SDK and hit this too, and it does not: 282
    binaryen objects on riscv64 against 6 on armv7hf, and those 6 are the gclient
    checkout, not compilation. So the model above is incomplete somewhere. Comparing
    out/linux_debug_arm/args.gn with out/linux_debug_riscv64/args.gn would settle
    it and is cheaper than my guessing further.

    Fix direction: tools/gn has no --gn-args passthrough, so this wants a patch
    alongside 0003-gn-riscv32-and-riscv64.patch, setting
    dart_include_wasm_opt = False when the SDK is built in-tree rather than only
    for host builds. That follows the upstream comment's intent and drops roughly
    7000 ninja targets from the riscv64 build as a side effect.

  3. jwinarske commented on Sep 2, 2026

    @jwinarske
    ContributorAuthor

    Following up on the gap I flagged: it is real, and it breaks the model above.

    I had only looked at the 2026-08-26 armv7hf log, which died at the first release
    step and so only ever ran Build Debug. The complete armv7hf run from today
    (all three ninja invocations, 34777 log lines) still shows zero wasm-opt:

    binaryen lines: 6      (gclient checkout only, no compilation)
    wasm-opt lines: 0
    ninja targets:  7412 / 7498 / 7585      vs riscv64's 14380
    

    So armv7hf genuinely never builds it, and that contradicts the reading above.
    flutter_prebuilt_dart_sdk defaults to false (flutter/common/config.gni:18)
    and can_use_prebuilt_dart() is false for linux_cpu=arm just as it is for
    riscv64, so by that path armv7hf should build the SDK in-tree and hit this too.
    It does not.

    Confirmed separately from out/linux_debug_arm64/args.gn on the runner:

    flutter_prebuilt_dart_sdk = true
    dart_sdk_output = "built-dart-sdk"
    

    and no wasm-opt binary in that out directory, so the prebuilt half of the
    explanation holds for arm64 and x86_64. It is the armv7hf-versus-riscv64 half
    that is unexplained.

    The gn flags in the two workflows are identical apart from linux_cpu,
    target_triple and target_sysroot, so whatever diverges is keyed on one of
    those inside gn. Diffing out/linux_debug_arm/args.gn against
    out/linux_debug_riscv64/args.gn is still the thing that answers it. I have not
    been able to: the two builds land on different runners and neither out directory
    survives.

    Leaving the dart_include_wasm_opt finding above as accurate about why the
    link fails
    -- it is what puts wasm-opt in the riscv64 graph -- but the reason
    armv7hf escapes is not yet established, so anyone patching should get that diff
    first rather than trusting my account of the gating.

  4. jwinarske commented on Sep 3, 2026

    @jwinarske
    ContributorAuthor

    Fixed by #26: the riscv64 workflow passes --gn-args=dart_include_wasm_opt=false,
    the same thing meta-flutter's flutter-engine recipe has done since it hit this.

    riscv64 builds and publishes again on both current revisions, for the first time
    since 3.44.2 in June:

    version srcrev artifacts
    3.47.1 5d531788 15 -- arm64 4, armv7hf 4, x86_64 4, riscv64 3
    3.47.2 a804b261 15 -- arm64 4, armv7hf 4, x86_64 4, riscv64 3

    riscv64 publishes three modes rather than four; its workflow has no
    Build Debug Unoptimized step.

    What is left unexplained

    armv7hf has no prebuilt Dart SDK either, so it should build the SDK in-tree and
    hit this the same way. It does not, and I could not work out why.

    Ruled out along the way:

    • the gn args are identical for the two, bar target_cpu, dart_target_arch,
      arm_float_abi, the sysroot and the triple. I ran the real to_gn_args() from
      tools/gn against both argument sets rather than eyeballing it
    • can_use_prebuilt_dart() and is_host_build() are both false for both
    • dart_include_wasm_opt has exactly three references in the tree
      (sdk/BUILD.gn:38, sdk/BUILD.gn:816, tools/gn:714) and none of them keys
      off the architecture
    • create_most_sdk gates copy_wasm_opt only on ia32/x86, which arm passes

    The one arch-keyed difference I did find is sdk/BUILD.gn:832, which excludes
    arm from the cross gen_snapshot deps. That accounts for the build-size gap
    (444 gen_snapshot lines on riscv64 against 304 on armv7hf) but not for wasm-opt.

    Answering it properly needs gn desc or gn path on a real generated build
    directory for each arch, which I could not produce: the two builds land on
    different runners and neither out directory survives. Closing anyway, since the
    defect is fixed and shipped -- worth reopening only if armv7hf ever starts
    building wasm-opt too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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