Repository navigation
schema/field: the loader lists a GoType's methods, and a program that makes fields keeps only the methods it calls - #20
Merged
Conversation
… makes fields keeps only the methods it calls A descriptor listed its Go type's methods as it was made (goType, with reflect.Type.Method for each of them), for codegen to read: entc knows what a GoType implements from RType.Methods. A running program makes descriptors too. A generated client whose schema has a default, a validator or a value scanner calls the schema's Fields at init (runtime.go), so every such program reached reflect.Type.Method with an index the compiler cannot see -- and in a program that can reach one, the linker has to assume that any exported method of any type converted to an interface may be called by name. It kept all of them, used or not, in the whole program. Nothing failed. In lesomnus/payday's app compiled to WebAssembly, the module a browser downloads, that was 24 MB of 63; an app whose own schema has no default has paid it since payday's Audit gave its `domain` column one. The loader lists them now (RType.LoadMethods, from entc/load's NewField), before it encodes the schema, and a descriptor records the type alone. What is generated is the same: the generate gate regenerates every example and integration schema, snapshots included, and nothing moves. RType.Implements could not stay as it was either. Its name and signature are reflect.Type's own Implements, which a program calls through the interface (encoding/json does), and the linker keeps a method of that name and type on any type converted to an interface -- which an RType is, behind each field's builder -- and its loop was the same call by index. It is `implements` now: it asks reflect of a type a descriptor holds, and the recorded methods of one decoded from a loaded schema, which is what entc has. The enum builder's check that a GoType is a string or a Stringer asked TypeInfo.Stringer, which reaches that loop; it asks reflect of the type itself. TestMakingFieldsLetsTheLinkerPruneMethods builds a program that makes a schema's fields as runtime.go does, and reads the linker's dump of it (-dumpdep): no function marked for calling a method by index may be reached. It fails with each of the three put back -- the listing in goType, an exported Implements, the enum check through TypeInfo -- and names the chain the linker took. The same program built with what the loader does must be marked, so a dump that could not show it would not pass. TestRType_Implements asks a descriptor's RType before it is loaded, and after it is loaded and decoded as entc has it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KxtnpRW533bdErnW8QPhsk
lesomnus
added a commit
to lesomnus/payday
that referenced
this pull request
Oct 23, 2026
…it calls, and a test links it to say so payday's own sandbox was 63 MB, and 24 MB of it was methods nothing calls. The generated client's runtime.go makes the ent schema's fields at init, for their defaults and value scanners, and every field it made listed its Go type's methods with reflect.Type.Method, by an index the compiler cannot see. In a program that can reach such a call the linker has to assume that any exported method of any type converted to an interface may be called by name, so it keeps all of them, in every package. The page worked, and no list of packages showed it: the call was in one the sandbox has to link. An app with no default of its own has paid it since #87. protoc-gen-orm-ent began writing a declared default as the column's (`.Default(v)`, protobuf-orm/protoc-gen-orm-ent#8), and payday's Audit declares one on `domain`, so since then every app's runtime.go reads the schema at init -- for Access's `domain` as well, since #112. khala's sandbox measured 25.8 MB on payday 6bb6d9c and 46.9 MB on c0f5e7d. This app paid it longer, for its Holder's value scanner. protobuf-orm/ent#20 (4d86cf3) lists the methods in its loader, which only codegen runs, and takes RType.Implements out of the exported API: it has reflect.Type's name and signature, which the linker keeps on any type converted to an interface, and the same call in its loop. What ent generates is the same but for its stamp of which ent made it (`internal/ent/runtime/runtime.go`), the one file apptest's regeneration moves; an app takes the fix with payday's go.mod, and regenerates for the stamp. The bump brings protobuf-orm/ent#17 too -- one process applies a migration directory at a time, and an edited file it ran is refused -- which payday does not call. TestTheSandboxLetsTheLinkerPruneMethods links this app's sandbox as it ships, under GOOS=js with grpcnotrace, with the linker's dump (-dumpdep), and fails on any function in it marked for calling a method by index, with the chain the linker took to it. On the ent before this it fails at field.methods, reached from runtime.go's init through schema.Access.Fields, field.Json and goType. Measured, `GOOS=js GOARCH=wasm go build -tags grpcnotrace ./wasm`: - this app: 62,994,314 B before, 39,013,431 B after (gzip -9: 10.99 MB, 7.61 MB). Without runtime.go's init at all it is 38,628,342 B. - roster c4ae61c, with this payday replaced in: 89,958,712 B before, 56,631,026 B after (gzip -9: 15.47 MB, 11.03 MB). docs: client.md says what a method called by index costs a sandbox, runtime.md's table of what enforces what has the test, and migrating.md says an app regenerates and what it gets back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KxtnpRW533bdErnW8QPhsk
lesomnus
added a commit
to lesomnus/payday
that referenced
this pull request
Oct 23, 2026
…it calls, and a test links it to say so payday's own sandbox was 63 MB, and 24 MB of it was methods nothing calls. The generated client's runtime.go makes the ent schema's fields at init, for their defaults and value scanners, and every field it made listed its Go type's methods with reflect.Type.Method, by an index the compiler cannot see. In a program that can reach such a call the linker has to assume that any exported method of any type converted to an interface may be called by name, so it keeps all of them, in every package. The page worked, and no list of packages showed it: the call was in one the sandbox has to link. An app with no default of its own has paid it since #87. protoc-gen-orm-ent began writing a declared default as the column's (`.Default(v)`, protobuf-orm/protoc-gen-orm-ent#8), and payday's Audit declares one on `domain`, so since then every app's runtime.go reads the schema at init -- for Access's `domain` as well, since #112. khala's sandbox measured 25.8 MB on payday 6bb6d9c and 46.9 MB on c0f5e7d. This app paid it longer, for its Holder's value scanner. protobuf-orm/ent#20 (4d86cf3) lists the methods in its loader, which only codegen runs, and takes RType.Implements out of the exported API: it has reflect.Type's name and signature, which the linker keeps on any type converted to an interface, and the same call in its loop. What ent generates is the same but for its stamp of which ent made it (`internal/ent/runtime/runtime.go`), the one file apptest's regeneration moves; an app takes the fix with payday's go.mod, and regenerates for the stamp. The bump brings protobuf-orm/ent#17 too -- one process applies a migration directory at a time, and an edited file it ran is refused -- which payday does not call. TestTheSandboxLetsTheLinkerPruneMethods links this app's sandbox as it ships, under GOOS=js with grpcnotrace, with the linker's dump (-dumpdep), and fails on any function in it marked for calling a method by index, with the chain the linker took to it. On the ent before this it fails at field.methods, reached from runtime.go's init through schema.Access.Fields, field.Json and goType. Measured, `GOOS=js GOARCH=wasm go build -tags grpcnotrace ./wasm`: - this app: 62,994,314 B before, 39,013,431 B after (gzip -9: 10.99 MB, 7.61 MB). Without runtime.go's init at all it is 38,628,342 B. - roster c4ae61c, with this payday replaced in: 89,958,712 B before, 56,631,026 B after (gzip -9: 15.47 MB, 11.03 MB). docs: client.md says what a method called by index costs a sandbox, runtime.md's table of what enforces what has the test, and migrating.md says an app regenerates and what it gets back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KxtnpRW533bdErnW8QPhsk
lesomnus
force-pushed
the
methods-at-load
branch
2 times, most recently
from
October 23, 2026 23:20
5742557 to
4d86cf3
Compare
lesomnus
added a commit
to lesomnus/payday
that referenced
this pull request
Oct 24, 2026
The same change as 4d86cf3, which this pinned while protobuf-orm/ent#20 was open: the merge commit is master's, and runtime.go carries its stamp. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KxtnpRW533bdErnW8QPhsk
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
For lesomnus/payday's sandbox, which a browser downloads: 24 MB of its 63 MB was the linker keeping methods nothing calls, and this was why.
What was wrong
goType, which everyfield.Uuid,field.Json,field.Otherand.GoTypegoes through, calledmethods, which isreflect.Type.Method(i)for each method -- for codegen, which readsRType.Methodsto know what a GoType implements.Fieldsat init (runtime.go), so it builds every field of that schema.reflect.Type.Method, orMethodByNamewith a name the compiler cannot see, is marked for it (<ReflectMethod>in-dumpdep). In a program that can reach one, it has to assume any exported method of any type converted to an interface may be called by name, so it keeps all of them, in every package, used or not. Nothing fails.RType.Implementswas a second one. Its name and signature arereflect.Type's ownImplements, which a program calls through the interface (encoding/json does), and the linker keeps a method of that name and type on every type converted to an interface. AnRTypeis one, behind each field's builder, and the method's loop is the same call by index. Taking out the listing alone left the binary as big: the linker reached this next.The chain the linker took, in payday's app:
Every payday app has paid it since payday's
Auditgave itsdomaincolumn a default (protoc-gen-orm-ent#8 turned a declareddefaultinto.Default(v), and payday took it in payday#87): that put the schema into every app'sruntime.go, including apps with no default of their own.What changed
RType.LoadMethodsdoes whatmethodsdid, andentc/load'sNewFieldcalls it on every field before the schema is encoded. A descriptor records the type alone, andMethodsis nil until it is loaded.RType.Implementsisimplements, unexported. It asks reflect of a type a descriptor holds, and the recorded methods of one decoded from a loaded schema, which is what entc has.TypeInfo'sValueScanner,Validator,ValuerandStringercall it as before.TypeInfo.Stringer, which reaches the loop; it would also have answered from methods that are no longer recorded at that point.UPSTREAM.mdgets a Behaviour row: an upstream change that lists methods ingoTypeagain, or checks a descriptor throughTypeInfo's predicates, undoes this.What is generated does not change. The generate gate regenerates every example and integration schema, including the snapshots that carry
RType.Methods(entc/integration/privacy/ent/internal/schema.goand others), and nothing moves. So an app regenerates nothing; it takes the fix by taking the module.Nothing in the protobuf-orm org calls
RType.Implements(protoc-gen-orm-ent, payday, roster and rove searched).Tests
TestMakingFieldsLetsTheLinkerPruneMethodsbuildsschema/field/testdata/linked, which makes a schema's fields asruntime.godoes -- a uuid with a default, JSON,Other, aGoTypewith a value scanner, an enumGoType, a defaulted uint32 -- and asksreflect.Type.Implementsthrough the interface. It reads the linker's dump (-dumpdep). No marked function may be reached, and a failure names the chain.goType, an exportedImplements, the enum check throughTypeInfo.Stringer. Each names its own chain.-tags load, which does what the loader does, must be marked at(*RType).LoadMethods. So a dump that could not show the mark would not pass.TestRType_Implementsasks a descriptor'sRTypebefore it is loaded (reflect), and after it is loaded and decoded through JSON (the recorded methods). A type with no methods loads as an empty set, as the snapshots have it ({}).go test -race ./...incmd,dialect,schema,entc/load,entc/genandexamples.dialect/sql/schema/integrationon SQLite..github/lint.shover every tracked file.entc/integrationwith-race -count=2on all six engines ofcompose.test.yaml, on this branch and on master. MySQL 8.0 ran on its own once it accepted connections: 8.0.46 took minutes to start here.CI has not run
GitHub has created no workflow run on this repository since 2026-10-21. These all made no run:
The workflow is active. Actions is enabled for the repository and allows every action. githubstatus.com shows no incident. lesomnus/payday's runs started as usual over the same hours. The checks above are what the
lint,unit,generateandintegrationjobs run, run by hand.Measured
GOOS=js GOARCH=wasm go build -tags grpcnotrace ./wasm, with this branch replaced in:internal/apptest, payday 1b4425d)With
runtime.go'sinitdeleted outright it is 38,628,342 B, so what is left of the schema at init is 0.4 MB.What follows
A payday PR that takes this commit, with a test that links payday's sandbox and refuses any marked function in it. Apps get it from payday's
go.mod.🤖 Generated with Claude Code
https://claude.ai/code/session_01KxtnpRW533bdErnW8QPhsk