Skip to content

Commit 87a4676

Browse files
aykevldeadprogram
authored andcommitted
all: add support for the embed package
1 parent fd20f63 commit 87a4676

File tree

12 files changed

+645
-14
lines changed

12 files changed

+645
-14
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ TEST_PACKAGES_FAST = \
265265
crypto/sha256 \
266266
crypto/sha512 \
267267
debug/macho \
268+
embed/internal/embedtest \
268269
encoding \
269270
encoding/ascii85 \
270271
encoding/base32 \

builder/build.go

Lines changed: 159 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package builder
55

66
import (
7+
"crypto/sha256"
78
"crypto/sha512"
89
"debug/elf"
910
"encoding/binary"
@@ -80,6 +81,7 @@ type packageAction struct {
8081
Config *compiler.Config
8182
CFlags []string
8283
FileHashes map[string]string // hash of every file that's part of the package
84+
EmbeddedFiles map[string]string // hash of all the //go:embed files in the package
8385
Imports map[string]string // map from imported package to action ID hash
8486
OptLevel int // LLVM optimization level (0-3)
8587
SizeLevel int // LLVM optimization for size level (0-2)
@@ -225,6 +227,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
225227
config.Options.GlobalValues["runtime"]["buildVersion"] = version
226228
}
227229

230+
var embedFileObjects []*compileJob
228231
for _, pkg := range lprogram.Sorted() {
229232
pkg := pkg // necessary to avoid a race condition
230233

@@ -234,6 +237,47 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
234237
}
235238
sort.Strings(undefinedGlobals)
236239

240+
// Make compile jobs to load files to be embedded in the output binary.
241+
var actionIDDependencies []*compileJob
242+
allFiles := map[string][]*loader.EmbedFile{}
243+
for _, files := range pkg.EmbedGlobals {
244+
for _, file := range files {
245+
allFiles[file.Name] = append(allFiles[file.Name], file)
246+
}
247+
}
248+
for name, files := range allFiles {
249+
name := name
250+
files := files
251+
job := &compileJob{
252+
description: "make object file for " + name,
253+
run: func(job *compileJob) error {
254+
// Read the file contents in memory.
255+
path := filepath.Join(pkg.Dir, name)
256+
data, err := os.ReadFile(path)
257+
if err != nil {
258+
return err
259+
}
260+
261+
// Hash the file.
262+
sum := sha256.Sum256(data)
263+
hexSum := hex.EncodeToString(sum[:16])
264+
265+
for _, file := range files {
266+
file.Size = uint64(len(data))
267+
file.Hash = hexSum
268+
if file.NeedsData {
269+
file.Data = data
270+
}
271+
}
272+
273+
job.result, err = createEmbedObjectFile(string(data), hexSum, name, pkg.OriginalDir(), dir, compilerConfig)
274+
return err
275+
},
276+
}
277+
actionIDDependencies = append(actionIDDependencies, job)
278+
embedFileObjects = append(embedFileObjects, job)
279+
}
280+
237281
// Action ID jobs need to know the action ID of all the jobs the package
238282
// imports.
239283
var importedPackages []*compileJob
@@ -243,14 +287,15 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
243287
return fmt.Errorf("package %s imports %s but couldn't find dependency", pkg.ImportPath, imported.Path())
244288
}
245289
importedPackages = append(importedPackages, job)
290+
actionIDDependencies = append(actionIDDependencies, job)
246291
}
247292

248293
// Create a job that will calculate the action ID for a package compile
249294
// job. The action ID is the cache key that is used for caching this
250295
// package.
251296
packageActionIDJob := &compileJob{
252297
description: "calculate cache key for package " + pkg.ImportPath,
253-
dependencies: importedPackages,
298+
dependencies: actionIDDependencies,
254299
run: func(job *compileJob) error {
255300
// Create a cache key: a hash from the action ID below that contains all
256301
// the parameters for the build.
@@ -262,6 +307,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
262307
Config: compilerConfig,
263308
CFlags: pkg.CFlags,
264309
FileHashes: make(map[string]string, len(pkg.FileHashes)),
310+
EmbeddedFiles: make(map[string]string, len(allFiles)),
265311
Imports: make(map[string]string, len(pkg.Pkg.Imports())),
266312
OptLevel: optLevel,
267313
SizeLevel: sizeLevel,
@@ -270,6 +316,9 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
270316
for filePath, hash := range pkg.FileHashes {
271317
actionID.FileHashes[filePath] = hex.EncodeToString(hash)
272318
}
319+
for name, files := range allFiles {
320+
actionID.EmbeddedFiles[name] = files[0].Hash
321+
}
273322
for i, imported := range pkg.Pkg.Imports() {
274323
actionID.Imports[imported.Path()] = importedPackages[i].result
275324
}
@@ -668,6 +717,9 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
668717
// Add libc dependencies, if they exist.
669718
linkerDependencies = append(linkerDependencies, libcDependencies...)
670719

720+
// Add embedded files.
721+
linkerDependencies = append(linkerDependencies, embedFileObjects...)
722+
671723
// Strip debug information with -no-debug.
672724
if !config.Debug() {
673725
for _, tag := range config.BuildTags() {
@@ -920,6 +972,112 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
920972
})
921973
}
922974

975+
// createEmbedObjectFile creates a new object file with the given contents, for
976+
// the embed package.
977+
func createEmbedObjectFile(data, hexSum, sourceFile, sourceDir, tmpdir string, compilerConfig *compiler.Config) (string, error) {
978+
// TODO: this works for small files, but can be a problem for larger files.
979+
// For larger files, it seems more appropriate to generate the object file
980+
// manually without going through LLVM.
981+
// On the other hand, generating DWARF like we do here can be difficult
982+
// without assistance from LLVM.
983+
984+
// Create new LLVM module just for this file.
985+
ctx := llvm.NewContext()
986+
defer ctx.Dispose()
987+
mod := ctx.NewModule("data")
988+
defer mod.Dispose()
989+
990+
// Create data global.
991+
value := ctx.ConstString(data, false)
992+
globalName := "embed/file_" + hexSum
993+
global := llvm.AddGlobal(mod, value.Type(), globalName)
994+
global.SetInitializer(value)
995+
global.SetLinkage(llvm.LinkOnceODRLinkage)
996+
global.SetGlobalConstant(true)
997+
global.SetUnnamedAddr(true)
998+
global.SetAlignment(1)
999+
if compilerConfig.GOOS != "darwin" {
1000+
// MachO doesn't support COMDATs, while COFF requires it (to avoid
1001+
// "duplicate symbol" errors). ELF works either way.
1002+
// Therefore, only use a COMDAT on non-MachO systems (aka non-MacOS).
1003+
global.SetComdat(mod.Comdat(globalName))
1004+
}
1005+
1006+
// Add DWARF debug information to this global, so that it is
1007+
// correctly counted when compiling with the -size= flag.
1008+
dibuilder := llvm.NewDIBuilder(mod)
1009+
dibuilder.CreateCompileUnit(llvm.DICompileUnit{
1010+
Language: 0xb, // DW_LANG_C99 (0xc, off-by-one?)
1011+
File: sourceFile,
1012+
Dir: sourceDir,
1013+
Producer: "TinyGo",
1014+
Optimized: false,
1015+
})
1016+
ditype := dibuilder.CreateArrayType(llvm.DIArrayType{
1017+
SizeInBits: uint64(len(data)) * 8,
1018+
AlignInBits: 8,
1019+
ElementType: dibuilder.CreateBasicType(llvm.DIBasicType{
1020+
Name: "byte",
1021+
SizeInBits: 8,
1022+
Encoding: llvm.DW_ATE_unsigned_char,
1023+
}),
1024+
Subscripts: []llvm.DISubrange{
1025+
{
1026+
Lo: 0,
1027+
Count: int64(len(data)),
1028+
},
1029+
},
1030+
})
1031+
difile := dibuilder.CreateFile(sourceFile, sourceDir)
1032+
diglobalexpr := dibuilder.CreateGlobalVariableExpression(difile, llvm.DIGlobalVariableExpression{
1033+
Name: globalName,
1034+
File: difile,
1035+
Line: 1,
1036+
Type: ditype,
1037+
Expr: dibuilder.CreateExpression(nil),
1038+
AlignInBits: 8,
1039+
})
1040+
global.AddMetadata(0, diglobalexpr)
1041+
mod.AddNamedMetadataOperand("llvm.module.flags",
1042+
ctx.MDNode([]llvm.Metadata{
1043+
llvm.ConstInt(ctx.Int32Type(), 2, false).ConstantAsMetadata(), // Warning on mismatch
1044+
ctx.MDString("Debug Info Version"),
1045+
llvm.ConstInt(ctx.Int32Type(), 3, false).ConstantAsMetadata(),
1046+
}),
1047+
)
1048+
mod.AddNamedMetadataOperand("llvm.module.flags",
1049+
ctx.MDNode([]llvm.Metadata{
1050+
llvm.ConstInt(ctx.Int32Type(), 7, false).ConstantAsMetadata(), // Max on mismatch
1051+
ctx.MDString("Dwarf Version"),
1052+
llvm.ConstInt(ctx.Int32Type(), 4, false).ConstantAsMetadata(),
1053+
}),
1054+
)
1055+
dibuilder.Finalize()
1056+
dibuilder.Destroy()
1057+
1058+
// Write this LLVM module out as an object file.
1059+
machine, err := compiler.NewTargetMachine(compilerConfig)
1060+
if err != nil {
1061+
return "", err
1062+
}
1063+
defer machine.Dispose()
1064+
outfile, err := os.CreateTemp(tmpdir, "embed-"+hexSum+"-*.o")
1065+
if err != nil {
1066+
return "", err
1067+
}
1068+
defer outfile.Close()
1069+
buf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile)
1070+
if err != nil {
1071+
return "", err
1072+
}
1073+
defer buf.Dispose()
1074+
_, err = outfile.Write(buf.Bytes())
1075+
if err != nil {
1076+
return "", err
1077+
}
1078+
return outfile.Name(), outfile.Close()
1079+
}
1080+
9231081
// optimizeProgram runs a series of optimizations and transformations that are
9241082
// needed to convert a program to its final form. Some transformations are not
9251083
// optional and must be run as the compiler expects them to run.

builder/sizes.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ var (
117117
// alloc: heap allocations during init interpretation
118118
// pack: data created when storing a constant in an interface for example
119119
// string: buffer behind strings
120-
packageSymbolRegexp = regexp.MustCompile(`\$(alloc|pack|string)(\.[0-9]+)?$`)
120+
packageSymbolRegexp = regexp.MustCompile(`\$(alloc|embedfsfiles|embedfsslice|embedslice|pack|string)(\.[0-9]+)?$`)
121121

122122
// Reflect sidetables. Created by the reflect lowering pass.
123123
// See src/reflect/sidetables.go.

0 commit comments

Comments
 (0)