Merge "Separate LLNDK from the platform library dumps" into main
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 7e67c0f..d3959ec 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -1455,6 +1455,7 @@
 			name: "libc",
 			no_libcrt: true,
 			nocrt: true,
+			no_crt_pad_segment: true,
 			stl: "none",
 			system_shared_libs: [],
 			stubs: { versions: ["1"] },
@@ -1469,6 +1470,7 @@
 			name: "libclang_rt.hwasan",
 			no_libcrt: true,
 			nocrt: true,
+			no_crt_pad_segment: true,
 			stl: "none",
 			system_shared_libs: [],
 			srcs: [""],
@@ -1511,6 +1513,7 @@
 			name: "libc",
 			no_libcrt: true,
 			nocrt: true,
+			no_crt_pad_segment: true,
 			stl: "none",
 			system_shared_libs: [],
 			stubs: { versions: ["1"] },
@@ -1521,6 +1524,7 @@
 			name: "libclang_rt.hwasan",
 			no_libcrt: true,
 			nocrt: true,
+			no_crt_pad_segment: true,
 			stl: "none",
 			system_shared_libs: [],
 			srcs: [""],
@@ -3768,13 +3772,6 @@
 				"lib64/libvndk.so",
 				"lib64/libvndksp.so"),
 		},
-		{
-			vndkVersion: "",
-			expectedFiles: append(commonFiles,
-				// Legacy VNDK APEX contains only VNDK-SP files (of core variant)
-				"lib/libvndksp.so",
-				"lib64/libvndksp.so"),
-		},
 	}
 	for _, tc := range testCases {
 		t.Run("VNDK.current with DeviceVndkVersion="+tc.vndkVersion, func(t *testing.T) {
diff --git a/apex/dexpreopt_bootjars_test.go b/apex/dexpreopt_bootjars_test.go
index d9ab8fa..7a17f50 100644
--- a/apex/dexpreopt_bootjars_test.go
+++ b/apex/dexpreopt_bootjars_test.go
@@ -164,6 +164,7 @@
 		"out/soong/dexpreopt_arm64/dex_bootjars_input/baz.jar",
 		"out/soong/.intermediates/art-bootclasspath-fragment/android_common_apex10000/art-bootclasspath-fragment/boot.prof",
 		"out/soong/.intermediates/default/java/dex_bootjars/android_common/boot/boot.prof",
+		"out/soong/dexpreopt/uffd_gc_flag.txt",
 	}
 
 	expectedOutputs := []string{
@@ -201,6 +202,7 @@
 		"out/soong/dexpreopt_arm64/dex_bootjars_input/baz.jar",
 		"out/soong/.intermediates/prebuilt_com.android.art.deapexer/android_common/deapexer/etc/boot-image.prof",
 		"out/soong/.intermediates/default/java/dex_bootjars/android_common/boot/boot.prof",
+		"out/soong/dexpreopt/uffd_gc_flag.txt",
 	}
 
 	expectedOutputs := []string{
diff --git a/apex/vndk_test.go b/apex/vndk_test.go
index e2aee96..894aece 100644
--- a/apex/vndk_test.go
+++ b/apex/vndk_test.go
@@ -8,66 +8,6 @@
 	"android/soong/android"
 )
 
-func TestVndkApexForVndkLite(t *testing.T) {
-	ctx := testApex(t, `
-		apex_vndk {
-			name: "com.android.vndk.current",
-			key: "com.android.vndk.current.key",
-			updatable: false,
-		}
-
-		apex_key {
-			name: "com.android.vndk.current.key",
-			public_key: "testkey.avbpubkey",
-			private_key: "testkey.pem",
-		}
-
-		cc_library {
-			name: "libvndk",
-			srcs: ["mylib.cpp"],
-			vendor_available: true,
-			product_available: true,
-			vndk: {
-				enabled: true,
-			},
-			system_shared_libs: [],
-			stl: "none",
-			apex_available: [ "com.android.vndk.current" ],
-		}
-
-		cc_library {
-			name: "libvndksp",
-			srcs: ["mylib.cpp"],
-			vendor_available: true,
-			product_available: true,
-			vndk: {
-				enabled: true,
-				support_system_process: true,
-			},
-			system_shared_libs: [],
-			stl: "none",
-			apex_available: [ "com.android.vndk.current" ],
-		}
-	`+vndkLibrariesTxtFiles("current"),
-		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
-			variables.DeviceVndkVersion = proptools.StringPtr("")
-			variables.KeepVndk = proptools.BoolPtr(true)
-		}),
-	)
-	// VNDK-Lite contains only core variants of VNDK-Sp libraries
-	ensureExactContents(t, ctx, "com.android.vndk.current", "android_common", []string{
-		"lib/libvndksp.so",
-		"lib/libc++.so",
-		"lib64/libvndksp.so",
-		"lib64/libc++.so",
-		"etc/llndk.libraries.29.txt",
-		"etc/vndkcore.libraries.29.txt",
-		"etc/vndksp.libraries.29.txt",
-		"etc/vndkprivate.libraries.29.txt",
-		"etc/vndkproduct.libraries.29.txt",
-	})
-}
-
 func TestVndkApexUsesVendorVariant(t *testing.T) {
 	bp := `
 		apex_vndk {
diff --git a/cc/androidmk.go b/cc/androidmk.go
index c39668c..20673e8 100644
--- a/cc/androidmk.go
+++ b/cc/androidmk.go
@@ -107,7 +107,6 @@
 				}
 				entries.SetString("LOCAL_SOONG_LINK_TYPE", c.makeLinkType)
 				if c.InVendorOrProduct() {
-					entries.SetBool("LOCAL_USE_VNDK", true)
 					if c.IsVndk() && !c.static() {
 						entries.SetString("LOCAL_SOONG_VNDK_VERSION", c.VndkVersion())
 						// VNDK libraries available to vendor are not installed because
@@ -117,6 +116,11 @@
 						}
 					}
 				}
+				if c.InVendor() {
+					entries.SetBool("LOCAL_IN_VENDOR", true)
+				} else if c.InProduct() {
+					entries.SetBool("LOCAL_IN_PRODUCT", true)
+				}
 				if c.Properties.IsSdkVariant && c.Properties.SdkAndPlatformVariantVisibleToMake {
 					// Make the SDK variant uninstallable so that there are not two rules to install
 					// to the same location.
diff --git a/cc/config/bionic.go b/cc/config/bionic.go
index a1e3851..ed724f5 100644
--- a/cc/config/bionic.go
+++ b/cc/config/bionic.go
@@ -24,6 +24,7 @@
 	bionicCrtBeginStaticBinary, bionicCrtEndStaticBinary   = []string{"crtbegin_static"}, []string{"crtend_android"}
 	bionicCrtBeginSharedBinary, bionicCrtEndSharedBinary   = []string{"crtbegin_dynamic"}, []string{"crtend_android"}
 	bionicCrtBeginSharedLibrary, bionicCrtEndSharedLibrary = []string{"crtbegin_so"}, []string{"crtend_so"}
+	bionicCrtPadSegmentSharedLibrary                       = []string{"crt_pad_segment"}
 )
 
 func (toolchainBionic) Bionic() bool { return true }
@@ -36,9 +37,10 @@
 
 func (toolchainBionic) AvailableLibraries() []string { return nil }
 
-func (toolchainBionic) CrtBeginStaticBinary() []string  { return bionicCrtBeginStaticBinary }
-func (toolchainBionic) CrtBeginSharedBinary() []string  { return bionicCrtBeginSharedBinary }
-func (toolchainBionic) CrtBeginSharedLibrary() []string { return bionicCrtBeginSharedLibrary }
-func (toolchainBionic) CrtEndStaticBinary() []string    { return bionicCrtEndStaticBinary }
-func (toolchainBionic) CrtEndSharedBinary() []string    { return bionicCrtEndSharedBinary }
-func (toolchainBionic) CrtEndSharedLibrary() []string   { return bionicCrtEndSharedLibrary }
+func (toolchainBionic) CrtBeginStaticBinary() []string       { return bionicCrtBeginStaticBinary }
+func (toolchainBionic) CrtBeginSharedBinary() []string       { return bionicCrtBeginSharedBinary }
+func (toolchainBionic) CrtBeginSharedLibrary() []string      { return bionicCrtBeginSharedLibrary }
+func (toolchainBionic) CrtEndStaticBinary() []string         { return bionicCrtEndStaticBinary }
+func (toolchainBionic) CrtEndSharedBinary() []string         { return bionicCrtEndSharedBinary }
+func (toolchainBionic) CrtEndSharedLibrary() []string        { return bionicCrtEndSharedLibrary }
+func (toolchainBionic) CrtPadSegmentSharedLibrary() []string { return bionicCrtPadSegmentSharedLibrary }
diff --git a/cc/config/toolchain.go b/cc/config/toolchain.go
index 62f75d1..71e98fe 100644
--- a/cc/config/toolchain.go
+++ b/cc/config/toolchain.go
@@ -100,6 +100,7 @@
 	CrtEndStaticBinary() []string
 	CrtEndSharedBinary() []string
 	CrtEndSharedLibrary() []string
+	CrtPadSegmentSharedLibrary() []string
 
 	// DefaultSharedLibraries returns the list of shared libraries that will be added to all
 	// targets unless they explicitly specify system_shared_libs.
@@ -155,12 +156,13 @@
 
 type toolchainNoCrt struct{}
 
-func (toolchainNoCrt) CrtBeginStaticBinary() []string  { return nil }
-func (toolchainNoCrt) CrtBeginSharedBinary() []string  { return nil }
-func (toolchainNoCrt) CrtBeginSharedLibrary() []string { return nil }
-func (toolchainNoCrt) CrtEndStaticBinary() []string    { return nil }
-func (toolchainNoCrt) CrtEndSharedBinary() []string    { return nil }
-func (toolchainNoCrt) CrtEndSharedLibrary() []string   { return nil }
+func (toolchainNoCrt) CrtBeginStaticBinary() []string       { return nil }
+func (toolchainNoCrt) CrtBeginSharedBinary() []string       { return nil }
+func (toolchainNoCrt) CrtBeginSharedLibrary() []string      { return nil }
+func (toolchainNoCrt) CrtEndStaticBinary() []string         { return nil }
+func (toolchainNoCrt) CrtEndSharedBinary() []string         { return nil }
+func (toolchainNoCrt) CrtEndSharedLibrary() []string        { return nil }
+func (toolchainNoCrt) CrtPadSegmentSharedLibrary() []string { return nil }
 
 func (toolchainBase) DefaultSharedLibraries() []string {
 	return nil
diff --git a/cc/config/x86_linux_host.go b/cc/config/x86_linux_host.go
index f95da0b..f497bf9 100644
--- a/cc/config/x86_linux_host.go
+++ b/cc/config/x86_linux_host.go
@@ -328,12 +328,13 @@
 
 func (toolchainMusl) Musl() bool { return true }
 
-func (toolchainMusl) CrtBeginStaticBinary() []string  { return muslCrtBeginStaticBinary }
-func (toolchainMusl) CrtBeginSharedBinary() []string  { return muslCrtBeginSharedBinary }
-func (toolchainMusl) CrtBeginSharedLibrary() []string { return muslCrtBeginSharedLibrary }
-func (toolchainMusl) CrtEndStaticBinary() []string    { return muslCrtEndStaticBinary }
-func (toolchainMusl) CrtEndSharedBinary() []string    { return muslCrtEndSharedBinary }
-func (toolchainMusl) CrtEndSharedLibrary() []string   { return muslCrtEndSharedLibrary }
+func (toolchainMusl) CrtBeginStaticBinary() []string       { return muslCrtBeginStaticBinary }
+func (toolchainMusl) CrtBeginSharedBinary() []string       { return muslCrtBeginSharedBinary }
+func (toolchainMusl) CrtBeginSharedLibrary() []string      { return muslCrtBeginSharedLibrary }
+func (toolchainMusl) CrtEndStaticBinary() []string         { return muslCrtEndStaticBinary }
+func (toolchainMusl) CrtEndSharedBinary() []string         { return muslCrtEndSharedBinary }
+func (toolchainMusl) CrtEndSharedLibrary() []string        { return muslCrtEndSharedLibrary }
+func (toolchainMusl) CrtPadSegmentSharedLibrary() []string { return nil }
 
 func (toolchainMusl) DefaultSharedLibraries() []string { return MuslDefaultSharedLibraries }
 
diff --git a/cc/library.go b/cc/library.go
index 9990138..4684d32 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -974,6 +974,10 @@
 		if library.baseLinker.Properties.crt() {
 			deps.CrtBegin = append(deps.CrtBegin, ctx.toolchain().CrtBeginSharedLibrary()...)
 			deps.CrtEnd = append(deps.CrtEnd, ctx.toolchain().CrtEndSharedLibrary()...)
+
+		}
+		if library.baseLinker.Properties.crtPadSegment() {
+			deps.CrtEnd = append(deps.CrtEnd, ctx.toolchain().CrtPadSegmentSharedLibrary()...)
 		}
 		deps.WholeStaticLibs = append(deps.WholeStaticLibs, library.SharedProperties.Shared.Whole_static_libs...)
 		deps.StaticLibs = append(deps.StaticLibs, library.SharedProperties.Shared.Static_libs...)
diff --git a/cc/linker.go b/cc/linker.go
index 85c128e..2c50db2 100644
--- a/cc/linker.go
+++ b/cc/linker.go
@@ -91,6 +91,10 @@
 	// compiling crt or libc.
 	Nocrt *bool `android:"arch_variant"`
 
+	// don't link in crt_pad_segment. This flag is currently only used internal to
+	// soong for testing and for vndk prebuilt shared libraries.
+	No_crt_pad_segment *bool `android:"arch_variant"`
+
 	// deprecated and ignored because lld makes it unnecessary. See b/189475744.
 	Group_static_libs *bool `android:"arch_variant"`
 
@@ -253,6 +257,10 @@
 	return blp.No_libcrt == nil || !*blp.No_libcrt
 }
 
+func (blp *BaseLinkerProperties) crtPadSegment() bool {
+	return blp.No_crt_pad_segment == nil || !*blp.No_crt_pad_segment
+}
+
 func NewBaseLinker(sanitize *sanitize) *baseLinker {
 	return &baseLinker{sanitize: sanitize}
 }
diff --git a/cc/testing.go b/cc/testing.go
index bac41e7..9c2900c 100644
--- a/cc/testing.go
+++ b/cc/testing.go
@@ -77,6 +77,7 @@
 			no_libcrt: true,
 			sdk_version: "minimum",
 			nocrt: true,
+			no_crt_pad_segment: true,
 			system_shared_libs: [],
 			stl: "none",
 			check_elf_files: false,
@@ -385,6 +386,11 @@
 		}
 
 		cc_object {
+			name: "crt_pad_segment",
+			defaults: ["crt_defaults"],
+		}
+
+		cc_object {
 			name: "crtbrand",
 			defaults: ["crt_defaults"],
 			srcs: ["crtbrand.c"],
diff --git a/cc/vendor_snapshot_test.go b/cc/vendor_snapshot_test.go
index 890a533..0a55431 100644
--- a/cc/vendor_snapshot_test.go
+++ b/cc/vendor_snapshot_test.go
@@ -341,6 +341,7 @@
 		vendor: true,
 		nocrt: true,
 		no_libcrt: true,
+		no_crt_pad_segment: true,
 		stl: "none",
 		system_shared_libs: [],
 		compile_multilib: "64",
@@ -458,6 +459,7 @@
 		vendor: true,
 		nocrt: true,
 		no_libcrt: true,
+		no_crt_pad_segment: true,
 		stl: "none",
 		system_shared_libs: [],
 	}
@@ -467,6 +469,7 @@
 		vendor: true,
 		nocrt: true,
 		no_libcrt: true,
+		no_crt_pad_segment: true,
 		stl: "none",
 		system_shared_libs: [],
 		shared_libs: ["libvndk", "libvendor_available", "libllndk"],
@@ -487,6 +490,7 @@
 		vendor: true,
 		nocrt: true,
 		no_libcrt: true,
+		no_crt_pad_segment: true,
 		stl: "none",
 		system_shared_libs: [],
 		static_libs: ["libvendor"],
@@ -501,6 +505,7 @@
 		vendor: true,
 		nocrt: true,
 		no_libcrt: true,
+		no_crt_pad_segment: true,
 		stl: "none",
 		system_shared_libs: [],
 		vndk: {
@@ -597,6 +602,7 @@
 		target_arch: "arm64",
 		compile_multilib: "both",
 		vendor: true,
+		no_crt_pad_segment: true,
 		shared_libs: [
 			"libvendor_without_snapshot",
 			"libvendor_available",
@@ -620,6 +626,7 @@
 		target_arch: "arm64",
 		compile_multilib: "both",
 		vendor: true,
+		no_crt_pad_segment: true,
 		overrides: ["libvendor"],
 		shared_libs: [
 			"libvendor_without_snapshot",
@@ -657,6 +664,7 @@
 		target_arch: "arm64",
 		compile_multilib: "32",
 		vendor: true,
+		no_crt_pad_segment: true,
 		arch: {
 			arm: {
 				src: "lib32.so",
@@ -683,6 +691,7 @@
 		target_arch: "arm64",
 		compile_multilib: "64",
 		vendor: true,
+		no_crt_pad_segment: true,
 		arch: {
 			arm64: {
 				src: "lib64.so",
@@ -722,6 +731,7 @@
 		target_arch: "arm64",
 		compile_multilib: "both",
 		vendor: true,
+		no_crt_pad_segment: true,
 		arch: {
 			arm64: {
 				src: "libvendor_available.so",
diff --git a/cc/vndk.go b/cc/vndk.go
index 0e0dba9..2b2ea64 100644
--- a/cc/vndk.go
+++ b/cc/vndk.go
@@ -377,22 +377,17 @@
 			return false
 		}
 
-		// ignore prebuilt vndk modules that are newer than or equal to the platform vndk version
-		platformVndkApiLevel := android.ApiLevelOrPanic(mctx, mctx.DeviceConfig().PlatformVndkVersion())
-		if platformVndkApiLevel.LessThanOrEqualTo(android.ApiLevelOrPanic(mctx, p.Version())) {
-			return false
+		platformVndkVersion := mctx.DeviceConfig().PlatformVndkVersion()
+		if platformVndkVersion != "" {
+			// ignore prebuilt vndk modules that are newer than or equal to the platform vndk version
+			platformVndkApiLevel := android.ApiLevelOrPanic(mctx, platformVndkVersion)
+			if platformVndkApiLevel.LessThanOrEqualTo(android.ApiLevelOrPanic(mctx, p.Version())) {
+				return false
+			}
 		}
 	}
 
 	if lib, ok := m.linker.(libraryInterface); ok {
-		// VNDK APEX for VNDK-Lite devices will have VNDK-SP libraries from core variants
-		if mctx.DeviceConfig().VndkVersion() == "" {
-			// b/73296261: filter out libz.so because it is considered as LLNDK for VNDK-lite devices
-			if mctx.ModuleName() == "libz" {
-				return false
-			}
-			return m.ImageVariation().Variation == android.CoreVariation && lib.shared() && m.IsVndkSp() && !m.IsVndkExt()
-		}
 		// VNDK APEX doesn't need stub variants
 		if lib.buildStubs() {
 			return false
diff --git a/cc/vndk_prebuilt.go b/cc/vndk_prebuilt.go
index eb1790f..43030b8 100644
--- a/cc/vndk_prebuilt.go
+++ b/cc/vndk_prebuilt.go
@@ -131,11 +131,14 @@
 
 func (p *vndkPrebuiltLibraryDecorator) link(ctx ModuleContext,
 	flags Flags, deps PathDeps, objs Objects) android.Path {
-	platformVndkApiLevel := android.ApiLevelOrPanic(ctx, ctx.DeviceConfig().PlatformVndkVersion())
-	if platformVndkApiLevel.LessThanOrEqualTo(android.ApiLevelOrPanic(ctx, p.Version())) {
-		// This prebuilt VNDK module is not required for the current build
-		ctx.Module().HideFromMake()
-		return nil
+	platformVndkVersion := ctx.DeviceConfig().PlatformVndkVersion()
+	if platformVndkVersion != "" {
+		platformVndkApiLevel := android.ApiLevelOrPanic(ctx, platformVndkVersion)
+		if platformVndkApiLevel.LessThanOrEqualTo(android.ApiLevelOrPanic(ctx, p.Version())) {
+			// This prebuilt VNDK module is not required for the current build
+			ctx.Module().HideFromMake()
+			return nil
+		}
 	}
 
 	if !p.MatchesWithDevice(ctx.DeviceConfig()) {
@@ -232,6 +235,7 @@
 	prebuilt.properties.Check_elf_files = BoolPtr(false)
 	prebuilt.baseLinker.Properties.No_libcrt = BoolPtr(true)
 	prebuilt.baseLinker.Properties.Nocrt = BoolPtr(true)
+	prebuilt.baseLinker.Properties.No_crt_pad_segment = BoolPtr(true)
 
 	// Prevent default system libs (libc, libm, and libdl) from being linked
 	if prebuilt.baseLinker.Properties.System_shared_libs == nil {
@@ -246,14 +250,6 @@
 		&prebuilt.properties,
 	)
 
-	android.AddLoadHook(module, func(ctx android.LoadHookContext) {
-		// empty BOARD_VNDK_VERSION implies that the device won't support
-		// system only OTA. In this case, VNDK snapshots aren't needed.
-		if ctx.DeviceConfig().VndkVersion() == "" {
-			ctx.Module().Disable()
-		}
-	})
-
 	return module
 }
 
diff --git a/dexpreopt/config.go b/dexpreopt/config.go
index 6163952..fe6317c 100644
--- a/dexpreopt/config.go
+++ b/dexpreopt/config.go
@@ -98,7 +98,9 @@
 	// measure, as it masks real errors and affects performance.
 	RelaxUsesLibraryCheck bool
 
-	EnableUffdGc bool // preopt with the assumption that userfaultfd GC will be used on device.
+	// "true" to force preopt with CMC GC (a.k.a., UFFD GC); "false" to force preopt with CC GC;
+	// "default" to determine the GC type based on the kernel version file.
+	EnableUffdGc string
 }
 
 var allPlatformSystemServerJarsKey = android.NewOnceKey("allPlatformSystemServerJars")
@@ -154,6 +156,7 @@
 	Zip2zip          android.Path
 	ManifestCheck    android.Path
 	ConstructContext android.Path
+	UffdGcFlag       android.WritablePath
 }
 
 type ModuleConfig struct {
@@ -537,6 +540,7 @@
 		Zip2zip:          ctx.Config().HostToolPath(ctx, "zip2zip"),
 		ManifestCheck:    ctx.Config().HostToolPath(ctx, "manifest_check"),
 		ConstructContext: ctx.Config().HostToolPath(ctx, "construct_context"),
+		UffdGcFlag:       getUffdGcFlagPath(ctx),
 	}
 }
 
@@ -588,6 +592,7 @@
 	Zip2zip          string
 	ManifestCheck    string
 	ConstructContext string
+	UffdGcFlag       string
 }
 
 // ParseGlobalSoongConfig parses the given data assumed to be read from the
@@ -609,6 +614,7 @@
 		Zip2zip:          constructPath(ctx, jc.Zip2zip),
 		ManifestCheck:    constructPath(ctx, jc.ManifestCheck),
 		ConstructContext: constructPath(ctx, jc.ConstructContext),
+		UffdGcFlag:       constructWritablePath(ctx, jc.UffdGcFlag),
 	}
 
 	return config, nil
@@ -633,12 +639,15 @@
 }
 
 func (s *globalSoongConfigSingleton) GenerateBuildActions(ctx android.SingletonContext) {
-	checkBootJarsConfigConsistency(ctx, GetGlobalConfig(ctx), ctx.Config())
+	global := GetGlobalConfig(ctx)
+	checkBootJarsConfigConsistency(ctx, global, ctx.Config())
 
-	if GetGlobalConfig(ctx).DisablePreopt {
+	if global.DisablePreopt {
 		return
 	}
 
+	buildUffdGcFlag(ctx, global)
+
 	config := GetCachedGlobalSoongConfig(ctx)
 	if config == nil {
 		// No module has enabled dexpreopting, so we assume there will be no calls
@@ -654,6 +663,7 @@
 		Zip2zip:          config.Zip2zip.String(),
 		ManifestCheck:    config.ManifestCheck.String(),
 		ConstructContext: config.ConstructContext.String(),
+		UffdGcFlag:       config.UffdGcFlag.String(),
 	}
 
 	data, err := json.Marshal(jc)
@@ -684,9 +694,32 @@
 		config.Zip2zip.String(),
 		config.ManifestCheck.String(),
 		config.ConstructContext.String(),
+		config.UffdGcFlag.String(),
 	}, " "))
 }
 
+func buildUffdGcFlag(ctx android.BuilderContext, global *GlobalConfig) {
+	uffdGcFlag := getUffdGcFlagPath(ctx)
+
+	if global.EnableUffdGc == "true" {
+		android.WriteFileRuleVerbatim(ctx, uffdGcFlag, "--runtime-arg -Xgc:CMC")
+	} else if global.EnableUffdGc == "false" {
+		android.WriteFileRuleVerbatim(ctx, uffdGcFlag, "")
+	} else if global.EnableUffdGc == "default" {
+		// Generated by `build/make/core/Makefile`.
+		kernelVersionFile := android.PathForOutput(ctx, "dexpreopt/kernel_version_for_uffd_gc.txt")
+		// Determine the UFFD GC flag by the kernel version file.
+		rule := android.NewRuleBuilder(pctx, ctx)
+		rule.Command().
+			Tool(ctx.Config().HostToolPath(ctx, "construct_uffd_gc_flag")).
+			Input(kernelVersionFile).
+			Output(uffdGcFlag)
+		rule.Restat().Build("dexpreopt_uffd_gc_flag", "dexpreopt_uffd_gc_flag")
+	} else {
+		panic(fmt.Sprintf("Unknown value of PRODUCT_ENABLE_UFFD_GC: %s", global.EnableUffdGc))
+	}
+}
+
 func GlobalConfigForTests(ctx android.PathContext) *GlobalConfig {
 	return &GlobalConfig{
 		DisablePreopt:                  false,
@@ -731,7 +764,7 @@
 	}
 }
 
-func globalSoongConfigForTests() *GlobalSoongConfig {
+func globalSoongConfigForTests(ctx android.BuilderContext) *GlobalSoongConfig {
 	return &GlobalSoongConfig{
 		Profman:          android.PathForTesting("profman"),
 		Dex2oat:          android.PathForTesting("dex2oat"),
@@ -740,5 +773,19 @@
 		Zip2zip:          android.PathForTesting("zip2zip"),
 		ManifestCheck:    android.PathForTesting("manifest_check"),
 		ConstructContext: android.PathForTesting("construct_context"),
+		UffdGcFlag:       android.PathForOutput(ctx, "dexpreopt_test", "uffd_gc_flag.txt"),
 	}
 }
+
+func GetDexpreoptDirName(ctx android.PathContext) string {
+	prefix := "dexpreopt_"
+	targets := ctx.Config().Targets[android.Android]
+	if len(targets) > 0 {
+		return prefix + targets[0].Arch.ArchType.String()
+	}
+	return prefix + "unknown_target"
+}
+
+func getUffdGcFlagPath(ctx android.PathContext) android.WritablePath {
+	return android.PathForOutput(ctx, "dexpreopt/uffd_gc_flag.txt")
+}
diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go
index 94707ba..04bc61d 100644
--- a/dexpreopt/dexpreopt.go
+++ b/dexpreopt/dexpreopt.go
@@ -390,7 +390,8 @@
 		Flag("--generate-build-id").
 		Flag("--abort-on-hard-verifier-error").
 		Flag("--force-determinism").
-		FlagWithArg("--no-inline-from=", "core-oj.jar")
+		FlagWithArg("--no-inline-from=", "core-oj.jar").
+		Text("$(cat").Input(globalSoong.UffdGcFlag).Text(")")
 
 	var preoptFlags []string
 	if len(module.PreoptFlags) > 0 {
@@ -506,10 +507,6 @@
 		cmd.FlagWithInput("--profile-file=", profile)
 	}
 
-	if global.EnableUffdGc {
-		cmd.Flag("--runtime-arg").Flag("-Xgc:CMC")
-	}
-
 	rule.Install(odexPath, odexInstallPath)
 	rule.Install(vdexPath, vdexInstallPath)
 }
diff --git a/dexpreopt/dexpreopt_test.go b/dexpreopt/dexpreopt_test.go
index 230fbb4..7071f3e 100644
--- a/dexpreopt/dexpreopt_test.go
+++ b/dexpreopt/dexpreopt_test.go
@@ -96,7 +96,7 @@
 func TestDexPreopt(t *testing.T) {
 	config := android.TestConfig("out", nil, "", nil)
 	ctx := android.BuilderContextForTesting(config)
-	globalSoong := globalSoongConfigForTests()
+	globalSoong := globalSoongConfigForTests(ctx)
 	global := GlobalConfigForTests(ctx)
 	module := testSystemModuleConfig(ctx, "test")
 	productPackages := android.PathForTesting("product_packages.txt")
@@ -114,12 +114,15 @@
 	if rule.Installs().String() != wantInstalls.String() {
 		t.Errorf("\nwant installs:\n   %v\ngot:\n   %v", wantInstalls, rule.Installs())
 	}
+
+	android.AssertStringListContains(t, "", rule.Inputs().RelativeToTop().Strings(),
+		"out/soong/dexpreopt_test/uffd_gc_flag.txt")
 }
 
 func TestDexPreoptSystemOther(t *testing.T) {
 	config := android.TestConfig("out", nil, "", nil)
 	ctx := android.BuilderContextForTesting(config)
-	globalSoong := globalSoongConfigForTests()
+	globalSoong := globalSoongConfigForTests(ctx)
 	global := GlobalConfigForTests(ctx)
 	systemModule := testSystemModuleConfig(ctx, "Stest")
 	systemProductModule := testSystemProductModuleConfig(ctx, "SPtest")
@@ -180,7 +183,7 @@
 func TestDexPreoptApexSystemServerJars(t *testing.T) {
 	config := android.TestConfig("out", nil, "", nil)
 	ctx := android.BuilderContextForTesting(config)
-	globalSoong := globalSoongConfigForTests()
+	globalSoong := globalSoongConfigForTests(ctx)
 	global := GlobalConfigForTests(ctx)
 	module := testApexModuleConfig(ctx, "service-A", "com.android.apex1")
 	productPackages := android.PathForTesting("product_packages.txt")
@@ -204,7 +207,7 @@
 func TestDexPreoptStandaloneSystemServerJars(t *testing.T) {
 	config := android.TestConfig("out", nil, "", nil)
 	ctx := android.BuilderContextForTesting(config)
-	globalSoong := globalSoongConfigForTests()
+	globalSoong := globalSoongConfigForTests(ctx)
 	global := GlobalConfigForTests(ctx)
 	module := testPlatformSystemServerModuleConfig(ctx, "service-A")
 	productPackages := android.PathForTesting("product_packages.txt")
@@ -228,7 +231,7 @@
 func TestDexPreoptSystemExtSystemServerJars(t *testing.T) {
 	config := android.TestConfig("out", nil, "", nil)
 	ctx := android.BuilderContextForTesting(config)
-	globalSoong := globalSoongConfigForTests()
+	globalSoong := globalSoongConfigForTests(ctx)
 	global := GlobalConfigForTests(ctx)
 	module := testSystemExtSystemServerModuleConfig(ctx, "service-A")
 	productPackages := android.PathForTesting("product_packages.txt")
@@ -252,7 +255,7 @@
 func TestDexPreoptApexStandaloneSystemServerJars(t *testing.T) {
 	config := android.TestConfig("out", nil, "", nil)
 	ctx := android.BuilderContextForTesting(config)
-	globalSoong := globalSoongConfigForTests()
+	globalSoong := globalSoongConfigForTests(ctx)
 	global := GlobalConfigForTests(ctx)
 	module := testApexModuleConfig(ctx, "service-A", "com.android.apex1")
 	productPackages := android.PathForTesting("product_packages.txt")
@@ -276,7 +279,7 @@
 func TestDexPreoptProfile(t *testing.T) {
 	config := android.TestConfig("out", nil, "", nil)
 	ctx := android.BuilderContextForTesting(config)
-	globalSoong := globalSoongConfigForTests()
+	globalSoong := globalSoongConfigForTests(ctx)
 	global := GlobalConfigForTests(ctx)
 	module := testSystemModuleConfig(ctx, "test")
 	productPackages := android.PathForTesting("product_packages.txt")
@@ -316,3 +319,55 @@
 	after := fmt.Sprintf("%v", parsed)
 	android.AssertStringEquals(t, "The result must be the same as the original after marshalling and unmarshalling it.", before, after)
 }
+
+func TestUffdGcFlagForce(t *testing.T) {
+	for _, enableUffdGc := range []string{"true", "false"} {
+		t.Run(enableUffdGc, func(t *testing.T) {
+			preparers := android.GroupFixturePreparers(
+				PrepareForTestWithFakeDex2oatd,
+				PrepareForTestWithDexpreoptConfig,
+				FixtureSetEnableUffdGc(enableUffdGc),
+			)
+
+			result := preparers.RunTest(t)
+			ctx := result.TestContext
+
+			ctx.SingletonForTests("dexpreopt-soong-config").Output("out/soong/dexpreopt/uffd_gc_flag.txt")
+		})
+	}
+}
+
+func TestUffdGcFlagDefault(t *testing.T) {
+	preparers := android.GroupFixturePreparers(
+		PrepareForTestWithFakeDex2oatd,
+		PrepareForTestWithDexpreoptConfig,
+		FixtureSetEnableUffdGc("default"),
+	)
+
+	result := preparers.RunTest(t)
+	ctx := result.TestContext
+	config := ctx.Config()
+
+	rule := ctx.SingletonForTests("dexpreopt-soong-config").Rule("dexpreopt_uffd_gc_flag")
+
+	android.AssertStringDoesContain(t, "", rule.RuleParams.Command, "construct_uffd_gc_flag")
+	android.AssertStringPathsRelativeToTopEquals(t, "", config, []string{
+		"out/soong/dexpreopt/uffd_gc_flag.txt",
+	}, rule.AllOutputs())
+	android.AssertPathsRelativeToTopEquals(t, "", []string{
+		"out/soong/dexpreopt/kernel_version_for_uffd_gc.txt",
+	}, rule.Implicits)
+}
+
+func TestUffdGcFlagBogus(t *testing.T) {
+	preparers := android.GroupFixturePreparers(
+		PrepareForTestWithFakeDex2oatd,
+		PrepareForTestWithDexpreoptConfig,
+		FixtureSetEnableUffdGc("bogus"),
+	)
+
+	preparers.
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
+			"Unknown value of PRODUCT_ENABLE_UFFD_GC: bogus")).
+		RunTest(t)
+}
diff --git a/dexpreopt/testing.go b/dexpreopt/testing.go
index 147a562..b1fbef5 100644
--- a/dexpreopt/testing.go
+++ b/dexpreopt/testing.go
@@ -88,6 +88,15 @@
 	FixtureModifyGlobalConfig(func(android.PathContext, *GlobalConfig) {}),
 )
 
+var PrepareForTestWithDexpreoptConfig = android.GroupFixturePreparers(
+	android.PrepareForTestWithAndroidBuildComponents,
+	android.FixtureModifyContext(func(ctx *android.TestContext) {
+		ctx.RegisterParallelSingletonType("dexpreopt-soong-config", func() android.Singleton {
+			return &globalSoongConfigSingleton{}
+		})
+	}),
+)
+
 // FixtureModifyGlobalConfig enables dexpreopt (unless modified by the mutator) and modifies the
 // configuration.
 func FixtureModifyGlobalConfig(configModifier func(ctx android.PathContext, dexpreoptConfig *GlobalConfig)) android.FixturePreparer {
@@ -195,3 +204,10 @@
 		dexpreoptConfig.DisablePreopt = disable
 	})
 }
+
+// FixtureSetEnableUffdGc sets the EnableUffdGc property in the global config.
+func FixtureSetEnableUffdGc(value string) android.FixturePreparer {
+	return FixtureModifyGlobalConfig(func(_ android.PathContext, dexpreoptConfig *GlobalConfig) {
+		dexpreoptConfig.EnableUffdGc = value
+	})
+}
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index ec8b4c8..01f60d4 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -617,7 +617,8 @@
 
 // GenerateSingletonBuildActions generates build rules for the dexpreopt config for Make.
 func (d *dexpreoptBootJars) GenerateSingletonBuildActions(ctx android.SingletonContext) {
-	d.dexpreoptConfigForMake = android.PathForOutput(ctx, getDexpreoptDirName(ctx), "dexpreopt.config")
+	d.dexpreoptConfigForMake =
+		android.PathForOutput(ctx, dexpreopt.GetDexpreoptDirName(ctx), "dexpreopt.config")
 	writeGlobalConfigForMake(ctx, d.dexpreoptConfigForMake)
 }
 
@@ -1066,8 +1067,8 @@
 		cmd.FlagWithArg("--instruction-set-features=", global.InstructionSetFeatures[arch])
 	}
 
-	if global.EnableUffdGc && image.target.Os == android.Android {
-		cmd.Flag("--runtime-arg").Flag("-Xgc:CMC")
+	if image.target.Os == android.Android {
+		cmd.Text("$(cat").Input(globalSoong.UffdGcFlag).Text(")")
 	}
 
 	if global.BootFlags != "" {
@@ -1235,7 +1236,7 @@
 func dumpOatRules(ctx android.ModuleContext, image *bootImageConfig) {
 	var allPhonies android.Paths
 	name := image.name
-	global := dexpreopt.GetGlobalConfig(ctx)
+	globalSoong := dexpreopt.GetGlobalSoongConfig(ctx)
 	for _, image := range image.variants {
 		arch := image.target.Arch.ArchType
 		suffix := arch.String()
@@ -1247,6 +1248,7 @@
 		output := android.PathForOutput(ctx, name+"."+suffix+".oatdump.txt")
 		rule := android.NewRuleBuilder(pctx, ctx)
 		imageLocationsOnHost, _ := image.imageLocations()
+
 		cmd := rule.Command().
 			BuiltTool("oatdump").
 			FlagWithInputList("--runtime-arg -Xbootclasspath:", image.dexPathsDeps.Paths(), ":").
@@ -1254,8 +1256,8 @@
 			FlagWithArg("--image=", strings.Join(imageLocationsOnHost, ":")).Implicits(image.imagesDeps.Paths()).
 			FlagWithOutput("--output=", output).
 			FlagWithArg("--instruction-set=", arch.String())
-		if global.EnableUffdGc && image.target.Os == android.Android {
-			cmd.Flag("--runtime-arg").Flag("-Xgc:CMC")
+		if image.target.Os == android.Android {
+			cmd.Text("$(cat").Input(globalSoong.UffdGcFlag).Text(")")
 		}
 		rule.Build("dump-oat-"+name+"-"+suffix, "dump oat "+name+" "+arch.String())
 
diff --git a/java/dexpreopt_config.go b/java/dexpreopt_config.go
index 254b2c1..dc0973c 100644
--- a/java/dexpreopt_config.go
+++ b/java/dexpreopt_config.go
@@ -115,7 +115,7 @@
 func genBootImageConfigs(ctx android.PathContext) map[string]*bootImageConfig {
 	return ctx.Config().Once(bootImageConfigKey, func() interface{} {
 		targets := dexpreoptTargets(ctx)
-		deviceDir := android.PathForOutput(ctx, getDexpreoptDirName(ctx))
+		deviceDir := android.PathForOutput(ctx, dexpreopt.GetDexpreoptDirName(ctx))
 
 		configs := genBootImageConfigRaw(ctx)
 
@@ -234,12 +234,3 @@
 func dexpreoptConfigMakevars(ctx android.MakeVarsContext) {
 	ctx.Strict("DEXPREOPT_BOOT_JARS_MODULES", strings.Join(defaultBootImageConfig(ctx).modules.CopyOfApexJarPairs(), ":"))
 }
-
-func getDexpreoptDirName(ctx android.PathContext) string {
-	prefix := "dexpreopt_"
-	targets := ctx.Config().Targets[android.Android]
-	if len(targets) > 0 {
-		return prefix + targets[0].Arch.ArchType.String()
-	}
-	return prefix + "unknown_target"
-}
diff --git a/java/droidstubs.go b/java/droidstubs.go
index 6ef2afe..dc3a0ec 100644
--- a/java/droidstubs.go
+++ b/java/droidstubs.go
@@ -537,11 +537,17 @@
 	var apiVersions android.Path
 	if proptools.Bool(d.properties.Api_levels_annotations_enabled) {
 		d.apiLevelsGenerationFlags(ctx, cmd, stubsType, apiVersionsXml)
-		apiVersions = d.everythingArtifacts.apiVersionsXml
+		apiVersions = apiVersionsXml
 	} else {
 		ctx.VisitDirectDepsWithTag(metalavaAPILevelsModuleTag, func(m android.Module) {
 			if s, ok := m.(*Droidstubs); ok {
-				apiVersions = s.everythingArtifacts.apiVersionsXml
+				if stubsType == Everything {
+					apiVersions = s.everythingArtifacts.apiVersionsXml
+				} else if stubsType == Exportable {
+					apiVersions = s.exportableArtifacts.apiVersionsXml
+				} else {
+					ctx.ModuleErrorf("%s stubs type does not generate api-versions.xml file", stubsType.String())
+				}
 			} else {
 				ctx.PropertyErrorf("api_levels_module",
 					"module %q is not a droidstubs module", ctx.OtherModuleName(m))
diff --git a/python/scripts/precompile_python.py b/python/scripts/precompile_python.py
index e12e7d2..80e7c76 100644
--- a/python/scripts/precompile_python.py
+++ b/python/scripts/precompile_python.py
@@ -16,6 +16,7 @@
 import argparse
 import py_compile
 import os
+import sys
 import shutil
 import tempfile
 import zipfile
@@ -23,22 +24,31 @@
 # This file needs to support both python 2 and 3.
 
 
-def process_one_file(name, inf, outzip):
-    if not name.endswith('.py'):
-        outzip.writestr(name, inf.read())
+def process_one_file(info, infile, outzip):
+    if not info.filename.endswith('.py'):
+        outzip.writestr(info, infile.read())
         return
 
     # Unfortunately py_compile requires the input/output files to be written
     # out to disk.
     with tempfile.NamedTemporaryFile(prefix="Soong_precompile_", delete=False) as tmp:
-        shutil.copyfileobj(inf, tmp)
+        shutil.copyfileobj(infile, tmp)
         in_name = tmp.name
     with tempfile.NamedTemporaryFile(prefix="Soong_precompile_", delete=False) as tmp:
         out_name = tmp.name
     try:
-        py_compile.compile(in_name, out_name, name, doraise=True)
+        # Ensure deterministic pyc by using the hash rather than timestamp.
+        # This is required to improve caching in accelerated builds.
+        # Only works on Python 3.7+ (see https://docs.python.org/3/library/py_compile.html#py_compile.PycInvalidationMode)
+        # which should cover most updated branches and developer machines.
+        if sys.version_info >= (3, 7):
+            py_compile.compile(in_name, out_name, info.filename, doraise=True, invalidation_mode=py_compile.PycInvalidationMode.CHECKED_HASH)
+        else:
+            py_compile.compile(in_name, out_name, info.filename, doraise=True)
         with open(out_name, 'rb') as f:
-            outzip.writestr(name + 'c', f.read())
+            info.filename = info.filename + 'c'
+            # Use ZipInfo rather than str to reuse timestamps for deterministic zip files.
+            outzip.writestr(info, f.read())
     finally:
         os.remove(in_name)
         os.remove(out_name)
@@ -52,9 +62,9 @@
 
     with open(args.dst_zip, 'wb') as outf, open(args.src_zip, 'rb') as inf:
         with zipfile.ZipFile(outf, mode='w') as outzip, zipfile.ZipFile(inf, mode='r') as inzip:
-            for name in inzip.namelist():
-                with inzip.open(name, mode='r') as inzipf:
-                    process_one_file(name, inzipf, outzip)
+            for info in inzip.infolist():
+                with inzip.open(info.filename, mode='r') as inzipf:
+                    process_one_file(info, inzipf, outzip)
 
 
 if __name__ == "__main__":
diff --git a/rust/androidmk.go b/rust/androidmk.go
index 17fd2d8..e0cb3ce 100644
--- a/rust/androidmk.go
+++ b/rust/androidmk.go
@@ -63,8 +63,10 @@
 				entries.AddStrings("LOCAL_SHARED_LIBRARIES", mod.transitiveAndroidMkSharedLibs.ToList()...)
 				entries.AddStrings("LOCAL_STATIC_LIBRARIES", mod.Properties.AndroidMkStaticLibs...)
 				entries.AddStrings("LOCAL_SOONG_LINK_TYPE", mod.makeLinkType)
-				if mod.UseVndk() {
-					entries.SetBool("LOCAL_USE_VNDK", true)
+				if mod.InVendor() {
+					entries.SetBool("LOCAL_IN_VENDOR", true)
+				} else if mod.InProduct() {
+					entries.SetBool("LOCAL_IN_PRODUCT", true)
 				}
 				android.SetAconfigFileMkEntries(mod.AndroidModuleBase(), entries, mod.mergedAconfigFiles)
 			},
diff --git a/rust/vendor_snapshot_test.go b/rust/vendor_snapshot_test.go
index 4f45799..7ebe66b 100644
--- a/rust/vendor_snapshot_test.go
+++ b/rust/vendor_snapshot_test.go
@@ -553,6 +553,7 @@
 		vendor: true,
 		nocrt: true,
 		no_libcrt: true,
+		no_crt_pad_segment: true,
 		stl: "none",
 		system_shared_libs: [],
 	}
@@ -857,6 +858,7 @@
 		target_arch: "arm64",
 		compile_multilib: "32",
 		vendor: true,
+		no_crt_pad_segment: true,
 		arch: {
 			arm: {
 				src: "lib32.so",
@@ -870,6 +872,7 @@
 		target_arch: "arm64",
 		compile_multilib: "64",
 		vendor: true,
+		no_crt_pad_segment: true,
 		arch: {
 			arm64: {
 				src: "lib64.so",
@@ -882,6 +885,7 @@
 		target_arch: "arm64",
 		compile_multilib: "64",
 		vendor: true,
+		no_crt_pad_segment: true,
 		arch: {
 			arm64: {
 				src: "liblog.so",
@@ -913,6 +917,7 @@
 		target_arch: "arm64",
 		compile_multilib: "both",
 		vendor: true,
+		no_crt_pad_segment: true,
 		arch: {
 			arm64: {
 				src: "libvendor_available.so",
diff --git a/scripts/Android.bp b/scripts/Android.bp
index 7baaadb..e2fd59f 100644
--- a/scripts/Android.bp
+++ b/scripts/Android.bp
@@ -143,6 +143,39 @@
 }
 
 python_library_host {
+    name: "uffd_gc_utils",
+    srcs: [
+        "uffd_gc_utils.py",
+    ],
+    visibility: [
+        "//build/make/tools:__subpackages__",
+    ],
+}
+
+python_test_host {
+    name: "uffd_gc_utils_test",
+    main: "uffd_gc_utils_test.py",
+    srcs: [
+        "uffd_gc_utils_test.py",
+    ],
+    libs: [
+        "uffd_gc_utils",
+    ],
+    test_suites: ["general-tests"],
+}
+
+python_binary_host {
+    name: "construct_uffd_gc_flag",
+    main: "construct_uffd_gc_flag.py",
+    srcs: [
+        "construct_uffd_gc_flag.py",
+    ],
+    libs: [
+        "uffd_gc_utils",
+    ],
+}
+
+python_library_host {
     name: "ninja_rsp",
     srcs: ["ninja_rsp.py"],
 }
diff --git a/scripts/construct_uffd_gc_flag.py b/scripts/construct_uffd_gc_flag.py
new file mode 100644
index 0000000..f437961
--- /dev/null
+++ b/scripts/construct_uffd_gc_flag.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2024 The Android Open Source Project
+#
+# 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.
+#
+"""A tool for constructing UFFD GC flag."""
+
+import argparse
+import os
+
+from uffd_gc_utils import should_enable_uffd_gc
+
+
+def parse_args():
+  parser = argparse.ArgumentParser()
+  parser.add_argument('kernel_version_file')
+  parser.add_argument('output')
+  return parser.parse_args()
+
+def main():
+  args = parse_args()
+  enable_uffd_gc = should_enable_uffd_gc(args.kernel_version_file)
+  flag = '--runtime-arg -Xgc:CMC' if enable_uffd_gc else ''
+  # Prevent the file's mtime from being changed if the contents don't change.
+  # This avoids unnecessary dexpreopt reruns.
+  if os.path.isfile(args.output):
+    with open(args.output, 'r') as f:
+      if f.read() == flag:
+        return
+  with open(args.output, 'w') as f:
+    f.write(flag)
+
+
+if __name__ == '__main__':
+  main()
diff --git a/scripts/uffd_gc_utils.py b/scripts/uffd_gc_utils.py
new file mode 100644
index 0000000..2d35494
--- /dev/null
+++ b/scripts/uffd_gc_utils.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2024 The Android Open Source Project
+#
+# 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.
+#
+"""Utils to determine whether to enable UFFD GC."""
+
+import re
+import sys
+
+
+def should_enable_uffd_gc(kernel_version_file):
+  with open(kernel_version_file, 'r') as f:
+    kernel_version = f.read().strip()
+  return should_enable_uffd_gc_impl(kernel_version)
+
+def should_enable_uffd_gc_impl(kernel_version):
+  # See https://source.android.com/docs/core/architecture/kernel/gki-versioning#determine-release
+  p = r"^(?P<w>\d+)[.](?P<x>\d+)[.](?P<y>\d+)(-android(?P<z>\d+)-(?P<k>\d+).*$)?"
+  m = re.match(p, kernel_version)
+  if m is not None:
+    if m.group('z') is not None:
+      android_release = int(m.group('z'))
+      # No need to check w, x, y because all Android 12 kernels have backports.
+      return android_release >= 12
+    else:
+      # Old kernel or non-GKI kernel.
+      version = int(m.group('w'))
+      patch_level = int(m.group('x'))
+      if version < 5:
+        # Old kernel.
+        return False
+      elif (version == 5 and patch_level >= 7) or version >= 6:
+        # New non-GKI kernel. 5.7 supports MREMAP_DONTUNMAP without the need for
+        # backports.
+        return True
+      else:
+        # Non-GKI kernel between 5 and 5.6. It may have backports.
+        raise exit_with_error(kernel_version)
+  elif kernel_version == '<unknown-kernel>':
+    # The kernel information isn't available to the build system, probably
+    # because PRODUCT_OTA_ENFORCE_VINTF_KERNEL_REQUIREMENTS is set to false. We
+    # assume that the kernel supports UFFD GC because it is the case for most of
+    # the products today and it is the future.
+    return True
+  else:
+    # Unrecognizable non-GKI kernel.
+    raise exit_with_error(kernel_version)
+
+def exit_with_error(kernel_version):
+  sys.exit(f"""
+Unable to determine UFFD GC flag for kernel version "{kernel_version}".
+You can fix this by explicitly setting PRODUCT_ENABLE_UFFD_GC to "true" or
+"false" based on the kernel version.
+1. Set PRODUCT_ENABLE_UFFD_GC to "true" if the kernel supports userfaultfd(2)
+   and MREMAP_DONTUNMAP.
+2. Set PRODUCT_ENABLE_UFFD_GC to "false" otherwise.""")
diff --git a/scripts/uffd_gc_utils_test.py b/scripts/uffd_gc_utils_test.py
new file mode 100644
index 0000000..c86ab4b
--- /dev/null
+++ b/scripts/uffd_gc_utils_test.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2024 The Android Open Source Project
+#
+# 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.
+#
+"""Unit tests for uffd_gc_utils.py."""
+
+import unittest
+
+from uffd_gc_utils import should_enable_uffd_gc_impl
+
+
+class UffdGcUtilsTest(unittest.TestCase):
+  def test_should_enable_uffd_gc_impl(self):
+    # GKI kernels in new format.
+    self.assertTrue(should_enable_uffd_gc_impl(
+        "6.1.25-android14-11-g34fde9ec08a3-ab10675345"))
+    self.assertTrue(should_enable_uffd_gc_impl(
+        "5.4.42-android12-0-something"))
+    self.assertFalse(should_enable_uffd_gc_impl(
+        "5.4.42-android11-0-something"))
+    # GKI kernels in old format.
+    self.assertFalse(should_enable_uffd_gc_impl(
+        "4.19.282-g4b749a433956-ab10893502"))
+    # Non GKI kernels.
+    self.assertTrue(should_enable_uffd_gc_impl(
+        "6.1.25-foo"))
+    self.assertTrue(should_enable_uffd_gc_impl(
+        "6.1.25"))
+    self.assertTrue(should_enable_uffd_gc_impl(
+        "5.10.19-foo"))
+    self.assertTrue(should_enable_uffd_gc_impl(
+        "5.10.19"))
+    with self.assertRaises(SystemExit):
+        should_enable_uffd_gc_impl("5.4.42-foo")
+    with self.assertRaises(SystemExit):
+        should_enable_uffd_gc_impl("5.4.42")
+    self.assertFalse(should_enable_uffd_gc_impl(
+        "4.19.282-foo"))
+    self.assertFalse(should_enable_uffd_gc_impl(
+        "4.19.282"))
+    with self.assertRaises(SystemExit):
+        should_enable_uffd_gc_impl("foo")
+    # No kernel.
+    self.assertTrue(should_enable_uffd_gc_impl(
+        "<unknown-kernel>"))
+
+
+if __name__ == '__main__':
+  unittest.main(verbosity=2)