diff --git a/.github/workflows/osx-nanoclr.yml b/.github/workflows/osx-nanoclr.yml new file mode 100644 index 0000000000..77eb7795aa --- /dev/null +++ b/.github/workflows/osx-nanoclr.yml @@ -0,0 +1,48 @@ +name: osx-nanoclr + +on: + push: + paths: + - "targets/osx/**" + - ".github/workflows/osx-nanoclr.yml" + pull_request: + paths: + - "targets/osx/**" + - ".github/workflows/osx-nanoclr.yml" + +jobs: + build-osx-nanoclr: + runs-on: macos-14 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Ninja (if missing) + run: | + if ! command -v ninja >/dev/null 2>&1; then + brew install ninja + fi + + - name: Configure + run: | + cmake -S targets/osx -B build/osx -G Ninja \ + -DNANO_OSX_ARCH=arm64 \ + -DNANO_OSX_ENABLE_SMOKE=ON + + - name: Build + run: cmake --build build/osx --verbose + + - name: Smoke Run + run: | + set -euo pipefail + ./build/osx/bin/nanoFramework.nanoCLR --version | tee build/osx/smoke.log + + - name: Upload Artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: osx-nanoclr + path: | + build/osx/bin/nanoFramework.nanoCLR + build/osx/smoke.log diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a9444f3333..acfb2b3f8d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -177,6 +177,7 @@ jobs: echo "##vso[task.setvariable variable=BUILD_TI;isOutput=true]false" echo "##vso[task.setvariable variable=BUILD_THREADX;isOutput=true]false" echo "##vso[task.setvariable variable=BUILD_WIN32;isOutput=true]false" + echo "##vso[task.setvariable variable=BUILD_OSX;isOutput=true]false" echo "##vso[task.setvariable variable=BUILD_NANOCLR_CLI;isOutput=true]false" echo "##vso[task.setvariable variable=BUILD_ALL;isOutput=true]false" @@ -250,6 +251,14 @@ jobs: Write-host "##[command] Building nanoCLR target" } + + if( ($files.where{$_.Contains('targets/osx')}).Count -gt 0) + { + # files at OSX folder + echo "##vso[task.setvariable variable=BUILD_OSX;isOutput=true]true" + + Write-host "##[command] Building OSX nanoCLR target" + } if( (($files.where{$_.Contains('targets/netcore/nanoFramework.nanoCLR.CLI')}).Count -gt 0) -Or @@ -345,7 +354,8 @@ jobs: eq(dependencies.Check_Build_Options.outputs['TargetsToBuild.BUILD_FREERTOS'], true), eq(dependencies.Check_Build_Options.outputs['TargetsToBuild.BUILD_TI'], true), eq(dependencies.Check_Build_Options.outputs['TargetsToBuild.BUILD_THREADX'], true), - eq(dependencies.Check_Build_Options.outputs['TargetsToBuild.BUILD_WIN32'], true) + eq(dependencies.Check_Build_Options.outputs['TargetsToBuild.BUILD_WIN32'], true), + eq(dependencies.Check_Build_Options.outputs['TargetsToBuild.BUILD_OSX'], true) ) ) @@ -1008,6 +1018,60 @@ jobs: - template: azure-pipelines-templates/publish-nanoclr.yml + ################# + # OSX host target + - job: Build_OSX_nanoCLR + condition: >- + or( + and( + succeeded('Check_Code_Style'), + ne( dependencies.Check_Build_Options.outputs['BuildOptions.SKIP_BUILD'], true ), + or( + eq(dependencies.Check_Build_Options.outputs['TargetsToBuild.BUILD_ALL'], true), + eq(dependencies.Check_Build_Options.outputs['TargetsToBuild.BUILD_OSX'], true) + ) + ), + and( + eq(variables['Build.Reason'], 'Manual'), + or( + eq(variables['BUILD_ALL__'], 'true'), + eq(variables['BUILD_OSX__'], 'true') + ) + ) + ) + + dependsOn: + - Check_Build_Options + - Check_Code_Style + + pool: + vmImage: "macos-14" + + steps: + - checkout: self + + - bash: | + set -euo pipefail + + AGENT_ARCH="$(uname -m)" + if [ "${AGENT_ARCH}" = "arm64" ]; then + NANO_OSX_ARCH="arm64" + else + NANO_OSX_ARCH="x86_64" + fi + + echo "Agent architecture: ${AGENT_ARCH}" + echo "Using NANO_OSX_ARCH=${NANO_OSX_ARCH}" + + cmake -S targets/osx -B build/osx -G Ninja -DNANO_OSX_ARCH="${NANO_OSX_ARCH}" -DNANO_OSX_ENABLE_SMOKE=ON + cmake --build build/osx + ./build/osx/bin/nanoFramework.nanoCLR --version | tee build/osx/smoke.log + if ! grep -Eq '[0-9]+\.[0-9]+\.[0-9]+' build/osx/smoke.log; then + echo "ERROR: smoke output validation failed, expected semantic version in --version output." + exit 1 + fi + displayName: Build and smoke run OSX nanoCLR + ################# # nanoCLR CLI tool - job: Build_nanoCLR_CLI @@ -1322,6 +1386,7 @@ jobs: - Build_TI_SimpleLink_targets - Build_ThreadX_targets - Build_WIN32_nanoCLR + - Build_OSX_nanoCLR - Build_nanoCLR_CLI - Check_Code_Style condition: >- @@ -1334,6 +1399,7 @@ jobs: failed('Build_TI_SimpleLink_targets'), failed('Build_ThreadX_targets'), failed('Build_WIN32_nanoCLR'), + failed('Build_OSX_nanoCLR'), failed('Build_nanoCLR_CLI') ) ) diff --git a/targets/netcore/nanoFramework.nanoCLR.CLI/Program.cs b/targets/netcore/nanoFramework.nanoCLR.CLI/Program.cs index acdfab221f..8a22219a46 100644 --- a/targets/netcore/nanoFramework.nanoCLR.CLI/Program.cs +++ b/targets/netcore/nanoFramework.nanoCLR.CLI/Program.cs @@ -44,10 +44,8 @@ static int Main(string[] args) _copyrightInfo = new CopyrightInfo(true, ".NET Foundation and nanoFramework project contributors", 2021); // need this to be able to use ProcessStart at the location where the .NET Core CLI tool is running from - string codeBase = Assembly.GetExecutingAssembly().Location; - var uri = new UriBuilder(codeBase); - var fullPath = Uri.UnescapeDataString(uri.Path); - ExecutingPath = Path.GetDirectoryName(fullPath); + string assemblyLocation = Assembly.GetExecutingAssembly().Location; + ExecutingPath = Path.GetDirectoryName(Path.GetFullPath(assemblyLocation)); // check for empty argument collection if (!args.Any()) @@ -93,9 +91,6 @@ static int Main(string[] args) VirtualSerialDeviceManager virtualSerialBridgeManager = new(); virtualSerialBridgeManager.Initialize(); - // need to set DLL directory to HHD interop DLL - SetDllDirectory(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Vendor")); - var parsedArguments = Parser.Default.ParseArguments(args); Console.ForegroundColor = ConsoleColor.White; @@ -240,8 +235,5 @@ private static void LogErrors(Action scope) } } - [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] - public static extern bool SetDllDirectory(string lpPathName); - } } diff --git a/targets/netcore/nanoFramework.nanoCLR.Host/Interop/NativeNanoClrLoader.cs b/targets/netcore/nanoFramework.nanoCLR.Host/Interop/NativeNanoClrLoader.cs new file mode 100644 index 0000000000..bb6785e21f --- /dev/null +++ b/targets/netcore/nanoFramework.nanoCLR.Host/Interop/NativeNanoClrLoader.cs @@ -0,0 +1,277 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace nanoFramework.nanoCLR.Host.Interop +{ + internal static class NativeNanoClrLoader + { + private const string LogicalLibraryName = "nanoFramework.nanoCLR"; + private const string TraceEnvVar = "NANOCLR_TRACE_LOAD"; + private static readonly object s_syncRoot = new(); + private static string s_configuredDirectory = string.Empty; + private static string s_loadedPath = string.Empty; + private static IntPtr s_loadedHandle = IntPtr.Zero; + private static bool s_resolverRegistered; + + internal static void ConfigureSearchDirectory(string dllPath) + { + lock (s_syncRoot) + { + s_configuredDirectory = NormalizeDirectory(dllPath); + EnsureResolverRegistered(); + } + } + + internal static void EnsureInitialized() + { + lock (s_syncRoot) + { + EnsureResolverRegistered(); + } + } + + internal static string GetLoadedPath() + { + lock (s_syncRoot) + { + return s_loadedPath; + } + } + + internal static bool TryUnload() + { + lock (s_syncRoot) + { + if (s_loadedHandle == IntPtr.Zero) + { + return false; + } + + try + { + NativeLibrary.Free(s_loadedHandle); + s_loadedHandle = IntPtr.Zero; + s_loadedPath = string.Empty; + return true; + } + catch (Exception ex) + { + Console.WriteLine($"Exception occurred while unloading nanoCLR library: {ex.Message}"); + return false; + } + } + } + + private static void EnsureResolverRegistered() + { + if (s_resolverRegistered) + { + return; + } + + NativeLibrary.SetDllImportResolver(typeof(nanoCLR).Assembly, ResolveLibrary); + s_resolverRegistered = true; + } + + private static IntPtr ResolveLibrary(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) + { + if (!string.Equals(libraryName, LogicalLibraryName, StringComparison.Ordinal)) + { + return IntPtr.Zero; + } + + lock (s_syncRoot) + { + if (s_loadedHandle != IntPtr.Zero) + { + TraceLoad($"already loaded from '{s_loadedPath}'"); + return s_loadedHandle; + } + + string runtimeIdentifier = GetRuntimeIdentifier(); + string nativeFileName = GetNativeLibraryFileName(); + List attemptedProbePaths = new(); + + TraceLoad( + $"OS='{RuntimeInformation.OSDescription}', ProcessArchitecture='{RuntimeInformation.ProcessArchitecture}', RuntimeIdentifier='{runtimeIdentifier}'"); + + foreach (string candidatePath in BuildProbePaths(nativeFileName, runtimeIdentifier)) + { + attemptedProbePaths.Add(candidatePath); + TraceLoad($"probe '{candidatePath}'"); + + if (NativeLibrary.TryLoad(candidatePath, out IntPtr handle)) + { + s_loadedHandle = handle; + s_loadedPath = candidatePath; + + TraceLoad($"selected '{s_loadedPath}'"); + TraceProbeSummary(attemptedProbePaths); + + return handle; + } + } + + if (NativeLibrary.TryLoad(nativeFileName, out IntPtr defaultHandle)) + { + s_loadedHandle = defaultHandle; + s_loadedPath = $"/{nativeFileName}"; + + TraceLoad("selected default OS loader path"); + TraceProbeSummary(attemptedProbePaths); + + return defaultHandle; + } + + TraceProbeSummary(attemptedProbePaths); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + throw new DllNotFoundException( + $"Unable to load '{nativeFileName}'. " + + $"The macOS native library is not available. " + + $"Build it from targets/osx and place it alongside the tool, " + + $"or set the search directory explicitly via NativeNanoClrLoader."); + } + + return IntPtr.Zero; + } + } + + private static IEnumerable BuildProbePaths(string nativeFileName, string runtimeIdentifier) + { + List probeDirectories = BuildBaseProbeDirectories(); + + foreach (string directory in probeDirectories) + { + yield return Path.Combine(directory, nativeFileName); + } + + if (!string.IsNullOrWhiteSpace(runtimeIdentifier)) + { + foreach (string directory in probeDirectories) + { + yield return Path.Combine(directory, "runtimes", runtimeIdentifier, "native", nativeFileName); + } + } + } + + private static List BuildBaseProbeDirectories() + { + List probeDirectories = new(); + string assemblyDirectory = Path.GetDirectoryName(typeof(nanoCLR).Assembly.Location) ?? string.Empty; + + AddProbeDirectory(probeDirectories, s_configuredDirectory); + AddProbeDirectory(probeDirectories, AppContext.BaseDirectory); + AddProbeDirectory(probeDirectories, Path.Combine(AppContext.BaseDirectory, "NanoCLR")); + AddProbeDirectory(probeDirectories, assemblyDirectory); + AddProbeDirectory(probeDirectories, Path.Combine(assemblyDirectory, "NanoCLR")); + + return probeDirectories; + } + + private static void AddProbeDirectory(List probeDirectories, string path) + { + string normalizedPath = NormalizeDirectory(path); + + if (string.IsNullOrWhiteSpace(normalizedPath)) + { + return; + } + + foreach (string probeDirectory in probeDirectories) + { + if (string.Equals(probeDirectory, normalizedPath, StringComparison.Ordinal)) + { + return; + } + } + + probeDirectories.Add(normalizedPath); + } + + private static string NormalizeDirectory(string path) + { + if (string.IsNullOrWhiteSpace(path)) + { + return string.Empty; + } + + string fullPath = Path.GetFullPath(path); + string rootPath = Path.GetPathRoot(fullPath) ?? string.Empty; + + if (string.Equals(fullPath, rootPath, StringComparison.Ordinal)) + { + return fullPath; + } + + return fullPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + } + + private static string GetNativeLibraryFileName() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return "nanoFramework.nanoCLR.dll"; + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return "nanoFramework.nanoCLR.dylib"; + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return "nanoFramework.nanoCLR.so"; + } + + return LogicalLibraryName; + } + + private static string GetRuntimeIdentifier() + { + try + { + return RuntimeInformation.RuntimeIdentifier; + } + catch + { + return string.Empty; + } + } + + private static void TraceProbeSummary(List attemptedProbePaths) + { + if (!IsTraceEnabled()) + { + return; + } + + Console.WriteLine("[nanoCLR-loader] attempted probe paths:"); + + foreach (string probePath in attemptedProbePaths) + { + Console.WriteLine($"[nanoCLR-loader] {probePath}"); + } + + Console.WriteLine($"[nanoCLR-loader] final path: '{s_loadedPath}'"); + } + + private static void TraceLoad(string message) + { + if (IsTraceEnabled()) + { + Console.WriteLine($"[nanoCLR-loader] {message}"); + } + } + + private static bool IsTraceEnabled() => + string.Equals(Environment.GetEnvironmentVariable(TraceEnvVar), "1", StringComparison.Ordinal); + } +} diff --git a/targets/netcore/nanoFramework.nanoCLR.Host/Interop/nanoCLR.cs b/targets/netcore/nanoFramework.nanoCLR.Host/Interop/nanoCLR.cs index ffb59908c8..6c2fc12ef7 100644 --- a/targets/netcore/nanoFramework.nanoCLR.Host/Interop/nanoCLR.cs +++ b/targets/netcore/nanoFramework.nanoCLR.Host/Interop/nanoCLR.cs @@ -2,9 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Diagnostics; using System.IO; -using System.Linq; using System.Runtime.InteropServices; namespace nanoFramework.nanoCLR.Host.Interop @@ -14,9 +12,13 @@ internal class nanoCLR internal const uint ClrOk = 0; internal const uint ClrErrorFail = 0xFF000000; private const string NativeLibraryName = "nanoFramework.nanoCLR"; - private const string _nanoClrDllName = "nanoFramework.nanoCLR.dll"; private static string _dllPath; + static nanoCLR() + { + NativeNanoClrLoader.EnsureInitialized(); + } + internal static string DllPath { get => _dllPath; @@ -31,8 +33,8 @@ internal static string DllPath _dllPath = value; - // set path to search nanoCLR DLL - _ = SetDllDirectory(_dllPath); + // set path to search nanoCLR native image + NativeNanoClrLoader.ConfigureSearchDirectory(_dllPath); } } @@ -116,38 +118,9 @@ internal static extern void nanoCLR_SetProfilerDataCallback( [DllImport(NativeLibraryName, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] internal static extern bool nanoCLR_GetNativeAssemblyInformation(byte[] buffer, int size); - [DllImport("kernel32", SetLastError = true)] - private static extern bool FreeLibrary(IntPtr hModule); - public static void UnloadNanoClrImageDll() { - string nanoClrDllLocation = Path.Combine(DllPath, _nanoClrDllName); - - var modules = Process.GetCurrentProcess().Modules.Cast() - .Where(mod => mod.FileName.Equals(nanoClrDllLocation, StringComparison.OrdinalIgnoreCase)); - - foreach (var mod in modules) - { - try - { - if (FreeLibrary(mod.BaseAddress)) - { - // Successfully unloaded the DLL - break; - } - else - { - // Handle the error if FreeLibrary fails - int errorCode = Marshal.GetLastWin32Error(); - Console.WriteLine($"Failed to unload nanoCLR DLL. Error code: {errorCode}"); - } - } - catch (Exception ex) - { - // Handle any exceptions that occur during the unload process - Console.WriteLine($"Exception occurred while unloading nanoCLR DLL: {ex.Message}"); - } - } + _ = NativeNanoClrLoader.TryUnload(); } public static string FindNanoClrDll() @@ -157,11 +130,7 @@ public static string FindNanoClrDll() // Perform dummy call to load DLL, in case it's not loaded _ = nanoCLR_GetVersion(); - // Sweep processes and look for a DLL with the nanoCLR name - var module = Process.GetCurrentProcess().Modules.Cast() - .FirstOrDefault(mod => mod.FileName.EndsWith(_nanoClrDllName, StringComparison.OrdinalIgnoreCase)); - - return module?.FileName ?? string.Empty; + return NativeNanoClrLoader.GetLoadedPath(); } catch (Exception ex) { @@ -170,8 +139,5 @@ public static string FindNanoClrDll() return string.Empty; } } - - [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] - public static extern bool SetDllDirectory(string lpPathName); } } diff --git a/targets/osx/CMakeLists.txt b/targets/osx/CMakeLists.txt new file mode 100644 index 0000000000..f9823b9656 --- /dev/null +++ b/targets/osx/CMakeLists.txt @@ -0,0 +1,25 @@ +# +# Copyright (c) .NET Foundation and Contributors +# See LICENSE file in the project root for full license information. +# + +cmake_minimum_required(VERSION 3.24) + +project(nanoFramework_nanoCLR_osx VERSION 0.1.0 LANGUAGES C CXX) + +if(NOT APPLE) + message(FATAL_ERROR "targets/osx can only be built on macOS.") +endif() + +set(NANO_OSX_ARCH "arm64" CACHE STRING "Target architecture for macOS host build (arm64 or x86_64)") +set_property(CACHE NANO_OSX_ARCH PROPERTY STRINGS arm64 x86_64) + +if(NOT NANO_OSX_ARCH STREQUAL "arm64" AND NOT NANO_OSX_ARCH STREQUAL "x86_64") + message(FATAL_ERROR "Invalid NANO_OSX_ARCH='${NANO_OSX_ARCH}'. Valid values: arm64, x86_64.") +endif() + +set(CMAKE_OSX_ARCHITECTURES "${NANO_OSX_ARCH}" CACHE STRING "macOS architecture" FORCE) + +option(NANO_OSX_ENABLE_SMOKE "Build smoke behavior (--help/--version/banner)." ON) + +add_subdirectory(nanoCLR) diff --git a/targets/osx/Include/TargetHAL_Spi.h b/targets/osx/Include/TargetHAL_Spi.h new file mode 100644 index 0000000000..20935e9c70 --- /dev/null +++ b/targets/osx/Include/TargetHAL_Spi.h @@ -0,0 +1,15 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +#ifndef TARGET_HAL_SPI_H +#define TARGET_HAL_SPI_H + +// # of buses +#define NUM_SPI_BUSES 0 + +// Maximum number of devices per SPI bus +#define MAX_SPI_DEVICES 5 + +#endif // TARGET_HAL_SPI_H diff --git a/targets/osx/Include/nanoHAL_Boot.h b/targets/osx/Include/nanoHAL_Boot.h new file mode 100644 index 0000000000..b16fdf8094 --- /dev/null +++ b/targets/osx/Include/nanoHAL_Boot.h @@ -0,0 +1,9 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +#ifndef NANOHAL_BOOT_H +#define NANOHAL_BOOT_H + +#endif // NANOHAL_BOOT_H diff --git a/targets/osx/Include/nanoHAL_Capabilites.h b/targets/osx/Include/nanoHAL_Capabilites.h new file mode 100644 index 0000000000..b8316b144f --- /dev/null +++ b/targets/osx/Include/nanoHAL_Capabilites.h @@ -0,0 +1,9 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +#ifndef NANOHAL_CAPABILITIES_H +#define NANOHAL_CAPABILITIES_H + +#endif // NANOHAL_CAPABILITIES_H diff --git a/targets/osx/Include/targetHAL.h b/targets/osx/Include/targetHAL.h new file mode 100644 index 0000000000..37df6411f2 --- /dev/null +++ b/targets/osx/Include/targetHAL.h @@ -0,0 +1,84 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +#ifndef TARGET_HAL_H +#define TARGET_HAL_H + +#include +#include +#include +#include + +// Keep the same macro shape used by other targets. +#define PLATFORM_DELAY(milliSecs) std::this_thread::sleep_for(std::chrono::milliseconds((milliSecs))) + +// Start with the same minimum socket count used by win32 host. +#define PLATFORM_DEPENDENT__SOCKETS_MAX_COUNT 1 + +#define NANOCLR_STOP() std::raise(SIGTRAP) + +inline bool Target_ConfigUpdateRequiresErase() +{ + return true; +} + +inline bool Target_HasNanoBooter() +{ + return false; +} + +inline bool Target_CanChangeMacAddress() +{ + return false; +} + +inline bool Target_IFUCapable() +{ + return false; +} + +inline bool Target_HasProprietaryBooter() +{ + return false; +} + +inline uint32_t GetPlatformCapabilities() +{ + return 0; +} + +inline uint32_t GetTargetCapabilities() +{ + return 0; +} + +inline bool RequestToLaunchProprietaryBootloader() +{ + return false; +} + +inline bool RequestToLaunchNanoBooter(int32_t errorCode) +{ + (void)errorCode; + return false; +} + +inline uint32_t CPU_TicksPerSecond() +{ + // 100ns ticks to align with nanoCLR time usage in virtual-device mode. + return 10000000U; +} + +inline uint64_t CPU_MicrosecondsToTicks(uint64_t uSec) +{ + return uSec * 10ULL; +} + +inline uint64_t CPU_MillisecondsToTicks(uint64_t mSec) +{ + return mSec * 10000ULL; +} + +#endif // TARGET_HAL_H diff --git a/targets/osx/Include/targetHAL_Power.h b/targets/osx/Include/targetHAL_Power.h new file mode 100644 index 0000000000..5d26aa724b --- /dev/null +++ b/targets/osx/Include/targetHAL_Power.h @@ -0,0 +1,21 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +#ifndef TARGET_HAL_POWER_H +#define TARGET_HAL_POWER_H + +#include + +inline void CPU_Reset() +{ + std::exit(0); +} + +inline bool CPU_IsSoftRebootSupported() +{ + return true; +} + +#endif // TARGET_HAL_POWER_H diff --git a/targets/osx/Include/targetHAL_Time.h b/targets/osx/Include/targetHAL_Time.h new file mode 100644 index 0000000000..5a971ac9ac --- /dev/null +++ b/targets/osx/Include/targetHAL_Time.h @@ -0,0 +1,15 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +#ifndef TARGET_HAL_TIME_H +#define TARGET_HAL_TIME_H + +#include + +uint64_t HAL_Time_CurrentSysTicks(); +void HAL_Time_Sleep_MicroSeconds(unsigned int uSec); +void HAL_Time_Sleep_MicroSeconds_InterruptEnabled(unsigned int uSec); + +#endif // TARGET_HAL_TIME_H diff --git a/targets/osx/Include/targetPAL_time.h b/targets/osx/Include/targetPAL_time.h new file mode 100644 index 0000000000..bcce0d6f4a --- /dev/null +++ b/targets/osx/Include/targetPAL_time.h @@ -0,0 +1,9 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +#ifndef TARGET_PAL_TIME_H +#define TARGET_PAL_TIME_H + +#endif // TARGET_PAL_TIME_H diff --git a/targets/osx/Include/target_BlockStorage.h b/targets/osx/Include/target_BlockStorage.h new file mode 100644 index 0000000000..b821c82b3f --- /dev/null +++ b/targets/osx/Include/target_BlockStorage.h @@ -0,0 +1,12 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +#ifndef TARGETPAL_BLOCKSTORAGE_H +#define TARGETPAL_BLOCKSTORAGE_H + +// Start with the same value as win32 target profile. +#define TARGET_BLOCKSTORAGE_COUNT 1 + +#endif // TARGETPAL_BLOCKSTORAGE_H diff --git a/targets/osx/Include/target_board.h b/targets/osx/Include/target_board.h new file mode 100644 index 0000000000..ff184d1319 --- /dev/null +++ b/targets/osx/Include/target_board.h @@ -0,0 +1,9 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +#ifndef TARGET_BOARD_H +#define TARGET_BOARD_H + +#endif // TARGET_BOARD_H diff --git a/targets/osx/Include/target_common.h b/targets/osx/Include/target_common.h new file mode 100644 index 0000000000..3d2d9077e2 --- /dev/null +++ b/targets/osx/Include/target_common.h @@ -0,0 +1,14 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +#ifndef TARGET_COMMON_H +#define TARGET_COMMON_H + +#define TARGETNAMESTRING "Virtual nanoDevice" +#define PLATFORMNAMESTRING "OSX" +#define TARGETINFOSTRING "CLR for OSX" +#define OEMSYSTEMINFOSTRING "nanoCLR running @ OSX" + +#endif // TARGET_COMMON_H diff --git a/targets/osx/Include/target_os.h b/targets/osx/Include/target_os.h new file mode 100644 index 0000000000..62723bc3b9 --- /dev/null +++ b/targets/osx/Include/target_os.h @@ -0,0 +1,11 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +#ifndef TARGET_OS_H +#define TARGET_OS_H + +#define TARGET_HAS_NANOBOOTER 0 + +#endif // TARGET_OS_H diff --git a/targets/osx/Include/target_platform.h b/targets/osx/Include/target_platform.h new file mode 100644 index 0000000000..2eecb314c6 --- /dev/null +++ b/targets/osx/Include/target_platform.h @@ -0,0 +1,9 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +#ifndef TARGET_PLATFORM_H +#define TARGET_PLATFORM_H + +#endif // TARGET_PLATFORM_H diff --git a/targets/osx/README.md b/targets/osx/README.md new file mode 100644 index 0000000000..980a9471d6 --- /dev/null +++ b/targets/osx/README.md @@ -0,0 +1,58 @@ +# .NET nanoFramework nanoCLR - macOS Host Target (Scaffold) + +This target provides an initial macOS host scaffold at `targets/osx`. + +## Status + +The target currently provides: + +- A CMake + Ninja build entrypoint for macOS. +- A minimal host glue structure aligned with `targets/win32`. +- A smoke executable (`--help`, `--version`, banner output). +- A GitHub Actions workflow that builds and runs smoke on macOS. + +This target is intentionally a scaffold baseline. + +## Build + +Apple Silicon: + +```bash +cmake -S targets/osx -B build/osx -G Ninja -DNANO_OSX_ARCH=arm64 -DNANO_OSX_ENABLE_SMOKE=ON +cmake --build build/osx +./build/osx/bin/nanoFramework.nanoCLR --version +``` + +Intel macOS: + +```bash +cmake -S targets/osx -B build/osx -G Ninja -DNANO_OSX_ARCH=x86_64 -DNANO_OSX_ENABLE_SMOKE=ON +cmake --build build/osx +./build/osx/bin/nanoFramework.nanoCLR --version +``` + +## Configuration Options + +`NANO_OSX_ARCH` +- Default: `arm64` +- Accepted values: `arm64`, `x86_64` + +`NANO_OSX_ENABLE_SMOKE` +- Default: `ON` +- Enables minimal command-line smoke behavior. + +## Current Limitations (Intentional) + +This target does not yet include: + +- Managed filesystem support. +- Networking support. +- Integration with the shared CLR runtime startup path (`src/CLR/**`). +- Full timer/event behavior (placeholder implementation only). + +## Follow-up Roadmap + +1. Wire a minimal shared runtime startup path (`src/CLR/**`) behind small host portability shims. +2. Replace placeholder PAL/HAL glue with deterministic POSIX/macOS implementations. +3. Enable optional managed payload smoke execution. +4. Add networking/TLS support in a dedicated follow-up change. diff --git a/targets/osx/nanoCLR/CLRStartup.cpp b/targets/osx/nanoCLR/CLRStartup.cpp new file mode 100644 index 0000000000..3d9f261de2 --- /dev/null +++ b/targets/osx/nanoCLR/CLRStartup.cpp @@ -0,0 +1,39 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +#include + +// TODO: Integrate shared CLR startup from src/CLR/Startup/CLRStartup.cpp once +// VIRTUAL_DEVICE code paths are made host-portable on macOS. + +extern "C" +{ + typedef struct CLR_SETTINGS + { + unsigned short MaxContextSwitches; + bool WaitForDebugger; + bool EnterDebuggerLoopAfterExit; + bool RevertToBooterOnFault; +#if defined(VIRTUAL_DEVICE) + bool PerformGarbageCollection; + bool PerformHeapCompaction; +#endif + } CLR_SETTINGS; + + void ClrExit() + { + } + + void ClrReboot() + { + } + + void ClrStartup(CLR_SETTINGS params) + { + (void)params; + + std::cerr << "TODO: ClrStartup for macOS host target is not implemented in this PR skeleton.\n"; + } +} diff --git a/targets/osx/nanoCLR/CMakeLists.txt b/targets/osx/nanoCLR/CMakeLists.txt new file mode 100644 index 0000000000..2d13aee0d4 --- /dev/null +++ b/targets/osx/nanoCLR/CMakeLists.txt @@ -0,0 +1,50 @@ +# +# Copyright (c) .NET Foundation and Contributors +# See LICENSE file in the project root for full license information. +# + +set(NANOCLR_OSX_SOURCES + CLRStartup.cpp + FileStore_OSX.cpp + main.cpp + Memory.cpp + + platform_heap.cpp + targetHAL_Time.cpp + targetPAL_Events.cpp + targetPAL_Time.cpp + targetRandom.cpp + Target_BlockStorage.cpp + Target_HAL.cpp + Various.cpp + WatchDog.cpp +) + +# Keep the output name aligned with existing host naming conventions. +add_executable(nanoFramework.nanoCLR ${NANOCLR_OSX_SOURCES}) + +target_compile_features(nanoFramework.nanoCLR PRIVATE cxx_std_20) + +target_include_directories( + nanoFramework.nanoCLR + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../Include +) + +target_compile_definitions( + nanoFramework.nanoCLR + PRIVATE + NANOCLR_HOST_POSIX=1 + NANOCLR_HOST_OSX=1 + NANOCLR_HOST_WINDOWS=0 + NANO_OSX_ENABLE_SMOKE=$ + NANOCLR_OSX_VERSION_STRING="${PROJECT_VERSION}" +) + +set_target_properties( + nanoFramework.nanoCLR + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" +) + diff --git a/targets/osx/nanoCLR/FileStore_OSX.cpp b/targets/osx/nanoCLR/FileStore_OSX.cpp new file mode 100644 index 0000000000..1b6e63f488 --- /dev/null +++ b/targets/osx/nanoCLR/FileStore_OSX.cpp @@ -0,0 +1,52 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +#include +#include +#include +#include + +// TODO: replace with CLR_RT_FileStore integration once shared runtime code is enabled. + +bool nanoCLR_OSX_LoadFile(const std::string &path, std::vector &content, std::string &error) +{ + std::ifstream in(path, std::ios::binary); + if (!in.is_open()) + { + error = "Cannot open file: " + path; + return false; + } + + content.assign(std::istreambuf_iterator(in), std::istreambuf_iterator()); + + if (in.bad() || (in.fail() && !in.eof())) + { + error = "Failed reading file: " + path; + content.clear(); + return false; + } + + return true; +} + +bool nanoCLR_OSX_SaveFile(const std::string &path, const std::vector &content, std::string &error) +{ + std::ofstream out(path, std::ios::binary | std::ios::trunc); + if (!out.is_open()) + { + error = "Cannot open file for writing: " + path; + return false; + } + + out.write(reinterpret_cast(content.data()), static_cast(content.size())); + + if (!out.good()) + { + error = "Failed writing file: " + path; + return false; + } + + return true; +} diff --git a/targets/osx/nanoCLR/Memory.cpp b/targets/osx/nanoCLR/Memory.cpp new file mode 100644 index 0000000000..3898dbb175 --- /dev/null +++ b/targets/osx/nanoCLR/Memory.cpp @@ -0,0 +1,50 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +#include +#include +#include + +namespace +{ +constexpr size_t c_DefaultHeapSizeBytes = 10 * 1024 * 1024; + +unsigned char *s_MemoryStart = nullptr; +unsigned char *s_CustomHeapStart = nullptr; +std::once_flag s_DefaultHeapOnce; +std::once_flag s_CustomHeapOnce; +} // namespace + +void HeapLocation(unsigned char *&BaseAddress, unsigned int &SizeInBytes) +{ + std::call_once(s_DefaultHeapOnce, []() { + // TODO: evaluate mmap() if we need stronger parity with win32 VirtualAlloc behavior. + s_MemoryStart = static_cast(std::malloc(c_DefaultHeapSizeBytes)); + + if (s_MemoryStart != nullptr) + { + std::memset(s_MemoryStart, 0xEA, c_DefaultHeapSizeBytes); + } + }); + + BaseAddress = s_MemoryStart; + SizeInBytes = (s_MemoryStart != nullptr) ? static_cast(c_DefaultHeapSizeBytes) : 0; +} + +void CustomHeapLocation(unsigned char *&BaseAddress, unsigned int &SizeInBytes) +{ + std::call_once(s_CustomHeapOnce, []() { + // TODO: revisit custom heap ownership model when CLR runtime is wired. + s_CustomHeapStart = static_cast(std::malloc(c_DefaultHeapSizeBytes)); + + if (s_CustomHeapStart != nullptr) + { + std::memset(s_CustomHeapStart, 0xEA, c_DefaultHeapSizeBytes); + } + }); + + BaseAddress = s_CustomHeapStart; + SizeInBytes = (s_CustomHeapStart != nullptr) ? static_cast(c_DefaultHeapSizeBytes) : 0; +} diff --git a/targets/osx/nanoCLR/Target_BlockStorage.cpp b/targets/osx/nanoCLR/Target_BlockStorage.cpp new file mode 100644 index 0000000000..30ec60a89a --- /dev/null +++ b/targets/osx/nanoCLR/Target_BlockStorage.cpp @@ -0,0 +1,18 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +#include + +void BlockStorage_AddDevices() +{ + // TODO: wire block storage implementation for host runtime mode. +} + +bool BlockStorageStream_Initialize(void *stream, uint32_t blockUsage) +{ + (void)stream; + (void)blockUsage; + return false; +} diff --git a/targets/osx/nanoCLR/Target_HAL.cpp b/targets/osx/nanoCLR/Target_HAL.cpp new file mode 100644 index 0000000000..ed9b9c97ab --- /dev/null +++ b/targets/osx/nanoCLR/Target_HAL.cpp @@ -0,0 +1,24 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +#include +#include + +void HAL_Windows_Debug_Print(const char *szText) +{ + if (szText == nullptr) + { + return; + } + + std::string text(szText); + + while (!text.empty() && (text.back() == '\r' || text.back() == '\n')) + { + text.pop_back(); + } + + std::cout << text << "\n" << std::flush; +} diff --git a/targets/osx/nanoCLR/Various.cpp b/targets/osx/nanoCLR/Various.cpp new file mode 100644 index 0000000000..b4aab1ce20 --- /dev/null +++ b/targets/osx/nanoCLR/Various.cpp @@ -0,0 +1,110 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +#include +#include +#include +#include +#include +#include +#include + +bool HAL_Windows_IsShutdownPending() +{ + return false; +} + +void HAL_Windows_AcquireGlobalLock() +{ + // TODO: This lock is currently a stub and can be inconsistent with HasGlobalLock(). + // Replace with a real host lock when shared runtime threading is wired for macOS. +} + +void HAL_Windows_ReleaseGlobalLock() +{ +} + +bool HAL_Windows_HasGlobalLock() +{ + return false; +} + +uint64_t HAL_Windows_GetPerformanceTicks() +{ + const auto now = std::chrono::steady_clock::now().time_since_epoch(); + const auto nanos = std::chrono::duration_cast(now).count(); + return static_cast(nanos / 100); +} + +void HAL_Windows_FastSleep(long long ticks) +{ + if (ticks <= 0) + { + return; + } + + std::this_thread::sleep_for(std::chrono::nanoseconds(ticks * 100LL)); +} + +void ClrExit() +{ +} + +int hal_vprintf(const char *format, va_list arg) +{ + return vprintf(format, arg); +} + +int hal_vfprintf(void *stream, const char *format, va_list arg) +{ + FILE *out = stream == nullptr ? stdout : static_cast(stream); + return vfprintf(out, format, arg); +} + +int hal_vsnprintf(char *buffer, size_t len, const char *format, va_list arg) +{ + return vsnprintf(buffer, len, format, arg); +} + +int hal_snprintf(char *buffer, size_t len, const char *format, ...) +{ + va_list arg_ptr; + va_start(arg_ptr, format); + const int chars = hal_vsnprintf(buffer, len, format, arg_ptr); + va_end(arg_ptr); + return chars; +} + +int hal_stricmp(const char *dst, const char *src) +{ + return strcasecmp(dst, src); +} + +void CPU_Hibernate() +{ + std::this_thread::sleep_for(std::chrono::seconds(1)); +} + +void CPU_Shutdown() +{ + std::exit(0); +} + +void CPU_SetPowerMode(int powerLevel) +{ + (void)powerLevel; +} + +void CPU_Sleep(int level, uint64_t wakeEvents) +{ + (void)level; + (void)wakeEvents; + std::this_thread::yield(); +} + +void RtosYield() +{ + std::this_thread::yield(); +} diff --git a/targets/osx/nanoCLR/WatchDog.cpp b/targets/osx/nanoCLR/WatchDog.cpp new file mode 100644 index 0000000000..8131d70cd3 --- /dev/null +++ b/targets/osx/nanoCLR/WatchDog.cpp @@ -0,0 +1,19 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +#include + +void Watchdog_Enable(uint32_t timeoutMilliseconds) +{ + (void)timeoutMilliseconds; +} + +void Watchdog_Disable() +{ +} + +void Watchdog_Reset() +{ +} diff --git a/targets/osx/nanoCLR/main.cpp b/targets/osx/nanoCLR/main.cpp new file mode 100644 index 0000000000..20ed85cdc0 --- /dev/null +++ b/targets/osx/nanoCLR/main.cpp @@ -0,0 +1,29 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +#include + +namespace +{ +void PrintBanner() +{ + std::cout << ".NET nanoFramework nanoCLR OSX v" << NANOCLR_OSX_VERSION_STRING << "\n"; + std::cout << "Copyright (c) .NET Foundation and Contributors\n\n"; +} +} // namespace + +int main() +{ + PrintBanner(); + +#if NANO_OSX_ENABLE_SMOKE + // TODO: Replace with real CLR startup flow once shared runtime portability shims are in place. + std::cout << "Smoke mode is enabled. Runtime startup is not wired yet.\n"; + return 0; +#else + std::cerr << "Smoke mode is disabled and runtime startup is not available yet.\n"; + return 2; +#endif +} diff --git a/targets/osx/nanoCLR/platform_heap.cpp b/targets/osx/nanoCLR/platform_heap.cpp new file mode 100644 index 0000000000..cf020fcca1 --- /dev/null +++ b/targets/osx/nanoCLR/platform_heap.cpp @@ -0,0 +1,28 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +#include + +#if __has_include() +#include +#else +extern "C" +{ + void *platform_malloc(size_t size); + void platform_free(void *ptr); +} +#endif + +#include + +void *platform_malloc(size_t size) +{ + return std::malloc(size); +} + +void platform_free(void *ptr) +{ + std::free(ptr); +} diff --git a/targets/osx/nanoCLR/platform_selector.h b/targets/osx/nanoCLR/platform_selector.h new file mode 100644 index 0000000000..b2a0a49510 --- /dev/null +++ b/targets/osx/nanoCLR/platform_selector.h @@ -0,0 +1,11 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +#ifndef PLATFORM_OSX_SELECTOR_H +#define PLATFORM_OSX_SELECTOR_H + +#define PLATFORM_OSX_HOST 1 + +#endif // PLATFORM_OSX_SELECTOR_H diff --git a/targets/osx/nanoCLR/targetHAL_Time.cpp b/targets/osx/nanoCLR/targetHAL_Time.cpp new file mode 100644 index 0000000000..63907b0517 --- /dev/null +++ b/targets/osx/nanoCLR/targetHAL_Time.cpp @@ -0,0 +1,28 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +#include +#include +#include +#include + +void HAL_Time_Sleep_MicroSeconds(unsigned int uSec) +{ + std::this_thread::sleep_for(std::chrono::microseconds(uSec)); +} + +void HAL_Time_Sleep_MicroSeconds_InterruptEnabled(unsigned int uSec) +{ + std::this_thread::sleep_for(std::chrono::microseconds(uSec)); +} + +uint64_t HAL_Time_CurrentSysTicks() +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + + // Convert to .NET ticks (100ns units). + return (static_cast(ts.tv_sec) * 10000000ULL) + (static_cast(ts.tv_nsec) / 100ULL); +} diff --git a/targets/osx/nanoCLR/targetPAL_Events.cpp b/targets/osx/nanoCLR/targetPAL_Events.cpp new file mode 100644 index 0000000000..88a2f598da --- /dev/null +++ b/targets/osx/nanoCLR/targetPAL_Events.cpp @@ -0,0 +1,143 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +#include +#include +#include +#include +#include +#include + +namespace +{ +std::mutex s_eventsMutex; +std::condition_variable s_eventsCondition; +uint32_t s_systemEvents = 0; +std::atomic s_boolTimerGeneration{0}; +bool *s_savedTimerCompleteFlag = nullptr; +} // namespace + +bool Events_Initialize_Platform() +{ + return true; +} + +void Events_SetBoolTimer(bool *timerCompleteFlag, uint32_t millisecondsFromNow) +{ + // TODO: Placeholder timer implementation. Make this path thread-safe when runtime integration is wired. + if (timerCompleteFlag == nullptr) + { + return; + } + + s_savedTimerCompleteFlag = timerCompleteFlag; + *timerCompleteFlag = false; + const uint64_t myGen = s_boolTimerGeneration.fetch_add(1, std::memory_order_acq_rel) + 1; + + std::thread([millisecondsFromNow, myGen]() { + std::this_thread::sleep_for(std::chrono::milliseconds(millisecondsFromNow)); + + if (s_boolTimerGeneration.load(std::memory_order_acquire) != myGen) + { + return; + } + + if (s_savedTimerCompleteFlag != nullptr) + { + // TODO: Scaffold placeholder until this is wired into a proper completion/event path. + *s_savedTimerCompleteFlag = true; + } + }).detach(); +} + +bool Events_Initialize() +{ + std::lock_guard lock(s_eventsMutex); + s_systemEvents = 0; + return Events_Initialize_Platform(); +} + +bool Events_Uninitialize() +{ + std::lock_guard lock(s_eventsMutex); + s_systemEvents = 0; + return true; +} + +void Events_Set(uint32_t events) +{ + { + std::lock_guard lock(s_eventsMutex); + s_systemEvents |= events; + } + + s_eventsCondition.notify_all(); +} + +uint32_t Events_Get(uint32_t eventsOfInterest) +{ + std::lock_guard lock(s_eventsMutex); + const uint32_t result = s_systemEvents & eventsOfInterest; + s_systemEvents &= ~eventsOfInterest; + return result; +} + +void Events_Clear(uint32_t events) +{ + { + std::lock_guard lock(s_eventsMutex); + s_systemEvents &= ~events; + } + + s_eventsCondition.notify_all(); +} + +uint32_t Events_MaskedRead(uint32_t events) +{ + std::lock_guard lock(s_eventsMutex); + return s_systemEvents & events; +} + +uint32_t Events_WaitForEvents(uint32_t powerLevel, uint32_t wakeupSystemEvents, uint32_t timeoutMilliseconds) +{ + (void)powerLevel; + + std::unique_lock lock(s_eventsMutex); + + if (timeoutMilliseconds == 0) + { + return s_systemEvents & wakeupSystemEvents; + } + + if (timeoutMilliseconds == static_cast(-1)) + { + s_eventsCondition.wait(lock, [wakeupSystemEvents]() { return (s_systemEvents & wakeupSystemEvents) != 0; }); + return s_systemEvents & wakeupSystemEvents; + } + + const bool signaled = + s_eventsCondition.wait_for(lock, std::chrono::milliseconds(timeoutMilliseconds), [wakeupSystemEvents]() { + return (s_systemEvents & wakeupSystemEvents) != 0; + }); + + return signaled ? (s_systemEvents & wakeupSystemEvents) : 0; +} + +using set_Event_Callback = void (*)(void *, uint32_t); + +void Events_SetCallback(set_Event_Callback pfn, void *arg) +{ + // TODO: callback registration support for host runtime integration. + (void)pfn; + (void)arg; +} + +void FreeManagedEvent(uint8_t category, uint8_t subCategory, uint16_t data1, uint32_t data2) +{ + (void)category; + (void)subCategory; + (void)data1; + (void)data2; +} diff --git a/targets/osx/nanoCLR/targetPAL_Time.cpp b/targets/osx/nanoCLR/targetPAL_Time.cpp new file mode 100644 index 0000000000..5893fdf7d8 --- /dev/null +++ b/targets/osx/nanoCLR/targetPAL_Time.cpp @@ -0,0 +1,24 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +#include +#include +#include "targetPAL_time.h" + +namespace +{ +std::atomic s_nextCompareValue{0}; +} // namespace + +extern "C" void Time_SetCompare(uint64_t compareValue) +{ + // TODO: replace with a real completion timer queue wired into HAL_COMPLETION. + s_nextCompareValue.store(compareValue, std::memory_order_relaxed); +} + +uint64_t nanoCLR_OSX_GetNextCompareValue() +{ + return s_nextCompareValue.load(std::memory_order_relaxed); +} diff --git a/targets/osx/nanoCLR/targetRandom.cpp b/targets/osx/nanoCLR/targetRandom.cpp new file mode 100644 index 0000000000..0d8ed4d8c2 --- /dev/null +++ b/targets/osx/nanoCLR/targetRandom.cpp @@ -0,0 +1,34 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +#include +#include + +uint32_t nanoCLR_OSX_NextRandomU32() +{ + static std::random_device randomDevice; + return randomDevice(); +} + +double nanoCLR_OSX_NextRandomDouble() +{ + static std::random_device randomDevice; + return static_cast(randomDevice()) / static_cast(0x100000000ULL); +} + +void nanoCLR_OSX_FillRandomBytes(uint8_t *buffer, unsigned int count) +{ + if (buffer == nullptr) + { + return; + } + + static std::random_device randomDevice; + + for (unsigned int i = 0; i < count; i++) + { + buffer[i] = static_cast(randomDevice()); + } +}