Implement fdo_profile module type

Introducing fdo_profile module type to reimplement the afdo support in cc moduels. This change allows the feature to be compatible with Bazel migration.

How it works:

PreDepsMutators:
  * BeginMutator: If non-static cc modules sets afdo prop, search and add corresponding fdo_profile module as a dep with fdoProfileTag
  * fdoProfileMutator:
    * If in fdo_profile module, set FdoProfileProvider with full path to profile
    * If in cc module, read FdoProfileProvider from dep with fdoProfileTag and set FdoProfileInfo.Path to FdoProfilePath field

PostDepsMutators:
  * afdoDepsMutator: If a module has FdoProfilePath set, walk to its static deps and set itself to the deps' AfdoRdeps
  * afdoMutator: If a static dep has AfdoRDeps set, create afdo variant.

Ignore-AOSP-First: Other CLs in the same topic are internal-only
Test: go test
Bug: b/267229065
Change-Id: I687d798a02d9743c92804fea36fb4ae3a7a0e5e3
Merged-In: I687d798a02d9743c92804fea36fb4ae3a7a0e5e3
diff --git a/android/config.go b/android/config.go
index 33deba5..0ef94a5 100644
--- a/android/config.go
+++ b/android/config.go
@@ -1418,6 +1418,21 @@
 	return c.config.productVariables.PgoAdditionalProfileDirs
 }
 
+// AfdoProfile returns fully qualified path associated to the given module name
+func (c *deviceConfig) AfdoProfile(name string) (*string, error) {
+	for _, afdoProfile := range c.config.productVariables.AfdoProfiles {
+		split := strings.Split(afdoProfile, ":")
+		if len(split) != 3 {
+			return nil, fmt.Errorf("AFDO_PROFILES has invalid value: %s. "+
+				"The expected format is <module>:<fully-qualified-path-to-fdo_profile>", afdoProfile)
+		}
+		if split[0] == name {
+			return proptools.StringPtr(strings.Join([]string{split[1], split[2]}, ":")), nil
+		}
+	}
+	return nil, nil
+}
+
 func (c *deviceConfig) VendorSepolicyDirs() []string {
 	return c.config.productVariables.BoardVendorSepolicyDirs
 }
diff --git a/android/variable.go b/android/variable.go
index 1da5974..249d53b 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -464,6 +464,8 @@
 
 	IncludeTags    []string `json:",omitempty"`
 	SourceRootDirs []string `json:",omitempty"`
+
+	AfdoProfiles []string `json:",omitempty"`
 }
 
 func boolPtr(v bool) *bool {
diff --git a/cc/Android.bp b/cc/Android.bp
index 5fd9afe..be2cc5a 100644
--- a/cc/Android.bp
+++ b/cc/Android.bp
@@ -21,6 +21,8 @@
     ],
     srcs: [
         "afdo.go",
+        "fdo_profile.go",
+
         "androidmk.go",
         "api_level.go",
         "bp2build.go",
diff --git a/cc/afdo.go b/cc/afdo.go
index d36f4af..0b662eb 100644
--- a/cc/afdo.go
+++ b/cc/afdo.go
@@ -18,9 +18,10 @@
 	"fmt"
 	"strings"
 
-	"github.com/google/blueprint/proptools"
-
 	"android/soong/android"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
 )
 
 var (
@@ -34,6 +35,7 @@
 
 const afdoCFlagsFormat = "-funique-internal-linkage-names -fprofile-sample-accurate -fprofile-sample-use=%s"
 
+// TODO(b/267229065): Remove getAfdoProfileProjects after reimplementing afdo support for rust
 func getAfdoProfileProjects(config android.DeviceConfig) []string {
 	return config.OnceStringSlice(afdoProfileProjectsConfigKey, func() []string {
 		return globalAfdoProfileProjects
@@ -44,13 +46,19 @@
 	getNamedMapForConfig(ctx.Config(), modulesMissingProfileFileKey).Store(missing, true)
 }
 
+type afdoRdep struct {
+	VariationName *string
+	ProfilePath   *string
+}
+
 type AfdoProperties struct {
 	// Afdo allows developers self-service enroll for
 	// automatic feedback-directed optimization using profile data.
 	Afdo bool
 
-	AfdoTarget *string  `blueprint:"mutated"`
-	AfdoDeps   []string `blueprint:"mutated"`
+	FdoProfilePath *string `blueprint:"mutated"`
+
+	AfdoRDeps []afdoRdep `blueprint:"mutated"`
 }
 
 type afdo struct {
@@ -61,8 +69,10 @@
 	return []interface{}{&afdo.Properties}
 }
 
-func (afdo *afdo) AfdoEnabled() bool {
-	return afdo != nil && afdo.Properties.Afdo && afdo.Properties.AfdoTarget != nil
+// afdoEnabled returns true for binaries and shared libraries
+// that set afdo prop to True and there is a profile available
+func (afdo *afdo) afdoEnabled() bool {
+	return afdo != nil && afdo.Properties.Afdo && afdo.Properties.FdoProfilePath != nil
 }
 
 // Get list of profile file names, ordered by level of specialisation. For example:
@@ -70,6 +80,7 @@
 //  2. libfoo.afdo
 //
 // Add more specialisation as needed.
+// TODO(b/267229065): Remove getProfileFiles after reimplementing afdo support for rust
 func getProfileFiles(ctx android.BaseModuleContext, moduleName string) []string {
 	var files []string
 	files = append(files, moduleName+"_"+ctx.Arch().ArchType.String()+".afdo")
@@ -77,6 +88,7 @@
 	return files
 }
 
+// TODO(b/267229065): Remove GetAfdoProfileFile after reimplementing afdo support for rust
 func (props *AfdoProperties) GetAfdoProfileFile(ctx android.BaseModuleContext, module string) android.OptionalPath {
 	// Test if the profile_file is present in any of the Afdo profile projects
 	for _, profileFile := range getProfileFiles(ctx, module) {
@@ -95,82 +107,125 @@
 	return android.OptionalPathForPath(nil)
 }
 
-func (afdo *afdo) begin(ctx BaseModuleContext) {
-	if ctx.Host() {
-		return
-	}
-	if ctx.static() && !ctx.staticBinary() {
-		return
-	}
-	if afdo.Properties.Afdo {
-		module := ctx.ModuleName()
-		if afdo.Properties.GetAfdoProfileFile(ctx, module).Valid() {
-			afdo.Properties.AfdoTarget = proptools.StringPtr(module)
-		}
-	}
-}
-
 func (afdo *afdo) flags(ctx ModuleContext, flags Flags) Flags {
-	if profile := afdo.Properties.AfdoTarget; profile != nil {
-		if profileFile := afdo.Properties.GetAfdoProfileFile(ctx, *profile); profileFile.Valid() {
-			profileFilePath := profileFile.Path()
+	if path := afdo.Properties.FdoProfilePath; path != nil {
+		profileUseFlag := fmt.Sprintf(afdoCFlagsFormat, *path)
+		flags.Local.CFlags = append(flags.Local.CFlags, profileUseFlag)
+		flags.Local.LdFlags = append(flags.Local.LdFlags, profileUseFlag)
+		flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,-mllvm,-no-warn-sample-unused=true")
 
-			profileUseFlag := fmt.Sprintf(afdoCFlagsFormat, profileFile)
-			flags.Local.CFlags = append(flags.Local.CFlags, profileUseFlag)
-			flags.Local.LdFlags = append(flags.Local.LdFlags, profileUseFlag)
-			flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,-mllvm,-no-warn-sample-unused=true")
-
-			// Update CFlagsDeps and LdFlagsDeps so the module is rebuilt
-			// if profileFile gets updated
-			flags.CFlagsDeps = append(flags.CFlagsDeps, profileFilePath)
-			flags.LdFlagsDeps = append(flags.LdFlagsDeps, profileFilePath)
-		}
+		// Update CFlagsDeps and LdFlagsDeps so the module is rebuilt
+		// if profileFile gets updated
+		pathForSrc := android.PathForSource(ctx, *path)
+		flags.CFlagsDeps = append(flags.CFlagsDeps, pathForSrc)
+		flags.LdFlagsDeps = append(flags.LdFlagsDeps, pathForSrc)
 	}
 
 	return flags
 }
 
-// Propagate afdo requirements down from binaries
+func (afdo *afdo) addDep(ctx BaseModuleContext, actx android.BottomUpMutatorContext) {
+	if ctx.Host() {
+		return
+	}
+
+	if ctx.static() && !ctx.staticBinary() {
+		return
+	}
+
+	if c, ok := ctx.Module().(*Module); ok && c.Enabled() {
+		if fdoProfileName, err := actx.DeviceConfig().AfdoProfile(actx.ModuleName()); fdoProfileName != nil && err == nil {
+			actx.AddFarVariationDependencies(
+				[]blueprint.Variation{
+					{Mutator: "arch", Variation: actx.Target().ArchVariation()},
+					{Mutator: "os", Variation: "android"},
+				},
+				FdoProfileTag,
+				[]string{*fdoProfileName}...,
+			)
+		}
+	}
+}
+
+// FdoProfileMutator reads the FdoProfileProvider from a direct dep with FdoProfileTag
+// assigns FdoProfileInfo.Path to the FdoProfilePath mutated property
+func (c *Module) FdoProfileMutator(ctx android.BottomUpMutatorContext) {
+	if !c.Enabled() {
+		return
+	}
+
+	ctx.VisitDirectDepsWithTag(FdoProfileTag, func(m android.Module) {
+		if ctx.OtherModuleHasProvider(m, FdoProfileProvider) {
+			info := ctx.OtherModuleProvider(m, FdoProfileProvider).(FdoProfileInfo)
+			c.afdo.Properties.FdoProfilePath = proptools.StringPtr(info.Path.String())
+		}
+	})
+}
+
+var _ FdoProfileMutatorInterface = (*Module)(nil)
+
+// Propagate afdo requirements down from binaries and shared libraries
 func afdoDepsMutator(mctx android.TopDownMutatorContext) {
-	if m, ok := mctx.Module().(*Module); ok && m.afdo.AfdoEnabled() {
-		afdoTarget := *m.afdo.Properties.AfdoTarget
-		mctx.WalkDeps(func(dep android.Module, parent android.Module) bool {
-			tag := mctx.OtherModuleDependencyTag(dep)
-			libTag, isLibTag := tag.(libraryDependencyTag)
+	if m, ok := mctx.Module().(*Module); ok && m.afdo.afdoEnabled() {
+		if path := m.afdo.Properties.FdoProfilePath; path != nil {
+			mctx.WalkDeps(func(dep android.Module, parent android.Module) bool {
+				tag := mctx.OtherModuleDependencyTag(dep)
+				libTag, isLibTag := tag.(libraryDependencyTag)
 
-			// Do not recurse down non-static dependencies
-			if isLibTag {
-				if !libTag.static() {
-					return false
+				// Do not recurse down non-static dependencies
+				if isLibTag {
+					if !libTag.static() {
+						return false
+					}
+				} else {
+					if tag != objDepTag && tag != reuseObjTag {
+						return false
+					}
 				}
-			} else {
-				if tag != objDepTag && tag != reuseObjTag {
-					return false
+
+				if dep, ok := dep.(*Module); ok {
+					dep.afdo.Properties.AfdoRDeps = append(
+						dep.afdo.Properties.AfdoRDeps,
+						afdoRdep{
+							VariationName: proptools.StringPtr(encodeTarget(m.Name())),
+							ProfilePath:   path,
+						},
+					)
 				}
-			}
 
-			if dep, ok := dep.(*Module); ok {
-				dep.afdo.Properties.AfdoDeps = append(dep.afdo.Properties.AfdoDeps, afdoTarget)
-			}
-
-			return true
-		})
+				return true
+			})
+		}
 	}
 }
 
 // Create afdo variants for modules that need them
 func afdoMutator(mctx android.BottomUpMutatorContext) {
 	if m, ok := mctx.Module().(*Module); ok && m.afdo != nil {
-		if m.afdo.AfdoEnabled() && !m.static() {
-			afdoTarget := *m.afdo.Properties.AfdoTarget
-			mctx.SetDependencyVariation(encodeTarget(afdoTarget))
+		if !m.static() && m.afdo.Properties.Afdo && m.afdo.Properties.FdoProfilePath != nil {
+			mctx.SetDependencyVariation(encodeTarget(m.Name()))
+			return
 		}
 
 		variationNames := []string{""}
-		afdoDeps := android.FirstUniqueStrings(m.afdo.Properties.AfdoDeps)
-		for _, dep := range afdoDeps {
-			variationNames = append(variationNames, encodeTarget(dep))
+
+		variantNameToProfilePath := make(map[string]*string)
+
+		for _, afdoRDep := range m.afdo.Properties.AfdoRDeps {
+			variantName := *afdoRDep.VariationName
+			// An rdep can be set twice in AfdoRDeps because there can be
+			// more than one path from an afdo-enabled module to
+			// a static dep such as
+			// afdo_enabled_foo -> static_bar ----> static_baz
+			//                   \                      ^
+			//                    ----------------------|
+			// We only need to create one variant per unique rdep
+			if variantNameToProfilePath[variantName] == nil {
+				variationNames = append(variationNames, variantName)
+				variantNameToProfilePath[variantName] = afdoRDep.ProfilePath
+			}
 		}
+
 		if len(variationNames) > 1 {
 			modules := mctx.CreateVariations(variationNames...)
 			for i, name := range variationNames {
@@ -180,7 +235,7 @@
 				variation := modules[i].(*Module)
 				variation.Properties.PreventInstall = true
 				variation.Properties.HideFromMake = true
-				variation.afdo.Properties.AfdoTarget = proptools.StringPtr(decodeTarget(name))
+				variation.afdo.Properties.FdoProfilePath = variantNameToProfilePath[name]
 			}
 		}
 	}
diff --git a/cc/afdo_test.go b/cc/afdo_test.go
index 40f705b..ef95b3f 100644
--- a/cc/afdo_test.go
+++ b/cc/afdo_test.go
@@ -23,6 +23,11 @@
 	"github.com/google/blueprint"
 )
 
+var prepareForTestWithFdoProfile = android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("soong_namespace", android.NamespaceFactory)
+	ctx.RegisterModuleType("fdo_profile", fdoProfileFactory)
+})
+
 type visitDirectDepsInterface interface {
 	VisitDirectDeps(blueprint.Module, func(dep blueprint.Module))
 }
@@ -58,38 +63,77 @@
 		srcs: ["bar.c"],
 	}
 	`
-	prepareForAfdoTest := android.FixtureAddTextFile("toolchain/pgo-profiles/sampling/libTest.afdo", "TEST")
 
 	result := android.GroupFixturePreparers(
+		prepareForTestWithFdoProfile,
 		prepareForCcTest,
-		prepareForAfdoTest,
+		android.FixtureAddTextFile("afdo_profiles_package/libTest.afdo", ""),
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.AfdoProfiles = []string{
+				"libTest://afdo_profiles_package:libTest_afdo",
+			}
+		}),
+		android.MockFS{
+			"afdo_profiles_package/Android.bp": []byte(`
+				fdo_profile {
+					name: "libTest_afdo",
+					profile: "libTest.afdo",
+				}
+			`),
+		}.AddToFixture(),
 	).RunTestWithBp(t, bp)
 
-	libTest := result.ModuleForTests("libTest", "android_arm64_armv8-a_shared")
-	libFoo := result.ModuleForTests("libFoo", "android_arm64_armv8-a_static_afdo-libTest")
-	libBar := result.ModuleForTests("libBar", "android_arm64_armv8-a_static_afdo-libTest")
+	expectedCFlag := "-fprofile-sample-use=afdo_profiles_package/libTest.afdo"
 
-	if !hasDirectDep(result, libTest.Module(), libFoo.Module()) {
+	libTest := result.ModuleForTests("libTest", "android_arm64_armv8-a_shared")
+	libFooAfdoVariant := result.ModuleForTests("libFoo", "android_arm64_armv8-a_static_afdo-libTest")
+	libBarAfdoVariant := result.ModuleForTests("libBar", "android_arm64_armv8-a_static_afdo-libTest")
+
+	// Check cFlags of afdo-enabled module and the afdo-variant of its static deps
+	cFlags := libTest.Rule("cc").Args["cFlags"]
+	if !strings.Contains(cFlags, expectedCFlag) {
+		t.Errorf("Expected 'libTest' to enable afdo, but did not find %q in cflags %q", expectedCFlag, cFlags)
+	}
+
+	cFlags = libFooAfdoVariant.Rule("cc").Args["cFlags"]
+	if !strings.Contains(cFlags, expectedCFlag) {
+		t.Errorf("Expected 'libFooAfdoVariant' to enable afdo, but did not find %q in cflags %q", expectedCFlag, cFlags)
+	}
+
+	cFlags = libBarAfdoVariant.Rule("cc").Args["cFlags"]
+	if !strings.Contains(cFlags, expectedCFlag) {
+		t.Errorf("Expected 'libBarAfdoVariant' to enable afdo, but did not find %q in cflags %q", expectedCFlag, cFlags)
+	}
+
+	// Check dependency edge from afdo-enabled module to static deps
+	if !hasDirectDep(result, libTest.Module(), libFooAfdoVariant.Module()) {
 		t.Errorf("libTest missing dependency on afdo variant of libFoo")
 	}
 
-	if !hasDirectDep(result, libFoo.Module(), libBar.Module()) {
+	if !hasDirectDep(result, libFooAfdoVariant.Module(), libBarAfdoVariant.Module()) {
 		t.Errorf("libTest missing dependency on afdo variant of libBar")
 	}
 
-	cFlags := libTest.Rule("cc").Args["cFlags"]
-	if w := "-fprofile-sample-accurate"; !strings.Contains(cFlags, w) {
-		t.Errorf("Expected 'libTest' to enable afdo, but did not find %q in cflags %q", w, cFlags)
-	}
+	// Verify non-afdo variant exists and doesn't contain afdo
+	libFoo := result.ModuleForTests("libFoo", "android_arm64_armv8-a_static")
+	libBar := result.ModuleForTests("libBar", "android_arm64_armv8-a_static")
 
 	cFlags = libFoo.Rule("cc").Args["cFlags"]
-	if w := "-fprofile-sample-accurate"; !strings.Contains(cFlags, w) {
-		t.Errorf("Expected 'libFoo' to enable afdo, but did not find %q in cflags %q", w, cFlags)
+	if strings.Contains(cFlags, expectedCFlag) {
+		t.Errorf("Expected 'libFoo' to not enable afdo, but found %q in cflags %q", expectedCFlag, cFlags)
+	}
+	cFlags = libBar.Rule("cc").Args["cFlags"]
+	if strings.Contains(cFlags, expectedCFlag) {
+		t.Errorf("Expected 'libBar' to not enable afdo, but found %q in cflags %q", expectedCFlag, cFlags)
 	}
 
-	cFlags = libBar.Rule("cc").Args["cFlags"]
-	if w := "-fprofile-sample-accurate"; !strings.Contains(cFlags, w) {
-		t.Errorf("Expected 'libBar' to enable afdo, but did not find %q in cflags %q", w, cFlags)
+	// Check dependency edges of static deps
+	if hasDirectDep(result, libTest.Module(), libFoo.Module()) {
+		t.Errorf("libTest should not depend on non-afdo variant of libFoo")
+	}
+
+	if !hasDirectDep(result, libFoo.Module(), libBar.Module()) {
+		t.Errorf("libFoo missing dependency on non-afdo variant of libBar")
 	}
 }
 
@@ -113,11 +157,21 @@
 		name: "libBar",
 	}
 	`
-	prepareForAfdoTest := android.FixtureAddTextFile("toolchain/pgo-profiles/sampling/libFoo.afdo", "TEST")
 
 	result := android.GroupFixturePreparers(
 		prepareForCcTest,
-		prepareForAfdoTest,
+		prepareForTestWithFdoProfile,
+		android.FixtureAddTextFile("toolchain/pgo-profiles/sampling/libFoo.afdo", ""),
+		android.MockFS{
+			"afdo_profiles_package/Android.bp": []byte(`
+				soong_namespace {
+				}
+				fdo_profile {
+					name: "libFoo_afdo",
+					profile: "libFoo.afdo",
+				}
+			`),
+		}.AddToFixture(),
 	).RunTestWithBp(t, bp)
 
 	libTest := result.ModuleForTests("libTest", "android_arm64_armv8-a_shared").Module()
@@ -150,7 +204,6 @@
 			t.Errorf("Expected no afdo variant of 'bar', got %q", v)
 		}
 	}
-
 }
 
 func TestAfdoEnabledWithRuntimeDepNoAfdo(t *testing.T) {
@@ -166,11 +219,24 @@
 		name: "libFoo",
 	}
 	`
-	prepareForAfdoTest := android.FixtureAddTextFile("toolchain/pgo-profiles/sampling/libTest.afdo", "TEST")
 
 	result := android.GroupFixturePreparers(
 		prepareForCcTest,
-		prepareForAfdoTest,
+		prepareForTestWithFdoProfile,
+		android.FixtureAddTextFile("afdo_profiles_package/libTest.afdo", ""),
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.AfdoProfiles = []string{
+				"libTest://afdo_profiles_package:libTest_afdo",
+			}
+		}),
+		android.MockFS{
+			"afdo_profiles_package/Android.bp": []byte(`
+				fdo_profile {
+					name: "libTest_afdo",
+					profile: "libTest.afdo",
+				}
+			`),
+		}.AddToFixture(),
 	).RunTestWithBp(t, bp)
 
 	libFooVariants := result.ModuleVariantsForTests("libFoo")
@@ -182,7 +248,6 @@
 }
 
 func TestAfdoEnabledWithMultiArchs(t *testing.T) {
-	t.Parallel()
 	bp := `
 	cc_library_shared {
 		name: "foo",
@@ -192,20 +257,43 @@
 	}
 `
 	result := android.GroupFixturePreparers(
+		prepareForTestWithFdoProfile,
 		prepareForCcTest,
-		android.FixtureAddTextFile("toolchain/pgo-profiles/sampling/foo_arm.afdo", "TEST"),
-		android.FixtureAddTextFile("toolchain/pgo-profiles/sampling/foo_arm64.afdo", "TEST"),
+		android.FixtureAddTextFile("afdo_profiles_package/foo_arm.afdo", ""),
+		android.FixtureAddTextFile("afdo_profiles_package/foo_arm64.afdo", ""),
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.AfdoProfiles = []string{
+				"foo://afdo_profiles_package:foo_afdo",
+			}
+		}),
+		android.MockFS{
+			"afdo_profiles_package/Android.bp": []byte(`
+				soong_namespace {
+				}
+				fdo_profile {
+					name: "foo_afdo",
+					arch: {
+						arm: {
+							profile: "foo_arm.afdo",
+						},
+						arm64: {
+							profile: "foo_arm64.afdo",
+						}
+					}
+				}
+			`),
+		}.AddToFixture(),
 	).RunTestWithBp(t, bp)
 
 	fooArm := result.ModuleForTests("foo", "android_arm_armv7-a-neon_shared")
 	fooArmCFlags := fooArm.Rule("cc").Args["cFlags"]
-	if w := "-fprofile-sample-use=toolchain/pgo-profiles/sampling/foo_arm.afdo"; !strings.Contains(fooArmCFlags, w) {
+	if w := "-fprofile-sample-use=afdo_profiles_package/foo_arm.afdo"; !strings.Contains(fooArmCFlags, w) {
 		t.Errorf("Expected 'foo' to enable afdo, but did not find %q in cflags %q", w, fooArmCFlags)
 	}
 
 	fooArm64 := result.ModuleForTests("foo", "android_arm64_armv8-a_shared")
 	fooArm64CFlags := fooArm64.Rule("cc").Args["cFlags"]
-	if w := "-fprofile-sample-use=toolchain/pgo-profiles/sampling/foo_arm64.afdo"; !strings.Contains(fooArm64CFlags, w) {
+	if w := "-fprofile-sample-use=afdo_profiles_package/foo_arm64.afdo"; !strings.Contains(fooArm64CFlags, w) {
 		t.Errorf("Expected 'foo' to enable afdo, but did not find %q in cflags %q", w, fooArm64CFlags)
 	}
 }
@@ -234,46 +322,65 @@
 	`
 
 	result := android.GroupFixturePreparers(
+		prepareForTestWithFdoProfile,
 		prepareForCcTest,
-		android.FixtureAddTextFile("toolchain/pgo-profiles/sampling/libTest.afdo", "TEST"),
-		android.FixtureAddTextFile("toolchain/pgo-profiles/sampling/libBar.afdo", "TEST"),
+		android.FixtureAddTextFile("afdo_profiles_package/libTest.afdo", ""),
+		android.FixtureAddTextFile("afdo_profiles_package/libBar.afdo", ""),
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.AfdoProfiles = []string{
+				"libTest://afdo_profiles_package:libTest_afdo",
+				"libBar://afdo_profiles_package:libBar_afdo",
+			}
+		}),
+		android.MockFS{
+			"afdo_profiles_package/Android.bp": []byte(`
+				fdo_profile {
+					name: "libTest_afdo",
+					profile: "libTest.afdo",
+				}
+				fdo_profile {
+					name: "libBar_afdo",
+					profile: "libBar.afdo",
+				}
+			`),
+		}.AddToFixture(),
 	).RunTestWithBp(t, bp)
 
-	expectedCFlagLibTest := "-fprofile-sample-use=toolchain/pgo-profiles/sampling/libTest.afdo"
-	expectedCFlagLibBar := "-fprofile-sample-use=toolchain/pgo-profiles/sampling/libBar.afdo"
+	expectedCFlagLibTest := "-fprofile-sample-use=afdo_profiles_package/libTest.afdo"
+	expectedCFlagLibBar := "-fprofile-sample-use=afdo_profiles_package/libBar.afdo"
 
 	libTest := result.ModuleForTests("libTest", "android_arm64_armv8-a_shared")
-	libTestAfdoVariantOfLibFoo := result.ModuleForTests("libFoo", "android_arm64_armv8-a_static_afdo-libTest")
+	libFooAfdoVariantWithLibTest := result.ModuleForTests("libFoo", "android_arm64_armv8-a_static_afdo-libTest")
 
 	libBar := result.ModuleForTests("libBar", "android_arm64_armv8-a_shared")
-	libBarAfdoVariantOfLibFoo := result.ModuleForTests("libFoo", "android_arm64_armv8-a_static_afdo-libBar")
+	libFooAfdoVariantWithLibBar := result.ModuleForTests("libFoo", "android_arm64_armv8-a_static_afdo-libBar")
 
-	// Check cFlags of afdo-enabled modules and the afdo-variant of their static deps
+	// Check cFlags of afdo-enabled module and the afdo-variant of its static deps
 	cFlags := libTest.Rule("cc").Args["cFlags"]
 	if !strings.Contains(cFlags, expectedCFlagLibTest) {
 		t.Errorf("Expected 'libTest' to enable afdo, but did not find %q in cflags %q", expectedCFlagLibTest, cFlags)
 	}
 	cFlags = libBar.Rule("cc").Args["cFlags"]
 	if !strings.Contains(cFlags, expectedCFlagLibBar) {
-		t.Errorf("Expected 'libBar' to enable afdo, but did not find %q in cflags %q", expectedCFlagLibBar, cFlags)
+		t.Errorf("Expected 'libTest' to enable afdo, but did not find %q in cflags %q", expectedCFlagLibBar, cFlags)
 	}
 
-	cFlags = libTestAfdoVariantOfLibFoo.Rule("cc").Args["cFlags"]
+	cFlags = libFooAfdoVariantWithLibTest.Rule("cc").Args["cFlags"]
 	if !strings.Contains(cFlags, expectedCFlagLibTest) {
-		t.Errorf("Expected 'libTestAfdoVariantOfLibFoo' to enable afdo, but did not find %q in cflags %q", expectedCFlagLibTest, cFlags)
+		t.Errorf("Expected 'libFooAfdoVariantWithLibTest' to enable afdo, but did not find %q in cflags %q", expectedCFlagLibTest, cFlags)
 	}
 
-	cFlags = libBarAfdoVariantOfLibFoo.Rule("cc").Args["cFlags"]
+	cFlags = libFooAfdoVariantWithLibBar.Rule("cc").Args["cFlags"]
 	if !strings.Contains(cFlags, expectedCFlagLibBar) {
-		t.Errorf("Expected 'libBarAfdoVariantOfLibFoo' to enable afdo, but did not find %q in cflags %q", expectedCFlagLibBar, cFlags)
+		t.Errorf("Expected 'libBarAfdoVariant' to enable afdo, but did not find %q in cflags %q", expectedCFlagLibBar, cFlags)
 	}
 
 	// Check dependency edges of static deps
-	if !hasDirectDep(result, libTest.Module(), libTestAfdoVariantOfLibFoo.Module()) {
+	if !hasDirectDep(result, libTest.Module(), libFooAfdoVariantWithLibTest.Module()) {
 		t.Errorf("libTest missing dependency on afdo variant of libFoo")
 	}
 
-	if !hasDirectDep(result, libBar.Module(), libBarAfdoVariantOfLibFoo.Module()) {
-		t.Errorf("libBar missing dependency on afdo variant of libFoo")
+	if !hasDirectDep(result, libBar.Module(), libFooAfdoVariantWithLibBar.Module()) {
+		t.Errorf("libFoo missing dependency on non-afdo variant of libBar")
 	}
 }
diff --git a/cc/cc.go b/cc/cc.go
index b029d71..9c555a1 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -52,6 +52,7 @@
 		ctx.BottomUp("test_per_src", TestPerSrcMutator).Parallel()
 		ctx.BottomUp("version", versionMutator).Parallel()
 		ctx.BottomUp("begin", BeginMutator).Parallel()
+		ctx.BottomUp("fdo_profile", fdoProfileMutator)
 	})
 
 	ctx.PostDepsMutators(func(ctx android.RegisterMutatorsContext) {
@@ -763,6 +764,7 @@
 	testPerSrcDepTag      = dependencyTag{name: "test_per_src"}
 	stubImplDepTag        = dependencyTag{name: "stub_impl"}
 	JniFuzzLibTag         = dependencyTag{name: "jni_fuzz_lib_tag"}
+	FdoProfileTag         = dependencyTag{name: "fdo_profile"}
 )
 
 func IsSharedDepTag(depTag blueprint.DependencyTag) bool {
@@ -1336,7 +1338,7 @@
 
 func (c *Module) isAfdoCompile() bool {
 	if afdo := c.afdo; afdo != nil {
-		return afdo.Properties.AfdoTarget != nil
+		return afdo.Properties.FdoProfilePath != nil
 	}
 	return false
 }
@@ -2162,9 +2164,6 @@
 	if c.lto != nil {
 		c.lto.begin(ctx)
 	}
-	if c.afdo != nil {
-		c.afdo.begin(ctx)
-	}
 	if c.pgo != nil {
 		c.pgo.begin(ctx)
 	}
@@ -2239,6 +2238,10 @@
 	}
 	ctx.ctx = ctx
 
+	if !actx.Host() || !ctx.static() || ctx.staticBinary() {
+		c.afdo.addDep(ctx, actx)
+	}
+
 	c.begin(ctx)
 }
 
diff --git a/cc/fdo_profile.go b/cc/fdo_profile.go
new file mode 100644
index 0000000..18af8b5
--- /dev/null
+++ b/cc/fdo_profile.go
@@ -0,0 +1,85 @@
+// Copyright 2023 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cc
+
+import (
+	"android/soong/android"
+
+	"github.com/google/blueprint"
+)
+
+func init() {
+	RegisterFdoProfileBuildComponents(android.InitRegistrationContext)
+}
+
+func RegisterFdoProfileBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("fdo_profile", fdoProfileFactory)
+}
+
+type fdoProfile struct {
+	android.ModuleBase
+
+	properties fdoProfileProperties
+}
+
+type fdoProfileProperties struct {
+	Profile *string `android:"arch_variant"`
+}
+
+// FdoProfileInfo is provided by FdoProfileProvider
+type FdoProfileInfo struct {
+	Path android.Path
+}
+
+// FdoProfileProvider is used to provide path to an fdo profile
+var FdoProfileProvider = blueprint.NewMutatorProvider(FdoProfileInfo{}, "fdo_profile")
+
+// FdoProfileMutatorInterface is the interface implemented by fdo_profile module type
+// module types that can depend on an fdo_profile module
+type FdoProfileMutatorInterface interface {
+	// FdoProfileMutator eithers set or get FdoProfileProvider
+	FdoProfileMutator(ctx android.BottomUpMutatorContext)
+}
+
+var _ FdoProfileMutatorInterface = (*fdoProfile)(nil)
+
+// GenerateAndroidBuildActions of fdo_profile does not have any build actions
+func (fp *fdoProfile) GenerateAndroidBuildActions(ctx android.ModuleContext) {}
+
+// FdoProfileMutator sets FdoProfileProvider to fdo_profile module
+// or sets afdo.Properties.FdoProfilePath to path in FdoProfileProvider of the depended fdo_profile
+func (fp *fdoProfile) FdoProfileMutator(ctx android.BottomUpMutatorContext) {
+	if fp.properties.Profile != nil {
+		path := android.PathForModuleSrc(ctx, *fp.properties.Profile)
+		ctx.SetProvider(FdoProfileProvider, FdoProfileInfo{
+			Path: path,
+		})
+	}
+}
+
+// fdoProfileMutator calls the generic FdoProfileMutator function of FdoProfileMutator
+// which is implemented by cc and cc.FdoProfile
+func fdoProfileMutator(ctx android.BottomUpMutatorContext) {
+	if f, ok := ctx.Module().(FdoProfileMutatorInterface); ok {
+		f.FdoProfileMutator(ctx)
+	}
+}
+
+func fdoProfileFactory() android.Module {
+	m := &fdoProfile{}
+	m.AddProperties(&m.properties)
+	android.InitAndroidMultiTargetsArchModule(m, android.DeviceSupported, android.MultilibBoth)
+	return m
+}