From 033d399565fd124d548d60a8bd409bfb3f610e2a Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 16 Apr 2026 13:01:19 +0000 Subject: [PATCH] runtime: gate __multiply / __divide on usage markers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop __mul_used from IrOp::Mul codegen and __div_used from IrOp::Div / IrOp::Mod codegen (modulo reuses the same routine). The linker skips gen_multiply / gen_divide for programs that never emit the markers, following the same pattern already used by __audio_used / __ppu_update_used / __sprite_cycle_used. The optimizer already rewrites multiplies and divides by constant powers of two into shifts (and modulo by constant powers of two into masks), so the markers only fire for genuinely runtime math. A program like `examples/comparisons.ne` that never multiplies or divides now reclaims ~56 bytes of PRG; programs that use only one of the two reclaim the other's share. Audio goldens flip for every example that uses audio. The .ne sources are unchanged and the pixel goldens are byte-identical — the audio stream differs only because removing the math routines shifts the audio tick's absolute address in PRG by 56 bytes, which changes which of its internal branches cross 6502 page boundaries and therefore the per-frame cycle count of a single NMI by 1-5 clocks. Over 180 frames the accumulated drift shifts APU register write timing enough to render a different digital sample stream at the same logical wave shape. Expected consequence of ROM-layout change under cycle-accurate emulation; documented path per CLAUDE.md "Updating goldens". https://claude.ai/code/session_016kM6P7PukktBDqTZexrrAN --- examples/arrays_and_functions.nes | Bin 24592 -> 24592 bytes examples/audio_demo.nes | Bin 24592 -> 24592 bytes examples/auto_chr_background.nes | Bin 24592 -> 24592 bytes examples/bitwise_ops.nes | Bin 24592 -> 24592 bytes examples/bouncing_ball.nes | Bin 24592 -> 24592 bytes examples/coin_cavern.nes | Bin 24592 -> 24592 bytes examples/comparisons.nes | Bin 24592 -> 24592 bytes examples/friendly_assets.nes | Bin 24592 -> 24592 bytes examples/function_chain.nes | Bin 24592 -> 24592 bytes examples/hello_sprite.nes | Bin 24592 -> 24592 bytes examples/inline_asm_demo.nes | Bin 24592 -> 24592 bytes examples/logic_ops.nes | Bin 24592 -> 24592 bytes examples/loop_break_continue.nes | Bin 24592 -> 24592 bytes examples/match_demo.nes | Bin 24592 -> 24592 bytes examples/metasprite_demo.nes | Bin 24592 -> 24592 bytes examples/mmc1_banked.nes | Bin 57360 -> 57360 bytes examples/mmc3_per_state_split.nes | Bin 24592 -> 24592 bytes examples/nested_structs.nes | Bin 24592 -> 24592 bytes examples/noise_triangle_sfx.nes | Bin 24592 -> 24592 bytes examples/palette_and_background.nes | Bin 24592 -> 24592 bytes examples/platformer.nes | Bin 24592 -> 24592 bytes examples/pong.nes | Bin 24592 -> 24592 bytes examples/scanline_split.nes | Bin 24592 -> 24592 bytes examples/sfx_pitch_envelope.nes | Bin 24592 -> 24592 bytes examples/sprite_flicker_demo.nes | Bin 24592 -> 24592 bytes examples/sprites_and_palettes.nes | Bin 24592 -> 24592 bytes examples/state_machine.nes | Bin 24592 -> 24592 bytes examples/structs_enums_for.nes | Bin 24592 -> 24592 bytes examples/two_player.nes | Bin 24592 -> 24592 bytes examples/uxrom_banked.nes | Bin 90128 -> 90128 bytes examples/uxrom_banked_to_banked.nes | Bin 57360 -> 57360 bytes examples/uxrom_user_banked.nes | Bin 40976 -> 40976 bytes examples/war.nes | Bin 24592 -> 24592 bytes src/codegen/ir_codegen.rs | 61 +++++++++++++++++- src/linker/mod.rs | 19 +++++- tests/emulator/goldens/audio_demo.audio.hash | 2 +- .../goldens/friendly_assets.audio.hash | 2 +- .../goldens/noise_triangle_sfx.audio.hash | 2 +- tests/emulator/goldens/platformer.audio.hash | 2 +- tests/emulator/goldens/pong.audio.hash | 2 +- tests/emulator/goldens/war.audio.hash | 2 +- 41 files changed, 80 insertions(+), 12 deletions(-) diff --git a/examples/arrays_and_functions.nes b/examples/arrays_and_functions.nes index 9faa196a2b234427b41312f28bdc4913e300d6e0..481c3dae9b02dff602deb6b3ae11b70dd8c83ca1 100644 GIT binary patch delta 397 zcmZ|KJx{_w7zglP|5xZypf4g{$!hQy1#8eh}!@F delta 355 zcmZ9Iu};G<5Qbx)owN(ku%u~=A_hk03=9>jn6mK#l@LgI1H=MKbGxNX8CVcEgsDPCNojI-)!RE(r^=hr93duRB?lOqFbT#p1P&W}3u@Z1@tQ8%=Uf!rTtr85h=u z$*|sG^hn~6Z1l;&5qT)cM;XK#$22s}sBI=Ru6hchH%*hS$oBZ!FkXKe+mI99p%MUGqRI3y`$&@?vG-H0Wh< zSoyq_Y2~5G_n6d#o*e?pFn-`(%G%1el&zKDr+Bg$^G-&u%@>%hY$hwP=&-gQW;k$a JGrt3)0swJFR}26E delta 282 zcmbPmfN{bB#tqGkvfZsbD-XBwuKeH1wUklQ;e*&pgBFe(55aZx2| diff --git a/examples/bitwise_ops.nes b/examples/bitwise_ops.nes index 9f298a82e1a8ea711be62805f0f8e6574a853269..50571da45259abf0d18d81e4f7c2a834475a6938 100644 GIT binary patch delta 491 zcmYk1Jxc>I9LE1iD0oG^I~QLHT8Dy%eguWvTJ@GLZZ1Nh;1}rD+|_Ir3Ptcn!3-G+ zg#%an0@5mqA3+=hmlE&`m|U)KWXhB0_v1-#gnA=1tfLESSP&4Lj|GzW#pTGOa)Y~b zkUG9nu?4~PSOAN7B#YcR^_e4Vrkj4Qd251ETe4iB=3)K#XvVv1rBw z|5)az0cTxA1avVOVDgSg`iVfJRdK+m3RxtIEaXv*hYUB%_fyv4bgR)}p_nQ6Ea&E- eaj&L&ffkqqC;dyZm&BxcTu7!c`b%sdmHz=5D7T0J delta 464 zcmYk2KTE?v7{>2UTH7p1lMKZ!QD`w;T`V}LgS2u8#nr*3(2vkXOjEAlWFR1P6>qr7 zLMOK-!O}R@OmqW_D5$29Tuhxe68Fad~z2}Bcwcz=RXT+R;DE~qYc z8O(UoMUl&pABev(+FYinZ+1gEV#8{0L>;~qoMn!~oE#@Mh#Zn#pGpFTpGxP*-KjPi u?nx9ErT10B@XdBslp2`fqaWmoVJwEfOqvC_ee9W1vv41Rc{Ec_p!p9gg{(mU diff --git a/examples/bouncing_ball.nes b/examples/bouncing_ball.nes index 9959992e77d3dc8f65b6ffa81259ca0538074701..74adae093070457e3a01c70533dbb48b3916f0b3 100644 GIT binary patch delta 161 zcmbPmfN{bB#tq?&^{guyeEbitWc(oUOkkxLOyi|kAcJ)K7hn4 zAmWUzLQ92DGJ=J=A$kRu3Z8rb);kBLSJ-FQ!DAqCh~6zQy~0ZcmvAl>oXNnnl40iL cO^l+O|1)yiO%7lZVZD8j;ehmJeg{Sc08fBGy8r+H delta 162 zcmbPmfN{bB#tq?&-poF+2cHQn6*&1}1G7)m!IccHLOwYMkAXxOJ}?8BD;ZmbmI|L_ z1ajIBJ`-Fjc=7>IWeZ59u+OxE$3P+=mD3JRKE|kO(aHjpU|z)G#x#LlVkvVfODi)Q y%c%<=mNF;6q}W*0n3pnJJY|{ylzax1e7BS(Ve@N7ZadcZ2N@3NZ{~MkQ~&@7K|c8a diff --git a/examples/coin_cavern.nes b/examples/coin_cavern.nes index 8fcf88a589f1ad9e455d1364b00864bed6ab4579..230f49761276601fbfd91d97619d3642a089cdab 100644 GIT binary patch delta 278 zcmbPmfN{bB#toi~`uZmsFEIP;Ie3x@NL@YTqjPX6OD_ZSS_bBmA3ktyU|q@JlW=e) z;|J!I3<*HN$=Qt7jH@RvXVj~gS}Gus$&=2n5YQqM$=i7pj| eTG$H`VxOGE)Vf)o`I!x?$YF*9A)EOf7!?4)du=fQ delta 278 zcmXwxu}i~16vp%JU8|G5BrRs>AVma0TwDS=bP|LE=WH45;D6v|m)iqboD_sNWU|oy z0m%u%b?Pi#4Y-s82UlN3r}y}Nzwaw*C~BBp!D!@Qh5Ds&g3p81Ck;vGy_liSnkvXi zmUnOtkL3bR@B5^ZTVd3n@ZMD3D1u|*_er^J=c^%=ezKy!xy>+kfk!>G6eg3%gl**3 ztPO3oHem%OW+~!@sSjBNR?jR)aV0rcZozKGU3as6_H5gK8_?i)GPC8AFsYHOn+a diff --git a/examples/comparisons.nes b/examples/comparisons.nes index 84044f6270c6fa061645e05cd76d75c768ab96ef..99c527b5476755f25cfc2822ba35f60d172c282a 100644 GIT binary patch delta 123 zcmbPmfN{bB#tqqwT&yb@e3B2YWc(mEIiFD(%$)$?PJ?pyLb!Vwm3i1FfRw+12s{G{ raBctzNFM_0=VyZG_keKCm=rhnGgaG8e!wKcnth1j!0OHX4vY!_yWT5) delta 140 zcmbPmfN{bB#tqqwEX+Q&2Pc;@Dgzly4o+SKW}H4a`8=aC8`B0wpKAvv{{u6bePjI;m1QMEEAt`_H>L^f5=)s=Sz4LdSWaE|u#`CgCdJ00#=Ml-;wjSv epyV^426rznR~GQ2_w#GA|?m diff --git a/examples/friendly_assets.nes b/examples/friendly_assets.nes index 803da81e95c34f24d4126bf820f7305a066cd075..fe80777f63a1e650004d4fba4a0d3bba05b94038 100644 GIT binary patch delta 269 zcmbPmfN=s4)iCl-tW{vtoj6aG^=&KD%3~8xRMbCdCm3QU;R<5OtnhqbtRyy=D zIjrR9Wp-GJEGKo6>jUdb2A`CJD;Yn?E|vbk?9+U3cD+`dc@$zD2;WX%Faafr%Ii5*NIQtM#l<@=iQr1?!rEIPI kK0gj_p2KvLh4K96GWIDJlNC60Sapvx9JsWZ-+@s90AeL;%K!iX delta 321 zcmZ|Hze~eV5C`zQA4%FYHi9B18QMXGIu%@6aCnOjA-H$zqO)TMi_?vPgb3P2iu4Vq zljI*r<4~(hSN{MVC5w|_eF%b!aNNOt?!IwOVpV6Pn+%(zmt1e_E>N5$kAw47u<-#_ za0!+qEm3Zuh^W9y#035|Z*G+=98RVGGw=>f^q~&A`gXuT0yXfZHy*N*(rVDjQA;HrFTm<^53YOx6t)Hmi$ZCZRcWSm%n2|lHWoGJrOXyjnI-@wp8+M`0e!Gp Tlj)KztHvRQ0~wq79T*h=RE1kW diff --git a/examples/hello_sprite.nes b/examples/hello_sprite.nes index 97eaccfae1fd468a68bcab0f28c6df9e5cb1e140..09872a2a4f288349373c004dc2745718bb73b081 100644 GIT binary patch delta 29 ncmV+&0OJ3UzyXlJ0kES1v)chLK9ev4I0lo!0Kn71qYywKwnz)H delta 82 zcmbPmfN{bB#tn-ZEm~PtGPE);;&5Y{z%H?rIhCcAnT_Stg%3-a6JSzoENaY4nJu0& gO#n(h14_PI%95~oF{8d6+v$T02e=O{7I07i0Er76ssI20 diff --git a/examples/inline_asm_demo.nes b/examples/inline_asm_demo.nes index 97e2b5fb81d91b4227156734fd14b224e7dd52fb..2c19839e68c715f207c57e6384b97a5324a1d27e 100644 GIT binary patch delta 58 zcmV-A0LA~1zyXlJ0kAs(7#K_*!KDO^00XT61C0R#r4$MZg%qs;1CvJqPP5GcJwB5l Q0xSiQ!2rO~vkyQ4APYqii~s-t delta 99 zcmbPmfN{bB#tqhtJUl*f2bZ$6ig0nYicAh>^s{JXS;^4Kyoke%X#%^%Qsz{aR%SMq xQx`rgWln%ev9YKzFJ-oP$}|Be`3xxeZYfK`=7o&5cC333G9383ncsm?0RR%8Am#u7 diff --git a/examples/logic_ops.nes b/examples/logic_ops.nes index ccdfff4736331a453e3b89f5f87a90758f29507f..460671b92d83d26d8a2895e357ba22185a3c6ff1 100644 GIT binary patch delta 360 zcmbPmfN{bB#tn&#^_&}6S2FlSA6&`!f$5moQbtXI4?;`DPBMG|2^Rr{IT?Ja53Xcr zZCcv&L3pX)2X-J^(5DY5(<-=9q!pya0ZGeJh?aFAEe%T>K5zhO0jRnLBz0#Y>h!L` z%;o?Z2vG=ffgsccpCQWrKvhlVU{VifUCHQUb7-jmREfnQkR!pCG5e$+S}J-{4(z7@ zp!f$-p!K3lMNWR00P;^VNQmi~$Wml0D!}@E8YfpU>2QHmZ2+l~o4kTa8O*&4;hu+b mxekM6zd^a?5Uwn<;^s2uDK?WYF!QiR9cDN%YcszCqXGcZL5kG? delta 348 zcmbPmfN{bB#tn&#kxUzyeIgGY6I;rtDeysPsn|(|53DO0d~y!1WaMPo z;|JlTf*;s{Y(bw6piHaaN|9EO76&9P^C4PRfV4C%ZJK<5QPT7{MC=007!IIb0kB?> zWr9%4-a=G;gQ}X$$E5Db>|=6hsQ^@g!6A@qK&F0R_6a_;RP>}A(AmL7~ z0*LcWWGS+tDPTQ5xs!XCbXb^uwjG*$h)Eg9xO-^wdoY9L@MIZgkcjr-$)3!L7OgBm z?J}UiZ0G|n; z8)C-I!A%u;NXd#G@K9_iIqYN~at~@dM#}k0!FO|i(@(KKuqEZ&YL@S)*iD2aI-TnD zTl5_z6E>P6=1sSfW`)`z#HvE)FP506gh`T8mNF*76xf&5J<0yinvS+$qeG+}uMsZQ k>YCP_@Sm3S;iQlLNA&L{W7uClLoha9*Q9Kg-US`}0^p;MeEY5QcYmW@7?^Q7=J)MFdT$m2!oGmDPf+SY2b`53sQ@Iam;@!)v@&oMNp4 zDXf(Hz~X|1jX&Y8l=}(pCDBTzcxRp$o(Wf1m17e hzyT1rg$Qg}+VY#wo4kQK4BNOZ?=luoCo9Li{{v8RhN}Po diff --git a/examples/match_demo.nes b/examples/match_demo.nes index 9343b055b93ba7e01bc2c7a6a4bc81a2824675ee..aa3dd91cd05f8ae4cfd651a654c7793bd045dbb0 100644 GIT binary patch delta 269 zcmbPmfN{bB#to5-vYZ=OS2FknA6&`!f$5m=Qh}2UA3y>rAOX+GS&X{6(}7G*2A>57 zS2DCVEN%F}v68V>V5LZ_u#d%Ipn68I`aM8J7npt0ChulU;#WHa)}en0qGPfSlccsE zL@XR6*0i(S4GY3~)PuLIOTY2RA1%udtbXfLWZ?`Y^+R>dpKPj0ymI;b>R@ delta 284 zcmbPmfN{bB#to5-f=nBjeF6_26J9EClHmih&*FoV%NTW4x(}{oYvaX35QMn1K8{Haapi1GxlYcYmM*oJWWH}5pvteliP$dK0Oc2vY>0m1h zkioo&!;NVIyTnrFRF+m|HkMNtJ}hNUfJw2js4*{Pws^`k0Vw$lDEV$FOTuPv<`p)q PL5CR*Oxn!vz^DKK5Mybd diff --git a/examples/metasprite_demo.nes b/examples/metasprite_demo.nes index 50f684ded1b10f7b8cdd7c179de3a5c436334b17..a42abea11526aa0fed2d1a69afcea6999b5170f0 100644 GIT binary patch delta 85 zcmbPmfN{bB#tos2#;hwDe0&eCWc(oUOkk!Imll8R<d6;3Lgou`)zLtwu(C?IU$1LN8>%$Nf2cFX_?aj^?UI z+e*kAlXE0Ly632TVnc2t@*8fwP7y1oY>T14K GG5G@lpLUo4 delta 269 zcmbPmfO*0J<_(dJ?z0arW%%%CB||I2Qs$EkAN)@;USRg|IOOAfa4Acx*izP3@ugxO zOT}B6G8C>HN?0m+ zqDuuNQbk(@mNIHGeGpnIaFXc*6Hw{vgG&W0UNV8j8$JjE4P*cs$aH8WW2^8=)>a{f za-f2f&%kP>K%&eF)`uqFZ&I0D)nuy(Q{RQ6o>5cb0~gQ>%nGw7-)jn!X=MR%7ICn# rs4*{Pws^`kfu)uC8Ox~)@0PM`Hg3LdGf_c;mFFEii}km4nWmBOPNnHT;N;j-^*~Iml22__?R39YT;WsA6aB2 uLodUFgjNBMm2ABX4H>VlGHbDd@^D)aNX&eSh@vWTS%W$BV5r`i6XdGV2(97^3p;f?RC0j2;Lq;!S zLqaPH&`{<@9Bxb#*d>-Sr?Rv%v$34I@L?%4T#AiFjd>}v#Z#sUK*?u7$#+Xx5)^(O V+8oNJ2Rre6Xf!xjf4vY!_Q)U|< diff --git a/examples/noise_triangle_sfx.nes b/examples/noise_triangle_sfx.nes index 8bfe2177b27432a22ba96c0fbfdb41f717cefa9a..619fda3feda463925c472b7e1e585cf5294d7a32 100644 GIT binary patch delta 271 zcmbPmfN{bB#tos2^{guye0&eCWc(oUOkk)EWLaVD>-`k9ai%9iaM+;?ES#L@^J4*_Lcv8Kd}RK z*noBH2I){=$@<~QL(WP7;q!%!3yRlsDR5yWwHyCf^xy3 zlm9=s`Zyn2wUk}+Kr0K7wDIy{W#KgFWpP;fvz2M(p~>w`YC;PS0c99Ja4%(T0=e7Tc%`hbz|pZLwBL zN}Jm|2%I1UpFyLQtiC{83!8zNZZvz{6JCC4u5 z8SU{{O_dAITpHaLA=OyasIG{J>PXB`eJU)`h4?0VEPjaVCaya$@&(L+3;6+7w>fqz z=%%s>Q!=a2A?7H+&qJ$F_a6ngi4&*p^hV1wQ2~Y{9rV VN4H?He}`D&Ul(5X){iJ?`~pK%Y+C>T diff --git a/examples/palette_and_background.nes b/examples/palette_and_background.nes index 88751f67bbcceabf8fd39b5034ef204e62a0821e..cd24ddce5dd57c0216a7b98ea47d80c3818d1733 100644 GIT binary patch delta 335 zcmbPmfN=s4)iBCVtW{u)oj6Yw$U5Rt&$^Pqhvnc(#t)J!8CoTtNiLN<$?^dtCl#52jIk|$X{K)I4jB~CH|IVTPR zMHzq$_k$}nT7^~~Y8758$jR6$xH6+v1jrKwii)i~)+)}))G7#63er<_aHR(kN|}I4 z=N_!bV(c-fv8Uk1GJ~Ch#aMKu5)zpz07^asO1@jl XlCW8q>5?s5#wmsaT&EWcI4A%B2pxc; diff --git a/examples/platformer.nes b/examples/platformer.nes index 911e40aa710318d1c93a73a46f7ee41e0b68432a..ab755550666f455e16183b68a2d49a6b66d1d99e 100644 GIT binary patch delta 1350 zcmZuxU1%It6uxus+?m~R({8AgO*TIh+F}w#+9Ed8D2pOUnNof5MNxv%he%Nb6$*u= zfm@I=?UrfP^g-g!)gECWQi_70yV)A&D%E}P!FTOyt;BiqNv!A0>}FF4JM7u-yXSo8 z+;h%sqiHqpIVCSC4(7t?vHP#l4<$adSJ>w~bp526cK*@4vY0Ba#UfMU`_6ChCVJht zkUUiXV@=Z-JHiP*S59FruokKjbwi zgrWv7sS>6gf;-Av(4&T>#Z`(pMiF~;#fS{PNYK-jxm!Y2f>&E`fT;>t>!ZPymabHe zS5-wdp)-| zws5PN*BNBe7cgbN-gk-E15S3sYS-WXhL3}lUy|xH6haM|gq6*9yf$bDumxZt7it_n zRW6)VJCp`C;2JyFN-Wx>0}`~!`!Kq7^n$mv|41AnRaKI%Es^cBS)lN+#lLC_%pig&hEOEAZw-fXsLE+N9_#W dO0G^9ezp100z7~qKE?6O6r1+ws=iW6M+H!|Ezd3Wx?|jU; z)tXtQb=rO?8Fnw69ohRl0+L^|mW{h?(e=BvFn9^fn+qiaK4(aNFZc^r$m;%u?ty)m z8eoL4(U1mkZ3JAbG_*!xNTp0&kB4TiNDa&fq>J~Kc0@`_ATfI)mCQ)S3%BV?;w zoT!7RIe~8oOg-h&6W`W})kAYVD;7xUoyS;>yw+2-7D5dbKp^o!nmWxc2EV!R>b*P^ z<5Liw(Cp>?FX}L+XDOeJCV_I*MngQMM3RPh(WE*mPzm!_>}$m&2V4IkIkHLeZ;pbr{A>e?vA|n$0!B+i@c^A};SD)~_Vpx8EEx># z)RM{2SYiwc>MMKnb0{ux)=Tz3tjZF(CSuozE&N%Q&>`1STkPfaq(SS4LcE7Xsg*6thLoCr?{VsU)%oqI*csZJ{q*aP8uUFes3eAH~b?S zaauG-)(kbkRUh%~Hk_sBSN}MY2yd^e$L@BOaMMW&8jbs-zGkAYiRS)wq$wu2D0T3R zti%rAyMyzUcz~um|35C#_|TFT#pTx$M_JYNfSHt><7~3 diff --git a/examples/pong.nes b/examples/pong.nes index aae3928c61c2f3e57f72db2758de2149c00441ad..24e406996c30d33ac68dd00d536ea845b48e8503 100644 GIT binary patch literal 24592 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 J!2f#&{s)et5GDWs literal 24592 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 diff --git a/examples/sfx_pitch_envelope.nes b/examples/sfx_pitch_envelope.nes index 032cd182b62ef97c6c3d5a38736691ecb29ea4f3..7075ebdac900fe76fe405f1b1ba284541ac947f1 100644 GIT binary patch delta 133 zcmV;00DAwBzyXlJ0kA#+F$JjrOg6!(0q`WL0EG~#K#c-GsR)e&K&h0445`9}4XLG# z_6MoLjrRws|Ah)n7QvID0TcnAlePgs2na+%0sQZE3fx{V_$iw_dEN_ z|E*j;Vh3AUfC`xxakw!}V3%0RoXXP5%*Jx+!iS~I2{0)(7B%Lj%ob0XCIBU$0VUrp zWl31d^}%`p!$zJ9JSz=)861FyaQR#~2xL9uxiGn#(NbRK(8>QFTz%djT(y*4^FS*L zkhJmgVrAhp=w)$O={NZ?qn42GA)qMZ2kxb;t$a(_TKRo$O_pcc%s6rLWhPczwikyO M4rm`F delta 93 zcmbPmfN{bB#toAh*%+8tGW0S|p2_HG(aN%tp_O?Nha1xbc8R6TsVuF`Y%HfPd|1ky r0Fz>4QDa`pZ1I$70#Nc9Q1abUmW0iZ7(dvuavfqg;I*0Gfl&bfhi)Gm diff --git a/examples/state_machine.nes b/examples/state_machine.nes index 97bbefacfc1c7d56f4505054d9dca03198fe4feb..c02ee943fc8b7c57916996c7eff87c4429faadc6 100644 GIT binary patch delta 439 zcmZXQu}Z^G6o${a$3kxiHcr+hWEBKA>Ci#B8B<@ zNv3A&E4YX*HJeKj(g%^h<8bMOD3@17{fD96PGo*h@XCjYId6#pJe|D-SwB8{HG z{5*mB;BWnHAkZ76_y%1kL#o`UW5B;H4AyS^fbL9+!&)z-{32!Ea7v1&PHhYGowA|LTu_&{s zdTp4lN@hAro&LlM9HW@$UY^P(kNJT0K;SrTE}01^0|}}`Dej+;de?tT9sr@eKb~uu uk4J-EZpjFG ztt+a3f&~nz!z_c>E`*AOJ8^mUz3+YB`{EVED_ESv^-&Im^s+QZe(o~wI3+Qf3EDmi zeCJbqk!JHm5lGWnSIV4T*~m_&q{_?1|Q#pn}eCQ+fLrVq{AwHh~Yr^ JW_|}o1ptY7C+`3N delta 153 zcmbPmfN{bB#tpX^>stkv3Z7({!0f|tXsO_;z3j}bjY|cVa4r>?$-uOdVJ0KfOeUtK zjWd~EC*sS6*LGAF>K v*jUt(^b delta 187 zcmbPmfN{bB#trq1V$43(2bVHxI&dt<6g~mp0F2U|PvAlaXmA6VprU^jFXF$n! YOIZ>&H!>yKvd%ojaNy=GBT<&9^HPBk&%gU`)?*je%px(YOF^OF&tps K&hNm;r~m+EQ7k9` delta 162 zcmbPmfOWzF)`l&N(TsLX8<>4U53Xcr68lwT)fqQ$zs$(U#AwmV0yKwt5r-So1a^s~%&9D`%xo;DE__(Z uoB)$zV^L#X%53qJX#!C48Bp@wQkI16SD6_3ZCPI*VmP3^o!^0xQ2_wy!8axV diff --git a/examples/uxrom_banked_to_banked.nes b/examples/uxrom_banked_to_banked.nes index a8e6eb7105ed6fa16612d6fb5187526dabbb3e34..f675664b451ee384730f5d7d33943fee76b29f66 100644 GIT binary patch delta 96 zcmbPmfO!HCi3+f;Wbol_SjqT-eI?^FK_97&@~`bD3plVU!6jH$GPVjW6+9X60VJdV pQp>WEp;ahhqv1{a%>oVGOq(w^ncGfuuwd&x#BkvBp~V6R6aaNvBn|)o delta 147 zcmbPmfO!HCi3&6O2sEr@d?x540;G-!DpVg#*l2j)exd{CWB~_OF=iiLpm?j$Qo)k} zADDfl8dfs23PDw{Z5C+gX0m8y0g5p%;&5Y{z%H?rIhCcAnT_Stg%3-a6JSzoENaY4 knJu0&O#n(h14_PI%95~oc9Xd++u=hD2bd2p7C4{)0EMA5g#Z8m diff --git a/examples/uxrom_user_banked.nes b/examples/uxrom_user_banked.nes index a71fe7eb0e97d0afda4072d1d720e923e61b3610..b9fb319b7510b6ea2639942fa44ca07fd5469b77 100644 GIT binary patch delta 93 zcmbPmfN25{iCVC(Wbol_SjqT7;+f!5!IRTIfCS_~0xT;TTZMdV8jgX)^T6U}5OIc9 jp@fZ&hwV2DI8-xjKJ4UfJ2Ak5wfYdlf!&+=8yFP;nb;%2 delta 132 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 literal 24592 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 { /// resulting `__ppu_update_used` marker label to decide whether /// to splice the in-NMI palette/nametable update helper. ppu_update_used: bool, + /// Set to true the first time we lower an `IrOp::Mul`. Drives + /// the `__mul_used` marker label so the linker can skip + /// `gen_multiply` (~50 bytes) for programs that never multiply. + /// The optimizer's strength reduction has already turned + /// multiplies by constant powers of two into shifts by the time + /// codegen runs, so this flag only gets set for real runtime + /// multiplications. + mul_used: bool, + /// Set to true the first time we lower an `IrOp::Div` or + /// `IrOp::Mod` (modulo reuses the divide routine). Drives the + /// `__div_used` marker label so the linker can skip `gen_divide` + /// (~70 bytes) for programs that never divide. Divide by + /// constant powers of two has been strength-reduced to shifts + /// by the optimizer, and modulo by constant powers of two to + /// masks; runtime divide only survives for non-constant or + /// non-power-of-two divisors. + div_used: bool, /// Source-location markers produced from [`IrOp::SourceLoc`]. /// Each entry is a `(label_name, span)` pair — the codegen /// emits a unique label-definition pseudo-op at the current @@ -338,6 +355,8 @@ impl<'a> IrCodeGen<'a> { noise_used: false, triangle_used: false, ppu_update_used: false, + mul_used: false, + div_used: false, source_locs: Vec::new(), next_source_loc: 0, emit_source_locs: false, @@ -1175,7 +1194,11 @@ impl<'a> IrCodeGen<'a> { self.store_temp(*d); } IrOp::Mul(d, a, b) => { - // Software multiply: multiplicand in A, multiplier in $02 + // Software multiply: multiplicand in A, multiplier in $02. + // Drop the `__mul_used` marker so the linker knows to + // link the `__multiply` subroutine in — programs that + // don't multiply skip ~50 bytes of runtime. + self.emit_mul_marker(); self.load_temp(*a); self.emit(PHA, AM::Implied); // Save for __multiply contract let b_addr = self.temp_addr(*b); @@ -1222,7 +1245,10 @@ impl<'a> IrCodeGen<'a> { IrOp::Div(d, a, b) => { // Software divide: dividend in A, divisor in $02. // `__divide` returns quotient in A and leaves - // remainder in ZP $03. + // remainder in ZP $03. Emit the `__div_used` marker + // so the linker links the `__divide` subroutine in; + // programs that don't divide skip ~70 bytes. + self.emit_div_marker(); self.load_temp(*a); self.emit(PHA, AM::Implied); let b_addr = self.temp_addr(*b); @@ -1234,7 +1260,10 @@ impl<'a> IrCodeGen<'a> { } IrOp::Mod(d, a, b) => { // Modulo reuses __divide and reads the remainder out - // of ZP $03 afterwards. + // of ZP $03 afterwards. Same `__div_used` marker as + // `IrOp::Div` — modulo doesn't have a separate + // runtime routine. + self.emit_div_marker(); self.load_temp(*a); self.emit(PHA, AM::Implied); let b_addr = self.temp_addr(*b); @@ -2018,6 +2047,32 @@ impl<'a> IrCodeGen<'a> { } } + /// Emit the `__mul_used` marker label at most once per program. + /// The linker uses this to decide whether to link in the + /// `gen_multiply` subroutine. Programs that never multiply (or + /// that only multiply by constant powers of two — those are + /// strength-reduced to shifts) skip the runtime entirely. + fn emit_mul_marker(&mut self) { + if !self.mul_used { + self.emit_label("__mul_used"); + self.mul_used = true; + } + } + + /// Emit the `__div_used` marker label at most once per program. + /// The linker uses this to decide whether to link in the + /// `gen_divide` subroutine. Both `IrOp::Div` and `IrOp::Mod` + /// trigger this marker because modulo reuses the divide + /// routine. Programs whose divide/modulo operations all use + /// constant power-of-two divisors have already been rewritten + /// to shifts / masks by the optimizer and never reach here. + fn emit_div_marker(&mut self) { + if !self.div_used { + self.emit_label("__div_used"); + self.div_used = true; + } + } + /// Emit the MMC3 `__irq_user` handler that dispatches on the /// `(current_state, scanline_step)` pair. Supports multiple /// `on scanline(N)` handlers per state — they fire in ascending diff --git a/src/linker/mod.rs b/src/linker/mod.rs index 94ec068..3f9927a 100644 --- a/src/linker/mod.rs +++ b/src/linker/mod.rs @@ -471,9 +471,22 @@ impl Linker { } } - // Math runtime routines (included always for simplicity) - all_instructions.extend(runtime::gen_multiply()); - all_instructions.extend(runtime::gen_divide()); + // Math runtime routines. Gated on the `__mul_used` / + // `__div_used` marker labels that the IR codegen drops at + // the first `IrOp::Mul` / `IrOp::Div` / `IrOp::Mod`. The + // optimizer rewrites multiplies and divides by constant + // powers of two into shifts (and modulo by constant powers + // of two into masks) before codegen runs, so these markers + // only fire for genuinely runtime-costly math. Programs + // without any surviving mul or div pay zero bytes here. + let has_mul = has_label(user_code, "__mul_used"); + let has_div = has_label(user_code, "__div_used"); + if has_mul { + all_instructions.extend(runtime::gen_multiply()); + } + if has_div { + all_instructions.extend(runtime::gen_divide()); + } // Audio subsystem — linked in whenever user code touched // audio (detected via the `__audio_used` marker emitted by diff --git a/tests/emulator/goldens/audio_demo.audio.hash b/tests/emulator/goldens/audio_demo.audio.hash index 220a846..aaa85d4 100644 --- a/tests/emulator/goldens/audio_demo.audio.hash +++ b/tests/emulator/goldens/audio_demo.audio.hash @@ -1 +1 @@ -7b16a9db 132084 +49c411ce 132084 diff --git a/tests/emulator/goldens/friendly_assets.audio.hash b/tests/emulator/goldens/friendly_assets.audio.hash index d5069db..0dca2ac 100644 --- a/tests/emulator/goldens/friendly_assets.audio.hash +++ b/tests/emulator/goldens/friendly_assets.audio.hash @@ -1 +1 @@ -e73a6a73 132084 +68089bdb 132084 diff --git a/tests/emulator/goldens/noise_triangle_sfx.audio.hash b/tests/emulator/goldens/noise_triangle_sfx.audio.hash index 7efa628..e0af6eb 100644 --- a/tests/emulator/goldens/noise_triangle_sfx.audio.hash +++ b/tests/emulator/goldens/noise_triangle_sfx.audio.hash @@ -1 +1 @@ -ea38044c 132084 +5146a336 132084 diff --git a/tests/emulator/goldens/platformer.audio.hash b/tests/emulator/goldens/platformer.audio.hash index e30b406..359b2c8 100644 --- a/tests/emulator/goldens/platformer.audio.hash +++ b/tests/emulator/goldens/platformer.audio.hash @@ -1 +1 @@ -dded3a6a 132084 +5cc6d0f5 132084 diff --git a/tests/emulator/goldens/pong.audio.hash b/tests/emulator/goldens/pong.audio.hash index da68c82..778c04f 100644 --- a/tests/emulator/goldens/pong.audio.hash +++ b/tests/emulator/goldens/pong.audio.hash @@ -1 +1 @@ -f2dbfbf3 132084 +714f679e 132084 diff --git a/tests/emulator/goldens/war.audio.hash b/tests/emulator/goldens/war.audio.hash index b37a532..7dc1df6 100644 --- a/tests/emulator/goldens/war.audio.hash +++ b/tests/emulator/goldens/war.audio.hash @@ -1 +1 @@ -4e8bdb33 132084 +575adcb6 132084