From 768064320614d124b769435454779fc90ab52165 Mon Sep 17 00:00:00 2001 From: Tinic Uro Date: Wed, 30 Sep 2026 09:10:04 +0000 Subject: [PATCH] size: the micro stack caches no name and fragments no packet Three size decisions for the 2026-09-30 pass, each one option apart from the same tree at 994aa0ed and measured on the pinned toolchain: micro -5,184 bytes of bsdsocket.library (206,856 -> 201,672) and 2,072 bytes of resident AmiNetStack AMINETXDUO_DNS_CACHE=OFF -3,236 alone AMINETXDUO_IP_FRAGMENTATION=OFF -1,892 alone both -5,184 minimal -3,228 bytes (241,288 -> 238,060): AMINETXDUO_HOT_O2=OFF, which pays that drawer's whole overshoot. The full drawer keeps the hot list -- nothing in this tree has ever measured what it buys, and the full drawer is where it was aimed. micro is 201,672 against a 205,000 budget, minimal 238,060 against 239,000. The full drawer's 2,828-byte overshoot is untouched: it is shared shim code, not a profile feature. The resolver keeps no answer cache in micro, so gethostbyname() and gethostbyaddr() ask the name server every time instead of once per TTL. That is a size decision with a price on a slow link, and it is the one drawer that pays it. Fragmentation off takes the transmit splitter and the receive reassembler out together, because NetX Duo has no receive-only arm. It needs shim work to be honest: with no fragmenter to hand an oversize packet to, nx_ip_driver_packet_send() releases it and returns NX_SUCCESS, so the sender is told the bytes went. bsd_nofrag_maxdgram() (transfer.c) caps UDP and raw sends at the smallest attached interface MTU less the headers and refuses the rest with EMSGSIZE, and src/netstack/netstack.c skips nx_ip_fragment_enable() rather than reporting the NX_NOT_ENABLED it now answers as a fault at every stack start. TCP is unaffected in both directions: a segment's size follows the MTU. Two options and the micro preset carry it; the guide and the changelog say what the user loses. CROSS_CONFIGS gets a nofrag arm because that combination builds the shim's smallest-MTU loop for four interfaces with IPv6 on, which no other arm compiles. The full flag space was swept the same day and is empty; the results, including why -mregparm is unusable as a flag though the attribute form is not, are in CMakeLists.txt. -Oz and -fomit-frame-pointer are each worth exactly nothing, -msmall-code is 800 bytes worse, and -fbaserel does not link. There is no floating-point code in any image. Not addressed here: anxgenet.device (29,096 against 29,000) and usergroup.library (10,172 against 10,000) are over their budgets in all three arms, before this change as after it. Co-Authored-By: Claude Code --- CHANGELOG.md | 10 +++ CMakeLists.txt | 102 ++++++++++++++++++++++++++++- CMakePresets.json | 12 ++++ docs/RESEARCH.md | 2 +- docs/user/AmiNetXDuo.guide | 16 ++++- port/netxduo-amiga/inc/nx_user.h | 7 +- src/bsdsocket/bsdsocket_internal.h | 27 ++++++++ src/bsdsocket/raw.c | 10 ++- src/bsdsocket/transfer.c | 46 ++++++++++++- src/netstack/netstack.c | 19 ++++++ tools/check-image-size.sh | 12 ++++ tools/ci.sh | 12 ++++ 12 files changed, 268 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 319199d1..f3329f38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,16 @@ version at the top when it merges. ## Unreleased +- The micro stack keeps no list of the names it has resolved: every + `gethostbyname`/`gethostbyaddr` asks the name server again. It also neither + splits nor joins an IPv4 packet, so a datagram larger than the link MTU is + refused with `EMSGSIZE` instead of being fragmented -- from `sendto` and + from a raw socket -- and a fragment that arrives is dropped. TCP is + unaffected in both directions. The two come to 5,184 bytes of + `bsdsocket.library` and 2 KB of the stack's resident memory. +- `bsdsocket.library` is 3,228 bytes smaller in the minimal drawer, whose + eight hottest NetX Duo translation units no longer build at `-O2` after the + `-Os` the rest of the tree uses. - `WaitSelect` re-arms a large timeout as the true remainder instead of a wrapped 32-bit value. A wait accepted for more than ~71 minutes, when a kept timer request fired early and the call had to re-arm the balance, multiplied diff --git a/CMakeLists.txt b/CMakeLists.txt index ee46cb32..c5fb93aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -191,6 +191,42 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) message(STATUS "No CMAKE_BUILD_TYPE given; using Release") endif() +# THE REST OF THE FLAG SPACE, swept 2026-09-30 against the micro drawer in its +# shipped shape (201,672 bytes at 994aa0ed with the preset's DNS-cache and +# fragmentation options off). Every number is one flag apart from that build +# on the pinned toolchain, m68k-amigaos-gcc 16.2.0b, and each flag was read +# back out of compile_commands.json rather than out of the CMake cache: +# +# -Oz 201,672, exactly nothing. Byte-identical output. +# -msmall-code 202,472, 800 bytes WORSE. +# -fbaserel does not link. cmake/check-pcrel-branches.cmake +# refuses anxwifipi.device: a 32-bit PC-relative branch +# lands inside a function (docs/RESEARCH.md 25). +# A library has no a4 on entry either. +# -mregparm 189,872, 11,800 bytes smaller, and unusable as a flag: +# it passes arguments in registers for EVERY call, +# including calls out of this code to Exec, DOS and +# SANA-II devices, which take them on the stack. The +# disassembly of `extern void ext(int,int); ext(1,2);` +# is moveq #2,d1 / moveq #1,d0 / jmp ext under it and +# pea 2 / pea 1 / jsr ext / addq.l #8,sp without. +# __attribute__((regparm(N))) does exist and is selective +# -- an annotated function takes d0/d1 while an +# unannotated one beside it still reads 4(sp) -- so a +# chosen set of functions can take it without moving the +# ABI of anything they call. That is a code change and a +# measurement, not a flag. +# -fomit-frame-pointer already stated in CMAKE_C_FLAGS_INIT, and -Os implies +# it: removing it from the command line gives 201,672, +# the same bytes to the byte. +# +# Floating point was checked the same day and there is none: the shipped +# disassembly contains no FPU instruction (the same scan finds 16,594 integer +# ones), no libgcc float member is pulled into the link (the map's archive +# members), and the only float or double in the tree that builds into an Amiga +# image is none -- the hosts in src/rfb/host are not images, and the rest of +# the hits are the word in comments. + # ---------------------------------------------------------------- options --- option(AMINETXDUO_IPV6 "Build the IPv6 dual stack" ON) @@ -431,6 +467,23 @@ set(AMINETXDUO_DNS_PACKETS "4" CACHE STRING add_compile_definitions( "NX_DNS_PACKET_POOL_SIZE=(${AMINETXDUO_DNS_PACKETS}*(NX_DNS_PACKET_PAYLOAD+sizeof(NX_PACKET)))") +# Keep the answers the resolver has already been given, for their TTL. +# +# NX_DNS_CACHE_ENABLE is what compiles the lookup table in, and +# nx_dns_cache_initialize() is what arms it -- without that call the code is +# inert, which is why src/netstack/netstack_dns.c guards the call on the same +# macro. OFF in the micro drawer: 3,236 bytes of the shipped bsdsocket.library +# (206,856 -> 203,620, micro's preset shape, one flag apart, 2026-09-30), and +# 3,224 of minimal's (238,060 -> 234,836, HOT_O2 already off in both). It +# also takes the 2 KB cache buffer out of AmiNetStack, which is RAM the machine +# keeps for as long as the stack is up. The price is a name-server round trip +# per lookup instead of per TTL, which a micro machine on a slow link pays on +# every gethostbyname, so it is a size decision and not a free one. +option(AMINETXDUO_DNS_CACHE "Keep resolved names for their TTL" ON) +if(NOT AMINETXDUO_DNS_CACHE) + add_compile_definitions(AMINETXDUO_DNS_CACHE_OFF) +endif() + # Half-open connections held with their options recorded exactly. # # NetX Duo's own number is 512, chosen against the four megabytes this project @@ -843,6 +896,40 @@ else() add_compile_definitions(NX_DISABLE_IGMP) endif() +# IPv4 fragmentation and reassembly, both directions. +# +# NX_DISABLE_FRAGMENTATION removes the transmit splitter and the receive +# reassembler together, because NetX Duo has no receive-only arm. That is +# 1,892 bytes of the shipped bsdsocket.library in micro's shape (206,856 -> +# 204,964), 2,024 in minimal's (241,288 -> 236,036, which is 3,228 less again +# for HOT_O2) and 3,560 in the full drawer's (368,828 -> 365,268), each one flag +# apart in the same tree on 2026-09-30. The three are net of the shim's +# refusal below, which the micro figure includes. What it costs is two +# different things: +# +# o a datagram larger than the interface MTU can no longer be SENT; sendto +# and the raw send both report EMSGSIZE where they used to split. That +# refusal is the shim's: NetX releases an oversize packet without an +# error once there is no fragmenter to take it, so a sender would +# otherwise be told the bytes had gone. bsd_nofrag_maxdgram() +# (src/bsdsocket/transfer.c) is that refusal, and it is why the size +# below is the option's cost net of a few dozen bytes of shim. +# o one that arrives fragmented is counted and released rather than +# reassembled. +# +# TCP is unaffected in both directions: the segment size is derived from the +# MTU, so no segment this stack sends is ever split, and a peer's segments are +# that peer's MSS. What breaks is a large UDP payload -- an 8 KB NFS read, a +# jumbo reply -- which is why this is off in micro and on everywhere else. +# +# nx_ip_fragment_enable() answers NX_NOT_ENABLED instead of failing when this +# is off, so src/netstack/netstack.c skips the call and its warning rather than +# logging a failure at every stack start. +option(AMINETXDUO_IP_FRAGMENTATION "Fragment and reassemble IPv4 packets" ON) +if(NOT AMINETXDUO_IP_FRAGMENTATION) + add_compile_definitions(NX_DISABLE_FRAGMENTATION) +endif() + option(AMINETXDUO_TESTS "Build the on-Amiga test harnesses" ON) # ASan and UBSan over the host test tier. The target has no MMU and no @@ -1550,7 +1637,20 @@ target_include_directories(threadx PUBLIC # and nx_tcp_socket_packet_process does exactly that once per received packet. # # AMINETXDUO_HOT_O2=OFF puts them back at -Os, which is the comparison to make -# when deciding whether the size is worth it. +# when deciding whether the size is worth it. Measured 2026-09-30, one flag +# apart in the same tree: 3,228 bytes of bsdsocket.library in minimal's shipped +# shape (241,288 -> 238,060) and 3,456 in the full drawer's (368,828 -> +# 365,372). +# +# MICRO AND MINIMAL BOTH TAKE IT OFF; the full drawer keeps it. Off in micro +# since micro existed. Off in minimal since 2026-09-30, where it pays that +# drawer's whole overshoot by itself -- a size decision taken without a +# throughput A/B, which this tree does not have for the option: what the hot +# list buys is the inlining above, and nothing here measures it. A minimal +# build that wants the old behaviour back gets it with +# -DAMINETXDUO_HOT_O2=ON, and one `tools/ci.sh rate` run on an A600 or a 68020 +# would turn the trade into a measurement. The full drawer, which is where the +# option was aimed, keeps it either way. # --------------------------------------------------------------------------- option(AMINETXDUO_HOT_O2 "Build the profiled hot paths at -O2" ON) diff --git a/CMakePresets.json b/CMakePresets.json index 7360a049..cdf55c35 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -36,6 +36,10 @@ "type": "BOOL", "value": "OFF" }, + "AMINETXDUO_HOT_O2": { + "type": "BOOL", + "value": "OFF" + }, "AMINETXDUO_IPV6": { "type": "BOOL", "value": "OFF" @@ -88,10 +92,18 @@ "value": "OFF" }, "AMINETXDUO_CPU": "68000", + "AMINETXDUO_DNS_CACHE": { + "type": "BOOL", + "value": "OFF" + }, "AMINETXDUO_HOT_O2": { "type": "BOOL", "value": "OFF" }, + "AMINETXDUO_IP_FRAGMENTATION": { + "type": "BOOL", + "value": "OFF" + }, "AMINETXDUO_IPV6": { "type": "BOOL", "value": "OFF" diff --git a/docs/RESEARCH.md b/docs/RESEARCH.md index 778137ee..d6edc145 100644 --- a/docs/RESEARCH.md +++ b/docs/RESEARCH.md @@ -58,7 +58,7 @@ is in git history, `git show 2b54025b:docs/RESEARCH.md`. | 55 | `AddDomainNameServer()` as ENOSYS made Roadshow's `AddNetInterface` return rc 20 after doing everything right. A server must land in both the NetX Duo DNS client and `ns_Config.resolver`. | `src/bsdsocket/roadshow.c:165` | current | | 55 | `IP_HDRINCL` is translated, not passed through: TOS, TTL, protocol and destination are mapped onto `nxd_ip_raw_packet_send()` and the caller's header stripped. | `src/bsdsocket/raw.c:635` | current | | 55 | ARP entries must be emitted as `RTF_LLINFO` routes or Roadshow's `arp` prints nothing. | `src/bsdsocket/routing.c:464` | see code | -| 57 | `-Os` tree-wide, -24% size at no throughput cost, and it must be set after `project()` or `Compiler/GNU` appends `-O3`. | `CMakeLists.txt:43` | superseded in part, eight profiled NetX Duo files build at `-O2` via `AMINETXDUO_HOT_O2` | +| 57 | `-Os` tree-wide, -24% size at no throughput cost, and it must be set after `project()` or `Compiler/GNU` appends `-O3`. | `CMakeLists.txt:43` | superseded in part, eight profiled NetX Duo files build at `-O2` via `AMINETXDUO_HOT_O2`; that is now the default drawer only, minimal and micro set it OFF (2026-09-30, 3,228 bytes in minimal, 3,456 in the full drawer) | | 58 | Sixteen ChaCha20 words into 8 data plus 7 address registers and one stack slot, moved with `EXG` at 4.01 cycles against 13.80 for a stack spill. | `src/crypto68k/c68k_chacha20.S` | current | | 63 | A frame the emulator's host sends to the guest's MAC never returns to that NIC's pcap, so the peer in any bridged test must be a third machine. | `tests/perf/run-fitzbench.sh:118` | see code | | 63.4 | A crashed WinUAE 6.0.3 on a bridged run is an oversized coalesced receive frame in `gotfunc2()`, not our stack. A dead emulator must report `reason=crash`, not rc 0. | `tools/winuae-run.sh:615` | current | diff --git a/docs/user/AmiNetXDuo.guide b/docs/user/AmiNetXDuo.guide index 80038275..3f61bd0e 100644 --- a/docs/user/AmiNetXDuo.guide +++ b/docs/user/AmiNetXDuo.guide @@ -143,7 +143,13 @@ micro stack also leaves out getaddrinfo/getnameinfo, ancillary data, out-of-band TCP and less-used administration calls, carries one interface and no route or address-allocation calls, and holds 243 KB; - it is the one for a 1 MB machine. DHCP, the classic resolver and raw sockets + it is the one for a 1 MB machine. It also keeps no list of the names it + has already resolved, so every lookup asks the name server again, and it + neither splits nor joins an IP packet: a datagram larger than the link's + MTU is refused with EMSGSIZE instead of being fragmented, and one that + arrives fragmented is dropped. TCP is unaffected in both directions, + because a segment's size comes from the MTU and never needs splitting. + DHCP, the classic resolver and raw sockets are in every stack: a minimal or micro machine still leases its address and can run ping or traceroute. @@ -2811,6 +2817,14 @@ classic resolver and raw sockets; route, address-allocation and less-used public administration calls are omitted there. Its private status/control calls remain for the supplied interface tools. + o @{b}The micro stack caches no name and fragments no packet.@{ub} + Every gethostbyname() and gethostbyaddr() goes to the name server, so + a name is looked up again on each call. A datagram larger than the + link MTU is refused with EMSGSIZE rather than split, both from sendto() + and from a raw socket, and an inbound fragment is dropped rather than + reassembled -- so a peer's large UDP reply, an 8 KB NFS-over-UDP read + among them, does not work there. TCP is unaffected in both directions: + its segment size follows the MTU and never needs splitting. o bind() to a particular local address is enforced for TCP listeners and UDP delivery. An address this machine does not have is refused with EADDRNOTAVAIL. diff --git a/port/netxduo-amiga/inc/nx_user.h b/port/netxduo-amiga/inc/nx_user.h index f8796a6a..5f35ee70 100644 --- a/port/netxduo-amiga/inc/nx_user.h +++ b/port/netxduo-amiga/inc/nx_user.h @@ -286,8 +286,13 @@ ULONG _nx_amiga_handshake_millis(VOID); /* -------------------------------------------------------------- resolver, */ /* Inert until nx_dns_cache_initialize() is called; the cache buffer belongs to - the caller (src/netstack/netstack_dns.c). */ + the caller (src/netstack/netstack_dns.c). AMINETXDUO_DNS_CACHE=OFF leaves the + macro undefined, which drops the table and its lookup code from nx_dns.c; + netstack_dns.c guards its call on the same macro, so nothing else moves. The + micro drawer takes that, at a name-server round trip per lookup. */ +#ifndef AMINETXDUO_DNS_CACHE_OFF #define NX_DNS_CACHE_ENABLE +#endif /* wait_option is a PER-QUERY timeout spent this many times over every configured server with the DNS mutex held. The retransmission ladder lives diff --git a/src/bsdsocket/bsdsocket_internal.h b/src/bsdsocket/bsdsocket_internal.h index 55152e13..9e09d9ac 100644 --- a/src/bsdsocket/bsdsocket_internal.h +++ b/src/bsdsocket/bsdsocket_internal.h @@ -1231,6 +1231,33 @@ BOOL bsd_udp_accepts_received_packet(const AmiSocket *sock, const NX_PACKET *packet); ULONG bsd_udp_available(const AmiSocket *sock); +#ifdef NX_DISABLE_FRAGMENTATION +/* The largest payload this stack can put on the wire with `overhead' bytes of + * headers above it, for a build with no fragmenter: the smallest attached link + * MTU, less those headers. transfer.c owns it; raw.c asks it too, because both + * senders used to hand NetX a datagram it would have split. + * + * Above that size NetX releases the packet without an error -- + * nx_ip_driver_packet_send.c takes the else branch of the fragmenter's NULL + * pointer test and returns -- so nx_udp_socket_send() reports success and the + * bytes never leave. A sender has to be refused here or it is lied to. + * + * The egress interface is not this layer's to know: NetX picks it from the + * route for the destination, and asking costs a route lookup and the IP mutex + * on every send. The smallest MTU is a read over at most NX_MAX_PHYSICAL_ + * INTERFACES words instead, and it is exact for a stack with one interface -- + * the micro drawer, AMINETXDUO_MAX_INTERFACES=1, the only shipping build that + * compiles the fragmenter out. With more than one interface, a machine whose + * links differ in MTU is refused a datagram that would have fit the egress + * one: wrong in the safe direction, and never silent. + * + * -1 means not even the headers fit. A link that has no MTU yet is skipped + * rather than counted, and a stack where none has one keeps the 65,535 cap: + * no link has an address then either, so the send is about to answer + * unreachable and EMSGSIZE would be the wrong answer to give it. */ +LONG bsd_nofrag_maxdgram(NX_IP *ip, ULONG overhead); +#endif + #ifdef AMINETXDUO_RX_DIRECT_COMPLETE /* as_RxDState. IDLE is zero so a MEMF_CLEAR socket starts unarmed. */ #define BSD_RXD_IDLE 0 diff --git a/src/bsdsocket/raw.c b/src/bsdsocket/raw.c index 5e473006..9945fa31 100644 --- a/src/bsdsocket/raw.c +++ b/src/bsdsocket/raw.c @@ -789,12 +789,20 @@ LONG bsd_raw_send_packet(struct AmiSocketBase *base, AmiSocket *sock, /* 65,535 less the IP header is what BSD lets a raw sender hand over; the rest is fragmented, here as there (nxd_ip_raw_packet_source_send() passes NX_FRAGMENT_OKAY). The cap used to be the link MTU, and - `ping -s 1473' was refused, never sent -- see bsd_udp_maxdgram(). */ + `ping -s 1473' was refused, never sent -- see bsd_udp_maxdgram(). + It is the link MTU again where there is no fragmenter to hand the + datagram to; NetX would drop it without saying so, and `ping -s 2000' + would be told it had been sent. */ { ULONG overhead = (dest.nxd_ip_version == NX_IP_VERSION_V6) ? 40UL : 20UL; +#ifdef NX_DISABLE_FRAGMENTATION + LONG maxraw = bsd_nofrag_maxdgram(ip, overhead); + if (maxraw < 0 || (LONG)handed->nx_packet_length > maxraw) +#else if (handed->nx_packet_length + overhead > 65535UL) +#endif { nx_packet_release(handed); return bsd_fail(base, AMI_EMSGSIZE); diff --git a/src/bsdsocket/transfer.c b/src/bsdsocket/transfer.c index cf4a729a..600a8e80 100644 --- a/src/bsdsocket/transfer.c +++ b/src/bsdsocket/transfer.c @@ -875,8 +875,13 @@ static LONG bsd_send_tcp(struct AmiSocketBase *base, AmiSocket *sock, * the last place that kept a datagram from reaching it. Measured 2026-09-19 * on the A1200 through a 1,400-byte hop: -s 1472 crossed (the router * fragmented it), -s 1473 and up were refused here, never sent. + * + * A build with no fragmenter (AMINETXDUO_IP_FRAGMENTATION=OFF, the micro + * drawer) has nothing to reach, and NetX would take the datagram apart + * silently instead of refusing it, so the cap goes back to the link MTU: + * bsd_nofrag_maxdgram(). */ -static LONG bsd_udp_maxdgram(const NXD_ADDRESS *addr) +static LONG bsd_udp_maxdgram(NX_IP *ip, const NXD_ADDRESS *addr) { ULONG overhead; @@ -890,8 +895,45 @@ static LONG bsd_udp_maxdgram(const NXD_ADDRESS *addr) overhead = (ULONG)NX_IPv4_UDP_PACKET - (ULONG)NX_PHYSICAL_HEADER; } +#ifdef NX_DISABLE_FRAGMENTATION + return bsd_nofrag_maxdgram(ip, overhead); +#else + (VOID)ip; return (LONG)(65535UL - overhead); +#endif +} + +#ifdef NX_DISABLE_FRAGMENTATION +/* See the declaration in bsdsocket_internal.h for what this answers and why + the smallest MTU is the one it answers with. */ +LONG bsd_nofrag_maxdgram(NX_IP *ip, ULONG overhead) +{ + ULONG smallest = 0UL; + UINT i; + + if (ip == NULL) + return (LONG)(65535UL - overhead); + + for (i = 0; i < (UINT)NX_MAX_PHYSICAL_INTERFACES; i++) + { + ULONG mtu = ip->nx_ip_interface[i].nx_interface_ip_mtu_size; + + if (ip->nx_ip_interface[i].nx_interface_valid == 0 || mtu == 0UL) + continue; + + if (smallest == 0UL || mtu < smallest) + smallest = mtu; + } + + if (smallest == 0UL) + return (LONG)(65535UL - overhead); + + if (smallest <= overhead) + return -1; + + return (LONG)(smallest - overhead); } +#endif /* * The interface a received datagram arrived on. @@ -1178,7 +1220,7 @@ static LONG bsd_send_udp(struct AmiSocketBase *base, AmiSocket *sock, return bsd_fail(base, AMI_ENETUNREACH); } - maxdgram = bsd_udp_maxdgram(addr); + maxdgram = bsd_udp_maxdgram(ip, addr); if (len > maxdgram) return bsd_fail(base, AMI_EMSGSIZE); diff --git a/src/netstack/netstack.c b/src/netstack/netstack.c index 730ec00f..1ace320a 100644 --- a/src/netstack/netstack.c +++ b/src/netstack/netstack.c @@ -733,10 +733,29 @@ static LONG ami_ns_create_ip(AmiNetStack *ns) * Inbound reassembly, both families; until this call a fragment is counted and * released. It enables transmit fragmentation in the same call because there * is no receive-only arm. + * + * AMINETXDUO_IP_FRAGMENTATION=OFF compiles the whole thing out, and + * nx_ip_fragment_enable() then answers NX_NOT_ENABLED rather than failing -- + * there is nothing to enable and nothing wrong -- so the call and its + * warning are skipped rather than taken and reported as a fault at every + * stack start. + * + * What the call would have set is nx_ip_fragment_assembly, the pointer the + * receive path tests before it queues an inbound fragment + * (nx_ipv4_packet_receive.c:575, :448 on the NAT path), and + * nx_ip_fragment_processing for the transmit half, tested the same way on + * the way out (nx_ip_driver_packet_send.c:491, :238). Both stay NULL, so + * an inbound fragment takes the drop branch -- the same branch it took + * before this call was reached -- and a datagram over the MTU fails to + * send instead of being split. */ +#ifdef NX_DISABLE_FRAGMENTATION + AMI_INFO("netstack: IPv4 fragmentation is compiled out"); +#else status = nx_ip_fragment_enable(&ns->ns_Ip); if (status != NX_SUCCESS) AMI_WARN("netstack: nx_ip_fragment_enable failed (%ld)", (long)status); +#endif #if AMI_CFG_MAX_ATTACHED > 1 /* diff --git a/tools/check-image-size.sh b/tools/check-image-size.sh index b2f1ae9f..d72a1020 100755 --- a/tools/check-image-size.sh +++ b/tools/check-image-size.sh @@ -135,6 +135,11 @@ BUDGETS=( # 237,348 with the #67 dormant-slot eviction (see the default row), # 2026-09-26. # 238,240 with the bounded S2_OFFLINE (#90, see the default row). + # 241,288 at the time of the 2026-09-30 size pass (994aa0ed), 2,288 over + # this budget, and 238,060 after it: AMINETXDUO_HOT_O2=OFF in this drawer, + # worth 3,228 (CMakeLists.txt, the hot translation units). The full + # drawer's overshoot was not addressed: it is shared shim code, not a + # profile feature. "minimal:src/bsdsocket/bsdsocket.library:239000" "minimal:src/netdev/anxnet.device:46000" "minimal:src/netdev/anxgenet.device:29000" @@ -155,6 +160,13 @@ BUDGETS=( # 203,264 with the #67 dormant-slot eviction (see the default row), # 2026-09-26. # 204,052 with the bounded S2_OFFLINE (#90, see the default row). + # 206,856 at the time of the 2026-09-30 size pass (994aa0ed), 1,856 over + # this budget, and 201,672 after it: the resolver's answer cache compiled + # out (-3,236 alone) and the fragmenter with it (-1,892 alone, including + # the bsd_nofrag_maxdgram() the shim needs to refuse a datagram NetX can + # no longer split; -5,184 with both, 56 bytes better than their sum). It + # also gives back 2,072 bytes of the resident AmiNetStack, which is not in + # this number. "micro:src/bsdsocket/bsdsocket.library:205000" "micro:src/netdev/anxnet.device:46000" "micro:src/netdev/anxgenet.device:29000" diff --git a/tools/ci.sh b/tools/ci.sh index 42797d12..c3fc6587 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -67,6 +67,18 @@ CROSS_CONFIGS=( # and the held-run code alone compiles out. (RX_VERIFY=OFF above turns # both optional layers off with it.) "nogro:-DAMINETXDUO_GRO=OFF" + # A build with no fragmenter. AMINETXDUO_IP_FRAGMENTATION=OFF takes the + # transmit splitter and the receive reassembler out of the vendored tree + # (2,024 bytes of bsdsocket.library in minimal's shape, 1,892 in micro's, + # and 3,560 in this arm's own), and micro is where it ships. It gets an + # arm of its own because the shim + # has to compensate for it: with no fragmenter to hand an oversize packet + # to, NetX releases it without an error and the sender is told the bytes + # went, so bsd_nofrag_maxdgram() (src/bsdsocket/transfer.c) refuses them + # first. That is code no other arm compiles: micro builds it for one + # interface with IPv6 off, this arm for four with IPv6 on, which is the + # only place its smallest-MTU loop and its IPv6 header arithmetic build. + "nofrag:-DAMINETXDUO_IP_FRAGMENTATION=OFF" # The three TCP option flags that change the layout of NX_TCP_SOCKET and # are in no arm above. SACK=OFF did not compile at all until it was built # here, and with all three on by default these arms are also the only place