Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
dc5765a
NativeAOT runtime skeleton
grendello Sep 1, 2025
08662fc
Slowly figuring out NativeAOT build complexities
grendello Sep 1, 2025
d0800ce
Link libc++ statically, include Android naot runtime in linking
grendello Sep 2, 2025
418015d
Don't link static libc++, it causes duplicate symbol errors with libs…
grendello Sep 2, 2025
42f7e10
Add logging to the naot android runtime
grendello Sep 2, 2025
1e0fc7f
Typo
grendello Sep 2, 2025
66fcd10
Shared code (gc bridge, os bridge), p/invokes
grendello Sep 3, 2025
178745b
Host code sharing tweaks
grendello Sep 3, 2025
9c5245d
Hook up NAOT-specific GC bridge code
grendello Sep 4, 2025
002d827
Enable ManagedTypeManager
grendello Sep 4, 2025
be644f8
Bring in the rest of relevant changes from #10402
grendello Sep 4, 2025
5fe1fb7
Temporarily add this, to be removed before merging
grendello Sep 4, 2025
7089008
Support logger configuration via the standard XA properties
grendello Sep 5, 2025
f9e0d9b
Beginnings of support for calling custom JNI_OnLoad functions
grendello Sep 5, 2025
a3eabc9
Assembly source generation
grendello Sep 8, 2025
01bc983
JNI on-load initialization of libraries
grendello Sep 9, 2025
e58d84b
Support for environment variables, TBC
grendello Sep 9, 2025
1c90315
[WIP] environment variables + system properties support
grendello Sep 10, 2025
c76ef87
Environment variables support complete
grendello Sep 10, 2025
0cc262a
Address feedback
grendello Sep 11, 2025
afa83ee
Remove log spam
grendello Sep 11, 2025
12c07d1
Initialize max gref count
grendello Sep 11, 2025
c2bda46
[WIP] System properties
grendello Sep 11, 2025
593a075
Partial support for system properties
grendello Sep 12, 2025
1d6b5dc
Fix after rebase
grendello Sep 15, 2025
5365266
Post-rebase fixes
grendello Sep 15, 2025
bc4134d
System properties work now
grendello Sep 15, 2025
6300544
Share env & sys prop code with CoreCLR
grendello Sep 16, 2025
e95a7a2
Statically link libc++, shrink apk by around 8mb
grendello Sep 17, 2025
350e543
not needed
grendello Sep 17, 2025
25083df
Fixlet
grendello Sep 17, 2025
f3e1b3b
Shared libc++ is no longer packaged (or needed)
grendello Sep 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion samples/NativeAOT/NativeAOT.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@
<!-- Only property required to opt into NativeAOT -->
<PublishAot>true</PublishAot>
</PropertyGroup>
</Project>

<ItemGroup>
<AndroidEnvironment Include="environment.txt" />
</ItemGroup>
</Project>
1 change: 1 addition & 0 deletions samples/NativeAOT/environment.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
naot.system.property=testing 1 2 3
2 changes: 1 addition & 1 deletion samples/NativeAOT/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ fi

adb uninstall "${PACKAGE}" || true
adb install -r -d --no-streaming --no-fastdeploy "${APK}"
#adb shell setprop debug.mono.log default,assembly,timing=bare
adb shell setprop debug.mono.log default,assembly,timing=bare
adb logcat -G 128M
adb logcat -c
adb shell am start -S --user "0" -a "android.intent.action.MAIN" -c "android.intent.category.LAUNCHER" -n "${PACKAGE}/${ACTIVITY}" -W
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android.
<CppCompilerAndLinker>clang++</CppCompilerAndLinker>
<ObjCopyName>llvm-objcopy</ObjCopyName>

<!-- We must ensure this is `false`, as it would interfere with statically linking libc++ -->
<LinkStandardCPlusPlusLibrary>false</LinkStandardCPlusPlusLibrary>

<!-- Example settings from: https://github.com/xamarin/xamarin-macios/blob/c43d4ea40bc777969e3b158cf46446df292d8449/dotnet/targets/Xamarin.Shared.Sdk.targets#L541-L550 -->
<RunILLink>true</RunILLink>
<!--
Expand Down Expand Up @@ -111,13 +114,19 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android.
<TrimmerRootAssembly Include="@(_AndroidILLinkAssemblies->'%(Filename)')" Exclude="System.Private.CoreLib" TrimMode="All" />
<!-- Passes linked assemblies to outer MSBuild tasks/targets -->
<ResolvedFileToPublish Include="@(IlcCompileInput);@(_AndroidILLinkAssemblies)" RuntimeIdentifier="$(RuntimeIdentifier)" />
<!-- Include libc++ -->
<ResolvedFileToPublish Include="$(_NdkSysrootDir)libc++_shared.so" RuntimeIdentifier="$(RuntimeIdentifier)" />

<!-- Satellite assemblies -->
<IlcSatelliteAssembly Include="$(_OuterIntermediateSatelliteAssembliesWithTargetPath)" />

<LinkerArg Include="@(RuntimePackAsset->WithMetadataValue('Filename', 'libnaot-android.$(Configuration.ToLower())-static-$(Configuration.ToLower())'))" />
<LinkerArg Include="$(_NdkSysrootDir)libc++_shared.so" />

<!-- Include libc++ -->
<LinkerArg Include="$(_NdkSysrootDir)libc++_static.a" />
<LinkerArg Include="$(_NdkSysrootDir)libc++abi.a" />

<!-- This library conflicts with static libc++ -->
<NativeLibrary Remove="$(IlcSdkPath)libstdc++compat.a" />
<LinkerArg Remove="$(IlcSdkPath)libstdc++compat.a" />

<!-- Every p/invoke using the `xa-internal-api` "library" will be called directly -->
<DirectPInvoke Include="xa-internal-api" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,11 +159,11 @@ public void BasicApplicationPublishReadyToRun (bool isComposite, string rid)
var helper = new ArchiveAssemblyHelper (apk, true);
var abi = MonoAndroidHelper.RidToAbi (rid);
Assert.IsTrue (helper.Exists ($"assemblies/{abi}/{assemblyName}.dll"), $"{assemblyName}.dll should exist in apk!");

using var stream = helper.ReadEntry ($"assemblies/{assemblyName}.dll");
stream.Position = 0;
using var peReader = new System.Reflection.PortableExecutable.PEReader (stream);
Assert.IsTrue (peReader.PEHeaders.CorHeader.ManagedNativeHeaderDirectory.Size > 0,
Assert.IsTrue (peReader.PEHeaders.CorHeader.ManagedNativeHeaderDirectory.Size > 0,
$"ReadyToRun image not found in {assemblyName}.dll! ManagedNativeHeaderDirectory should not be empty!");
}

Expand Down Expand Up @@ -193,9 +193,7 @@ public void NativeAOT ()
];
string [] nativeaot_files = [
$"lib/arm64-v8a/lib{proj.ProjectName}.so",
"lib/arm64-v8a/libc++_shared.so",
$"lib/x86_64/lib{proj.ProjectName}.so",
"lib/x86_64/libc++_shared.so",
];

var intermediate = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -332,10 +332,21 @@ protected override void Construct (LlvmIrModule module)
module.Add (envVars);
module.AddGlobalVariable ("app_environment_variable_contents", envVarsBlob, LlvmIrVariableOptions.GlobalConstant);

var sysProps = new LlvmIrGlobalVariable (systemProperties ?? new SortedDictionary<string, string>(), "app_system_properties") {
// We reuse the same structure as for environment variables, there's no point in adding a new, identical, one
var sysPropsBlob = new LlvmIrStringBlob ();
List<StructureInstance<LlvmIrHelpers.AppEnvironmentVariable>> appSysProps = LlvmIrHelpers.MakeEnvironmentVariableList (
Log,
systemProperties,
sysPropsBlob,
appEnvironmentVariableStructureInfo
);

var sysProps = new LlvmIrGlobalVariable (appSysProps, "app_system_properties") {
Comment = " System properties defined by the application",
Options = LlvmIrVariableOptions.GlobalConstant,
};
module.Add (sysProps, stringGroupName: "sysprop", stringGroupComment: " System properties name:value pairs");
module.Add (sysProps);
module.AddGlobalVariable ("app_system_property_contents", sysPropsBlob, LlvmIrVariableOptions.GlobalConstant);

DsoCacheState dsoState = InitDSOCache ();
var app_cfg = new ApplicationConfigCLR {
Expand All @@ -347,7 +358,7 @@ protected override void Construct (LlvmIrModule module)
number_of_runtime_properties = (uint)(runtimeProperties == null ? 0 : runtimeProperties.Count),
package_naming_policy = (uint)PackageNamingPolicy,
environment_variable_count = (uint)(environmentVariables == null ? 0 : environmentVariables.Count),
system_property_count = (uint)(systemProperties == null ? 0 : systemProperties.Count * 2),
system_property_count = (uint)(appSysProps.Count),
number_of_assemblies_in_apk = (uint)NumberOfAssembliesInApk,
number_of_shared_libraries = (uint)NativeLibraries.Count,
bundled_assembly_name_width = (uint)BundledAssemblyNameWidth,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ protected override void Construct (LlvmIrModule module)
SortedDictionary<string, string>? systemProperties = null;
if (envBuilder.SystemProperties.Count > 0) {
systemProperties = new (envBuilder.SystemProperties, StringComparer.Ordinal);
} else {
systemProperties = new (StringComparer.Ordinal);
}

var envVarsBlob = new LlvmIrStringBlob ();
Expand All @@ -47,6 +49,25 @@ protected override void Construct (LlvmIrModule module)
};
module.Add (envVars);
module.AddGlobalVariable ("__naot_android_app_environment_variable_contents", envVarsBlob, LlvmIrVariableOptions.GlobalConstant);

// We reuse the same structure as for environment variables, there's no point in adding a new, identical, one
var sysPropsBlob = new LlvmIrStringBlob ();
List<StructureInstance<LlvmIrHelpers.AppEnvironmentVariable>> appSysProps = LlvmIrHelpers.MakeEnvironmentVariableList (
Log,
systemProperties,
sysPropsBlob,
appEnvironmentVariableStructureInfo
);

var sysPropsCount = new LlvmIrGlobalVariable ((uint)appSysProps.Count, "__naot_android_app_system_property_count");
module.Add (sysPropsCount);

var sysProps = new LlvmIrGlobalVariable (appSysProps, "__naot_android_app_system_properties") {
Comment = " System properties defined by the application",
Options = LlvmIrVariableOptions.GlobalConstant,
};
module.Add (sysProps);
module.AddGlobalVariable ("__naot_android_app_system_property_contents", sysPropsBlob, LlvmIrVariableOptions.GlobalConstant);
}

void MapStructures (LlvmIrModule module)
Expand Down
4 changes: 4 additions & 0 deletions src/native/clr/include/host/host-environment-clr.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#pragma once

#include <xamarin-app.hh>
#include <host/host-environment.hh>
76 changes: 76 additions & 0 deletions src/native/clr/include/host/host-environment.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#pragma once

#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <string_view>

#include <runtime-base/logger.hh>

struct AppEnvironmentVariable;

namespace xamarin::android {
class HostEnvironment
{
public:
static void init () noexcept;

[[gnu::flatten, gnu::always_inline]]
static void set_variable (const char *name, const char *value) noexcept
{
log_debug (LOG_DEFAULT, " Variable {} = '{}'", optional_string (name), optional_string (value));
if (::setenv (name, value, 1) < 0) {
log_warn (LOG_DEFAULT, "Failed to set environment variable '{}': {}", name, ::strerror (errno));
}
}

[[gnu::flatten, gnu::always_inline]]
static void set_variable (std::string_view const& name, std::string_view const& value) noexcept
{
set_variable (name.data (), value.data ());
}

[[gnu::flatten, gnu::always_inline]]
static void set_system_property (const char *name, const char *value) noexcept
{
// TODO: should we **actually** try to set the system property here? Would that even work? Needs testing
log_debug (LOG_DEFAULT, " System property {} = '{}'", optional_string (name), optional_string (value));
}

[[gnu::flatten, gnu::always_inline]]
static auto lookup_system_property (std::string_view const& name, size_t &value_len,
uint32_t const count, AppEnvironmentVariable const (&entries)[],
const char (&contents)[]) noexcept -> const char*
{
if (count == 0) {
return nullptr;
}

for (size_t i = 0; i < count; i++) {
AppEnvironmentVariable const& sys_prop = entries[i];
const char *prop_name = &contents[sys_prop.name_index];
if (name.compare (prop_name) != 0) {
continue;
}

const char *prop_value = &contents[sys_prop.value_index];
value_len = strlen (prop_value);
return prop_value;
}

return nullptr;
}

template<void (*setter)(const char *name, const char *value) noexcept> [[gnu::flatten, gnu::always_inline]]
static void set_values (uint32_t const& count, AppEnvironmentVariable const (&entries)[], const char (&contents)[]) noexcept
{
for (size_t i = 0; i < count; i++) {
AppEnvironmentVariable const& env_var = entries[i];
const char *var_name = &contents[env_var.name_index];
const char *var_value = &contents[env_var.value_index];

setter (var_name, var_value);
}
}
};
}
3 changes: 2 additions & 1 deletion src/native/clr/include/xamarin-app.hh
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,8 @@ extern "C" {
[[gnu::visibility("default")]] extern const ApplicationConfig application_config;
[[gnu::visibility("default")]] extern const AppEnvironmentVariable app_environment_variables[];
[[gnu::visibility("default")]] extern const char app_environment_variable_contents[];
[[gnu::visibility("default")]] extern const char* const app_system_properties[];
[[gnu::visibility("default")]] extern const AppEnvironmentVariable app_system_properties[];
[[gnu::visibility("default")]] extern const char app_system_property_contents[];

[[gnu::visibility("default")]] extern const char* const mono_aot_mode_name;

Expand Down
75 changes: 32 additions & 43 deletions src/native/clr/runtime-base/android-system.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include <constants.hh>
#include <xamarin-app.hh>
#include <host/host-environment-clr.hh>
#include <runtime-base/android-system.hh>
#include <runtime-base/cpu-arch.hh>
#include <runtime-base/dso-loader.hh>
Expand Down Expand Up @@ -224,32 +225,34 @@ AndroidSystem::setup_app_library_directories (jstring_array_wrapper& runtimeApks
void
AndroidSystem::setup_environment () noexcept
{
const char *var_name;
const char *var_value;
for (size_t i = 0uz; i < application_config.environment_variable_count; i++) {
AppEnvironmentVariable const& env_var = app_environment_variables [i];
var_name = &app_environment_variable_contents[env_var.name_index];
var_value = &app_environment_variable_contents[env_var.value_index];

if constexpr (Constants::is_debug_build) {
log_info (LOG_DEFAULT, "Setting environment variable '{}' to '{}'", var_name, var_value);
}
if (application_config.environment_variable_count > 0) {
log_debug (LOG_DEFAULT, "Setting environment variables ({})", application_config.environment_variable_count);
HostEnvironment::set_values<HostEnvironment::set_variable> (
application_config.environment_variable_count,
app_environment_variables,
app_environment_variable_contents
);
}

if (setenv (var_name, var_value, 1) < 0) {
log_warn (LOG_DEFAULT, "Failed to set environment variable: {}", strerror (errno));
}
if (application_config.system_property_count > 0) {
log_debug (LOG_DEFAULT, "Setting system properties ({})", application_config.system_property_count);
HostEnvironment::set_values<HostEnvironment::set_system_property> (
application_config.system_property_count,
app_system_properties,
app_system_property_contents
);
}

#if defined(DEBUG)
log_debug (LOG_DEFAULT, "Loading environment from the override directory."sv);

dynamic_local_string<Constants::SENSIBLE_PATH_MAX> env_override_file;
Util::path_combine (env_override_file, std::string_view {primary_override_dir}, Constants::OVERRIDE_ENVIRONMENT_FILE_NAME);
log_debug (LOG_DEFAULT, "{}", env_override_file.get ());
if (Util::file_exists (env_override_file)) {
log_debug (LOG_DEFAULT, "Loading {}"sv, env_override_file.get ());
setup_environment_from_override_file (env_override_file);
}
log_debug (LOG_DEFAULT, "Loading environment from the override directory."sv);

dynamic_local_string<Constants::SENSIBLE_PATH_MAX> env_override_file;
Util::path_combine (env_override_file, std::string_view {primary_override_dir}, Constants::OVERRIDE_ENVIRONMENT_FILE_NAME);
log_debug (LOG_DEFAULT, "{}", env_override_file.get ());
if (Util::file_exists (env_override_file)) {
log_debug (LOG_DEFAULT, "Loading {}"sv, env_override_file.get ());
setup_environment_from_override_file (env_override_file);
}
#endif // def DEBUG
}

Expand Down Expand Up @@ -289,27 +292,13 @@ AndroidSystem::lookup_system_property (std::string_view const& name, size_t &val
return nullptr;
}

const char *prop_name;
const char *prop_value;
for (size_t i = 0uz; i < application_config.system_property_count; i += 2uz) {
prop_name = app_system_properties[i];
if (prop_name == nullptr || *prop_name == '\0') {
continue;
}

if (strcmp (prop_name, name.data ()) == 0) {
prop_value = app_system_properties [i + 1uz];
if (prop_value == nullptr || *prop_value == '\0') {
value_len = 0uz;
return "";
}

value_len = strlen (prop_value);
return prop_value;
}
}

return nullptr;
return HostEnvironment::lookup_system_property (
name,
value_len,
application_config.system_property_count,
app_system_properties,
app_system_property_contents
);
}

auto AndroidSystem::get_full_dso_path (std::string const& base_dir, std::string_view const& dso_path, dynamic_local_string<SENSIBLE_PATH_MAX>& path) noexcept -> bool
Expand Down
5 changes: 4 additions & 1 deletion src/native/clr/xamarin-app-stub/application_dso_stub.cc
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ const ApplicationConfig application_config = {
// TODO: migrate to std::string_view for these two
const AppEnvironmentVariable app_environment_variables[] = {};
const char app_environment_variable_contents[] = {};
const char* const app_system_properties[] = {};
const AppEnvironmentVariable app_system_properties[] = {};
const char app_system_property_contents[] = {};



Expand Down Expand Up @@ -138,6 +139,7 @@ DSOCacheEntry aot_dso_cache[] = {
.hash = xamarin::android::xxhash::hash (fake_dso_name, sizeof(fake_dso_name) - 1),
.real_name_hash = xamarin::android::xxhash::hash (fake_dso_name, sizeof(fake_dso_name) - 1),
.ignore = true,
.is_jni_library = true,
.name_index = 3,
.handle = nullptr,
},
Expand All @@ -146,6 +148,7 @@ DSOCacheEntry aot_dso_cache[] = {
.hash = xamarin::android::xxhash::hash (fake_dso_name2, sizeof(fake_dso_name2) - 1),
.real_name_hash = xamarin::android::xxhash::hash (fake_dso_name2, sizeof(fake_dso_name2) - 1),
.ignore = true,
.is_jni_library = false,
.name_index = 4,
.handle = nullptr,
},
Expand Down
Loading
Loading