Skip to content

Conversation

@josesimoes
Copy link
Member

@josesimoes josesimoes commented Nov 25, 2025

Description

  • Implement localloc IL instruction.
    • Add handler for instruction.
    • Add storage pointers to stack frame to allow deallocation on method return.
  • Implement cpblk IL instruction.
  • Add new data type DATATYPE_PTR.
  • Add new helper APIs in heap block to set and get unmanaged pointers.
  • Numeric add and substract can now handle operations with unmanaged pointers.
  • ldsflda now properly handles static fields with RVA (now gets pointer to metadata storage).
  • Implement ctor from pointer for Span<T> and ReadOnlySpan<T>.
  • Update mscorlib declaration.

Motivation and Context

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

  • New Features
    • Track per-frame local allocations to support LOCALLOC within methods.
    • Added constructors for Span and ReadOnlySpan to create spans from raw pointers.
    • Implemented LOCALLOC and CPBLK IL opcodes (bounded local allocation and block copy).
    • Introduced unmanaged-pointer support and pointer arithmetic for pointer-valued values.

✏️ Tip: You can customize this high-level summary in your review settings.

- Add handler for instruction. Add storage pointers to stack frame to allow dealocation on moethod return.
@josesimoes josesimoes added the Area: Common libs Everything related with common libraries label Nov 25, 2025
@nfbot nfbot added Type: enhancement Type: dependencies Pull requests that update a dependency file(s) or version labels Nov 25, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 25, 2025

Walkthrough

Adds per-frame localloc tracking and implementations for CEE_LOCALLOC/CEE_CPBLK and RVA field loads; introduces DATATYPE_PTR and pointer-aware HeapBlock operations; adds native constructors for Span/ReadOnlySpan built from void*+length and updates native method tables and an mscorlib metadata token.

Changes

Cohort / File(s) Summary
Span/ReadOnlySpan native declarations & impl
src/CLR/CorLib/corlib_native.h, src/CLR/CorLib/corlib_native_System_ReadOnlySpan_1.cpp, src/CLR/CorLib/corlib_native_System_Span_1.cpp
Added native constructor declarations _ctor___VOID__VOIDptr__I4 for ReadOnlySpan1 and Span1; implemented constructors that validate generic element type (no refs), validate length and pointer, allocate typed array, copy bytes from void* into the array, and initialize Span state.
Native method table & metadata token
src/CLR/CorLib/corlib_native.cpp
Inserted new Span/ReadOnlySpan entries into native method lookup tables (and added placeholder null slots to preserve alignment) for reflective and non-reflective paths; updated g_CLR_AssemblyNative_mscorlib token value when NANOCLR_REFLECTION is TRUE.
Interpreter — IL opcode handling
src/CLR/Core/Interpreter.cpp
Implemented CEE_LOCALLOC and CEE_CPBLK opcode handling in main IL path; added handling for fields with RVA (ldfld/ldsfld load raw blob data); added generic cctor reschedule helper; integrated local-allocation bookkeeping into execution.
Stack frame local-allocation tracking
src/CLR/Core/CLR_RT_StackFrame.cpp, src/CLR/Include/nanoCLR_Runtime.h
Added per-frame fields (m_localAllocCount, m_localAllocs[]) and MAX_LOCALALLOC_COUNT = 4; initialize, propagate to inline frames, save/restore, and cleanup on Push/Pop and inline transitions.
HeapBlock pointer support & arithmetic
src/CLR/Core/CLR_RT_HeapBlock.cpp, src/CLR/Include/nanoCLR_Runtime__HeapBlock.h, src/CLR/Include/nanoCLR_Types.h
Added new data type DATATYPE_PTR; added SetUnmanagedPointer(uintptr_t) and UnmanagedPointer() accessors; StoreToReference supports copying DATATYPE_PTR; NumericAdd/NumericSub perform pointer arithmetic with I4 offsets.

Sequence Diagram(s)

sequenceDiagram
    participant IL as IL Interpreter
    participant Frame as Stack Frame
    participant Heap as Heap (Array)
    participant GC as GC

    IL->>Frame: CEE_LOCALLOC(size)
    Frame->>Heap: allocate byte[] (size)
    Heap-->>Frame: arrayRef
    Frame->>GC: protect arrayRef (store in m_localAllocs)
    Frame->>Frame: m_localAllocCount++
    Frame-->>IL: push arrayRef
    Note right of Frame: PushInline/RestoreFromInline copy/restore\nm_localAllocCount & m_localAllocs
    Note right of Frame: On Pop: free/clear m_localAllocs and reset count
Loading
sequenceDiagram
    participant Caller as Managed Call
    participant SpanNative as Span native ctor
    participant TypeSys as Type System / Metadata
    participant Heap as Heap

    Caller->>SpanNative: _ctor(void* ptr, int length)
    SpanNative->>TypeSys: resolve and validate generic T (no refs)
    TypeSys-->>SpanNative: resolved / error
    SpanNative->>Heap: create T[] of length
    Heap-->>SpanNative: arrayRef
    SpanNative->>SpanNative: copy bytes from ptr -> arrayRef (element-size aware)
    SpanNative-->>Caller: initialized Span instance (or throw)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas needing extra attention:
    • Span constructors: generic type resolution, reference-type containment check, element-size calculations and memory-copy safety.
    • Interpreter changes: correctness of CEE_LOCALLOC and CEE_CPBLK, interaction with GC protections, RVA load semantics.
    • Frame state propagation: correct copy/restore of m_localAllocCount and m_localAllocs across inline frames and stack saves/restores.
    • Pointer semantics: DATATYPE_PTR invariants, StoreToReference behavior, and pointer arithmetic edge cases.
    • Native method table/token update: ensure table alignment, placeholder null placements, and token change are consistent with reflection expectations.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% 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 'Implement localloc and cpblk IL instructions' directly matches the main changes in the PR, which focuses on implementing these two IL instructions along with supporting infrastructure.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@josesimoes
Copy link
Member Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 25, 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: 2

🧹 Nitpick comments (4)
src/CLR/CorLib/corlib_native_System_Span_1.cpp (1)

9-151: Span(void, int) ctor: logic is sound; consider minor robustness tweaks*

Overall the implementation is careful: it reconstructs T from the caller’s generic context, rejects reference/contains‑reference element types, validates length, enforces that the backing buffer is byte[], computes the element size via c_CLR_RT_DataTypeLookup, validates capacity, reshapes the array metadata, and finally sets the Span length. This aligns with the ReadOnlySpan variant and the intended localloc‑backed usage.

A few points to double‑check:

  • sourceArray = stack.Arg1().DereferenceArray(); assumes the VOID* argument is always a managed array reference. If any other caller ever passes a non‑array object (or null), this will AV; consider adding a null/array check and returning CLR_E_WRONG_TYPE or CLR_E_NULL_REFERENCE for robustness.
  • newNumElements = sourceArray->m_numOfElements / elementSize silently truncates if the byte count is not an exact multiple of elementSize. If the intent is to require exact divisibility (to avoid silently “losing” trailing bytes), you might want to check m_numOfElements % elementSize == 0 and treat mismatch as CLR_E_INVALID_PARAMETER.
  • ClearElements(0, length) after reshaping is good for safety, but note that it zeroes the region even if the caller had already written into the localloc buffer; this is acceptable if the design is that the Span ctor owns initialization, but it’s worth confirming that no caller expects preserved contents.

If all current call sites are only the new CoreLib APIs that hand in localloc-allocated byte[], the current behavior is acceptable; the above mainly protects against future misuse.

src/CLR/CorLib/corlib_native_System_ReadOnlySpan_1.cpp (1)

9-151: ReadOnlySpan(void, int) mirrors Span correctly*

The ReadOnlySpan pointer ctor is essentially identical to the Span version: it reconstructs T from the generic context, blocks reference/contains‑reference types, validates length, enforces byte[] backing, reshapes the array metadata, and sets span length. That symmetry is good and keeps the two behaviors aligned.

Same optional points as for Span:

  • Consider a defensive null/array check on sourceArray = stack.Arg1().DereferenceArray(); to avoid hard faults on misuse.
  • Decide whether m_numOfElements % elementSize != 0 should be rejected explicitly rather than truncating via /.
  • Confirm that clearing elements via ClearElements(0, length) matches expected semantics for any caller that might preinitialize the underlying buffer.

Given the current intended use (backing from localloc), the implementation is coherent; the suggestions are mainly to harden against future callers.

src/CLR/Core/CLR_RT_StackFrame.cpp (1)

334-359: Inline frame save/restore of localloc state is consistent

In PushInline, you:

  • Save m_localAllocCount and all m_localAllocs[] into the inline frame backup.
  • Then reset m_localAllocCount and clear m_localAllocs[] for the inlined callee.

This achieves the desired behavior: the caller’s locallocs are preserved and isolated from any additional locallocs in the inlined method. On PopInline, RestoreFromInlineStack restores the caller’s m_localAllocCount and array. This is logically sound and matches how other frame state is managed.

src/CLR/Include/nanoCLR_Runtime.h (1)

2532-2534: Localloc tracking fields are consistent; just confirm init/GC integration and fix a small comment nit

The shared MAX_LOCALALLOC_COUNT and mirrored m_localAllocCount/m_localAllocs[] in both CLR_RT_InlineFrame and CLR_RT_StackFrame look coherent and match the intended per-frame localloc tracking.

Two follow‑ups to double‑check in the implementation files:

  • Ensure m_localAllocCount is always reset and m_localAllocs[] are zeroed when stack frames are created or reused (including inline frames pulled from CLR_RT_EventCache::m_inlineBufferStart), so no stale GC roots are left behind.
  • Ensure the GC mark path that walks stack/frame roots also walks each non‑null entry in m_localAllocs[], otherwise these temporary arrays could be collected while still in use.

Minor: the comment at Line 2570 has a typo (“max mumber” → “max number”).

Also applies to: 2545-2547, 2570-2572, 2674-2676

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 577be8f and 9c548a9.

📒 Files selected for processing (7)
  • src/CLR/CorLib/corlib_native.cpp (5 hunks)
  • src/CLR/CorLib/corlib_native.h (2 hunks)
  • src/CLR/CorLib/corlib_native_System_ReadOnlySpan_1.cpp (1 hunks)
  • src/CLR/CorLib/corlib_native_System_Span_1.cpp (1 hunks)
  • src/CLR/Core/CLR_RT_StackFrame.cpp (7 hunks)
  • src/CLR/Core/Interpreter.cpp (1 hunks)
  • src/CLR/Include/nanoCLR_Runtime.h (4 hunks)
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-01-09T13:32:43.711Z
Learnt from: josesimoes
Repo: nanoframework/nf-interpreter PR: 3062
File: src/System.Device.Spi/sys_dev_spi_native_System_Device_Spi_SpiDevice.cpp:106-188
Timestamp: 2025-01-09T13:32:43.711Z
Learning: In nanoFramework, CLR_RT_HeapBlock_Array::Pin() method returns void and cannot fail. It should be called without error handling.

Applied to files:

  • src/CLR/CorLib/corlib_native_System_ReadOnlySpan_1.cpp
  • src/CLR/CorLib/corlib_native_System_Span_1.cpp
📚 Learning: 2024-10-12T19:00:39.000Z
Learnt from: josesimoes
Repo: nanoframework/nf-interpreter PR: 3023
File: targets/netcore/nanoFramework.nanoCLR/nanoCLR_native.cpp:191-225
Timestamp: 2024-10-12T19:00:39.000Z
Learning: When working with `nanoCLR_GetNativeAssemblyInformation`, fixed-size assembly names are required, so code that deals with variable-length names cannot be used.

Applied to files:

  • src/CLR/CorLib/corlib_native.h
  • src/CLR/CorLib/corlib_native.cpp
  • src/CLR/Include/nanoCLR_Runtime.h
📚 Learning: 2025-06-26T09:16:55.184Z
Learnt from: josesimoes
Repo: nanoframework/nf-interpreter PR: 3190
File: src/CLR/Core/TypeSystem.cpp:0-0
Timestamp: 2025-06-26T09:16:55.184Z
Learning: In nanoFramework's CLR attribute parsing (src/CLR/Core/TypeSystem.cpp), the sentinel value 0xFFFF in string tokens represents a null string. When encountered, this should result in a true null reference (using SetObjectReference(nullptr)) rather than an empty string instance, and the boxing operation should be skipped via early return.

Applied to files:

  • src/CLR/CorLib/corlib_native.cpp
📚 Learning: 2024-09-25T11:28:38.536Z
Learnt from: josesimoes
Repo: nanoframework/nf-interpreter PR: 3023
File: targets/netcore/nanoFramework.nanoCLR/nanoCLR_native.cpp:191-225
Timestamp: 2024-09-25T11:28:38.536Z
Learning: In `nanoCLR_GetNativeAssemblyInformation`, there is no need to return the number of bytes written, as the memory buffer is zeroed, making the string buffer terminated.

Applied to files:

  • src/CLR/CorLib/corlib_native.cpp
📚 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.h
🧬 Code graph analysis (3)
src/CLR/CorLib/corlib_native_System_ReadOnlySpan_1.cpp (1)
src/CLR/CorLib/corlib_native_System_Span_1.cpp (2)
  • _ctor___VOID__VOIDptr__I4 (11-151)
  • _ctor___VOID__VOIDptr__I4 (11-11)
src/CLR/CorLib/corlib_native_System_Span_1.cpp (1)
src/CLR/CorLib/corlib_native_System_ReadOnlySpan_1.cpp (2)
  • _ctor___VOID__VOIDptr__I4 (11-151)
  • _ctor___VOID__VOIDptr__I4 (11-11)
src/CLR/CorLib/corlib_native.cpp (2)
src/CLR/CorLib/corlib_native_System_ReadOnlySpan_1.cpp (2)
  • _ctor___VOID__VOIDptr__I4 (11-151)
  • _ctor___VOID__VOIDptr__I4 (11-11)
src/CLR/CorLib/corlib_native_System_Span_1.cpp (2)
  • _ctor___VOID__VOIDptr__I4 (11-151)
  • _ctor___VOID__VOIDptr__I4 (11-11)
⏰ 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_Azure_RTOS_targets SL_STK3701A)
  • GitHub Check: nf-interpreter (Build_WIN32_nanoCLR)
  • 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_STM32_targets ST_STM32F429I_DISCOVERY)
  • GitHub Check: nf-interpreter (Build_ESP32_targets ESP32_PSRAM_REV0)
  • GitHub Check: nf-interpreter (Build_NXP_targets NXP_MIMXRT1060_EVK)
  • GitHub Check: nf-interpreter (Build_TI_SimpleLink_targets TI_CC1352R1_LAUNCHXL_915)
  • 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 (6)
src/CLR/CorLib/corlib_native.h (1)

781-790: Span/ReadOnlySpan pointer ctor declarations look consistent

The new native declarations for _ctor___VOID__VOIDptr__I4 on both Library_corlib_native_System_ReadOnlySpan_1 and Library_corlib_native_System_Span_1 match the corresponding implementations and are correctly guarded by #if (NANOCLR_REFLECTION == TRUE). No further changes needed here.

src/CLR/Core/Interpreter.cpp (1)

1005-1034: Generic .cctor reschedule helper is correct but edge cases should be confirmed

The new HandleGenericCctorReschedule helper cleanly centralizes the pattern of:

  • Computing a hash for a closed generic type using ComputeHashForClosedGenericType.
  • Treating 0xFFFFFFFF as an invalid hash and returning CLR_E_WRONG_TYPE.
  • Looking up or creating the CLR_RT_GenericCctorExecutionRecord.
  • If a .cctor is scheduled but not executed, rewinding ip by 3 bytes (opcode + compressed field token) and returning CLR_E_RESCHEDULE.

Two points to verify:

  • The “3 bytes” rewind (*pIp -= 3) assumes this is only used for opcodes with exactly one‑byte opcode + two‑byte compressed token. You’re using it with field opcodes like LDSFLD/LDSFLDA/STSFLD whose encoding here indeed uses a 2‑byte compressed token; please confirm we don’t call this helper from any opcode with a different operand layout in future.
  • FindOrCreateGenericCctorRecord(hash, nullptr) can return non‑null with flags that don’t have c_Scheduled set; you correctly treat that as “no reschedule” and return S_OK, which is fine. Just be aware this will not attempt to schedule .cctors, only observe already‑scheduled ones.

Conceptually this is a good extraction and keeps the LDSFLD/LDSFLDA/STSFLD paths simpler.

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

124-130: Initialize localloc tracking in Push to a known state

Zeroing m_localAllocCount and clearing m_localAllocs[] on frame creation is correct and prevents stale pointers from previous uses of the same event‑heap block. No changes needed here.


425-473: RestoreStack/SaveStack correctly propagate localloc metadata across inline frames

SaveStack and RestoreStack now copy m_localAllocCount and each m_localAllocs[i] to and from the CLR_RT_InlineFrame. This keeps the localloc tracking coherent across stack saves/restores (e.g., inlining transitions, exception unwinds). The loops mirror each other and use c_Max_Localloc_Count, so they’ll stay in sync with the header definition.

src/CLR/CorLib/corlib_native.cpp (2)

699-702: ReadOnlySpan pointer ctor wiring looks correct; please confirm index alignment

The new Library_corlib_native_System_ReadOnlySpan_1::_ctor___VOID__VOIDptr__I4 entry, with surrounding nullptr padding and the existing NativeReadOnlySpanConstructor slot, matches the established layout pattern for generic natives in this table.

Given how fragile this array/index mapping is, please confirm that:

  • The managed ReadOnlySpan<T> pointer ctor is marked as native and its method index matches this new slot in the reflection-enabled mscorlib.
  • Any other new ReadOnlySpan<T> natives introduced in the managed assembly are either accounted for here or intentionally left as managed.

Also applies to: 708-709, 718-725


813-825: Span pointer ctor and updated reflection token look consistent; verify against regenerated mscorlib

The added Library_corlib_native_System_Span_1::_ctor___VOID__VOIDptr__I4 entry, along with the nearby CopyTo and NativeSpanConstructor entries and the extra nullptr padding before the threading methods, follows the existing grouping and spacing conventions for Span-related natives.

The updated native assembly token 0xCECAB752 under NANOCLR_REFLECTION == TRUE is appropriate given the mscorlib metadata change; the non‑reflection token and table remain unchanged.

Please verify that:

  • The managed Span<T> pointer ctor’s native index matches this new table position in the reflection-enabled mscorlib build.
  • The updated token matches the regenerated managed mscorlib (so loader and debugger see this as a single coherent version).
  • For NANOCLR_REFLECTION == FALSE, either the managed mscorlib did not change or its native table/token are regenerated in lockstep in the corresponding build configuration.

Also applies to: 843-846, 1616-1616

@josesimoes josesimoes changed the title Implement localloc Implement localloc and cpblk IL instructions Nov 27, 2025
@josesimoes
Copy link
Member Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 27, 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.

Automated fixes for code style.
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: 5

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/CLR_RT_HeapBlock.cpp (1)

496-657: Fix DATATYPE_PTR handling in StoreToReference; pointer arithmetic looks OK

The new DATATYPE_PTR support in NumericAdd/NumericSub is fine in spirit (treat pointers as byte-wise addresses and only allow I4 offsets), but the StoreToReference branch for DATATYPE_PTR is unsafe and does not implement cpblk semantics correctly:

  • dt in StoreToReference is ref.DataType(), so the DATATYPE_PTR case runs when the reference is an unmanaged pointer.
  • obj = ref.Dereference(); returns the raw address stored in that pointer, which is an arbitrary unmanaged buffer, not a CLR_RT_HeapBlock*.
  • &NumericByRef() is the address of this heap block’s internal Numeric union, not the memory pointed to by the source pointer.
  • memcpy((void *)obj, (void *)&NumericByRef(), size); therefore copies bytes from the heapblock’s internal storage into the unmanaged buffer, not from the source buffer.
  • After that, the common obj->Assign(*this); path treats the unmanaged buffer as a CLR_RT_HeapBlock and writes headers/fields into it. That is undefined behavior and will corrupt whatever memory the pointer happens to reference.

For cpblk-style scenarios (dest ptr, src ptr, size), you likely want:

  • Both operands to be unmanaged pointers (e.g., src.StoreToReference(dest, size)), and
  • A raw block copy between the underlying addresses, without interpreting them as heap blocks or assigning heapblock headers.

A safer implementation for the DATATYPE_PTR branch would look like:

-    else if (dt == DATATYPE_PTR)
-    {
-        // unmanaged pointer, perform a direct memory copy
-        obj = ref.Dereference();
-        memcpy((void *)obj, (void *)&NumericByRef(), size);
-    }
+    else if (dt == DATATYPE_PTR)
+    {
+        // Unmanaged pointer: copy raw bytes from this->Dereference() to ref.Dereference().
+        void *dst = (void *)ref.Dereference();
+        void *src = (void *)this->Dereference();
+
+        // cpblk allows overlapping regions; memmove is safer than memcpy here.
+        memmove(dst, src, size);
+
+        // No heapblock assignment for raw pointer copies.
+        NANOCLR_SET_AND_LEAVE(S_OK);
+    }
@@
-    obj->Assign(*this);
+    obj->Assign(*this);

(Where this->Dereference() and ref.Dereference() for DATATYPE_PTR are assumed to return the stored raw addresses.)

This avoids treating arbitrary unmanaged buffers as CLR_RT_HeapBlock instances and aligns DATATYPE_PTR with the intended “raw address” semantics.

The NumericAdd/NumericSub DATATYPE_PTR cases themselves look reasonable (byte-wise pointer arithmetic with an I4 offset), so once StoreToReference is fixed, the unmanaged pointer story should be consistent.

Also applies to: 2005-2095, 2103-2199

♻️ Duplicate comments (1)
src/CLR/Core/Interpreter.cpp (1)

4224-4259: Validate negative sizes and address GC rooting concerns from previous review.

This implementation has two issues:

  1. New concern: Negative size check needed. Line 4226 reads the size as u4 (unsigned). If IL passes a negative int32 value, it will be reinterpreted as a large unsigned value, potentially causing a huge allocation attempt. Unlike the array creation path that uses CreateInstance (which checks for negative lengths), platform_malloc has no such guard.

  2. Previously flagged: GC rooting and OOM handling still missing. A past review comment (lines 4224-4293) identified that m_localAllocs pointers are not scanned or relocated by the GC, and that OOM handling should include compaction+retry. Those concerns remain unaddressed.

Immediate fix for negative size:

 OPDEF(CEE_LOCALLOC, "localloc", PopI, PushI, InlineNone, IPrimitive, 2, 0xFE, 0x0F, NEXT)
 {
-    CLR_UINT32 size = evalPos[0].NumericByRef().u4;
+    CLR_INT32 sizeRaw = evalPos[0].NumericByRef().s4;
+    
+    if (sizeRaw < 0)
+    {
+        NANOCLR_SET_AND_LEAVE(CLR_E_OUT_OF_RANGE);
+    }
+    
+    CLR_UINT32 size = (CLR_UINT32)sizeRaw;

     evalPos--;
     CHECKSTACK(stack, evalPos);

For the GC rooting and OOM handling issues, please see the detailed previous review comment that recommended:

  • Adding CheckMultipleBlocks(stack->m_localAllocs, stack->m_localAllocCount) in Thread_Mark (GC marking)
  • Calling Heap_Relocate on each stored pointer in the relocation path
  • Adding compaction+retry on CLR_E_OUT_OF_MEMORY similar to the array allocation flow (lines ~3410-3420)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9c548a9 and 750b1ef.

📒 Files selected for processing (8)
  • src/CLR/CorLib/corlib_native_System_ReadOnlySpan_1.cpp (1 hunks)
  • src/CLR/CorLib/corlib_native_System_Span_1.cpp (1 hunks)
  • src/CLR/Core/CLR_RT_HeapBlock.cpp (3 hunks)
  • src/CLR/Core/CLR_RT_StackFrame.cpp (7 hunks)
  • src/CLR/Core/Interpreter.cpp (3 hunks)
  • src/CLR/Include/nanoCLR_Runtime.h (4 hunks)
  • src/CLR/Include/nanoCLR_Runtime__HeapBlock.h (1 hunks)
  • src/CLR/Include/nanoCLR_Types.h (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/CLR/CorLib/corlib_native_System_ReadOnlySpan_1.cpp
🧰 Additional context used
🧠 Learnings (4)
📚 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/Core/CLR_RT_HeapBlock.cpp
  • src/CLR/Include/nanoCLR_Runtime.h
📚 Learning: 2025-01-09T13:32:43.711Z
Learnt from: josesimoes
Repo: nanoframework/nf-interpreter PR: 3062
File: src/System.Device.Spi/sys_dev_spi_native_System_Device_Spi_SpiDevice.cpp:106-188
Timestamp: 2025-01-09T13:32:43.711Z
Learning: In nanoFramework, CLR_RT_HeapBlock_Array::Pin() method returns void and cannot fail. It should be called without error handling.

Applied to files:

  • src/CLR/Include/nanoCLR_Runtime__HeapBlock.h
📚 Learning: 2024-10-12T19:00:39.000Z
Learnt from: josesimoes
Repo: nanoframework/nf-interpreter PR: 3023
File: targets/netcore/nanoFramework.nanoCLR/nanoCLR_native.cpp:191-225
Timestamp: 2024-10-12T19:00:39.000Z
Learning: When working with `nanoCLR_GetNativeAssemblyInformation`, fixed-size assembly names are required, so code that deals with variable-length names cannot be used.

Applied to files:

  • src/CLR/Include/nanoCLR_Runtime.h
📚 Learning: 2025-11-20T14:08:30.044Z
Learnt from: josesimoes
Repo: nanoframework/nf-interpreter PR: 3240
File: src/CLR/Core/TypeSystem.cpp:812-818
Timestamp: 2025-11-20T14:08:30.044Z
Learning: Repo: nanoframework/nf-interpreter
File: src/CLR/Core/TypeSystem.cpp
Context: Generics resolution in CLR_RT_TypeSpec_Instance::ResolveToken
Learning: When resolving a VAR (!T) TypeSpec using the caller’s closed generic, after switching to the caller context (Set(caller->genericType->Assembly(), closedTsRow) and assigning assembly accordingly), always fetch the TypeSpec with target = assembly->GetTypeSpec(closedTsRow) rather than assm->GetTypeSpec(closedTsRow) to avoid cross-assembly mismatches.

Applied to files:

  • src/CLR/Core/Interpreter.cpp
🧬 Code graph analysis (1)
src/CLR/CorLib/corlib_native_System_Span_1.cpp (1)
src/CLR/CorLib/corlib_native_System_ReadOnlySpan_1.cpp (2)
  • _ctor___VOID__VOIDptr__I4 (11-112)
  • _ctor___VOID__VOIDptr__I4 (11-11)
⏰ 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). (6)
  • GitHub Check: nf-interpreter (Check_Code_Style)
  • 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 (Nightly build) (Check_Build_Options)
  • GitHub Check: nf-interpreter (Check_Build_Options)
🔇 Additional comments (5)
src/CLR/Include/nanoCLR_Runtime.h (4)

2570-2571: LGTM - Good practice using a class-scoped constant.

The constant c_Max_Localloc_Count correctly references the global macro and follows the existing naming convention for constants in this structure.


2545-2546: I'll verify the concerns raised in the original review comment about the new inline frame local allocation tracking fields. Let me start by examining the codebase to understand the implementation details.
<function_calls>

#!/bin/bash

Find the full CLR_RT_InlineFrame structure context

rg -n "struct CLR_RT_InlineFrame|class CLR_RT_InlineFrame" -A 50 --type=cpp --type=h | head -100


</function_calls>

Now let me search for the PopInline implementation and localloc cleanup logic:
<function_calls>

#!/bin/bash

Search for PopInline and inline frame cleanup

rg -n "PopInline\s*(" -B 3 -A 15 --type=cpp --type=h | head -150


</function_calls>

Let me find the MAX_LOCALALLOC_COUNT definition and search for localloc-related cleanup:
<function_calls>

#!/bin/bash

Find MAX_LOCALALLOC_COUNT and localloc handling

rg -n "MAX_LOCALALLOC_COUNT|m_localAlloc" --type=cpp --type=h | head -100


</function_calls>

Let me also check the frame cleanup mechanism to understand how memory is deallocated:
<function_calls>

#!/bin/bash

Search for frame cleanup and platform_free calls related to allocations

rg -n "platform_free|CleanupAlllocations|m_localAlloc" -B 3 -A 3 --type=cpp | head -150


</function_calls>

Let me also look for how regular frames handle localloc allocations to understand the pattern:
<function_calls>

#!/bin/bash

Check if there's a regular frame localloc tracking similar to what's being added for inline frames

rg -n "m_localAllocCount|m_localAllocs" --type=cpp --type=h -B 5 -A 5


</function_calls>


2674-2676: I'm unable to access the repository to verify the specific concerns raised in the review comment. However, based on the review comment itself, I can provide a rewritten version that clarifies the key verification points that need manual confirmation:


Verify cleanup responsibility, document fields, and confirm struct size constraints.

These fields mirror the inline frame additions and share the same requirements for proper maintenance:

  1. Cleanup responsibility: Confirm that CLR_RT_StackFrame::Pop() properly deallocates pointers tracked in m_localAllocs before frame destruction. If cleanup is missing, localloc'd memory could leak.

  2. Documentation: Add inline comments explaining that m_localAllocCount and m_localAllocs track unmanaged localloc pointers and clarify the cleanup lifecycle.

  3. Struct size impact: These additions increase the per-frame overhead by approximately 17–33 bytes (1 byte count + 4–8 bytes × 4 pointers, architecture-dependent). For deeply nested call stacks, verify this trade-off is acceptable and document it.

  4. Struct layout validation: Confirm the placement before m_extension[1] does not violate alignment expectations and that the CT_ASSERT statements at lines 2876–2887 still validate field offsets correctly with these new members.


2532-2533: The review comment cannot be verified due to repository access limitations in the sandbox environment.

The original comment raises valid concerns about the MAX_LOCALALLOC_COUNT hard limit and requests verification of:

  • Overflow handling when the limit of 4 is exceeded
  • Documentation explaining why 4 was chosen
  • Bounds checking in the CEE_LOCALLOC implementation

However, these verifications require access to:

  • The CEE_LOCALLOC instruction handler implementation
  • Memory management and frame cleanup code
  • Any validation logic related to m_localAllocCount

Since the header file alone cannot confirm whether proper overflow handling exists in the implementation, and sandbox repository access is unavailable, the original review comment's verification request remains unresolved but valid.

The comment should be addressed by the developer reviewing/implementing this change by confirming overflow behavior and adding documentation if not already present.

src/CLR/Include/nanoCLR_Types.h (1)

521-524: I'll help you verify and rewrite this review comment. Let me start by examining the enum structure and the specific concerns raised.
<function_calls>

First, let's read the header file to see the full enum context

cat -n src/CLR/Include/nanoCLR_Types.h | sed -n '500,540p'


</function_calls>

<function_calls>

Check the full DataType enum to understand the structure

rg -n "enum.*DataType|DATATYPE_" src/CLR/Include/nanoCLR_Types.h | head -50


</function_calls>

<function_calls>

Let's see more context around DATATYPE_FIRST_INVALID and DATATYPE_PTR

rg -n -B5 -A5 "DATATYPE_FIRST_INVALID|DATATYPE_PTR" src/CLR/Include/nanoCLR_Types.h


</function_calls>

Comment on lines 3069 to 3083
// check if field has RVA
CLR_RT_FieldDef_Instance inst;
if (inst.InitializeFromIndex(field) && (inst.target->flags & CLR_RECORD_FIELDDEF::FD_HasFieldRVA) &&
inst.target->defaultValue != CLR_EmptyIndex)
{
CLR_PMETADATA ptrSrc;

// Get the data from the Signatures table (this contains the raw byte array)
ptrSrc = inst.assembly->GetSignature(inst.target->defaultValue);
CLR_UINT32 elementCount;
NANOCLR_READ_UNALIGNED_UINT16(elementCount, ptrSrc);

// ptrSrc is now pointing to the raw byte data
ptr = (CLR_RT_HeapBlock *)ptrSrc;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Incorrect pointer cast and unclear RVA semantics.

Line 3082 casts the raw metadata pointer from GetSignature directly to CLR_RT_HeapBlock*. The Signatures table contains raw bytes, not CLR_RT_HeapBlock structures, so this cast is a type mismatch that could lead to incorrect pointer interpretation.

Additionally, the elementCount read at line 3079 is never used. More importantly, this code changes the semantics of ldsflda for RVA fields: instead of returning a reference to the static field storage (which AccessStaticField would provide), it now returns a pointer to raw metadata bytes. Managed code expecting a typed field reference will receive a raw byte pointer instead.

Clarify the intended behavior:

  • If RVA fields should expose raw metadata bytes, document this deviation and ensure the cast is correct (likely void* or CLR_UINT8* rather than CLR_RT_HeapBlock*)
  • If RVA fields should behave like normal static fields, remove this special case and let AccessStaticField handle them
  • Consider whether ldsflda on an RVA field should even be allowed, as it exposes internal metadata layout
// Example fix if raw bytes are intended:
// check if field has RVA
CLR_RT_FieldDef_Instance inst;
if (inst.InitializeFromIndex(field) && (inst.target->flags & CLR_RECORD_FIELDDEF::FD_HasFieldRVA) &&
    inst.target->defaultValue != CLR_EmptyIndex)
{
    CLR_PMETADATA ptrSrc;
    
    // Get the data from the Signatures table (this contains the raw byte array)
    ptrSrc = inst.assembly->GetSignature(inst.target->defaultValue);
    CLR_UINT32 elementCount;
    NANOCLR_READ_UNALIGNED_UINT16(elementCount, ptrSrc);
    
    // ptrSrc is now pointing to the raw byte data
    // TODO: Verify this is the correct interpretation for RVA fields
    ptr = (CLR_RT_HeapBlock *)(void *)ptrSrc;
}

josesimoes and others added 7 commits November 27, 2025 01:31
…d0f50-b140-488f-b95f-db856b2f79b6

Code style fixes for nanoframework/nf-interpreter PR#3242
Automated fixes for code style.
…c26bb-406f-4265-bae1-c4f38e4cd866

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

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 27, 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: 0

Caution

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

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

644-654: Critical: Missing early return after DATATYPE_PTR case causes undefined behavior.

After the memcpy on line 647, execution falls through to line 654 (obj->Assign(*this)), but obj was never initialized in the DATATYPE_PTR branch. This will dereference an uninitialized pointer, causing a crash or memory corruption.

Apply this diff to fix the control flow:

     else if (dt == DATATYPE_PTR)
     {
         // unmanaged pointer, perform a direct memory copy
         memcpy((void *)ref.UnmanagedPointer(), (void *)&NumericByRef(), size);
+
+        NANOCLR_SET_AND_LEAVE(S_OK);
     }
     else
     {
         NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE);
     }
-
-    obj->Assign(*this);

Alternatively, move the obj->Assign(*this) inside the previous else-if branches that initialize obj.

src/CLR/CorLib/corlib_native_System_Span_1.cpp (1)

163-205: Existing NativeSpanConstructor missing error check on CreateInstance.

Line 189 calls CLR_RT_HeapBlock_Array::CreateInstance without NANOCLR_CHECK_HRESULT, unlike the new constructor at line 99. If allocation fails, subsequent code will operate on invalid data.

Apply this diff for consistency:

         CLR_RT_HeapBlock &refArray = thisSpan[FIELD___array];
-        CLR_RT_HeapBlock_Array::CreateInstance(refArray, length, sourceType);
+        NANOCLR_CHECK_HRESULT(CLR_RT_HeapBlock_Array::CreateInstance(refArray, length, sourceType));
src/CLR/CorLib/corlib_native_System_ReadOnlySpan_1.cpp (1)

117-159: Existing NativeReadOnlySpanConstructor missing error check on CreateInstance.

Line 143 calls CLR_RT_HeapBlock_Array::CreateInstance without NANOCLR_CHECK_HRESULT, same issue as in the Span implementation. This should be fixed for consistency with the new constructors.

Apply this diff:

         CLR_RT_HeapBlock &refArray = thisSpan[FIELD___array];
-        CLR_RT_HeapBlock_Array::CreateInstance(refArray, length, sourceType);
+        NANOCLR_CHECK_HRESULT(CLR_RT_HeapBlock_Array::CreateInstance(refArray, length, sourceType));
🧹 Nitpick comments (1)
src/CLR/Core/Interpreter.cpp (1)

3051-3089: RVA handling in ldsflda looks coherent; only a tiny readability nit

The new RVA path cleanly:

  • Re-resolves the field,
  • Reads and discards the length prefix from the signature blob to advance ptrSrc,
  • Treats the remaining bytes as the backing storage whose address is then exposed via SetReference.

This matches the intent of exposing a stable address for RVA-backed data and is self‑contained. If you want, you could make dummyVar a CLR_UINT16 to better document the blob header width, but that’s purely cosmetic.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 750b1ef and 90e4a41.

📒 Files selected for processing (5)
  • src/CLR/CorLib/corlib_native_System_ReadOnlySpan_1.cpp (1 hunks)
  • src/CLR/CorLib/corlib_native_System_Span_1.cpp (1 hunks)
  • src/CLR/Core/CLR_RT_HeapBlock.cpp (3 hunks)
  • src/CLR/Core/Interpreter.cpp (3 hunks)
  • src/CLR/Include/nanoCLR_Runtime__HeapBlock.h (2 hunks)
🧰 Additional context used
🧠 Learnings (9)
📓 Common learnings
Learnt from: josesimoes
Repo: nanoframework/nf-interpreter PR: 3242
File: src/CLR/Core/Interpreter.cpp:4263-4293
Timestamp: 2025-11-27T02:57:09.957Z
Learning: In nanoFramework's nf-interpreter, the cpblk IL instruction implementation in src/CLR/Core/Interpreter.cpp does not require runtime validation of pointer addresses, size, or bounds. Per ECMA-335 specification, cpblk is an unverifiable instruction where the CIL compiler ensures correctness, not the runtime.
📚 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/CorLib/corlib_native_System_Span_1.cpp
  • src/CLR/Core/CLR_RT_HeapBlock.cpp
  • src/CLR/Core/Interpreter.cpp
  • src/CLR/Include/nanoCLR_Runtime__HeapBlock.h
📚 Learning: 2025-01-09T13:32:43.711Z
Learnt from: josesimoes
Repo: nanoframework/nf-interpreter PR: 3062
File: src/System.Device.Spi/sys_dev_spi_native_System_Device_Spi_SpiDevice.cpp:106-188
Timestamp: 2025-01-09T13:32:43.711Z
Learning: In nanoFramework, CLR_RT_HeapBlock_Array::Pin() method returns void and cannot fail. It should be called without error handling.

Applied to files:

  • src/CLR/CorLib/corlib_native_System_Span_1.cpp
  • src/CLR/CorLib/corlib_native_System_ReadOnlySpan_1.cpp
  • src/CLR/Core/Interpreter.cpp
  • src/CLR/Include/nanoCLR_Runtime__HeapBlock.h
📚 Learning: 2024-10-08T15:52:09.445Z
Learnt from: josesimoes
Repo: nanoframework/nf-interpreter PR: 3023
File: targets/netcore/nanoFramework.nanoCLR/nanoCLR_native.cpp:191-225
Timestamp: 2024-10-08T15:52:09.445Z
Learning: In `nanoCLR_GetNativeAssemblyInformation`, there is no need to return the number of bytes written, as the memory buffer is zeroed, making the string buffer terminated.

Applied to files:

  • src/CLR/CorLib/corlib_native_System_Span_1.cpp
  • src/CLR/Include/nanoCLR_Runtime__HeapBlock.h
📚 Learning: 2025-11-27T02:57:09.957Z
Learnt from: josesimoes
Repo: nanoframework/nf-interpreter PR: 3242
File: src/CLR/Core/Interpreter.cpp:4263-4293
Timestamp: 2025-11-27T02:57:09.957Z
Learning: In nanoFramework's nf-interpreter, the cpblk IL instruction implementation in src/CLR/Core/Interpreter.cpp does not require runtime validation of pointer addresses, size, or bounds. Per ECMA-335 specification, cpblk is an unverifiable instruction where the CIL compiler ensures correctness, not the runtime.

Applied to files:

  • src/CLR/Core/Interpreter.cpp
  • src/CLR/Include/nanoCLR_Runtime__HeapBlock.h
📚 Learning: 2024-10-12T19:00:39.000Z
Learnt from: josesimoes
Repo: nanoframework/nf-interpreter PR: 3023
File: targets/netcore/nanoFramework.nanoCLR/nanoCLR_native.cpp:191-225
Timestamp: 2024-10-12T19:00:39.000Z
Learning: When working with `nanoCLR_GetNativeAssemblyInformation`, fixed-size assembly names are required, so code that deals with variable-length names cannot be used.

Applied to files:

  • src/CLR/Core/Interpreter.cpp
📚 Learning: 2025-11-20T14:08:30.044Z
Learnt from: josesimoes
Repo: nanoframework/nf-interpreter PR: 3240
File: src/CLR/Core/TypeSystem.cpp:812-818
Timestamp: 2025-11-20T14:08:30.044Z
Learning: Repo: nanoframework/nf-interpreter
File: src/CLR/Core/TypeSystem.cpp
Context: Generics resolution in CLR_RT_TypeSpec_Instance::ResolveToken
Learning: When resolving a VAR (!T) TypeSpec using the caller’s closed generic, after switching to the caller context (Set(caller->genericType->Assembly(), closedTsRow) and assigning assembly accordingly), always fetch the TypeSpec with target = assembly->GetTypeSpec(closedTsRow) rather than assm->GetTypeSpec(closedTsRow) to avoid cross-assembly mismatches.

Applied to files:

  • src/CLR/Core/Interpreter.cpp
  • src/CLR/Include/nanoCLR_Runtime__HeapBlock.h
📚 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 CLR_RT_GarbageCollector::ValidateCluster, DataSize() validation is already handled by ValidateBlock() and other caller code, making additional size checks redundant.

Applied to files:

  • src/CLR/Core/Interpreter.cpp
  • src/CLR/Include/nanoCLR_Runtime__HeapBlock.h
📚 Learning: 2025-06-26T09:16:55.184Z
Learnt from: josesimoes
Repo: nanoframework/nf-interpreter PR: 3190
File: src/CLR/Core/TypeSystem.cpp:0-0
Timestamp: 2025-06-26T09:16:55.184Z
Learning: In nanoFramework's CLR attribute parsing (src/CLR/Core/TypeSystem.cpp), the sentinel value 0xFFFF in string tokens represents a null string. When encountered, this should result in a true null reference (using SetObjectReference(nullptr)) rather than an empty string instance, and the boxing operation should be skipped via early return.

Applied to files:

  • src/CLR/Include/nanoCLR_Runtime__HeapBlock.h
🧬 Code graph analysis (3)
src/CLR/CorLib/corlib_native_System_Span_1.cpp (1)
src/CLR/CorLib/corlib_native_System_ReadOnlySpan_1.cpp (2)
  • _ctor___VOID__VOIDptr__I4 (11-115)
  • _ctor___VOID__VOIDptr__I4 (11-11)
src/CLR/Core/CLR_RT_HeapBlock.cpp (1)
src/CLR/Include/nanoCLR_Runtime__HeapBlock.h (1)
  • SetUnmanagedPointer (1106-1110)
src/CLR/CorLib/corlib_native_System_ReadOnlySpan_1.cpp (1)
src/CLR/CorLib/corlib_native_System_Span_1.cpp (2)
  • _ctor___VOID__VOIDptr__I4 (11-115)
  • _ctor___VOID__VOIDptr__I4 (11-11)
⏰ 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_Azure_RTOS_targets SL_STK3701A)
  • GitHub Check: nf-interpreter (Build_WIN32_nanoCLR)
  • 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_STM32_targets ST_STM32F769I_DISCOVERY)
  • GitHub Check: nf-interpreter (Build_ESP32_targets ESP32_BLE_REV0)
  • GitHub Check: nf-interpreter (Build_TI_SimpleLink_targets TI_CC1352R1_LAUNCHXL_915)
  • GitHub Check: nf-interpreter (Build_ESP32_targets ESP32_PSRAM_REV0)
  • GitHub Check: nf-interpreter (Build_NXP_targets NXP_MIMXRT1060_EVK)
  • GitHub Check: nf-interpreter (Build_STM32_targets ST_STM32F429I_DISCOVERY)
  • GitHub Check: nf-interpreter (Check_Code_Style)
  • 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 (Nightly build) (Check_Build_Options)
  • GitHub Check: nf-interpreter (Check_Build_Options)
🔇 Additional comments (11)
src/CLR/Include/nanoCLR_Runtime__HeapBlock.h (2)

763-768: New UnmanagedPointer struct correctly defined for pointer storage.

The struct layout with a single uintptr_t member is appropriate for storing raw unmanaged pointers, and integrates well with the existing union structure.


1106-1115: Accessor methods for unmanaged pointer look correct.

  • SetUnmanagedPointer properly sets DATATYPE_PTR with size 1 and stores the pointer
  • UnmanagedPointer() safely returns 0 for non-PTR types, preventing misuse

This is a clean API for the new pointer data type.

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

2081-2094: Pointer addition implementation looks correct.

The byte-wise pointer arithmetic follows ECMA-335 Table III.2 semantics. The [[fallthrough]] attribute properly documents the intentional fall-through to the error case when the RHS type is not I4.


2181-2194: Pointer subtraction implementation is consistent with addition.

Mirrors the addition logic correctly for subtraction, maintaining consistency in pointer arithmetic handling.

src/CLR/CorLib/corlib_native_System_Span_1.cpp (2)

9-9: Type alias improves readability.

The typedef alias for RuntimeHelpers is consistent with the parallel change in corlib_native_System_ReadOnlySpan_1.cpp.


11-115: Span pointer constructor implementation looks correct.

The implementation properly:

  • Validates the generic type argument and ensures T doesn't contain references
  • Validates length is non-negative and input is DATATYPE_PTR
  • Uses NANOCLR_CHECK_HRESULT for CreateInstance (addressing previous review)
  • Protects array from GC before dereferencing (addressing previous review)
  • Correctly uses UnmanagedPointer() to retrieve the raw pointer

The logic is consistent with the parallel ReadOnlySpan implementation.

src/CLR/CorLib/corlib_native_System_ReadOnlySpan_1.cpp (2)

9-9: Type alias consistent with Span implementation.


11-115: ReadOnlySpan pointer constructor implementation is correct and consistent.

The implementation mirrors the Span<T> version exactly, which is appropriate since both types need identical construction semantics from unmanaged pointers. All validations, error handling, and GC protection are properly in place.

src/CLR/Core/Interpreter.cpp (3)

1777-1818: STIND handler: behavior unchanged; whitespace-only delta

This region’s logic (size selection + Promote() + StoreToReference) remains consistent with existing semantics; the only observable change is a blank line, so there’s no behavioral impact to review here.


4263-4293: CEE_CPBLK: direct memmove is appropriate for an unverifiable instruction

The cpblk handler correctly:

  • Pops size, sourceAddress, and destinationAddress (using uintptr_t and handling 32/64‑bit via _WIN64),
  • Uses memmove to allow overlapping regions, and
  • Cleans up the eval stack consistently with the existing opcode patterns.

Per the ECMA-335 semantics and your documented design for this runtime, cpblk is unverifiable and the CIL producer is responsible for pointer validity, so the lack of extra runtime checks here is intentional and acceptable. Based on learnings, no additional validation is required.


4224-4259: I'm unable to complete the verification due to repository access limitations. The repository clone failed, and there are no public references to CEE_LOCALLOC or m_localAllocs in available documentation.

Per the instructions, absence of evidence is not evidence of absence—the code patterns may exist in the implementation but not be publicly documented. Without direct access to verify the frame teardown cleanup flow, I cannot confirm or contradict the review's assumption that "corresponding stack-frame teardown frees entries in m_localAllocs."


CEE_LOCALLOC: unmanaged allocation flow is straightforward and bounded by per-frame count

This implementation:

  • Pops the requested size, enforces the per-frame m_localAllocCount < c_Max_Localloc_Count guard,
  • Allocates via platform_malloc, maps nullptr to CLR_E_OUT_OF_MEMORY,
  • Zeroes the buffer per ECMA-335, tracks the pointer in m_localAllocs, and
  • Exposes it as an unmanaged pointer via SetUnmanagedPointer.

Assuming the corresponding stack-frame teardown frees entries in m_localAllocs, this is a clear and reasonable design for stackalloc-like behavior on your platforms.

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: dependencies Pull requests that update a dependency file(s) or version Type: enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants