The standard-library conversion applies -tags purego

Reference index · Summary of this topic

The converted standard library reproduces Go built with -tags purego, not the default amd64/arm64 build. This is a fidelity decision, not a convenience one: Go implements hot cryptographic and hashing functions in hand-written .s assembly, with the Go source carrying only a bodyless declaration (e.g. crypto/sha256/sha256block_decl.go is //go:build (386 || amd64 || s390x || ppc64le || ppc64) && !purego and declares func block(dig *digest, p []byte) with no body). A transpiler works from Go source and cannot convert assembly, so those declarations become throwing stubs — they compile (they are part of the 302-package clean-compile milestone) but cannot run, which blocks Phase-4 validation. A managed C# runtime can never execute those .s files, so “Go built with -tags purego” is a claim go2cs can actually honor, whereas “the default amd64 build” is one it fails on every asm-backed function. The purego build tag selects the portable pure-Go variants instead (sha256block_generic.go is //go:build purego || !(386 || …) and has a real body), replacing ~42 stubs across ~13 packages with convertible code. Adopting the tag drops zero compiling packages (302/302 with the tag, 302/302 without) and unblocks running.

How the tag is applied — and made visible, not magic. -stdlib applies -tags purego by default; an explicit -tags on the same command overrides it verbatim (including -tags= to clear it and reproduce the asm-backed default build). -tests gets the SAME default (resolveBuildTags): a -tests run reconverts the package’s PRODUCTION sources and recompiles them into the test assembly, so it must select the exact same source files the committed corpus was built from — that tree is Go-under-purego. Without it, a package whose asm and pure-Go variants are gated !purego/purego (crypto/subtle’s xor_amd64.go and xor_generic.go, both declaring func xorBytes) has BOTH files converted and collides (CS0111 duplicate member), and the regenerated production .cs diverges from the committed purego emission. The default is scoped to -stdlib and -tests — the whole-library corpus and its test validation — because the purego claim is about what the corpus is, so it must hold for every such invocation (direct or via any script), not depend on remembering a flag. Scripts-only (having the deploy/convert scripts pass the tag) was rejected for that reason: the canonical go2cs -stdlib invocation is used throughout the repo and docs, and a script-gated tag would make a bare -stdlib produce a different corpus. Crucially the default does not touch -recurse end-user conversions or single-file/dir conversions — there the user’s own build tags govern, and a hidden default would be least welcome. Discoverability is threefold: the -stdlib and -tags --help text both state the default, and the stdlib converter prints the effective tags at the start of every run (Applying build tags: purego (default; pass -tags to override), or … none (-tags= cleared the purego default) when overridden). The tag threads into both the go/packages loader and the converter’s own BuildConstraintEvaluator (seeding only the loader would silently re-exclude the file the tag just selected).

purego is not the only spelling of this decision — math_big_pure_go is the same one. The tag set the corpus applies is purego, math_big_pure_go, because purego is a convention the crypto and hashing packages adopted, not a language rule, and math/big predates it. math/big gates its own portable fallbacks on math_big_pure_go: arith_decl.go is //go:build !math_big_pure_go and declares eight bodyless //go:linkname///go:noescape functions — addVV, subVV, addVW, subVW, shlVU, shrVU, mulAddVWW, addMulVVW — whose bodies are arith_$GOARCH.s, while arith_decl_pure.go is //go:build math_big_pure_go and forwards each to the _g pure-Go implementation that already sits in arith.go. With only purego seeded the converter selected the declaration file, so all eight became throwing partial stubs: math/big compiled clean and could not run — every big.Int, big.Float and big.Rat arithmetic path raised on first use. (It surfaced as a single time verdict, TestTruncateRound → big.Int.Mul → mulAddVWW, a good illustration of how little a visible symptom says about the size of its root.) Selecting arith_decl_pure.go also drops arith_amd64.go, whose only declaration is support_adx — read exclusively by the assembly that is not there either. The general rule this states: a package’s portable-fallback tag belongs in the default set whatever it is called; the taxonomy below classifies by whether a portable sibling exists, not by whether it happens to be spelled purego. defaultStdLibBuildTags is content-pinned by TestDefaultStdLibBuildTagsContent, which fails on an undocumented addition as well as a silent removal.

The asm-stub taxonomy. A Go declaration that a platform build binds to assembly falls into one of three buckets, each with a distinct treatment:

Known accepted divergence — crypto/elliptic P256 Inverse panics under purego

One package regresses behaviorally under purego relative to the default build, and it is recorded as a known divergence, not fixed, because matching it is fidelity: real Go panics there too under -tags purego. Upstream crypto/elliptic/nistec_p256.go is gated //go:build amd64 || arm64 without && !purego, while its dependency crypto/internal/nistec/p256_ordinv.go is (amd64 || arm64) && !purego. So under purego the ordinv fallback p256_ordinv_noasm.go — which returns errors.New("unimplemented") — is selected, and crypto/elliptic’s Inverse (reached via the deprecated invertible interface) treats any error from the nistec scalar inverse as an invariant violation and panics crypto/elliptic: nistec rejected normalized scalar. (crypto/ecdsa handles the same error correctly with a Fermat-little-theorem fallback, so ECDSA is unaffected.) Verified against real Go: the default build returns a valid inverse, and go run -tags purego panics with that exact message. This is an upstream Go inconsistency in a largely-superseded package; go2cs reproduces Go-under-purego faithfully, so the panic is expected. It is a divergence from the default (-tags=) build only, which binds the asm ordinv.


← Source Generators · Index · Manually-Converted Declarations →