Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions docs/design/datacontracts/DebugInfo.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,50 @@ Each variable entry in the Vars section is nibble-encoded as follows:

Signed integers are encoded using the same unsigned scheme, with the sign bit stored in bit 0 (`value = unsigned >> 1`, negate if `unsigned & 1`). On x86, stack offsets are DWORD-aligned and stored divided by `sizeof(DWORD)`.

### WebAssembly Variable Register Encoding

WASM has no physical registers. Every register field in the Vars stream holds a WebAssembly local
index biased past the reserved register numbers:

```text
register = localIndex + WASM_LOCAL_REGNUM_BASE
```

`WASM_LOCAL_REGNUM_BASE` is `3`, one past `REGNUM_AMBIENT_SP`. Values `0` through `2` remain
`REGNUM_PC`, `REGNUM_COUNT`, and `REGNUM_AMBIENT_SP`. As on other targets, the value type is not
encoded: it is implied by the variable's type and by the local's declaration in the WebAssembly
function.

`WASM_LOCAL_REGNUM_BASE` (`3`) and the stack base register (`2`, described below) are fixed
constants of DebugInfo contract version `c1`; readers hardcode them. Changing either requires a new
DebugInfo contract version. Because ReadyToRun images persist this encoding, it also requires a
ReadyToRun format version change so static readers such as R2RDump do not misdecode older images.

### WebAssembly Stack Base Encoding

WASM `VLT_STK` and `VLT_STK2` records currently encode base register `2`.
`REG_FPBASE`, `REG_SPBASE`, and `REGNUM_AMBIENT_SP` all have that value on this target, so the
debug record identifies a logical frame-relative stack home; it does not identify a particular
WebAssembly engine local.

The absolute logical frame address is reconstructed by the runtime stack-walk and unwind
protocol from shadow-stack linear memory. The engine's current per-function SP/FP local allocation
is a separate code-generation detail:

* Frame access allocates an FP value when a method has frame locals, uses `localloc`, or has
funclets.
* Without `localloc`, the root function's FP aliases its SP, including methods that make calls.
* `localloc` gives the root a distinct FP so later SP movement does not change frame-relative
addresses.
* Funclets receive a distinct parent establishing FP; with `localloc`, that remains the root's
pre-adjustment frame base.

The numeric WebAssembly local indices holding those values can vary with function parameters and
compiler-created locals and are not part of this debug-info format. Readers must not infer the
logical frame address from a hardcoded `$varN`, and the producer does not advertise SP/FP engine
local indices. A future producer that changes stack records away from base `2` requires a
coordinated, fail-loud reader update.

### Async Suspension Point APIs

We also support decoding async suspension points (and their captured continuation-object locals) from the `AsyncInfo` chunk of the debug info blob. The chunk is present only for methods that the JIT compiled with runtime-async suspension points; for all other methods, `AsyncInfoSize` is `0` in the FAT header and the API returns an empty list.
Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/inc/cordebuginfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,12 @@ class ICorDebugInfo

};

#ifdef TARGET_WASM
// WASM has no physical registers. A register field in a variable location holds a wasm
// local index biased past the reserved RegNum values above.
static constexpr uint32_t WASM_LOCAL_REGNUM_BASE = REGNUM_AMBIENT_SP + 1;
#endif // TARGET_WASM

// VarLoc describes the location of a native variable. Note that currently, VLT_REG_BYREF and VLT_STK_BYREF
// are only used for value types on X64.
// [cDAC]: Mirrored in managed code (IDacDbiInterface.cs).
Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/jit/codegen.h
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,12 @@ class CodeGen final : public CodeGenInterface

IL_OFFSET siLastEndOffs; // IL offset of the (exclusive) end of the last block processed

#if defined(TARGET_WASM)
// Wasm opens its method-wide scopes in the first emitted block rather than relying on the
// monotonic enter/exit cursors used by other targets.
bool siWasmScopesOpened;
#endif // defined(TARGET_WASM)

/*
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Expand Down
7 changes: 0 additions & 7 deletions src/coreclr/jit/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2901,13 +2901,6 @@ void Compiler::compInitOptions(JitFlags* jitFlags)

opts.compScopeInfo = opts.compDbgInfo;

#ifdef TARGET_WASM
// Wasm uses virtual registers that cannot be encoded in the
// ICorDebugInfo register scheme, and there is no native debugger
// to consume scope info, so disable it entirely.
opts.compScopeInfo = false;
#endif

#ifdef LATE_DISASM
codeGen->getDisAssembler().disOpenForLateDisAsm(info.compMethodName, info.compClassName,
info.compMethodInfo->args.pSig);
Expand Down
4 changes: 4 additions & 0 deletions src/coreclr/jit/ee_il_dll.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -866,7 +866,11 @@ void Compiler::eeDispVar(ICorDebugInfo::NativeVarInfo* var)
{
case CodeGenInterface::VLT_REG:
case CodeGenInterface::VLT_REG_BYREF:
#if defined(TARGET_WASM)
printf("$%u", (unsigned)var->loc.vlReg.vlrReg - ICorDebugInfo::WASM_LOCAL_REGNUM_BASE);
#else
printf("%s", getRegName(var->loc.vlReg.vlrReg));
#endif
if (var->loc.vlType == (ICorDebugInfo::VarLocType)CodeGenInterface::VLT_REG_BYREF)
{
printf(" byref");
Expand Down
7 changes: 6 additions & 1 deletion src/coreclr/jit/lclvars.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1281,7 +1281,7 @@ unsigned Compiler::compMap2ILvarNum(unsigned varNum) const
}

#if defined(TARGET_WASM)
if (varNum == lvaWasmSpArg)
if ((varNum == lvaWasmSpArg) || (varNum == lvaWasmPortableEntryPointArg))
{
return (unsigned)ICorDebugInfo::UNKNOWN_ILNUM;
}
Expand Down Expand Up @@ -1319,6 +1319,11 @@ unsigned Compiler::compMap2ILvarNum(unsigned varNum) const
{
varNum--;
}

if ((lvaWasmPortableEntryPointArg != BAD_VAR_NUM) && (originalVarNum > lvaWasmPortableEntryPointArg))
{
varNum--;
}
#endif

if (varNum >= info.compLocalsCount)
Expand Down
94 changes: 89 additions & 5 deletions src/coreclr/jit/scopeinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,28 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
#include "emit.h"
#include "codegen.h"

#if defined(TARGET_WASM)
// Stack VarLoc records use the reserved ambient SP register number, not a wasm local.
static_assert(REG_FPBASE == REG_SPBASE);
static_assert(REG_FPBASE == REG_NA);
static_assert(static_cast<int>(REG_NA) == static_cast<int>(ICorDebugInfo::REGNUM_AMBIENT_SP));
static_assert(static_cast<int>(ICorDebugInfo::REGNUM_AMBIENT_SP) == 2);

//------------------------------------------------------------------------
// siWasmDebugRegNum: Encode a wasm local as a debug-info register number.
//
// Arguments:
// reg - the JIT register representing a wasm local.
//
// Return Value:
// The wasm local index biased past the reserved debug-info register numbers.
//
static regNumber siWasmDebugRegNum(regNumber reg)
{
return static_cast<regNumber>(ICorDebugInfo::WASM_LOCAL_REGNUM_BASE + WasmRegToIndex(reg));
}
#endif // defined(TARGET_WASM)

//============================================================================
// siVarLoc functions
//============================================================================
Expand Down Expand Up @@ -208,6 +230,30 @@ void CodeGenInterface::siVarLoc::storeVariableInRegisters(regNumber reg, regNumb
// Note: mask registers (K0-K7) and XMM16+ are accepted but will produce
// VLT_INVALID since they can't be encoded in debug info.

#if defined(TARGET_WASM)
if (reg == REG_NA)
{
vlType = VLT_INVALID;
return;
}

assert(genIsValidReg(reg));

if (otherReg == REG_NA)
{
vlType = VLT_REG;
vlReg.vlrReg = siWasmDebugRegNum(reg);
}
else
{
assert(genIsValidReg(otherReg));
vlType = VLT_REG_REG;
vlRegReg.vlrrReg1 = siWasmDebugRegNum(reg);
vlRegReg.vlrrReg2 = siWasmDebugRegNum(otherReg);
}
return;
#endif // defined(TARGET_WASM)

if (otherReg == REG_NA)
{
if (genIsValidFloatReg(reg))
Expand Down Expand Up @@ -462,6 +508,12 @@ void CodeGenInterface::siVarLoc::siFillStackVarLoc(
void CodeGenInterface::siVarLoc::siFillRegisterVarLoc(
const LclVarDsc* varDsc, var_types type, regNumber baseReg, int offset, bool isFramePointerUsed)
{
#if defined(TARGET_WASM)
this->vlType = VLT_REG;
this->vlReg.vlrReg = siWasmDebugRegNum(varDsc->GetRegNum());
return;
#endif // defined(TARGET_WASM)

switch (type)
{
case TYP_INT:
Expand Down Expand Up @@ -623,7 +675,11 @@ void CodeGenInterface::dumpSiVarLoc(const siVarLoc* varLoc) const
{
case VLT_REG:
case VLT_REG_BYREF:
#if defined(TARGET_WASM)
printf("$%u", (unsigned)varLoc->vlReg.vlrReg - ICorDebugInfo::WASM_LOCAL_REGNUM_BASE);
#else
printf("%s", getRegName(varLoc->vlReg.vlrReg));
#endif
if (varLoc->vlType == VLT_REG_BYREF)
{
printf(" byref");
Expand Down Expand Up @@ -1589,6 +1645,10 @@ void CodeGen::siInit()
siLastEndOffs = 0;

m_compiler->compResetScopeLists();

#if defined(TARGET_WASM)
siWasmScopesOpened = false;
#endif // defined(TARGET_WASM)
}

/*****************************************************************************
Expand Down Expand Up @@ -1674,11 +1734,35 @@ void CodeGen::siBeginBlock(BasicBlock* block)
void CodeGen::siOpenScopesForNonTrackedVars(const BasicBlock* block, unsigned int lastBlockILEndOffset)
{
#if defined(TARGET_WASM)
// TODO-WASM: Wasm structured control flow
// requirements are incompatible with debug codegen's
// desire to keep blocks in increasing IL offset
// order. Figure out the proper scope manipulations.
//
// Wasm is compiled only ahead of time, where getVars reports no explicit ranges, so every
// scope covers the whole method. Our elimination of irreducible flow does not emit blocks in
// increasing IL offset order, so open all scopes in the first emitted block.
if (m_compiler->opts.OptimizationDisabled() && !siWasmScopesOpened)
{
siWasmScopesOpened = true;

for (unsigned i = 0; i < m_compiler->info.compVarScopesCount; i++)
{
VarScopeDsc* varScope = &m_compiler->info.compVarScopes[i];
assert(varScope->vsdLifeBeg == 0);

LclVarDsc* lclVarDsc = m_compiler->lvaGetDesc(varScope->vsdVarNum);

// Only report locals that were referenced, if we're not doing debug codegen
if (m_compiler->opts.compDbgCode || (lclVarDsc->lvRefCnt() > 0))
{
JITDUMP("Scope info: opening scope, LVnum=%u [%03X..%03X)\n", varScope->vsdLVnum, varScope->vsdLifeBeg,
varScope->vsdLifeEnd);

varLiveKeeper->siStartVariableLiveRange(lclVarDsc, varScope->vsdVarNum);
}
else
{
JITDUMP("Skipping open scope for V%02u, unreferenced\n", varScope->vsdVarNum);
}
}
}
Comment thread
lewing marked this conversation as resolved.

return;
#endif // defined(TARGET_WASM)

Expand Down
Loading
Loading