From 6a7d0aa27f4b492be345f5f1caa7c723b7aae6d9 Mon Sep 17 00:00:00 2001 From: rajames Date: Thu, 8 Oct 2026 20:01:11 -0400 Subject: [PATCH] fix(v4.0.0): step 6e review round 1 -- a take that does not take does nothing; output and answers wait at a middle node; no queue on the kernel's port; a printing wait is not interrupted - The result of every take is looked at: a wire cut between the look and the take leaves nothing acted on (the text before run again, a refusal counted, a wait ended by an old answer, a way forgotten). - A node with no room to pass on what another printed, or its word of how text ended, leaves it on the wire it came by and moves it when there is room; what is behind it for the same port waits too. Sleep for room (9) is woken by room on any of the node's wires (engine; MESH.md 7d.3). - The lone node's host keeps no queue for the kernel's port: a message to it is error 18. The step limit is counted while the node waits for room. - A text waiting for room to print is not interrupted by console text; AWAIT is, as before. (BREAK?) is gone. - A GONE is believed only by the centre's port; unawaited answers let go are counted; nothing past a message's last character goes on a wire; AWAIT sets (AWAIT-FROM) only once the stack has been tried. - test_host_depth.c: ten paths, each run works or ends in one named stack error; it fails with (ROOM) or (ROOM-SERVE) taken out. Co-Authored-By: Claude Opus 5.5 --- capsules/v4/nucleus-64.f18 | Bin 56176 -> 57048 bytes docs/v4.0.0/MESH.md | 22 +++- v4/Makefile | 4 + v4/capsule/core.v4 | 119 ++++++++++++-------- v4/capsule/quit.v4 | 94 +++++++++++----- v4/include/v4/fabric.h | 16 ++- v4/include/v4/wire.h | 1 + v4/src/fabric.c | 15 ++- v4/src/wire.c | 2 + v4/system/boot.c | 58 +++++----- v4/tests/host_map.h | 13 ++- v4/tests/test_fabric.c | 51 ++++++++- v4/tests/test_host_depth.c | 187 +++++++++++++++++++++++++------- v4/tests/test_host_mesh.c | 216 +++++++++++++++++++++++++++++++++---- v4/tests/test_host_quit.c | 3 +- v4/tests/test_host_unit.c | 5 +- 16 files changed, 624 insertions(+), 182 deletions(-) diff --git a/capsules/v4/nucleus-64.f18 b/capsules/v4/nucleus-64.f18 index e3657eb60f56df4f0a6666fcbb9b0f705fde7de2..fa7a270faeeba15a621455fb74aca3b2d9c1c933 100644 GIT binary patch literal 57048 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
  • Ki*MKhv-B1ef~JF4TkV z!*bfyJtr_0_M%@9zXJJuUVI+fi_gvPa_s6pH~%imUx6LDKJ>a{SH4$)?fDGaf%}Gh zF9^@Atq0$i&`+Mm^SgY0@O6l{x_`|2nzZ$2rl~*izXLzopZc*M zJdXPDJsT|NdkE0I<-JgLJS5#5oL`_FrlLI__f)?Uyf>l*`Q*pvCXKw`i(m8o5$tD_ z<31l&4nCwe@B3;Idak>&ocrg@r~TM3IL`7t6bE<2zIaEB^JovoCDUKlh#Wr0Pk%VP zW7r>9f6hzzJbZufWxo|(a;lAdY%g)Jgful?G{BmuTq!L#R^ezBAy<_XMQhFxZ z<;;$H-i7=A@h^3sdno_^y04O`iv2YM^ydN6pJ~5KVgL5tTNei&-&=Q4$KbtnHl7&!KP>`VF4=A&#|zV5 zcS}CyF~73=A$LhW+jYw=nol5g6pN#GJM^K*e$q5rfj!E;QQfBQKHeIezv z^Nt7fzMSvzShc9Z-e-)4z24&cyfAO!_>zNi;*p;7g%;GG`F`g$sIDgG{+tc3lTU7h+9?;zd==O8knH2OBuB)1P)5lWAezOX8 zZEDnZWB)hn4h4coyZ({m51#AagByvb*e+k{b1t;wgyV$Tyf?F1&o}K4`*ObaUdQ-i zDfin>uqL8kaNpOgAO1zksb?9+6TWYx@{Xwsq?~cdEhpX-yw8Ns?@>O>Q^6Gvj<`+u zQ*Xa~@!OK7zq|I{+h{G+stK z>G&A!6poiGV6PUupON)(`!VAb{WrIhj=Ow65aXaZ7j6BA@z>bH&W|gd^X8n-($9Vb zeus1lx65Hp+HI#Es5sAKGhVoOY#%qjJLseN3ElVKm8r=Q^1Yl@TReDgT?L#p>< zm4g2$;89=pH_mT4&!#;pJL&rdNk{#QVISTf)H?imf6%p)#$U#nRP?_{LC^C-^aJX3 zb|>9;BER#Y59J)!N$)RNPqqv3UHzx0kk9*(*(j%-TzzK2PDZXBpD35{kooSse-QAa zmHNJfnnUS<3NMDA9trp>^#9Ayj+Z(8-}_MKOYFbx&DS#f>H24WS^tpWX2^>Ja{kWe zA}{;7qBEb*D_oBJ63j=5Pdk|NF25Jbe`0>ovAn{D)S>};ZlOxe5WLs+M#x_TXyQ+v z&@--O^wTE|sXgzPZUcUcL&x_rX7|(m`SR)tGqjog3NRr3y`a0>q33-{Wd^}N)eav% z_m-RBW5?5qs?nBjO^uFou03A@pBEf{d>$@iY;CF7(cRzvEAZEo?sGj~xee_<8~6J> zr?mv@Ji<$^XujS@gx{XvdGCVXn}AK5!+d#ub9VM+c3wfgj1N3VmOpuNgUDgJZsyEb za=Lz2V>o?wEQCLC#yNF@@9NKZL%!9G6DK9}Jug;&L2|mT;p}P2>H67{`fvEonjv(Q zJJ~Q!PEP~l_Vlf)Yn+u#*DxbCGdXRlb6#@VRL9zZa!qyWlgm+^cJ1^Z%bhi2aGz!U$4>fdC!M)pxIR6cbYCYuz)25v(j`v1+DV`8q+R=7 z9no_T<#p#wO!kk)sbN1g$4WzfKBG~v|6RWW-}-P|m>8Q8;xqlMIvXca_cQRBHT^7+ z$9_}S5VIOEU0)vy$AuZ^2>QS2N2&CiG)3shcVa_09yO}E?eZ<0Iw_>z)cP4A`!-w{ z3&opR)2#aK@-3e@JrqA;)2D{w&CID<@%HRlP_He>dN$NgizU~4W@AIBohCKT3dQp} zBRGUVePXD-YQ^^L|3B+7b1y##$GJMY+}5sLtEWs2wMV0Kj(XONupj^L4g)ZQ1>HCQ?A`AvSqIU&E8UKjSG zm@!O9PW{;-ISpnakX&vnYckrCJ8D|JJ1+?7(HN@_wO`B#N|vLukM`x1*T+I}#)u7% z-&4c$p0nzvhULV<-`Bjgv2v6&%y+Xz3v zuH}2h=l{;%svE=lO+2SAmTdoqy0b&=HB+mXvfWe7D@^;K-ny?t`^>0geruk{oe^sc z$MMFP4FtrmZ%_-h*DhnsL^rt}|7*R1@wR?)sJ&w6g!|_Y?H8pp!ttV^VOFS~jqTX^ zBitYF0!-_y>t&q*4+K0Ckk93s^-*hQJ{M%#t4Qp``-VoWGf19X++Ui7e9k9{7vE3k zlkR&ih+ny*-x}dV{*-HON_$H_-*?ftU-;Zhs`p)70eSBGF6JfZb^W~Gz{7+;@AGmc zVXoiqr|=)Ruksg8dPQ=m&-LK%FATpInB{zLFwY-zJ@6{XZ*0CJ^xPKL6C++UUc5Ri2}`8+!H4)bWRO=RxjV%m-;-^WFkmQ*gA|{3GIqE_1>Jt6$9r!R z-@^m_iG{U7M}6hDT)jcp1MrX>jhjZ_Rw<|68AHPKlph1#j{wd7n)SEnkV0!pp64R| z;lTSzfUooM7D3{!v+K>vu9M z1b8OeHHi$ufuaCH^E*;Zg0hV-P()!<~=bs?jN0_ z`#nY-t4c35K6@!B&CMvZ8!}{B9xjZ2|sLz#R+gl@9xrnYHEC9u)`#q`MpV zcLMVJbZL1aB@y42qkI|h+Vp*gX@`dK9zprT3A}FwBXX1?GXDG#<&P6@o4!9hT2N&5 z)Tfpb@>c=xDdMAEX-9?SuR-~<3A`4;NE;BwdjaLo1M>TYX-6s^|G$Frm&xzluJ#-W z%NbtcmkgRGd70(<+z+4IV|_P(ejWMb^t(~;)8wDIB0tlL5dUr9y+wSy_cDR66(auo zz1?-K`~`?QfEYB=h%9eDo=@P}Pt>p5hQtuOJu zLiv{oysHEu^?0IPisQ)eSYc=!xnSzdP`Z9b_`d(b*hQiHGhH1q@-T08$C*`&^m&8p z(9kAM{zde=9_b!kaPF?Hf2cisb!q)t@XX}Nb~W=|0~<%_FNcEe;4TXn3ZA)Ncv2wS z_mD0@+}w&*Zm&v3FOQ7Pvaav?VJZXKQH{A3lpzbiXQ3J zBMSxc%I~7TzJni?bkTDnN$)kYU4d!RMZ%wN_D%wyB0#tOtErdM-s8Hoek43iJBIwA zJI;ggzL@np4SYs9-zA)|T+f%?K!3ydN4iNV`KLo3`CkFPalorm@#lQb(Vy(dKYHY#F~VDGMJb+l4frnrTmm_# z)9&;2c_iA^u&EGg@{JrhD8bD9x&?f00K6-~PvZ^k0lp+A8}f~g*|)y;f$!~r4}(Ab zz<2aDY$`+s`KIUV^JR?lkAcsFfTkbcBlzhP^uPo0o<#W*3A~2{FXEhwcn0OGiFf8F z8!X;nO#CHbuC*wC4)A5v!;J$>2jk`75y}An{|x#U0XGEwF#Nt9@*#10x!z|o{W|E^ zC-~(`Sz1_a?`@R-Ex?OgJEVox@;0ITJwPAdUH=gJt?#1m<1**k>^NfNsyv{VVVz`uc$2es?SIJ`3dPxs(_ycX+POFMgc6?|DMs+)J z=r~Up1bN5j>O6sbI8PW7l!xaD$C6))dBVZD?mR)yQAS4mSgIRb!(o>`w9h8}9&PTI zit-`bzeMjh8868oA9N#0kM}n6fu%9IIxe~I)z1I})9zXF)4aFe&c`TkV75kvTGZdA zPj82wab=1(|2aX;s4lhhLHaMqy;GofG2leVE6&yXZJm|NJVWO}X<_quXMyevK=WOb zJn|NJP_E8LIbY-Y)b)|#wtrXdH~Y4H7b-FfdU4)57(!;#4=|rS9{Hv|R-?#|tC6Fi z33{HLfOhBFT?EMYVBwf^ zBJPwFO;8H`zZCdO96BTex@d)dpf4=d^X>%VZ4N!-6XTt`ZuKzg|0vplb~O6i_6pYh z@x95V=%*_KJB8bC+rpcz|8rezbztA{{CQ+<*nds?T6t&W>b%dik4-P9-z~f;#Fw@+ z@xbDd-lcQ(c@_Hb{>`ONB|kEdE7vM{1MGYs?ftpNGwMYav$xEW(?vM?La=) z!45vgWAdfi?3XS-*4K(HqrBfd>Pgo-?`8|(IAfNBaWiS6V{TS~#o`;5-2mD_{Uzd*i z?mx8tM!%t=&-vh2h5hlM-Wb=}-{^PrS0DbwcBWnb0KUd=E$WD_abM&#!GG!h><^~@ z*z)L*>hXe?Un&0+*An!@fq(x|_e2caBz!Y! zs_g!(oy?gu@m~S{%YcS&p5QZ%M2mH%rB5{^=>7`)^$wlT%9UAKj`q-@ZU_( zT`%~NA$bCVYZJ=fBVNup9mj?o8sNx}4^Y0D@a?B|OTM3T#>)aD1H$;90PiEfbw%sH z6TCz`(dTE=2an(oC&k_uz~2h^HRCtlGeLW?T_S@+$N>nCCS zTFw+yKu_Rx2jqIq!K~-f`ey`_>oo4VO{6w#uNe6Bp?vf&leYS%=Z^|aXbuNnU%&yB z>#SR|ebUF|Pf*SE|3J_kNqJo^v-fGovOTU+{rS9h`WRi;A&=ugH^ia;IqC77w+}v< zj{-Z=0t0rY3+IxzcddSQ4W_cGwkCw`lbo9u6q!f^jy0K8^Eqo3{H zhF<$uAvQkIj=u-qRe(2k+xNQN61E%nzj7Mv{$6?^Hb}wmCh++KVB_`c?YN0z6TiP$ zEx#jUun~@e+kxK#X!f(L-a}x|$e3`u-3z?C9elU_wk*5&1rhOdw*{{W^k?XM)*eH9 zJ>D(6-uwjepX#RTS4nZ&uCECXxy-!A&L2!WzbNS)3-!5i|NdDvzO11?BR)O{`!PN~ zkMbAWk+b6goi8=sJk#3Eze?LbQW%cYFT)PY0UtuUZ@zfzXQChdF{5xyzNAAz(xm?a zx>p_gx6uzbUwpBRAFQvRqx(OGU#ajbmw!puM(}yV;kTJ_6Z9_M>=Shbx(adFT@U&v z_`MJ4<6PI*2zZ$Vy00_N`OxI&!2cBRE7;$C-Zp8z;7fJ(K;KQv!g0vFKSAyKxASd# zPr(%LE5;?-XAI`ENw%>+_t%W=&?jqu%o{-0Wq+M7%z1jX9VaR`==V^lAE949*7_m+ z)bP>xL;1z}##DPqB}s=|$d3YY{>l0JLHn0mzU|rP$I#Pnd(7%dJNV5*Lg&$_-$9Ok zQM^Z<^H8eT7kc#wU_6gy0;8kKtRK7IQ9TOy{T;d(h_eUORLRf$QhbNiuD+qrfFYn8 z40rVZm?HxKWb=ziIP`H6r67;FHe0k8+&kpSpkB9Rg3=AK%GA zJe!XCmwA`jb~oqn*9jiK3vv$1CqpjZQ^R^Q9x*@gXYOZg2%j$r${l|{4fgAY_YAQf zo8QuNldMl8>fz46XR{sAzJVN`L#x*FYgGkyIwn=LR}bfjnp_Qjv_n7_*L5}WQ|&!3_Q7I1o=~3zzbC#BoV!xA zZ_EDr9)}%x?@+3K-i&rJam)HA?Mmpzt?Sqi;ySL-Pco_t@d!dA#W#j&szLdOWg!yhT_Wzr{HC?4Ko_QGK!<|0*Ss zbghD3%Twt0dLG&gr4Oc$5q&UvHVfB zb|}rSIwhIC_ci!#2jqJqzeKvd?*eq`uH${G@7uOve#7rD^1b|VJnzJFjl@fgHv?>Z zr@i_>&YWg_za0IW^nTl<)1_i_v7UHYq|kcKA*e0aF->z>zi`jZGfL8qQGA!wUC)bk zSB86FAD)k_uAA5(B}`A6601*6H<%YBB&UbRBxU;LLOsXF_R8(9<1p8mkL$j`)`ND) z?XJ%qF@1dZU>w%|jrvoMf>{kAx#n@x;UbsiF-e$<=jP~7)We+DwBvl#eBed!3ajOR z;u-~e`~;BidnE>+Phu%s(&y&7j|3sgub7WdR*8&{QXX@3mN;`ag6$r zkH35OH=@V+)ED!-OvJO%&WFA(g`P72Q+>at75bU)p;^1Iy%?V>3qQ1S*)G3_z1{U1 z?$5J7uEl%lSYLm~$Y#->@BLwas7L>yKQpe}o5F4_j@|BnztL{glYFl4zV=&@OMdm; zTU#YfKOp`cuq)r=ME?);Z{A|#74aW}9Ug^z^WAFOZmbvS`JO8F(|})j*PBHT@_Q2e zo=(A!^t2QCYz@AHtovh($DNKJW*0K}eVM|1{cf|!lxq$AVm=_-Eo+=c&}uWVDW?8^ z33x98G9H=t*M#C#F&;E#`IOiN#|Q_e2M!)MI5{0F{@Kt_S`y~+7xo(^5W`VmC&`n` z?6=$dAMzb=*3?GH=XrJ3C!;do-g#N)76e@XKt0+3xxaTH%9%dRNe^+-CpqcV^zl4F z<+zn$K5QxJG4e0z+68^K0J46Gaj-Q{&`Sm!KTIIYW8=f^Fg;e?m|Q;5dH$Pp)fdhT z;nhu#h1#=jX5++UJ`H%ln)Cyu&|I`TpKHx1*!MZKZaozLThx#9Bfd9`@qzh#Zy5Vk ztiQ%r;xkV1eZ0hD|Koc)na_TtT6iAg1nC*axDQEs;?Ko>QUn<@Z7az8jlHsrtUwr@fGG!aR=PyX-QcWN@98B-fWY4iBuMakuLleLB2_BCX|3i4dT}Mz?<5RoJ&&3nGCwgkmGN< z%EmRm@2-=TX8NI%-x>KG#}2giud2`mZhR&brJgq*{4W4(SupbBpfKsjApAD*b*uc` ze8~y(1Tx-s7(1{=(iH=AW6CBZ1J`2kZw73f8ldu>0<}}Oh|cuw*++80h)Cd zJ5D4lt%3}l9cM^)8}M)0iw=qbWR2B>P6hd8cE}+8y`Z~mFM4`XAUCw(@G$Tn05s!= zoi`f&?D$OSkE8rCK(n7>@uFh~<_k`Ex6<>T0^XB=W}k=ncHGvd?a1Bpo&{bjpfMWt zKtxmA7f}8@pxLh>UT7oW6_mdWXx8(IH!>mB?AM}v9iTZ!VdEb0Gtf>UB@BZ%fd3jG z_mzx2?2sAWef7L|fVUCQ><7>u7T?(>_5K08zXO_ee(HZ@vi={Td zp>kLcQ@+M!!Ta>h{;vr?Kd0d?fg9i5Y2#lr9B0ms zQ`;o}3Zz+&*!*?pNq(wp5ekf6Tjl5GYc=kXPq&_0PjjDaHjsXwSB%g>Gz5^^bD_H@>U;hU-S?e%#M{w%#Ck=YlW! z_gl2X(zE_NFHr>kh2UQbx(Yz<3zh(n`NT8pzSchjzVi>a{wF%f#de$qeW&$|=h*`M zGxfH1BK?li?Vm~!^*bGMeXQ@Fiu74d`!h}6)ced7_^w|yexUnRwEvcoQcYzH(HT4YS*nljgH2fuJ=&(@wtfGEO+%``CQD$%(!Id z3ll1p0slXQcE13S?Vi~S^X3nAo$P9~^HV{6w4MEhSG}&hc#={zVLbP7PUCyfZCSL! zjt7gtkKc7L`8H1d&dK-Ny4v}EqGzi#@@Je~47wQNYy=g&2Kj!Q?i+$?aLc;bVotXz(e>V29u<`rFw!Wla^#}Ba)ykK8q|%S$0O>#I`HGeMNl)EZ{1EN_ z3Hry!J#`(f4Gp}V{RaClJU2^y87IvCYJ=2gC+NO_{ntD6O|S>`W;=h^vvsW6L)VR5 z`*WUT>|*C7CLXO6eD1rEPkJx(zkT;7GhXU@ja>irg6CgZBjIk&gJJ$VwkkYx&+et= zkHL@k?}g{c13p^L|MSp)Hr%V@S$QuV|E5f5JW{zn`egm`tNm)|8ib`Lkj<;e9~Re6aEW7WIp{c=A1u*y)WIes{TPy zX4|#;egx7nZj*jE+L8GqV2{FH>bE0%X}l`zrSpq|Uh21e?(7ciOOwCxmkA=*Ks&Cu zc4$IB4(fk-|0=sc&jOu0uAR$xiu$H%j~28C`TC<5e<6I?3r_FF{-)m* z@%fx2EK?6TGoeT9gR!fGp8ROP^Lov{UGgh&?%_t%gBEU@ug`@nL4Kwno9pJlABWAB zbklsimkIex0f#Q?*C6H8{1(y$&kaRvX$$h3=Ij1E>20C;NBN)i$h7Ux{MZNKb|4>? zGhUyG@r!(8qg7w(!?^l~UahwZ?NZo{&ygnhA|8}4eokd*WW5>vVm{j)?aSv$XjjVH z6wEjEd(XyHVt@-BqS}e*o-tLC>%~8Zn=zyGe%_68sR!*l^gf-B??n$ga6Dx_*9LlopBH=q@!}>p=%1Z-px@DdU+J~L zw!_6K>`r~?_is|}!mwQ%9@ZN1{?acGzqjOV*u&hf<_RY88Mof;m1FPs-19U{EB8d4 z<9v|p(!`t4+U2koWL z`?S8TUr5;<(CIwz#J=fP^7qWo(f#v4F3)!%|5tzf+BO;c_wA?`)c0;f+U?(GI{o`j z;C1&h?L3x#VA8hU>?x*SAgvN&=>L>^7x?t-y}-s*x4nDyW_;CgiSgnN(2>4r^Ievn ze9e5z#wU(DODwL&g)oxYF^&3&kq z7~8Dzg?^&mXP$>fdkkH4m8GXYuzd_Q*VhV!Z-p+a2O=s^pW%S4&x92EPr>u5WI+2f z{aFu<4@|py(?3~%>dk!G!_b+oZr07fi1pmNe)Da;&3drCqpNZj%+Eu;Y5*Bm?t|SL zQBNbxVl>Uy@#tQZ-vel#e`^p`xZiX)@=d?8`5mDr%lGWj4D&vW`{Wnj&k!3mX|1G* zZ~WS>519MA#gfm9&xP`N$@U=6;c#7`82hg5FRTaaHF<}ggQq;c->VBUHsgJ8ZoYqp z>Z$GEd3+w7;~VXI2ITWOPd-OWyU|Wb=SQrcrdJo(_`!Mq9c(v3JhwI%_D>&GcCx5U z{QH1+kAr_H@So_d_w$eU*7LYLN8#>=?MK60uKkYtH(im>`SjD^$91ZQ7_V~9vh&T% zQMJWFQtWJ~t%iONP*2PUsSm&Z^d$T5DE(fMzO9nVdEN`4f6n2) zGw3%Zp!JviipJP}UvtU;8!u{&B0^E+DDpArKB69YFAKl-PCYWpbmK96S8bl>0G-(< zwwKxeTq8d&z`nAvn=LWtbL_aweEO*$dETCH;lA+R-wjZ{Z#vK6b3d2+t%{X&PHLgP z-<0P}Se`0=us^cj@%+Sa5b}JSD=#?Tg!kOBUaa@p-n!pUJ?0cnS|BV*XP%Gvg{0l* zs1HAY>yrAulb9fx>!$}`{^2}FeI)RIdVtO+824h9%CtMq>79x1mr*|NAB_Bq1eNE~ zjyXW%L2vTIxir$VJ}y01`9^@gV?a;6)E$0Hk*kSE{*$UQHRujBZ-h1>1;9zVc2H|TGF zTIkGtz^?lmy=_YxfBci=Q@=TA&+IZ?p)sG;v-0(nXkwiE4dh;&ke?@fcy%B>&M%dt zfaB!vKzF4>Pdx(q(sDa~RXFt5g6?XEp7XVUzNW}VV#ZU-zZrB(9D2@cgZdX1X-C|(BAZihUgyy-J^i3(I3|yp!=vq zX1wD#vnIe}oY1&Oy%`s%$GV;Ctv@mS@?L(W`zEg)5Pw2sZaP5cH5~sxanie-bXuP< ze&;@_7rzH`;AuzyO!#u#nl1@*F>Tz_x(3r#!(*ZH15X=lNjmq@c%H=f=wv=FU*opc zWyu$81G)c(?~s4T*SK>?-~WK`vHy;*adYe9|AoHC{5!tJ{cY$W-_h;Zm*cvbH|YF` z{UC@V1GGQds3MZ}%ITx0VLTb2^>x^Iy}+jjAo(z^1bi4*Qt*j^ z{$Pg><4eGY@g)VH{-8g?;lntyAL|vwnSI-52aVt+`$`!mT8%EeY5*Izoa9{Kt`g^KZENe_Vk-HsgBzA-5FRM1}G_qlMLo8xXL%B|ED z#PXuT6InNbPea}cNA7Q7#}^$rZvg?|OV zHis|ueJ9ZOSv${N+xw^|g%ZnP-m>0$Kj?3p0zc3ixqje?_5KcdZ>5m?sUvr5AKic5 z)yJKO9koVlqH=czbUOc{zwE+1#n|~7!Cwo#30rz-J&5n;9K2lc_V%-IO}>TxTT;{` zdLZI7-XGK9K%LLU$FF}<_`e4S*l@3=W0Gkub6)yUfq}o3mX}Dns7jxEl;3i7I#6L6 zAp0xVt(bQGo()Jmx18q&YSa{MG8-scmKd}3c~4)ORr2>BDwi!0tq zs2B11e29zBb|fS|pEGgw@M4-nyf04FwC);8KT`X&AfNQqhx##}kn8yTe)RtXdH=0? diff --git a/docs/v4.0.0/MESH.md b/docs/v4.0.0/MESH.md index 97ac8d79..864e5d32 100644 --- a/docs/v4.0.0/MESH.md +++ b/docs/v4.0.0/MESH.md @@ -794,7 +794,7 @@ the engine and happens whole. | 6, first | The mark is put at the message at the front of the wire. | | 7, next | The mark is put at the message after the one it is at. | | 8, sleep | The node is blocked until a message has come to one of its wires since it last fetched "which wires have a message". | -| 9, sleep for room | The node is blocked until the wire has A cells free, or a message has come as in 8, whichever is first. | +| 9, sleep for room | If the wire has A cells free it answers 0 at once. If not, the node is blocked until a message has come as in 8, or a message has come off any of the wires that lead from it since it was last woken that way: it is then answered 0 if the wire has the A cells and 1 if not, and asks which wire has what (10). *(Changed 2026-10-08, fix round 1 of tasks 2 and 3: it was woken by room on the named wire only. A node may be waiting to put on several wires -- output it is passing on, the answers of texts it has not begun -- and can name one.)* | | 10, room | Has the wire A cells free? It answers at once and changes nothing. | - Each wire has a mark for the node that reads it: the message that @@ -835,6 +835,15 @@ waiting, the words that turned it, the list of NACKs owed, and it has been passed on 16 times, it is dropped, counted in `(LOST)`, and a NACK is put on the wire back toward its sender. A NACK or a GONE that cannot go is dropped and counted; nothing is owed for it. + *(Ruled 2026-10-08, fix round 1 of tasks 2 and 3:)* **but what a + node printed (type 2) and its word of how text ended (type 3) are + not dropped for want of room.** The message is left on the wire it + came by and gone past with next; what is behind it is served, but + for what goes the same way, which is left too so that the order is + kept; and it is moved when room appears. The node then sleeps for + room (9), which room on any of its wires ends. The code before step + 6e waited here; letting output go while the line said ok was a + step back. - *For itself:* it is taken and dealt with as now: text is done; a NACK is counted; a GONE from its centre is believed. - *`SEND`, how text ended, a NACK, a GONE* are built in the node's own @@ -842,10 +851,13 @@ waiting, the words that turned it, the list of NACKs owed, and others are counted in `(LOST)`. - *What text prints* is put when the output buffer fills and when the text ends. If the wire has no room the node sleeps for room (9). Woken - by a message instead, it passes on what is for other nodes, as above, - and sleeps again; text for itself from its console ends the text it is - doing in error 21, "Interrupted"; anything else for itself stays on - the wire. + with no room yet, it passes on what is for other nodes, as above, and + sleeps again; everything for itself stays on the wire. *(Reworded + 2026-10-08, ruled by Captain Bob in fix round 1 of tasks 2 and 3: as + first ruled, text from its console ended the text in error 21. A line + typed ahead while a text printed then cut the printing short. A text + waiting for room to print is not interrupted; a console line ends an + `AWAIT` only.)* - *`AWAIT`* sleeps (8) and, woken, goes through each wire with first and next: what is for other nodes is passed on; what would end its wait is taken -- the answer, a NACK or a believed GONE about the node waited diff --git a/v4/Makefile b/v4/Makefile index a0f8053e..7476317d 100644 --- a/v4/Makefile +++ b/v4/Makefile @@ -206,6 +206,10 @@ hosted-check: $(foreach i,$(HOSTED_ISAS),$(BINDIR)/boot-$(i).txt) $(BINDIR)/star @grep -q '^ok> Stack overflow$$' $(BINDIR)/deep-amd64.txt && grep -q '^ok> 3 ok$$' $(BINDIR)/deep-amd64.txt || { echo "hosted-check: a line that left 29 values on the stack was not an error the node came back from"; tail -4 $(BINDIR)/deep-amd64.txt; exit 1; } @printf '1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26\nWORDS\nDEPTH .\n' | $(BINDIR)/starforth4-amd64 > $(BINDIR)/deepwords-amd64.txt 2>&1; true @grep -q 'DUP' $(BINDIR)/deepwords-amd64.txt && grep -q '^ok> 26 ok$$' $(BINDIR)/deepwords-amd64.txt || { echo "hosted-check: WORDS with 26 values on the stack did not print and come back"; tail -4 $(BINDIR)/deepwords-amd64.txt | cut -c1-200; exit 1; } + @printf '1 0 ROUTE\n65 EMIT\n: P 400 0 DO I . LOOP ; P\nNO-ROUTES\n1 2 + .\n' | timeout 120 $(BINDIR)/starforth4-amd64 > $(BINDIR)/kport-amd64.txt 2>&1; true + @test $$(grep -c '^ok> ERROR$$' $(BINDIR)/kport-amd64.txt) -eq 2 && grep -q '^ok> 3 ok$$' $(BINDIR)/kport-amd64.txt || { echo "hosted-check: with the way to the console set to the kernel's port, a line that prints did not end in an error at the prompt, or the node did not come back"; tail -6 $(BINDIR)/kport-amd64.txt; exit 1; } + @printf ': S0 S" 1 DROP" 0 0 SEND-ON ; S0\n1 2 + .\n65 1 BLOCK C! 1 BLOCK C@ .\n' | timeout 120 $(BINDIR)/starforth4-amd64 > $(BINDIR)/ksend-amd64.txt 2>&1; true + @grep -q '^ok> No one on that port$$' $(BINDIR)/ksend-amd64.txt && grep -q '^ok> 3 ok$$' $(BINDIR)/ksend-amd64.txt && grep -q '^ok> 65 ok$$' $(BINDIR)/ksend-amd64.txt || { echo "hosted-check: a message sent to the kernel's port was not refused with No one on that port, or the kernel's requests no longer work"; tail -6 $(BINDIR)/ksend-amd64.txt; exit 1; } @python3 $(HERE)/tools/depthsweep.py $(BINDIR)/starforth4-amd64 $$(( $(HOST_DATA_RING) + 2 )) $$(( $(HOST_RET_RING) + 1 )) || { echo "hosted-check: at some depth of a stack a line was not answered, or the node did not come back"; exit 1; } @$(BINDIR)/starforth4-postfail < /dev/null > $(BINDIR)/postfail.txt; test $$? -ne 0 || { echo "hosted-check: a boot with a failing case did not fail"; exit 1; } @grep -q '^POST FAIL: fail.wrong out<5 5 $$' $(BINDIR)/postfail.txt || { echo "hosted-check: the failing case was not named"; cat $(BINDIR)/postfail.txt; exit 1; } diff --git a/v4/capsule/core.v4 b/v4/capsule/core.v4 index a50f52c5..1d0ce132 100644 --- a/v4/capsule/core.v4 +++ b/v4/capsule/core.v4 @@ -246,12 +246,28 @@ header CMOVE MINE: drop 1 (A-SELF) a! ! ; NOT: drop ; +\ ( port -- bit ) the bit for a port, by its number, in a word of a bit to a port +: (WBIT) + push 1 pop if Z -1 + FOR 2* NEXT ; + Z: drop ; + \ ( -- ) A MESSAGE FOR ANOTHER NODE is passed on (MESH.md section 7): the \ one the mark of the wire in (WIRE) is at, whose seven words are in (IN), \ is moved, whole, to the wire that leads toward that node. It is refused -\ instead if it has been passed on as often as a message may be; if there -\ is no way, or no one on the port; if that wire has no room for it; or if -\ that wire is the one room is kept on and it would take that room. +\ instead if it has been passed on as often as a message may be; or if +\ there is no way, or no one on the port. +\ WITH NO ROOM on that wire -- or none but the room kept on it, (KEEP) -- +\ text, a NACK and a GONE are refused, at once. But what a node printed +\ (type 2) and its word of how text ended (type 3) are NOT let go for want +\ of room: the message is left where it is, on the wire it came by, and +\ gone past; what is behind it is served; and it is moved on a later pass, +\ when there is room. Printing waits; it is not lost (MESH.md 7d.1, +\ ruling 3). (H-PORT) and (H-NEED) are the port of the first message left +\ so on this pass and the cells it needs there: a node that then sleeps, +\ sleeps for that room, (SLEEP), and is woken by room on any wire. +\ (H-MASK) has a bit for each port something was left for: what is behind +\ it for the same port is left too, though it be shorter and would fit, so +\ that what a node printed goes on in the order it came. \ HOW OFTEN. A message's fourth word is how many more nodes may pass it \ on; a node sends it with 16, and a move takes one off. The node that \ finds 1 there refuses it. A device sends it with 0, which is taken for @@ -262,13 +278,31 @@ header CMOVE 14 + -if OK jump SPENT POS: if OK -1 + if SPENT -16 + -if SPENT OK: drop - (IN) a! @ (WAY) dup (P-WAY) a! ! - (KEEP) a! @ xor if KEPT - GO: drop (P-WAY) a! @ (WIRE-A) b! !b 3 (DO) if MOVED + (IN) a! @ (WAY) (P-WAY) a! ! + (IN)+6 a! @ 3 + 2/ 2/ 7 + \ the cells it takes, + (P-WAY) a! @ (KEEP) a! @ xor if KEPT drop jump NEED + KEPT: drop 8 + \ and the room kept, if it is that wire + NEED: (P-NEED) a! ! + (P-WAY) a! @ -if WAY \ no way: refused SPENT: drop jump (REFUSE) + WAY: (WBIT) (H-MASK) a! @ and if CLEAR \ something is left for that port already? + FULL: drop (IN)+2 a! @ -2 + if HOLD -1 + if HOLD jump SPENT \ no room: only output and answers wait + CLEAR: drop + (P-NEED) a! @ (P-WAY) a! @ (ROOM?) (S-WIRE) a! @ (WIRE!) + if GO -1 + if FULL jump SPENT \ no one on that port: refused + GO: drop (P-WAY) a! @ (WIRE-A) b! !b 3 (DO) if MOVED jump SPENT MOVED: drop ; - KEPT: drop (IN)+6 a! @ 3 + 2/ 2/ 15 + (P-WAY) a! @ (ROOM?) - (S-WIRE) a! @ (WIRE!) if GO jump SPENT + HOLD: drop + (P-WAY) a! @ (WBIT) dup inv (H-MASK) a! @ and + ! + (H-PORT) a! @ -if HELD drop (P-WAY) a! @ (H-PORT) a! ! (P-NEED) a! @ (H-NEED) a! ! 7 (DO) drop ; + HELD: drop 7 (DO) drop ; + +\ ( -- ) SLEEP until a message comes -- or, if (SERVE) left a message +\ on a wire for want of room, until there is that room or a message comes +\ or any wire of this node's gains room (operation 9, MESH.md 7d.3). +: (SLEEP) + (H-PORT) a! @ -if ROOM drop 8 (DO) drop ; + ROOM: (WIRE!) (H-NEED) a! @ (WIRE-A) b! !b 9 (DO) drop ; \ ( -- ) ONE PASS OVER EVERY WIRE THAT HAS A MESSAGE. What is for another \ node is passed on, or refused; what is for this node is left where it @@ -278,7 +312,7 @@ header CMOVE \ looks for what is this node's own, (MINE). : (SERVE) (WIRE-HAVE) b! @b dup (S-HAVE) a! ! (S-MASK) a! ! - 0 (S-WIRE) a! ! + 0 (S-WIRE) a! ! -1 (H-PORT) a! ! 0 (H-MASK) a! ! W: (S-MASK) a! @ if END dup 2/ ! 1 and if NEXTW drop (S-WIRE) a! @ (WIRE!) 6 (DO) drop @@ -316,44 +350,29 @@ header CMOVE \ ( -- ) go past the message (MINE) found: it stays on its wire : (SKIP) (S-WIRE) a! @ (WIRE!) 7 (DO) drop ; -\ ( -- ) take the message (MINE) found, which has one word of text: its -\ seven words stay in (IN), and its word goes to (ONE) -: (TAKE1) (S-WIRE) a! @ (WIRE!) (IN) (WIRE-A) b! !b (ONE) (WIRE-B) b! !b 2 (DO) drop ; - -\ ( -- n ) the number of this node's console: the one it was told of, or -\ with none told of, whoever sent the text it is doing -: (A-CONSOLE) - (CONSOLE) a! @ if C0 ; - C0: drop (MSG)+1 a! @ ; - -\ ( -- ) A LINE FROM THE CONSOLE BREAKS A WAIT FOR ROOM (MESH.md 7d.4). -\ If text for this node from its console is on a wire, the text being -\ done ends in error 21, Interrupted; the text from the console stays -\ where it is, to be done when this one has been finished with. Only -\ text that is still being done is broken: not once it has ended in an -\ error -- NODE-ERROR holds the code from then until the next text begins -\ -- or been finished with, (QUIET), when what waits for room is only what -\ it printed and the word of how it ended. -: (BREAK?) - (QUIET) a! @ if TEXT drop ; - TEXT: drop NODE-ERROR b! @b if CLEAN drop ; - CLEAN: drop (MINE0) - L: (MINE) if NO - drop (IN)+2 a! @ -1 + if T - KEEP: drop (SKIP) jump L - T: drop (A-CONSOLE) (IN)+1 a! @ xor if BRK jump KEEP - BRK: drop NODE-ERROR b! 21 !b ; - NO: drop ; +\ ( -- how ) take the message (MINE) found, which has one word of text: +\ its seven words stay in (IN), and its word goes to (ONE). 0: it is +\ taken. Anything else: it is not -- the wire was cut after the message +\ was looked at -- and (ONE) is not its word: nothing is to be done for it. +: (TAKE1) (S-WIRE) a! @ (WIRE!) (IN) (WIRE-A) b! !b (ONE) (WIRE-B) b! !b 2 jump (DO) \ ( -- how ) WAIT UNTIL THE WIRE of the port (W-PORT) has (W-NEED) cells \ free. 0: it has. 2: no one is on that port. The node sleeps until -\ there is room or a message comes; woken by a message it passes on what -\ is for other nodes, (SERVE), and sleeps again. It is the one thing a -\ node waits to put: what a text prints (MESH.md 7d.1, ruling 3). +\ there is room, or a message comes, or another wire of its own gains +\ room; woken with no room yet, it passes on what is for other nodes, +\ (SERVE), and sleeps again. It is the one thing a node waits to put: +\ what a text prints (MESH.md 7d.1, ruling 3). +\ NOTHING BREAKS THIS WAIT but the room coming or the wire being cut +\ (ruled 2026-10-08): text for this node that comes meanwhile, from its +\ console or anyone, stays on its wire and is done when this text has been +\ finished with. A text whose printing can never go, because a stuck node +\ is on the way, waits until that node is killed (MESH.md 7d.6). : (WAIT-ROOM) L: (W-PORT) a! @ (WIRE!) (W-NEED) a! @ (WIRE-A) b! !b 9 (DO) if DONE -1 + if WOKEN drop 2 ; - WOKEN: drop (ROOM-SERVE) (SERVE) (BREAK?) jump L + WOKEN: drop (WIRE-HAVE) b! @b if QUIET \ no wire has a message: there is nothing to pass on, + drop (ROOM-SERVE) (SERVE) jump L \ and no more of the stacks is asked for than a wait needs + QUIET: drop jump L DONE: ; \ ( -- ) send what has been printed, to where (PRINT-TO) says, by the port @@ -363,10 +382,15 @@ header CMOVE \ wire is the one the answer goes by. The characters are made ready in \ (OUT-TEXT), not where they are: until the message is on the wire the \ buffer is as it was, and a fault on the way leaves it to be sent again. +\ The three cells after the last character are made zero first, so that +\ nothing printed earlier goes on the wire in the last word (they are in +\ the buffer, or the four free cells after it). \ The three cells and two return entries this needs are tried first. -\ WITH NO ONE ON THE PORT what was printed cannot be sent. While the text -\ is being done that is its error, 18; once it has been finished with -\ there is no one to tell: it is let go and counted in (LOST). +\ WITH NO ONE ON THE PORT -- when it waits for room, or when it comes to +\ put -- what was printed cannot be sent. While the text is being done +\ that is its error, 18. Once it has been finished with, what it printed +\ is let go and counted in (LOST), and the text is said to have ended in +\ an error: (LINE-STATUS), which is the word then sent of how it ended. : (FLUSH-OUT) (OUT^) a! @ (OUT) xor if NONE drop 0 0 0 drop drop drop 0 push 0 push pop drop pop drop @@ -375,16 +399,17 @@ header CMOVE SAME: drop 8 + NEED: (W-NEED) a! ! (PRINT-PORT) a! @ (W-PORT) a! ! (WAIT-ROOM) if ROOM - drop (QUIET) a! @ if TELL - LOSE: drop (OUT) (OUT^) a! ! (LOST) a! @ 1 + ! ; + NOONE: drop (QUIET) a! @ if TELL + drop (OUT) (OUT^) a! ! (LOST) a! @ 1 + ! 2 (LINE-STATUS) a! ! ; TELL: drop NODE-ERROR b! 18 !b ; ROOM: drop (PRINT-TO) a! @ 2 (HDR) + (OUT^) a! @ a! 0 !+ 0 !+ 0 ! (OUT^) a! @ (OUT) - dup (OUT-HDR)+6 a! ! 3 + 2/ 2/ -1 + push (OUT-TEXT) pop (OUT) a! FOR @+ @+ 8* + @+ 8* 8* + @+ 8* 8* 8* + over b! !b 1 + NEXT drop - (OUT-TEXT) (PRINT-PORT) a! @ (PUT-ON) if SENT jump LOSE + (OUT-TEXT) (PRINT-PORT) a! @ (PUT-ON) if SENT jump NOONE SENT: drop (OUT) (OUT^) a! ! ; NONE: drop ; diff --git a/v4/capsule/quit.v4 b/v4/capsule/quit.v4 index a1058e47..0f9f7dc5 100644 --- a/v4/capsule/quit.v4 +++ b/v4/capsule/quit.v4 @@ -80,13 +80,17 @@ macro R-CLEAR RSTACK-DEPTH b! a !b endmacro OK: drop 1 jump (FINISH) GO: drop 0 jump (FINISH) -\ ( -- flag ) A GONE has been taken: its seven words are in (IN) and the -\ node it is about in (ONE). It is believed only if this node's centre -\ sent it (MESH.md 7b.4): the way to the node is then forgotten, and the -\ flag is -1. Otherwise nothing is done, and 0. +\ ( -- flag ) A GONE has been taken: its seven words are in (IN), the +\ node it is about in (ONE), and (S-WIRE) is the wire it came by. It is +\ believed only if it came by the port that leads to this node's centre +\ and says it is from the centre (MESH.md 7b.4): the way to the node is +\ then forgotten, and the flag is -1. Otherwise nothing is done, and 0. +\ (As first built only what it said it was from was looked at, and one +\ that came by another port was believed.) : (GONE?) - (CENTRE) if NOT (IN)+1 a! @ xor if YES drop 0 ; + (CENTRE) if NOT (IN)+1 a! @ xor if FROM drop 0 ; NOT: ; + FROM: drop (ROUTE-DEFAULT) a! @ (PORT) - (S-WIRE) a! @ xor if YES drop 0 ; YES: drop (ONE) a! @ NO-ROUTE -1 ; \ ( -- ) WAITING (MESH.md 7d.4). A node with nothing to do passes on @@ -94,17 +98,25 @@ macro R-CLEAR RSTACK-DEPTH b! a !b endmacro \ come for itself, the lowest port first and each wire from its front: \ text is taken, into TIB, and done: (LINE), and then (FINISH) says \ how it ended. But not unless the wire its answer goes by -- -\ the way to whoever sent it, or with none known the wire it -\ came on -- has room for the answer, 8 cells. If it has not, -\ the text stays on its wire and the node looks further; that -\ room is kept from then until the answer has gone. +\ the way to whoever sent it; with none known, or no one on +\ that port, the wire it came on -- has room for the answer, +\ 8 cells. If it has not, the text stays on its wire and the +\ node looks further: there may be several such, on wires +\ whose answers go different ways, and each is begun when +\ its own has the room. That room is kept from then until +\ the answer has gone. \ a NACK is taken and counted in (REFUSED) \ a GONE is taken, and believed if this node's centre sent it -\ any other is dropped: an answer no one waits for, or what is not as +\ any other is dropped, and counted in (LOST): an answer no one waits +\ for, what a node printed that came here, or what is not as \ long as its type says. +\ A TAKE THAT DOES NOT TAKE -- the wire was cut after the message was +\ looked at -- leaves nothing to act on: no text is done and nothing is +\ counted or believed for it. \ With nothing to begin it sleeps, executing nothing, until a message -\ comes; or, if text is waiting for room for its answer, until that wire -\ has the room or a message comes. Then it does all of this again. +\ comes; or, if text is waiting for room for its answer, or (SERVE) left a +\ message for want of room, until there is room on any wire that leads +\ from it or a message comes. Then it does all of this again. \ It has fetched which wires have a message before it looks at any, and \ sleeps after: a message that comes while it looks wakes it at once, and \ one it left on a wire does not keep it awake (MESH.md 7d.3). @@ -117,23 +129,29 @@ macro R-CLEAR RSTACK-DEPTH b! a !b endmacro (SERVE) (MINE0) M: (MINE) if REST drop (IN)+2 a! @ -1 + if TEXT -3 + if NACK -1 + if GONE - OTHER: drop 4 (DO) drop jump M \ let go + OTHER: drop 4 (DO) drop (LOST) a! @ 1 + ! jump M \ let go, and counted NACK: drop (IN)+6 a! @ -4 + if N1 jump OTHER - N1: drop (TAKE1) (REFUSED) a! @ 1 + ! jump M \ a message of this node's was refused: counted + N1: drop (TAKE1) if N2 drop jump M \ not taken: nothing is done for it + N2: drop (REFUSED) a! @ 1 + ! jump M \ a message of this node's was refused: counted GONE: drop (IN)+6 a! @ -4 + if G1 jump OTHER - G1: drop (TAKE1) (GONE?) drop jump M + G1: drop (TAKE1) if G2 drop jump M + G2: drop (GONE?) drop jump M TEXT: drop (IN)+1 a! @ (WAY) -if T1 drop (S-WIRE) a! @ \ the way back; with none known, the wire it came on T1: (DONE-PORT) a! ! - 8 (DONE-PORT) a! @ (ROOM?) -1 + if FULL drop jump START + 8 (DONE-PORT) a! @ (ROOM?) if START -1 + if FULL + drop (S-WIRE) a! @ (DONE-PORT) a! ! \ no one is on that port: back by the wire it came on, + 8 (S-WIRE) a! @ (ROOM?) -1 + if FULL jump START \ if that has the room -- or has no one on it either FULL: drop (DEFER) a! @ -if LATER drop (DONE-PORT) a! @ (DEFER) a! ! (SKIP) jump M LATER: drop (SKIP) jump M REST: drop - (DEFER) a! @ -if ROOM drop 8 (DO) drop jump L + (DEFER) a! @ -if ROOM drop (SLEEP) jump L ROOM: (WIRE!) 8 (WIRE-A) b! !b 9 (DO) drop jump L - START: + START: drop (S-WIRE) a! @ dup (REPLY) a! ! (WIRE!) - (MSG) (WIRE-A) b! !b TIB 2/ 2/ (WIRE-B) b! !b 2 (DO) drop \ its seven words, and its text + (MSG) (WIRE-A) b! !b TIB 2/ 2/ (WIRE-B) b! !b 2 (DO) \ its seven words, and its text + if TAKEN drop jump L \ not taken: the wire was cut since it was looked at + TAKEN: drop 0 (MSG)+6 a! @ TIB + C! \ a zero after the text (MSG)+6 a! @ SPAN a! ! (DONE-PORT) a! @ (KEEP) a! ! \ the room for its answer is kept from here @@ -230,7 +248,9 @@ header NO-ROUTES \ (REFUSED), as a refusal that came back would be, and is error 19, Message \ refused; with no one on the port, error 18. Nothing is on a \ wire until the put, the last thing done. More characters than a message -\ carries, or fewer than none, is error 12. +\ carries, or fewer than none, is error 12. What lies after the text in +\ this node's memory does not go with it: the last word is made zero past +\ the last character. : (SEND-ON) push 1 (HDR) -if POS @@ -242,9 +262,16 @@ header NO-ROUTES 3 + 2/ 2/ if NONE -1 + FOR dup C@ over 1 + C@ 8* + over 2 + C@ 8* 8* + over 3 + C@ 8* 8* 8* + (SEND^) b! @b a! !+ a !b 4 + NEXT - drop jump PUT - NONE: drop drop - PUT: (SEND-TEXT) pop (PUT-ON) + drop + (OUT-HDR)+6 a! @ 3 and if PUT \ the last word: nothing in it past the last character + (SEND^) b! @b -1 + a! + -1 + if M1 -1 + if M2 + drop @ 16777215 and ! jump PUT0 + M2: drop @ 65535 and ! jump PUT0 + M1: drop @ 255 and ! jump PUT0 + NONE: drop + PUT: drop + PUT0: (SEND-TEXT) pop (PUT-ON) if SENT -1 + if FULL drop NODE-ERROR b! 18 !b ; FULL: drop (REFUSED) a! @ 1 + ! NODE-ERROR b! 19 !b ; SENT: drop ; @@ -273,6 +300,12 @@ header SEND-ON (ROUTE-DEFAULT) a! @ if NONE (PORT) - (NEAR) + a! @ ; NONE: ; +\ ( -- n ) the number of this node's console: the one it was told of, or +\ with none told of, whoever sent the text it is doing +: (A-CONSOLE) + (CONSOLE) a! @ if C0 ; + C0: drop (MSG)+1 a! @ ; + \ ( what -- how | ) the wait is over: 1 the answer has come, and its \ word, in (ONE), is left; 2 refused, error 19; 3 the node is gone, error \ 20; 4 a line from the console, error 21 @@ -308,8 +341,8 @@ header SEND-ON \ has left too little ends "Stack overflow" with nothing taken off a wire. header AWAIT : AWAIT - (AWAIT-FROM) a! ! 0 (A-SELF) a! ! - (ROOM) + (ONE) a! ! (ROOM) \ if this faults, no one is waited for: + (ONE) a! @ (AWAIT-FROM) a! ! 0 (A-SELF) a! ! \ (AWAIT-FROM) is set only when the stack has been tried L: (SERVE) (A-SELF) a! @ if SCAN drop 2 jump (A-END) \ this node's own message for that node came back to it SCAN: drop (MINE0) @@ -320,17 +353,20 @@ header AWAIT BREAK: drop 4 jump (A-END) ANSWER: drop (IN)+1 a! @ (AWAIT-FROM) a! @ xor if A1 jump KEEP A1: drop (IN)+6 a! @ -4 + if A2 jump KEEP - A2: drop (TAKE1) 1 jump (A-END) + A2: drop (TAKE1) if A3 drop jump M \ not taken: nothing is done for it + A3: drop 1 jump (A-END) NACK: drop (IN)+6 a! @ -4 + if N1 jump KEEP - N1: drop (TAKE1) (ONE) a! @ (AWAIT-FROM) a! @ xor if N2 \ about the node waited for? + N1: drop (TAKE1) if N0 drop jump M + N0: drop (ONE) a! @ (AWAIT-FROM) a! @ xor if N2 \ about the node waited for? drop (REFUSED) a! @ 1 + ! jump M \ another: counted N2: drop 2 jump (A-END) GONE: drop (IN)+6 a! @ -4 + if G1 jump KEEP - G1: drop (TAKE1) (GONE?) if M0 \ only its centre is believed + G1: drop (TAKE1) if G0 drop jump M + G0: drop (GONE?) if M0 \ only its centre is believed drop (ONE) a! @ (AWAIT-FROM) a! @ xor if G2 drop jump M G2: drop 3 jump (A-END) M0: drop jump M - SLEEP: drop 8 (DO) drop jump L + SLEEP: drop (SLEEP) jump L \ ( w port -- ) write a cell to the port, by its number. It waits until \ the neighbour has taken it. This is how a node is sent a capsule of F18 diff --git a/v4/include/v4/fabric.h b/v4/include/v4/fabric.h index 12ae10c5..f321d054 100644 --- a/v4/include/v4/fabric.h +++ b/v4/include/v4/fabric.h @@ -147,10 +147,18 @@ void v4_fabric_wake(v4_fabric *f, unsigned id); * the node is let go: put, take, move and drop and the rest (MESH.md 7d.3). * Sleep (8) and sleep for room (9) are the two that wait: the node stays * blocked, executing nothing, until a message has come to one of its wires - * since it last fetched which wires have one -- or, for 9, until its wire - * has the room (a wire that can never have the room answers 1 at once). A message has "come" when a put or a move or a device put - * put it on the back of a queue; fetching Have clears that for all of the - * node's wires. + * since it last fetched which wires have one. A message has "come" when a + * put or a move or a device put put it on the back of a queue; fetching Have + * clears that for all of the node's wires. + * SLEEP FOR ROOM (9) answers 0 at once if the wire it names has the room, + * and 1 at once if it can never have it. Otherwise the node sleeps until a + * message has come, as for 8, or a message has come off ANY of the wires + * that lead from it -- taken, moved on or dropped by whoever reads that wire + * -- since it was last woken that way; then it is answered 0 if the wire it + * named now has the room and 1 if not, and it asks which wire has what + * (10). A node may be waiting to put on several wires at once and can name + * only one. A wake-up that finds nothing to do is answered by asking + * again. * * Time passes: every node that is awake and not blocked executes one * instruction word; then every write with a reader waiting on the other end diff --git a/v4/include/v4/wire.h b/v4/include/v4/wire.h index 859d0b62..ad588b2f 100644 --- a/v4/include/v4/wire.h +++ b/v4/include/v4/wire.h @@ -39,6 +39,7 @@ typedef struct { unsigned used; /* how many cells they take */ unsigned mark; /* how many cells from the front the reader's mark is; == used: at no message */ unsigned arrived; /* not 0: a message has come since the reader last asked which wires have one */ + unsigned freed; /* not 0: a message has come off since the writer was last woken from a sleep for room */ } v4_wire_queue; enum { V4_WIRE_DONE = 0, V4_WIRE_NO_ROOM = 1, V4_WIRE_NO_ONE = 2, V4_WIRE_NO_MESSAGE = 3, V4_WIRE_NOT_A_MESSAGE = 4 }; diff --git a/v4/src/fabric.c b/v4/src/fabric.c index 0c167ad3..74028a58 100644 --- a/v4/src/fabric.c +++ b/v4/src/fabric.c @@ -316,6 +316,17 @@ static int arrived(const v4_fabric *f, unsigned id) return 0; } +/* has a message come off any of the wires that lead from the node since it was last woken by that */ +static int freed(const v4_fabric *f, unsigned id) +{ + unsigned k; + for (k = 0; k < V4_PORTS; k++) { + const v4_wire_queue *q = f->place[id].wire[k].tx; + if (q && q->freed) return 1; + } + return 0; +} + /* Node `id` is doing an operation. Returns 1 and puts how it went in * *how, or returns 0: it waits (8 and 9 only). Nothing is copied, put or * taken unless the answer is 0 -- done. */ @@ -372,7 +383,9 @@ static int operate(v4_fabric *f, unsigned id, v4_cell *how) if (!tx) { *how = V4_WIRE_NO_ONE; break; } if (a < 0 || a > (v4_cell)V4_WIRE_CELLS) { *how = V4_WIRE_NO_ROOM; break; } /* no waiting can make that room */ if (v4_wire_room(tx, (unsigned)a) == V4_WIRE_DONE) { *how = V4_WIRE_DONE; break; } - if (!arrived(f, id)) return 0; + if (!arrived(f, id) && !freed(f, id)) return 0; + for (cells = 0; cells < V4_PORTS; cells++) /* woken: the room that has come on any of its wires is seen */ + if (f->place[id].wire[cells].tx) f->place[id].wire[cells].tx->freed = 0; *how = V4_WIRE_NO_ROOM; break; case 10: /* room */ diff --git a/v4/src/wire.c b/v4/src/wire.c index d524aa1d..7903077d 100644 --- a/v4/src/wire.c +++ b/v4/src/wire.c @@ -13,6 +13,7 @@ void v4_wire_reset(v4_wire_queue *q) q->used = 0; q->mark = 0; q->arrived = 0; + q->freed = 0; } unsigned v4_wire_cells(v4_cell length) @@ -89,6 +90,7 @@ static void remove_marked(v4_wire_queue *q) for (i = q->mark; i + n < q->used; i++) q->cell[slot(q, i)] = q->cell[slot(q, i + n)]; } q->used -= n; + q->freed = 1; } int v4_wire_take(v4_wire_queue *q, v4_cell *header7, v4_cell *text, unsigned text_cap) diff --git a/v4/system/boot.c b/v4/system/boot.c index 49ff118b..124e18ce 100644 --- a/v4/system/boot.c +++ b/v4/system/boot.c @@ -93,15 +93,18 @@ static uint32_t word_count(const v4_node *n, const v4_image *im) /* What the kernel keeps of the node's block window (blocks.h). */ static v4_blocks boot_blocks; -/* THE WIRES (docs/v4.0.0/MESH.md 7d.5). The lone node has no fabric: the - * boot keeps the queues of its two wires, a queue each way (wire.h), and - * does what the node asks of them, as a fabric does for a node of a mesh - * (fabric.c). Every other port has no one on it. The kernel's wire has - * its queues as any wire has; the kernel puts nothing on it and takes - * nothing off: its requests are words, not messages. */ -static v4_wire_queue boot_wire[2][2]; /* by port: [0] to the node, [1] from it */ -#define TO_NODE(port) (&boot_wire[port][0]) -#define FROM_NODE(port) (&boot_wire[port][1]) +/* THE WIRE (docs/v4.0.0/MESH.md 7d.5). The lone node has no fabric: the + * boot keeps the two queues of its console's wire, one each way (wire.h), + * and does what the node asks of them, as a fabric does for a node of a + * mesh (fabric.c). + * THE KERNEL'S PORT HAS NO QUEUE (ruled 2026-10-08). The kernel is asked by + * words written to port 0 and takes no messages: a message for that port + * -- SEND-ON to it, or what a text prints when the way to its console has + * been set there -- is refused at once, as on any port with no one to take + * messages: the node's error 18, "No one on that port". */ +static v4_wire_queue boot_wire[2]; /* the console's: [0] to the node, [1] from it */ +#define TO_NODE (&boot_wire[0]) +#define FROM_NODE (&boot_wire[1]) /* 1 if `count` words from `addr` are the node's memory and not its ports * or the addresses after them: they may be copied to and from. */ @@ -113,20 +116,17 @@ static int span_ok(const v4_node *n, v4_cell addr, unsigned count) return 1; } -static int boot_arrived(void) -{ - return TO_NODE(KERNEL_PORT)->arrived || TO_NODE(CONSOLE_PORT)->arrived; -} /* The node is blocked at an operation on a wire (node.h, v4_node_doing): * do it, whole, and let the node go on; the operations and their answers * are MESH.md 7d.3's, as fabric.c does them. Returns 0 if the node sleeps * on: it asked to sleep (8), and no message has come since it last asked - * which wires have one; or for room (9) that is not there. */ + * which wires have one; or for room (9) that is not there, and nothing + * has come off its wire either. */ static int boot_do(const v4_boot *bt) { v4_node *n = bt->n; - v4_wire_queue *rx = n->wire <= CONSOLE_PORT ? TO_NODE(n->wire) : 0, *tx = n->wire <= CONSOLE_PORT ? FROM_NODE(n->wire) : 0; + v4_wire_queue *rx = n->wire == CONSOLE_PORT ? TO_NODE : 0, *tx = n->wire == CONSOLE_PORT ? FROM_NODE : 0; v4_cell a = n->wire_a, b = n->wire_b, h[V4_WIRE_HEADER], how = V4_WIRE_NO_ONE; unsigned cells; @@ -146,7 +146,7 @@ static int boot_do(const v4_boot *bt) case 3: /* move */ if (!rx) break; if (rx->mark >= rx->used) how = V4_WIRE_NO_MESSAGE; - else how = a >= 0 && a <= (v4_cell)CONSOLE_PORT ? v4_wire_move(rx, FROM_NODE((unsigned)a)) : V4_WIRE_NO_ONE; + else how = a == (v4_cell)CONSOLE_PORT ? v4_wire_move(rx, FROM_NODE) : V4_WIRE_NO_ONE; break; case 4: /* drop */ if (rx) how = v4_wire_drop(rx); @@ -164,14 +164,15 @@ static int boot_do(const v4_boot *bt) if (rx) how = v4_wire_next(rx); break; case 8: /* sleep */ - if (!boot_arrived()) return 0; + if (!TO_NODE->arrived) return 0; how = V4_WIRE_DONE; break; case 9: /* sleep for room */ if (!tx) break; if (a < 0 || a > (v4_cell)V4_WIRE_CELLS) { how = V4_WIRE_NO_ROOM; break; } if (v4_wire_room(tx, (unsigned)a) == V4_WIRE_DONE) { how = V4_WIRE_DONE; break; } - if (!boot_arrived()) return 0; + if (!TO_NODE->arrived && !FROM_NODE->freed) return 0; /* a message has come, or the console has taken one */ + FROM_NODE->freed = 0; how = V4_WIRE_NO_ROOM; break; case 10: /* room */ @@ -192,11 +193,10 @@ static int boot_do(const v4_boot *bt) static void boot_exec(const v4_boot *b) { v4_node *n = b->n; - v4_node_have(n, (TO_NODE(KERNEL_PORT)->used ? 1u << KERNEL_PORT : 0u) | (TO_NODE(CONSOLE_PORT)->used ? 1u << CONSOLE_PORT : 0u)); + v4_node_have(n, TO_NODE->used ? 1u << CONSOLE_PORT : 0u); (void)v4_exec_step_word(n, b->es, b->h); if (n->have_fetched) { - TO_NODE(KERNEL_PORT)->arrived = 0; - TO_NODE(CONSOLE_PORT)->arrived = 0; + TO_NODE->arrived = 0; n->have_fetched = 0; } } @@ -225,14 +225,14 @@ static int run_line(const v4_boot *b, const char *text, unsigned len, char *keep if (text) { /* with none, the node has a line waiting on its wire (boot.h) */ if (!v4_message_text(&out, 0, CONSOLE_ID, V4_MSG_TEXT, text, len)) return V4_BOOT_LINE_TOO_LONG; - if (v4_wire_put(TO_NODE(CONSOLE_PORT), out.word, out.word + V4_MSG_HEADER) != V4_WIRE_DONE) return V4_BOOT_LINE_STOPPED; + if (v4_wire_put(TO_NODE, out.word, out.word + V4_MSG_HEADER) != V4_WIRE_DONE) return V4_BOOT_LINE_STOPPED; } for (;;) { int awake = boot_step(b); if (n->stopped) return V4_BOOT_LINE_STOPPED; - v4_wire_first(FROM_NODE(CONSOLE_PORT)); /* what the node has sent its console, in the order it was sent */ - while (v4_wire_take(FROM_NODE(CONSOLE_PORT), in, in_text, V4_MSG_MAX_CHARS / 4u) == V4_WIRE_DONE) { + v4_wire_first(FROM_NODE); /* what the node has sent its console, in the order it was sent */ + while (v4_wire_take(FROM_NODE, in, in_text, V4_MSG_MAX_CHARS / 4u) == V4_WIRE_DONE) { if (in[2] == V4_MSG_OUTPUT) { for (i = 0; i < (unsigned)in[6]; i++) { char c = (char)(((v4_ucell)in_text[i / 4u] >> (8u * (i % 4u))) & 0xFFu); @@ -248,7 +248,10 @@ static int run_line(const v4_boot *b, const char *text, unsigned len, char *keep } } if (!awake) { - if (n->do_op == 9) continue; /* waiting for room, which the console has just made */ + if (n->do_op == 9) { /* waiting for room, which the console has just made */ + if (++steps > STEP_LIMIT) return V4_BOOT_LINE_STOPPED; + continue; + } return V4_BOOT_LINE_WAITING; /* waiting for an answer: only the console can end that */ } @@ -474,9 +477,8 @@ int v4_boot_run(const v4_boot *b) say(b, "V4: the nucleus is not for this build of the engine\nPARITY:FAIL\nPOST: FAILED\n"); return 0; } - /* its two wires, the kernel's and the console's, with nothing on them */ - v4_wire_reset(TO_NODE(KERNEL_PORT)); v4_wire_reset(FROM_NODE(KERNEL_PORT)); - v4_wire_reset(TO_NODE(CONSOLE_PORT)); v4_wire_reset(FROM_NODE(CONSOLE_PORT)); + /* its console's wire, with nothing on it */ + v4_wire_reset(TO_NODE); v4_wire_reset(FROM_NODE); v4_blocks_init(&boot_blocks, b->im->block_window); if (!load_nucleus(b)) { say(b, "PARITY:FAIL\nPOST: FAILED\n"); return 0; } diff --git a/v4/tests/host_map.h b/v4/tests/host_map.h index aaffb1f3..df9bce4a 100644 --- a/v4/tests/host_map.h +++ b/v4/tests/host_map.h @@ -95,12 +95,17 @@ #define OUT_HDR (BUF0_W - 112) /* the seven words of a message this node is putting on a wire */ #define W_NEED (BUF0_W - 105) /* the wait for room: how many cells, */ #define W_PORT (BUF0_W - 104) /* and on which port's wire */ -#define P_WAY (BUF0_W - 103) /* the port a message being passed on goes by */ +#define P_WAY (BUF0_W - 103) /* the port a message being passed on goes by, */ +#define P_NEED (BUF0_W - 100) /* and the cells it needs there */ +#define H_PORT (BUF0_W - 99) /* the port of the first message left on its wire for want of room on this pass; -1: none */ +#define H_NEED (BUF0_W - 98) /* the cells it needs there */ +#define H_MASK (BUF0_W - 97) /* and a bit for each port something was left for */ #define A_SELF (BUF0_W - 102) /* 1: a message of this node's own, for the node it waits on, came back and was let go here */ #define SEND_PTR (BUF0_W - 101) /* where the next word of a text being made ready to send goes */ #define OUT_TEXT (OUT_W - 64) /* what was printed, four characters to a cell, as it goes on the wire: 64 cells */ #define SEND_TEXT (OUT_W - 400) /* the text of a message SEND is putting, four characters to a cell: 256 cells */ -/* FREE, plain memory: BUF0_W - 8; - 32 .. - 21; - 100 .. - 97; - 115 .. - 113; and OUT_W - 144 .. OUT_W - 65. They and +/* FREE, plain memory: BUF0_W - 8; - 32 .. - 21; - 122 .. - 113; BUF0_W - 144 .. - 141 (what a flush makes zero after + * a full output buffer); and OUT_W - 144 .. OUT_W - 65. They and * the cells above held the messages waiting, the refusals owed and what went with them (MESH.md 7a, 7b), which a * node no longer keeps. */ #define AWAIT_FROM (BUF0_W - 39) /* the node whose word of how text ended is waited for */ @@ -228,6 +233,10 @@ static int host_load(v4_text *tx, v4_node *n, const char *const *files, unsigned v4_text_constant(tx, "(W-NEED)", W_NEED); v4_text_constant(tx, "(W-PORT)", W_PORT); v4_text_constant(tx, "(P-WAY)", P_WAY); + v4_text_constant(tx, "(P-NEED)", P_NEED); + v4_text_constant(tx, "(H-PORT)", H_PORT); + v4_text_constant(tx, "(H-NEED)", H_NEED); + v4_text_constant(tx, "(H-MASK)", H_MASK); v4_text_constant(tx, "(A-SELF)", A_SELF); v4_text_constant(tx, "(SEND^)", SEND_PTR); v4_text_constant(tx, "(OUT-TEXT)", OUT_TEXT); diff --git a/v4/tests/test_fabric.c b/v4/tests/test_fabric.c index f59db470..39339ae3 100644 --- a/v4/tests/test_fabric.c +++ b/v4/tests/test_fabric.c @@ -743,9 +743,11 @@ int main(void) CHECK(ask(a, 0, 250, 0, 9) == -99, "asleep for 250"); v4_wire_first(f.place[b].wire[3].rx); CHECK(ask(b, 3, 400, 420, 4) == 0 && V4_WIRE_CELLS - f.place[a].wire[0].tx->used == 200, "the reader drops the small one: 200 are free"); + CHECK(!ND(a)->n.doing && ND(a)->n.how == 1, "the sleeper, which wants 250, is woken, and told 1: a wire of its own has gained room, and not enough"); + CHECK(ask(a, 0, 250, 0, 9) == -99, "it asks again, and sleeps"); for (i = 0; i < 50; i++) (void)v4_fabric_step(&f); - CHECK(ND(a)->n.doing, "and the sleeper, which wants 250, is not woken by a take that did not give it room"); - CHECK(ask(b, 3, 400, 420, 4) == 0 && !ND(a)->n.doing && ND(a)->n.how == 0, "the next take does, with 0"); + CHECK(ND(a)->n.doing, "and sleeps on while nothing changes"); + CHECK(ask(b, 3, 400, 420, 4) == 0 && !ND(a)->n.doing && ND(a)->n.how == 0, "the next take gives it the room: 0"); /* woken by a message coming instead */ v4_wire_reset(f.place[a].wire[0].tx); fill(f.place[a].wire[0].tx, 100); @@ -757,6 +759,51 @@ int main(void) (void)v4_fabric_step(&f); CHECK(!ND(a)->n.doing && ND(a)->n.how == 1 && f.place[a].wire[0].tx->used + 200 > V4_WIRE_CELLS, "it wakes, and how it went says 1: no room, but a message"); + /* SLEEP FOR ROOM IS WOKEN BY ROOM ON ANY OF THE NODE'S WIRES (MESH.md 7d.3): a node may be waiting to + * put on more than one, and names only one */ + v4_fabric_init(&f, more_places, 8); + v4_fabric_queues(&f, qpool, 8); + a = (unsigned)v4_fabric_add(&f, &pool[0], PB); + b = (unsigned)v4_fabric_add(&f, &pool[1], PB); + c = (unsigned)v4_fabric_add(&f, &pool[2], PB); + CHECK(v4_fabric_wire(&f, a, 0, b, 3) && v4_fabric_wire(&f, a, 1, c, 2), "a node with two wires"); + fill(f.place[a].wire[0].tx, 0); + fill(f.place[a].wire[1].tx, 0); + (void)v4_node_fetch(&ND(a)->n, P_HAVE); + (void)v4_fabric_step(&f); + CHECK(ask(a, 0, 100, 0, 9) == -99, "both full: asleep for room on wire 0"); + for (i = 0; i < 100; i++) (void)v4_fabric_step(&f); + CHECK(ND(a)->n.doing, "and it sleeps on while nothing changes"); + v4_wire_first(f.place[c].wire[2].rx); + CHECK(ask(c, 2, 400, 420, 4) == 0, "the reader of wire 1 drops a message"); + CHECK(!ND(a)->n.doing && ND(a)->n.how == 1 && f.place[a].wire[0].tx->used == V4_WIRE_CELLS, "it wakes, told 1: the wire it named has no room, but something has changed"); + CHECK(ask(a, 0, 100, 0, 10) == 1 && ask(a, 1, 100, 0, 10) == 0, "it asks which: wire 1 has the room"); + CHECK(ask(a, 0, 100, 0, 9) == -99, "asleep for room on wire 0 again: that room on wire 1 was seen"); + for (i = 0; i < 100; i++) (void)v4_fabric_step(&f); + CHECK(ND(a)->n.doing, "and it is not woken twice for it"); + /* the room comes in the very step the node goes to sleep: the reader's take is done before the sleep is looked at */ + v4_node_store(&ND(c)->n, P_WIRE, 2); v4_node_store(&ND(c)->n, P_DO, 4); + ND(a)->n.doing = 0; + v4_node_store(&ND(a)->n, P_WIRE, 0); v4_node_store(&ND(a)->n, P_A, 100); v4_node_store(&ND(a)->n, P_DO, 9); + (void)v4_fabric_step(&f); + CHECK(!ND(c)->n.doing && !ND(a)->n.doing && ND(a)->n.how == 1, "room that comes in the step the node goes to sleep wakes it: no wake-up is lost"); + /* a device's take is a take */ + { + static const v4_device dev = { 0, 0, 0, 0 }; + v4_cell hh[7], tt[V4_WIRE_CELLS / 4]; + CHECK(v4_fabric_wire_device(&f, a, 4, &dev), "a device on a third wire"); + fill(f.place[a].wire[4].tx, 0); + CHECK(ask(a, 0, 100, 0, 9) == -99, "asleep for room on wire 0"); + CHECK(v4_fabric_device_take(&f, a, 4, hh, tt, V4_WIRE_CELLS / 4) == V4_WIRE_DONE, "the device takes a message between steps"); + (void)v4_fabric_step(&f); + CHECK(!ND(a)->n.doing && ND(a)->n.how == 1, "and that wakes it"); + } + /* a plain sleep is not woken by room */ + (void)v4_node_fetch(&ND(a)->n, P_HAVE); + (void)v4_fabric_step(&f); + CHECK(ask(a, 0, 0, 0, 8) == -99, "asleep, for a message"); + CHECK(ask(c, 2, 400, 420, 4) == 0 && ND(a)->n.doing, "room on a wire does not wake a node that sleeps for a message only"); + /* move from one of a node's wires to another */ v4_fabric_init(&f, more_places, 8); v4_fabric_queues(&f, qpool, 8); diff --git a/v4/tests/test_host_depth.c b/v4/tests/test_host_depth.c index bb662fe9..f56b3845 100644 --- a/v4/tests/test_host_depth.c +++ b/v4/tests/test_host_depth.c @@ -7,11 +7,19 @@ * attempt at step 6d was tested on a shallow stack with every node there, * and hung on a deep one. Here node 11 does each thing a node does with a * message -- print, SEND, SEND with no way, AWAIT answered, AWAIT ended by a - * GONE, pass a message on while its own text waits for room -- with its data - * stack holding 20 to 32 values, and from 20 to 32 calls deep. After each, - * every node must answer, and node 11's stack must be as deep as the text - * made it, or its text must have ended in an error it reported. It may - * refuse; it may not hang, and it may not swallow a line. + * GONE, pass a message on while its own text waits for room, AWAIT broken + * by a line from the console, refuse a message while its own text waits for + * room, send a GONE -- with its data stack holding 20 to 32 values, and from + * 20 to 33 calls deep. + * + * EVERY RUN ENDS ONE OF TWO WAYS, and which is checked: the path WORKS, all + * of it, as it does on a shallow stack; or node 11's text ends in "Stack + * overflow" or "Return stack overflow" and nothing else -- with nothing + * half done: no message lost that was owed, no line swallowed. Where the + * stack is shallow enough it must work. And after each, every node must + * answer. "It came back" is not enough: with the nucleus's tries of the + * stack taken out -- (ROOM-SERVE), (ROOM) -- this test fails (it was + * mutated to see). */ #include "v4/image.h" #include "v4/fabric.h" @@ -173,16 +181,32 @@ static int row(void) } /* ---- the sweep ---- */ -static unsigned runs, bad; +static unsigned runs, bad, worked, refused_clean; -/* what the last path watches for: node 11 asleep for room, and, while it is, a message it has passed on to - * the wire to node 12 */ +/* what the paths that wait for room watch for: node 11 asleep for room, and, while it is, a message it has + * passed on to the wire to node 12 */ static unsigned saw_room_wait, saw_passed; static void watch_mid(void) { if (pool[mid].n.doing && pool[mid].n.do_op == 9) { saw_room_wait++; if (places[mid].wire[3].tx && places[mid].wire[3].tx->used > 0) saw_passed++; } } +/* A MESSAGE THAT HAS BEEN PASSED ON AS OFTEN AS IT MAY BE comes to node 11 from node 12, for a node 99: its + * fourth word is 1. No row of three can make one -- it takes sixteen nodes, or a ring -- so it is put on the + * wire here, once, the first time node 11 is asleep for room. Node 11 refuses it without looking for its way: + * the one refusal in which a message is dropped before the node's deepest use of its stacks. */ +static int spent_put; +static void put_spent(void) +{ + static const v4_cell hd[V4_WIRE_HEADER] = { 99, 12, V4_MSG_TEXT, 1, 0, 0, 4 }, text[1] = { 0x31 }; + if (!spent_put && places[mid].wire[3].rx) spent_put = v4_wire_put(places[mid].wire[3].rx, hd, text) == V4_WIRE_DONE; +} +static void watch_mid_spent(void) +{ + watch_mid(); + if (saw_room_wait) put_spent(); +} + /* words that push 1, 2, 4, 8 and 16 values and call nothing while they do */ static void fill(char *text, size_t cap, unsigned count) { @@ -193,17 +217,27 @@ static void fill(char *text, size_t cap, unsigned count) for (bit = 8; bit; bit >>= 1) if (count >= bit) { at += (size_t)snprintf(text + at, cap - at, "P%u ", bit); count -= bit; } } +#define PATHS 10u +static const char *const path_name[PATHS] = { "a text that prints", "SEND", "SEND with no way", "AWAIT answered", "AWAIT ended by a GONE", "passing on while its text waits for room", + "AWAIT broken by a line from the console", "a refusal while its text waits for room", "GONE", + "a spent message refused while its text waits" }; +static char numbers[8192]; /* what 1200 0 DO I . LOOP prints */ + /* A row made afresh for one path; node 11 is given the word ACT that takes it, and the words that fill the * stack and that call each other. */ static int made(unsigned path) { - static const char *const act[] = { + static const char *const act[PATHS] = { ": ACT 600 0 DO 65 EMIT LOOP ;", ": ACT S\" 1 DROP\" 12 SEND ;", ": ACT S\" 1 DROP\" 99 SEND ;", ": ACT S\" 1 DROP\" 12 SEND 12 AWAIT DROP ;", ": ACT 12 AWAIT DROP ;", ": ACT 1200 0 DO I . LOOP ;", + ": ACT 12 AWAIT DROP ;", + ": ACT 1200 0 DO I . LOOP ;", + ": ACT 44 12 GONE ;", + ": ACT 1200 0 DO I . LOOP ;", }; char line[64]; unsigned k; @@ -216,30 +250,60 @@ static int made(unsigned path) if (path == 2 && !told(11, "NO-ROUTES 1 2 ROUTE 10 2 ROUTE 12 3 ROUTE")) return 0; /* no way for any node it has not been told of */ if (path == 4 && !told(12, ": LL 400000 0 DO LOOP ;")) return 0; if (path == 5 && !told(10, ": SLOW 500000 0 DO LOOP ; : BUSY SLOW S\" 1 DROP\" 12 SEND SLOW ;")) return 0; + if (path == 9 && !told(10, ": SLOW 500000 0 DO LOOP ; : BUSY SLOW SLOW ;")) return 0; + /* 320 characters: more than fits beside what node 11 itself is waiting to print */ + if (path == 7 && !(told(10, ": SLOW 500000 0 DO LOOP ; : BUSY SLOW SLOW ;") && told(12, ": LATE 250000 0 DO LOOP PAD 320 BLANK PAD 320 99 SEND ;"))) return 0; return 1; } -/* Node 11 takes the path with `text`; `values` is how many values the text leaves on its stack if it completes. */ -static int one(unsigned path, const char *what, unsigned depth, const char *text, unsigned values) +/* 1 if `s` is `lead`, then some of `body` from its start, then exactly `end`. */ +static int is_cut_short(const char *s, const char *body, const char *end) { - static const char *const name[] = { "a text that prints", "SEND", "SEND with no way", "AWAIT answered", "AWAIT ended by a GONE", "passing on while its text waits for room" }; - const char *why = 0; - int how11; + size_t n = strlen(s), e = strlen(end); + return n >= e && strcmp(s + n - e, end) == 0 && strncmp(s, body, n - e) == 0 && n - e <= strlen(body); +} + +/* Node 11 takes the path with `text`; `values` is how many values the text leaves on its stack if it completes; + * `must` says the stack is shallow enough that it must work. Returns 1 if it worked, 0 if it ended in a clean + * stack error, and counts a failure if it did neither. */ +static int one(unsigned path, const char *what, unsigned depth, const char *text, unsigned values, int must) +{ + static char sixhundred[601]; + const char *why = 0, *over = 0; + v4_cell lost11 = pool[mid].n.mem[LOST], refused12 = pool[far].n.mem[REFUSED], ways12; + int how11, works = 0, clean_error = 0; unsigned left; + if (!sixhundred[0]) memset(sixhundred, 'A', 600); runs++; - forget(); step_cap = 60000000; - if (path == 4) { /* node 12 is busy for a long while and sends no answer; node 11 waits; its centre says 12 is gone */ + if (path == 8 && !told(12, "44 2 ROUTE")) why = "node 12 did not take a way"; + ways12 = pool[far].n.mem[ROUTE_COUNT]; + forget(); + saw_room_wait = 0; saw_passed = 0; + if (why) { } + else if (path == 4) { /* node 12 is busy for a long while and sends no answer; node 11 waits; its centre says 12 is gone */ stuck = 1u << far; if (!put_text(12, "LL") || !put_text(11, text) || !run()) why = "it did not come to wait"; else if (!put_text(10, "12 11 GONE") || !run()) why = "the GONE was not done"; stuck = 0; if (!why && !run()) why = "the nodes did not come to rest"; } else if (path == 5) { /* node 10, by which node 11 prints, is busy, and sends node 12 a message by node 11 meanwhile */ - saw_room_wait = 0; saw_passed = 0; watch = watch_mid; + watch = watch_mid; if (!put_text(11, text) || !put_text(10, "BUSY") || !run()) why = "the nodes did not come to rest"; watch = 0; + } else if (path == 6) { /* node 12 sends no answer; node 11 waits, and is typed to */ + if (!put_text(11, text) || !run()) why = "it did not come to wait"; + else if (!put_text(11, "77 .") || !run()) why = "the nodes did not come to rest"; + } else if (path == 7) { /* node 10 is busy; node 12 sends a message for 99 by node 11, whose way for it is the full wire */ + watch = watch_mid; + if (!put_text(11, text) || !put_text(10, "BUSY") || !put_text(12, "LATE") || !run()) why = "the nodes did not come to rest"; + watch = 0; + } else if (path == 9) { /* node 10 is busy; a spent message comes while node 11 waits for room -- or, if it never does, afterwards */ + spent_put = 0; watch = watch_mid_spent; + if (!put_text(11, text) || !put_text(10, "BUSY") || !run()) why = "the nodes did not come to rest"; + watch = 0; + if (!why && !spent_put) { put_spent(); if (!run()) why = "the nodes did not come to rest"; } } else { if (!put_text(11, text) || !run()) why = "the nodes did not come to rest"; } @@ -247,15 +311,43 @@ static int one(unsigned path, const char *what, unsigned depth, const char *text left = (unsigned)pool[mid].n.ds.depth; if (!why && !(asleep(hera) && asleep(mid) && asleep(far))) why = "a node is not asleep"; if (!why && ended < 1) why = "node 11's text was not answered"; - if (!why && how11 == V4_TEXT_COMPLETED && left != values) why = "its stack is not as deep as the text made it"; - if (!why && how11 == V4_TEXT_ERROR && !(strstr(printed, "overflow") || strstr(printed, "Argument out of range") || strstr(printed, "Node gone") || strstr(printed, "Message refused"))) - why = "its text ended in an error it did not report"; - if (!why && how11 != V4_TEXT_COMPLETED && how11 != V4_TEXT_ERROR) why = "its text ended neither way"; - if (!why && path == 0 && how11 == V4_TEXT_COMPLETED && printed_len != 600) why = "not all it printed arrived"; - if (!why && path == 5 && how11 == V4_TEXT_COMPLETED && !strstr(printed, " 1199 ")) why = "not all it printed arrived"; - if (!why && path == 5 && how11 == V4_TEXT_COMPLETED && !(saw_room_wait > 0 && saw_passed > 0)) why = "it did not wait for room, or passed nothing on while it did"; + + /* which of the two ways did it end? */ + if (!why) { + if (strstr(printed, "Return stack overflow\n")) over = "Return stack overflow\n"; + else if (strstr(printed, "Stack overflow\n")) over = "Stack overflow\n"; + switch (path) { + case 0: works = how11 == V4_TEXT_COMPLETED && strcmp(printed, sixhundred) == 0 && left == values; + clean_error = over && is_cut_short(printed, sixhundred, over); break; + case 1: works = how11 == V4_TEXT_COMPLETED && printed_len == 0 && left == values && pool[mid].n.mem[LOST] == lost11 + 1; /* node 12's answer, which it did not wait for */ + clean_error = over && strcmp(printed, over) == 0 && pool[mid].n.mem[LOST] == lost11; break; + case 2: works = how11 == V4_TEXT_ERROR && strcmp(printed, "Argument out of range\n") == 0 && left == values; + clean_error = over && strcmp(printed, over) == 0; break; + case 3: works = how11 == V4_TEXT_COMPLETED && printed_len == 0 && left == values && pool[mid].n.mem[LOST] == lost11; + clean_error = over && strcmp(printed, over) == 0; break; + case 4: works = how11 == V4_TEXT_ERROR && strcmp(printed, "Node gone\n") == 0 && left == values; + clean_error = over && strcmp(printed, over) == 0; break; + case 5: works = how11 == V4_TEXT_COMPLETED && strcmp(printed, numbers) == 0 && left == values && saw_room_wait > 0 && saw_passed > 0; + clean_error = over && is_cut_short(printed, numbers, over); break; + case 6: works = strcmp(printed, "Interrupted\n77 ") == 0 && ended == 2; + if (over) { char want[64]; snprintf(want, sizeof want, "%s77 ", over); clean_error = strcmp(printed, want) == 0 && ended == 2; } + break; + case 7: works = how11 == V4_TEXT_COMPLETED && strcmp(printed, numbers) == 0 && left == values && saw_room_wait > 0 + && pool[mid].n.mem[LOST] == lost11 + 1 && pool[far].n.mem[REFUSED] == refused12 + 1; + clean_error = over && is_cut_short(printed, numbers, over) && pool[far].n.mem[REFUSED] == refused12 + 1; break; /* whoever refused it, node 12 was told */ + case 8: works = how11 == V4_TEXT_COMPLETED && printed_len == 0 && left == values && pool[far].n.mem[ROUTE_COUNT] == ways12 - 1; + clean_error = over && strcmp(printed, over) == 0; break; + case 9: works = how11 == V4_TEXT_COMPLETED && strcmp(printed, numbers) == 0 && left == values && saw_room_wait > 0 + && pool[mid].n.mem[LOST] == lost11 + 1 && pool[far].n.mem[REFUSED] == refused12 + 1; + clean_error = over && is_cut_short(printed, numbers, over) && pool[mid].n.mem[LOST] == lost11 + 1 && pool[far].n.mem[REFUSED] == refused12 + 1; break; /* it was dropped: its sender was told */ + } + if (clean_error && !(path == 6 || (how11 == V4_TEXT_ERROR && left == 0))) clean_error = 0; /* a stack fault empties the stack */ + if (works && over) works = 0; + if (!works && !clean_error) why = "it neither worked nor ended in a stack error and nothing else"; + else if (!works && must) why = "it ended in a stack error where the stack is shallow enough for it to work"; + } /* every node comes back */ - if (!why && !(strcmp(tell(11, "ABORT"), "") == 0 && ended == 1 && pool[mid].n.ds.depth == 0)) why = "node 11 did not answer ABORT"; + if (!why && !(strcmp(tell(11, "ABORT"), "") == 0 && ended == 1 && pool[mid].n.ds.depth == 0 && pool[mid].n.mem[AWAIT_FROM] == 0)) why = "node 11 did not answer ABORT"; if (!why && path == 4 && !told(11, "12 3 ROUTE")) why = "node 11 did not take the way to 12 again"; if (!why && strcmp(tell(11, "1 2 + ."), "3 ") != 0) why = "node 11 did not answer"; if (!why && strcmp(tell(10, "1 2 + ."), "3 ") != 0) why = "node 10 did not answer"; @@ -264,11 +356,14 @@ static int one(unsigned path, const char *what, unsigned depth, const char *text checks++; if (why) { failures++; bad++; - printf("FAIL %s, %s %u: %s: \"%.60s\"\n", name[path], what, depth, why, printed); + printf("FAIL %s, %s %u: %s: how %d, %u left, lost %+ld, refused %+ld, waits %u/%u: \"%.50s\"\n", path_name[path], what, depth, why, how11, left, + (long)(pool[mid].n.mem[LOST] - lost11), (long)(pool[far].n.mem[REFUSED] - refused12), saw_room_wait, saw_passed, printed); show_stuck(text); (void)made(path); /* the next begins on a row that is whole */ + return -1; } - return how11; + if (works) worked++; else refused_clean++; + return works; } int main(void) @@ -299,30 +394,39 @@ int main(void) nucleus.mem[SRC] = TIB; nucleus.mem[LINE_STATUS] = 1; HOST_MESSAGES_EMPTY(&nucleus); + { size_t at = 0; for (k = 0; k < 1200; k++) at += (size_t)snprintf(numbers + at, sizeof numbers - at, "%u ", k); } - for (path = 0; path < 6; path++) { - unsigned before = bad, done = 0, errors = 0; + for (path = 0; path < PATHS; path++) { + unsigned before = bad, w0 = worked, e0 = refused_clean, first_error = 0; + int r; CHECK(made(path), "a row for path %u", path); if (failures > (int)bad) break; - /* the data stack at every depth from 20 to full */ + /* the data stack at every depth from 20 to full: with 24 or fewer every path must work */ for (k = V4_DATA_DEPTH - 12u; k <= V4_DATA_DEPTH; k++) { size_t at; fill(text, sizeof text, k); at = strlen(text); snprintf(text + at, sizeof text - at, "ACT"); - if (one(path, "values on the stack", k, text, k) == V4_TEXT_COMPLETED) done++; else errors++; + r = one(path, "values on the stack", k, text, k, k + 8u <= V4_DATA_DEPTH); + if (r == 0 && !first_error) first_error = k; } - /* and from every depth of calls from 20 to past the end of the return stack */ - for (k = V4_RET_DEPTH - 12u; k <= V4_RET_DEPTH + 1u; k++) { + printf(" %-42s %2u..%u values: works to %u, then a stack error and nothing else;", path_name[path], (unsigned)(V4_DATA_DEPTH - 12u), (unsigned)V4_DATA_DEPTH, first_error ? first_error - 1u : (unsigned)V4_DATA_DEPTH); + /* and from every depth of calls from 14 to past the end of the return stack: 21 deep or less every path + * must work -- 16 or less for the two that print and are woken while they wait for room, which go + * deepest: the loop, the word that prints, EMIT, the wait, and (SERVE) under it */ + first_error = 0; + for (k = V4_RET_DEPTH - 18u; k <= V4_RET_DEPTH + 1u; k++) { snprintf(text, sizeof text, "N%u", k); - (void)one(path, "calls deep", k, text, 0); + r = one(path, "calls deep", k, text, 0, k + ((path == 5 || path == 7 || path == 9) ? 16u : 11u) <= V4_RET_DEPTH); + if (r == 0 && !first_error) first_error = k; } - printf(" path %u: with %u to %u values on the stack, %u completed and %u ended in an error reported; %u failures\n", path, (unsigned)(V4_DATA_DEPTH - 12u), (unsigned)V4_DATA_DEPTH, done, errors, bad - before); + printf(" calls: works to %u deep; %u worked, %u clean errors, %u failures\n", first_error ? first_error - 1u : (unsigned)(V4_RET_DEPTH + 1u), worked - w0, refused_clean - e0, bad - before); } - /* ---- between texts: node 11 passes on, and refuses, with as many values waiting on its stack as a text - * may leave (MESH.md 7d.4: the rule of 28). Not one fault. ---- */ + + /* ---- between texts: node 11 passes on, refuses, and holds what it has no room to pass on, with as many + * values waiting on its stack as a text may leave (MESH.md 7d.4: the rule of 28). Not one fault. ---- */ CHECK(row() && told(11, ": P1 7 ; : P2 7 7 ; : P4 7 7 7 7 ; : P8 7 7 7 7 7 7 7 7 ;") && told(11, ": P16 P8 P8 ;") && told(11, "77 5 ROUTE") - && told(12, ": S77 S\" 1 DROP\" 77 SEND ;"), "a row; node 11's way to 77 is a port with no one on it"); + && told(12, ": S77 S\" 1 DROP\" 77 SEND ; : PR 1200 0 DO I . LOOP ;") && told(10, ": SLOW 500000 0 DO LOOP ; : BUSY SLOW SLOW ;"), "a row; node 11's way to 77 is a port with no one on it"); for (k = V4_DATA_DEPTH - 12u; k + 4u <= V4_DATA_DEPTH; k++) { unsigned faults; v4_cell lost = pool[mid].n.mem[LOST], refused = pool[far].n.mem[REFUSED]; @@ -331,11 +435,16 @@ int main(void) faults = pool[mid].n.faults; CHECK(strcmp(tell(12, "1 2 + ."), "3 ") == 0 && ended == 1, "with %u values waiting it passes text on to node 12, and what comes back", k); CHECK(told(12, "S77") && pool[mid].n.mem[LOST] == lost + 1 && pool[far].n.mem[REFUSED] == refused + 1, "and refuses a message it has no way for, and tells node 12"); + forget(); + saw_room_wait = 0; watch = watch_mid; + CHECK(put_text(12, "PR") && put_text(10, "BUSY") && run() && strcmp(printed, numbers) == 0 && saw_room_wait > 0, + "and holds what node 12 prints while node 10 is busy, asleep for room, and passes all of it on: %u characters", printed_len); + watch = 0; CHECK(pool[mid].n.faults == faults && pool[mid].n.ds.depth == k && asleep(mid), "without a fault, its stack as it was: %u values", (unsigned)pool[mid].n.ds.depth); CHECK(strcmp(tell(11, "ABORT"), "") == 0 && strcmp(tell(11, "1 2 + ."), "3 ") == 0, "and it goes on"); } - printf(" %u runs; %u in which a node did not come back\n", runs, bad); + printf(" %u runs: %u worked, %u ended in a stack error and nothing else, %u did neither\n", runs, worked, refused_clean, bad); printf(" %d checks, %d failures\n", checks, failures); return failures != 0; } diff --git a/v4/tests/test_host_mesh.c b/v4/tests/test_host_mesh.c index 4b0ca921..bc5d0436 100644 --- a/v4/tests/test_host_mesh.c +++ b/v4/tests/test_host_mesh.c @@ -46,6 +46,7 @@ static v4_cell printed_from; /* which node the last of it came from static int ended; /* how many messages of how text ended have come, */ static v4_cell ended_from, ended_how; /* and the last of them */ static unsigned ended_at; /* and how much had been printed when it came */ +static unsigned stale_out; /* output messages whose last word had anything in it past the last character */ static int nacks; /* how many NACKs have come to the console, */ static v4_cell nack_from, nack_about; /* and the last of them: who refused, and the node the refused message was for */ @@ -60,6 +61,7 @@ static unsigned console_takes(void) for (i = 0; i < (unsigned)hd[6] && printed_len + 1 < sizeof printed; i++) printed[printed_len++] = (char)(((v4_ucell)text[i / 4u] >> (8u * (i % 4u))) & 0xFFu); printed[printed_len] = 0; printed_from = hd[1]; + if (hd[6] % 4 != 0 && ((v4_ucell)text[hd[6] / 4] >> (8u * (unsigned)(hd[6] % 4))) != 0) stale_out++; } else if (hd[2] == V4_MSG_DONE) { ended++; ended_from = hd[1]; ended_how = text[0]; ended_at = printed_len; } else if (hd[2] == V4_MSG_NACK) { @@ -129,6 +131,17 @@ static int run(void) } return 0; } +/* Let time pass until `when` says so; 0 if it never does. */ +static int run_until(int (*when)(void), unsigned cap) +{ + unsigned steps; + for (steps = 0; steps < cap; steps++) { + if (when()) return 1; + (void)v4_fabric_step(&f); (void)console_takes(); steps_run++; + if (watch) watch(); + } + return when(); +} /* The console sends text to a node: the message is put on the wire to the node the console is on. */ static int put_text(v4_cell node, const char *text) { @@ -202,6 +215,22 @@ static int triangle(void) } static v4_uheat_t clock_of(unsigned id) { return pool[id].es.anticlock; } +/* The whole of the mesh as it stands, kept and put back: so that one moment can be gone on from many times. */ +static v4_fabric_node kept_pool[3]; +static v4_place kept_places[3]; +static v4_wire_queue kept_queues[QUEUES]; +static v4_fabric kept_f; +static void keep(void) { memcpy(kept_pool, pool, sizeof pool); memcpy(kept_places, places, sizeof places); memcpy(kept_queues, queues, sizeof queues); kept_f = f; } +static void put_back(void) { memcpy(pool, kept_pool, sizeof pool); memcpy(places, kept_places, sizeof places); memcpy(queues, kept_queues, sizeof queues); f = kept_f; forget(); } + +/* what a scene waits for */ +static int far_has_one(void) { return coming_to(far, 2) && coming_to(far, 2)->used > 0; } +static int far_has_nack(void) { return far_has_one() && coming_to(far, 2)->cell[(coming_to(far, 2)->head + 2u) % V4_WIRE_CELLS] == V4_MSG_NACK; } +static int far_awaits_and_has_one(void) { return pool[far].n.mem[AWAIT_FROM] == 11 && far_has_one(); } +static int mid_holds(void) { return pool[mid].n.doing && pool[mid].n.do_op == 9 && pool[mid].n.mem[H_PORT] >= 0; } +static unsigned saw_mid_hold; +static void watch_mid_holds(void) { if (pool[mid].n.doing && pool[mid].n.do_op == 9 && pool[mid].n.mem[H_PORT] >= 0) saw_mid_hold++; } + /* what a scene watches for while it runs */ static unsigned saw_room_wait, saw_full; static void watch_far_waits(void) @@ -281,11 +310,14 @@ int main(void) /* ---- no way ---- */ nacks = 0; + i = (unsigned)pool[hera].n.mem[LOST]; CHECK(is_empty(tell(99, "1 2 + .")) && ended == 0, "text for a node there is no way to is not done by anyone"); CHECK(nacks == 1 && nack_from == 10 && nack_about == 99, "the console, which sent it, is told it was refused, by the node that could not pass it on: %d from %ld about %ld", nacks, (long)nack_from, (long)nack_about); - CHECK(strcmp(tell(10, "(LOST) @ ."), "1 ") == 0, "and that node has counted it"); + CHECK(pool[hera].n.mem[LOST] == (v4_cell)i + 1, "and that node has counted it"); CHECK(strcmp(tell(11, "(LOST) @ ."), "0 ") == 0 && strcmp(tell(12, "(LOST) @ ."), "0 ") == 0, "the others have lost nothing"); + i = (unsigned)pool[hera].n.mem[LOST]; + CHECK(told(10, ": TELL2 S\" 1 DROP\" 12 SEND ; TELL2") && pool[hera].n.mem[LOST] == (v4_cell)i + 1, "an answer that comes to a node that is not waiting for it is let go, and counted"); /* ---- waiting ---- */ { @@ -326,8 +358,10 @@ int main(void) const char *r = tell(10, ": GO S\" 6 10 BURST 6 12 BURST\" 11 SEND S\" 6 11 BURST 6 10 BURST\" 12 SEND 6 11 BURST 6 12 BURST ; GO"); CHECK(is_empty(r), "six messages from each node to each other, all at once: \"%s\"", r); CHECK(asleep(hera) && asleep(mid) && asleep(far), "all three come to rest"); - CHECK(strcmp(tell(10, "GOT @ . (LOST) @ ."), "12 1 ") == 0 && strcmp(tell(11, "GOT @ . (LOST) @ ."), "12 0 ") == 0 && strcmp(tell(12, "GOT @ . (LOST) @ ."), "12 0 ") == 0, - "every one of them arrived, and none was let go: \"%s\"", printed); + /* each has let go, and counted, the answers to what it sent, which it did not wait for: node 10 to its + * 12 messages and its two texts, and two from before and the message for 99; the others to 12 */ + CHECK(strcmp(tell(10, "GOT @ . (LOST) @ ."), "12 17 ") == 0 && strcmp(tell(11, "GOT @ . (LOST) @ ."), "12 12 ") == 0 && strcmp(tell(12, "GOT @ . (LOST) @ ."), "12 13 ") == 0, + "every one of them arrived; what each has counted is the answers it did not wait for: \"%s\"", printed); } /* ---- THE FLOOD: more than the wires hold (MESH.md 7d.6, acceptance 2) ---- @@ -353,16 +387,11 @@ int main(void) lost += pool[k].n.mem[LOST]; refused += pool[k].n.mem[REFUSED]; } - lost -= 1; /* the one for node 99, earlier */ printf(" the flood: %ld sends tried, %ld on a wire, %ld refused at once; %ld done; (LOST) %ld, (REFUSED) %ld in all\n", tried, sent, tried - sent, got, lost, refused); - CHECK(tried > sent && sent >= got && got > 0, "there was not room for all of them: some were refused at once"); - /* Every message that got on a wire and was let go on its way is counted exactly twice: once in (LOST) - * where it was let go, and once where its NACK ended -- in (REFUSED) at the node it was for, or in - * (LOST) if it could not go either. Every SEND refused at once is counted once, in (REFUSED). So - * (LOST) + (REFUSED) - the refused at once is twice the messages let go; and those are the messages - * the nodes sent each other that were not done, and answers to them. */ - CHECK((lost + refused - (tried - sent)) % 2 == 0 && lost + refused - (tried - sent) >= 2 * (sent - got), - "every message sent was done or was counted: %ld on a wire, %ld done; (LOST) + (REFUSED) %ld, of which %ld were refused at once", sent, got, lost + refused, tried - sent); + /* A SEND refused at once ends the word that sends, so the flood holds itself back: what got on a wire + * is what there was room for. (LOST) counts, with what was let go on the way, every answer that + * came to a node that did not wait for it, so it is not a count of what was lost. */ + CHECK(tried > sent && sent >= got && got > 0 && refused >= tried - sent, "there was not room for all of them: some were refused at once, and counted"); CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && printed_from == 12, "and the nodes go on as before"); } @@ -413,6 +442,10 @@ int main(void) c = clock_of(far); for (i = 0; i < 500; i++) (void)v4_fabric_step(&f); CHECK(asleep(far) && clock_of(far) == c, "node 12 is asleep, not going round: it owes nothing"); + /* nothing but the text goes on the wire: not what lies after it in the node's memory */ + (void)tell(12, ": S5 S\" ABCDEXYZ\" DROP 5 11 SEND ; S5"); + CHECK(coming_to(mid, 3)->used == 8 + 9 && coming_to(mid, 3)->cell[(coming_to(mid, 3)->head + 16u) % V4_WIRE_CELLS] == 'E', + "five characters sent of eight: the last word on the wire holds the fifth and nothing else: %lx", (unsigned long)coming_to(mid, 3)->cell[(coming_to(mid, 3)->head + 16u) % V4_WIRE_CELLS]); CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && printed_from == 12 && strcmp(tell(10, "1 2 + ."), "3 ") == 0, "and it and node 10 answer: \"%s\"", printed); } @@ -472,7 +505,24 @@ int main(void) CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && printed_from == 12 && asleep(far) && asleep(hera), "told another way to the console, it goes on: \"%s\"", printed); } - /* ---- a line from the console breaks a wait for room (MESH.md 7d.4) ---- */ + /* ---- NOTHING BREAKS A WAIT FOR ROOM TO PRINT (MESH.md 7d.4, ruled 2026-10-08) ---- + * A line typed ahead while a text prints more than the wire holds does not cut the printing short. */ + CHECK(triangle(), "the same made afresh"); + { + static char want[32768]; + size_t at = 0; + for (i = 0; i < 3000; i++) at += (size_t)snprintf(want + at, sizeof want - at, "%u ", i); + snprintf(want + at, sizeof want - at, "56 "); + CHECK(told(12, ": P 3000 0 DO I . LOOP ;") && told(10, ": SLOW 600000 0 DO LOOP ;"), "node 12 has a word that prints 3000 numbers, and node 10 one that takes a while"); + forget(); + saw_room_wait = 0; watch = watch_far_waits; + CHECK(put_text(10, "SLOW") && put_text(12, "P") && put_text(12, "7 8 * .") && put_text(10, "SLOW") && put_text(10, "SLOW") && run(), + "node 12 is typed two lines, the first of which prints more than the wire holds, while node 10 is busy"); + watch = 0; + CHECK(saw_room_wait > 0 && strstr(printed, "Interrupted") == NULL, "node 12 slept for room to print with the second line waiting on its wire, and was not interrupted"); + CHECK(strcmp(printed, want) == 0 && ended == 5 && pool[far].n.mem[LOST] == 0, "the first text printed everything, and then the second line was done: %u characters of %u, %d ended", printed_len, (unsigned)strlen(want), ended); + } + /* and with a stuck node on the way, where the room never comes, text for the node is left on its wire */ CHECK(triangle(), "the same made afresh"); { CHECK(told(12, ": P 3000 0 DO I . LOOP ;") && told(12, "NO-ROUTES 2 DEFAULT-ROUTE 10 3 ROUTE"), "node 12's way to the console is by node 11 again"); @@ -480,20 +530,131 @@ int main(void) (void)tell(11, ": C BEGIN 0 UNTIL ; C"); (void)tell(12, "P"); CHECK(ended == 0 && waits_for_room(far) && pool[far].n.mem[NODE_ERROR] == 0, "node 11 is stuck, and node 12 sleeps for room to print"); - (void)tell(11, "1 DROP"); (void)tell(10, ": T12 S\" 1 DROP\" 12 SEND ; T12"); - CHECK(waits_for_room(far) && pool[far].n.mem[NODE_ERROR] == 0 && coming_to(far, 3)->used == 9, - "text for node 12 from another node does not break the wait: it is left on the wire, and node 12 sleeps on: %u cells", coming_to(far, 3)->used); (void)tell(12, "65 EMIT"); - CHECK(pool[far].n.mem[NODE_ERROR] == 21 && waits_for_room(far) && coming_to(far, 3)->used == 18, - "text for it from its console does: the text it was doing ends in error 21, Interrupted; the line that broke it stays on the wire: error %ld, %u cells", + CHECK(waits_for_room(far) && pool[far].n.mem[NODE_ERROR] == 0 && pool[far].n.mem[QUIET] == 0 && coming_to(far, 3)->used == 18, + "text for node 12, from another node and from its console, is left on the wire: its text is not ended, and it sleeps on: error %ld, %u cells", (long)pool[far].n.mem[NODE_ERROR], coming_to(far, 3)->used); - (void)tell(12, "66 EMIT"); - CHECK(pool[far].n.mem[NODE_ERROR] == 21 && waits_for_room(far) && coming_to(far, 3)->used == 27 && pool[far].n.mem[OUT_PTR] <= OUT_W + OUT_CELLS, - "its message has still to wait for the room, and is not broken a second time: MESH.md 7d.6"); CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0, "node 10 answers meanwhile"); } + /* ---- PRINTING THROUGH A MIDDLE NODE IS NOT LOST (ruled 2026-10-08) ---- + * In the row, what node 12 prints goes by node 11 to node 10, which is busy. The wire from 11 to 10 + * fills; node 11 does not let go what it has no room to pass on: it leaves it on the wire from 12, which + * fills in its turn, and node 12 waits. */ + CHECK(row(), "a row made afresh"); + { + static char want[32768]; + size_t at = 0; + for (i = 0; i < 3000; i++) at += (size_t)snprintf(want + at, sizeof want - at, "%u ", i); + CHECK(told(12, ": P 3000 0 DO I . LOOP ;") && told(10, ": SLOW 600000 0 DO LOOP ;"), "node 12 has a word that prints 3000 numbers, and node 10 one that takes a while"); + forget(); + saw_room_wait = 0; saw_mid_hold = 0; watch = watch_mid_holds; + CHECK(put_text(12, "P") && put_text(10, "SLOW") && put_text(10, "SLOW") && put_text(10, "SLOW") && put_text(10, "SLOW") && run(), + "node 12 prints them while node 10 is busy with one line after another"); + watch = 0; + CHECK(saw_mid_hold > 0, "node 11 left output on the wire it came by, for want of room, and slept for room"); + CHECK(strcmp(printed, want) == 0, "every number arrived, in order: %u characters of %u", printed_len, (unsigned)strlen(want)); + CHECK(ended == 5 && ended_at <= printed_len && pool[far].n.mem[LINE_STATUS] == 1 && pool[hera].n.mem[LOST] == 0 && pool[mid].n.mem[LOST] == 0 && pool[far].n.mem[LOST] == 0 && pool[far].n.mem[REFUSED] == 0, + "and then how the text ended; nothing was let go anywhere: %ld %ld %ld", (long)pool[hera].n.mem[LOST], (long)pool[mid].n.mem[LOST], (long)pool[far].n.mem[LOST]); + CHECK(asleep(hera) && asleep(mid) && asleep(far) && pool[mid].n.mem[H_PORT] == -1, "all three sleep, node 11 for a message and not for room"); + + /* the middle node removed while it holds output: node 12's text ends in an error; node 10 goes on */ + CHECK(row() && told(12, ": P 3000 0 DO I . LOOP ;") && told(10, ": SLOW 600000 0 DO LOOP ;"), "the same again"); + forget(); + CHECK(put_text(12, "P") && put_text(10, "SLOW") && put_text(10, "SLOW") && put_text(10, "SLOW") && put_text(10, "SLOW") && run_until(mid_holds, 20000000), + "node 11 is holding output, asleep for room"); + CHECK(v4_fabric_remove(&f, mid) == &pool[1] && run(), "node 11 is removed, and the two that are left come to rest"); + CHECK(asleep(far) && pool[far].n.mem[NODE_ERROR] == 18 && pool[far].n.mem[LINE_STATUS] == 2 && pool[far].n.mem[KEEP] == -1, "node 12's text has ended in error 18, No one on that port, and it sleeps"); + CHECK(strncmp(printed, want, printed_len) == 0 && strcmp(tell(10, "1 2 + ."), "3 ") == 0 && asleep(hera), "what had arrived was in order, and node 10 goes on"); + + /* the node that prints removed while the middle node holds its output: what is on the wires arrives */ + CHECK(row() && told(12, ": P 3000 0 DO I . LOOP ;") && told(10, ": SLOW 600000 0 DO LOOP ;"), "and again"); + forget(); + CHECK(put_text(12, "P") && put_text(10, "SLOW") && put_text(10, "SLOW") && put_text(10, "SLOW") && put_text(10, "SLOW") && run_until(mid_holds, 20000000), "node 11 is holding output again"); + CHECK(v4_fabric_remove(&f, far) == &pool[2] && run(), "node 12 is removed, and the two that are left come to rest"); + CHECK(asleep(mid) && asleep(hera) && pool[mid].n.mem[H_PORT] == -1 && coming_to(hera, 2)->used == 0, "node 11 sleeps, holding nothing; nothing is left on the wire to node 10"); + CHECK(printed_len > 0 && strncmp(printed, want, printed_len) == 0 && ended >= 4, "what node 12 had printed before it went arrived, in order: %u characters", printed_len); + CHECK(strcmp(tell(11, "20 22 + ."), "42 ") == 0 && strcmp(tell(10, "1 2 + ."), "3 ") == 0, "and nodes 10 and 11 go on"); + } + + /* ---- A TAKE THAT DOES NOT TAKE (fix round 1, F1) ---- + * The wire is cut at every step from a message's coming to its being taken. If it was still on the wire + * when the wire was cut it was not taken, and nothing may be done for it: not the text before it run + * again, not a refusal counted, not a wait ended with an old answer, not a way forgotten. */ + { + unsigned k, cut_before = 0, cut_after = 0, wrong = 0; + CHECK(row() && told(12, "VARIABLE N : T1 1 N +! ; : T2 10 N +! ;") && told(12, "T1") && put_text(12, "T2") && run_until(far_has_one, 1000000), "text has come to node 12's wire"); + keep(); + for (k = 0; k < 200; k++) { /* text, taken by (IDLE) */ + int there; + v4_cell lost; + put_back(); + lost = pool[far].n.mem[LOST]; + for (i = 0; i < k; i++) { (void)v4_fabric_step(&f); (void)console_takes(); } + there = far_has_one(); + (void)v4_fabric_unwire(&f, mid, 3); + if (!run() || !asleep(far) || !v4_fabric_wire(&f, mid, 3, far, 2)) { wrong += 1000; break; } + if (there) cut_before++; else { if (!cut_after && getenv("V4_MESH_STUCK")) printf(" first cut after the take: %u steps\n", k); cut_after++; } + if (strcmp(tell(12, "N @ ."), there ? "1 " : "11 ") != 0 || pool[far].n.mem[LOST] != lost + (there ? 0 : 1)) { + if (!wrong) printf(" cut %u steps after the text came, it %s on the wire: N is %s, %ld counted lost\n", k, there ? "still" : "no longer", printed, (long)(pool[far].n.mem[LOST] - lost)); + wrong++; + } + } + CHECK(wrong == 0 && cut_before > 20 && cut_after > 0, "text: cut before it was taken (%u times) nothing was done; cut after (%u), the text was done once, and its answer counted: %u wrong", cut_before, cut_after, wrong); + + cut_before = cut_after = wrong = 0; + CHECK(row() && told(12, ": N77 S\" 1 DROP\" 77 SEND ;") && put_text(12, "N77") && run_until(far_has_nack, 1000000), "a NACK has come to node 12's wire"); + keep(); + for (k = 0; k < 200; k++) { /* a NACK, taken by (IDLE) */ + int there; + v4_cell refused; + put_back(); + refused = pool[far].n.mem[REFUSED]; + for (i = 0; i < k; i++) { (void)v4_fabric_step(&f); (void)console_takes(); } + there = far_has_nack(); + (void)v4_fabric_unwire(&f, mid, 3); + if (!run() || !asleep(far)) { wrong += 1000; break; } + if (there) cut_before++; else { if (!cut_after && getenv("V4_MESH_STUCK")) printf(" first cut after the take: %u steps\n", k); cut_after++; } + if (pool[far].n.mem[REFUSED] != refused + (there ? 0 : 1)) wrong++; + } + CHECK(wrong == 0 && cut_before > 20 && cut_after > 0, "a NACK: cut before it was taken (%u times) no refusal was counted; cut after (%u), one: %u wrong", cut_before, cut_after, wrong); + + cut_before = cut_after = wrong = 0; + CHECK(row() && told(12, ": Q S\" 1 DROP\" 11 SEND 11 AWAIT 7 + . ;") && put_text(12, "Q") && run_until(far_awaits_and_has_one, 1000000), "an answer has come to the wire of node 12, which waits for it"); + keep(); + for (k = 0; k < 200; k++) { /* an answer, taken by AWAIT */ + int there; + put_back(); + for (i = 0; i < k; i++) { (void)v4_fabric_step(&f); (void)console_takes(); } + there = far_has_one(); + (void)v4_fabric_unwire(&f, mid, 3); + if (!run() || !asleep(far)) { wrong += 1000; break; } + if (there) cut_before++; else { if (!cut_after && getenv("V4_MESH_STUCK")) printf(" first cut after the take: %u steps\n", k); cut_after++; } + /* not taken: it waits on. Taken: the wait ended with the answer, 1, and "8 " was printed -- to a wire with no one on it. */ + if (there ? !(pool[far].n.mem[AWAIT_FROM] == 11 && pool[far].n.mem[QUIET] == 0) : !(pool[far].n.mem[AWAIT_FROM] == 0 && pool[far].n.mem[QUIET] == 1)) wrong++; + } + CHECK(wrong == 0 && cut_before > 5 && cut_after > 0, "an answer: cut before it was taken (%u times) the wait went on; cut after (%u), it ended: %u wrong", cut_before, cut_after, wrong); + + cut_before = cut_after = wrong = 0; + CHECK(row() && told(12, "55 2 ROUTE 44 2 ROUTE") && told(11, "55 12 GONE") && told(12, "55 2 ROUTE") && told(11, ": G44 44 12 GONE ;") && pool[far].n.mem[ROUTE_COUNT] == 2 + && put_text(11, "G44") && run_until(far_has_one, 1000000), "a GONE from its centre has come to node 12's wire"); + keep(); + for (k = 0; k < 200; k++) { /* a GONE, taken by (IDLE): node 11 is node 12's centre */ + int there; + v4_cell ways; + put_back(); + ways = pool[far].n.mem[ROUTE_COUNT]; + for (i = 0; i < k; i++) { (void)v4_fabric_step(&f); (void)console_takes(); } + there = far_has_one(); + (void)v4_fabric_unwire(&f, mid, 3); + if (!run() || !asleep(far)) { wrong += 1000; break; } + if (there) cut_before++; else { if (!cut_after && getenv("V4_MESH_STUCK")) printf(" first cut after the take: %u steps\n", k); cut_after++; } + if (pool[far].n.mem[ROUTE_COUNT] != ways - (there ? 0 : 1)) wrong++; + } + CHECK(wrong == 0 && cut_before > 20 && cut_after > 0, "a GONE: cut before it was taken (%u times) no way was forgotten; cut after (%u), one: %u wrong", cut_before, cut_after, wrong); + } + /* ---- Review Focus 4: THE ANSWER HAS ROOM KEPT ---- * Node 11 sends the console 127 short messages by way of node 12, which passes them on to the wire to * node 10 while node 10 is busy: that wire is then full but for 8 cells. Node 10 has sent node 12 text @@ -508,7 +669,7 @@ int main(void) (void)tell(10, "G"); watch = 0; CHECK(saw_full > 0, "node 12 slept for room to print with the wire to node 10 holding exactly the 8 cells kept for its answer (%u, %u)", saw_room_wait, saw_full); - CHECK(strcmp(printed, "A1 ") == 0 && pool[far].n.mem[LOST] == 0 && pool[hera].n.mem[LOST] == 0, + CHECK(strcmp(printed, "A1 ") == 0 && pool[far].n.mem[LOST] == 0, "what it printed arrived, and then how the text ended, which node 10 waited for: the answer was not refused: \"%s\"", printed); CHECK(asleep(hera) && asleep(mid) && asleep(far) && pool[far].n.mem[KEEP] == -1, "all three sleep, and node 12 keeps no room"); } @@ -579,6 +740,16 @@ int main(void) (void)tell(11, "33 AWAIT ."); CHECK(ended == 0 && told(10, "44 11 GONE") && pool[mid].n.mem[AWAIT_FROM] == 33 && asleep(mid), "a GONE from its centre about another node does not end a wait"); } + /* a GONE that says it is from the centre but comes by another port is not believed (MESH.md 7b.4) */ + CHECK(triangle(), "a row made afresh, with nodes 10 and 12 wired straight to each other"); + { + v4_cell ways; + CHECK(told(11, "44 3 ROUTE") && told(12, ": FORGE 10 (ME) ! 44 11 GONE 12 (ME) ! ;"), "node 11 has a way to 44; node 12 has a word that sends a GONE saying it is from node 10"); + ways = pool[mid].n.mem[ROUTE_COUNT]; + CHECK(told(12, "FORGE") && asleep(mid) && coming_to(mid, 3)->used == 0 && pool[mid].n.mem[ROUTE_COUNT] == ways, + "it comes to node 11 by the wire from node 12, not by its centre's: it is not believed, and the way stays: %ld ways of %ld", (long)pool[mid].n.mem[ROUTE_COUNT], (long)ways); + CHECK(told(10, "44 11 GONE") && pool[mid].n.mem[ROUTE_COUNT] == ways - 1, "from node 10, by its centre's port, it is"); + } /* interrupted: text from the console */ CHECK(row(), "a row made afresh"); { @@ -626,6 +797,7 @@ int main(void) * how it ended. It is looked at once an instruction word, so it is the least that was used; that 28 * values may wait is measured in test_host_quit.c and, for passing on and refusing, in test_host_depth.c. * The return stack is empty when a text ends; (FINISH)'s wait for room goes seven deep on it. */ + CHECK(stale_out == 0, "no message of what a node printed carried anything past its last character: %u did", stale_out); printf(" between texts the nucleus used at most %u cells of the data stack and %u entries of the return stack\n", between_data, between_ret); CHECK(between_data <= 4 && between_ret <= 7, "between texts: four cells of the data stack, and seven return entries"); diff --git a/v4/tests/test_host_quit.c b/v4/tests/test_host_quit.c index 21b94780..8350d140 100644 --- a/v4/tests/test_host_quit.c +++ b/v4/tests/test_host_quit.c @@ -237,7 +237,8 @@ static int wire_do(void) if (!tx) break; if (a < 0 || a > (v4_cell)V4_WIRE_CELLS) { how = V4_WIRE_NO_ROOM; break; } if (v4_wire_room(tx, (unsigned)a) == V4_WIRE_DONE) { how = V4_WIRE_DONE; break; } - if (!wire_in.arrived) return 0; + if (!wire_in.arrived && !wire_out.freed) return 0; + wire_out.freed = 0; how = V4_WIRE_NO_ROOM; break; case 10: if (tx) how = a < 0 ? V4_WIRE_NOT_A_MESSAGE : a <= (v4_cell)V4_WIRE_CELLS ? v4_wire_room(tx, (unsigned)a) : V4_WIRE_NO_ROOM; break; diff --git a/v4/tests/test_host_unit.c b/v4/tests/test_host_unit.c index 50ff1477..33ee7e51 100644 --- a/v4/tests/test_host_unit.c +++ b/v4/tests/test_host_unit.c @@ -490,7 +490,8 @@ int main(void) CHECK(strcmp(tell(11, ": X S\" 66 EMIT\" 14 SEND ; X"), "B") == 0 && printed_from == 14, "and to the one across from it, which it is not wired to: \"%s\"", printed); CHECK(clock_of(10) > hera, "that went by a node in between"); } - CHECK(strcmp(tell(10, "(LOST) @ ."), "0 ") == 0 && strcmp(tell(11, "(LOST) @ ."), "0 ") == 0 && strcmp(tell(14, "(LOST) @ ."), "0 ") == 0, "nothing was let go"); + CHECK(strcmp(tell(10, "(LOST) @ ."), "0 ") == 0 && strcmp(tell(11, "(LOST) @ ."), "2 ") == 0 && strcmp(tell(14, "(LOST) @ ."), "0 ") == 0, + "nothing was let go but the two answers that came back to node 11, which did not wait for them: \"%s\"", printed); /* ---- they share the store (acceptance 3) ---- */ step_limit = 30000000ul; @@ -514,7 +515,7 @@ int main(void) CHECK(told(10, ": GO S\" W\" 12 SEND W ; GO"), "Hera sets node 12 writing and writes her own meanwhile: \"%s\"", printed); for (i = 0; i < 10 && common_dev[(12 + i) * V4_BLOCK_BYTES] == 'a' && common_dev[(22 + i) * V4_BLOCK_BYTES] == 'b'; i++) { } CHECK(i == 10, "all twenty blocks reached the store: the first that did not is %u", i); - CHECK(strcmp(tell(10, "(LOST) @ ."), "0 ") == 0 && strcmp(tell(12, "(LOST) @ ."), "0 ") == 0, "and no message was let go"); + CHECK(strcmp(tell(10, "(LOST) @ ."), "1 ") == 0 && strcmp(tell(12, "(LOST) @ ."), "0 ") == 0, "and no message was let go but node 12's answer to Hera, who did not wait for it: \"%s\"", printed); CHECK(waiting(10) && waiting(11) && waiting(12) && waiting(13) && waiting(14), "all five are waiting at their ports again"); /* ---- Hera kills a node by number, and tells the others it is gone (MESH.md 7b, 7d) ----