From 662835a88f6f47d5b1451b453046ae80b4b0fc62 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 25 Jan 2024 13:23:45 -0800 Subject: RemoveUnusedModuleElements: Do not remove unused-but-trapping segments (#6242) An out of bounds active segment traps during startup, which is an effect we must preserve. To avoid a regression here, ignore this in TNH mode (where the user assures us nothing will trap), and also check if a segment will trivially be in bounds and not trap (if so, it can be removed). Fixes the remove-unused-module-elements part of #6230 The small change to an existing testcase made a segment there be in bounds, to avoid this affecting it. Tests for this are in a new file. --- src/passes/RemoveUnusedModuleElements.cpp | 53 +++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/passes/RemoveUnusedModuleElements.cpp b/src/passes/RemoveUnusedModuleElements.cpp index 6ceab0132..b3874b8dc 100644 --- a/src/passes/RemoveUnusedModuleElements.cpp +++ b/src/passes/RemoveUnusedModuleElements.cpp @@ -45,6 +45,7 @@ #include "ir/subtypes.h" #include "ir/utils.h" #include "pass.h" +#include "support/stdckdint.h" #include "wasm-builder.h" #include "wasm.h" @@ -635,17 +636,57 @@ struct RemoveUnusedModuleElements : public Pass { // Active segments that write to imported tables and memories are roots // because those writes are externally observable even if the module does // not otherwise use the tables or memories. + // + // Likewise, if traps are possible during startup then just trapping is an + // effect (which can happen if the offset is out of bounds). + auto maybeRootSegment = [&](ModuleElementKind kind, + Name segmentName, + Index segmentSize, + Expression* offset, + Importable* parent, + Index parentSize) { + auto writesToVisible = parent->imported() && segmentSize; + auto mayTrap = false; + if (!getPassOptions().trapsNeverHappen) { + // Check if this might trap. If it is obviously in bounds then it + // cannot. + auto* c = offset->dynCast(); + // Check for overflow in the largest possible space of addresses. + using AddressType = Address::address64_t; + AddressType maxWritten; + // If there is no integer, or if there is and the addition overflows, or + // if the addition leads to a too-large value, then we may trap. + mayTrap = !c || + std::ckd_add(&maxWritten, + (AddressType)segmentSize, + (AddressType)c->value.getInteger()) || + maxWritten > parentSize; + } + if (writesToVisible || mayTrap) { + roots.emplace_back(kind, segmentName); + } + }; ModuleUtils::iterActiveDataSegments(*module, [&](DataSegment* segment) { - if (module->getMemory(segment->memory)->imported() && - !segment->data.empty()) { - roots.emplace_back(ModuleElementKind::DataSegment, segment->name); + if (segment->memory.is()) { + auto* memory = module->getMemory(segment->memory); + maybeRootSegment(ModuleElementKind::DataSegment, + segment->name, + segment->data.size(), + segment->offset, + memory, + memory->initial * Memory::kPageSize); } }); ModuleUtils::iterActiveElementSegments( *module, [&](ElementSegment* segment) { - if (module->getTable(segment->table)->imported() && - !segment->data.empty()) { - roots.emplace_back(ModuleElementKind::ElementSegment, segment->name); + if (segment->table.is()) { + auto* table = module->getTable(segment->table); + maybeRootSegment(ModuleElementKind::ElementSegment, + segment->name, + segment->data.size(), + segment->offset, + table, + table->initial * Table::kPageSize); } }); -- cgit v1.2.3