Stage G: SEND, and two workers interleave; decision compares work heat (FABRIC-3.8 §3.8, §3.13.7)
SEND ( paddr plen name-c name-u -- ok? ): general kernel-Hermes send to a named VM, from = calling VM, one block max (refused, not truncated), every VM. New SK_HERMES_MSG_TYPE_SEND (30). Ruling C-4 (beta), Captain Bob: the switch decision compares work heat -- the heat each VM pulled in by its own per-beat charges -- not absolute heat, so Hera's structural endowment no longer makes her hottest. Before it, the two-worker test ran serial (AAAA...BBBB); after it, byte-identical on amd64/aarch64/riscv64: AAABBBBBBBBAAAAAAAABBBBBAAAAABBBBBAAAAABBBBBAAAAABBBBBAAAAABBBBBAAAAABBBBBAAAABB (40 A, 40 B, 16 runs; ordinary words only, nothing yields). Acceptance: relay 3, idle growth 0; dict_hash set changed by the new word (word_count 533) and identical across ISAs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c7185facf0
commit
81def7b4a5
@@ -67,6 +67,16 @@ typedef struct {
|
||||
* FABRIC-3.8.md §3.13.2: kept, never truncated
|
||||
* 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
|
||||
* 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). */
|
||||
} VMPhysics;
|
||||
|
||||
typedef struct vm_physics_node {
|
||||
@@ -208,6 +218,7 @@ void vm_physics_init(VMUuid vm_id)
|
||||
node->physics.touched = 0;
|
||||
node->physics.is_live = 1;
|
||||
node->physics.charge_carry = 0;
|
||||
node->physics.work_heat_q48 = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -220,6 +231,7 @@ void vm_physics_init(VMUuid vm_id)
|
||||
node->physics.touched = 0;
|
||||
node->physics.is_live = 1;
|
||||
node->physics.charge_carry = 0;
|
||||
node->physics.work_heat_q48 = 0;
|
||||
node->next = vm_physics_head;
|
||||
vm_physics_head = node;
|
||||
}
|
||||
@@ -407,6 +419,16 @@ 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)
|
||||
{
|
||||
vm_physics_node_t *n = vm_physics_find(vm_id);
|
||||
if (!n || !n->physics.is_live) return 0;
|
||||
return n->physics.work_heat_q48;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
#include "starkernel/kmalloc.h"
|
||||
#include "starkernel/console.h"
|
||||
#include "starkernel/capsule_birth.h" /* registry, capsule_vm_force_reap() */
|
||||
#include "starkernel/capsule_vm_physics.h" /* vm_physics_heat_of() -- the decision's heat */
|
||||
#include "starkernel/capsule_vm_physics.h" /* vm_physics_work_heat_of() -- the decision's heat (C-4 β) */
|
||||
#include "starkernel/vm/kernel_hermes.h" /* sk_hermes_pending_peek() -- eligibility is the queue */
|
||||
#include "starkernel/vm/vm_frame.h" /* top frame kind -- in progress vs parked idle */
|
||||
#include "starkernel/vm/bootstrap/sk_vm_bootstrap.h" /* sk_get_mama_vm() */
|
||||
@@ -222,7 +222,10 @@ static int candidate_standing(VM *v, VM *mama, uint64_t parked_stamp,
|
||||
}
|
||||
}
|
||||
if (!eligible) return 0;
|
||||
*heat_out = vm_physics_heat_of(v->stadium_vm_id);
|
||||
/* §3.13.7, ruling C-4 (β): work heat -- heat earned by this VM's own
|
||||
* work -- not absolute heat, so Hera's structural endowment never
|
||||
* makes her the "hottest" VM by construction. */
|
||||
*heat_out = vm_physics_work_heat_of(v->stadium_vm_id);
|
||||
*stamp_out = stamp;
|
||||
return 1;
|
||||
}
|
||||
@@ -260,7 +263,7 @@ VM *sk_vm_switch_decide(VM *self, int self_competes) {
|
||||
/* A running VM that still has work keeps the CPU unless the best
|
||||
* candidate is strictly cooler: a tie with the incumbent is "no reason
|
||||
* to move," not a coin toss (ruling C-3). */
|
||||
if (best && self_competes && !(best_heat < vm_physics_heat_of(self->stadium_vm_id))) {
|
||||
if (best && self_competes && !(best_heat < vm_physics_work_heat_of(self->stadium_vm_id))) {
|
||||
return (VM *)0;
|
||||
}
|
||||
return best;
|
||||
|
||||
@@ -489,10 +489,73 @@ static void sk_word_kh_elevate_send(VM *vm) {
|
||||
vm_push(vm, rc == 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
/* FABRIC-3.8.md §3.8 (Stage G, 2026-09-23; name ruled by Captain Bob:
|
||||
* "SEND"). SEND ( paddr plen name-c name-u -- ok? ): the general
|
||||
* FORTH-callable kernel-Hermes send. Delivers a copy of the payload text
|
||||
* to the VM named name-c/name-u, where it is interpreted -- on that VM's own
|
||||
* native stack, by a switch, once the compudynamic decision chooses it.
|
||||
* `from` is the calling VM, derived here, never taken from the stack (the
|
||||
* anti-spoof rule KH-ELEVATE-SEND established). Bounded to one block of
|
||||
* text (the payload is sent NUL-terminated, so at most
|
||||
* SK_HERMES_CHUNK_MAX_PAYLOAD - 1 characters); an oversized payload is
|
||||
* refused, not truncated -- truncated code would run as different code.
|
||||
* A plain, unconditional primitive: restricting who may SEND is ACL.4th's
|
||||
* job, never a C check. Returns 1 if admitted, 0 if refused (no such VM,
|
||||
* dead, oversized, bad address, or kernel-Hermes refused: queue full or the
|
||||
* sender's reservoir can't fund it). */
|
||||
static char g_send_buf[SK_HERMES_CHUNK_MAX_PAYLOAD];
|
||||
|
||||
static void sk_word_send(VM *vm) {
|
||||
cell_t name_u, name_c, plen, paddr;
|
||||
char name[VM_NAME_MAX];
|
||||
VMRegistryEntry target;
|
||||
const char *src;
|
||||
int i, rc;
|
||||
|
||||
if (vm->dsp < 3) {
|
||||
console_println("SEND: expects paddr plen S\" name\" SEND");
|
||||
vm->error = 1;
|
||||
return;
|
||||
}
|
||||
name_u = vm_pop(vm);
|
||||
name_c = vm_pop(vm);
|
||||
plen = vm_pop(vm);
|
||||
paddr = vm_pop(vm);
|
||||
|
||||
if (name_u <= 0 || (uint32_t)name_u >= VM_NAME_MAX ||
|
||||
plen < 0 || plen > SK_HERMES_CHUNK_MAX_PAYLOAD - 1) {
|
||||
vm_push(vm, 0);
|
||||
return;
|
||||
}
|
||||
src = (const char *)vm_ptr(vm, (vaddr_t)name_c);
|
||||
if (!src) { vm_push(vm, 0); return; }
|
||||
for (i = 0; i < (int)name_u; i++) name[i] = src[i];
|
||||
name[name_u] = '\0';
|
||||
|
||||
if (capsule_vm_find_by_name_nocase(name, &target) != 0 || !target.vm_ptr ||
|
||||
target.state == VM_STATE_DEAD || target.state == VM_STATE_STILLBORN) {
|
||||
vm_push(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (plen > 0) {
|
||||
src = (const char *)vm_ptr(vm, (vaddr_t)paddr);
|
||||
if (!src) { vm_push(vm, 0); return; }
|
||||
for (i = 0; i < (int)plen; i++) g_send_buf[i] = src[i];
|
||||
}
|
||||
g_send_buf[plen] = '\0';
|
||||
|
||||
rc = sk_hermes_send_one(vm->stadium_vm_id, target.vm_id,
|
||||
SK_HERMES_MSG_TYPE_SEND, 0,
|
||||
g_send_buf, (uint32_t)(plen + 1));
|
||||
vm_push(vm, rc == 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
void sk_repl_register_words(VM *vm) {
|
||||
register_word(vm, "BLK-ATTACH-ACK", sk_word_blk_attach_ack);
|
||||
register_word(vm, "KH-BLK-ATTACH-SEND", sk_word_kh_blk_attach_send);
|
||||
register_word(vm, "KH-ELEVATE-SEND", sk_word_kh_elevate_send);
|
||||
register_word(vm, "SEND", sk_word_send); /* FABRIC-3.8.md §3.8 */
|
||||
}
|
||||
|
||||
/*===========================================================================
|
||||
|
||||
Reference in New Issue
Block a user