Use r8/d8 optimized profile for dexpreopt

Currently, dexpreopt supports profile guided optimization. This does not
work well with r8/d8 optimization, since the checked-in profile will not
match the dex signatures after r8/d8 has optimized the dex code.

This CL introduces a new property `dex_preopt.enable_profile_rewrting`.
If set, the checked-in profile will passed as `input` to r8 via
`--art-profile <input> <output>`. The <output> from the previous command
will be used as the profile for dexpreopt.

Test: m nothing --no-skip-soong-tests
Test: m CredentialManager with https://ag.corp.google.com/27448930
and obfuscation turned on
Test: nm -U symbol.odex # contains obfuscated methods

Bug: 335418838

Change-Id: I53beed9ed76f013262f1c503de0f2b74997c2a7f
This commit is contained in:
Spandan Das 2024-05-20 22:23:10 +00:00
parent 1705676dd0
commit 3dbda18e80
5 changed files with 156 additions and 31 deletions

View file

@ -1650,11 +1650,23 @@ func (j *Module) compile(ctx android.ModuleContext, extraSrcJars, extraClasspath
classesJar: implementationAndResourcesJar,
jarName: jarName,
}
dexOutputFile = j.dexer.compileDex(ctx, params)
if j.EnableProfileRewriting() {
profile := j.GetProfile()
if profile == "" || !j.GetProfileGuided() {
ctx.PropertyErrorf("enable_profile_rewriting", "Profile and Profile_guided must be set when enable_profile_rewriting is true")
}
params.artProfileInput = &profile
}
dexOutputFile, dexArtProfileOutput := j.dexer.compileDex(ctx, params)
if ctx.Failed() {
return
}
// If r8/d8 provides a profile that matches the optimized dex, use that for dexpreopt.
if dexArtProfileOutput != nil {
j.dexpreopter.SetRewrittenProfile(*dexArtProfileOutput)
}
// merge dex jar with resources if necessary
if j.resourceJar != nil {
jars := android.Paths{dexOutputFile, j.resourceJar}

View file

@ -253,17 +253,25 @@ func (d *dexer) dexCommonFlags(ctx android.ModuleContext,
return flags, deps
}
func d8Flags(flags javaBuilderFlags) (d8Flags []string, d8Deps android.Paths) {
func (d *dexer) d8Flags(ctx android.ModuleContext, dexParams *compileDexParams) (d8Flags []string, d8Deps android.Paths, artProfileOutput *android.OutputPath) {
flags := dexParams.flags
d8Flags = append(d8Flags, flags.bootClasspath.FormRepeatedClassPath("--lib ")...)
d8Flags = append(d8Flags, flags.dexClasspath.FormRepeatedClassPath("--lib ")...)
d8Deps = append(d8Deps, flags.bootClasspath...)
d8Deps = append(d8Deps, flags.dexClasspath...)
return d8Flags, d8Deps
if flags, deps, profileOutput := d.addArtProfile(ctx, dexParams); profileOutput != nil {
d8Flags = append(d8Flags, flags...)
d8Deps = append(d8Deps, deps...)
artProfileOutput = profileOutput
}
return d8Flags, d8Deps, artProfileOutput
}
func (d *dexer) r8Flags(ctx android.ModuleContext, flags javaBuilderFlags) (r8Flags []string, r8Deps android.Paths) {
func (d *dexer) r8Flags(ctx android.ModuleContext, dexParams *compileDexParams) (r8Flags []string, r8Deps android.Paths, artProfileOutput *android.OutputPath) {
flags := dexParams.flags
opt := d.dexProperties.Optimize
// When an app contains references to APIs that are not in the SDK specified by
@ -375,18 +383,44 @@ func (d *dexer) r8Flags(ctx android.ModuleContext, flags javaBuilderFlags) (r8Fl
}
}
return r8Flags, r8Deps
if flags, deps, profileOutput := d.addArtProfile(ctx, dexParams); profileOutput != nil {
r8Flags = append(r8Flags, flags...)
r8Deps = append(r8Deps, deps...)
artProfileOutput = profileOutput
}
return r8Flags, r8Deps, artProfileOutput
}
type compileDexParams struct {
flags javaBuilderFlags
sdkVersion android.SdkSpec
minSdkVersion android.ApiLevel
classesJar android.Path
jarName string
flags javaBuilderFlags
sdkVersion android.SdkSpec
minSdkVersion android.ApiLevel
classesJar android.Path
jarName string
artProfileInput *string
}
func (d *dexer) compileDex(ctx android.ModuleContext, dexParams *compileDexParams) android.OutputPath {
// Adds --art-profile to r8/d8 command.
// r8/d8 will output a generated profile file to match the optimized dex code.
func (d *dexer) addArtProfile(ctx android.ModuleContext, dexParams *compileDexParams) (flags []string, deps android.Paths, artProfileOutputPath *android.OutputPath) {
if dexParams.artProfileInput != nil {
artProfileInputPath := android.PathForModuleSrc(ctx, *dexParams.artProfileInput)
artProfileOutputPathValue := android.PathForModuleOut(ctx, "profile.prof.txt").OutputPath
artProfileOutputPath = &artProfileOutputPathValue
flags = []string{
"--art-profile",
artProfileInputPath.String(),
artProfileOutputPath.String(),
}
deps = append(deps, artProfileInputPath)
}
return flags, deps, artProfileOutputPath
}
// Return the compiled dex jar and (optional) profile _after_ r8 optimization
func (d *dexer) compileDex(ctx android.ModuleContext, dexParams *compileDexParams) (android.OutputPath, *android.OutputPath) {
// Compile classes.jar into classes.dex and then javalib.jar
javalibJar := android.PathForModuleOut(ctx, "dex", dexParams.jarName).OutputPath
@ -406,6 +440,7 @@ func (d *dexer) compileDex(ctx android.ModuleContext, dexParams *compileDexParam
}
useR8 := d.effectiveOptimizeEnabled()
var artProfileOutputPath *android.OutputPath
if useR8 {
proguardDictionary := android.PathForModuleOut(ctx, "proguard_dictionary")
d.proguardDictionary = android.OptionalPathForPath(proguardDictionary)
@ -418,8 +453,19 @@ func (d *dexer) compileDex(ctx android.ModuleContext, dexParams *compileDexParam
d.proguardUsageZip = android.OptionalPathForPath(proguardUsageZip)
resourcesOutput := android.PathForModuleOut(ctx, "package-res-shrunken.apk")
d.resourcesOutput = android.OptionalPathForPath(resourcesOutput)
r8Flags, r8Deps := d.r8Flags(ctx, dexParams.flags)
r8Deps = append(r8Deps, commonDeps...)
implicitOutputs := android.WritablePaths{
proguardDictionary,
proguardUsageZip,
proguardConfiguration,
}
r8Flags, r8Deps, r8ArtProfileOutputPath := d.r8Flags(ctx, dexParams)
if r8ArtProfileOutputPath != nil {
artProfileOutputPath = r8ArtProfileOutputPath
implicitOutputs = append(
implicitOutputs,
artProfileOutputPath,
)
}
rule := r8
args := map[string]string{
"r8Flags": strings.Join(append(commonFlags, r8Flags...), " "),
@ -436,10 +482,6 @@ func (d *dexer) compileDex(ctx android.ModuleContext, dexParams *compileDexParam
rule = r8RE
args["implicits"] = strings.Join(r8Deps.Strings(), ",")
}
implicitOutputs := android.WritablePaths{
proguardDictionary,
proguardUsageZip,
proguardConfiguration}
if d.resourcesInput.Valid() {
implicitOutputs = append(implicitOutputs, resourcesOutput)
args["resourcesOutput"] = resourcesOutput.String()
@ -454,18 +496,27 @@ func (d *dexer) compileDex(ctx android.ModuleContext, dexParams *compileDexParam
Args: args,
})
} else {
d8Flags, d8Deps := d8Flags(dexParams.flags)
implicitOutputs := android.WritablePaths{}
d8Flags, d8Deps, d8ArtProfileOutputPath := d.d8Flags(ctx, dexParams)
if d8ArtProfileOutputPath != nil {
artProfileOutputPath = d8ArtProfileOutputPath
implicitOutputs = append(
implicitOutputs,
artProfileOutputPath,
)
}
d8Deps = append(d8Deps, commonDeps...)
rule := d8
if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_D8") {
rule = d8RE
}
ctx.Build(pctx, android.BuildParams{
Rule: rule,
Description: "d8",
Output: javalibJar,
Input: dexParams.classesJar,
Implicits: d8Deps,
Rule: rule,
Description: "d8",
Output: javalibJar,
Input: dexParams.classesJar,
ImplicitOutputs: implicitOutputs,
Implicits: d8Deps,
Args: map[string]string{
"d8Flags": strings.Join(append(commonFlags, d8Flags...), " "),
"zipFlags": zipFlags,
@ -480,5 +531,5 @@ func (d *dexer) compileDex(ctx android.ModuleContext, dexParams *compileDexParam
javalibJar = alignedJavalibJar
}
return javalibJar
return javalibJar, artProfileOutputPath
}

View file

@ -662,3 +662,30 @@ func TestProguardFlagsInheritanceAppImport(t *testing.T) {
android.AssertStringDoesContain(t, "expected aarimports's proguard flags",
appR8.Args["r8Flags"], "proguard.txt")
}
func TestR8FlagsArtProfile(t *testing.T) {
result := PrepareForTestWithJavaDefaultModules.RunTestWithBp(t, `
android_app {
name: "app",
srcs: ["foo.java"],
platform_apis: true,
dex_preopt: {
profile_guided: true,
profile: "profile.txt.prof",
enable_profile_rewriting: true,
},
}
`)
app := result.ModuleForTests("app", "android_common")
appR8 := app.Rule("r8")
android.AssertStringDoesContain(t, "expected --art-profile in app r8 flags",
appR8.Args["r8Flags"], "--art-profile")
appDexpreopt := app.Rule("dexpreopt")
android.AssertStringDoesContain(t,
"expected --art-profile output to be used to create .prof binary",
appDexpreopt.RuleParams.Command,
"--create-profile-from=out/soong/.intermediates/app/android_common/profile.prof.txt --output-profile-type=app",
)
}

View file

@ -19,6 +19,8 @@ import (
"sort"
"strings"
"github.com/google/blueprint/proptools"
"android/soong/android"
"android/soong/dexpreopt"
)
@ -139,6 +141,10 @@ type dexpreopter struct {
// The path to the profile that dexpreopter accepts. It must be in the binary format. If this is
// set, it overrides the profile settings in `dexpreoptProperties`.
inputProfilePathOnHost android.Path
// The path to the profile that matches the dex optimized by r8/d8. It is in text format. If this is
// set, it will be converted to a binary profile which will be subsequently used for dexpreopt.
rewrittenProfile android.Path
}
type DexpreoptProperties struct {
@ -158,6 +164,11 @@ type DexpreoptProperties struct {
// defaults to searching for a file that matches the name of this module in the default
// profile location set by PRODUCT_DEX_PREOPT_PROFILE_DIR, or empty if not found.
Profile *string `android:"path"`
// If set to true, r8/d8 will use `profile` as input to generate a new profile that matches
// the optimized dex.
// The new profile will be subsequently used as the profile to dexpreopt the dex file.
Enable_profile_rewriting *bool
}
Dex_preopt_result struct {
@ -421,13 +432,17 @@ func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, libName string, dexJa
if d.inputProfilePathOnHost != nil {
profileClassListing = android.OptionalPathForPath(d.inputProfilePathOnHost)
} else if BoolDefault(d.dexpreoptProperties.Dex_preopt.Profile_guided, true) && !forPrebuiltApex(ctx) {
// If dex_preopt.profile_guided is not set, default it based on the existence of the
// dexprepot.profile option or the profile class listing.
if String(d.dexpreoptProperties.Dex_preopt.Profile) != "" {
// If enable_profile_rewriting is set, use the rewritten profile instead of the checked-in profile
if d.EnableProfileRewriting() {
profileClassListing = android.OptionalPathForPath(d.GetRewrittenProfile())
profileIsTextListing = true
} else if profile := d.GetProfile(); profile != "" {
// If dex_preopt.profile_guided is not set, default it based on the existence of the
// dexprepot.profile option or the profile class listing.
profileClassListing = android.OptionalPathForPath(
android.PathForModuleSrc(ctx, String(d.dexpreoptProperties.Dex_preopt.Profile)))
android.PathForModuleSrc(ctx, profile))
profileBootListing = android.ExistentPathForSource(ctx,
ctx.ModuleDir(), String(d.dexpreoptProperties.Dex_preopt.Profile)+"-boot")
ctx.ModuleDir(), profile+"-boot")
profileIsTextListing = true
} else if global.ProfileDir != "" {
profileClassListing = android.ExistentPathForSource(ctx,
@ -588,3 +603,23 @@ func (d *dexpreopter) OutputProfilePathOnHost() android.Path {
func (d *dexpreopter) disableDexpreopt() {
d.shouldDisableDexpreopt = true
}
func (d *dexpreopter) EnableProfileRewriting() bool {
return proptools.Bool(d.dexpreoptProperties.Dex_preopt.Enable_profile_rewriting)
}
func (d *dexpreopter) GetProfile() string {
return proptools.String(d.dexpreoptProperties.Dex_preopt.Profile)
}
func (d *dexpreopter) GetProfileGuided() bool {
return proptools.Bool(d.dexpreoptProperties.Dex_preopt.Profile_guided)
}
func (d *dexpreopter) GetRewrittenProfile() android.Path {
return d.rewrittenProfile
}
func (d *dexpreopter) SetRewrittenProfile(p android.Path) {
d.rewrittenProfile = p
}

View file

@ -2339,7 +2339,7 @@ func (al *ApiLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
classesJar: al.stubsJar,
jarName: ctx.ModuleName() + ".jar",
}
dexOutputFile := al.dexer.compileDex(ctx, dexParams)
dexOutputFile, _ := al.dexer.compileDex(ctx, dexParams)
uncompressed := true
al.initHiddenAPI(ctx, makeDexJarPathFromPath(dexOutputFile), al.stubsJar, &uncompressed)
dexOutputFile = al.hiddenAPIEncodeDex(ctx, dexOutputFile)
@ -2723,7 +2723,7 @@ func (j *Import) GenerateAndroidBuildActions(ctx android.ModuleContext) {
jarName: jarName,
}
dexOutputFile = j.dexer.compileDex(ctx, dexParams)
dexOutputFile, _ = j.dexer.compileDex(ctx, dexParams)
if ctx.Failed() {
return
}