WasmMemory.h (10904B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * 4 * Copyright 2021 Mozilla Foundation 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 #ifndef wasm_memory_h 20 #define wasm_memory_h 21 22 #include "mozilla/CheckedInt.h" 23 #include "mozilla/Maybe.h" 24 25 #include <compare> // std::strong_ordering 26 #include <stdint.h> 27 28 #include "js/Value.h" 29 #include "vm/NativeObject.h" 30 #include "wasm/WasmConstants.h" 31 #include "wasm/WasmValType.h" 32 33 namespace js { 34 namespace wasm { 35 36 // Limits are parameterized by an AddressType which is used to index the 37 // underlying resource (either a Memory or a Table). Tables are restricted to 38 // I32, while memories may use I64 when memory64 is enabled. 39 40 enum class AddressType : uint8_t { I32, I64 }; 41 42 inline ValType ToValType(AddressType at) { 43 return at == AddressType::I64 ? ValType::I64 : ValType::I32; 44 } 45 46 inline AddressType MinAddressType(AddressType a, AddressType b) { 47 return (a == AddressType::I32 || b == AddressType::I32) ? AddressType::I32 48 : AddressType::I64; 49 } 50 51 extern bool ToAddressType(JSContext* cx, HandleValue value, 52 AddressType* addressType); 53 54 extern const char* ToString(AddressType addressType); 55 56 static constexpr unsigned PageSizeInBytes(PageSize sz) { 57 return 1U << static_cast<uint8_t>(sz); 58 } 59 60 static constexpr unsigned StandardPageSizeBytes = 61 PageSizeInBytes(PageSize::Standard); 62 static_assert(StandardPageSizeBytes == 64 * 1024); 63 64 // By spec, see 65 // https://github.com/WebAssembly/spec/issues/1895#issuecomment-2895078022 66 static_assert((StandardPageSizeBytes * MaxMemory64StandardPagesValidation) <= 67 (uint64_t(1) << 53) - 1); 68 69 // Pages is a typed unit representing a multiple of the page size, which 70 // defaults to wasm::StandardPageSizeBytes. The page size can be customized 71 // only if the custom page sizes proposal is enabled. 72 // 73 // We generally use pages as the unit of length when representing linear memory 74 // lengths so as to avoid overflow when the specified initial or maximum pages 75 // would overflow the native word size. 76 // 77 // Modules may specify pages up to 2^48 (or 2^64 - 1 with tiny pages) inclusive 78 // and so Pages is 64-bit on all platforms. 79 // 80 // We represent byte lengths using the native word size, as it is assumed that 81 // consumers of this API will only need byte lengths once it is time to 82 // allocate memory, at which point the pages will be checked against the 83 // implementation limits `MaxMemoryPages()` and will then be guaranteed to 84 // fit in a native word. 85 struct Pages { 86 private: 87 // Pages are specified by limit fields, which in general may be up to 2^48, 88 // so we must use uint64_t here. 89 uint64_t pageCount_; 90 PageSize pageSize_; 91 92 constexpr Pages(uint64_t pageCount, PageSize pageSize) 93 : pageCount_(pageCount), pageSize_(pageSize) {} 94 95 public: 96 static constexpr Pages fromPageCount(uint64_t pageCount, PageSize pageSize) { 97 return Pages(pageCount, pageSize); 98 } 99 100 static constexpr Pages forPageSize(PageSize pageSize) { 101 return Pages(0, pageSize); 102 } 103 104 static constexpr bool byteLengthIsMultipleOfPageSize(size_t byteLength, 105 PageSize pageSize) { 106 return byteLength % PageSizeInBytes(pageSize) == 0; 107 } 108 109 // Converts from a byte length to pages, assuming that the length is an 110 // exact multiple of the page size. 111 static constexpr Pages fromByteLengthExact(size_t byteLength, 112 PageSize pageSize) { 113 MOZ_RELEASE_ASSERT(byteLengthIsMultipleOfPageSize(byteLength, pageSize)); 114 return Pages(byteLength / PageSizeInBytes(pageSize), pageSize); 115 } 116 117 Pages& operator=(const Pages& other) { 118 MOZ_RELEASE_ASSERT(other.pageSize_ == pageSize_); 119 pageCount_ = other.pageCount_; 120 return *this; 121 } 122 123 // Get the wrapped page count and size. Only use this if you must, prefer to 124 // use or add new APIs to Page. 125 uint64_t pageCount() const { return pageCount_; } 126 PageSize pageSize() const { return pageSize_; } 127 128 // Return whether the page length may overflow when converted to a byte 129 // length in the native word size. 130 bool hasByteLength() const { 131 mozilla::CheckedInt<size_t> length(pageCount_); 132 length *= PageSizeInBytes(pageSize_); 133 return length.isValid(); 134 } 135 136 // Converts from pages to byte length in the native word size. Users must 137 // check for overflow, or be assured else-how that overflow cannot happen. 138 size_t byteLength() const { 139 mozilla::CheckedInt<size_t> length(pageCount_); 140 length *= PageSizeInBytes(pageSize_); 141 return length.value(); 142 } 143 144 // Return the byteLength for a 64-bits memory. 145 uint64_t byteLength64() const { 146 mozilla::CheckedInt<uint64_t> length(pageCount_); 147 length *= PageSizeInBytes(pageSize_); 148 return length.value(); 149 } 150 151 // Increment this pages by delta and return whether the resulting value 152 // did not overflow. If there is no overflow, then this is set to the 153 // resulting value. 154 bool checkedIncrement(uint64_t delta) { 155 mozilla::CheckedInt<uint64_t> newValue = pageCount_; 156 newValue += delta; 157 if (!newValue.isValid()) { 158 return false; 159 } 160 pageCount_ = newValue.value(); 161 return true; 162 } 163 164 // Implement pass-through comparison operators so that Pages can be compared. 165 166 constexpr auto operator<=>(const Pages& other) const { 167 MOZ_RELEASE_ASSERT(other.pageSize_ == pageSize_); 168 return pageCount_ <=> other.pageCount_; 169 } 170 constexpr auto operator==(const Pages& other) const { 171 MOZ_RELEASE_ASSERT(other.pageSize_ == pageSize_); 172 return pageCount_ == other.pageCount_; 173 } 174 }; 175 176 // The largest number of pages the application can request. 177 extern Pages MaxMemoryPages(AddressType t, PageSize pageSize); 178 179 // The byte value of MaxMemoryPages(t). 180 static inline size_t MaxMemoryBytes(AddressType t, PageSize pageSize) { 181 return MaxMemoryPages(t, pageSize).byteLength(); 182 } 183 184 // A value representing the largest valid value for boundsCheckLimit. 185 extern size_t MaxMemoryBoundsCheckLimit(AddressType t, PageSize pageSize); 186 187 static inline uint64_t MaxMemoryPagesValidation(AddressType addressType, 188 PageSize pageSize) { 189 #ifdef ENABLE_WASM_CUSTOM_PAGE_SIZES 190 if (pageSize == PageSize::Tiny) { 191 return addressType == AddressType::I32 ? MaxMemory32TinyPagesValidation 192 : MaxMemory64TinyPagesValidation; 193 } 194 #endif 195 196 MOZ_ASSERT(pageSize == PageSize::Standard); 197 return addressType == AddressType::I32 ? MaxMemory32StandardPagesValidation 198 : MaxMemory64StandardPagesValidation; 199 } 200 201 static inline uint64_t MaxTableElemsValidation(AddressType addressType) { 202 return addressType == AddressType::I32 ? MaxTable32ElemsValidation 203 : MaxTable64ElemsValidation; 204 } 205 206 // Compute the 'clamped' maximum size of a memory. See 207 // 'WASM Linear Memory structure' in ArrayBufferObject.cpp for background. 208 extern Pages ClampedMaxPages(AddressType t, Pages initialPages, 209 const mozilla::Maybe<Pages>& sourceMaxPages, 210 bool useHugeMemory); 211 212 // For a given WebAssembly/asm.js 'clamped' max pages, return the number of 213 // bytes to map which will necessarily be a multiple of the system page size and 214 // greater than clampedMaxPages in bytes. See "Wasm Linear Memory Structure" in 215 // vm/ArrayBufferObject.cpp. 216 extern size_t ComputeMappedSize(Pages clampedMaxPages); 217 218 extern uint64_t GetMaxOffsetGuardLimit(bool hugeMemory, PageSize sz); 219 220 // Return the next higher valid immediate that satisfies the constraints of the 221 // platform. 222 extern uint64_t RoundUpToNextValidBoundsCheckImmediate(uint64_t i); 223 224 #ifdef WASM_SUPPORTS_HUGE_MEMORY 225 // On WASM_SUPPORTS_HUGE_MEMORY platforms, every asm.js or WebAssembly 32-bit 226 // memory unconditionally allocates a huge region of virtual memory of size 227 // wasm::HugeMappedSize. This allows all memory resizing to work without 228 // reallocation and provides enough guard space for most offsets to be folded 229 // into memory accesses. See "Linear memory addresses and bounds checking" in 230 // wasm/WasmMemory.cpp for more information. 231 232 // Reserve 4GiB to support any i32 index. 233 static const uint64_t HugeIndexRange = uint64_t(UINT32_MAX) + 1; 234 // Reserve 32MiB to support most offset immediates. Any immediate that is over 235 // this will require a bounds check to be emitted. 32MiB was chosen to 236 // generously cover the max offset immediate, 20MiB, found in a corpus of wasm 237 // modules. 238 static const uint64_t HugeOffsetGuardLimit = 1 << 25; 239 // Reserve a wasm page (64KiB) to support slop on unaligned accesses. 240 static const uint64_t HugeUnalignedGuardPage = StandardPageSizeBytes; 241 242 // Compute the total memory reservation. 243 static const uint64_t HugeMappedSize = 244 HugeIndexRange + HugeOffsetGuardLimit + HugeUnalignedGuardPage; 245 246 // Try to keep the memory reservation aligned to the wasm page size. This 247 // ensures that it's aligned to the system page size. 248 static_assert(HugeMappedSize % StandardPageSizeBytes == 0); 249 250 #endif 251 252 // The size of the guard page for non huge-memories. 253 static const size_t GuardSize = StandardPageSizeBytes; 254 255 // The size of the guard page that included NULL pointer. Reserve a smallest 256 // range for typical hardware, to catch near NULL pointer accesses, e.g. 257 // for a structure fields operations. 258 static const size_t NullPtrGuardSize = 4096; 259 260 // Check if a range of wasm memory is within bounds, specified as byte offset 261 // and length (using 32-bit indices). Omits one check by converting from 262 // uint32_t to uint64_t, at which point overflow cannot occur. 263 static inline bool MemoryBoundsCheck(uint32_t offset, uint32_t len, 264 size_t memLen) { 265 uint64_t offsetLimit = uint64_t(offset) + uint64_t(len); 266 return offsetLimit <= memLen; 267 } 268 269 // Check if a range of wasm memory is within bounds, specified as byte offset 270 // and length (using 64-bit indices). 271 static inline bool MemoryBoundsCheck(uint64_t offset, uint64_t len, 272 size_t memLen) { 273 uint64_t offsetLimit = offset + len; 274 bool didOverflow = offsetLimit < offset; 275 bool tooLong = memLen < offsetLimit; 276 return !didOverflow && !tooLong; 277 } 278 279 } // namespace wasm 280 } // namespace js 281 282 #endif // wasm_memory_h