Skip to content

[TrimmableTypeMap] Use Crc64 package naming by default with LowercaseCrc64 compatibility#11193

Open
Copilot wants to merge 14 commits into
mainfrom
copilot/update-hash-algorithm-to-xxhash64
Open

[TrimmableTypeMap] Use Crc64 package naming by default with LowercaseCrc64 compatibility#11193
Copilot wants to merge 14 commits into
mainfrom
copilot/update-hash-algorithm-to-xxhash64

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 23, 2026

This updates trimmable type map generation to default to Crc64-based package naming for unregistered types, while preserving backwards compatibility for projects that explicitly set:

<AndroidPackageNamingPolicy>LowercaseCrc64</AndroidPackageNamingPolicy>
  • Package naming policy support in trimmable typemap

    • Wired AndroidPackageNamingPolicy into trimmable typemap generation.
    • Added policy-aware hashing:
      • Crc64 (trimmable default) → crc64... using System.IO.Hashing.Crc64 with length mixing
      • LowercaseCrc64crc64... using the existing legacy/custom CRC64 implementation for compatibility with previous naming
    • Removed trimmable XxHash64 handling, as it no longer provides value for this path.
    • Kept Mono.Android namespace-preserving behavior unchanged.
  • Default policy update

    • Crc64 is now the default only for trimmable typemap generation.
    • The global AndroidPackageNamingPolicy default remains LowercaseCrc64 for non-trimmable typemap paths.
  • Hashing implementation refactor

    • Moved scanner hashing logic out of JavaPeerScanner into a dedicated static ScannerHashingHelper for easier focused testing.
    • Added dedicated ScannerHashingHelper.ToCrc64() for System.IO.Hashing.Crc64.
    • Updated UTF-8 buffer handling for the fast Crc64 path to use stackalloc with new byte[N] fallback for larger buffers (>256 bytes).
    • Added length mixing (crc64 ^ byteLength) in ToCrc64() to avoid equal outputs for different-length all-zero inputs.
    • Kept legacy CRC64 compatibility behavior intact.
  • Targeted test updates

    • Updated scanner expectations for crc64 trimmable defaults.
    • Added coverage to verify:
      • default Crc64 output,
      • legacy LowercaseCrc64 compatibility output.
    • Removed XxHash64-specific scanner expectations.
    • Kept fixed-vector tests for legacy CRC64 known input/output pairs to validate backward compatibility behavior.
    • Converted Crc64 known-output validation into a [Theory] with multiple input/output vectors.
    • Added a regression test to ensure different-length zero-byte inputs produce different Crc64 outputs.
  • Related plumbing updates

    • Passed package naming policy through the trimmable typemap target/task into TrimmableTypeMapGenerator and scanner hashing logic.
    • Merged latest main conflict changes so trimmable typemap generation keeps both package naming policy support and MaxArrayRank plumbing.
return packageNamingPolicy switch {
	HashedPackageNamingPolicy.LowercaseCrc64 => "crc64" + ScannerHashingHelper.ToLegacyCrc64 (ns, assemblyName),
	HashedPackageNamingPolicy.Crc64 => "crc64" + ScannerHashingHelper.ToCrc64 (ns, assemblyName),
	_ => throw new InvalidOperationException ($"Unsupported package naming policy: {packageNamingPolicy}"),
};

Copilot AI linked an issue Apr 23, 2026 that may be closed by this pull request
Copilot AI changed the title [WIP] Use xxhash64 instead of crc64 at build time [TrimmableTypeMap] Use XxHash64 for auto-generated Java package hashing Apr 23, 2026
Copilot AI requested a review from simonrozsival April 23, 2026 07:38
@simonrozsival
Copy link
Copy Markdown
Member

@copilot I would prefer changing the prefix from crc64 to xx64 to reflect the change.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 23, 2026

@copilot I would prefer changing the prefix from crc64 to xx64 to reflect the change.

Updated in 1049678: auto-generated TrimmableTypeMap package names now use the xx64 prefix (with XxHash64), and related unit/integration test expectations were updated accordingly.

Copy link
Copy Markdown
Member

@jonathanpeppers jonathanpeppers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that there is the property:

<AndroidPackageNamingPolicy>LowercaseCrc64</AndroidPackageNamingPolicy>

We will probably want to introduce a new one for XxHash64.

It is a slight breaking change, because people could have a hardcoded package name in a script for launching their main activity, etc.

So, I think what we did in some really old Xamarin.Android release, we changed the default but allowed projects to still use the old one.

@simonrozsival
Copy link
Copy Markdown
Member

It is a slight breaking change, because people could have a hardcoded package name in a script for launching their main activity, etc.

So funny thing is that the CSC64 we have in our C++(or C#?) code and CSC64 that is in System.IO.Hashing produce different results. I decided to use the System.IO.Hashing one in the trimmable typemap so that would be a breaking change anyway. I will need to revisit this and try to make the LowercaseCrc64 option be consistent with previous naming. The scenario with launching a specific Activity makes sense.

@simonrozsival
Copy link
Copy Markdown
Member

@copilot let's respect <AndroidPackageNamingPolicy>LowercaseCrc64</AndroidPackageNamingPolicy> with a new (default) value of XxHash64. For backwards compatibility, let's use the custom Crc64 implementation that is in existing C# or C++ code used by the "llvm-ir" typemap

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 23, 2026

@copilot let's respect <AndroidPackageNamingPolicy>LowercaseCrc64</AndroidPackageNamingPolicy> with a new (default) value of XxHash64. For backwards compatibility, let's use the custom Crc64 imple...

Implemented in ec92067: trimmable typemap now respects AndroidPackageNamingPolicy, defaults to XxHash64, and uses the legacy custom CRC64 path when policy is LowercaseCrc64 for backwards compatibility.

Note that there is the property:

<AndroidPackageNamingPolicy>LowercaseCrc64</AndroidPackageNamingPolicy>

...

Addressed in ec92067: added trimmable support for AndroidPackageNamingPolicy with a new default of XxHash64, while keeping LowercaseCrc64 compatibility via the existing legacy CRC64 implementation.

Copilot AI changed the title [TrimmableTypeMap] Use XxHash64 for auto-generated Java package hashing [TrimmableTypeMap] Use XxHash64 package naming by default with LowercaseCrc64 compatibility Apr 23, 2026
Comment thread src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs Outdated
Copy link
Copy Markdown
Member

@simonrozsival simonrozsival left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot address these comments

I want to move the hashing code from JavaPeerScanner to a separate static ScannerHashingHelper class which can be better tested. I want a test that will just simply validate some known inputs to some known outputs of the legacy crc64 algorithm to prove we're maintaining backwards compatibility.

Comment thread src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.props.in
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 24, 2026

@copilot address these comments

I want to move the hashing code from JavaPeerScanner to a separate static ScannerHashingHelper class which can be better tested. I want a test that will just simply va...

Implemented in 05cebc7: moved scanner hashing into ScannerHashingHelper and added fixed-vector tests that validate known legacy CRC64 outputs to lock backward compatibility behavior.

@copilot let's respect <AndroidPackageNamingPolicy>LowercaseCrc64</AndroidPackageNamingPolicy> with a new (default) value of XxHash64. For backwards compatibility, let's use the custom Crc64 imple...

Handled in 05cebc7: trimmable typemap continues to respect explicit <AndroidPackageNamingPolicy>LowercaseCrc64</AndroidPackageNamingPolicy>, while using XxHash64 as the trimmable default and keeping the legacy custom CRC64 path for compatibility.

@copilot I would prefer changing the prefix from crc64 to xx64 to reflect the change.

xx64 is used for the new hash-based default path, while crc64 remains only for explicit LowercaseCrc64 compatibility. Confirmed in 05cebc7.

@simonrozsival
Copy link
Copy Markdown
Member

@copilot update the PR by pulling latest main and resolve conflicts

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 27, 2026

@copilot update the PR by pulling latest main and resolve conflicts

Done in 3722fd9: pulled latest main, merged it into this branch, and resolved the conflict in Microsoft.Android.Sdk.TypeMap.Trimmable.targets.

@simonrozsival
Copy link
Copy Markdown
Member

@copilot resolve the merge conflicts in this pull request, also address review comments from Jon

Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 7, 2026

@copilot resolve the merge conflicts in this pull request, also address review comments from Jon

Done in 7aa55cd: I merged latest origin/main, resolved the conflicts in the trimmable typemap generator/task/targets files, and addressed Jon’s CRC64 review items. No UI changes (screenshot N/A).

…CI fixes

# Conflicts:
#	build-tools/automation/yaml-templates/variables.yaml
#	src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs
#	src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets
#	src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs
@simonrozsival simonrozsival marked this pull request as ready for review May 9, 2026 21:01
Copilot AI review requested due to automatic review settings May 9, 2026 21:01
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates trimmable typemap generation to default to Crc64-based hashed package naming for unregistered types, while preserving legacy LowercaseCrc64 behavior when explicitly requested via AndroidPackageNamingPolicy.

Changes:

  • Plumbs AndroidPackageNamingPolicy into the trimmable typemap pipeline and introduces a trimmable-specific default (Crc64).
  • Refactors hashing into a new ScannerHashingHelper with both modern System.IO.Hashing.Crc64 and legacy CRC64 implementations.
  • Updates and expands test coverage for both the new default and legacy compatibility behavior.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/ScannerHashingHelperTests.cs Adds fixed-vector coverage for both legacy and new CRC64 hashing + length-mixing regression test.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.cs Updates expected hashed package naming for the new default and adds a compatibility-difference test.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/FixtureTestBase.cs Adds helpers to rescan fixtures using a specified package naming policy.
src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.props.in Introduces a sentinel property to detect when AndroidPackageNamingPolicy is user-specified.
src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs Passes package naming policy through to the trimmable typemap generator.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets Sets Crc64 as the trimmable default while honoring explicitly-set policy values.
src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs Threads package naming policy into assembly scanning.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/ScannerHashingHelper.cs New helper implementing modern CRC64 hashing and legacy compatibility hashing.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs Switches to policy-aware hashed package naming using ScannerHashingHelper.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Microsoft.Android.Sdk.TrimmableTypeMap.csproj Enables unsafe blocks and links legacy CRC64 sources for compatibility.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ManifestGenerator.cs Updates documentation wording to reflect “hashed package names”.

Comment thread src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs Outdated
Comment thread tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/FixtureTestBase.cs Outdated
Comment thread src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs Outdated
simonrozsival and others added 2 commits May 11, 2026 09:52
- Remove unused 'using Java.Interop.Tools.JavaCallableWrappers;' from
  JavaPeerScanner.cs (CS8019 would break warnings-as-errors).
- Wrap PEReader in 'using' inside FixtureTestBase.ScanFixtures (string)
  so TestFixtures.dll is released even when Scan() throws.
- Make JavaPeerScanner.ParsePackageNamingPolicy strict: only null/empty,
  'Crc64', and 'LowercaseCrc64' (case-insensitive) are accepted; anything
  else now throws ArgumentException instead of silently falling back to
  Crc64. Added xunit Theories covering supported and unsupported values.
- Validate AndroidPackageNamingPolicy in
  Microsoft.Android.Sdk.TypeMap.Trimmable.targets: emit a build error
  before the trimmable typemap generator runs when the user explicitly
  set a policy other than Crc64 or LowercaseCrc64 (e.g. 'Lowercase').

Co-authored-by: Copilot <[email protected]>
MSBuild conditions are case-sensitive by default but the C# parser in
JavaPeerScanner.ParsePackageNamingPolicy uses StringComparison.OrdinalIgnoreCase.
Lowercase the property before comparing so e.g. 'crc64' or 'lowercasecrc64'
do not falsely trigger the unsupported-policy build error.

Co-authored-by: Copilot <[email protected]>
Copy link
Copy Markdown
Member

@simonrozsival simonrozsival left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot address these comments

Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 11, 2026

@copilot address these comments

Addressed in 79586ba (plus follow-up 5668156): scanner hash expectations are now hardcoded, the outdated StartupHookSupport tweak was removed, and the test fixture lifetime adjustment was finalized. No UI changes (screenshot N/A).

@simonrozsival
Copy link
Copy Markdown
Member

/review

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 11, 2026

Android PR Reviewer completed successfully!

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot

⚠️ Needs Changes

Summary: This PR cleanly wires AndroidPackageNamingPolicy into the trimmable typemap path, defaulting to Crc64 (with length mixing) while preserving LowercaseCrc64 backward compatibility. The architecture is sound — hashing logic is well-extracted into ScannerHashingHelper, the MSBuild plumbing correctly detects user-set vs. default policies, and tests cover known-value stability, policy validation, and the zero-length collision edge case.

Issues by severity

Severity Count
💡 Suggestion 3

Positive callouts

  • Good design: The _AndroidPackageNamingPolicySetByUser sentinel in .props.in correctly captures user intent before the default is applied, avoiding the common MSBuild "is this the default or did the user set it?" ambiguity.
  • Length mixing: XORing the CRC64 hash with the byte length is a simple and effective way to prevent collisions for inputs of different lengths that hash to the same value — the zero-byte regression test validates this nicely.
  • Clean separation: Extracting ScannerHashingHelper as a standalone internal static class with focused tests is well-structured. The internal/InternalsVisibleTo pattern follows repo conventions.
  • Validation: The _ValidateTrimmableTypeMapPackageNamingPolicy target gives a clear, actionable error message for unsupported policies, consistent with the existing _ValidateTrimmableTypeMapRuntime pattern.

Notes

  • CI checks (dotnet-android, license/cla) are green.
  • The 3 inline suggestions are all 💡-level (convention alignment and removing hand-rolled utilities). None block merge.

Generated by Android PR Reviewer for issue #11193 · ● 8.4M

Comment thread src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/ScannerHashingHelper.cs Outdated
Comment thread src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs Outdated
Address review feedback:
- ScannerHashingHelper: replace hand-rolled WriteUInt64LittleEndian/
  ReadUInt64LittleEndian helpers with System.Buffers.Binary.BinaryPrimitives
  (available transitively via System.IO.Hashing).
- JavaPeerScanner.ParsePackageNamingPolicy: use the project-local
  NullableExtensions.IsNullOrEmpty() extension method (NRT-aware) instead
  of the static string.IsNullOrEmpty().

Co-authored-by: Copilot <[email protected]>
@simonrozsival simonrozsival added the ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). label May 11, 2026
@simonrozsival
Copy link
Copy Markdown
Member

/review

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 11, 2026

Android PR Reviewer completed successfully!

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot

Review Summary

✅ Overall Assessment

Well-structured PR that wires AndroidPackageNamingPolicy into the trimmable typemap path with proper backward compatibility. The implementation cleanly separates concerns:

  • ScannerHashingHelper isolates hashing logic for focused testing
  • HashedPackageNamingPolicy enum is correctly private to JavaPeerScanner
  • MSBuild plumbing correctly distinguishes user-set vs default policy via _AndroidPackageNamingPolicySetByUser
  • Incremental build correctness is maintained via the existing AndroidPackageNamingPolicy entry in _PropertyCacheItems
  • Validation exists at both the MSBuild target level (early, clear error) and C# level (defensive)

Verified

  • ✅ Hash output consistency: ToLegacyCrc64 and ToCrc64 test vectors match scanner expectations
  • ✅ Legacy compatibility: LowercaseCrc64 path uses the original Jones CRC64 implementation from Java.Interop
  • ✅ Length mixing prevents zero-byte collisions (with regression test)
  • ✅ Byte-level encoding: GetNamespaceAssemblyUtf8Bytes correctly handles the ns:assemblyName format
  • ✅ Incremental builds: AndroidPackageNamingPolicy is already in _PropertyCacheItems (line 991 of Xamarin.Android.Common.targets)
  • ParsePackageNamingPolicy uses IsNullOrEmpty() extension method per repo convention

CI Status

  • license/cla: ✅ Passed
  • dotnet-android: ✅ Passed
  • Xamarin.Android-PR: Not visible — may not have triggered yet

Issues Found

Severity Count
⚠️ Warning 1
💡 Suggestion 3

No blocking issues. The warning is about ArrayPool consistency in ToCrc64's heap fallback path — low practical impact since namespace:assembly pairs are almost always under 256 bytes.

Generated by Android PR Reviewer for issue #11193 · ● 6M

Comment on lines +25 to +31
[Fact]
public void ToCrc64_DifferentLengthsOfZeroBytes_HaveDifferentOutput ()
{
Assert.NotEqual (
ScannerHashingHelper.ToCrc64 ("\0", ""),
ScannerHashingHelper.ToCrc64 ("\0\0\0\0", ""));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 💡 Testing — Good regression test for the length-mixing defense. Consider also adding test vectors for edge cases that exercise the UTF-8 encoding boundary conditions in GetNamespaceAssemblyUtf8Bytes:

  • Empty namespace with non-empty assembly name (i.e., ToCrc64("", "MyAssembly")) — verifies the ":MyAssembly" encoding path
  • A non-ASCII namespace (e.g., multi-byte UTF-8) — verifies the byte count / byte write consistency

These would increase confidence that the unsafe encoding helper handles boundary inputs correctly.

Rule: Test edge cases

return bytesWritten;
}

static string ToHexString (ReadOnlySpan<byte> hash, bool lowercase)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 💡 YAGNI — The lowercase parameter is always passed as true (both call sites). Consider removing it and hardcoding the lowercase behavior to simplify this helper and GetHexValue. If an uppercase variant is needed later, it can be added then.

Rule: YAGNI (Postmortem #58)

Comment on lines +1534 to +1535
HashedPackageNamingPolicy.LowercaseCrc64 => "crc64" + ScannerHashingHelper.ToLegacyCrc64 (ns, assemblyName),
HashedPackageNamingPolicy.Crc64 => "crc64" + ScannerHashingHelper.ToCrc64 (ns, assemblyName),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we put a character to make it easy to tell the difference between these? crc64s (short for System.IO.Hashing?) isn't the best, but that is an example that would work. We probably shouldn't put characters 0-f, as the actual hash has those.

public sealed class JavaPeerScanner : IDisposable
{
enum HashedPackageNamingPolicy {
Crc64,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add the new option to java-interop, too?

I'm wondering if there are cases old code paths could get called, even when opting into the new trimmable typemap.

}
}

internal static string ToCrc64 (string ns, string assemblyName)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new fast CRC64 path (System.IO.Hashing) now emits names with an
'scrc64' prefix so they are visibly distinct from the legacy 'crc64'
prefix produced by the LowercaseCrc64 policy. This avoids ambiguity
in acw-map.txt / manifests when comparing the two algorithms and
makes scanner-vs-generator diffs easier to read.

Also drops the unused 'lowercase' parameter from
ScannerHashingHelper.ToHexString / GetHexValue (it was always passed
as true).

Co-authored-by: Copilot <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

copilot `copilot-cli` or other AIs were used to author this ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). trimmable-type-map

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[TrimmableTypeMap] Use xxhash64 instead of crc64 at build time

4 participants