| Differences between
and this patch
- a/Source/JavaScriptCore/ChangeLog +41 lines
Lines 1-3 a/Source/JavaScriptCore/ChangeLog_sec1
1
2016-09-26  Keith Miller  <keith_miller@apple.com>
2
3
        Add support for WASM Memory.
4
        https://bugs.webkit.org/show_bug.cgi?id=161710
5
6
        Reviewed by NOBODY (OOPS!).
7
8
        This patch add initial support for WASM memory operations. First,
9
        it adds the concept of a WASM Module memory management object.
10
        This object currently mmaps a 32-bit address space for WASM use,
11
        although it marks all the memory outside the current breakpoint as
12
        PROT_NONE. For now, we do a range check on each store and load. In
13
        the future, we should change this to be an signal handler that
14
        checks what module the program trapped in.
15
16
        * testWASM.cpp:
17
        (runWASMTests):
18
        * wasm/WASMB3IRGenerator.cpp:
19
        (JSC::WASM::parseAndCompile):
20
        * wasm/WASMB3IRGenerator.h:
21
        * wasm/WASMFormat.h:
22
        (JSC::WASM::ModuleMemoryInformation::ModuleMemoryInformation):
23
        (JSC::WASM::ModuleMemoryInformation::~ModuleMemoryInformation):
24
        (JSC::WASM::ModuleMemoryInformation::memory):
25
        (JSC::WASM::ModuleMemoryInformation::size):
26
        (JSC::WASM::ModuleMemoryInformation::growMemory):
27
        (JSC::WASM::ModuleMemoryInformation::offsetOfSize):
28
        * wasm/WASMFunctionParser.h:
29
        (JSC::WASM::FunctionParser<Context>::parseExpression):
30
        * wasm/WASMModuleParser.cpp:
31
        (JSC::WASM::ModuleParser::parse):
32
        (JSC::WASM::ModuleParser::parseMemory):
33
        * wasm/WASMModuleParser.h:
34
        (JSC::WASM::ModuleParser::functionInformation):
35
        (JSC::WASM::ModuleParser::memory):
36
        * wasm/WASMOps.h:
37
        * wasm/WASMPlan.cpp:
38
        (JSC::WASM::Plan::Plan):
39
        * wasm/WASMPlan.h:
40
        * wasm/WASMSections.h:
41
1
2016-09-26  Don Olmstead  <don.olmstead@am.sony.com>
42
2016-09-26  Don Olmstead  <don.olmstead@am.sony.com>
2
43
3
        [JSC] Allow fixedExecutableMemoryPoolSize to be set during build
44
        [JSC] Allow fixedExecutableMemoryPoolSize to be set during build
- a/Source/JavaScriptCore/testWASM.cpp -1 / +231 lines
Lines 242-249 static void runWASMTests() a/Source/JavaScriptCore/testWASM.cpp_sec1
242
{
242
{
243
    {
243
    {
244
        // Generated from:
244
        // Generated from:
245
        // (module
246
        //  (memory 1)
247
        //  (func (export "i32_load8_s") (param $i i32) (param $ptr i32) (result i32)
248
        //   (i32.store (get_local $ptr) (get_local $i))
249
        //   (return (i32.load (get_local $ptr)))
250
        //   )
251
        //  )
252
        Vector<uint8_t> vector = {
253
            0x00, 0x61, 0x73, 0x6d, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x87, 0x80, 0x80, 0x80, 0x00, 0x01, 0x40,
254
            0x02, 0x02, 0x01, 0x01, 0x02, 0x03, 0x82, 0x80, 0x80, 0x80, 0x00, 0x01, 0x00, 0x05, 0x83, 0x80,
255
            0x80, 0x80, 0x00, 0x01, 0x01, 0x01, 0x07, 0x8f, 0x80, 0x80, 0x80, 0x00, 0x01, 0x0b, 0x69, 0x33,
256
            0x32, 0x5f, 0x6c, 0x6f, 0x61, 0x64, 0x38, 0x5f, 0x73, 0x00, 0x00, 0x0a, 0x95, 0x80, 0x80, 0x80,
257
            0x00, 0x01, 0x8f, 0x80, 0x80, 0x80, 0x00, 0x00, 0x14, 0x01, 0x14, 0x00, 0x34, 0x03, 0x00, 0x14,
258
            0x01, 0x2b, 0x03, 0x00, 0x09, 0x0f
259
        };
260
261
        Plan plan(*vm, vector);
262
        if (plan.result.size() != 1 || !plan.result[0]) {
263
            dataLogLn("Module failed to compile correctly.");
264
            CRASH();
265
        }
266
267
        // Test this doesn't crash.
268
        CHECK_EQ(invoke<int>(*plan.result[0], { box(0), box(10) }), 0);
269
        CHECK_EQ(invoke<int>(*plan.result[0], { box(100), box(2) }), 100);
270
        CHECK_EQ(invoke<int>(*plan.result[0], { box(1), box(100) }), 1);
271
    }
272
273
    {
274
        // Generated from:
275
        // (module
276
        //  (memory 1)
277
        //  (func (export "i32_load8_s") (param $i i32) (param $ptr i32) (result i32)
278
        //   (i32.store (get_local $ptr) (get_local $i))
279
        //   (return (i32.load (get_local $ptr)))
280
        //   )
281
        //  )
282
        Vector<uint8_t> vector = {
283
            0x00, 0x61, 0x73, 0x6d, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x87, 0x80, 0x80, 0x80, 0x00, 0x01, 0x40,
284
            0x02, 0x01, 0x01, 0x01, 0x01, 0x03, 0x82, 0x80, 0x80, 0x80, 0x00, 0x01, 0x00, 0x05, 0x83, 0x80,
285
            0x80, 0x80, 0x00, 0x01, 0x01, 0x01, 0x07, 0x8f, 0x80, 0x80, 0x80, 0x00, 0x01, 0x0b, 0x69, 0x33,
286
            0x32, 0x5f, 0x6c, 0x6f, 0x61, 0x64, 0x38, 0x5f, 0x73, 0x00, 0x00, 0x0a, 0x95, 0x80, 0x80, 0x80,
287
            0x00, 0x01, 0x8f, 0x80, 0x80, 0x80, 0x00, 0x00, 0x14, 0x01, 0x14, 0x00, 0x33, 0x02, 0x00, 0x14,
288
            0x01, 0x2a, 0x02, 0x00, 0x09, 0x0f
289
        };
290
291
        Plan plan(*vm, vector);
292
        if (plan.result.size() != 1 || !plan.result[0]) {
293
            dataLogLn("Module failed to compile correctly.");
294
            CRASH();
295
        }
296
297
        // Test this doesn't crash.
298
        CHECK_EQ(invoke<int>(*plan.result[0], { box(0), box(10) }), 0);
299
        CHECK_EQ(invoke<int>(*plan.result[0], { box(100), box(2) }), 100);
300
        CHECK_EQ(invoke<int>(*plan.result[0], { box(1), box(100) }), 1);
301
    }
302
303
    {
304
        // Generated from:
305
        //    (module
306
        //     (memory 1)
307
        //     (func (export "write_array") (param $x i32) (param $p i32) (param $length i32) (local $i i32)
308
        //      (set_local $i (i32.const 0))
309
        //      (block
310
        //       (loop
311
        //        (br_if 1 (i32.ge_u (get_local $i) (get_local $length)))
312
        //        (i32.store (i32.add (get_local $p) (i32.mul (get_local $i) (i32.const 4))) (get_local $x))
313
        //        (set_local $i (i32.add (i32.const 1) (get_local $i)))
314
        //        (br 0)
315
        //        )
316
        //       )
317
        //      (return)
318
        //      )
319
        //     )
320
        Vector<uint8_t> vector = {
321
            0x00, 0x61, 0x73, 0x6d, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x87, 0x80, 0x80, 0x80, 0x00, 0x01, 0x40,
322
            0x03, 0x01, 0x01, 0x01, 0x00, 0x03, 0x82, 0x80, 0x80, 0x80, 0x00, 0x01, 0x00, 0x05, 0x83, 0x80,
323
            0x80, 0x80, 0x00, 0x01, 0x01, 0x01, 0x07, 0x8f, 0x80, 0x80, 0x80, 0x00, 0x01, 0x0b, 0x77, 0x72,
324
            0x69, 0x74, 0x65, 0x5f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x00, 0x00, 0x0a, 0xb2, 0x80, 0x80, 0x80,
325
            0x00, 0x01, 0xac, 0x80, 0x80, 0x80, 0x00, 0x01, 0x01, 0x01, 0x10, 0x00, 0x15, 0x03, 0x01, 0x00,
326
            0x02, 0x00, 0x14, 0x03, 0x14, 0x02, 0x56, 0x07, 0x01, 0x14, 0x01, 0x14, 0x03, 0x10, 0x04, 0x42,
327
            0x40, 0x14, 0x00, 0x33, 0x02, 0x00, 0x10, 0x01, 0x14, 0x03, 0x40, 0x15, 0x03, 0x06, 0x00, 0x0f,
328
            0x0f, 0x09, 0x0f
329
        };
330
331
        Plan plan(*vm, vector);
332
        if (plan.result.size() != 1 || !plan.result[0]) {
333
            dataLogLn("Module failed to compile correctly.");
334
            CRASH();
335
        }
336
        ASSERT(plan.memory->size());
337
338
        // Test this doesn't crash.
339
        unsigned length = 5;
340
        unsigned offset = sizeof(uint32_t);
341
        uint32_t* memory = static_cast<uint32_t*>(plan.memory->memory());
342
        invoke<void>(*plan.result[0], { box(100), box(offset), box(length) });
343
        offset /= sizeof(uint32_t);
344
        CHECK_EQ(memory[offset - 1], 0u);
345
        CHECK_EQ(memory[offset + length], 0u);
346
        for (unsigned i = 0; i < length; ++i)
347
            CHECK_EQ(memory[i + offset], 100u);
348
349
        length = 10;
350
        offset = 5 * sizeof(uint32_t);
351
        invoke<void>(*plan.result[0], { box(5), box(offset), box(length) });
352
        offset /= sizeof(uint32_t);
353
        CHECK_EQ(memory[offset - 1], 100u);
354
        CHECK_EQ(memory[offset + length], 0u);
355
        for (unsigned i = 0; i < length; ++i)
356
            CHECK_EQ(memory[i + offset], 5u);
357
    }
358
359
    {
360
        // Generated from:
361
        //    (module
362
        //     (memory 1)
363
        //     (func (export "write_array") (param $x i32) (param $p i32) (param $length i32) (local $i i32)
364
        //      (set_local $i (i32.const 0))
365
        //      (block
366
        //       (loop
367
        //        (br_if 1 (i32.ge_u (get_local $i) (get_local $length)))
368
        //        (i32.store8 (i32.add (get_local $p) (get_local $i)) (get_local $x))
369
        //        (set_local $i (i32.add (i32.const 1) (get_local $i)))
370
        //        (br 0)
371
        //        )
372
        //       )
373
        //      (return)
374
        //      )
375
        //     )
376
        Vector<uint8_t> vector = {
377
            0x00, 0x61, 0x73, 0x6d, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x87, 0x80, 0x80, 0x80, 0x00, 0x01, 0x40,
378
            0x03, 0x01, 0x01, 0x01, 0x00, 0x03, 0x82, 0x80, 0x80, 0x80, 0x00, 0x01, 0x00, 0x05, 0x83, 0x80,
379
            0x80, 0x80, 0x00, 0x01, 0x01, 0x01, 0x07, 0x8f, 0x80, 0x80, 0x80, 0x00, 0x01, 0x0b, 0x77, 0x72,
380
            0x69, 0x74, 0x65, 0x5f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x00, 0x00, 0x0a, 0xaf, 0x80, 0x80, 0x80,
381
            0x00, 0x01, 0xa9, 0x80, 0x80, 0x80, 0x00, 0x01, 0x01, 0x01, 0x10, 0x00, 0x15, 0x03, 0x01, 0x00,
382
            0x02, 0x00, 0x14, 0x03, 0x14, 0x02, 0x56, 0x07, 0x01, 0x14, 0x01, 0x14, 0x03, 0x40, 0x14, 0x00,
383
            0x2e, 0x00, 0x00, 0x10, 0x01, 0x14, 0x03, 0x40, 0x15, 0x03, 0x06, 0x00, 0x0f, 0x0f, 0x09, 0x0f
384
        };
385
386
        Plan plan(*vm, vector);
387
        if (plan.result.size() != 1 || !plan.result[0]) {
388
            dataLogLn("Module failed to compile correctly.");
389
            CRASH();
390
        }
391
        ASSERT(plan.memory->size());
392
393
        // Test this doesn't crash.
394
        unsigned length = 5;
395
        unsigned offset = 1;
396
        uint8_t* memory = static_cast<uint8_t*>(plan.memory->memory());
397
        invoke<void>(*plan.result[0], { box(100), box(offset), box(length) });
398
        CHECK_EQ(memory[offset - 1], 0u);
399
        CHECK_EQ(memory[offset + length], 0u);
400
        for (unsigned i = 0; i < length; ++i)
401
            CHECK_EQ(memory[i + offset], 100u);
402
403
        length = 10;
404
        offset = 5;
405
        invoke<void>(*plan.result[0], { box(5), box(offset), box(length) });
406
        CHECK_EQ(memory[offset - 1], 100u);
407
        CHECK_EQ(memory[offset + length], 0u);
408
        for (unsigned i = 0; i < length; ++i)
409
            CHECK_EQ(memory[i + offset], 5u);
410
    }
411
412
    {
413
        // Generated from:
414
        //    (module
415
        //     (memory 1)
416
        //     (func (export "i32_load8_s") (param $i i32) (param $ptr i32) (result i32)
417
        //      (i32.store8 (get_local $ptr) (get_local $i))
418
        //      (return (i32.load8_s (get_local $ptr)))
419
        //      )
420
        //     )
421
        Vector<uint8_t> vector = {
422
            0x00, 0x61, 0x73, 0x6d, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x87, 0x80, 0x80, 0x80, 0x00, 0x01, 0x40,
423
            0x02, 0x01, 0x01, 0x01, 0x01, 0x03, 0x82, 0x80, 0x80, 0x80, 0x00, 0x01, 0x00, 0x05, 0x83, 0x80,
424
            0x80, 0x80, 0x00, 0x01, 0x01, 0x01, 0x07, 0x8f, 0x80, 0x80, 0x80, 0x00, 0x01, 0x0b, 0x69, 0x33,
425
            0x32, 0x5f, 0x6c, 0x6f, 0x61, 0x64, 0x38, 0x5f, 0x73, 0x00, 0x00, 0x0a, 0x95, 0x80, 0x80, 0x80,
426
            0x00, 0x01, 0x8f, 0x80, 0x80, 0x80, 0x00, 0x00, 0x14, 0x01, 0x14, 0x00, 0x2e, 0x00, 0x00, 0x14,
427
            0x01, 0x20, 0x00, 0x00, 0x09, 0x0f
428
        };
429
430
        Plan plan(*vm, vector);
431
        if (plan.result.size() != 1 || !plan.result[0]) {
432
            dataLogLn("Module failed to compile correctly.");
433
            CRASH();
434
        }
435
        ASSERT(plan.memory->size());
436
437
        // Test this doesn't crash.
438
        CHECK_EQ(invoke<int>(*plan.result[0], { box(0), box(10) }), 0);
439
        CHECK_EQ(invoke<int>(*plan.result[0], { box(100), box(2) }), 100);
440
        CHECK_EQ(invoke<int>(*plan.result[0], { box(1), box(100) }), 1);
441
    }
442
443
    {
444
        // Generated from:
445
        //    (module
446
        //     (memory 1)
447
        //     (func (export "i32_load8_s") (param $i i32) (result i32)
448
        //      (i32.store8 (i32.const 8) (get_local $i))
449
        //      (return (i32.load8_s (i32.const 8)))
450
        //      )
451
        //     )
452
        Vector<uint8_t> vector = {
453
            0x00, 0x61, 0x73, 0x6d, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x86, 0x80, 0x80, 0x80, 0x00, 0x01, 0x40,
454
            0x01, 0x01, 0x01, 0x01, 0x03, 0x82, 0x80, 0x80, 0x80, 0x00, 0x01, 0x00, 0x05, 0x83, 0x80, 0x80,
455
            0x80, 0x00, 0x01, 0x01, 0x01, 0x07, 0x8f, 0x80, 0x80, 0x80, 0x00, 0x01, 0x0b, 0x69, 0x33, 0x32,
456
            0x5f, 0x6c, 0x6f, 0x61, 0x64, 0x38, 0x5f, 0x73, 0x00, 0x00, 0x0a, 0x95, 0x80, 0x80, 0x80, 0x00,
457
            0x01, 0x8f, 0x80, 0x80, 0x80, 0x00, 0x00, 0x10, 0x08, 0x14, 0x00, 0x2e, 0x00, 0x00, 0x10, 0x08,
458
            0x20, 0x00, 0x00, 0x09, 0x0f
459
        };
460
461
        Plan plan(*vm, vector);
462
        if (plan.result.size() != 1 || !plan.result[0]) {
463
            dataLogLn("Module failed to compile correctly.");
464
            CRASH();
465
        }
466
467
        // Test this doesn't crash.
468
        CHECK_EQ(invoke<int>(*plan.result[0], { box(0) }), 0);
469
        CHECK_EQ(invoke<int>(*plan.result[0], { box(100) }), 100);
470
        CHECK_EQ(invoke<int>(*plan.result[0], { box(1) }), 1);
471
    }
472
473
    {
474
        // Generated from:
245
        //    (module
475
        //    (module
246
        //     (func (export "dumb-eq") (param $x i32) (param $y i32) (result i32)
476
        //     (func "dumb-eq" (param $x i32) (param $y i32) (result i32)
247
        //      (if (i32.eq (get_local $x) (get_local $y))
477
        //      (if (i32.eq (get_local $x) (get_local $y))
248
        //       (then (br 0))
478
        //       (then (br 0))
249
        //       (else (return (i32.const 1))))
479
        //       (else (return (i32.const 1))))
- a/Source/JavaScriptCore/wasm/WASMB3IRGenerator.cpp -7 / +171 lines
Lines 30-35 a/Source/JavaScriptCore/wasm/WASMB3IRGenerator.cpp_sec1
30
30
31
#include "B3BasicBlockInlines.h"
31
#include "B3BasicBlockInlines.h"
32
#include "B3FixSSA.h"
32
#include "B3FixSSA.h"
33
#include "B3StackmapGenerationParams.h"
33
#include "B3Validate.h"
34
#include "B3Validate.h"
34
#include "B3ValueInlines.h"
35
#include "B3ValueInlines.h"
35
#include "B3Variable.h"
36
#include "B3Variable.h"
Lines 165-182 public: a/Source/JavaScriptCore/wasm/WASMB3IRGenerator.cpp_sec2
165
    typedef Vector<Variable*, 1> ResultList;
166
    typedef Vector<Variable*, 1> ResultList;
166
    static constexpr ExpressionType emptyExpression = nullptr;
167
    static constexpr ExpressionType emptyExpression = nullptr;
167
168
168
    B3IRGenerator(Procedure&);
169
    B3IRGenerator(const ModuleMemoryInformation&, Procedure&);
169
170
170
    void addArguments(const Vector<Type>&);
171
    void addArguments(const Vector<Type>&);
171
    void addLocal(Type, uint32_t);
172
    void addLocal(Type, uint32_t);
172
    ExpressionType addConstant(Type, uint64_t);
173
    ExpressionType addConstant(Type, uint64_t);
173
174
175
    // Locals
174
    bool WARN_UNUSED_RETURN getLocal(uint32_t index, ExpressionType& result);
176
    bool WARN_UNUSED_RETURN getLocal(uint32_t index, ExpressionType& result);
175
    bool WARN_UNUSED_RETURN setLocal(uint32_t index, ExpressionType value);
177
    bool WARN_UNUSED_RETURN setLocal(uint32_t index, ExpressionType value);
176
178
179
    // Memory
180
    bool WARN_UNUSED_RETURN load(LoadOpType, uint32_t offset, ExpressionType pointer, ExpressionType& result);
181
    bool WARN_UNUSED_RETURN store(StoreOpType, uint32_t offset, ExpressionType pointer, ExpressionType value);
182
183
    // Basic operators
177
    bool WARN_UNUSED_RETURN binaryOp(BinaryOpType, ExpressionType left, ExpressionType right, ExpressionType& result);
184
    bool WARN_UNUSED_RETURN binaryOp(BinaryOpType, ExpressionType left, ExpressionType right, ExpressionType& result);
178
    bool WARN_UNUSED_RETURN unaryOp(UnaryOpType, ExpressionType arg, ExpressionType& result);
185
    bool WARN_UNUSED_RETURN unaryOp(UnaryOpType, ExpressionType arg, ExpressionType& result);
179
186
187
    // Control flow
180
    ControlData WARN_UNUSED_RETURN addBlock(Type signature);
188
    ControlData WARN_UNUSED_RETURN addBlock(Type signature);
181
    ControlData WARN_UNUSED_RETURN addLoop(Type signature);
189
    ControlData WARN_UNUSED_RETURN addLoop(Type signature);
182
    ControlData WARN_UNUSED_RETURN addIf(ExpressionType condition, Type signature);
190
    ControlData WARN_UNUSED_RETURN addIf(ExpressionType condition, Type signature);
Lines 194-206 private: a/Source/JavaScriptCore/wasm/WASMB3IRGenerator.cpp_sec3
194
    void unify(Variable* target, const ExpressionType source);
202
    void unify(Variable* target, const ExpressionType source);
195
    void unifyValuesWithBlock(const ExpressionList& resultStack, ResultList& stack);
203
    void unifyValuesWithBlock(const ExpressionList& resultStack, ResultList& stack);
196
204
205
    const ModuleMemoryInformation& m_memory;
197
    Procedure& m_proc;
206
    Procedure& m_proc;
198
    BasicBlock* m_currentBlock;
207
    BasicBlock* m_currentBlock;
199
    Vector<Variable*> m_locals;
208
    Vector<Variable*> m_locals;
200
};
209
};
201
210
202
B3IRGenerator::B3IRGenerator(Procedure& procedure)
211
B3IRGenerator::B3IRGenerator(const ModuleMemoryInformation& memory, Procedure& procedure)
203
    : m_proc(procedure)
212
    : m_memory(memory)
213
    , m_proc(procedure)
204
{
214
{
205
    m_currentBlock = m_proc.addBlock();
215
    m_currentBlock = m_proc.addBlock();
206
}
216
}
Lines 224-243 void B3IRGenerator::addArguments(const Vector<Type>& types) a/Source/JavaScriptCore/wasm/WASMB3IRGenerator.cpp_sec4
224
        });
234
        });
225
}
235
}
226
236
227
bool WARN_UNUSED_RETURN B3IRGenerator::getLocal(uint32_t index, ExpressionType& result)
237
bool B3IRGenerator::getLocal(uint32_t index, ExpressionType& result)
228
{
238
{
229
    ASSERT(m_locals[index]);
239
    ASSERT(m_locals[index]);
230
    result = m_currentBlock->appendNew<VariableValue>(m_proc, B3::Get, Origin(), m_locals[index]);
240
    result = m_currentBlock->appendNew<VariableValue>(m_proc, B3::Get, Origin(), m_locals[index]);
231
    return true;
241
    return true;
232
}
242
}
233
243
234
bool WARN_UNUSED_RETURN B3IRGenerator::setLocal(uint32_t index, ExpressionType value)
244
bool B3IRGenerator::setLocal(uint32_t index, ExpressionType value)
235
{
245
{
236
    ASSERT(m_locals[index]);
246
    ASSERT(m_locals[index]);
237
    m_currentBlock->appendNew<VariableValue>(m_proc, B3::Set, Origin(), m_locals[index], value);
247
    m_currentBlock->appendNew<VariableValue>(m_proc, B3::Set, Origin(), m_locals[index], value);
238
    return true;
248
    return true;
239
}
249
}
240
250
251
inline B3::Type typeForLoad(LoadOpType op)
252
{
253
    switch (op) {
254
    case LoadOpType::I32Load8S:
255
    case LoadOpType::I32Load8U:
256
    case LoadOpType::I32Load:
257
        return Int32;
258
    case LoadOpType::I64Load8S:
259
    case LoadOpType::I64Load8U:
260
    case LoadOpType::I64Load16S:
261
    case LoadOpType::I64Load16U:
262
    case LoadOpType::I64Load32S:
263
    case LoadOpType::I64Load32U:
264
    case LoadOpType::I64Load:
265
        return Int64;
266
    default:
267
        break;
268
    }
269
    RELEASE_ASSERT_NOT_REACHED();
270
}
271
272
// We don't have appropriate sign extend for the 64 bit small loads.
273
inline void loadForOp(CCallHelpers& jit, LoadOpType op, MacroAssembler::BaseIndex address, GPRReg dest)
274
{
275
    switch (op) {
276
    case LoadOpType::I32Load8S:
277
        jit.load8SignedExtendTo32(address, dest);
278
        return;
279
    case LoadOpType::I64Load8S:
280
        jit.load8SignedExtendTo32(address, dest);
281
        jit.signExtend32ToPtr(dest, dest);
282
        return;
283
    case LoadOpType::I32Load8U:
284
    case LoadOpType::I64Load8U:
285
        jit.load8(address, dest);
286
        return;
287
    case LoadOpType::I32Load16S:
288
        jit.load16SignedExtendTo32(address, dest);
289
        return;
290
    case LoadOpType::I64Load16S:
291
        jit.load16SignedExtendTo32(address, dest);
292
        jit.signExtend32ToPtr(dest, dest);
293
        return;
294
    case LoadOpType::I32Load16U:
295
    case LoadOpType::I64Load16U:
296
        jit.load16(address, dest);
297
        return;
298
    case LoadOpType::I32Load:
299
    case LoadOpType::I64Load32U:
300
        jit.load32(address, dest);
301
        return;
302
    case LoadOpType::I64Load32S:
303
        jit.load32(address, dest);
304
        jit.signExtend32ToPtr(dest, dest);
305
        return;
306
    case LoadOpType::I64Load:
307
        jit.load64(address, dest);
308
        return;
309
    }
310
    RELEASE_ASSERT_NOT_REACHED();
311
}
312
313
bool B3IRGenerator::load(LoadOpType op, uint32_t offset, ExpressionType pointer, ExpressionType& result)
314
{
315
    ASSERT(pointer->type() == Int32);
316
317
    Value* memoryOffset = m_currentBlock->appendIntConstant(m_proc, Origin(), Int32, offset);
318
319
    // We need to make sure that we add the memoryOffset to the pointer with a 32bit add
320
    // so if it overflows we stay within our mapped memory.
321
    pointer = m_currentBlock->appendNew<Value>(m_proc, Add, Origin(), pointer, memoryOffset);
322
323
    PatchpointValue* patchpoint = m_currentBlock->appendNew<PatchpointValue>(m_proc, typeForLoad(op), Origin());
324
    patchpoint->appendSomeRegister(pointer);
325
    patchpoint->numGPScratchRegisters++;
326
    patchpoint->setGenerator([=] (CCallHelpers& jit, const StackmapGenerationParams& params) {
327
        GPRReg scratch = params.gpScratch(0);
328
        GPRReg result = params[0].gpr();
329
        GPRReg ptr = params[1].gpr();
330
331
        jit.load32(bitwise_cast<char*>(&m_memory) + ModuleMemoryInformation::offsetOfSize(), scratch);
332
        CCallHelpers::Jump invalid = jit.branch32(CCallHelpers::AboveOrEqual, ptr, scratch);
333
334
        jit.move(CCallHelpers::ImmPtr(m_memory.memory()), scratch);
335
        loadForOp(jit, op, MacroAssembler::BaseIndex(scratch, ptr, MacroAssembler::Scale::TimesOne), result);
336
        CCallHelpers::Jump done = jit.jump();
337
338
        invalid.link(&jit);
339
        // Right now we crash; In the future we should do the trapping thing.
340
        jit.breakpoint();
341
        done.link(&jit);
342
    });
343
344
    result = patchpoint;
345
    return true;
346
}
347
348
inline void storeForOp(CCallHelpers& jit, StoreOpType op, MacroAssembler::BaseIndex address, GPRReg value)
349
{
350
    switch (op) {
351
    case StoreOpType::I32Store8:
352
    case StoreOpType::I64Store8:
353
        jit.store8(value, address);
354
        return;
355
    case StoreOpType::I32Store16:
356
    case StoreOpType::I64Store16:
357
        jit.store16(value, address);
358
        return;
359
    case StoreOpType::I32Store:
360
    case StoreOpType::I64Store32:
361
        jit.store32(value, address);
362
        return;
363
    case StoreOpType::I64Store:
364
        jit.store64(value, address);
365
        return;
366
    }
367
    RELEASE_ASSERT_NOT_REACHED();
368
}
369
370
bool B3IRGenerator::store(StoreOpType op, uint32_t offset, ExpressionType pointer, ExpressionType value)
371
{
372
    ASSERT(pointer->type() == Int32);
373
374
    Value* memoryOffset = m_currentBlock->appendIntConstant(m_proc, Origin(), Int32, offset);
375
376
    // We need to make sure that we add the memoryOffset to the pointer with a 32bit add
377
    // so if it overflows we stay within our mapped memory.
378
    pointer = m_currentBlock->appendNew<Value>(m_proc, Add, Origin(), pointer, memoryOffset);
379
380
    PatchpointValue* patchpoint = m_currentBlock->appendNew<PatchpointValue>(m_proc, B3::Void, Origin());
381
    patchpoint->appendSomeRegister(pointer);
382
    patchpoint->appendSomeRegister(value);
383
    patchpoint->numGPScratchRegisters++;
384
    patchpoint->setGenerator([=] (CCallHelpers& jit, const StackmapGenerationParams& params) {
385
        GPRReg scratch = params.gpScratch(0);
386
        GPRReg ptr = params[0].gpr();
387
        GPRReg val = params[1].gpr();
388
389
        jit.load32(bitwise_cast<char*>(&m_memory) + ModuleMemoryInformation::offsetOfSize(), scratch);
390
        CCallHelpers::Jump invalid = jit.branch32(CCallHelpers::AboveOrEqual, ptr, scratch);
391
392
        jit.move(CCallHelpers::ImmPtr(m_memory.memory()), scratch);
393
        storeForOp(jit, op, MacroAssembler::BaseIndex(scratch, ptr, MacroAssembler::Scale::TimesOne), val);
394
        CCallHelpers::Jump done = jit.jump();
395
396
        invalid.link(&jit);
397
        // Right now we crash; in the future we should do the trapping thing.
398
        jit.breakpoint();
399
        done.link(&jit);
400
    });
401
402
    return true;
403
}
404
241
bool B3IRGenerator::unaryOp(UnaryOpType op, ExpressionType arg, ExpressionType& result)
405
bool B3IRGenerator::unaryOp(UnaryOpType op, ExpressionType arg, ExpressionType& result)
242
{
406
{
243
    result = m_currentBlock->appendNew<Value>(m_proc, toB3Op(op), Origin(), arg);
407
    result = m_currentBlock->appendNew<Value>(m_proc, toB3Op(op), Origin(), arg);
Lines 403-412 void B3IRGenerator::dump(const Vector<ControlType>& controlStack, const Expressi a/Source/JavaScriptCore/wasm/WASMB3IRGenerator.cpp_sec5
403
567
404
} // anonymous namespace
568
} // anonymous namespace
405
569
406
std::unique_ptr<Compilation> parseAndCompile(VM& vm, Vector<uint8_t>& source, FunctionInformation info, unsigned optLevel)
570
std::unique_ptr<Compilation> parseAndCompile(VM& vm, Vector<uint8_t>& source, const ModuleMemoryInformation& memory, FunctionInformation info, unsigned optLevel)
407
{
571
{
408
    Procedure procedure;
572
    Procedure procedure;
409
    B3IRGenerator context(procedure);
573
    B3IRGenerator context(memory, procedure);
410
    FunctionParser<B3IRGenerator> parser(context, source, info);
574
    FunctionParser<B3IRGenerator> parser(context, source, info);
411
    if (!parser.parse())
575
    if (!parser.parse())
412
        RELEASE_ASSERT_NOT_REACHED();
576
        RELEASE_ASSERT_NOT_REACHED();
- a/Source/JavaScriptCore/wasm/WASMB3IRGenerator.h -1 / +1 lines
Lines 35-41 extern "C" void dumpProcedure(void*); a/Source/JavaScriptCore/wasm/WASMB3IRGenerator.h_sec1
35
35
36
namespace JSC { namespace WASM {
36
namespace JSC { namespace WASM {
37
37
38
std::unique_ptr<B3::Compilation> parseAndCompile(VM&, Vector<uint8_t>&, FunctionInformation, unsigned optLevel = 1);
38
std::unique_ptr<B3::Compilation> parseAndCompile(VM&, Vector<uint8_t>&, const ModuleMemoryInformation&, FunctionInformation, unsigned optLevel = 1);
39
39
40
} } // namespace JSC::WASM
40
} } // namespace JSC::WASM
41
41
- a/Source/JavaScriptCore/wasm/WASMFormat.h +48 lines
Lines 120-125 struct FunctionPointerTable { a/Source/JavaScriptCore/wasm/WASMFormat.h_sec1
120
    Vector<JSFunction*> functions;
120
    Vector<JSFunction*> functions;
121
};
121
};
122
122
123
class ModuleMemoryInformation {
124
    WTF_MAKE_NONCOPYABLE(ModuleMemoryInformation);
125
    WTF_MAKE_FAST_ALLOCATED;
126
public:
127
    
128
    ModuleMemoryInformation() = default;
129
    ModuleMemoryInformation(uint32_t startingSize, uint32_t maxSize)
130
        : m_size(startingSize)
131
        , m_maxSize(maxSize)
132
    {
133
        // Perhaps we should try MAP_32BIT on X86-64 since that might allow B3 to emit better Loads/Stores.
134
        void* result = mmap(nullptr, maxSize, PROT_NONE, MAP_PRIVATE | MAP_ANON, 0, 0);
135
        if (!result)
136
            return;
137
138
        if (mprotect(result, startingSize, PROT_READ | PROT_WRITE)) {
139
            munmap(result, maxSize);
140
            return;
141
        }
142
        m_memory = result;
143
    }
144
145
    ~ModuleMemoryInformation()
146
    {
147
        if (m_memory)
148
            munmap(m_memory, m_maxSize);
149
    }
150
151
    void* memory() const { return m_memory; }
152
    uint32_t size() const { return m_size; }
153
154
    bool growMemory(uint32_t newSize)
155
    {
156
        ASSERT(m_memory);
157
        if (newSize > m_maxSize)
158
            return false;
159
160
        return !mprotect(m_memory, newSize, PROT_READ | PROT_WRITE);
161
    }
162
163
    static ptrdiff_t offsetOfSize() { return OBJECT_OFFSETOF(ModuleMemoryInformation, m_size); }
164
165
private:
166
    void* m_memory { nullptr };
167
    uint32_t m_size { 0 };
168
    uint32_t m_maxSize { 0 };
169
};
170
123
struct FunctionInformation {
171
struct FunctionInformation {
124
    Signature* signature;
172
    Signature* signature;
125
    size_t start;
173
    size_t start;
- a/Source/JavaScriptCore/wasm/WASMFunctionParser.h -1 / +34 lines
Lines 49-55 public: a/Source/JavaScriptCore/wasm/WASMFunctionParser.h_sec1
49
    bool WARN_UNUSED_RETURN parse();
49
    bool WARN_UNUSED_RETURN parse();
50
50
51
private:
51
private:
52
    static const bool verbose = false;
52
    static const bool verbose = true;
53
53
54
    bool WARN_UNUSED_RETURN parseBlock();
54
    bool WARN_UNUSED_RETURN parseBlock();
55
    bool WARN_UNUSED_RETURN parseExpression(OpType);
55
    bool WARN_UNUSED_RETURN parseExpression(OpType);
Lines 154-159 bool FunctionParser<Context>::parseExpression(OpType op) a/Source/JavaScriptCore/wasm/WASMFunctionParser.h_sec2
154
        return true;
154
        return true;
155
    }
155
    }
156
156
157
    FOR_EACH_WASM_MEMORY_LOAD_OP(CREATE_CASE) {
158
        // TODO: What do I do with this?
159
        uint32_t alignment;
160
        if (!parseVarUInt32(alignment))
161
            return false;
162
163
        uint32_t offset;
164
        if (!parseVarUInt32(offset))
165
            return false;
166
167
        ExpressionType pointer = m_expressionStack.takeLast();
168
        ExpressionType result;
169
        if (!m_context.load(static_cast<LoadOpType>(op), offset, pointer, result))
170
            return false;
171
        m_expressionStack.append(result);
172
        return true;
173
    }
174
175
    FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_CASE) {
176
        // TODO: What do I do with this?
177
        uint32_t alignment;
178
        if (!parseVarUInt32(alignment))
179
            return false;
180
181
        uint32_t offset;
182
        if (!parseVarUInt32(offset))
183
            return false;
184
185
        ExpressionType value = m_expressionStack.takeLast();
186
        ExpressionType pointer = m_expressionStack.takeLast();
187
        return m_context.store(static_cast<StoreOpType>(op), offset, pointer, value);
188
    }
189
157
    case OpType::I32Const: {
190
    case OpType::I32Const: {
158
        uint32_t constant;
191
        uint32_t constant;
159
        if (!parseVarUInt32(constant))
192
        if (!parseVarUInt32(constant))
- a/Source/JavaScriptCore/wasm/WASMModuleParser.cpp -4 / +44 lines
Lines 28-37 a/Source/JavaScriptCore/wasm/WASMModuleParser.cpp_sec1
28
28
29
#if ENABLE(WEBASSEMBLY)
29
#if ENABLE(WEBASSEMBLY)
30
30
31
#include "JSWASMModule.h"
31
#include "WASMFormat.h"
32
#include "WASMFormat.h"
32
#include "WASMOps.h"
33
#include "WASMOps.h"
33
#include "WASMSections.h"
34
#include "WASMSections.h"
34
35
36
#include <sys/mman.h>
37
35
namespace JSC { namespace WASM {
38
namespace JSC { namespace WASM {
36
39
37
static const bool verbose = false;
40
static const bool verbose = false;
Lines 62-76 bool ModuleParser::parse() a/Source/JavaScriptCore/wasm/WASMModuleParser.cpp_sec2
62
        if (verbose)
65
        if (verbose)
63
            dataLogLn("Starting to parse next section at offset: ", m_offset);
66
            dataLogLn("Starting to parse next section at offset: ", m_offset);
64
67
65
        Sections::Section section = Sections::Unknown;
66
        uint8_t sectionByte;
68
        uint8_t sectionByte;
67
        if (!parseUInt7(sectionByte))
69
        if (!parseUInt7(sectionByte))
68
            return false;
70
            return false;
69
71
72
        dataLogLn("Section byte: ", sectionByte);
73
74
        Sections::Section section = Sections::Unknown;
70
        if (sectionByte) {
75
        if (sectionByte) {
71
            if (sectionByte >= Sections::Unknown)
76
            if (sectionByte < Sections::Unknown)
72
                section = Sections::Unknown;
73
            else
74
                section = static_cast<Sections::Section>(sectionByte);
77
                section = static_cast<Sections::Section>(sectionByte);
75
        } else {
78
        } else {
76
            uint32_t sectionNameLength;
79
            uint32_t sectionNameLength;
Lines 96-101 bool ModuleParser::parse() a/Source/JavaScriptCore/wasm/WASMModuleParser.cpp_sec3
96
        unsigned end = m_offset + sectionLength;
99
        unsigned end = m_offset + sectionLength;
97
100
98
        switch (section) {
101
        switch (section) {
102
103
        case Sections::Memory: {
104
            if (verbose)
105
                dataLogLn("Parsing Memory.");
106
            if (!parseMemory())
107
                return false;
108
            break;
109
        }
110
99
        case Sections::FunctionTypes: {
111
        case Sections::FunctionTypes: {
100
            if (verbose)
112
            if (verbose)
101
                dataLogLn("Parsing types.");
113
                dataLogLn("Parsing types.");
Lines 143-148 bool ModuleParser::parse() a/Source/JavaScriptCore/wasm/WASMModuleParser.cpp_sec4
143
    return true;
155
    return true;
144
}
156
}
145
157
158
bool ModuleParser::parseMemory()
159
{
160
    uint32_t maxPageNum = std::numeric_limits<uint32_t>::max() / 64 / KB;
161
    uint8_t flags;
162
    if (!parseVarUInt1(flags))
163
        return false;
164
165
    uint32_t size;
166
    if (!parseVarUInt32(size))
167
        return false;
168
    if (size > maxPageNum)
169
        return false;
170
171
    uint32_t max = maxPageNum;
172
    if (flags) {
173
        if (!parseVarUInt32(max))
174
            return false;
175
        if (size > max || max > maxPageNum)
176
            return false;
177
    }
178
179
    max *= 64 * KB;
180
    size *= 64 * KB;
181
182
    m_memory = std::make_unique<ModuleMemoryInformation>(size, max);
183
    return m_memory->memory();
184
}
185
146
bool ModuleParser::parseFunctionTypes()
186
bool ModuleParser::parseFunctionTypes()
147
{
187
{
148
    uint32_t count;
188
    uint32_t count;
- a/Source/JavaScriptCore/wasm/WASMModuleParser.h -3 / +4 lines
Lines 45-62 public: a/Source/JavaScriptCore/wasm/WASMModuleParser.h_sec1
45
45
46
    bool WARN_UNUSED_RETURN parse();
46
    bool WARN_UNUSED_RETURN parse();
47
47
48
    const Vector<FunctionInformation>& functionInformation() { return m_functions; }
48
    const Vector<FunctionInformation>& functionInformation() const { return m_functions; }
49
    std::unique_ptr<ModuleMemoryInformation>& memory() { return m_memory; }
49
50
50
private:
51
private:
52
    bool WARN_UNUSED_RETURN parseMemory();
51
    bool WARN_UNUSED_RETURN parseFunctionTypes();
53
    bool WARN_UNUSED_RETURN parseFunctionTypes();
52
    bool WARN_UNUSED_RETURN parseFunctionSignatures();
54
    bool WARN_UNUSED_RETURN parseFunctionSignatures();
53
    bool WARN_UNUSED_RETURN parseFunctionDefinitions();
55
    bool WARN_UNUSED_RETURN parseFunctionDefinitions();
54
    bool WARN_UNUSED_RETURN parseFunctionDefinition(uint32_t number);
56
    bool WARN_UNUSED_RETURN parseFunctionDefinition(uint32_t number);
55
    bool WARN_UNUSED_RETURN parseBlock();
56
    bool WARN_UNUSED_RETURN parseExpression(OpType);
57
57
58
    Vector<FunctionInformation> m_functions;
58
    Vector<FunctionInformation> m_functions;
59
    Vector<Signature> m_signatures;
59
    Vector<Signature> m_signatures;
60
    std::unique_ptr<ModuleMemoryInformation> m_memory;
60
};
61
};
61
62
62
} } // namespace JSC::WASM
63
} } // namespace JSC::WASM
- a/Source/JavaScriptCore/wasm/WASMOps.h -1 / +36 lines
Lines 103-113 namespace JSC { namespace WASM { a/Source/JavaScriptCore/wasm/WASMOps.h_sec1
103
    macro(I64LeU, 0x6d, BelowEqual) \
103
    macro(I64LeU, 0x6d, BelowEqual) \
104
104
105
105
106
#define FOR_EACH_WASM_MEMORY_LOAD_OP(macro) \
107
    macro(I32Load8S, 0x20, NA) \
108
    macro(I32Load8U, 0x21, NA) \
109
    macro(I32Load16S, 0x22, NA) \
110
    macro(I32Load16U, 0x23, NA) \
111
    macro(I64Load8S, 0x24, NA) \
112
    macro(I64Load8U, 0x25, NA) \
113
    macro(I64Load16S, 0x26, NA) \
114
    macro(I64Load16U, 0x27, NA) \
115
    macro(I64Load32S, 0x28, NA) \
116
    macro(I64Load32U, 0x29, NA) \
117
    macro(I32Load, 0x2a, NA) \
118
    macro(I64Load, 0x2b, NA) \
119
120
121
#define FOR_EACH_WASM_MEMORY_STORE_OP(macro) \
122
    macro(I32Store8, 0x2e, NA) \
123
    macro(I32Store16, 0x2f, NA) \
124
    macro(I64Store8, 0x30, NA) \
125
    macro(I64Store16, 0x31, NA) \
126
    macro(I64Store32, 0x32, NA) \
127
    macro(I32Store, 0x33, NA) \
128
    macro(I64Store, 0x34, NA) \
129
130
106
#define FOR_EACH_WASM_OP(macro) \
131
#define FOR_EACH_WASM_OP(macro) \
107
    FOR_EACH_WASM_SPECIAL_OP(macro) \
132
    FOR_EACH_WASM_SPECIAL_OP(macro) \
108
    FOR_EACH_WASM_CONTROL_FLOW_OP(macro) \
133
    FOR_EACH_WASM_CONTROL_FLOW_OP(macro) \
109
    FOR_EACH_WASM_UNARY_OP(macro) \
134
    FOR_EACH_WASM_UNARY_OP(macro) \
110
    FOR_EACH_WASM_BINARY_OP(macro)
135
    FOR_EACH_WASM_BINARY_OP(macro) \
136
    FOR_EACH_WASM_MEMORY_LOAD_OP(macro) \
137
    FOR_EACH_WASM_MEMORY_STORE_OP(macro)
111
138
112
#define CREATE_ENUM_VALUE(name, id, b3op) name = id,
139
#define CREATE_ENUM_VALUE(name, id, b3op) name = id,
113
140
Lines 123-128 enum class UnaryOpType : uint8_t { a/Source/JavaScriptCore/wasm/WASMOps.h_sec2
123
    FOR_EACH_WASM_UNARY_OP(CREATE_ENUM_VALUE)
150
    FOR_EACH_WASM_UNARY_OP(CREATE_ENUM_VALUE)
124
};
151
};
125
152
153
enum class LoadOpType : uint8_t {
154
    FOR_EACH_WASM_MEMORY_LOAD_OP(CREATE_ENUM_VALUE)
155
};
156
157
enum class StoreOpType : uint8_t {
158
    FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_ENUM_VALUE)
159
};
160
126
#undef CREATE_ENUM_VALUE
161
#undef CREATE_ENUM_VALUE
127
162
128
inline bool isControlOp(OpType op)
163
inline bool isControlOp(OpType op)
- a/Source/JavaScriptCore/wasm/WASMPlan.cpp -1 / +2 lines
Lines 53-60 Plan::Plan(VM& vm, Vector<uint8_t> source) a/Source/JavaScriptCore/wasm/WASMPlan.cpp_sec1
53
    for (const FunctionInformation& info : moduleParser.functionInformation()) {
53
    for (const FunctionInformation& info : moduleParser.functionInformation()) {
54
        if (verbose)
54
        if (verbose)
55
            dataLogLn("Processing funcion starting at: ", info.start, " and ending at: ", info.end);
55
            dataLogLn("Processing funcion starting at: ", info.start, " and ending at: ", info.end);
56
        result.append(parseAndCompile(vm, source, info));
56
        result.append(parseAndCompile(vm, source, *moduleParser.memory(), info));
57
    }
57
    }
58
    memory = WTFMove(moduleParser.memory());
58
}
59
}
59
60
60
} } // namespace JSC::WASM
61
} } // namespace JSC::WASM
- a/Source/JavaScriptCore/wasm/WASMPlan.h +2 lines
Lines 29-34 a/Source/JavaScriptCore/wasm/WASMPlan.h_sec1
29
29
30
#include "CompilationResult.h"
30
#include "CompilationResult.h"
31
#include "VM.h"
31
#include "VM.h"
32
#include "WASMFormat.h"
32
#include <wtf/ThreadSafeRefCounted.h>
33
#include <wtf/ThreadSafeRefCounted.h>
33
#include <wtf/Vector.h>
34
#include <wtf/Vector.h>
34
35
Lines 46-51 public: a/Source/JavaScriptCore/wasm/WASMPlan.h_sec2
46
    JS_EXPORT_PRIVATE Plan(VM&, Vector<uint8_t> source);
47
    JS_EXPORT_PRIVATE Plan(VM&, Vector<uint8_t> source);
47
48
48
    Vector<std::unique_ptr<B3::Compilation>> result;
49
    Vector<std::unique_ptr<B3::Compilation>> result;
50
    std::unique_ptr<ModuleMemoryInformation> memory;
49
};
51
};
50
52
51
} } // namespace JSC::WASM
53
} } // namespace JSC::WASM
- a/Source/JavaScriptCore/wasm/WASMSections.h +1 lines
Lines 33-38 struct Sections { a/Source/JavaScriptCore/wasm/WASMSections.h_sec1
33
    enum Section : uint8_t {
33
    enum Section : uint8_t {
34
        FunctionTypes = 1,
34
        FunctionTypes = 1,
35
        Signatures = 3,
35
        Signatures = 3,
36
        Memory = 5,
36
        Definitions = 10,
37
        Definitions = 10,
37
        Unknown
38
        Unknown
38
    };
39
    };

Return to Bug 161710