4
4
package builder
5
5
6
6
import (
7
+ "crypto/sha256"
7
8
"crypto/sha512"
8
9
"debug/elf"
9
10
"encoding/binary"
@@ -80,6 +81,7 @@ type packageAction struct {
80
81
Config * compiler.Config
81
82
CFlags []string
82
83
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
83
85
Imports map [string ]string // map from imported package to action ID hash
84
86
OptLevel int // LLVM optimization level (0-3)
85
87
SizeLevel int // LLVM optimization for size level (0-2)
@@ -225,6 +227,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
225
227
config .Options .GlobalValues ["runtime" ]["buildVersion" ] = version
226
228
}
227
229
230
+ var embedFileObjects []* compileJob
228
231
for _ , pkg := range lprogram .Sorted () {
229
232
pkg := pkg // necessary to avoid a race condition
230
233
@@ -234,6 +237,47 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
234
237
}
235
238
sort .Strings (undefinedGlobals )
236
239
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
+
237
281
// Action ID jobs need to know the action ID of all the jobs the package
238
282
// imports.
239
283
var importedPackages []* compileJob
@@ -243,14 +287,15 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
243
287
return fmt .Errorf ("package %s imports %s but couldn't find dependency" , pkg .ImportPath , imported .Path ())
244
288
}
245
289
importedPackages = append (importedPackages , job )
290
+ actionIDDependencies = append (actionIDDependencies , job )
246
291
}
247
292
248
293
// Create a job that will calculate the action ID for a package compile
249
294
// job. The action ID is the cache key that is used for caching this
250
295
// package.
251
296
packageActionIDJob := & compileJob {
252
297
description : "calculate cache key for package " + pkg .ImportPath ,
253
- dependencies : importedPackages ,
298
+ dependencies : actionIDDependencies ,
254
299
run : func (job * compileJob ) error {
255
300
// Create a cache key: a hash from the action ID below that contains all
256
301
// the parameters for the build.
@@ -262,6 +307,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
262
307
Config : compilerConfig ,
263
308
CFlags : pkg .CFlags ,
264
309
FileHashes : make (map [string ]string , len (pkg .FileHashes )),
310
+ EmbeddedFiles : make (map [string ]string , len (allFiles )),
265
311
Imports : make (map [string ]string , len (pkg .Pkg .Imports ())),
266
312
OptLevel : optLevel ,
267
313
SizeLevel : sizeLevel ,
@@ -270,6 +316,9 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
270
316
for filePath , hash := range pkg .FileHashes {
271
317
actionID .FileHashes [filePath ] = hex .EncodeToString (hash )
272
318
}
319
+ for name , files := range allFiles {
320
+ actionID .EmbeddedFiles [name ] = files [0 ].Hash
321
+ }
273
322
for i , imported := range pkg .Pkg .Imports () {
274
323
actionID .Imports [imported .Path ()] = importedPackages [i ].result
275
324
}
@@ -668,6 +717,9 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
668
717
// Add libc dependencies, if they exist.
669
718
linkerDependencies = append (linkerDependencies , libcDependencies ... )
670
719
720
+ // Add embedded files.
721
+ linkerDependencies = append (linkerDependencies , embedFileObjects ... )
722
+
671
723
// Strip debug information with -no-debug.
672
724
if ! config .Debug () {
673
725
for _ , tag := range config .BuildTags () {
@@ -920,6 +972,112 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
920
972
})
921
973
}
922
974
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
+
923
1081
// optimizeProgram runs a series of optimizations and transformations that are
924
1082
// needed to convert a program to its final form. Some transformations are not
925
1083
// optional and must be run as the compiler expects them to run.
0 commit comments