Stage H: DoE rebuilt on BIRTH/SEND/KILL, defs-only workloads; work heat floor on contact (FABRIC-3.8 §3.9, §3.13.7)
Workload capsules the DoE births are definitions only; each defines WL-RUN (its old birth-time work plus its entry word twice) and WL-DONE-ARGS. The driver births N workers, SENDs each 'WL-RUN WL-DONE-ARGS SEND', waits in an ordinary loop for N MU-DONE messages, KILLs them (parked idle), and emits the marker. The serialized VM-EXEC round-robin is gone. Kernel: no switch checkpoint after a payload's last word; work heat is the assessed per-beat charge, and a VM gaining work is raised to the busy set's floor (Captain Bob's ruling) -- fixes a live stall where a busy-waiting Hera never yielded, without serializing batch starts. Evidence: cfg-4 rows spread evenly over Hera and all 8 worker slots (44k switches); full cfg-4 trials on amd64/aarch64/riscv64 end fail=0 with 8/8 KILL dead; Stage G interleave byte-identical on all three. Acceptance: dict_hash unchanged, relay 3, idle growth 0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
81def7b4a5
commit
f152a91099
@@ -68,15 +68,16 @@ typedef struct {
|
||||
* away, so a one-beat charge (slope < 1 unit)
|
||||
* still moves heat once enough beats accrue */
|
||||
uint64_t work_heat_q48; /* FABRIC-3.8.md §3.13.7, ruling C-4 (β), Captain
|
||||
* Bob 2026-09-23: cumulative heat this VM has
|
||||
* pulled in through its OWN per-beat charges --
|
||||
* heat earned by working. The switch decision
|
||||
* Bob 2026-09-23: heat earned by working -- the
|
||||
* charge assessed at each of this VM's own
|
||||
* beats (sub-unit scale), whether or not it
|
||||
* could be moved; raised to the busy set's
|
||||
* floor when the VM gains work (Stage H,
|
||||
* "floor on contact"). The switch decision
|
||||
* compares this, not execution_heat_q48, so
|
||||
* Hera's structural endowment (all of Q48_ONE
|
||||
* at init) never counts against her. Only ever
|
||||
* grows; a record of transfers that already
|
||||
* happened, not heat of its own (fleet K is
|
||||
* unaffected). */
|
||||
* Hera's structural endowment never counts
|
||||
* against her. Bookkeeping, not heat of its
|
||||
* own: fleet K is unaffected. */
|
||||
} VMPhysics;
|
||||
|
||||
typedef struct vm_physics_node {
|
||||
@@ -388,6 +389,15 @@ void vm_physics_charge(VMUuid vm_id)
|
||||
|
||||
if (!target || !target->physics.is_live) return;
|
||||
|
||||
/* §3.13.7 (β), refined at Stage H (Captain Bob, "floor on contact"):
|
||||
* work heat accrues the charge each beat was ASSESSED, in the carry's
|
||||
* own sub-unit scale -- not what could actually be moved. Counting
|
||||
* only moved heat left a busy VM at 0 whenever the rest of the fleet
|
||||
* held nothing to pull (seen live: Hera busy-waiting in the DoE never
|
||||
* yielded). This is work done, beats x slope, independent of who holds
|
||||
* heat. */
|
||||
target->physics.work_heat_q48 += fleet_transfer_slope_q48;
|
||||
|
||||
target->physics.charge_carry += fleet_transfer_slope_q48;
|
||||
amount = target->physics.charge_carry >> 16;
|
||||
if (amount == 0) return;
|
||||
@@ -419,9 +429,6 @@ void vm_physics_charge(VMUuid vm_id)
|
||||
* vanishing. Conservation is unaffected either way -- only what
|
||||
* vm_physics_transfer() actually moved ever changed any heat. */
|
||||
target->physics.charge_carry += (moved_total - moved) << 16;
|
||||
|
||||
/* §3.13.7 (β): what this VM's own work actually pulled in. */
|
||||
target->physics.work_heat_q48 += moved;
|
||||
}
|
||||
|
||||
uint64_t vm_physics_work_heat_of(VMUuid vm_id)
|
||||
@@ -431,6 +438,13 @@ uint64_t vm_physics_work_heat_of(VMUuid vm_id)
|
||||
return n->physics.work_heat_q48;
|
||||
}
|
||||
|
||||
void vm_physics_work_heat_floor(VMUuid vm_id, uint64_t floor_q48)
|
||||
{
|
||||
vm_physics_node_t *n = vm_physics_find(vm_id);
|
||||
if (!n || !n->physics.is_live) return;
|
||||
if (n->physics.work_heat_q48 < floor_q48) n->physics.work_heat_q48 = floor_q48;
|
||||
}
|
||||
|
||||
/**
|
||||
* l8_regime_modulation_q16 - Read-only Q16 multiplier from Hera's own L8
|
||||
*
|
||||
|
||||
@@ -269,6 +269,46 @@ VM *sk_vm_switch_decide(VM *self, int self_competes) {
|
||||
return best;
|
||||
}
|
||||
|
||||
/* Stage H, "floor on contact" (Captain Bob, 2026-09-23). See the header. */
|
||||
void sk_vm_switch_signal_note_work_gained(VMUuid vm_id) {
|
||||
VM *current = sk_vm_switch_current_vm();
|
||||
VM *mama = (VM *)sk_get_mama_vm();
|
||||
VMRegistryEntry e;
|
||||
VM *v;
|
||||
uint64_t floor_q48 = 0;
|
||||
int have = 0, i;
|
||||
|
||||
if (capsule_vm_registry_get(vm_id, &e) != 0 || !e.vm_ptr) return;
|
||||
v = (VM *)e.vm_ptr;
|
||||
|
||||
/* Only a VM that had NO work gains some here: not the one running, and
|
||||
* not one parked mid-task (either already had work, and keeps its own
|
||||
* work heat). */
|
||||
if (v == current) return;
|
||||
if (v->native_stack_saved_sp != 0 && v->frames.count > 0 &&
|
||||
v->frames.entries[v->frames.count - 1].kind != VM_FRAME_IDLE) return;
|
||||
|
||||
/* The floor: the lowest work heat among the VMs that have work right
|
||||
* now -- the one running, and every other eligible participant. */
|
||||
if (current) {
|
||||
floor_q48 = vm_physics_work_heat_of(current->stadium_vm_id);
|
||||
have = 1;
|
||||
}
|
||||
for (i = 0; i < g_slot_count; i++) {
|
||||
VMRegistryEntry o;
|
||||
VM *ov;
|
||||
uint64_t heat, stamp;
|
||||
if (vm_uuid_equal(g_slots[i].vm_id, vm_id)) continue;
|
||||
if (capsule_vm_registry_get(g_slots[i].vm_id, &o) != 0) continue;
|
||||
ov = (VM *)o.vm_ptr;
|
||||
if (!ov || ov == current || o.pending_reap) continue;
|
||||
if (o.state == VM_STATE_DEAD || o.state == VM_STATE_STILLBORN) continue;
|
||||
if (!candidate_standing(ov, mama, g_slots[i].parked_stamp, &heat, &stamp)) continue;
|
||||
if (!have || heat < floor_q48) { floor_q48 = heat; have = 1; }
|
||||
}
|
||||
if (have) vm_physics_work_heat_floor(vm_id, floor_q48);
|
||||
}
|
||||
|
||||
void sk_vm_switch_reap_pending(void) {
|
||||
VM *current = sk_vm_switch_current_vm();
|
||||
int i;
|
||||
|
||||
@@ -536,6 +536,7 @@ int sk_hermes_send_one(VMUuid from, VMUuid to, uint32_t type, uint32_t channel_i
|
||||
* the switch decision told something changed -- the recipient may now
|
||||
* be the coolest VM with work. */
|
||||
msg->wait_stamp = sk_vm_switch_signal_next_stamp();
|
||||
if (q->count == 1) sk_vm_switch_signal_note_work_gained(to); /* Stage H floor */
|
||||
sk_vm_switch_signal_note_mail();
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -1213,6 +1213,19 @@ int sk_vm_owns_outermost_frame(VM *vm) {
|
||||
vm->mode != MODE_COMPILE;
|
||||
}
|
||||
|
||||
/* Stage H (FABRIC-3.8.md §3.9): 1 if the rest of the input holds no more
|
||||
* words -- the same whitespace set vm_parse_word() skips, so "exhausted"
|
||||
* means exactly "the next vm_parse_word() would return 0". */
|
||||
static int vm_input_exhausted(const VM *vm) {
|
||||
size_t p = vm->input_pos;
|
||||
while (p < vm->input_length) {
|
||||
char c = vm->input_buffer[p];
|
||||
if (c != ' ' && c != '\t' && c != '\n' && c != '\r') return 0;
|
||||
p++;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void vm_interpret(VM* vm, const char* input)
|
||||
{
|
||||
vm_interpret_kind(vm, input, VM_FRAME_INTERPRET);
|
||||
@@ -1276,7 +1289,15 @@ void vm_interpret_kind(VM* vm, const char* input, uint32_t kind)
|
||||
* is a clean switch point too -- without it, a payload made only
|
||||
* of primitives (no colon word, so no execute_colon_word()
|
||||
* checkpoint) could never be displaced. Same ownership gate. */
|
||||
if (!vm->error && !vm->abort_requested && sk_vm_owns_outermost_frame(vm)) {
|
||||
/* Stage H (2026-09-23): not after the LAST word. A VM with nothing
|
||||
* left to interpret isn't mid-task -- its trampoline decides the
|
||||
* moment this returns -- and switching it out here would park it
|
||||
* "mid-task" with no work left, which KILL (rightly) refuses. Seen
|
||||
* in design for the DoE's completion notice: a payload whose final
|
||||
* word is a SEND to Hera sets decision-due, and this checkpoint
|
||||
* would otherwise act on it one word too late to matter. */
|
||||
if (!vm->error && !vm->abort_requested && sk_vm_owns_outermost_frame(vm) &&
|
||||
!vm_input_exhausted(vm)) {
|
||||
sk_vm_switch_checkpoint(vm);
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user