Repository navigation
Fix #167 (68060 half): decline cleanly when the 68060 has no FPU - #169
Merged
Merged
Conversation
…an FPU
--cpu 68060 --no-fpu crashed partway through a run with "continuation
stub trapped ... with no pending continuation". Root cause, confirmed
with a repro test against the real Runtime/dispatch path (not just the
bare M68kCpu unit-test harness): the m68k crate's internal 68060
FPU-disabled exception delivery (take_fp_disabled_060, called directly
from decode.rs/fpu/operations.rs) bypasses Cpu::take_hardware_exception
entirely, unlike every other modeled FPU-absence path (020/030/040,
which surface through the host-interceptable FlineTrap escape hatch).
It jumps straight through vector 11's slot (guest address 0x2C), which
this runtime deliberately leaves genuinely zero so a guest that installs
its own real FPU-probe handler there gets real semantics. With no
handler installed (the ordinary case for a -m68020-60 -m68881 build that
never probes), PC is set to 0 and falls through the deliberately-zeroed
exception-vector-table span until it happens to reach
CONTINUATION_STUB_ADDR (0xC4) and executes it directly -- exactly what
the confusing error reports.
Fix: pre-seed vector 11's slot with the address of a new, dedicated
ILLEGAL-opcode stub (FPU_DISABLED_060_DECLINE_STUB_ADDR) whenever the
resolved CPU is M68060 with fpu_present == false. This surfaces through
the existing IllegalInstruction trap path with no new plumbing, turning
the crash into a clean, attributable "unexpected CPU stop ... Illegal"
report. If the guest later installs its own real handler at 0x2C (the
FPU-probe idiom), that ordinary write simply overwrites the pre-seed --
no special-casing needed.
The seeding has to run *after* Runtime::new (its own sentinel prefill
sweep covers this address and would clobber an earlier write), via a new
install_fpu_disabled_060_decline_stub(&mut Runtime, cpu_type, fpu)
shared helper called from every real Runtime construction site in
main.rs: both of run()'s loading strategies (through
build_runtime_with_vfs) and the nested System()/Execute() path (through
a newly-extracted build_nested_runtime, factored out of
run_nested_program so its construction can be tested directly -- the
old i32-collapsing return made this fix unobservable at that boundary).
This mirrors attn_flags_for's own precedent for why the CLI -- not
Runtime/StartConfig -- computes CPU-model-dependent guest state.
TRAP_TABLE_SIZE grows by 4 bytes (0x2C00 -> 0x2C04) to fit the new stub
while keeping TRAP_TABLE_END 4-byte aligned.
Tests added (volamos-core: 1109 -> 1112; volamos: 159 -> 160):
- empirical repro against the real Runtime/dispatch path, asserting the
fixed clean-trap behavior (first run, pre-fix, confirmed the
hand-derived EmptyContinuationStack{pc:0xC4} mechanism exactly)
- every other cpu_type/fpu combination leaves vector 11 genuinely zero
- a guest-installed real handler at 0x2C runs instead of the stub
- the nested System()/Execute() construction path gets the same pre-seed
Out of scope: the underlying m68k crate design gap (68060 FPU-disabled
exception delivery bypassing the host interception hook) is not fixed
here -- a volamos-side mitigation only, same as the already-merged
68040 half of this issue. Worth reporting upstream separately.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Godbk9UDW5obqYsdkXkyRR
This was referenced Oct 19, 2026
sidick
added a commit
that referenced
this pull request
Oct 19, 2026
PR #169 fixed this but didn't touch userdocs; both the Changelog and CLI-Reference still described it as a separate, still-open crash. Claude-Session: https://claude.ai/code/session_01Godbk9UDW5obqYsdkXkyRR Co-authored-by: Claude Sonnet 5 <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Scope
This closes the second half of #167 -- the
--cpu 68060 --no-fpucrash. The first half (--cpu 68040 --no-fpu) was already fixed and merged separately. With this PR, issue #167 as filed is fully addressed.The bug
--cpu 68060 --no-fpudied partway through a run with:Empirical root cause (verified, not just hand-derived)
I built a repro test driving the real
Runtime/dispatchpath (not the bareM68kCpuunit-test harness, which never installs the trap table and can't reproduce this): aCpuType::M68060,fpu_present = falseruntime whose only instruction is a real F-line FPU opcode (0xF200, 0x0000).Before the fix, this produced exactly:
This confirms the hand-derived mechanism exactly: for 68020/68030/68040, an FPU-absence trap surfaces through the
m68kcrate's host-interceptableStepResult::FlineTrap/CycleBatchExit::FlineTrapescape hatch, whichCpu::take_hardware_exception(crates/volamos-core/src/backend.rs) catches and declines cleanly if the guest never installed a handler. The 68060's on-die-FPU-disabled exception doesn't go through that hatch at all: them68kcrate's owndecode.rs/fpu/operations.rscallcpu.take_fp_disabled_060(bus)directly, bypassingtake_hardware_exceptionentirely, and jump straight through vector 11's slot (guest address0x2C). That slot is deliberately left genuinely zero (so a guest installing its own real FPU-probe handler there gets real semantics) -- so with no handler installed (the ordinary case for a-m68020-60 -m68881build that never probes),PCis set to0and falls through the deliberately-zeroed exception-vector-table span until it happens to reachCONTINUATION_STUB_ADDR(0xC4) and executes it directly.The fix
Pre-seed vector 11's slot with the address of a new, dedicated stub (
FPU_DISABLED_060_DECLINE_STUB_ADDR, holding a singleILLEGALopcode) whenever the resolved CPU isCpuType::M68060withfpu_present == false. This surfaces through the existingStepResult::IllegalInstruction/CycleBatchExit::IllegalInstructionpath with zero new plumbing -- turning the crash into a clean, attributableunexpected CPU stop ... Illegalreport.If the guest later installs its own real handler at
0x2C(the FPU-probe idiom), that ordinary guest write simply overwrites the pre-seed like any other guest memory write -- no special-casing needed, verified with a dedicated test.Where the seeding logic lives, and why
New shared helper:
install_fpu_disabled_060_decline_stub(&mut Runtime<C>, cpu_type, fpu_present)incrates/volamos-core/src/dispatch.rs. It has to run afterRuntime::new, not before:Runtime::new's own "unknown call" sentinel prefill sweep covers this address range and would clobber an earlier write (confirmed by first writing it before construction and watching the test fail with the sentinel opcode instead).Called from
crates/volamos/src/main.rsat every realRuntimeconstruction site:run()'s loading strategies, viabuild_runtime_with_vfs(one call site covers both).System()/Execute()path. I extracted the construction-only half ofrun_nested_programinto a newbuild_nested_runtimereturningOption<Runtime<M68kCpu>>, specifically so this could be tested directly --run_nested_program's owni32return collapses every construction failure and every runtime error to-1, which made this fix unobservable at that boundary.This matches
attn_flags_for's own established precedent (see its doc comment) for why the CLI, notRuntime/StartConfig, computes CPU-model-dependent guest state:Runtime/StartConfigdon't otherwise know or care which concreteCpuType/FPU configuration is in use, and this keeps it that way.Address bookkeeping
TRAP_TABLE_SIZEgrows by 4 bytes (0x2C00->0x2C04): oneILLEGALopcode word plus one padding word, keepingTRAP_TABLE_END4-byte aligned (an invariantRuntime::new's heap-start rounding relies on -- growing by just 2 bytes brokeheap_starts_at_load_end, caught it before pushing).FPU_DISABLED_060_DECLINE_STUB_ADDR = TRAP_TABLE_END - 2, right afterEXIT_STUB_ADDR's own 4-byte slot.I did not add this to
EXEC_LIBRARY_BASE's "Reserved-region memory map" ASCII diagram -- that diagram only ever documented the original0x1200-end region; every laterTRAP_TABLE_SIZEgrowth (the math libraries,timer.device,locale.library,intuition.library,bsdsocket.library,graphics.library, theSetFunctionstub pool) got its own doc comment instead, following the same convention this PR follows for the new constant.Tests (volamos-core: 1109 -> 1112; volamos: 159 -> 160)
dispatch::tests::m68060_no_fpu_fpu_instruction_repro_167-- the empirical repro above, now asserting the fixed clean-trap behavior. Doc comment records the exact pre-fix failure for posterity.dispatch::tests::install_fpu_disabled_060_decline_stub_is_a_no_op_for_every_other_combination--M68020/M68030/M68040withfpu=falseandM68060withfpu=trueall leave vector 11 genuinely zero.dispatch::tests::guest_installed_fpu_handler_runs_instead_of_the_decline_stub-- a guest-installed real handler at0x2C, written after the pre-seed, runs instead of the stub (both by trappcand by an observableD0side effect).tests::nested_runtime_68060_no_fpu_gets_a_clean_trap_not_the_continuation_stub(inmain.rs, via the newbuild_nested_runtime) -- the nestedSystem()/Execute()construction path gets the same pre-seed, exercised with a minimal hand-built hunk executable.All green:
cargo fmt --all --checkcargo clippy --workspace --all-targets -- -D warningscargo test --workspaceExplicitly out of scope
This is a volamos-side mitigation that turns a confusing crash into a clean, attributable trap -- it does not fix the underlying
m68kcrate design gap (68060 FPU-disabled exception delivery bypassing the host's interception hook entirely, unlike every other modeled FPU-absence path). Worth reporting upstream againstm68k-rsseparately, same as the already-merged 68040 half of this issue noted the 68060 crash as a separate, deeper issue.Not merging -- leaving that for review.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Godbk9UDW5obqYsdkXkyRR