File: parent\parent\Common\Compiler\ObjectWriter\WasmRelocatableObjectWriter.cs
Web Access
Project: ILCompiler.Compiler.csproj (ILCompiler.Compiler)
// 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.Diagnostics;
using System.Collections.Generic;
using System.IO;
using System.Numerics;
using ILCompiler.DependencyAnalysis;
using Internal.JitInterface;
using Internal.Text;
using Internal.TypeSystem.TypesDebugInfo;
using ILCompiler.DependencyAnalysis.Wasm;
using System.Linq;

namespace ILCompiler.ObjectWriter
{
    internal sealed partial class WasmRelocatableObjectWriter : WasmObjectWriter
    {
        private WasmDataSection _dataSection;
        private DwarfBuilder _dwarfBuilder;
        private uint _nextVirtualIP = 1; // Zero marks an invalid stack frame.
        private readonly Dictionary<Utf8String, uint> _methodStartVirtualIPs = new();

        // https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md
        private enum SymbolKind : byte
        {
            Function = 0, // SYMTAB_FUNCTION
            Data = 1, // SYMTAB_DATA
            Global = 2, // SYMTAB_GLOBAL
            Section = 3, // SYMTAB_SECTION
            Tag = 4, // SYMTAB_EVENT
            Table = 5, // SYMTAB_TABLE
        }

        private const uint UndefinedSymbol = 0x10; // WASM_SYM_UNDEFINED
        private const uint LocalSymbol = 0x2; // WASM_SYM_BINDING_LOCAL
        private const uint ExportedSymbol = 0x20; // WASM_SYM_EXPORTED
        private const uint ExplicitNameSymbol = 0x40; // WASM_SYM_EXPLICIT_NAME
        private const byte SegmentInfoSubsection = 5; // WASM_SEGMENT_INFO
        private const byte SymbolTableSubsection = 8; // WASM_SYMBOL_TABLE
        private const uint LinkingVersion = 2;

        private enum WasmRelocationType : byte
        {
            FunctionIndexLeb = 0, // R_WASM_FUNCTION_INDEX_LEB
            TableIndexSleb = 1, // R_WASM_TABLE_INDEX_SLEB
            TableIndexI32 = 2, // R_WASM_TABLE_INDEX_I32
            MemoryAddrLeb = 3, // R_WASM_MEMORY_ADDR_LEB
            MemoryAddrSleb = 4, // R_WASM_MEMORY_ADDR_SLEB
            MemoryAddrI32 = 5, // R_WASM_MEMORY_ADDR_I32
            TypeIndexLeb = 6, // R_WASM_TYPE_INDEX_LEB
            GlobalIndexLeb = 7, // R_WASM_GLOBAL_INDEX_LEB
            FunctionOffsetI32 = 8, // R_WASM_FUNCTION_OFFSET_I32
            SectionOffsetI32 = 9, // R_WASM_SECTION_OFFSET_I32
            MemoryAddrRelSleb = 11, // R_WASM_MEMORY_ADDR_REL_SLEB
            MemoryAddrI64 = 16, // R_WASM_MEMORY_ADDR_I64
            TableIndexI64 = 19, // R_WASM_TABLE_INDEX_I64
            MemoryAddrLocrelI32 = 23, // R_WASM_MEMORY_ADDR_LOCREL_I32
        }

        private readonly Dictionary<Utf8String, int> _linkingSymbolIndices = new();
        private readonly Dictionary<int, List<SymbolicRelocation>> _relocations = new();
        private readonly Dictionary<int, long> _functionOffsets = new();

        public WasmRelocatableObjectWriter(NodeFactory factory, ObjectWritingOptions options, OutputInfoBuilder outputInfoBuilder = null) : base(factory, options, outputInfoBuilder)
        {
        }

        private protected override ObjectNodeSection GetEmitSection(ObjectNodeSection section)
        {
            if (section == ObjectNodeSection.TextSection ||
                section == ObjectNodeSection.ManagedCodeUnixContentSection ||
                section == ObjectNodeSection.ManagedCodeWindowsContentSection)
            {
                return ObjectNodeSection.WasmCodeSection;
            }

            return section;
        }

        private protected override void EmitSectionsAndLayout()
        {
            List<IWasmDataSegment> dataSegments = new();
            foreach (SectionDataEmitter section in _sections.Sections)
            {
                if (section is WasmDataSegmentEmitter dataSegment)
                {
                    dataSegments.Add(dataSegment);
                }
            }

            if (dataSegments.Count == 0)
            {
                return;
            }

            SectionWriter writer = GetOrCreateSection(WasmObjectNodeSection.DataCountSection);
            writer.WriteULEB128((ulong)dataSegments.Count);
            _dataSection = new WasmDataSection(dataSegments, new Utf8String("data"));
        }

        private protected override void EmitObjectFile(Stream outputFileStream)
        {
            Debug.Assert(outputFileStream.CanSeek, $"EmitObjectFile requires seekable output stream");

            FinalizeSectionEntryCounts();
            _dataSection?.AssignSegmentLayout();

            List<IWasmSection> sections = new();
            foreach (int index in SectionEmitOrder)
            {
                if (_sections[index] is IWasmSection section)
                {
                    sections.Add(section);
                }
            }

            int dataSectionIndex = sections.Count;
            if (_dataSection is not null)
            {
                sections.Add(_dataSection);
            }

            foreach (SectionDataEmitter section in _sections.Sections)
            {
                if (section is WasmCustomSection customSection)
                {
                    sections.Add(customSection);
                }
            }

            Dictionary<int, int> fileSectionIndices = new();
            for (int i = 0; i < sections.Count; i++)
            {
                if (sections[i] is SectionDataEmitter section)
                {
                    fileSectionIndices.Add(section.SectionIndex, i);
                }
            }

            using MemoryStream linking = CreateLinkingSection(fileSectionIndices);
            sections.Add(new WasmCustomSection(linking, new Utf8String("linking"), sections.Count));
            Dictionary<int, (string Name, MemoryStream Entries, int Count)> relocationSections = new();
            try
            {
                foreach ((int sectionIndex, List<SymbolicRelocation> relocations) in _relocations)
                {
                    SectionDataEmitter section = _sections[sectionIndex];
                    (int contentOffset, int targetSectionIndex, string name) = section switch
                    {
                        WasmCustomSection customSection => (
                            0,
                            fileSectionIndices[sectionIndex],
                            $"reloc.{customSection.SectionName}"),
                        WasmSection wasmSection => (
                            wasmSection.ContentPrefixSize,
                            fileSectionIndices[sectionIndex],
                            "reloc.CODE"),
                        WasmDataSegmentEmitter segment => (
                            _dataSection.GetSegmentContentOffset(segment),
                            dataSectionIndex,
                            "reloc.DATA"),
                        _ => throw new UnreachableException(),
                    };
                    if (!relocationSections.TryGetValue(targetSectionIndex, out var relocationSection))
                    {
                        relocationSection = (name, new MemoryStream(), 0);
                        relocationSections.Add(targetSectionIndex, relocationSection);
                    }
                    int relocationCount = WriteRelocationEntries(section, contentOffset, relocations, relocationSection.Entries);
                    relocationSections[targetSectionIndex] = (relocationSection.Name, relocationSection.Entries, relocationSection.Count + relocationCount);
                }

                foreach ((int targetSectionIndex, var relocationSection) in relocationSections)
                {
                    if (relocationSection.Count == 0)
                    {
                        continue;
                    }

                    sections.Add(new WasmRelocationSection(
                        relocationSection.Entries,
                        new Utf8String(relocationSection.Name),
                        sections.Count,
                        targetSectionIndex,
                        relocationSection.Count));
                }

                EmitWasmHeader(outputFileStream);
                foreach (IWasmSection section in sections)
                {
                    section.EmitToStream(outputFileStream);
                }
            }
            finally
            {
                foreach (var relocationSection in relocationSections.Values)
                {
                    relocationSection.Entries.Dispose();
                }
            }
        }

        private MemoryStream CreateLinkingSection(Dictionary<int, int> fileSectionIndices)
        {
            MemoryStream linking = new();
            WriteULEB128(linking, LinkingVersion); // version: the version of linking metadata contained in this section.
            using MemoryStream symbols = new();
            using MemoryStream entries = new();

            foreach (WasmIndexSpace indexSpace in new[] { WasmIndexSpace.Function, WasmIndexSpace.Global, WasmIndexSpace.Table, WasmIndexSpace.Tag })
            {
                foreach (WasmSymbol symbol in _wasmSymbolManager.GetDefinitions(indexSpace))
                {
                    WriteIndexedSymbol(entries, symbol);
                }
            }

            foreach ((Utf8String name, SymbolDefinition definition) in _definedSymbols)
            {
                if (_wasmSymbolManager.TryGetSymbol(name, out WasmSymbol symbol) && symbol.IndexSpace == WasmIndexSpace.Function)
                {
                    if (!_linkingSymbolIndices.ContainsKey(name))
                    {
                        WriteIndexedSymbol(entries, symbol);
                    }
                    continue;
                }

                if (_sections[definition.SectionIndex] is not WasmDataSegmentEmitter segment)
                {
                    continue;
                }

                AddSymbolIndex(name);
                entries.WriteByte((byte)SymbolKind.Data); // kind: the symbol type (SYMTAB_DATA).
                WriteULEB128(entries, 0); // flags: a bitfield containing flags for this symbol.
                WriteName(entries, name); // name_len/name_data: the UTF-8 symbol name.
                WriteULEB128(entries, (ulong)_dataSection.GetSegmentIndex(segment)); // index: the index of the data segment.
                WriteULEB128(entries, checked((ulong)definition.Value)); // offset: the offset within the segment.
                WriteULEB128(entries, (ulong)definition.Size); // size: the size of the symbol.
            }

            foreach (Utf8String name in GetUndefinedSymbols())
            {
                if (_linkingSymbolIndices.ContainsKey(name))
                {
                    continue;
                }

                AddSymbolIndex(name);
                entries.WriteByte((byte)SymbolKind.Data); // kind: the symbol type (SYMTAB_DATA).
                WriteULEB128(entries, UndefinedSymbol); // flags: a bitfield containing flags for this symbol.
                WriteName(entries, name); // name_len/name_data: the UTF-8 symbol name.
            }

            foreach (SectionDataEmitter section in _sections.Sections)
            {
                if (section is WasmCustomSection)
                {
                    AddSymbolIndex(section.SectionName);
                    entries.WriteByte((byte)SymbolKind.Section);
                    WriteULEB128(entries, LocalSymbol);
                    WriteULEB128(entries, (ulong)fileSectionIndices[section.SectionIndex]);
                }
            }

            WriteULEB128(symbols, (ulong)_linkingSymbolIndices.Count); // count: number of syminfo entries in infos.
            entries.Position = 0;
            entries.CopyTo(symbols); // infos: sequence of syminfo entries.
            WriteSubsection(linking, SymbolTableSubsection, symbols);

            if (_dataSection is not null)
            {
                using MemoryStream segments = new();
                WriteULEB128(segments, (ulong)_dataSection.SegmentCount); // count: number of segment entries in segments.
                foreach (WasmDataSegmentEmitter segment in _dataSection.Segments)
                {
                    WriteName(segments, segment.SectionName); // name_len/name_data: the UTF-8 segment name.
                    WriteULEB128(segments, (ulong)BitOperations.Log2((uint)segment.MemoryAlignment)); // alignment: required segment alignment, encoded as a power of 2.
                    WriteULEB128(segments, 0); // flags: a bitfield containing flags for this segment.
                }
                WriteSubsection(linking, SegmentInfoSubsection, segments);
            }

            return linking;
        }

        private void AddSymbolIndex(Utf8String name) =>
            _linkingSymbolIndices.Add(name, _linkingSymbolIndices.Count);

        private void WriteIndexedSymbol(Stream entries, WasmSymbol symbol)
        {
            AddSymbolIndex(symbol.Name);
            SymbolKind kind = symbol.IndexSpace switch
            {
                WasmIndexSpace.Function => SymbolKind.Function,
                WasmIndexSpace.Global => SymbolKind.Global,
                WasmIndexSpace.Table => SymbolKind.Table,
                WasmIndexSpace.Tag => SymbolKind.Tag,
                _ => throw new UnreachableException(),
            };
            uint flags = symbol.IsImport ? UndefinedSymbol : 0;
            if (symbol.HasExplicitName)
            {
                flags |= ExplicitNameSymbol;
            }
            if (!symbol.IsImport && _definedSymbols.TryGetValue(symbol.Name, out SymbolDefinition definition) && definition.Global)
            {
                flags |= ExportedSymbol;
            }
            entries.WriteByte((byte)kind); // kind: the symbol type.
            WriteULEB128(entries, flags); // flags: a bitfield containing flags for this symbol.
            WriteULEB128(entries, (ulong)symbol.Index); // index: the index of the Wasm object corresponding to the symbol.
            if (!symbol.IsImport || symbol.HasExplicitName)
            {
                WriteName(entries, symbol.Name); // name_len/name_data: the UTF-8 symbol name, omitted for imports without an explicit name.
            }
        }

        private static void WriteULEB128(Stream stream, ulong value)
        {
            Span<byte> buffer = stackalloc byte[10];
            int size = DwarfHelper.WriteULEB128(buffer, value);
            stream.Write(buffer.Slice(0, size));
        }

        private static void WriteSLEB128(Stream stream, long value)
        {
            Span<byte> buffer = stackalloc byte[10];
            int size = DwarfHelper.WriteSLEB128(buffer, value);
            stream.Write(buffer.Slice(0, size));
        }

        private static void WriteName(Stream stream, Utf8String name)
        {
            WriteULEB128(stream, (ulong)name.Length); // name_len: the length of name_data in bytes.
            stream.Write(name.AsSpan()); // name_data: UTF-8 encoding of the name.
        }

        private static void WriteSubsection(Stream stream, byte kind, MemoryStream payload)
        {
            stream.WriteByte(kind); // type: code identifying the type of subsection.
            WriteULEB128(stream, (ulong)payload.Length); // payload_len: size of this subsection in bytes.
            payload.Position = 0;
            payload.CopyTo(stream); // payload_data: content of this subsection, of length payload_len.
        }

        private protected override void EmitRelocations(int sectionIndex, List<SymbolicRelocation> relocationList)
        {
            if (relocationList.Count > 0)
            {
                _relocations.Add(sectionIndex, relocationList);
            }
        }

        private unsafe int WriteRelocationEntries(SectionDataEmitter section, int contentOffset, List<SymbolicRelocation> relocs, MemoryStream payload)
        {
            using Stream originalStream = section.ContentReadStream;
            MemoryStream sectionStream = new((int)originalStream.Length);
            originalStream.Position = 0;
            originalStream.CopyTo(sectionStream);
            byte[] relocScratchBuffer = new byte[Relocation.MaxSize];
            int relocationCount = 0;
            WasmSection codeSection = _sections.GetSection<WasmSection>(ObjectNodeSection.WasmCodeSection.Name);
            int codeSectionPrefixSize = codeSection.ContentPrefixSize;

            foreach (SymbolicRelocation reloc in relocs)
            {
                int size = Relocation.GetSize(reloc.Type);

                sectionStream.Position = reloc.Offset;
                sectionStream.ReadExactly(relocScratchBuffer.AsSpan(0, size));
                fixed (byte* pData = relocScratchBuffer)
                {
                    long addend = reloc.Addend + Relocation.ReadValue(reloc.Type, pData);
                    if (reloc.Type is RelocType.WASM_METHOD_FRAME_IDENTITY_SLEB)
                    {
                        Debug.Assert(addend == 0);
                        // Check that the method start fits alongside the frame-identity tag, then encode its i32 bits.
                        addend = unchecked((int)checked(_methodStartVirtualIPs[reloc.SymbolName] * 2 + 1));
                        Relocation.WriteValue(reloc.Type, pData, addend);
                        sectionStream.Position = reloc.Offset;
                        sectionStream.Write(relocScratchBuffer.AsSpan(0, size));
                        continue;
                    }

                    bool isFunction = _wasmSymbolManager.TryGetSymbol(reloc.SymbolName, out WasmSymbol symbol) &&
                        symbol.IndexSpace == WasmIndexSpace.Function;
                    bool isSection = _definedSymbols.TryGetValue(reloc.SymbolName, out SymbolDefinition definition) &&
                        _sections[definition.SectionIndex] is WasmCustomSection;
                    WasmRelocationType type = reloc.Type switch
                    {
                        RelocType.WASM_FUNCTION_INDEX_LEB => WasmRelocationType.FunctionIndexLeb,
                        RelocType.WASM_TYPE_INDEX_LEB => WasmRelocationType.TypeIndexLeb,
                        RelocType.WASM_GLOBAL_INDEX_LEB => WasmRelocationType.GlobalIndexLeb,
                        RelocType.WASM_TABLE_INDEX_SLEB => WasmRelocationType.TableIndexSleb,
                        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,
                        RelocType.IMAGE_REL_BASED_HIGHLOW when isFunction && section is WasmCustomSection => WasmRelocationType.FunctionOffsetI32,
                        RelocType.IMAGE_REL_BASED_HIGHLOW when isSection => WasmRelocationType.SectionOffsetI32,
                        RelocType.IMAGE_REL_BASED_HIGHLOW when isFunction => WasmRelocationType.TableIndexI32,
                        RelocType.IMAGE_REL_BASED_HIGHLOW => WasmRelocationType.MemoryAddrI32,
                        RelocType.IMAGE_REL_BASED_DIR64 => WasmRelocationType.MemoryAddrI64,
                        RelocType.IMAGE_REL_BASED_RELPTR32 => WasmRelocationType.MemoryAddrLocrelI32,
                        _ => throw new NotSupportedException($"Relocation type {reloc.Type} for symbol '{reloc.SymbolName}' in section {section.SectionName} not yet implemented"),
                    };
                    Utf8String symbolName = reloc.SymbolName;
                    if (isFunction && type != WasmRelocationType.FunctionOffsetI32 && addend != 0)
                    {
                        // The JIT encodes funclet references as parent + ordinal, but the linker can reorder table entries.
                        Utf8String funcletName = GetFuncletName(symbolName, checked((int)addend - 1));
                        symbol = _wasmSymbolManager.GetSymbol(funcletName);
                        Debug.Assert(symbol.IndexSpace == WasmIndexSpace.Function);
                        symbolName = funcletName;
                        addend = 0;
                    }
                    bool hasAddend = type is WasmRelocationType.MemoryAddrLeb or WasmRelocationType.MemoryAddrSleb or
                        WasmRelocationType.MemoryAddrI32 or WasmRelocationType.MemoryAddrI64 or
                        WasmRelocationType.FunctionOffsetI32 or WasmRelocationType.SectionOffsetI32 or
                        WasmRelocationType.MemoryAddrRelSleb or WasmRelocationType.MemoryAddrLocrelI32;
                    if (!hasAddend && addend != 0)
                    {
                        throw new NotSupportedException($"Nonzero addend for {reloc.Type} relocation to '{reloc.SymbolName}'");
                    }

                    relocationCount++;
                    payload.WriteByte((byte)type); // type: the relocation type.
                    WriteULEB128(payload, checked((ulong)(contentOffset + reloc.Offset))); // offset: offset of the value to rewrite, relative to the section's contents.
                    // index: the symbol index, or the type index for R_WASM_TYPE_INDEX_LEB.
                    WriteULEB128(payload, (ulong)(type == WasmRelocationType.TypeIndexLeb
                        ? symbol.Index : _linkingSymbolIndices[symbolName]));
                    if (hasAddend)
                    {
                        WriteSLEB128(payload, addend); // addend: addend to add to the address.
                    }

                    long value = type switch
                    {
                        WasmRelocationType.FunctionIndexLeb or WasmRelocationType.TypeIndexLeb or WasmRelocationType.GlobalIndexLeb => symbol.Index,
                        WasmRelocationType.FunctionOffsetI32 => codeSectionPrefixSize + _functionOffsets[symbol.Index] + addend,
                        WasmRelocationType.SectionOffsetI32 => addend,
                        _ => 0,
                    };
                    Relocation.WriteValue(reloc.Type, pData, value);
                    sectionStream.Position = reloc.Offset;
                    sectionStream.Write(relocScratchBuffer.AsSpan(0, size));
                }
            }

            section.ContentReadStream = sectionStream;
            return relocationCount;
        }

        // COMDAT groups are not supported yet.
        private protected override bool UsesSubsectionsViaSymbols => true;

        private protected override SectionDataEmitter CreateDataSection(
            ObjectNodeSection section,
            int sectionIndex,
            Stream sectionStream)
        {
            return new WasmDataSegmentEmitter(
                sectionStream,
                new Utf8String(section.Name),
                sectionIndex);
        }

        protected internal override void UpdateSectionAlignment(int sectionIndex, int alignment)
        {
            if (_sections[sectionIndex] is WasmDataSegmentEmitter dataSegment)
            {
                dataSegment.UpdateAlignment(alignment);
            }
        }
        private protected override void WriteGlobalSection()
        {
        }

        private protected override void WriteImports()
        {
            WriteImport(new WasmImport("env", WasmWellKnownGlobalSymbolNode.StackPointerName, import: new WasmGlobalImportType(WasmValueType.I32, WasmMutabilityType.Mut)));
            WriteImport(new WasmImport("env", WasmWellKnownGlobalSymbolNode.AsyncContinuationName, import: new WasmGlobalImportType(WasmValueType.I32, WasmMutabilityType.Mut)));
            WriteImport(new WasmImport("env", "__indirect_function_table", new WasmTableImportType()));
            WriteImport(new WasmImport("env", "memory", new WasmMemoryImportType(WasmLimitType.HasMin, 0)));
        }

        private protected override void WriteExports()
        {
            WasmSection codeSection = GetOrCreateSection<WasmSection>(ObjectNodeSection.WasmCodeSection, out _);
            int codeSectionIndex = codeSection.SectionIndex;
            foreach (var symbol in _definedSymbols)
            {
                // We only export methods for now
                if (!symbol.Value.Global || symbol.Value.SectionIndex != codeSectionIndex)
                    continue;

                WasmSymbol methodEntry = _wasmSymbolManager.GetSymbol(symbol.Key);
                Debug.Assert(methodEntry.IndexSpace == WasmIndexSpace.Function);
                WriteFunctionExport(symbol.Key.ToString(), methodEntry.Index);
            }
        }

        private protected override void WriteElements()
        {
        }

        private protected override ITypesDebugInfoWriter CreateDebugInfoBuilder()
        {
            return _dwarfBuilder = new DwarfBuilder(
                _nodeFactory.NameMangler,
                _nodeFactory.Target,
                _options.HasFlag(ObjectWritingOptions.UseDwarf5));
        }

        private protected override void EmitDebugFunctionInfo(
            uint methodTypeIndex,
            Utf8String methodDisplayName,
            Utf8String methodName,
            SymbolDefinition methodSymbol,
            INodeWithDebugInfo debugNode)
        {
            EmitDebugMethodInfo(methodTypeIndex, methodName, methodSymbol, debugNode);
        }

        private protected override void EmitDebugThunkInfo(
            Utf8String methodName,
            SymbolDefinition methodSymbol,
            INodeWithDebugInfo debugNode)
        {
            EmitDebugMethodInfo(0, methodName, methodSymbol, debugNode);
        }

        private void EmitDebugMethodInfo(
            uint methodTypeIndex,
            Utf8String methodName,
            SymbolDefinition methodSymbol,
            INodeWithDebugInfo debugNode)
        {
            // RyuJIT includes a five-byte padded body-size field in each function's frame extent.
            const int FunctionBodySizePrefixSize = 5;
            FrameInfo[] frameInfos = ((INodeWithCodeInfo)debugNode).FrameInfos;
            List<NativeSequencePoint> sequencePoints = new(debugNode.GetNativeSequencePoints());
            int sequencePointIndex = 0;
            for (int functionOrdinal = 0; functionOrdinal < frameInfos.Length; functionOrdinal++)
            {
                FrameInfo frameInfo = frameInfos[functionOrdinal];
                int bodyStart = checked(frameInfo.StartOffset + FunctionBodySizePrefixSize);
                int bodyEnd = frameInfo.EndOffset;
                int bodySize = bodyEnd - bodyStart;
                Debug.Assert(bodySize > 0);
                Utf8String functionName = functionOrdinal == 0 ? methodName : GetFuncletName(methodName, functionOrdinal - 1);
                int functionIndex = _wasmSymbolManager.GetSymbol(functionName).Index;
                // Folded methods share the same function index and body offset.
                _functionOffsets[functionIndex] = checked(methodSymbol.Value + bodyStart);
                _dwarfBuilder.EmitSectionInfo(functionName, (ulong)bodySize);
                // Wasm variable locations are not described yet.
                _dwarfBuilder.EmitSubprogramInfo(methodName, functionName, 0, bodySize, methodTypeIndex, [], []);

                List<NativeSequencePoint> functionPoints = new();
                while (sequencePointIndex < sequencePoints.Count && sequencePoints[sequencePointIndex].NativeOffset < bodyEnd)
                {
                    NativeSequencePoint point = sequencePoints[sequencePointIndex++];
                    Debug.Assert(point.NativeOffset >= frameInfo.StartOffset);
                    // JIT offsets include the body length; DWARF function offsets start at the locals vector.
                    functionPoints.Add(new NativeSequencePoint(
                        Math.Max(0, point.NativeOffset - bodyStart),
                        point.FileName,
                        point.LineNumber,
                        point.ColNumber));
                }
                if (functionPoints.Count > 0)
                {
                    _dwarfBuilder.EmitLineInfo(
                        functionIndex,
                        functionName,
                        0,
                        functionPoints,
                        bodySize);
                }
            }
        }

        private protected override void EmitDebugSections(IDictionary<Utf8String, SymbolDefinition> definedSymbols)
        {
            SectionWriter info = GetOrCreateSection(new ObjectNodeSection(".debug_info", SectionType.Debug));
            SectionWriter strings = GetOrCreateSection(new ObjectNodeSection(".debug_str", SectionType.Debug));
            SectionWriter abbrev = GetOrCreateSection(new ObjectNodeSection(".debug_abbrev", SectionType.Debug));
            SectionWriter loc = GetOrCreateSection(new ObjectNodeSection(".debug_loc", SectionType.Debug));
            SectionWriter ranges = GetOrCreateSection(new ObjectNodeSection(".debug_ranges", SectionType.Debug));
            SectionWriter lines = GetOrCreateSection(new ObjectNodeSection(".debug_line", SectionType.Debug));
            SectionWriter aranges = GetOrCreateSection(new ObjectNodeSection(".debug_aranges", SectionType.Debug));
            _dwarfBuilder.Write(info, strings, abbrev, loc, ranges, lines, aranges, _ => (default, 0));
        }

        private protected override void EmitUnwindInfo(
            SectionWriter sectionWriter,
            INodeWithCodeInfo nodeWithCodeInfo,
            Utf8String currentSymbolName)
        {
            if (nodeWithCodeInfo.FrameInfos is not FrameInfo[] frames || frames.Length == 0)
            {
                return;
            }

            _methodStartVirtualIPs.Add(currentSymbolName, _nextVirtualIP);
            for (int i = 0; i < frames.Length; i++)
            {
                FrameInfo frame = frames[i];
                DwarfHelper.ReadULEB128(frame.BlobData, out int bytesRead);
                uint virtualSize = checked((uint)DwarfHelper.ReadULEB128(frame.BlobData.AsSpan(bytesRead)));
                Debug.Assert(virtualSize != 0);
                _nextVirtualIP = checked(_nextVirtualIP + virtualSize);
            }
        }

        private protected override void CreateEhSections()
        {
        }
    }
}