From cdf64b2e59f6b2640feb5bca7a9ac0c732cc888f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Wed, 14 Oct 2026 22:04:44 +0900 Subject: [PATCH 1/2] Absolute/global addressing changes for native AOT/WASM This is so we can emit proper relocs, not offsets from__image_base. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: c78266f3-08e3-4918-b2e5-9fe0ddf2ca17 --- src/coreclr/jit/codegenwasm.cpp | 31 ++++++++--- src/coreclr/jit/emitwasm.cpp | 54 +++++++++++-------- src/coreclr/jit/lir.h | 6 +-- src/coreclr/jit/lowerwasm.cpp | 21 +++++--- .../WasmRelocatableObjectWriter.cs | 1 + 5 files changed, 76 insertions(+), 37 deletions(-) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 54680df80ecd24..ec3fc6f0a45261 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -2176,11 +2176,18 @@ void CodeGen::genCodeForConstant(GenTree* treeNode) icon = treeNode->AsIntConCommon(); if (icon->IsIconHandle()) { - // Wasm has no absolute-address literals; every handle is materialized as a module-base- - // relative constant and relocated. compReloc is always on for a real AOT compile, so a - // handle only reaches here without needing a reloc under a cross-VM SuperPMI replay. + // Handles need linker relocations for NativeAOT or image-relative relocations for R2R. + // A handle only reaches here without needing a reloc under a cross-VM SuperPMI replay. assert(icon->ImmedValNeedsReloc(m_compiler) || m_compiler->RunningSuperPmiReplay()); - GetEmitter()->emitAddressConstant((void*)icon->IntegralValue()); + if ((treeNode->gtLIRFlags & LIR::Flags::FoldedAddr) != LIR::Flags::None) + { + assert(m_compiler->IsNativeAot()); + GetEmitter()->emitIns_I(INS_i32_const, EA_PTRSIZE, 0); + } + else + { + GetEmitter()->emitAddressConstant((void*)icon->IntegralValue()); + } WasmProduceReg(treeNode); return; } @@ -3150,11 +3157,18 @@ void CodeGen::genCodeForIndir(GenTreeIndir* tree) if (addr->isContained() && !addr->OperIs(GT_LEA)) { - // A contained address constant folds into the memarg offset, so emit just the image base here. + // A contained address constant folds into the memarg offset, so emit just the address base here. // assert(addr->IsIconHandle() && !tree->TypeIs(TYP_SIMD12)); assert(addr->AsIntConCommon()->ImmedValNeedsReloc(m_compiler)); - GetEmitter()->emitImageBase(); + if (m_compiler->IsNativeAot()) + { + GetEmitter()->emitIns_I(INS_i32_const, EA_PTRSIZE, 0); + } + else + { + GetEmitter()->emitImageBase(); + } GetEmitter()->emitIns_MemargAddress(ins_Load(type), emitActualTypeSize(type), (void*)addr->AsIntConCommon()->IntegralValue()); } @@ -3220,6 +3234,11 @@ void CodeGen::genCodeForStoreInd(GenTreeStoreInd* tree) { genStoreIndTypeSimd12(tree); } + else if (addr->IsIconHandle() && ((addr->gtLIRFlags & LIR::Flags::FoldedAddr) != LIR::Flags::None)) + { + GetEmitter()->emitIns_MemargAddress(ins_Store(type), emitActualTypeSize(type), + (void*)addr->AsIntConCommon()->IntegralValue()); + } else { GetEmitter()->emitIns_I(ins_Store(type), emitActualTypeSize(type), offset); diff --git a/src/coreclr/jit/emitwasm.cpp b/src/coreclr/jit/emitwasm.cpp index 805171f2d9e02a..e2788216d58528 100644 --- a/src/coreclr/jit/emitwasm.cpp +++ b/src/coreclr/jit/emitwasm.cpp @@ -198,19 +198,25 @@ bool emitter::emitInsIsStore(instruction ins) // void emitter::emitImageBase() { + assert(!m_compiler->IsNativeAot()); emitIns_I(INS_global_get, EA_HANDLE_CNS_RELOC, (cnsval_ssize_t)(size_t)m_compiler->eeGetWasmWellKnownGlobals()->imageBase); } //------------------------------------------------------------------------ // emitAddressConstant: Emit a memory address constant, like an indirection cell. -// This will automatically make use of relocations and the module base (imageBase). +// NativeAOT uses linker-relocated absolute addresses; R2R adds the module base. void emitter::emitAddressConstant(void* address) { - // Load our module base from the image base global, then load our address constant, then sum them. - emitImageBase(); + if (!m_compiler->IsNativeAot()) + { + emitImageBase(); + } emitIns_I(INS_i32_const_address, EA_SET_FLG(EA_PTRSIZE, EA_CNS_RELOC_FLG), (cnsval_ssize_t)address); - emitIns(INS_i32_add); + if (!m_compiler->IsNativeAot()) + { + emitIns(INS_i32_add); + } } //------------------------------------------------------------------------ @@ -219,12 +225,13 @@ void emitter::emitAddressConstant(void* address) // Arguments: // ins - the load or store instruction // attr - emit attributes -// address - the address constant, relocated relative to the module base +// address - the relocatable address constant // // Notes: -// The module base must already be on the stack; see emitImageBase. This folds the addition that -// emitAddressConstant would otherwise emit into the memarg offset. The memarg offset is always a -// relocation, never a raw address, so the caller must only reach here when relocating. +// The memory-address base (zero for NativeAOT, image base for R2R) must already be on the stack. +// This folds the addition that emitAddressConstant would otherwise emit into the memarg offset. +// The memarg offset is always a relocation, never a raw address, so the caller must only reach here +// when relocating. // void emitter::emitIns_MemargAddress(instruction ins, emitAttr attr, void* address) { @@ -252,9 +259,7 @@ void emitter::emitFuncletAddressConstant(cnsval_ssize_t funcletId) // emitDataOffsetConstant: Emit a constant whose value is the linear-memory // address of an entry in this method's JIT-emitted data (constants) section. // -// The encoding mirrors emitAddressConstant: we load the image base global -// (the module image base), emit a relocated i32.const for the entry's offset, -// then add them. At output time the reloc target is resolved to the host +// The encoding mirrors emitAddressConstant. At output time the reloc target is resolved to the host // pointer of the data section entry, which crossgen2 maps to the per-method // read-only data symbol + offset. // @@ -264,8 +269,10 @@ void emitter::emitFuncletAddressConstant(cnsval_ssize_t funcletId) // void emitter::emitDataOffsetConstant(UNATIVE_OFFSET dataOffs) { - emitIns_I(INS_global_get, EA_HANDLE_CNS_RELOC, - (cnsval_ssize_t)(size_t)m_compiler->eeGetWasmWellKnownGlobals()->imageBase); + if (!m_compiler->IsNativeAot()) + { + emitImageBase(); + } instrDesc* id = emitNewInstrSC(EA_SET_FLG(EA_PTRSIZE, EA_CNS_RELOC_FLG), (cnsval_ssize_t)dataOffs); id->idIns(INS_i32_const_dataoffs); @@ -273,7 +280,10 @@ void emitter::emitDataOffsetConstant(UNATIVE_OFFSET dataOffs) dispIns(id); appendToCurIG(id); - emitIns(INS_i32_add); + if (!m_compiler->IsNativeAot()) + { + emitIns(INS_i32_add); + } } /***************************************************************************** @@ -1007,9 +1017,9 @@ size_t emitter::emitOutputInstr(insGroup* ig, instrDesc* id, BYTE** dp) case IF_MEMADDR: { dst += emitOutputOpcode(dst, ins); - // TODO-WASM: The below reloc we're emitting here is specific to R2R and assumes the address we want - // is an offset from __image_base - dst += emitOutputConstant(dst, id, SIGNED, CorInfoReloc::WASM_MEMORY_ADDR_REL_SLEB); + CorInfoReloc reloc = m_compiler->IsNativeAot() ? CorInfoReloc::WASM_MEMORY_ADDR_SLEB + : CorInfoReloc::WASM_MEMORY_ADDR_REL_SLEB; + dst += emitOutputConstant(dst, id, SIGNED, reloc); break; } case IF_FUNCLETIDX: @@ -1038,7 +1048,9 @@ size_t emitter::emitOutputInstr(insGroup* ig, instrDesc* id, BYTE** dp) dst += emitOutputOpcode(dst, ins); UNATIVE_OFFSET dataOffs = (UNATIVE_OFFSET)emitGetInsSC(id); BYTE* target = emitDataOffsetToPtr(dataOffs); - emitRecordRelocation(dst, target, CorInfoReloc::WASM_MEMORY_ADDR_REL_SLEB); + CorInfoReloc reloc = m_compiler->IsNativeAot() ? CorInfoReloc::WASM_MEMORY_ADDR_SLEB + : CorInfoReloc::WASM_MEMORY_ADDR_REL_SLEB; + emitRecordRelocation(dst, target, reloc); dst += emitOutputPaddedReloc(dst); break; } @@ -1102,9 +1114,9 @@ size_t emitter::emitOutputInstr(insGroup* ig, instrDesc* id, BYTE** dp) assert(align <= UINT32_MAX); // spec says memarg alignment is u32 assert(align < 64); // spec says align > 2^6 produces a memidx for multiple memories. dst += emitOutputULEB128(dst, align); - // TODO-WASM: as with IF_MEMADDR, this reloc is specific to R2R and assumes the address we want is an - // offset from __image_base. - dst += emitOutputConstant(dst, id, UNSIGNED, CorInfoReloc::WASM_MEMORY_ADDR_REL_LEB); + CorInfoReloc reloc = + m_compiler->IsNativeAot() ? CorInfoReloc::WASM_MEMORY_ADDR_LEB : CorInfoReloc::WASM_MEMORY_ADDR_REL_LEB; + dst += emitOutputConstant(dst, id, UNSIGNED, reloc); break; } case IF_LOCAL_DECL: diff --git a/src/coreclr/jit/lir.h b/src/coreclr/jit/lir.h index e01d14bfccf72d..b52dd8554cd63b 100644 --- a/src/coreclr/jit/lir.h +++ b/src/coreclr/jit/lir.h @@ -45,9 +45,9 @@ class LIR final MultiplyUsed = 0x08, // Set by lowering on nodes that the RA should allocate into // a dedicated register (WASM local), for multiple uses. - FoldedAddr = 0x10, // Set by lowering on a GT_LCL_ADDR whose frame offset is folded into - // the memarg of the indirection using it. Codegen for such a node - // emits just the frame pointer. + FoldedAddr = 0x10, // The address offset is folded into its indirection's memarg. + // A GT_LCL_ADDR emits just the frame pointer; a NativeAOT + // global-address constant emits zero. #endif // TARGET_WASM }; }; diff --git a/src/coreclr/jit/lowerwasm.cpp b/src/coreclr/jit/lowerwasm.cpp index 2feb985d1d0435..b0f2b6299bb678 100644 --- a/src/coreclr/jit/lowerwasm.cpp +++ b/src/coreclr/jit/lowerwasm.cpp @@ -580,19 +580,26 @@ void Lowering::ContainCheckIndir(GenTreeIndir* indirNode) // Contain a relocatable address constant so it folds into the load's memarg offset. // - // Codegen for such a constant is "global.get $imageBase; i32.const ; i32.add"; containing it lets - // genCodeForIndir emit just the image base and put the relocated address in the memarg instead. Only loads - // can do this: the parent emits the base in place of the address, and a store cannot because its value - // operand has already been pushed. A multiply-used address is excluded because codegen re-materializes it - // from a register that would no longer hold the full address. + // The load emits the address base and puts the relocated address in the memarg. A store cannot contain + // its address because its value operand has already been pushed. Multiply-used addresses are excluded + // because codegen re-materializes them from registers that must still hold the full address. // - GenTree* addr = indirNode->Addr(); + GenTree* addr = indirNode->Addr(); + bool isNativeFunctionAddress = m_compiler->IsNativeAot() && addr->IsIconHandle(GTF_ICON_FTN_ADDR); if (indirNode->OperIs(GT_IND) && !indirNode->TypeIs(TYP_SIMD12) && addr->IsIconHandle() && - addr->AsIntConCommon()->ImmedValNeedsReloc(m_compiler) && + !isNativeFunctionAddress && addr->AsIntConCommon()->ImmedValNeedsReloc(m_compiler) && ((addr->gtLIRFlags & LIR::Flags::MultiplyUsed) == LIR::Flags::None)) { MakeSrcContained(indirNode, addr); } + else if (m_compiler->IsNativeAot() && indirNode->OperIs(GT_STOREIND) && varTypeIsArithmetic(indirNode->TypeGet()) && + addr->IsIconHandle() && !isNativeFunctionAddress && + addr->AsIntConCommon()->ImmedValNeedsReloc(m_compiler) && + ((addr->gtLIRFlags & LIR::Flags::MultiplyUsed) == LIR::Flags::None)) + { + // Keep the address node to push zero before the value; the store supplies the relocated memarg. + addr->gtLIRFlags |= LIR::Flags::FoldedAddr; + } } //------------------------------------------------------------------------ diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmRelocatableObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmRelocatableObjectWriter.cs index b6c9ab8224433a..068a20f4955fa9 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmRelocatableObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmRelocatableObjectWriter.cs @@ -389,6 +389,7 @@ private unsafe int WriteRelocationEntries(SectionDataEmitter section, int conten RelocType.WASM_TABLE_INDEX_I32 => WasmRelocationType.TableIndexI32, RelocType.WASM_TABLE_INDEX_I64 => WasmRelocationType.TableIndexI64, RelocType.WASM_MEMORY_ADDR_LEB => WasmRelocationType.MemoryAddrLeb, + RelocType.WASM_MEMORY_ADDR_SLEB when isFunction => WasmRelocationType.TableIndexSleb, RelocType.WASM_MEMORY_ADDR_SLEB => WasmRelocationType.MemoryAddrSleb, RelocType.WASM_MEMORY_ADDR_REL_SLEB when isFunction => WasmRelocationType.TableIndexSleb, RelocType.WASM_MEMORY_ADDR_REL_SLEB => WasmRelocationType.MemoryAddrRelSleb, From 0d66b20ada0e4528e901ffb94d9b5608a4b9b769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Sat, 17 Oct 2026 08:02:59 +0900 Subject: [PATCH 2/2] Simplify NativeAOT Wasm global store addressing Materialize relocated absolute addresses directly and use the ordinary zero-offset store path. Keep FoldedAddr limited to local frame addresses. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: c78266f3-08e3-4918-b2e5-9fe0ddf2ca17 --- src/coreclr/jit/codegenwasm.cpp | 15 +-------------- src/coreclr/jit/lir.h | 6 +++--- src/coreclr/jit/lowerwasm.cpp | 8 -------- 3 files changed, 4 insertions(+), 25 deletions(-) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index ec3fc6f0a45261..c03032987e5ddd 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -2179,15 +2179,7 @@ void CodeGen::genCodeForConstant(GenTree* treeNode) // Handles need linker relocations for NativeAOT or image-relative relocations for R2R. // A handle only reaches here without needing a reloc under a cross-VM SuperPMI replay. assert(icon->ImmedValNeedsReloc(m_compiler) || m_compiler->RunningSuperPmiReplay()); - if ((treeNode->gtLIRFlags & LIR::Flags::FoldedAddr) != LIR::Flags::None) - { - assert(m_compiler->IsNativeAot()); - GetEmitter()->emitIns_I(INS_i32_const, EA_PTRSIZE, 0); - } - else - { - GetEmitter()->emitAddressConstant((void*)icon->IntegralValue()); - } + GetEmitter()->emitAddressConstant((void*)icon->IntegralValue()); WasmProduceReg(treeNode); return; } @@ -3234,11 +3226,6 @@ void CodeGen::genCodeForStoreInd(GenTreeStoreInd* tree) { genStoreIndTypeSimd12(tree); } - else if (addr->IsIconHandle() && ((addr->gtLIRFlags & LIR::Flags::FoldedAddr) != LIR::Flags::None)) - { - GetEmitter()->emitIns_MemargAddress(ins_Store(type), emitActualTypeSize(type), - (void*)addr->AsIntConCommon()->IntegralValue()); - } else { GetEmitter()->emitIns_I(ins_Store(type), emitActualTypeSize(type), offset); diff --git a/src/coreclr/jit/lir.h b/src/coreclr/jit/lir.h index b52dd8554cd63b..e01d14bfccf72d 100644 --- a/src/coreclr/jit/lir.h +++ b/src/coreclr/jit/lir.h @@ -45,9 +45,9 @@ class LIR final MultiplyUsed = 0x08, // Set by lowering on nodes that the RA should allocate into // a dedicated register (WASM local), for multiple uses. - FoldedAddr = 0x10, // The address offset is folded into its indirection's memarg. - // A GT_LCL_ADDR emits just the frame pointer; a NativeAOT - // global-address constant emits zero. + FoldedAddr = 0x10, // Set by lowering on a GT_LCL_ADDR whose frame offset is folded into + // the memarg of the indirection using it. Codegen for such a node + // emits just the frame pointer. #endif // TARGET_WASM }; }; diff --git a/src/coreclr/jit/lowerwasm.cpp b/src/coreclr/jit/lowerwasm.cpp index b0f2b6299bb678..a8dffecc1c2001 100644 --- a/src/coreclr/jit/lowerwasm.cpp +++ b/src/coreclr/jit/lowerwasm.cpp @@ -592,14 +592,6 @@ void Lowering::ContainCheckIndir(GenTreeIndir* indirNode) { MakeSrcContained(indirNode, addr); } - else if (m_compiler->IsNativeAot() && indirNode->OperIs(GT_STOREIND) && varTypeIsArithmetic(indirNode->TypeGet()) && - addr->IsIconHandle() && !isNativeFunctionAddress && - addr->AsIntConCommon()->ImmedValNeedsReloc(m_compiler) && - ((addr->gtLIRFlags & LIR::Flags::MultiplyUsed) == LIR::Flags::None)) - { - // Keep the address node to push zero before the value; the store supplies the relocated memarg. - addr->gtLIRFlags |= LIR::Flags::FoldedAddr; - } } //------------------------------------------------------------------------