build: add lg_no_http to drop net/http from the runtime (#652) - #658
Conversation
installHttpNS is registered from an init(), so net/http — and crypto/tls and crypto/x509 behind it — is reachable in every binary linking pkg/rt, whether or not the program ever opens a socket. The linker has no way to see it is unused, which is part of why an AOT hello-world and an AOT fib(35) differ by 16 bytes. Build with -tags lg_no_http to leave it out. On darwin/arm64, cmd/lg-runtime goes 17,594,402 -> 12,175,106 bytes, and 12,131,874 -> 8,382,754 with -s -w: about 30% either way, and `go tool nm` finds zero net/http or crypto symbols in the tagged binary. That is more than the ~1.9 MB the issue estimated from symbol sizes, because the symbol table undercounts what a package drags in. TinyGo's net/http does not compile, so that lane has always built without the namespace and gains nothing new here — but it already had exactly the stub this tag needs, so http_tinygo.go becomes http_stub.go and serves both lanes off `tinygo || lg_no_http`. One stub, two reasons. This is the http half of #652 only. The other tagged tree named there, go/parser and go/printer via pkg/rt/gogen.go, is left alone because #628 and #657 are both in flight on gogen. The no-http-build CI job exists because nothing in the default build compiles http_stub.go; without it a signature change in http.go would break the tagged lane silently. It also asserts net/http stayed out, so the size win cannot regress unnoticed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nooga
left a comment
There was a problem hiding this comment.
LGTM. Verified the headline claim directly — built cmd/lg-runtime both ways and checked with go tool nm: 2735 net/http/crypto/tls symbol hits by default, 0 under -tags lg_no_http. No leftover unconditional imports outside the two intentionally-tagged files. Manually exercised the stub's three documented behaviors (http/get unresolved, URL-reader error, bare-string slurp unaffected) and they match what the PR/docs claim.
Verified: go build/go test clean on both tag lanes; go vet/gofmt clean.
One gap worth a fast follow-up rather than blocking this: the new no-http-build CI job only checks the binary links/boots and that the symbols are absent — it never asserts the stub's actual error text or that the http namespace is genuinely unresolvable. That's currently verified by hand (mine, and presumably the author's). A small -tags lg_no_http Go test asserting those error strings would close the loop.
Minor nit: http_stub.go's error message ends with "(under tinygo, use JS fetch)" for both the tinygo and lg_no_http tags — confusing for a plain lg_no_http embedder who has nothing to do with tinygo.
Part of #652 — the http half.
Why
installHttpNSis registered from aninit(). That makesnet/http, andcrypto/tlsandcrypto/x509behind it, reachable in every binary that linkspkg/rt, whether or not the program ever opens a socket. The linker cannot prove it unused, so it stays.This is one concrete piece of the flat AOT floor described in #652, where a hello-world and a fib(35) binary differ by 16 bytes. Nothing gets eliminated because nothing is unreachable.
The tag
go build -tags lg_no_httpleaves it out.Measured on darwin/arm64 against
cmd/lg-runtime:lg_no_http-s -wlg_no_http,-s -wgo tool nmfinds 3,083net/http,crypto/tls, andcrypto/x509symbols in the default binary and zero in the tagged one.That is well above the ~1.9 MB I estimated on the issue from
go tool nm -sizetotals. The symbol table undercounts what a package drags in: type metadata, pclntab, and rodata attributable to those trees are not in the per-symbol sizes I grouped.Behavior under the tag
The
httpnamespace is not installed, sohttp/get,http/serve, and the rest do not resolve.io/readeron anio/urlrecord returns an error rather than fetching. Everything else is untouched: file and resourceslurp,io/readeron a path, and the remainder ofiobehave identically. A bare-string(slurp "https://…")was already treated as a file path before this change, so that is unchanged too.The stub file
TinyGo's
net/httpdoes not compile, so that lane has never had a workinghttpnamespace and gains nothing new from the tag. It already carried exactly the stub this needs, sohttp_tinygo.gobecomeshttp_stub.goundertinygo || lg_no_httpand serves both lanes.Scope
The http half of #652 only. The other build-tag-reachable tree named there,
go/parserandgo/printerviapkg/rt/gogen.goat about 232 KB, is left alone since #628 and #657 are both in flight on gogen. It composes as a separate tag later if that shape is wanted.I went with a narrow per-feature tag rather than one umbrella "runtime-only" tag. An umbrella forces all-or-nothing and its meaning drifts as things are added to it;
lg_no_httpcomposes with whatever comes next. Happy to fold it into a broader tag instead if you'd rather have one knob.CI
Nothing in the default build compiles
http_stub.go, so a signature change inhttp.gowould break the tagged lane silently.no-http-buildbuilds it, boots it, and assertsnet/httpstayed out, the last part so the size win cannot regress unnoticed.Checks
gofmtandgo vetclean on both tag lanes.go test ./pkg/...passes with the tag. The default suite is unchanged apart fromTestGogenAOTDiff, which needs a generated tree absent from a fresh worktree and fails the same way on an untouched checkout. Cross-builds pass for linux/amd64, js/wasm, plan9/amd64, and wasip1/wasm, each with and without the tag.tinygo build -target=wasistill produces a booting module after the rename.