Line data Source code
1 : // Copyright 2017 the V8 project authors. All rights reserved.
2 : // Use of this source code is governed by a BSD-style license that can be
3 : // found in the LICENSE file.
4 :
5 : #include "test/unittests/test-utils.h"
6 : #include "testing/gmock/include/gmock/gmock.h"
7 :
8 : #include "src/wasm/function-compiler.h"
9 : #include "src/wasm/jump-table-assembler.h"
10 : #include "src/wasm/wasm-code-manager.h"
11 : #include "src/wasm/wasm-engine.h"
12 : #include "src/wasm/wasm-memory.h"
13 :
14 : namespace v8 {
15 : namespace internal {
16 : namespace wasm {
17 : namespace wasm_heap_unittest {
18 :
19 24 : class DisjointAllocationPoolTest : public ::testing::Test {
20 : public:
21 : void CheckPool(const DisjointAllocationPool& mem,
22 : std::initializer_list<base::AddressRegion> expected_regions);
23 : void CheckRange(base::AddressRegion region1, base::AddressRegion region2);
24 : DisjointAllocationPool Make(
25 : std::initializer_list<base::AddressRegion> regions);
26 : };
27 :
28 14 : void DisjointAllocationPoolTest::CheckPool(
29 : const DisjointAllocationPool& mem,
30 : std::initializer_list<base::AddressRegion> expected_regions) {
31 : const auto& regions = mem.regions();
32 14 : CHECK_EQ(regions.size(), expected_regions.size());
33 : auto iter = expected_regions.begin();
34 50 : for (auto it = regions.begin(), e = regions.end(); it != e; ++it, ++iter) {
35 18 : CHECK_EQ(*it, *iter);
36 : }
37 14 : }
38 :
39 0 : void DisjointAllocationPoolTest::CheckRange(base::AddressRegion region1,
40 : base::AddressRegion region2) {
41 3 : CHECK_EQ(region1, region2);
42 0 : }
43 :
44 0 : DisjointAllocationPool DisjointAllocationPoolTest::Make(
45 : std::initializer_list<base::AddressRegion> regions) {
46 : DisjointAllocationPool ret;
47 49 : for (auto& region : regions) {
48 20 : ret.Merge(region);
49 : }
50 0 : return ret;
51 : }
52 :
53 15444 : TEST_F(DisjointAllocationPoolTest, ConstructEmpty) {
54 : DisjointAllocationPool a;
55 : CHECK(a.IsEmpty());
56 1 : CheckPool(a, {});
57 1 : a.Merge({1, 4});
58 1 : CheckPool(a, {{1, 4}});
59 1 : }
60 :
61 15444 : TEST_F(DisjointAllocationPoolTest, ConstructWithRange) {
62 : DisjointAllocationPool a({1, 4});
63 1 : CHECK(!a.IsEmpty());
64 1 : CheckPool(a, {{1, 4}});
65 1 : }
66 :
67 15444 : TEST_F(DisjointAllocationPoolTest, SimpleExtract) {
68 1 : DisjointAllocationPool a = Make({{1, 4}});
69 1 : base::AddressRegion b = a.Allocate(2);
70 1 : CheckPool(a, {{3, 2}});
71 : CheckRange(b, {1, 2});
72 1 : a.Merge(b);
73 1 : CheckPool(a, {{1, 4}});
74 1 : CHECK_EQ(a.regions().size(), 1);
75 1 : CHECK_EQ(a.regions().front().begin(), 1);
76 1 : CHECK_EQ(a.regions().front().end(), 5);
77 1 : }
78 :
79 15444 : TEST_F(DisjointAllocationPoolTest, ExtractAll) {
80 : DisjointAllocationPool a({1, 4});
81 1 : base::AddressRegion b = a.Allocate(4);
82 : CheckRange(b, {1, 4});
83 1 : CHECK(a.IsEmpty());
84 1 : a.Merge(b);
85 1 : CheckPool(a, {{1, 4}});
86 1 : }
87 :
88 15444 : TEST_F(DisjointAllocationPoolTest, FailToExtract) {
89 1 : DisjointAllocationPool a = Make({{1, 4}});
90 1 : base::AddressRegion b = a.Allocate(5);
91 1 : CheckPool(a, {{1, 4}});
92 1 : CHECK(b.is_empty());
93 1 : }
94 :
95 15444 : TEST_F(DisjointAllocationPoolTest, FailToExtractExact) {
96 1 : DisjointAllocationPool a = Make({{1, 4}, {10, 4}});
97 1 : base::AddressRegion b = a.Allocate(5);
98 1 : CheckPool(a, {{1, 4}, {10, 4}});
99 1 : CHECK(b.is_empty());
100 1 : }
101 :
102 15444 : TEST_F(DisjointAllocationPoolTest, ExtractExact) {
103 1 : DisjointAllocationPool a = Make({{1, 4}, {10, 5}});
104 1 : base::AddressRegion b = a.Allocate(5);
105 1 : CheckPool(a, {{1, 4}});
106 : CheckRange(b, {10, 5});
107 1 : }
108 :
109 15444 : TEST_F(DisjointAllocationPoolTest, Merging) {
110 1 : DisjointAllocationPool a = Make({{10, 5}, {20, 5}});
111 1 : a.Merge({15, 5});
112 1 : CheckPool(a, {{10, 15}});
113 1 : }
114 :
115 15444 : TEST_F(DisjointAllocationPoolTest, MergingMore) {
116 1 : DisjointAllocationPool a = Make({{10, 5}, {20, 5}, {30, 5}});
117 1 : a.Merge({15, 5});
118 1 : a.Merge({25, 5});
119 1 : CheckPool(a, {{10, 25}});
120 1 : }
121 :
122 15444 : TEST_F(DisjointAllocationPoolTest, MergingSkip) {
123 1 : DisjointAllocationPool a = Make({{10, 5}, {20, 5}, {30, 5}});
124 1 : a.Merge({25, 5});
125 1 : CheckPool(a, {{10, 5}, {20, 15}});
126 1 : }
127 :
128 15444 : TEST_F(DisjointAllocationPoolTest, MergingSkipLargerSrc) {
129 1 : DisjointAllocationPool a = Make({{10, 5}, {20, 5}, {30, 5}});
130 1 : a.Merge({25, 5});
131 1 : a.Merge({35, 5});
132 1 : CheckPool(a, {{10, 5}, {20, 20}});
133 1 : }
134 :
135 15444 : TEST_F(DisjointAllocationPoolTest, MergingSkipLargerSrcWithGap) {
136 1 : DisjointAllocationPool a = Make({{10, 5}, {20, 5}, {30, 5}});
137 1 : a.Merge({25, 5});
138 1 : a.Merge({36, 4});
139 1 : CheckPool(a, {{10, 5}, {20, 15}, {36, 4}});
140 1 : }
141 :
142 : enum ModuleStyle : int { Fixed = 0, Growable = 1 };
143 :
144 43232 : std::string PrintWasmCodeManageTestParam(
145 : ::testing::TestParamInfo<ModuleStyle> info) {
146 43232 : switch (info.param) {
147 : case Fixed:
148 21616 : return "Fixed";
149 : case Growable:
150 21616 : return "Growable";
151 : }
152 0 : UNREACHABLE();
153 : }
154 :
155 28 : class WasmCodeManagerTest : public TestWithContext,
156 : public ::testing::WithParamInterface<ModuleStyle> {
157 : public:
158 : static constexpr uint32_t kNumFunctions = 10;
159 : static constexpr uint32_t kJumpTableSize = RoundUp<kCodeAlignment>(
160 : JumpTableAssembler::SizeForNumberOfSlots(kNumFunctions));
161 : static size_t page_size;
162 :
163 28 : WasmCodeManagerTest() {
164 14 : if (page_size == 0) page_size = AllocatePageSize();
165 : DCHECK_NE(0, page_size);
166 14 : }
167 :
168 : using NativeModulePtr = std::shared_ptr<NativeModule>;
169 :
170 16 : NativeModulePtr AllocModule(size_t size, ModuleStyle style) {
171 32 : std::shared_ptr<WasmModule> module(new WasmModule);
172 16 : module->num_declared_functions = kNumFunctions;
173 16 : bool can_request_more = style == Growable;
174 : return engine()->NewNativeModule(i_isolate(), kAllWasmFeatures, size,
175 48 : can_request_more, std::move(module));
176 : }
177 :
178 29 : WasmCode* AddCode(NativeModule* native_module, uint32_t index, size_t size) {
179 29 : CodeDesc desc;
180 : memset(reinterpret_cast<void*>(&desc), 0, sizeof(CodeDesc));
181 29 : std::unique_ptr<byte[]> exec_buff(new byte[size]);
182 29 : desc.buffer = exec_buff.get();
183 29 : desc.instr_size = static_cast<int>(size);
184 : std::unique_ptr<WasmCode> code = native_module->AddCode(
185 116 : index, desc, 0, 0, {}, {}, WasmCode::kFunction, ExecutionTier::kNone);
186 58 : return native_module->PublishCode(std::move(code));
187 : }
188 :
189 : WasmEngine* engine() { return i_isolate()->wasm_engine(); }
190 :
191 : WasmCodeManager* manager() { return engine()->code_manager(); }
192 :
193 : void SetMaxCommittedMemory(size_t limit) {
194 14 : manager()->SetMaxCommittedMemoryForTesting(limit);
195 : }
196 : };
197 :
198 : // static
199 : size_t WasmCodeManagerTest::page_size = 0;
200 :
201 123520 : INSTANTIATE_TEST_SUITE_P(Parameterized, WasmCodeManagerTest,
202 : ::testing::Values(Fixed, Growable),
203 : PrintWasmCodeManageTestParam);
204 :
205 18534 : TEST_P(WasmCodeManagerTest, EmptyCase) {
206 : SetMaxCommittedMemory(0);
207 2 : CHECK_EQ(0, manager()->committed_code_space());
208 :
209 4 : ASSERT_DEATH_IF_SUPPORTED(AllocModule(page_size, GetParam()),
210 : "OOM in NativeModule::AllocateForCode commit");
211 : }
212 :
213 18534 : TEST_P(WasmCodeManagerTest, AllocateAndGoOverLimit) {
214 2 : SetMaxCommittedMemory(page_size);
215 2 : CHECK_EQ(0, manager()->committed_code_space());
216 2 : NativeModulePtr native_module = AllocModule(page_size, GetParam());
217 2 : CHECK(native_module);
218 2 : CHECK_EQ(page_size, manager()->committed_code_space());
219 4 : WasmCodeRefScope code_ref_scope;
220 : uint32_t index = 0;
221 2 : WasmCode* code = AddCode(native_module.get(), index++, 1 * kCodeAlignment);
222 2 : CHECK_NOT_NULL(code);
223 2 : CHECK_EQ(page_size, manager()->committed_code_space());
224 :
225 2 : code = AddCode(native_module.get(), index++, 3 * kCodeAlignment);
226 2 : CHECK_NOT_NULL(code);
227 2 : CHECK_EQ(page_size, manager()->committed_code_space());
228 :
229 2 : code = AddCode(native_module.get(), index++,
230 2 : page_size - 4 * kCodeAlignment - kJumpTableSize);
231 2 : CHECK_NOT_NULL(code);
232 2 : CHECK_EQ(page_size, manager()->committed_code_space());
233 :
234 : // This fails in "reservation" if we cannot extend the code space, or in
235 : // "commit" it we can (since we hit the allocation limit in the
236 : // WasmCodeManager). Hence don't check for that part of the OOM message.
237 4 : ASSERT_DEATH_IF_SUPPORTED(
238 : AddCode(native_module.get(), index++, 1 * kCodeAlignment),
239 : "OOM in NativeModule::AllocateForCode");
240 : }
241 :
242 18534 : TEST_P(WasmCodeManagerTest, TotalLimitIrrespectiveOfModuleCount) {
243 2 : SetMaxCommittedMemory(3 * page_size);
244 2 : NativeModulePtr nm1 = AllocModule(2 * page_size, GetParam());
245 2 : NativeModulePtr nm2 = AllocModule(2 * page_size, GetParam());
246 2 : CHECK(nm1);
247 2 : CHECK(nm2);
248 4 : WasmCodeRefScope code_ref_scope;
249 2 : WasmCode* code = AddCode(nm1.get(), 0, 2 * page_size - kJumpTableSize);
250 2 : CHECK_NOT_NULL(code);
251 4 : ASSERT_DEATH_IF_SUPPORTED(
252 : AddCode(nm2.get(), 0, 2 * page_size - kJumpTableSize),
253 : "OOM in NativeModule::AllocateForCode commit");
254 : }
255 :
256 18534 : TEST_P(WasmCodeManagerTest, GrowingVsFixedModule) {
257 2 : SetMaxCommittedMemory(3 * page_size);
258 2 : NativeModulePtr nm = AllocModule(page_size, GetParam());
259 2 : size_t module_size = GetParam() == Fixed ? kMaxWasmCodeMemory : page_size;
260 : size_t remaining_space_in_module = module_size - kJumpTableSize;
261 2 : if (GetParam() == Fixed) {
262 : // Requesting more than the remaining space fails because the module cannot
263 : // grow.
264 2 : ASSERT_DEATH_IF_SUPPORTED(
265 : AddCode(nm.get(), 0, remaining_space_in_module + kCodeAlignment),
266 : "OOM in NativeModule::AllocateForCode");
267 : } else {
268 : // The module grows by one page. One page remains uncommitted.
269 2 : WasmCodeRefScope code_ref_scope;
270 1 : CHECK_NOT_NULL(
271 : AddCode(nm.get(), 0, remaining_space_in_module + kCodeAlignment));
272 1 : CHECK_EQ(2 * page_size, manager()->committed_code_space());
273 : }
274 : }
275 :
276 18534 : TEST_P(WasmCodeManagerTest, CommitIncrements) {
277 2 : SetMaxCommittedMemory(10 * page_size);
278 2 : NativeModulePtr nm = AllocModule(3 * page_size, GetParam());
279 4 : WasmCodeRefScope code_ref_scope;
280 2 : WasmCode* code = AddCode(nm.get(), 0, kCodeAlignment);
281 2 : CHECK_NOT_NULL(code);
282 2 : CHECK_EQ(page_size, manager()->committed_code_space());
283 2 : code = AddCode(nm.get(), 1, 2 * page_size);
284 2 : CHECK_NOT_NULL(code);
285 2 : CHECK_EQ(3 * page_size, manager()->committed_code_space());
286 2 : code = AddCode(nm.get(), 2, page_size - kCodeAlignment - kJumpTableSize);
287 2 : CHECK_NOT_NULL(code);
288 2 : CHECK_EQ(3 * page_size, manager()->committed_code_space());
289 2 : }
290 :
291 18534 : TEST_P(WasmCodeManagerTest, Lookup) {
292 2 : SetMaxCommittedMemory(2 * page_size);
293 :
294 2 : NativeModulePtr nm1 = AllocModule(page_size, GetParam());
295 2 : NativeModulePtr nm2 = AllocModule(page_size, GetParam());
296 : Address mid_code1_1;
297 : {
298 : // The {WasmCodeRefScope} needs to die before {nm1} dies.
299 4 : WasmCodeRefScope code_ref_scope;
300 2 : WasmCode* code1_0 = AddCode(nm1.get(), 0, kCodeAlignment);
301 2 : CHECK_EQ(nm1.get(), code1_0->native_module());
302 2 : WasmCode* code1_1 = AddCode(nm1.get(), 1, kCodeAlignment);
303 2 : WasmCode* code2_0 = AddCode(nm2.get(), 0, kCodeAlignment);
304 2 : WasmCode* code2_1 = AddCode(nm2.get(), 1, kCodeAlignment);
305 2 : CHECK_EQ(nm2.get(), code2_1->native_module());
306 :
307 2 : CHECK_EQ(0, code1_0->index());
308 2 : CHECK_EQ(1, code1_1->index());
309 2 : CHECK_EQ(0, code2_0->index());
310 2 : CHECK_EQ(1, code2_1->index());
311 :
312 : // we know the manager object is allocated here, so we shouldn't
313 : // find any WasmCode* associated with that ptr.
314 : WasmCode* not_found =
315 2 : manager()->LookupCode(reinterpret_cast<Address>(manager()));
316 2 : CHECK_NULL(not_found);
317 2 : WasmCode* found = manager()->LookupCode(code1_0->instruction_start());
318 2 : CHECK_EQ(found, code1_0);
319 2 : found = manager()->LookupCode(code2_1->instruction_start() +
320 4 : (code2_1->instructions().size() / 2));
321 2 : CHECK_EQ(found, code2_1);
322 2 : found = manager()->LookupCode(code2_1->instruction_start() +
323 2 : code2_1->instructions().size() - 1);
324 2 : CHECK_EQ(found, code2_1);
325 2 : found = manager()->LookupCode(code2_1->instruction_start() +
326 2 : code2_1->instructions().size());
327 2 : CHECK_NULL(found);
328 : mid_code1_1 =
329 2 : code1_1->instruction_start() + (code1_1->instructions().size() / 2);
330 2 : CHECK_EQ(code1_1, manager()->LookupCode(mid_code1_1));
331 : }
332 : nm1.reset();
333 2 : CHECK_NULL(manager()->LookupCode(mid_code1_1));
334 2 : }
335 :
336 18534 : TEST_P(WasmCodeManagerTest, LookupWorksAfterRewrite) {
337 2 : SetMaxCommittedMemory(2 * page_size);
338 :
339 2 : NativeModulePtr nm1 = AllocModule(page_size, GetParam());
340 :
341 4 : WasmCodeRefScope code_ref_scope;
342 2 : WasmCode* code0 = AddCode(nm1.get(), 0, kCodeAlignment);
343 2 : WasmCode* code1 = AddCode(nm1.get(), 1, kCodeAlignment);
344 2 : CHECK_EQ(0, code0->index());
345 2 : CHECK_EQ(1, code1->index());
346 2 : CHECK_EQ(code1, manager()->LookupCode(code1->instruction_start()));
347 2 : WasmCode* code1_1 = AddCode(nm1.get(), 1, kCodeAlignment);
348 2 : CHECK_EQ(1, code1_1->index());
349 2 : CHECK_EQ(code1, manager()->LookupCode(code1->instruction_start()));
350 2 : CHECK_EQ(code1_1, manager()->LookupCode(code1_1->instruction_start()));
351 2 : }
352 :
353 : } // namespace wasm_heap_unittest
354 : } // namespace wasm
355 : } // namespace internal
356 9264 : } // namespace v8
|