Skip to content

Commit

Permalink
internal_gengo: store raw descriptor in .rodata section
Browse files Browse the repository at this point in the history
In Go, []byte literals go into the writable .data (or .noptrdata) section,
but string literals goes into the read-only .rodata section.

I verified that the contents move from .noptrdata to .rodata:

% (cd internal/reflection_test && \
  go test -c && \
  objdump -s -j .rodata reflection_test.test | grep '0a4669 6e')
 88bfd0 6e7370e8 070a4669 6e746572 6e616c2f  nsp...Finternal/

Change-Id: I87e190b41c02235abea1967eddca2f0262060ed9
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/638135
LUCI-TryBot-Result: Go LUCI <[email protected]>
Reviewed-by: Nicolas Hillegeer <[email protected]>
Reviewed-by: Damien Neil <[email protected]>
  • Loading branch information
stapelberg committed Jan 15, 2025
1 parent 132f042 commit cc8d1c2
Show file tree
Hide file tree
Showing 145 changed files with 537 additions and 27,038 deletions.
76 changes: 55 additions & 21 deletions cmd/protoc-gen-go/internal_gengo/reflect.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ func genReflectFileDescriptor(gen *protogen.Plugin, g *protogen.GeneratedFile, f
g.P("out := ", protoimplPackage.Ident("TypeBuilder"), "{")
g.P("File: ", protoimplPackage.Ident("DescBuilder"), "{")
g.P("GoPackagePath: ", reflectPackage.Ident("TypeOf"), "(x{}).PkgPath(),")
g.P("RawDescriptor: ", rawDescVarName(f), ",")
g.P("RawDescriptor: []byte(", rawDescVarName(f), "),")
g.P("NumEnums: ", len(f.allEnums), ",")
g.P("NumMessages: ", len(f.allMessages), ",")
g.P("NumExtensions: ", len(f.allExtensions), ",")
Expand All @@ -208,7 +208,6 @@ func genReflectFileDescriptor(gen *protogen.Plugin, g *protogen.GeneratedFile, f
g.P(f.GoDescriptorIdent, " = out.File")

// Set inputs to nil to allow GC to reclaim resources.
g.P(rawDescVarName(f), " = nil")
g.P(goTypesVarName(f), " = nil")
g.P(depIdxsVarName(f), " = nil")
g.P("}")
Expand All @@ -233,6 +232,57 @@ func stripSourceRetentionFieldsFromMessage(m protoreflect.Message) {
})
}

// escapeForLiteral formats data as characters that are valid to use in a Go
// string literal. This implementation was used (before go:embed existed) for
// many years in https://github.com/dsymonds/goembed
func escapeForLiteral(data []byte) []byte {
escaped := make([]byte, 0, len(data) /* right order of magnitude */)

for len(data) > 0 {
// https://golang.org/ref/spec#String_literals: "Within the quotes, any
// character may appear except newline and unescaped double quote. The
// text between the quotes forms the value of the literal, with backslash
// escapes interpreted as they are in rune literals […]."
switch b := data[0]; b {
case '\\':
escaped = append(escaped, []byte(`\\`)...)
case '"':
escaped = append(escaped, []byte(`\"`)...)
case '\n':
escaped = append(escaped, []byte(`\n`)...)

// While \r does not need to be escaped for Go string literals, some
// tools (like Gerrit) detect files as binary if they contain a \r
// which is not followed by \n.
case '\r':
escaped = append(escaped, []byte(`\r`)...)

case '\x00':
// https://golang.org/ref/spec#Source_code_representation: "Implementation
// restriction: For compatibility with other tools, a compiler may
// disallow the NUL character (U+0000) in the source text."
escaped = append(escaped, []byte(`\x00`)...)

default:
// https://golang.org/ref/spec#Source_code_representation: "Implementation
// restriction: […] A byte order mark may be disallowed anywhere else in
// the source."
const byteOrderMark = '\uFEFF'

if r, size := utf8.DecodeRune(data); r != utf8.RuneError && r != byteOrderMark {
escaped = append(escaped, data[:size]...)
data = data[size:]
continue
}

escaped = append(escaped, []byte(fmt.Sprintf(`\x%02x`, b))...)
}
data = data[1:]
}

return escaped
}

func genFileDescriptor(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo) {
descProto := proto.Clone(f.Proto).(*descriptorpb.FileDescriptorProto)
descProto.SourceCodeInfo = nil // drop source code information
Expand All @@ -243,23 +293,7 @@ func genFileDescriptor(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileI
return
}

g.P("var ", rawDescVarName(f), " = []byte{")
for len(b) > 0 {
n := 16
if n > len(b) {
n = len(b)
}

s := ""
for _, c := range b[:n] {
s += fmt.Sprintf("0x%02x,", c)
}
g.P(s)

b = b[n:]
}
g.P("}")
g.P()
g.P("const ", rawDescVarName(f), ` = "`, string(escapeForLiteral(b)), `"`)

if f.needRawDesc {
onceVar := rawDescVarName(f) + "Once"
Expand All @@ -272,9 +306,9 @@ func genFileDescriptor(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileI

g.P("func ", rawDescVarName(f), "GZIP() []byte {")
g.P(onceVar, ".Do(func() {")
g.P(dataVar, " = ", protoimplPackage.Ident("X"), ".CompressGZIP(", dataVar, ")")
g.P(dataVar, " = string(", protoimplPackage.Ident("X"), ".CompressGZIP([]byte(", dataVar, ")))")
g.P("})")
g.P("return ", dataVar)
g.P("return []byte(", dataVar, ")")
g.P("}")
g.P()
}
Expand Down
33 changes: 4 additions & 29 deletions cmd/protoc-gen-go/testdata/annotations/annotations.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 4 additions & 34 deletions cmd/protoc-gen-go/testdata/comments/comments.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 4 additions & 23 deletions cmd/protoc-gen-go/testdata/comments/deprecated.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit cc8d1c2

Please sign in to comment.