Skip to content

Softfloat on riscv64 - #38

Closed
maximmenshikov wants to merge 13 commits into
masterfrom
feature/softfloat-riscv64
Closed

maximmenshikov wants to merge 13 commits into
masterfrom
feature/softfloat-riscv64

Conversation

@maximmenshikov

Copy link
Copy Markdown
Collaborator

No description provided.

Seed the rv64gc baseline (base, d, c, a) for riscv64 in
ConfigureInstructionSetSupport - previously no baseline was declared, so
with a runtime that models F/D/C/A as instruction sets every riscv64
build would look like a no-F target and silently flip to the soft-float
ABI.

For the zisk/zisk_sim flavors append -c,-a,-f,-d to --targetisa: dropping
C/A stops compressed/atomic emission through the same instruction-set
mechanism as the EnableRiscV64* knobs, and dropping F/D switches ilc and
the JIT into the lp64 soft-float ABI and soft-float lowering (dotnet-riscv
fixup patches 26-28). Explicit --targetisa values keep working - the
reduction is appended on top.

Signed-off-by: Maxim Menshikov <maksim.menshikov@nethermind.io>
The nofp module models the FP-free contract: every soft-float builtin
traps and the whole libm surface is wrapped to traps. With the JIT now
lowering FP into soft-float calls (dotnet-riscv fixups 26-28) a no-F
build needs those symbols to work instead:

- modules/softfloat: vendored compiler-rt builtins (llvmorg-18.1.8,
  Apache-2.0 WITH LLVM-exception) compiled strictly for rv64im via the
  new multi-file module mechanism (module_srcs.lst + module_cflags in
  build.sh: plain ELF objects merged with ld -r, no LTO so the builtins
  are never re-codegenned with the common rv64imad flags), plus soft
  replacements for the hard-float runtime conversion helpers
  (RhpDbl2Lng family, .NET saturating semantics) and vendored musl
  fmod/fmodf (MIT, pure bit manipulation), all diverted with --wrap.

- BuildCommand: a riscv64 target without the F instruction set links
  softfloat.o instead of nofp.o and drops the nofp libm wraps. The libm
  surface itself still resolves to hard-float musl members and remains
  guarded by --error-on-float-binary until a soft-float libm ships.

- bflat.variant.props: pin the .NET 11 min variant to the v11.0.0.x11-sf
  runtime release, which carries the soft-float runtime patches and the
  RiscV64_F instruction set in the compiler package.

Signed-off-by: Maxim Menshikov <maksim.menshikov@nethermind.io>
…shutdown

Two fixes found while bringing up soft-float execution on ziskemu:

1. patch_elf writes its output to Path.ChangeExtension(output, .patched)
   and bflat never moved it back, nor checked the exit code. Guest
   recipes historically passed -o <name>.patched, which made
   ChangeExtension a no-op and the script overwrite its input in place -
   with any other output name the user received the RAW linker output,
   which does not boot on the zkVM (unfixed .tdata flags/init_array
   type/bss layout make the loader jump through a zero slot at
   __libc_start_init). Move the result over the requested output and
   fail the build on postprocess errors.

2. The zkVM decodes only base rv64im: extend the zisk instruction-set
   reduction with -zba,-zbb,-zbs,-zicond and force the corresponding
   EnableRiscV64* JIT knobs (plus F/D for hygiene) off. The cross-JIT
   resolves ISAs through the altjit path where every knob defaults to
   on, independent of ilc instruction-set support - Zicond notably
   turned GT_SELECT into czero.* encodings.

Also: BFLAT_NO_ZK_ISA_REDUCTION / BFLAT_NO_RISCV_BASELINE env escape
hatches used for bisection, kept for diagnostics.

Signed-off-by: Maxim Menshikov <maksim.menshikov@nethermind.io>
… pin x13-sf

The libziskos nupkg places its manifest under contentFiles/any/any/ while
runtimes/<rid>/native/ sits at the package root, so the manifest-relative
static-library path missed it. Walk up towards the extraction root and
take the first match.

Pin the .NET 11 min variant to v11.0.0.x13-sf (soft-float runtime with
the ReturnTypeDesc FP-struct-return gate). Keep the bisection env
hatches (BFLAT_NO_ZK_ISA_REDUCTION / BFLAT_NO_RISCV_BASELINE) documented
in-line.

Signed-off-by: Maxim Menshikov <maksim.menshikov@nethermind.io>
The compiler package for the .NET 10 variants has no RiscV64_F enum
member and its instruction-set tables know nothing of f/d/c/a, so the
zisk ISA reduction and the soft-float switch must not assume them.
Probe a throwaway InstructionSetSupportBuilder for "f" support and
resolve the enum member reflectively; on older packages the reduction
is skipped and the classic nofp path is linked, exactly as before the
soft-float work.

Signed-off-by: Maxim Menshikov <maksim.menshikov@nethermind.io>
Review feedback: module_cflags was a second ad-hoc side file next to
module_params.yml. Fold it into the existing declarative params as an
options.cc section (same '- value:' entry style as options.ld) and read
it in build.sh with yq like the other module options. The softfloat
module is the only consumer; behavior is unchanged.

Signed-off-by: Maxim Menshikov <maksim.menshikov@nethermind.io>
…ms.yml

Review feedback: module_cflags and module_srcs.lst were ad-hoc side
files next to the module's declarative params. Fold both into
module_params.yml as options.sources / options.cc sections (same
'- value:' entry style as options.ld) and read them in build.sh with yq
like the other module options. The softfloat module is the only
consumer; behavior is unchanged.

Signed-off-by: Maxim Menshikov <maksim.menshikov@nethermind.io>
The ACSL gate requires every defined function in a C module to carry a
contract. Annotate the .NET conversion semantics (NaN -> 0, saturation
bounds) on the Rhp wrappers and put declaration specs on the renamed
musl fmod/fmodf definitions; Frama-C attaches a declaration contract to
the definition the include pulls in.

Locally: check_acsl.py reports softfloat 8/8 ok.

Signed-off-by: Maxim Menshikov <maksim.menshikov@nethermind.io>
The compiler package now models the lp64 (soft-float) calling convention
as TargetAbi.NativeAotRiscV64SoftFloat (dotnet-riscv fixup 27): the JIT
flag and the ELF float-ABI marker follow the ABI rather than the F
instruction set. Select it whenever the target lacks F or D, resolving
the member reflectively so that older packages keep building.

Signed-off-by: Maxim Menshikov <maksim.menshikov@nethermind.io>
…at link

The zisk substitution surface removes or rewrites every CoreLib body that
carries floating point (Math.Truncate, Number.FormatFloat, Double/Single
ToString, the Equals/CompareTo bit-pattern snippets, the Number..cctor
sqrt-table deletion, ...) because the classic rv64ima link has no floating
point at all and the nofp module traps libm. A soft-float link
(riscv64-lp64, softfloat module) has none of that problem: the JIT lowers
FP to soft-float helper calls and the toolchain's libc/libm are built
-mabi=lp64, so the original bodies are correct there - and removing them
turns e.g. Math.Truncate into ThrowFeatureBodyRemoved (the SoftFloat
regression test fail-fasts at its first Truncate call).

Split the FP-elimination entries out of zisk.substitutions.xml into
zisk.nofp.substitutions.xml, add an "fpu: none|soft|hard" condition to the
module params.yml syntax, and select the nofp XML and the C# snippets with
"fpu: none" only. The targeted IL rewrites of CustomILProvider (Number..cctor,
FormatBigInteger) are likewise skipped on a soft-float link. The single-hart
zkVM tuning entries (processor count, hill climbing, memory pressure, ...)
stay in zisk.substitutions.xml and apply to both links.

Signed-off-by: Maxim Menshikov <maksim.menshikov@nethermind.io>
The verification probe (bflat.csproj, --libc zisk/zisk_sim) must see the
snippets and the nofp substitutions even when the package's default
instruction set makes the link soft-float; the drift check is about the
snippet set matching the CoreLib, not about the FP model of one link.

Signed-off-by: Maxim Menshikov <maksim.menshikov@nethermind.io>
Signed-off-by: Maxim Menshikov <maksim.menshikov@nethermind.io>
…Threaded

A zkVM guest can now choose its GC: 'ugc' (default, unchanged) or 'clr',
the upstream workstation GC inside the single-threaded runtime flavor that
dotnet-riscv fixup 30 adds to the runtime pack (no background GC, no
finalizer thread; finalizers run from GC.WaitForPendingFinalizers).

Under 'clr' the uGC module is not linked, the reference-assignment and bulk
copy wraps that reduce the write barriers to a bare store stay out (the
params.yml entries carry a new "gc: ugc" condition), and embedded runtime
knobs size the GC for a guest without virtual memory: concurrent GC off,
heap hard limit 96 MB, region range 128 MB (the reservation is a bump
allocation from the script.ld RAM window, so it must be explicit rather
than the 256 GB default). pal gains mprotect/madvise/getrlimit wraps: the
GC's commit/advise are no-ops on a flat heap and musl's versions would
issue ecalls the emulator does not serve.

Signed-off-by: Maxim Menshikov <maksim.menshikov@nethermind.io>

@LukaszRozmej LukaszRozmej left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed c29a3fe against merge base 9b73858.

Scope: all 69 changed files: compiler/target and external-library resolution (BuildCommand.cs, ExtLibResolver.cs, InstructionSetHelpers.cs, ZkvmIL.cs, ZkvmSubstitutions.cs), build/packaging (build.sh, bflat.csproj, bflat.variant.props), PAL and runtime-helper module changes, the softfloat module and its 50 vendored compiler-rt files, and the zisk substitution split. No files excluded; vendored compiler-rt files were verified byte-for-byte after newline normalization against LLVM llvmorg-18.1.8 rather than independently re-auditing upstream arithmetic.

Three correctness/integration findings are attached inline, all concerning the new collecting-GC path. No additional project-rule findings: the Nethermind execution-client directory rules are not conventions of this separate repository.

Validation: downloaded and inspected the pinned runtime archive; traced the runtime's GC transition and virtual-memory contracts; all 50 compiler-rt files match upstream; 10,018 host-side saturation-boundary and randomized double-remainder checks passed. These host checks do not validate RISC-V lowering or ABI. No end-to-end guest build/test was run because the local cross sysroot is unavailable.

Total: 3 findings. Please include regressions for explicit collection, managed callbacks/finalizers, and decommit/recommit reuse when addressing these.

Remarks: 🤖 AI agent (Codex / GPT-6) on behalf of @LukaszRozmej.

<RuntimeVersionPostfix Condition="'$(DotnetVersion)' == '10' and '$(Variant)' == 'perf'">.p3</RuntimeVersionPostfix>
<RuntimeVersionPostfix Condition="'$(DotnetVersion)' == '10' and '$(Variant)' == 'min'">.x10</RuntimeVersionPostfix>
<RuntimeVersionPostfix Condition="'$(DotnetVersion)' == '11' and '$(Variant)' == 'min'">.x9</RuntimeVersionPostfix>
<RuntimeVersionPostfix Condition="'$(DotnetVersion)' == '11' and '$(Variant)' == 'min'">.x13-sf</RuntimeVersionPostfix>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Pin a runtime release that actually contains the new GC flavor

The bflat-libs-linux-musl-riscv64.zip asset in v11.0.0.x13-sf contains libRuntime.WorkstationGC.a and libRuntime.ServerGC.a, but no libRuntime.SingleThreaded.a (verified by downloading and listing the archive). Consequently the new --zk-gc clr branch passes -lRuntime.SingleThreaded for a library that the default .NET 11 layout cannot supply. Pin a release containing fixup 30 and its packaged library, and reject this option clearly for older runtime packs.

- value: --wrap=RhpReversePInvokeReturn
# No write barrier under uGC: bulk reference copy is a plain memmove.
- value: --wrap=RhBulkMoveWithWriteBarrier
gc: ugc

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Keep the P/Invoke transitions when using the collecting GC

Conditioning only the bulk-copy wrapper leaves the four RhpPInvoke/RhpReversePInvoke wrappers above active under --zk-gc clr; their implementations are no-ops. Even an explicit GC.Collect() requires the transition: RhCollect defers the P/Invoke frame before collecting so the managed stack is crawlable. Removing that frame can crash collection or lose live roots, and reverse transitions are also needed across native callbacks/finalizer entry. Gate these four no-op wrappers on gc: ugc as well, preserving the real transitions for CLR mode.

Comment on lines +419 to +425
/* The upstream GC (--zk-gc clr) commits its regions with mprotect(RW),
* advises with madvise(MADV_DONTDUMP/DONTNEED) and sizes the reservation by
* getrlimit(RLIMIT_AS). The guest has no MMU: every mmap is a bump
* allocation that is readable and writable from the start, so commit and
* advise are no-ops, and the address-space limit query fails (the GC then
* treats it as unlimited and falls back to the configured hard limit). musl's
* own implementations would issue raw ecalls the emulator does not serve. */

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Implement in-place decommit before enabling the upstream GC

A bump-only mmap is insufficient for the collecting GC introduced here: VirtualDecommit calls mmap(address, size, PROT_NONE, MAP_FIXED | MAP_ANON | MAP_PRIVATE, ...) and relies on the original range being zeroed when recommitted. The wrapper immediately above ignores address/flags and allocates a different block; the new no-op mprotect then reports a successful recommit of the unchanged old range. Once regions are decommitted/reused, this both consumes extra bump-heap space and leaves stale contents where the GC expects zeroed pages. Handle fixed anonymous remaps in place (including zeroing), with appropriate failure semantics, and add a decommit/recommit reuse regression.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants