Closed Bug 1244405 Opened 4 years ago Closed 4 years ago

Baldr: add memory section

Categories

(Core :: JavaScript Engine, defect)

defect
Not set

Tracking

()

RESOLVED FIXED
mozilla47
Tracking Status
firefox47 --- fixed

People

(Reporter: luke, Assigned: luke)

References

Details

Attachments

(6 files, 4 obsolete files)

This bug just adds the memory section; load/store to access memory will be separate.
Attached patch memory-section (WIP) (obsolete) — Splinter Review
Needs tests, comments and to be split up into patches.
Attached patch move-constantsSplinter Review
This patch moves some constants and static asserts out of AsmJS.* and renames some heap constants from AsmJS* to Wasm*.
Attachment #8713934 - Attachment is obsolete: true
Attachment #8714359 - Flags: review?(bbouvier)
Attached patch wasm-bufferSplinter Review
This patch factors an ArrayBuffer::createForWasm used in the next patch.
Attachment #8714360 - Flags: review?(bbouvier)
Attached patch memory-section (obsolete) — Splinter Review
Pretty easy, given preceding.
Attachment #8714428 - Flags: review?(bbouvier)
Attached patch fix-export-name (obsolete) — Splinter Review
This patch makes generated exported function names match the names generated for stack-walking.  Doing this led to factoring out the building of ExportMap from AsmJS/Wasm.cpp into ModuleGenerator which simplifies the code a bit.  Technically unrelated but prepares for the next patch which adds a second kind of export (memory).
Attachment #8714625 - Flags: review?(bbouvier)
Attached patch memory-export (obsolete) — Splinter Review
This adds '(export "foo" memory)' as a second kind of export which has a nice symmetry with normal function imports.
Attachment #8714626 - Flags: review?(bbouvier)
Attached patch fix-export-nameSplinter Review
Actually, the 'DynamicLinkData' was not that useful of an abstraction when paired with the change in this patch and the change in the next patch, so it is removed.
Attachment #8714625 - Attachment is obsolete: true
Attachment #8714625 - Flags: review?(bbouvier)
Attachment #8714836 - Flags: review?(bbouvier)
Attached patch memory-sectionSplinter Review
This version creates the buffer when decoding the memory section. This will be useful when adding data segments since it will allow the data segments to be copied directly into the buffer, avoiding an intermediate copy of potentially large segments.
Attachment #8714428 - Attachment is obsolete: true
Attachment #8714428 - Flags: review?(bbouvier)
Attachment #8714837 - Flags: review?(bbouvier)
Attached patch memory-exportSplinter Review
Rebased.
Attachment #8714626 - Attachment is obsolete: true
Attachment #8714626 - Flags: review?(bbouvier)
Attachment #8714838 - Flags: review?(bbouvier)
Attached patch memory-segmentsSplinter Review
Last patch for this bug adds (segment) for initializing memory.  Spreading the reviews around a bit...
Attachment #8715031 - Flags: review?(sunfish)
Comment on attachment 8715031 [details] [diff] [review]
memory-segments

Review of attachment 8715031 [details] [diff] [review]:
-----------------------------------------------------------------

::: js/src/asmjs/WasmText.cpp
@@ +593,5 @@
>      return c == '_' || IsWasmDigit(c) || IsWasmLetter(c);
>  }
>  
> +static bool
> +IsHexDigit(char c, uint8_t* value)

value can be a pointer-to-const.

@@ +595,5 @@
>  
> +static bool
> +IsHexDigit(char c, uint8_t* value)
> +{
> +    c = tolower(c);

tolower is locale-sensitive, which isn't desirable here. Just as we have our own IsWasmLetter etc., we should do our own thing here.

@@ +631,5 @@
> +    switch (*cur) {
> +      case 'n': u8 = '\n'; break;
> +      case 't': u8 = '\t'; break;
> +      case '\\': u8 = '\\'; break;
> +      case '\"': u8 = '\"'; break;

The spec repo also recognizes '\'' in this context.

@@ +2075,5 @@
> +        MOZ_ALWAYS_TRUE(ConsumeTextByte(&cur, end, &byte));
> +        bytes.infallibleAppend(byte);
> +    }
> +
> +    if (!e.writeVarU32(bytes.length()))

This implicitly converts bytes.length() to uint32_t, but I'm not currently able to find any code which limits the size of the data held in bytes to the uint32_t range. Does this need a length check? Or if it doesn't, an assert here would be good.

@@ +2099,5 @@
> +    size_t offset;
> +    if (!e.startSection(&offset))
> +        return false;
> +
> +    if (!e.writeVarU32(segments.length()))

Similarly, this converts to uint32_t, and the number of segments doesn't appear explicitly bounded.
Attachment #8715031 - Flags: review?(sunfish) → review+
Comment on attachment 8715031 [details] [diff] [review]
memory-segments

Review of attachment 8715031 [details] [diff] [review]:
-----------------------------------------------------------------

::: js/src/asmjs/WasmText.cpp
@@ +593,5 @@
>      return c == '_' || IsWasmDigit(c) || IsWasmLetter(c);
>  }
>  
> +static bool
> +IsHexDigit(char c, uint8_t* value)

Oops, I misread the code here. Ignore this comment :).
(In reply to Dan Gohman [:sunfish] from comment #11)
> > +static bool
> > +IsHexDigit(char c, uint8_t* value)
> > +{
> > +    c = tolower(c);
> 
> tolower is locale-sensitive, which isn't desirable here.

And...thread-unsafe!  
Hah, and here I thought I was just saving myself an extra if-then.  I'll just add an explicit 'A'-'F' case :)
(In reply to Jeff Walden [:Waldo] (remove +bmo to email) from comment #13)
> And...thread-unsafe!

Let the record reflect that my comment as I wrote it had a U+1F4AF HUNDRED POINTS SYMBOL after this, that Bugzilla ate.  :-)  Known bug in Bugzilla, on file.
Comment on attachment 8714359 [details] [diff] [review]
move-constants

Review of attachment 8714359 [details] [diff] [review]:
-----------------------------------------------------------------

I don't understand how you choose to replace AsmJSPageSize by either wasm::PageSize or gc::SystemPageSize(). Can you please explain this?

::: js/src/asmjs/AsmJS.cpp
@@ +8267,5 @@
>      // See EstablishPreconditions.
>  #if defined(JS_CODEGEN_NONE) || defined(JS_CODEGEN_ARM64)
>      bool available = false;
>  #else
> +    bool available = HasCompilerSupport(cx) &&

You can remove the #ifdef because HasCompilerSupport will return false for NONE and ARM64 \o/

::: js/src/asmjs/Wasm.h
@@ +33,5 @@
> +HasCompilerSupport(ExclusiveContext* cx);
> +
> +// The WebAssembly spec hard-codes the virtual page size to be 64KiB and limits
> +// forces the linear memory to always be a multiple of 64KiB.
> +static const unsigned PageSize = 64 * 1024;

As this is larger than the requirements for both MIPS platforms, I assume this won't be an issue for the MIPS platforms?

@@ +40,5 @@
> +// reserved and the subrange [0, memory_size) is given readwrite permission.
> +// See also static asserts in MIRGenerator::foldableOffsetRange.
> +#ifdef ASMJS_MAY_USE_SIGNAL_HANDLERS_FOR_OOB
> +static const uint64_t Uint32Range = uint64_t(UINT32_MAX) + 1;
> +static const uint64_t MappedSize = 2 * Uint32Range + PageSize;

In the previous version, it was Uint32Range + constants, now 2 * Uint32Range + constants. Is that intended?

::: js/src/vm/ArrayBufferObject.cpp
@@ +434,5 @@
>      }
>  #   if defined(MOZ_VALGRIND) && defined(VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE)
>      // Tell Valgrind/Memcheck to not report accesses in the inaccessible region.
>      VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE((unsigned char*)data + validLength,
> +                                                   wasm::MappedSize-validLength);

nit: pre-existing: can you add spaces before/after the minus operator, please?
(In reply to Benjamin Bouvier [:bbouvier] from comment #16)
> I don't understand how you choose to replace AsmJSPageSize by either
> wasm::PageSize or gc::SystemPageSize(). Can you please explain this?

The WebAssembly defines a virtual page size (64KiB) that wasm linear memory must also be a multiple of.  That's also true for asm.js memory sizes.  AsmJSPageSize was an internal constant that was asserted to be the same as the page size... but it didn't need to be, I could've just used the dynamically-determined page size (as done in this patch).  This is good b/c we're starting to see a variety of page sizes (16KiB).

> As this is larger than the requirements for both MIPS platforms, I assume
> this won't be an issue for the MIPS platforms?

Right.  The only interaction with OS page size is that, if we want to use mprotect/signals, we need the OS page size to be smaller and divisor of wasm::PageSize which should almost always be the case.

> > +#ifdef ASMJS_MAY_USE_SIGNAL_HANDLERS_FOR_OOB
> > +static const uint64_t Uint32Range = uint64_t(UINT32_MAX) + 1;
> > +static const uint64_t MappedSize = 2 * Uint32Range + PageSize;
> 
> In the previous version, it was Uint32Range + constants, now 2 * Uint32Range
> + constants. Is that intended?

Yes: AsmJSImmediateRange was effectively Uint32Range so 2 * Uint32Range is just nice way to describe the range without pulling in JIT headers.  (Statically asserted, of course.)
Comment on attachment 8714360 [details] [diff] [review]
wasm-buffer

Review of attachment 8714360 [details] [diff] [review]:
-----------------------------------------------------------------

Much cleaner, thank you! Sorry the review took a bit long, but the initial code was very hard to read in splinter, and comparing before/after impls wasn't obvious. Now it's much better.

::: js/src/vm/ArrayBufferObject.cpp
@@ +374,5 @@
> +        return nullptr;
> +
> +    if (!VirtualAlloc(data, numBytes, MEM_COMMIT, PAGE_READWRITE)) {
> +        VirtualFree(data, 0, MEM_RELEASE);
> +        MemProfiler::RemoveNative(data);

pre-existing: Not too sure about this, but if you use RemoveNative, shouldn't you have used SampleNative in the first place? If so, maybe you can move the SampleNative call after this branch, so that you don't need to Sample/Remove in the case the second VirtualAlloc call failed.

@@ +398,5 @@
> +        return nullptr;
> +
> +    if (mprotect(data, numBytes, PROT_READ | PROT_WRITE)) {
> +        munmap(data, wasm::MappedSize);
> +        MemProfiler::RemoveNative(data);

ditto

@@ +437,5 @@
> +            return nullptr;
> +        }
> +
> +        BufferContents contents = BufferContents::create<WASM_MAPPED>(data);
> +        ArrayBufferObject* buffer = ArrayBufferObject::create(cx, numBytes, contents);

You could use auto* here too, if you wanted.

@@ +449,5 @@
> +        MOZ_CRASH("shouldn't be using signal handlers for out-of-bounds");
> +#endif
> +    }
> +
> +    auto buffer = ArrayBufferObject::create(cx, numBytes);

nit: auto*, to explicitly indicate pointer?
Attachment #8714360 - Flags: review?(bbouvier) → review+
Comment on attachment 8714359 [details] [diff] [review]
move-constants

Review of attachment 8714359 [details] [diff] [review]:
-----------------------------------------------------------------

Thank you for the explanation!
Attachment #8714359 - Flags: review?(bbouvier) → review+
Comment on attachment 8714836 [details] [diff] [review]
fix-export-name

Review of attachment 8714836 [details] [diff] [review]:
-----------------------------------------------------------------

Nice!
Attachment #8714836 - Flags: review?(bbouvier) → review+
Comment on attachment 8714837 [details] [diff] [review]
memory-section

Review of attachment 8714837 [details] [diff] [review]:
-----------------------------------------------------------------

Cool.

::: js/src/asmjs/WasmText.cpp
@@ +253,5 @@
>  };
>  
> +class WasmAstMemory : public WasmAstNode
> +{
> +    uint32_t initial_;

If you haven't read Wasm.cpp before this file, you might wonder what this "initial_" is. Could we name it initialSize_ or just size_, please?

::: js/src/jit-test/tests/wasm/basic.js
@@ +134,5 @@
> +assertErrorMessage(() => wasmEvalText('(module (memory 0))'), Error, /not a multiple of 0x10000/);
> +assertErrorMessage(() => wasmEvalText('(module (memory 1))'), Error, /not a multiple of 0x10000/);
> +assertErrorMessage(() => wasmEvalText('(module (memory 65535))'), Error, /not a multiple of 0x10000/);
> +assertErrorMessage(() => wasmEvalText('(module (memory 131071))'), Error, /not a multiple of 0x10000/);
> +assertErrorMessage(() => wasmEvalText('(module (memory 2147483648))'), Error, /initial memory size too big/);

Can you check with the biggest size which is less than INT32_MAX and a multiple of 0x10000, please?
Attachment #8714837 - Flags: review?(bbouvier) → review+
Comment on attachment 8714838 [details] [diff] [review]
memory-export

Review of attachment 8714838 [details] [diff] [review]:
-----------------------------------------------------------------

Nice! I like the refactorings as well. A few more test cases and this is good to land.

::: js/src/asmjs/Wasm.cpp
@@ +547,5 @@
>      return mg.declareExport(Move(fieldName), funcIndex);
>  }
>  
>  static bool
> +DecodeMemoryExport(JSContext* cx, Decoder& d, ModuleGenerator& mg, CStringSet* dupSet)

dupSet is unused, and you probably don't want to allow several exports with the same name here too (probably the name check can be made common with DecodeFunctionExport). Can you add tests where there are memory exports of the same name, and one when two exports (1 function, 1 memory) have the same name too?

::: js/src/asmjs/WasmGenerator.cpp
@@ +372,5 @@
>          return exportMap_->fieldsToExports.append(p->value());
>      }
>  
>      uint32_t newExportIndex = module_->exports.length();
> +    if (newExportIndex == ExportMap::MemoryExport)

You could probably instead MOZ_ASSERT, because there's already a limit on the number of .exports (MaxExports in WasmBinary.h)

::: js/src/asmjs/WasmModule.cpp
@@ +53,5 @@
>  using mozilla::PodZero;
>  using mozilla::Swap;
>  using JS::GenericNaN;
>  
> +const uint32_t ExportMap::MemoryExport;

Is this declaration necessary?

@@ +1115,5 @@
>          if (!*fieldName) {
>              MOZ_ASSERT(!exportObj);
>              uint32_t exportIndex = exportMap.fieldsToExports[fieldIndex];
> +            if (exportIndex == ExportMap::MemoryExport) {
> +                exportObj.set(heap);

Maybe MOZ_ASSERT(heap); ?

::: js/src/asmjs/WasmText.cpp
@@ +1361,5 @@
>      return true;
>  }
>  
>  static bool
> +EncodeCString(Encoder& e, TwoByteChars twoByteChars)

Nice!

@@ +1482,5 @@
> +            if (!EncodeFunctionExport(e, *exp))
> +                return false;
> +            break;
> +          case WasmAstExportKind::Memory:
> +            if (!EncodeMemoryExport(e, *exp)) 

nit: trailing whitespace

::: js/src/jit-test/tests/wasm/basic.js
@@ +151,5 @@
> +
> +var obj = wasmEvalText('(module (memory 65536) (func (result i32) (i32.const 42)) (export "" memory) (export "a" 0) (export "b" 0))');
> +assertEq(obj instanceof ArrayBuffer, true);
> +assertEq(obj.a instanceof Function, true);
> +assertEq(obj.b instanceof Function, true);

Could we have a test in which memory is exported twice, and we assertEq the two exports are the same?
Attachment #8714838 - Flags: review?(bbouvier) → review+
(In reply to Benjamin Bouvier [:bbouvier] from comment #22)
Good points!

> > +const uint32_t ExportMap::MemoryExport;
> 
> Is this declaration necessary?

Yeah, it's C++ minutia: the in-class 'static const uint32_t MemoryExport = UINT32_MAX' is a *declaration* and *initialization* but not a *definition*.  Most of the time this doesn't matter b/c the initializer gets inlined and no definition is required for linking.  But if you use the static member variable in a way that can't be inlined (anything that ends up wanting a pointer; but in GCC often random stuff that you'd think would be inlined) it requires a definition which is necessarily out-of-line.  Blech.
Blocks: 1247104
You need to log in before you can comment on or make changes to this bug.