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 <stdint.h>
26 
27 #include "js/Value.h"
28 #include "vm/NativeObject.h"
29 #include "wasm/WasmConstants.h"
30 #include "wasm/WasmValType.h"
31 
32 namespace js {
33 namespace wasm {
34 
35 // Limits are parameterized by an IndexType which is used to index the
36 // underlying resource (either a Memory or a Table). Tables are restricted to
37 // I32, while memories may use I64 when memory64 is enabled.
38 
39 enum class IndexType : uint8_t { I32, I64 };
40 
ToValType(IndexType it)41 inline ValType ToValType(IndexType it) {
42   return it == IndexType::I64 ? ValType::I64 : ValType::I32;
43 }
44 
45 extern bool ToIndexType(JSContext* cx, HandleValue value, IndexType* indexType);
46 
47 extern const char* ToString(IndexType indexType);
48 
49 // Pages is a typed unit representing a multiple of wasm::PageSize. We
50 // generally use pages as the unit of length when representing linear memory
51 // lengths so as to avoid overflow when the specified initial or maximum pages
52 // would overflow the native word size.
53 //
54 // Modules may specify pages up to 2^48 inclusive and so Pages is 64-bit on all
55 // platforms.
56 //
57 // We represent byte lengths using the native word size, as it is assumed that
58 // consumers of this API will only need byte lengths once it is time to
59 // allocate memory, at which point the pages will be checked against the
60 // implementation limits `MaxMemoryPages()` and will then be guaranteed to
61 // fit in a native word.
62 struct Pages {
63  private:
64   // Pages are specified by limit fields, which in general may be up to 2^48,
65   // so we must use uint64_t here.
66   uint64_t value_;
67 
68  public:
PagesPages69   constexpr Pages() : value_(0) {}
PagesPages70   constexpr explicit Pages(uint64_t value) : value_(value) {}
71 
72   // Get the wrapped page value. Only use this if you must, prefer to use or
73   // add new APIs to Page.
valuePages74   uint64_t value() const { return value_; }
75 
76   // Converts from a byte length to pages, assuming that the length is an
77   // exact multiple of the page size.
fromByteLengthExactPages78   static Pages fromByteLengthExact(size_t byteLength) {
79     MOZ_ASSERT(byteLength % PageSize == 0);
80     return Pages(byteLength / PageSize);
81   }
82 
83   // Return whether the page length may overflow when converted to a byte
84   // length in the native word size.
hasByteLengthPages85   bool hasByteLength() const {
86     mozilla::CheckedInt<size_t> length(value_);
87     length *= PageSize;
88     return length.isValid();
89   }
90 
91   // Converts from pages to byte length in the native word size. Users must
92   // check for overflow, or be assured else-how that overflow cannot happen.
byteLengthPages93   size_t byteLength() const {
94     mozilla::CheckedInt<size_t> length(value_);
95     length *= PageSize;
96     return length.value();
97   }
98 
99   // Increment this pages by delta and return whether the resulting value
100   // did not overflow. If there is no overflow, then this is set to the
101   // resulting value.
checkedIncrementPages102   bool checkedIncrement(Pages delta) {
103     mozilla::CheckedInt<uint64_t> newValue = value_;
104     newValue += delta.value_;
105     if (!newValue.isValid()) {
106       return false;
107     }
108     value_ = newValue.value();
109     return true;
110   }
111 
112   // Implement pass-through comparison operators so that Pages can be compared.
113 
114   bool operator==(Pages other) const { return value_ == other.value_; }
115   bool operator!=(Pages other) const { return value_ != other.value_; }
116   bool operator<=(Pages other) const { return value_ <= other.value_; }
117   bool operator<(Pages other) const { return value_ < other.value_; }
118   bool operator>=(Pages other) const { return value_ >= other.value_; }
119   bool operator>(Pages other) const { return value_ > other.value_; }
120 };
121 
122 // The largest number of pages the application can request.
123 extern Pages MaxMemoryPages(IndexType t);
124 
125 // The byte value of MaxMemoryPages(t).
MaxMemoryBytes(IndexType t)126 static inline size_t MaxMemoryBytes(IndexType t) {
127   return MaxMemoryPages(t).byteLength();
128 }
129 
130 // A value at least as large as MaxMemoryBytes(t) representing the largest valid
131 // bounds check limit on the system.  (It can be larger than MaxMemoryBytes()
132 // because bounds check limits are rounded up to fit formal requirements on some
133 // platforms.  Also see ComputeMappedSize().)
134 extern size_t MaxMemoryBoundsCheckLimit(IndexType t);
135 
MaxMemoryLimitField(IndexType indexType)136 static inline uint64_t MaxMemoryLimitField(IndexType indexType) {
137   return indexType == IndexType::I32 ? MaxMemory32LimitField
138                                      : MaxMemory64LimitField;
139 }
140 
141 // Compute the 'clamped' maximum size of a memory. See
142 // 'WASM Linear Memory structure' in ArrayBufferObject.cpp for background.
143 extern Pages ClampedMaxPages(IndexType t, Pages initialPages,
144                              const mozilla::Maybe<Pages>& sourceMaxPages,
145                              bool useHugeMemory);
146 
147 // For a given WebAssembly/asm.js 'clamped' max pages, return the number of
148 // bytes to map which will necessarily be a multiple of the system page size and
149 // greater than clampedMaxPages in bytes.  See "Wasm Linear Memory Structure" in
150 // vm/ArrayBufferObject.cpp.
151 extern size_t ComputeMappedSize(Pages clampedMaxPages);
152 
153 extern size_t GetMaxOffsetGuardLimit(bool hugeMemory);
154 
155 // Return whether the given immediate satisfies the constraints of the platform.
156 extern bool IsValidBoundsCheckImmediate(uint32_t i);
157 
158 // Return whether the given immediate is valid on arm.
159 extern bool IsValidARMImmediate(uint32_t i);
160 
161 // Return the next higher valid immediate that satisfies the constraints of the
162 // platform.
163 extern uint64_t RoundUpToNextValidBoundsCheckImmediate(uint64_t i);
164 
165 // Return the next higher valid immediate for arm.
166 extern uint64_t RoundUpToNextValidARMImmediate(uint64_t i);
167 
168 #ifdef WASM_SUPPORTS_HUGE_MEMORY
169 // On WASM_SUPPORTS_HUGE_MEMORY platforms, every asm.js or WebAssembly 32-bit
170 // memory unconditionally allocates a huge region of virtual memory of size
171 // wasm::HugeMappedSize. This allows all memory resizing to work without
172 // reallocation and provides enough guard space for all offsets to be folded
173 // into memory accesses.  See "Linear memory addresses and bounds checking" in
174 // wasm/WasmMemory.cpp for more information.
175 
176 static const uint64_t HugeIndexRange = uint64_t(UINT32_MAX) + 1;
177 static const uint64_t HugeOffsetGuardLimit = uint64_t(INT32_MAX) + 1;
178 static const uint64_t HugeUnalignedGuardPage = PageSize;
179 static const uint64_t HugeMappedSize =
180     HugeIndexRange + HugeOffsetGuardLimit + HugeUnalignedGuardPage;
181 #endif
182 
183 // The size of the guard page for non huge-memories.
184 static const size_t GuardSize = PageSize;
185 
186 }  // namespace wasm
187 }  // namespace js
188 
189 #endif  // wasm_memory_h
190