File: Contracts\StackWalk\Context\WasmContext.cs
Web Access
Project: src\runtime\src\native\managed\cdac\Microsoft.Diagnostics.DataContractReader.Contracts\Microsoft.Diagnostics.DataContractReader.Contracts.csproj (Microsoft.Diagnostics.DataContractReader.Contracts)
// 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.Runtime.InteropServices;

namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers;

/// <summary>
/// Platform context for CoreCLR on WebAssembly.
/// </summary>
/// <remarks>
/// WebAssembly has no native register context: the runtime's <c>DT_CONTEXT</c> is an empty
/// struct and <c>REGDISPLAY</c> is zeroed (see <c>src/coreclr/debug/inc/dbgtargetcontext.h</c>
/// and <c>src/coreclr/inc/regdisp.h</c>). Instead, the context is driven by the managed linear
/// stack pointer (<c>$sp</c>): ReadyToRun frames are unwound over the linear stack with a
/// frameSize-based virtual unwind (see <see cref="Wasm.WasmUnwinder"/>) using synthetic virtual
/// IPs, and interpreter frames are the explicit
/// <c>InterpreterFrame.TopInterpMethodContextFrame</c> -&gt; <c>InterpMethodContextFrame.pParent</c>
/// chain. A real stack is a mix of the two.
///
/// The instruction/stack/frame pointer slots are 32-bit (wasm32): <see cref="StackPointer"/> is
/// the managed linear stack pointer and <see cref="InstructionPointer"/> is the current virtual IP.
/// <see cref="Unwind"/> advances the context by one ReadyToRun frame.
/// </remarks>
[StructLayout(LayoutKind.Sequential)]
internal struct WasmContext : IPlatformContext
{
    // Field order and size mirror the native wasm T_CONTEXT (src/coreclr/pal/inc/pal.h,
    // HOST_WASM branch) so that a serialized WasmContext is byte-compatible with the
    // runtime's context blob:
    //   ContextFlags @0, InterpreterWalkFramePointer @4, InterpreterSP @8,
    //   InterpreterFP @12, InterpreterIP @16  (20 bytes, all 32-bit / wasm32).
    // There is no native register file; these slots are populated by the R2R virtual
    // unwind and the interpreter frame-chain walker.
    private uint _contextFlags;
    private uint _interpreterWalkFramePointer;
    private uint _interpreterSP;
    private uint _interpreterFP;
    private uint _interpreterIP;

    // Name of the synthetic "first argument register" the interpreter stack walk uses to
    // stash the owning InterpreterFrame address (native SetFirstArgReg / GetFirstArgReg in
    // src/coreclr/vm/wasm/cgencpu.h write context->InterpreterWalkFramePointer).
    internal const string InterpreterWalkFramePointerRegister = "interpreterwalkframepointer";

    // Size matches the serialized native wasm T_CONTEXT so that ContextHolder.GetBytes()
    // and Size stay consistent.
    public readonly uint Size => 5 * sizeof(uint);

    public readonly uint ContextControlFlags => 0;

    public readonly uint FullContextFlags => 0;

    public readonly uint AllContextFlags => 0;

    // No register file: there is no stack-pointer register index.
    public readonly int StackPointerRegister => -1;

    public TargetPointer StackPointer
    {
        readonly get => new(_interpreterSP);
        set => _interpreterSP = (uint)value.Value;
    }

    public TargetCodePointer InstructionPointer
    {
        readonly get => new(_interpreterIP);
        set => _interpreterIP = (uint)value.Value;
    }

    public TargetPointer FramePointer
    {
        readonly get => new(_interpreterFP);
        set => _interpreterFP = (uint)value.Value;
    }

    public uint RawContextFlags { readonly get => _contextFlags; set => _contextFlags = value; }

    public void Unwind(Target target)
    {
        // Advance one ReadyToRun frame over the managed linear stack. When the R2R walk
        // terminates (an interpreter transition or the stack top), StackPointer becomes null and
        // the caller falls back to the explicit Frame chain / interpreter frame chain.
        Wasm.WasmUnwinder unwinder = new(target, new Wasm.WasmR2RInfo(target));
        TargetPointer sp = StackPointer;
        if (unwinder.TryUnwindOneFrame(ref sp, out TargetCodePointer ip))
        {
            StackPointer = sp;
            InstructionPointer = ip;
        }
        else
        {
            StackPointer = TargetPointer.Null;
            InstructionPointer = TargetCodePointer.Null;
        }
    }

    // WASM has no hardware single-step flag; like other architectures without one (ARM, LoongArch64,
    // RISC-V) this is a no-op. Callers (e.g. Debugger_1.PrepareExceptionHijack) invoke it
    // unconditionally, so it must not throw.
    public void UnsetSingleStepFlag() { }

    public bool TrySetRegister(string name, TargetNUInt value)
    {
        if (name.Equals("pc", StringComparison.OrdinalIgnoreCase) || name.Equals("ip", StringComparison.OrdinalIgnoreCase))
        {
            _interpreterIP = (uint)value.Value;
            return true;
        }
        if (name.Equals("sp", StringComparison.OrdinalIgnoreCase))
        {
            _interpreterSP = (uint)value.Value;
            return true;
        }
        if (name.Equals("fp", StringComparison.OrdinalIgnoreCase))
        {
            _interpreterFP = (uint)value.Value;
            return true;
        }
        if (name.Equals(InterpreterWalkFramePointerRegister, StringComparison.OrdinalIgnoreCase))
        {
            _interpreterWalkFramePointer = (uint)value.Value;
            return true;
        }
        return false;
    }

    public readonly bool TryReadRegister(string name, out TargetNUInt value)
    {
        if (name.Equals("pc", StringComparison.OrdinalIgnoreCase) || name.Equals("ip", StringComparison.OrdinalIgnoreCase))
        {
            value = new TargetNUInt(_interpreterIP);
            return true;
        }
        if (name.Equals("sp", StringComparison.OrdinalIgnoreCase))
        {
            value = new TargetNUInt(_interpreterSP);
            return true;
        }
        if (name.Equals("fp", StringComparison.OrdinalIgnoreCase))
        {
            value = new TargetNUInt(_interpreterFP);
            return true;
        }
        if (name.Equals(InterpreterWalkFramePointerRegister, StringComparison.OrdinalIgnoreCase))
        {
            value = new TargetNUInt(_interpreterWalkFramePointer);
            return true;
        }
        value = default;
        return false;
    }

    public bool TrySetRegister(int number, TargetNUInt value) => false;

    public readonly bool TryReadRegister(int number, out TargetNUInt value)
    {
        value = default;
        return false;
    }
}