Repository navigation
Softfloat on riscv64 - #38
maximmenshikov wants to merge 13 commits into
Conversation
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
left a comment
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
[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 |
There was a problem hiding this comment.
[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.
| /* 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. */ |
There was a problem hiding this comment.
[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.
No description provided.