Repository: dotnet/runtime
File: src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs (C#)

Code before:
            // Code section (10)
            WasmSection codeSection = SectionByName(ObjectNodeSection.WasmCodeSection.Name);
            PrependCount(codeSection, _methodCount);
            codeSection.Emit(outputFileStream);
            // Data section (11) (all data segments combined)
            SectionByName(WasmObjectNodeSection.CombinedDataSection.Name).Emit(outputFileStream);
        }

        private protected override void EmitRelocations(int sectionIndex, List<SymbolicRelocation> relocationList)
        {
            // This is a no-op for now under Wasm
        }

        private WasmImport[] _defaultImports = new[]
        {
            null, // placeholder for memory, which is set up dynamically in WriteImports()
            new WasmImport("env", "__stack_pointer", WasmExternalKind.Global, new WasmGlobalType(WasmValueType.I32, WasmMutabilityType.Mut)),
            new WasmImport("env", "__r2r_start", WasmExternalKind.Global, new WasmGlobalType(WasmValueType.I32, WasmMutabilityType.Const)),
        };

        private void WriteImports()
        {
            // Calculate the required memory size based on the combined data section size
            ulong contentSize = (ulong)SectionByName(WasmObjectNodeSection.CombinedDataSection.Name).ContentSize;
            ulong numPages = (contentSize + (1<<16) - 1) >> 16;

            _defaultImports[0] = new WasmImport("env", "memory", WasmExternalKind.Memory,
                new WasmMemoryType(0x00, (uint)numPages)); // memory limits: flags (0 = only minimum)

            foreach (WasmImport import in _defaultImports)
            {
                WriteImport(import);
            }
        }

        private void WriteExports()
        {
            WriteTableExport("table", 0);
            string[] functionExports = _uniqueSymbols.Keys.ToArray();
            // TODO-WASM: Handle exports better (e.g., only export public methods, etc.)
            // Also, see if we could leverage definedSymbols for this instead of doing our own bookkeeping in _uniqueSymbols.
            foreach (string name in functionExports.OrderBy(name => name))
            {
                WriteFunctionExport(name, _uniqueSymbols[name]);
            }
        }

        // For now, this function just prepares the function, exports, and type sections for emission by prepending the counts.
        private protected override void EmitSymbolTable(IDictionary<Utf8String, SymbolDefinition> definedSymbols, SortedSet<Utf8String> undefinedSymbols)
        {
            WriteImports();

Review comment (suggestion):
`numPages` is computed only from the combined data section’s encoded size. This can (a) produce a 0-page minimum when there are no data segments, and (b) ignores any non-zero base placement such as the previous `DataStartOffset` (and now potentially `__r2r_start`). Since the import’s minimum is the module’s only enforcement on the host-provided memory size, consider clamping to at least 1 page and/or adding a fixed base reservation (stack / `__r2r_start` placement) so the requested minimum can’t be smaller than the runtime’s expected address range.

Code after:
            // Export section (7)
            SectionByName(WasmObjectNodeSection.ExportSection.Name).Emit(outputFileStream);
            // Code section (10)
            WasmSection codeSection = SectionByName(ObjectNodeSection.WasmCodeSection.Name);
            PrependCount(codeSection, _methodCount);
            codeSection.Emit(outputFileStream);
            // Data section (11) (all data segments combined)
            SectionByName(WasmObjectNodeSection.CombinedDataSection.Name).Emit(outputFileStream);
        }

        private protected override void EmitRelocations(int sectionIndex, List<SymbolicRelocation> relocationList)
        {
            // This is a no-op for now under Wasm
        }

        const int StackPointerGlobalIndex = 0;
        const int R2RStartGlobalIndex = 1;

        private WasmImport[] _defaultImports = new[]
        {
            null, // placeholder for memory, which is set up dynamically in WriteImports()
            new WasmImport("env", "__stack_pointer", import: new WasmGlobalImportType(WasmValueType.I32, WasmMutabilityType.Mut), index: StackPointerGlobalIndex),
            new WasmImport("env", "__r2r_start", import: new WasmGlobalImportType(WasmValueType.I32, WasmMutabilityType.Const), index: R2RStartGlobalIndex),
        };

        private void WriteImports()
        {
            // Calculate the minimum required memory size based on the combined data section size
            ulong contentSize = (ulong)SectionByName(WasmObjectNodeSection.CombinedDataSection.Name).ContentSize;
            uint dataPages = checked((uint)((contentSize + (1<<16) - 1) >> 16));
            uint numPages = Math.Max(dataPages, 1); // Ensure at least one page is allocated for the minimum

            _defaultImports[0] = new WasmImport("env", "memory", import: new WasmMemoryImportType(WasmLimitType.HasMin, numPages)); // memory limits: flags (0 = only minimum)

            int[] assignedImportIndices = new int[(int)WasmExternalKind.Count];
            foreach (WasmImport import in _defaultImports)
            {
                if (import.Index.HasValue)
                {
                    int assigned = assignedImportIndices[(int)import.Kind];
                    if (assigned != import.Index.Value)
                    {
                        throw new InvalidOperationException($"Import {import.Module}.{import.Name} of kind {import.Kind} assigned index {assigned}, needs {import.Index.Value}");
                    }
                }
                assignedImportIndices[(int)import.Kind]++;
                WriteImport(import);
            }
        }

        private void WriteExports()