wasm-code-manager-unittest.cc 11.7 KB
Newer Older
1 2 3 4 5 6 7
// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "test/unittests/test-utils.h"
#include "testing/gmock/include/gmock/gmock.h"

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 15 16

namespace v8 {
namespace internal {
namespace wasm {
17
namespace wasm_heap_unittest {
18

19
class DisjointAllocationPoolTest : public ::testing::Test {
20
 public:
21
  void CheckPool(const DisjointAllocationPool& mem,
22 23 24 25
                 std::initializer_list<base::AddressRegion> expected_regions);
  void CheckRange(base::AddressRegion region1, base::AddressRegion region2);
  DisjointAllocationPool Make(
      std::initializer_list<base::AddressRegion> regions);
26 27
};

28
void DisjointAllocationPoolTest::CheckPool(
29
    const DisjointAllocationPool& mem,
30 31 32 33 34
    std::initializer_list<base::AddressRegion> expected_regions) {
  const auto& regions = mem.regions();
  CHECK_EQ(regions.size(), expected_regions.size());
  auto iter = expected_regions.begin();
  for (auto it = regions.begin(), e = regions.end(); it != e; ++it, ++iter) {
35
    CHECK_EQ(*it, *iter);
36 37 38
  }
}

39 40 41
void DisjointAllocationPoolTest::CheckRange(base::AddressRegion region1,
                                            base::AddressRegion region2) {
  CHECK_EQ(region1, region2);
42 43
}

44
DisjointAllocationPool DisjointAllocationPoolTest::Make(
45
    std::initializer_list<base::AddressRegion> regions) {
46
  DisjointAllocationPool ret;
47 48
  for (auto& region : regions) {
    ret.Merge(region);
49 50 51 52
  }
  return ret;
}

53
TEST_F(DisjointAllocationPoolTest, ConstructEmpty) {
54 55
  DisjointAllocationPool a;
  CHECK(a.IsEmpty());
56
  CheckPool(a, {});
57 58
  a.Merge({1, 4});
  CheckPool(a, {{1, 4}});
59 60 61
}

TEST_F(DisjointAllocationPoolTest, ConstructWithRange) {
62
  DisjointAllocationPool a({1, 4});
63
  CHECK(!a.IsEmpty());
64
  CheckPool(a, {{1, 4}});
65 66 67
}

TEST_F(DisjointAllocationPoolTest, SimpleExtract) {
68 69 70 71
  DisjointAllocationPool a = Make({{1, 4}});
  base::AddressRegion b = a.Allocate(2);
  CheckPool(a, {{3, 2}});
  CheckRange(b, {1, 2});
72
  a.Merge(b);
73 74 75 76
  CheckPool(a, {{1, 4}});
  CHECK_EQ(a.regions().size(), 1);
  CHECK_EQ(a.regions().front().begin(), 1);
  CHECK_EQ(a.regions().front().end(), 5);
77 78 79
}

TEST_F(DisjointAllocationPoolTest, ExtractAll) {
80 81 82
  DisjointAllocationPool a({1, 4});
  base::AddressRegion b = a.Allocate(4);
  CheckRange(b, {1, 4});
83
  CHECK(a.IsEmpty());
84
  a.Merge(b);
85
  CheckPool(a, {{1, 4}});
86 87 88
}

TEST_F(DisjointAllocationPoolTest, FailToExtract) {
89 90 91
  DisjointAllocationPool a = Make({{1, 4}});
  base::AddressRegion b = a.Allocate(5);
  CheckPool(a, {{1, 4}});
92
  CHECK(b.is_empty());
93 94 95
}

TEST_F(DisjointAllocationPoolTest, FailToExtractExact) {
96 97 98
  DisjointAllocationPool a = Make({{1, 4}, {10, 4}});
  base::AddressRegion b = a.Allocate(5);
  CheckPool(a, {{1, 4}, {10, 4}});
99
  CHECK(b.is_empty());
100 101 102
}

TEST_F(DisjointAllocationPoolTest, ExtractExact) {
103 104 105 106
  DisjointAllocationPool a = Make({{1, 4}, {10, 5}});
  base::AddressRegion b = a.Allocate(5);
  CheckPool(a, {{1, 4}});
  CheckRange(b, {10, 5});
107 108 109
}

TEST_F(DisjointAllocationPoolTest, Merging) {
110 111 112
  DisjointAllocationPool a = Make({{10, 5}, {20, 5}});
  a.Merge({15, 5});
  CheckPool(a, {{10, 15}});
113 114 115
}

TEST_F(DisjointAllocationPoolTest, MergingMore) {
116 117 118 119
  DisjointAllocationPool a = Make({{10, 5}, {20, 5}, {30, 5}});
  a.Merge({15, 5});
  a.Merge({25, 5});
  CheckPool(a, {{10, 25}});
120 121 122
}

TEST_F(DisjointAllocationPoolTest, MergingSkip) {
123 124 125
  DisjointAllocationPool a = Make({{10, 5}, {20, 5}, {30, 5}});
  a.Merge({25, 5});
  CheckPool(a, {{10, 5}, {20, 15}});
126 127 128
}

TEST_F(DisjointAllocationPoolTest, MergingSkipLargerSrc) {
129 130 131 132
  DisjointAllocationPool a = Make({{10, 5}, {20, 5}, {30, 5}});
  a.Merge({25, 5});
  a.Merge({35, 5});
  CheckPool(a, {{10, 5}, {20, 20}});
133 134 135
}

TEST_F(DisjointAllocationPoolTest, MergingSkipLargerSrcWithGap) {
136 137 138 139
  DisjointAllocationPool a = Make({{10, 5}, {20, 5}, {30, 5}});
  a.Merge({25, 5});
  a.Merge({36, 4});
  CheckPool(a, {{10, 5}, {20, 15}, {36, 4}});
140 141
}

142 143
enum ModuleStyle : int { Fixed = 0, Growable = 1 };

144 145 146 147 148 149 150 151 152 153 154
std::string PrintWasmCodeManageTestParam(
    ::testing::TestParamInfo<ModuleStyle> info) {
  switch (info.param) {
    case Fixed:
      return "Fixed";
    case Growable:
      return "Growable";
  }
  UNREACHABLE();
}

155 156
class WasmCodeManagerTest : public TestWithContext,
                            public ::testing::WithParamInterface<ModuleStyle> {
157
 public:
158 159
  static constexpr uint32_t kNumFunctions = 10;
  static constexpr uint32_t kJumpTableSize = RoundUp<kCodeAlignment>(
160
      JumpTableAssembler::SizeForNumberOfSlots(kNumFunctions));
161

162 163
  using NativeModulePtr = std::unique_ptr<NativeModule>;

164
  NativeModulePtr AllocModule(size_t size, ModuleStyle style) {
165 166
    std::shared_ptr<WasmModule> module(new WasmModule);
    module->num_declared_functions = kNumFunctions;
167
    bool can_request_more = style == Growable;
168 169
    return engine()->NewNativeModule(i_isolate(), kAllWasmFeatures, size,
                                     can_request_more, std::move(module));
170 171 172 173 174 175 176 177
  }

  WasmCode* AddCode(NativeModule* native_module, uint32_t index, size_t size) {
    CodeDesc desc;
    memset(reinterpret_cast<void*>(&desc), 0, sizeof(CodeDesc));
    std::unique_ptr<byte[]> exec_buff(new byte[size]);
    desc.buffer = exec_buff.get();
    desc.instr_size = static_cast<int>(size);
178 179 180
    return native_module->AddCode(index, desc, 0, 0, 0, 0, {},
                                  OwnedVector<byte>(), WasmCode::kFunction,
                                  WasmCode::kOther);
181 182
  }

183
  size_t page() const { return AllocatePageSize(); }
184

185 186 187
  WasmEngine* engine() { return i_isolate()->wasm_engine(); }

  WasmCodeManager* manager() { return engine()->code_manager(); }
188

189 190 191
  void SetMaxCommittedMemory(size_t limit) {
    manager()->SetMaxCommittedMemoryForTesting(limit);
  }
192 193
};

194
INSTANTIATE_TEST_CASE_P(Parameterized, WasmCodeManagerTest,
195 196
                        ::testing::Values(Fixed, Growable),
                        PrintWasmCodeManageTestParam);
197 198

TEST_P(WasmCodeManagerTest, EmptyCase) {
199 200
  SetMaxCommittedMemory(0 * page());
  CHECK_EQ(0, manager()->remaining_uncommitted_code_space());
201

202
  ASSERT_DEATH_IF_SUPPORTED(AllocModule(1 * page(), GetParam()),
203
                            "OOM in NativeModule::AllocateForCode commit");
204 205
}

206
TEST_P(WasmCodeManagerTest, AllocateAndGoOverLimit) {
207 208 209
  SetMaxCommittedMemory(1 * page());
  CHECK_EQ(1 * page(), manager()->remaining_uncommitted_code_space());
  NativeModulePtr native_module = AllocModule(1 * page(), GetParam());
210
  CHECK(native_module);
211
  CHECK_EQ(0, manager()->remaining_uncommitted_code_space());
212 213 214
  uint32_t index = 0;
  WasmCode* code = AddCode(native_module.get(), index++, 1 * kCodeAlignment);
  CHECK_NOT_NULL(code);
215
  CHECK_EQ(0, manager()->remaining_uncommitted_code_space());
216 217 218

  code = AddCode(native_module.get(), index++, 3 * kCodeAlignment);
  CHECK_NOT_NULL(code);
219
  CHECK_EQ(0, manager()->remaining_uncommitted_code_space());
220

221 222
  code = AddCode(native_module.get(), index++,
                 page() - 4 * kCodeAlignment - kJumpTableSize);
223
  CHECK_NOT_NULL(code);
224
  CHECK_EQ(0, manager()->remaining_uncommitted_code_space());
225

226 227 228
  // This fails in "reservation" if we cannot extend the code space, or in
  // "commit" it we can (since we hit the allocation limit in the
  // WasmCodeManager). Hence don't check for that part of the OOM message.
229 230
  ASSERT_DEATH_IF_SUPPORTED(
      AddCode(native_module.get(), index++, 1 * kCodeAlignment),
231
      "OOM in NativeModule::AllocateForCode");
232 233
}

234
TEST_P(WasmCodeManagerTest, TotalLimitIrrespectiveOfModuleCount) {
235 236 237
  SetMaxCommittedMemory(3 * page());
  NativeModulePtr nm1 = AllocModule(2 * page(), GetParam());
  NativeModulePtr nm2 = AllocModule(2 * page(), GetParam());
238 239
  CHECK(nm1);
  CHECK(nm2);
240
  WasmCode* code = AddCode(nm1.get(), 0, 2 * page() - kJumpTableSize);
241
  CHECK_NOT_NULL(code);
242
  ASSERT_DEATH_IF_SUPPORTED(AddCode(nm2.get(), 0, 2 * page() - kJumpTableSize),
243
                            "OOM in NativeModule::AllocateForCode commit");
244 245
}

246
TEST_P(WasmCodeManagerTest, GrowingVsFixedModule) {
247 248
  SetMaxCommittedMemory(3 * page());
  NativeModulePtr nm = AllocModule(1 * page(), GetParam());
249 250
  size_t module_size = GetParam() == Fixed ? kMaxWasmCodeMemory : 1 * page();
  size_t remaining_space_in_module = module_size - kJumpTableSize;
251
  if (GetParam() == Fixed) {
252 253 254 255
    // Requesting more than the remaining space fails because the module cannot
    // grow.
    ASSERT_DEATH_IF_SUPPORTED(
        AddCode(nm.get(), 0, remaining_space_in_module + kCodeAlignment),
256
        "OOM in NativeModule::AllocateForCode");
257
  } else {
258 259 260
    // The module grows by one page. One page remains uncommitted.
    CHECK_NOT_NULL(
        AddCode(nm.get(), 0, remaining_space_in_module + kCodeAlignment));
261
    CHECK_EQ(manager()->remaining_uncommitted_code_space(), 1 * page());
262 263 264
  }
}

265
TEST_P(WasmCodeManagerTest, CommitIncrements) {
266 267
  SetMaxCommittedMemory(10 * page());
  NativeModulePtr nm = AllocModule(3 * page(), GetParam());
268 269
  WasmCode* code = AddCode(nm.get(), 0, kCodeAlignment);
  CHECK_NOT_NULL(code);
270
  CHECK_EQ(manager()->remaining_uncommitted_code_space(), 9 * page());
271 272
  code = AddCode(nm.get(), 1, 2 * page());
  CHECK_NOT_NULL(code);
273
  CHECK_EQ(manager()->remaining_uncommitted_code_space(), 7 * page());
274
  code = AddCode(nm.get(), 2, page() - kCodeAlignment - kJumpTableSize);
275
  CHECK_NOT_NULL(code);
276
  CHECK_EQ(manager()->remaining_uncommitted_code_space(), 7 * page());
277 278
}

279
TEST_P(WasmCodeManagerTest, Lookup) {
280
  SetMaxCommittedMemory(2 * page());
281

282 283
  NativeModulePtr nm1 = AllocModule(1 * page(), GetParam());
  NativeModulePtr nm2 = AllocModule(1 * page(), GetParam());
284 285 286 287 288 289 290 291 292 293 294 295 296 297
  WasmCode* code1_0 = AddCode(nm1.get(), 0, kCodeAlignment);
  CHECK_EQ(nm1.get(), code1_0->native_module());
  WasmCode* code1_1 = AddCode(nm1.get(), 1, kCodeAlignment);
  WasmCode* code2_0 = AddCode(nm2.get(), 0, kCodeAlignment);
  WasmCode* code2_1 = AddCode(nm2.get(), 1, kCodeAlignment);
  CHECK_EQ(nm2.get(), code2_1->native_module());

  CHECK_EQ(0, code1_0->index());
  CHECK_EQ(1, code1_1->index());
  CHECK_EQ(0, code2_0->index());
  CHECK_EQ(1, code2_1->index());

  // we know the manager object is allocated here, so we shouldn't
  // find any WasmCode* associated with that ptr.
298 299
  WasmCode* not_found =
      manager()->LookupCode(reinterpret_cast<Address>(manager()));
300
  CHECK_NULL(not_found);
301
  WasmCode* found = manager()->LookupCode(code1_0->instruction_start());
302
  CHECK_EQ(found, code1_0);
303 304
  found = manager()->LookupCode(code2_1->instruction_start() +
                                (code2_1->instructions().size() / 2));
305
  CHECK_EQ(found, code2_1);
306 307
  found = manager()->LookupCode(code2_1->instruction_start() +
                                code2_1->instructions().size() - 1);
308
  CHECK_EQ(found, code2_1);
309 310
  found = manager()->LookupCode(code2_1->instruction_start() +
                                code2_1->instructions().size());
311 312
  CHECK_NULL(found);
  Address mid_code1_1 =
313
      code1_1->instruction_start() + (code1_1->instructions().size() / 2);
314
  CHECK_EQ(code1_1, manager()->LookupCode(mid_code1_1));
315
  nm1.reset();
316
  CHECK_NULL(manager()->LookupCode(mid_code1_1));
317 318
}

319
TEST_P(WasmCodeManagerTest, LookupWorksAfterRewrite) {
320
  SetMaxCommittedMemory(2 * page());
321

322
  NativeModulePtr nm1 = AllocModule(1 * page(), GetParam());
323 324 325 326 327

  WasmCode* code0 = AddCode(nm1.get(), 0, kCodeAlignment);
  WasmCode* code1 = AddCode(nm1.get(), 1, kCodeAlignment);
  CHECK_EQ(0, code0->index());
  CHECK_EQ(1, code1->index());
328
  CHECK_EQ(code1, manager()->LookupCode(code1->instruction_start()));
329 330
  WasmCode* code1_1 = AddCode(nm1.get(), 1, kCodeAlignment);
  CHECK_EQ(1, code1_1->index());
331 332
  CHECK_EQ(code1, manager()->LookupCode(code1->instruction_start()));
  CHECK_EQ(code1_1, manager()->LookupCode(code1_1->instruction_start()));
333 334
}

335
}  // namespace wasm_heap_unittest
336 337 338
}  // namespace wasm
}  // namespace internal
}  // namespace v8