From 26c34ede294735354d75a5f511d9afd39dc8013c Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Fri, 30 Sep 2016 17:10:16 -0700 Subject: [PATCH] Add support for toc optimization in soong Skip relinking against shared libraries whose interface hasn't changed. Test: mmma -j frameworks/native/libs/gui Test: touch frameworks/native/libs/gui/BufferItem.cpp Test: mmma -j frameworks/native/libs/gui, see nothing relinks past libgui Bug: 26014946 Change-Id: I4d4b8da6a35c682341ae51869f5c72b51e192053 --- cc/binary.go | 3 ++ cc/builder.go | 29 ++++++++++++++++++- cc/cc.go | 43 ++++++++++++++++++++------- cc/library.go | 26 +++++++++++++++++ scripts/strip.sh | 1 + scripts/toc.sh | 75 ++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 166 insertions(+), 11 deletions(-) create mode 100755 scripts/toc.sh diff --git a/cc/binary.go b/cc/binary.go index 8afce0941..083cf0d08 100644 --- a/cc/binary.go +++ b/cc/binary.go @@ -281,6 +281,9 @@ func (binary *binaryDecorator) link(ctx ModuleContext, flagsToBuilderFlags(flags), afterPrefixSymbols) } + linkerDeps = append(linkerDeps, deps.SharedLibsDeps...) + linkerDeps = append(linkerDeps, deps.LateSharedLibsDeps...) + TransformObjToDynamicBinary(ctx, objFiles, sharedLibs, deps.StaticLibs, deps.LateStaticLibs, deps.WholeStaticLibs, linkerDeps, deps.CrtBegin, deps.CrtEnd, true, builderFlags, outputFile) diff --git a/cc/builder.go b/cc/builder.go index 42a7f48a6..b813783de 100644 --- a/cc/builder.go +++ b/cc/builder.go @@ -138,6 +138,18 @@ var ( Description: "copy gcc $out", }, "ccCmd", "cFlags", "libName") + + tocPath = pctx.SourcePathVariable("tocPath", "build/soong/scripts/toc.sh") + + toc = pctx.AndroidStaticRule("toc", + blueprint.RuleParams{ + Depfile: "${out}.d", + Deps: blueprint.DepsGCC, + Command: "CROSS_COMPILE=$crossCompile $tocPath -i ${in} -o ${out} -d ${out}.d", + CommandDeps: []string{"$tocPath"}, + Restat: true, + }, + "crossCompile") ) func init() { @@ -380,7 +392,6 @@ func TransformObjToDynamicBinary(ctx android.ModuleContext, libFlagsList = append(libFlagsList, lib.String()) } - deps = append(deps, sharedLibs...) deps = append(deps, staticLibs...) deps = append(deps, lateStaticLibs...) deps = append(deps, wholeStaticLibs...) @@ -403,6 +414,22 @@ func TransformObjToDynamicBinary(ctx android.ModuleContext, }) } +// Generate a rule for extract a table of contents from a shared library (.so) +func TransformSharedObjectToToc(ctx android.ModuleContext, inputFile android.WritablePath, + outputFile android.WritablePath, flags builderFlags) { + + crossCompile := gccCmd(flags.toolchain, "") + + ctx.ModuleBuild(pctx, android.ModuleBuildParams{ + Rule: toc, + Output: outputFile, + Input: inputFile, + Args: map[string]string{ + "crossCompile": crossCompile, + }, + }) +} + // Generate a rule for compiling multiple .o files to a .o using ld partial linking func TransformObjsToObj(ctx android.ModuleContext, objFiles android.Paths, flags builderFlags, outputFile android.WritablePath) { diff --git a/cc/cc.go b/cc/cc.go index 9a1981279..75292aa73 100644 --- a/cc/cc.go +++ b/cc/cc.go @@ -70,18 +70,25 @@ type Deps struct { } type PathDeps struct { - SharedLibs, LateSharedLibs android.Paths + // Paths to .so files + SharedLibs, LateSharedLibs android.Paths + // Paths to the dependencies to use for .so files (.so.toc files) + SharedLibsDeps, LateSharedLibsDeps android.Paths + // Paths to .a files StaticLibs, LateStaticLibs, WholeStaticLibs android.Paths + // Paths to .o files ObjFiles android.Paths WholeStaticLibObjFiles android.Paths + // Paths to generated source files GeneratedSources android.Paths GeneratedHeaders android.Paths Flags, ReexportedFlags []string ReexportedFlagsDeps android.Paths + // Paths to crt*.o files CrtBegin, CrtEnd android.OptionalPath } @@ -815,19 +822,27 @@ func (c *Module) depsToPaths(ctx android.ModuleContext) PathDeps { checkLinkType(c, cc) } + var ptr *android.Paths var depPtr *android.Paths + linkFile := cc.outputFile + depFile := android.OptionalPath{} + switch tag { case ndkStubDepTag, sharedDepTag, sharedExportDepTag: - depPtr = &depPaths.SharedLibs + ptr = &depPaths.SharedLibs + depPtr = &depPaths.SharedLibsDeps + depFile = cc.linker.(libraryInterface).toc() case lateSharedDepTag, ndkLateStubDepTag: - depPtr = &depPaths.LateSharedLibs + ptr = &depPaths.LateSharedLibs + depPtr = &depPaths.LateSharedLibsDeps + depFile = cc.linker.(libraryInterface).toc() case staticDepTag, staticExportDepTag: - depPtr = &depPaths.StaticLibs + ptr = &depPaths.StaticLibs case lateStaticDepTag: - depPtr = &depPaths.LateStaticLibs + ptr = &depPaths.LateStaticLibs case wholeStaticDepTag: - depPtr = &depPaths.WholeStaticLibs + ptr = &depPaths.WholeStaticLibs staticLib, ok := cc.linker.(libraryInterface) if !ok || !staticLib.static() { ctx.ModuleErrorf("module %q not a static library", name) @@ -844,17 +859,25 @@ func (c *Module) depsToPaths(ctx android.ModuleContext) PathDeps { depPaths.WholeStaticLibObjFiles = append(depPaths.WholeStaticLibObjFiles, staticLib.objs()...) case objDepTag: - depPtr = &depPaths.ObjFiles + ptr = &depPaths.ObjFiles case crtBeginDepTag: - depPaths.CrtBegin = cc.outputFile + depPaths.CrtBegin = linkFile case crtEndDepTag: - depPaths.CrtEnd = cc.outputFile + depPaths.CrtEnd = linkFile default: panic(fmt.Errorf("unknown dependency tag: %s", tag)) } + if ptr != nil { + *ptr = append(*ptr, linkFile.Path()) + } + if depPtr != nil { - *depPtr = append(*depPtr, cc.outputFile.Path()) + dep := depFile + if !dep.Valid() { + dep = linkFile + } + *depPtr = append(*depPtr, dep.Path()) } }) diff --git a/cc/library.go b/cc/library.go index 7cc587f5c..53c9a580c 100644 --- a/cc/library.go +++ b/cc/library.go @@ -18,6 +18,7 @@ import ( "strings" "github.com/google/blueprint" + "github.com/google/blueprint/pathtools" "android/soong" "android/soong/android" @@ -158,6 +159,8 @@ type libraryDecorator struct { // For reusing static library objects for shared library reuseObjFiles android.Paths + // table-of-contents file to optimize out relinking when possible + tocFile android.OptionalPath flagExporter stripper @@ -269,6 +272,7 @@ type libraryInterface interface { static() bool objs() android.Paths reuseObjs() android.Paths + toc() android.OptionalPath // Returns true if the build options for the module have selected a static or shared build buildStatic() bool @@ -434,10 +438,28 @@ func (library *libraryDecorator) linkShared(ctx ModuleContext, } } + linkerDeps = append(linkerDeps, deps.SharedLibsDeps...) + linkerDeps = append(linkerDeps, deps.LateSharedLibsDeps...) + TransformObjToDynamicBinary(ctx, objFiles, sharedLibs, deps.StaticLibs, deps.LateStaticLibs, deps.WholeStaticLibs, linkerDeps, deps.CrtBegin, deps.CrtEnd, false, builderFlags, outputFile) + if ctx.Device() { + // For device targets, optimize out relinking against shared + // libraries whose interface hasn't changed by depending on + // a table of contents file instead of the library itself. + // For host targets, the library might be part of a host tool + // that is run during the build, use the library directly so + // that the timestamp of the binary changes whenever a library + // changes and any necessary tools get re-run. + tocPath := outputFile.String() + tocPath = pathtools.ReplaceExtension(tocPath, flags.Toolchain.ShlibSuffix()[1:]+".toc") + tocFile := android.PathForOutput(ctx, tocPath) + library.tocFile = android.OptionalPathForPath(tocFile) + TransformSharedObjectToToc(ctx, outputFile, tocFile, builderFlags) + } + return ret } @@ -482,6 +504,10 @@ func (library *libraryDecorator) reuseObjs() android.Paths { return library.reuseObjFiles } +func (library *libraryDecorator) toc() android.OptionalPath { + return library.tocFile +} + func (library *libraryDecorator) install(ctx ModuleContext, file android.Path) { if !ctx.static() { library.baseInstaller.install(ctx, file) diff --git a/scripts/strip.sh b/scripts/strip.sh index 5c43028c8..82249422c 100755 --- a/scripts/strip.sh +++ b/scripts/strip.sh @@ -5,6 +5,7 @@ # Environment: # CROSS_COMPILE: prefix added to readelf, objcopy tools # Arguments: +# -i ${file}: input file (required) # -o ${file}: output file (required) # -d ${file}: deps file (required) # --keep-symbols diff --git a/scripts/toc.sh b/scripts/toc.sh new file mode 100755 index 000000000..59bf8a3bb --- /dev/null +++ b/scripts/toc.sh @@ -0,0 +1,75 @@ +#!/bin/bash -eu + +# Script to handle generating a .toc file from a .so file +# Inputs: +# Environment: +# CROSS_COMPILE: prefix added to readelf tool +# Arguments: +# -i ${file}: input file (required) +# -o ${file}: output file (required) +# -d ${file}: deps file (required) + +OPTSTRING=d:i:o:-: + +usage() { + cat < "${outfile}.tmp" + "${CROSS_COMPILE}readelf" --dyn-syms "${infile}" | awk '{$2=""; $3=""; print}' >> "${outfile}.tmp" +} + +do_macho() { + otool -l "${infile}" | grep LC_ID_DYLIB -A 5 > "${outfile}.tmp" + nm -gP "${infile}" | cut -f1-2 -d" " | grep -v 'U$' >> "${outfile}.tmp" +} + + +while getopts $OPTSTRING opt; do + case "$opt" in + d) depsfile="${OPTARG}" ;; + i) infile="${OPTARG}" ;; + o) outfile="${OPTARG}" ;; + -) + case "${OPTARG}" in + *) echo "Unknown option --${OPTARG}"; usage ;; + esac;; + ?) usage ;; + *) echo "'${opt}' '${OPTARG}'" + esac +done + +if [ -z "${infile}" ]; then + echo "-i argument is required" + usage +fi + +if [ -z "${outfile}" ]; then + echo "-o argument is required" + usage +fi + +if [ -z "${depsfile}" ]; then + echo "-d argument is required" + usage +fi + +rm -f "${outfile}.tmp" + +cat < "${depsfile}" +${outfile}: \\ + ${CROSS_COMPILE}readelf \\ +EOF + +do_elf + +if cmp "${outfile}" "${outfile}.tmp" > /dev/null 2> /dev/null; then + rm -f "${outfile}.tmp" +else + mv -f "${outfile}.tmp" "${outfile}" +fi