Skip to content

Conversation

@josesimoes
Copy link
Member

@josesimoes josesimoes commented Nov 10, 2025

Description

  • Add new spawn helper for static constructors of closed generic types.
  • Adjust existing SpawnStaticConstructor() to handle these.
  • CLR_RT_HeapBlock_Delegate now stores a TypeDef index to be able to deal with generic types.

Motivation and Context

  • Static constructors for generic types where being skipped. Still the closed generic types that have static constructors need to be executed, in the same fashion as the static constructors for the non-generic types. This PR takes care of that spawning the respective cctor for each closed generic instance.

How Has This Been Tested?

Screenshots

Types of changes

  • Improvement (non-breaking change that improves a feature, code or algorithm)
  • Bug fix (non-breaking change which fixes an issue with code or algorithm)
  • New feature (non-breaking change which adds functionality to code)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Config and build (change in the configuration and build system, has no impact on code or features)
  • Dev Containers (changes related with Dev Containers, has no impact on code or features)
  • Dependencies/declarations (update dependencies or assembly declarations and changes associated, has no impact on code or features)
  • Documentation (changes or updates in the documentation, has no impact on code or features)

Checklist

  • My code follows the code style of this project (only if there are changes in source code).
  • My changes require an update to the documentation (there are changes that require the docs website to be updated).
  • I have updated the documentation accordingly (the changes require an update on the docs in this repo).
  • I have read the CONTRIBUTING document.
  • I have tested everything locally and all new and existing tests passed (only if there are changes in source code).

Summary by CodeRabbit

  • Bug Fixes
    • Fixed static constructor execution for generic types to ensure proper initialization during runtime startup.
    • Improved generic type context handling in delegate execution to support proper static constructor invocation.

- Add new spawn helper for static constructors of closed generic types.
- Adjust existing SpawnStaticConstructor() to handle these.
- CLR_RT_HeapBlock_Delegate now stores a TypeDef index to be able to deal with generic types.
@josesimoes josesimoes added the Area: Common libs Everything related with common libraries label Nov 10, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 10, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

The pull request extends static constructor execution to support closed generic types. Changes add a generic type specification field to delegates, introduce a new helper method to spawn generic static constructors, add an assembly flag to track generic constructor completion, and adjust thread context setup and delegate initialization for generic scenarios.

Changes

Cohort / File(s) Summary
Header declarations and interface updates
src/CLR/Include/nanoCLR_Runtime.h, src/CLR/Include/nanoCLR_Runtime__HeapBlock.h
Added StaticGenericConstructorsExecuted flag (0x00000040) to assembly state tracking; declared HasStaticConstructor() method on CLR_RT_Assembly; declared SpawnGenericTypeStaticConstructorsHelper() on CLR_RT_ExecutionEngine; added m_genericTypeSpec field to CLR_RT_HeapBlock_Delegate struct
Type system and helper methods
src/CLR/Core/TypeSystem.cpp
Implemented new HasStaticConstructor() method to check for static constructor presence on a given generic type definition by iterating methods and checking the MD_StaticConstructor flag
Static constructor orchestration
src/CLR/Core/Execution.cpp
Introduced SpawnGenericTypeStaticConstructorsHelper() to iterate TypeSpec entries and spawn static constructors for closed generic types; refactored SpawnStaticConstructor() to branch based on delegate's generic type specification, routing generic static constructors through the new helper; adjusted assembly dependency sequencing to execute both regular and generic static constructor phases
Delegate and thread context
src/CLR/Core/Thread.cpp
Added generic context setup in PushThreadProcDelegate() to assign delegate's m_genericTypeSpec to thread method instance if present
Delegate initialization
src/CLR/Core/CLR_RT_HeapBlock_Delegate.cpp
Added Clear() call on m_genericTypeSpec during delegate CreateInstance() to initialize the new generic type specification field

Sequence Diagram(s)

sequenceDiagram
    actor Runtime
    participant Exec as ExecutionEngine
    participant Asm as Assembly
    participant TypeSpec as TypeSpec Index
    participant Thread as cctor Thread
    
    Runtime->>Exec: SpawnStaticConstructor(delegate)
    alt delegate has m_genericTypeSpec
        Exec->>TypeSpec: Extract generic type info
        Exec->>Exec: SpawnGenericTypeStaticConstructorsHelper()
        loop For each TypeSpec
            Exec->>Asm: HasStaticConstructor(generic type)
            alt Has cctor
                Exec->>Thread: Create & schedule delegate
                Thread->>Thread: Set generic context from m_genericTypeSpec
            end
        end
        Exec->>Asm: Mark StaticGenericConstructorsExecuted
    else Regular static constructor
        Exec->>Exec: SpawnStaticConstructorHelper()
        Exec->>Thread: Schedule on cctor thread
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Static constructor orchestration logic (Execution.cpp): New branching between generic and non-generic paths with TypeSpec iteration and assembly dependency coordination requires careful validation
  • Interaction between new method and existing flow (Execution.cpp, Thread.cpp): Verify generic type specification is correctly threaded through delegate creation, spawning, and execution
  • New helper method correctness (TypeSystem.cpp): Ensure HasStaticConstructor() correctly identifies static constructors on generic type definitions
  • Delegate initialization and thread context (CLR_RT_HeapBlock_Delegate.cpp, Thread.cpp): Confirm m_genericTypeSpec is properly initialized and propagated through thread delegation

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main objective of the changeset: implementing the spawning of static constructors for closed generic types.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

nfbot and others added 2 commits November 10, 2025 18:42
Automated fixes for code style.
…4b2a5-0a80-441d-a7be-d524cbc7c506

Code style fixes for nanoframework/nf-interpreter PR#3235
@josesimoes
Copy link
Member Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 10, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/CLR/Core/TypeSystem.cpp (1)

5988-5998: Guard against invalid TypeDef index; add bounds assert; consider making method const.

If genericTypeDef is invalid, indexing m_assemblies[Assembly()-1] underflows and can crash. Also, early-return when there are no methods, and assert bounds to avoid OOB. This method doesn’t mutate state, so it can be const.

Apply:

-bool CLR_RT_Assembly::HasStaticConstructor(const CLR_RT_TypeDef_Index &genericTypeDef)
+bool CLR_RT_Assembly::HasStaticConstructor(const CLR_RT_TypeDef_Index &genericTypeDef) /* could be const */
 {
     NATIVE_PROFILE_CLR_CORE();
 
-    CLR_RT_Assembly *ownerAsm = g_CLR_RT_TypeSystem.m_assemblies[genericTypeDef.Assembly() - 1];
-    const CLR_RECORD_TYPEDEF *ownerTd = ownerAsm->GetTypeDef(genericTypeDef.Type());
+    // Validate input to prevent underflow/null deref.
+    if (!NANOCLR_INDEX_IS_VALID(genericTypeDef))
+    {
+        return false;
+    }
+    CLR_RT_Assembly *ownerAsm = g_CLR_RT_TypeSystem.m_assemblies[genericTypeDef.Assembly() - 1];
+    if (ownerAsm == nullptr)
+    {
+        return false;
+    }
+    const CLR_RECORD_TYPEDEF *ownerTd = ownerAsm->GetTypeDef(genericTypeDef.Type());
+    if (ownerTd == nullptr)
+    {
+        return false;
+    }
 
-    // Calculate total method count for this type
-    int methodCount = ownerTd->virtualMethodCount + ownerTd->instanceMethodCount + ownerTd->staticMethodCount;
+    // Calculate total method count for this type
+    const int firstMethod = ownerTd->firstMethod;
+    const int methodCount = ownerTd->virtualMethodCount + ownerTd->instanceMethodCount + ownerTd->staticMethodCount;
+    if (firstMethod == CLR_EmptyIndex || methodCount == 0)
+    {
+        return false;
+    }
+#if !defined(BUILD_RTM)
+    _ASSERTE(firstMethod + methodCount <= ownerAsm->tablesSize[TBL_MethodDef]);
+#endif
 
     // Iterate through all methods of the generic type definition
-    const CLR_RECORD_METHODDEF *md = ownerAsm->GetMethodDef(ownerTd->firstMethod);
+    const CLR_RECORD_METHODDEF *md = ownerAsm->GetMethodDef(firstMethod);
     for (int i = 0; i < methodCount; i++, md++)
     {
         // Check if this is a static constructor
         if (md->flags & CLR_RECORD_METHODDEF::MD_StaticConstructor)
         {
             return true;
         }
     }
 
     return false;
 }
🧹 Nitpick comments (1)
src/CLR/Core/Execution.cpp (1)

1148-1175: Consider gating generic phase on dependencies' generic phase completion

The code currently gates generic type static constructor execution only on whether dependencies have completed regular static constructors (checking StaticConstructorsExecuted). To improve robustness and prevent surprises when generic static constructors depend on other generics' initialization, also check that all dependent assemblies have completed their generic phase (StaticGenericConstructorsExecuted). This mirrors the existing pattern for regular static constructor ordering. As noted, this can be relaxed if it causes deadlocks in legitimate dependency cycles.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7b050bb and c0db7cc.

📒 Files selected for processing (6)
  • src/CLR/Core/CLR_RT_HeapBlock_Delegate.cpp (1 hunks)
  • src/CLR/Core/Execution.cpp (3 hunks)
  • src/CLR/Core/Thread.cpp (2 hunks)
  • src/CLR/Core/TypeSystem.cpp (1 hunks)
  • src/CLR/Include/nanoCLR_Runtime.h (3 hunks)
  • src/CLR/Include/nanoCLR_Runtime__HeapBlock.h (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-01-22T03:38:57.394Z
Learnt from: josesimoes
Repo: nanoframework/nf-interpreter PR: 3074
File: src/CLR/Core/GarbageCollector_Info.cpp:107-167
Timestamp: 2025-01-22T03:38:57.394Z
Learning: In nanoFramework's memory management code, DataSize() validation is comprehensively handled through CLR_RT_HeapCluster::ValidateBlock() and other caller code. Additional size checks in ValidateCluster() are redundant as the validation is already performed at multiple levels.

Applied to files:

  • src/CLR/Include/nanoCLR_Runtime__HeapBlock.h
  • src/CLR/Core/CLR_RT_HeapBlock_Delegate.cpp
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: nf-interpreter (Build_WIN32_nanoCLR)
  • GitHub Check: nf-interpreter (Build_Azure_RTOS_targets SL_STK3701A)
  • GitHub Check: nf-interpreter (Build_ESP32_targets ESP32_P4_UART)
  • GitHub Check: nf-interpreter (Build_ESP32_targets ESP32_ETHERNET_KIT_1.2)
  • GitHub Check: nf-interpreter (Build_ESP32_targets ESP32_S3_ALL)
  • GitHub Check: nf-interpreter (Build_ESP32_targets ESP32_H2_THREAD)
  • GitHub Check: nf-interpreter (Build_ESP32_targets ESP32_C6_THREAD)
  • GitHub Check: nf-interpreter (Build_ESP32_targets ESP32_C3)
  • GitHub Check: nf-interpreter (Build_ESP32_targets ESP32_BLE_REV0)
  • GitHub Check: nf-interpreter (Build_STM32_targets ST_STM32F769I_DISCOVERY)
  • GitHub Check: nf-interpreter (Build_ESP32_targets ESP32_PSRAM_REV0)
  • GitHub Check: nf-interpreter (Build_TI_SimpleLink_targets TI_CC1352R1_LAUNCHXL_915)
  • GitHub Check: nf-interpreter (Build_NXP_targets NXP_MIMXRT1060_EVK)
  • GitHub Check: nf-interpreter (Build_STM32_targets ST_STM32F429I_DISCOVERY)
  • GitHub Check: nf-interpreter (Nightly build) (Build_STM32_targets MXCHIP_AZ3166)
  • GitHub Check: nf-interpreter (Nightly build) (Build_STM32_targets ORGPAL_PALX)
  • GitHub Check: nf-interpreter (Nightly build) (Build_STM32_targets ORGPAL_PALTHREE)
  • GitHub Check: nf-interpreter (Check_Code_Style)
  • GitHub Check: nf-interpreter (Nightly build) (Check_Build_Options)
  • GitHub Check: nf-interpreter (Check_Build_Options)
🔇 Additional comments (4)
src/CLR/Include/nanoCLR_Runtime.h (2)

1342-1342: New assembly flag for generic cctors — LGTM

Bit 0x00000040 doesn’t collide with existing flags. No issues.


4236-4239: Generic helper only compiled for !NANOCLR_APPDOMAINS

Closed generic .cctors won’t run in APPDOMAINS builds with current guards. If AppDomains are supported in your targets, mirror this helper in the APPDOMAINS path or explicitly document the limitation.

src/CLR/Core/Execution.cpp (2)

969-972: Marking regular static constructors executed — LGTM

Flagging at the point no more .cctors are found is correct and matches dependency checks later.


1091-1104: Delegate lifecycle and generic context are correctly preserved.

The code at lines 1091-1104 is correct. Verification confirms:

  1. m_genericTypeSpec persistence: Set once at Execution.cpp:1056 before the delegate is pushed, and never cleared during delegate invocation. It remains accessible when the callback reads it at line 1091.

  2. Delegate validity: The delegate (m_dlg) is stored in the thread object and remains valid throughout the callback execution. It's only cleared at Thread.cpp:527 after the termination callback returns, ensuring it exists when StaticConstructorTerminationCallback accesses it at line 1091.

  3. Sequence confirmation: The callback chain is: delegate thread proc → termination callback invocation → callback reads m_dlg and m_genericTypeSpec → delegate cleared after callback returns.

  4. GC safety: The m_dlg field is properly marked for heap relocation and handled via Thread.Relocate(), maintaining validity through garbage collection cycles.

@josesimoes josesimoes merged commit a0b3e1d into nanoframework:develop Nov 10, 2025
26 checks passed
@josesimoes josesimoes deleted the add-generic-cctor branch November 10, 2025 23:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: Common libs Everything related with common libraries Type: enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants