Bug 1276028 - Baldr: store names as offsets into bytecode (r=bbouvier)
☠☠ backed out by 284a185682c0 ☠ ☠
authorLuke Wagner <luke@mozilla.com>
Sun, 19 Jun 2016 00:29:11 +0100
changeset 302059 f1209b27c6a8cb72768d01e2d4a6b318d6fd60c4
parent 302058 ea578e2813f422d30631ef8dd27c8763ec8bad37
child 302060 7813b8ae63b9fb5689ddd766b950d1bc680b0950
push id78564
push userlwagner@mozilla.com
push dateSat, 18 Jun 2016 23:30:28 +0000
treeherdermozilla-inbound@f1209b27c6a8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbbouvier
bugs1276028
milestone50.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1276028 - Baldr: store names as offsets into bytecode (r=bbouvier)
js/public/CharacterEncoding.h
js/src/asmjs/AsmJS.cpp
js/src/asmjs/Wasm.cpp
js/src/asmjs/WasmBinary.h
js/src/asmjs/WasmBinaryToAST.cpp
js/src/asmjs/WasmCode.cpp
js/src/asmjs/WasmCode.h
js/src/asmjs/WasmFrameIterator.cpp
js/src/asmjs/WasmGenerator.cpp
js/src/asmjs/WasmGenerator.h
js/src/asmjs/WasmInstance.cpp
js/src/asmjs/WasmInstance.h
js/src/asmjs/WasmModule.cpp
js/src/asmjs/WasmTypes.h
js/src/jit-test/tests/wasm/binary.js
--- a/js/public/CharacterEncoding.h
+++ b/js/public/CharacterEncoding.h
@@ -169,16 +169,23 @@ class ConstTwoByteChars : public mozilla
  * respectively. If allocation fails, an OOM error will be set and the method
  * will return a nullptr chars (which can be tested for with the ! operator).
  * This method cannot trigger GC.
  */
 extern Latin1CharsZ
 LossyTwoByteCharsToNewLatin1CharsZ(js::ExclusiveContext* cx,
                                    const mozilla::Range<const char16_t> tbchars);
 
+inline Latin1CharsZ
+LossyTwoByteCharsToNewLatin1CharsZ(js::ExclusiveContext* cx, const char16_t* begin, size_t length)
+{
+    const mozilla::Range<const char16_t> tbchars(begin, length);
+    return JS::LossyTwoByteCharsToNewLatin1CharsZ(cx, tbchars);
+}
+
 template <typename CharT>
 extern UTF8CharsZ
 CharsToNewUTF8CharsZ(js::ExclusiveContext* maybeCx, const mozilla::Range<CharT> chars);
 
 uint32_t
 Utf8ToOneUcs4Char(const uint8_t* utf8Buffer, int utf8Length);
 
 /*
--- a/js/src/asmjs/AsmJS.cpp
+++ b/js/src/asmjs/AsmJS.cpp
@@ -276,16 +276,17 @@ struct AsmJSMetadataCacheablePod
     AsmJSMetadataCacheablePod() { PodZero(this); }
 };
 
 struct js::AsmJSMetadata : Metadata, AsmJSMetadataCacheablePod
 {
     AsmJSGlobalVector       asmJSGlobals;
     AsmJSImportVector       asmJSImports;
     AsmJSExportVector       asmJSExports;
+    CacheableCharsVector    asmJSFuncNames;
     CacheableChars          globalArgumentName;
     CacheableChars          importArgumentName;
     CacheableChars          bufferArgumentName;
 
     CacheResult             cacheResult;
 
     // These values are not serialized since they are relative to the
     // containing script which can be different between serialization and
@@ -321,16 +322,31 @@ struct js::AsmJSMetadata : Metadata, Asm
         return scriptSource.get()->mutedErrors();
     }
     const char16_t* displayURL() const override {
         return scriptSource.get()->hasDisplayURL() ? scriptSource.get()->displayURL() : nullptr;
     }
     ScriptSource* maybeScriptSource() const override {
         return scriptSource.get();
     }
+    bool getFuncName(JSContext* cx, const Bytes*, uint32_t funcIndex, TwoByteName* name) const override {
+        const char* p = asmJSFuncNames[funcIndex].get();
+        UTF8Chars utf8(p, strlen(p));
+
+        size_t twoByteLength;
+        UniqueTwoByteChars chars(JS::UTF8CharsToNewTwoByteCharsZ(cx, utf8, &twoByteLength).get());
+        if (!chars)
+            return false;
+
+        if (!name->initLengthUninitialized(twoByteLength))
+            return false;
+
+        PodCopy(name->begin(), chars.get(), twoByteLength);
+        return true;
+    }
 
     AsmJSMetadataCacheablePod& pod() { return *this; }
     const AsmJSMetadataCacheablePod& pod() const { return *this; }
 
     WASM_DECLARE_SERIALIZABLE_OVERRIDE(AsmJSMetadata)
 };
 
 typedef RefPtr<AsmJSMetadata> MutableAsmJSMetadata;
@@ -2254,23 +2270,22 @@ class MOZ_STACK_CLASS ModuleValidator
     }
     bool finishFunctionBodies() {
         return mg_.finishFuncDefs();
     }
     UniqueModule finish() {
         if (!arrayViews_.empty())
             mg_.initHeapUsage(atomicsPresent_ ? HeapUsage::Shared : HeapUsage::Unshared);
 
-        CacheableCharsVector funcNames;
+        MOZ_ASSERT(asmJSMetadata_->asmJSFuncNames.empty());
         for (const Func* func : functions_) {
             CacheableChars funcName = StringToNewUTF8CharsZ(cx_, *func->name());
-            if (!funcName || !funcNames.emplaceBack(Move(funcName)))
+            if (!funcName || !asmJSMetadata_->asmJSFuncNames.emplaceBack(Move(funcName)))
                 return nullptr;
         }
-        mg_.setFuncNames(Move(funcNames));
 
         uint32_t endBeforeCurly = tokenStream().currentToken().pos.end;
         asmJSMetadata_->srcLength = endBeforeCurly - asmJSMetadata_->srcStart;
 
         TokenPos pos;
         JS_ALWAYS_TRUE(tokenStream().peekTokenPos(&pos, TokenStream::Operand));
         uint32_t endAfterCurly = pos.end;
         asmJSMetadata_->srcLengthWithRightBrace = endAfterCurly - asmJSMetadata_->srcStart;
@@ -8046,57 +8061,61 @@ AsmJSGlobal::sizeOfExcludingThis(MallocS
 size_t
 AsmJSMetadata::serializedSize() const
 {
     return Metadata::serializedSize() +
            sizeof(pod()) +
            SerializedVectorSize(asmJSGlobals) +
            SerializedPodVectorSize(asmJSImports) +
            SerializedPodVectorSize(asmJSExports) +
+           SerializedVectorSize(asmJSFuncNames) +
            globalArgumentName.serializedSize() +
            importArgumentName.serializedSize() +
            bufferArgumentName.serializedSize();
 }
 
 uint8_t*
 AsmJSMetadata::serialize(uint8_t* cursor) const
 {
     cursor = Metadata::serialize(cursor);
     cursor = WriteBytes(cursor, &pod(), sizeof(pod()));
     cursor = SerializeVector(cursor, asmJSGlobals);
     cursor = SerializePodVector(cursor, asmJSImports);
     cursor = SerializePodVector(cursor, asmJSExports);
+    cursor = SerializeVector(cursor, asmJSFuncNames);
     cursor = globalArgumentName.serialize(cursor);
     cursor = importArgumentName.serialize(cursor);
     cursor = bufferArgumentName.serialize(cursor);
     return cursor;
 }
 
 const uint8_t*
 AsmJSMetadata::deserialize(ExclusiveContext* cx, const uint8_t* cursor)
 {
     (cursor = Metadata::deserialize(cx, cursor)) &&
     (cursor = ReadBytes(cursor, &pod(), sizeof(pod()))) &&
     (cursor = DeserializeVector(cx, cursor, &asmJSGlobals)) &&
     (cursor = DeserializePodVector(cx, cursor, &asmJSImports)) &&
     (cursor = DeserializePodVector(cx, cursor, &asmJSExports)) &&
+    (cursor = DeserializeVector(cx, cursor, &asmJSFuncNames)) &&
     (cursor = globalArgumentName.deserialize(cx, cursor)) &&
     (cursor = importArgumentName.deserialize(cx, cursor)) &&
     (cursor = bufferArgumentName.deserialize(cx, cursor));
     cacheResult = CacheResult::Hit;
     return cursor;
 }
 
 size_t
 AsmJSMetadata::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
 {
     return Metadata::sizeOfExcludingThis(mallocSizeOf) +
            SizeOfVectorExcludingThis(asmJSGlobals, mallocSizeOf) +
            asmJSImports.sizeOfExcludingThis(mallocSizeOf) +
            asmJSExports.sizeOfExcludingThis(mallocSizeOf) +
+           SizeOfVectorExcludingThis(asmJSFuncNames, mallocSizeOf) +
            globalArgumentName.sizeOfExcludingThis(mallocSizeOf) +
            importArgumentName.sizeOfExcludingThis(mallocSizeOf) +
            bufferArgumentName.sizeOfExcludingThis(mallocSizeOf);
 }
 
 namespace {
 
 class ModuleChars
--- a/js/src/asmjs/Wasm.cpp
+++ b/js/src/asmjs/Wasm.cpp
@@ -682,32 +682,32 @@ CheckTypeForJS(JSContext* cx, Decoder& d
         return Fail(cx, d, "cannot import/export SIMD return type");
 
     return true;
 }
 
 static UniqueChars
 MaybeDecodeName(JSContext* cx, Decoder& d)
 {
-    Bytes bytes;
-    if (!d.readBytes(&bytes))
+    uint32_t numBytes;
+    if (!d.readVarU32(&numBytes))
+        return nullptr;
+
+    const uint8_t* bytes;
+    if (!d.readBytes(numBytes, &bytes))
         return nullptr;
 
-    // Rejecting a name if null or non-ascii character was found.
-    // TODO Relax the requirement and allow valid non-null valid UTF8 characters.
-    for (size_t i = 0; i < bytes.length(); i++) {
-        const uint8_t ch = bytes[i];
-        if (ch == 0 || ch >= 128)
-            return nullptr;
-    }
-
-    if (!bytes.append(0))
+    UniqueChars name(cx->pod_malloc<char>(numBytes + 1));
+    if (!name)
         return nullptr;
 
-    return UniqueChars((char*)bytes.extractOrCopyRawBuffer());
+    memcpy(name.get(), bytes, numBytes);
+    name[numBytes] = '\0';
+
+    return name;
 }
 
 static bool
 DecodeImport(JSContext* cx, Decoder& d, ModuleGeneratorData* init, ImportNameVector* importNames)
 {
     const DeclaredSig* sig = nullptr;
     if (!DecodeSignatureIndex(cx, d, *init, &sig))
         return false;
@@ -1006,17 +1006,17 @@ DecodeDataSection(JSContext* cx, Decoder
         uint32_t numBytes;
         if (!d.readVarU32(&numBytes))
             return Fail(cx, d, "expected segment size");
 
         if (dstOffset > heapLength || heapLength - dstOffset < numBytes)
             return Fail(cx, d, "data segment does not fit in memory");
 
         const uint8_t* src;
-        if (!d.readBytesRaw(numBytes, &src))
+        if (!d.readBytes(numBytes, &src))
             return Fail(cx, d, "data segment shorter than declared");
 
         memcpy(heapBase + dstOffset, src, numBytes);
         prevEnd = dstOffset + numBytes;
     }
 
     if (!d.finishSection(sectionStart, sectionSize))
         return Fail(cx, d, "data section byte size mismatch");
@@ -1030,40 +1030,43 @@ MaybeDecodeNameSectionBody(JSContext* cx
 {
     uint32_t numFuncNames;
     if (!d.readVarU32(&numFuncNames))
         return false;
 
     if (numFuncNames > MaxFuncs)
         return false;
 
-    CacheableCharsVector funcNames;
+    NameInBytecodeVector funcNames;
     if (!funcNames.resize(numFuncNames))
         return false;
 
     for (uint32_t i = 0; i < numFuncNames; i++) {
-        UniqueChars funcName = MaybeDecodeName(cx, d);
-        if (!funcName)
+        uint32_t numBytes;
+        if (!d.readVarU32(&numBytes))
             return false;
 
-        funcNames[i] = strlen(funcName.get()) ? Move(funcName) : nullptr;
+        NameInBytecode name;
+        name.offset = d.currentOffset();
+        name.length = numBytes;
+        funcNames[i] = name;
 
-        // Skipping local names for a function.
+        if (!d.readBytes(numBytes))
+            return false;
+
+        // Skip local names for a function.
         uint32_t numLocals;
         if (!d.readVarU32(&numLocals))
             return false;
-
         for (uint32_t j = 0; j < numLocals; j++) {
-            UniqueChars localName = MaybeDecodeName(cx, d);
-            if (!localName) {
-                cx->clearPendingException();
+            uint32_t numBytes;
+            if (!d.readVarU32(&numBytes))
                 return false;
-            }
-
-            Unused << localName;
+            if (!d.readBytes(numBytes))
+                return false;
         }
     }
 
     mg.setFuncNames(Move(funcNames));
     return true;
 }
 
 static bool
--- a/js/src/asmjs/WasmBinary.h
+++ b/js/src/asmjs/WasmBinary.h
@@ -740,29 +740,17 @@ class Decoder
         if (u8 == UINT8_MAX)
             return false;
         *expr = Expr(u8 + UINT8_MAX);
         return true;
     }
 
     // See writeBytes comment.
 
-    MOZ_MUST_USE bool readBytes(Bytes* bytes) {
-        uint32_t numBytes;
-        if (!readVarU32(&numBytes))
-            return false;
-        if (bytesRemain() < numBytes)
-            return false;
-        if (!bytes->resize(numBytes))
-            return false;
-        memcpy(bytes->begin(), cur_, numBytes);
-        cur_ += numBytes;
-        return true;
-    }
-    MOZ_MUST_USE bool readBytesRaw(uint32_t numBytes, const uint8_t** bytes) {
+    MOZ_MUST_USE bool readBytes(uint32_t numBytes, const uint8_t** bytes = nullptr) {
         if (bytes)
             *bytes = cur_;
         if (bytesRemain() < numBytes)
             return false;
         cur_ += numBytes;
         return true;
     }
 
--- a/js/src/asmjs/WasmBinaryToAST.cpp
+++ b/js/src/asmjs/WasmBinaryToAST.cpp
@@ -1151,23 +1151,28 @@ AstDecodeTableSection(AstDecodeContext& 
         return AstDecodeFail(c, "table section byte size mismatch");
 
     return true;
 }
 
 static bool
 AstDecodeName(AstDecodeContext& c, AstName* name)
 {
-    Bytes bytes;
-    if (!c.d.readBytes(&bytes))
+    uint32_t length;
+    if (!c.d.readVarU32(&length))
         return false;
-    size_t length = bytes.length();
-    char16_t *buffer = static_cast<char16_t *>(c.lifo.alloc(length * sizeof(char16_t)));
+
+    const uint8_t* bytes;
+    if (!c.d.readBytes(length, &bytes))
+        return false;
+
+    char16_t* buffer = static_cast<char16_t *>(c.lifo.alloc(length * sizeof(char16_t)));
     for (size_t i = 0; i < length; i++)
         buffer[i] = bytes[i];
+
     *name = AstName(buffer, length);
     return true;
 }
 
 static bool
 AstDecodeImport(AstDecodeContext& c, uint32_t importIndex, AstImport** import)
 {
     uint32_t sigIndex = AstNoIndex;
@@ -1483,17 +1488,17 @@ AstDecodeDataSection(AstDecodeContext &c
         uint32_t numBytes;
         if (!c.d.readVarU32(&numBytes))
             return AstDecodeFail(c, "expected segment size");
 
         if (dstOffset > heapLength || heapLength - dstOffset < numBytes)
             return AstDecodeFail(c, "data segment does not fit in memory");
 
         const uint8_t* src;
-        if (!c.d.readBytesRaw(numBytes, &src))
+        if (!c.d.readBytes(numBytes, &src))
             return AstDecodeFail(c, "data segment shorter than declared");
 
         char16_t *buffer = static_cast<char16_t *>(c.lifo.alloc(numBytes * sizeof(char16_t)));
         for (size_t i = 0; i < numBytes; i++)
             buffer[i] = src[i];
 
         AstName name(buffer, numBytes);
         AstSegment* segment = new(c.lifo) AstSegment(dstOffset, name);
--- a/js/src/asmjs/WasmCode.cpp
+++ b/js/src/asmjs/WasmCode.cpp
@@ -118,17 +118,18 @@ SpecializeToHeap(CodeSegment& cs, const 
         uint32_t disp = reinterpret_cast<uint32_t>(X86Encoding::GetPointer(addr));
         MOZ_ASSERT(disp <= INT32_MAX);
         X86Encoding::SetPointer(addr, (void*)(heapBase + disp));
     }
 #endif
 }
 
 static bool
-SendCodeRangesToProfiler(ExclusiveContext* cx, CodeSegment& cs, const Metadata& metadata)
+SendCodeRangesToProfiler(JSContext* cx, CodeSegment& cs, const Bytes& bytecode,
+                         const Metadata& metadata)
 {
     bool enabled = false;
 #ifdef JS_ION_PERF
     enabled |= PerfFuncEnabled();
 #endif
 #ifdef MOZ_VTUNE
     enabled |= IsVTuneProfilingActive();
 #endif
@@ -138,99 +139,102 @@ SendCodeRangesToProfiler(ExclusiveContex
     for (const CodeRange& codeRange : metadata.codeRanges) {
         if (!codeRange.isFunction())
             continue;
 
         uintptr_t start = uintptr_t(cs.code() + codeRange.begin());
         uintptr_t end = uintptr_t(cs.code() + codeRange.end());
         uintptr_t size = end - start;
 
-        UniqueChars owner;
-        const char* name = metadata.getFuncName(cx, codeRange.funcIndex(), &owner);
-        if (!name)
+        TwoByteName name(cx);
+        if (!metadata.getFuncName(cx, &bytecode, codeRange.funcIndex(), &name))
+            return false;
+
+        UniqueChars chars(
+            (char*)JS::LossyTwoByteCharsToNewLatin1CharsZ(cx, name.begin(), name.length()).get());
+        if (!chars)
             return false;
 
         // Avoid "unused" warnings
         (void)start;
         (void)size;
-        (void)name;
 
 #ifdef JS_ION_PERF
         if (PerfFuncEnabled()) {
             const char* file = metadata.filename.get();
             unsigned line = codeRange.funcLineOrBytecode();
             unsigned column = 0;
-            writePerfSpewerAsmJSFunctionMap(start, size, file, line, column, name);
+            writePerfSpewerAsmJSFunctionMap(start, size, file, line, column, chars.get());
         }
 #endif
 #ifdef MOZ_VTUNE
         if (IsVTuneProfilingActive()) {
             unsigned method_id = iJIT_GetNewMethodID();
             if (method_id == 0)
                 return true;
             iJIT_Method_Load method;
             method.method_id = method_id;
-            method.method_name = const_cast<char*>(name);
+            method.method_name = chars.get();
             method.method_load_address = (void*)start;
             method.method_size = size;
             method.line_number_size = 0;
             method.line_number_table = nullptr;
             method.class_id = 0;
             method.class_file_name = nullptr;
             method.source_file_name = nullptr;
             iJIT_NotifyEvent(iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED, (void*)&method);
         }
 #endif
     }
 
     return true;
 }
 
 /* static */ UniqueCodeSegment
-CodeSegment::create(ExclusiveContext* cx,
-                    const Bytes& code,
+CodeSegment::create(JSContext* cx,
+                    const Bytes& bytecode,
                     const LinkData& linkData,
                     const Metadata& metadata,
                     uint8_t* heapBase,
                     uint32_t heapLength)
 {
-    MOZ_ASSERT(code.length() % gc::SystemPageSize() == 0);
+    MOZ_ASSERT(bytecode.length() % gc::SystemPageSize() == 0);
     MOZ_ASSERT(linkData.globalDataLength % gc::SystemPageSize() == 0);
-    MOZ_ASSERT(linkData.functionCodeLength < code.length());
+    MOZ_ASSERT(linkData.functionCodeLength < bytecode.length());
 
     auto cs = cx->make_unique<CodeSegment>();
     if (!cs)
         return nullptr;
 
-    cs->bytes_ = AllocateCodeSegment(cx, code.length() + linkData.globalDataLength);
+    cs->bytes_ = AllocateCodeSegment(cx, bytecode.length() + linkData.globalDataLength);
     if (!cs->bytes_)
         return nullptr;
 
     cs->functionCodeLength_ = linkData.functionCodeLength;
-    cs->codeLength_ = code.length();
+    cs->codeLength_ = bytecode.length();
     cs->globalDataLength_ = linkData.globalDataLength;
     cs->interruptCode_ = cs->code() + linkData.interruptOffset;
     cs->outOfBoundsCode_ = cs->code() + linkData.outOfBoundsOffset;
 
     {
         JitContext jcx(CompileRuntime::get(cx->compartment()->runtimeFromAnyThread()));
         AutoFlushICache afc("CodeSegment::create");
         AutoFlushICache::setRange(uintptr_t(cs->code()), cs->codeLength());
 
-        memcpy(cs->code(), code.begin(), code.length());
+        memcpy(cs->code(), bytecode.begin(), bytecode.length());
         StaticallyLink(*cs, linkData, cx);
         SpecializeToHeap(*cs, metadata, heapBase, heapLength);
     }
 
     if (!ExecutableAllocator::makeExecutable(cs->code(), cs->codeLength())) {
         ReportOutOfMemory(cx);
         return nullptr;
     }
 
-    if (!SendCodeRangesToProfiler(cx, *cs, metadata))
+    if (!SendCodeRangesToProfiler(cx, *cs, bytecode, metadata))
         return nullptr;
 
     return cs;
 }
 
 CodeSegment::~CodeSegment()
 {
     if (!bytes_)
@@ -477,84 +481,103 @@ Metadata::serializedSize() const
     return sizeof(pod()) +
            SerializedVectorSize(imports) +
            SerializedVectorSize(exports) +
            SerializedPodVectorSize(memoryAccesses) +
            SerializedPodVectorSize(boundsChecks) +
            SerializedPodVectorSize(codeRanges) +
            SerializedPodVectorSize(callSites) +
            SerializedPodVectorSize(callThunks) +
-           SerializedVectorSize(funcNames) +
+           SerializedPodVectorSize(funcNames) +
            filename.serializedSize();
 }
 
 uint8_t*
 Metadata::serialize(uint8_t* cursor) const
 {
     cursor = WriteBytes(cursor, &pod(), sizeof(pod()));
     cursor = SerializeVector(cursor, imports);
     cursor = SerializeVector(cursor, exports);
     cursor = SerializePodVector(cursor, memoryAccesses);
     cursor = SerializePodVector(cursor, boundsChecks);
     cursor = SerializePodVector(cursor, codeRanges);
     cursor = SerializePodVector(cursor, callSites);
     cursor = SerializePodVector(cursor, callThunks);
-    cursor = SerializeVector(cursor, funcNames);
+    cursor = SerializePodVector(cursor, funcNames);
     cursor = filename.serialize(cursor);
     return cursor;
 }
 
 /* static */ const uint8_t*
 Metadata::deserialize(ExclusiveContext* cx, const uint8_t* cursor)
 {
     (cursor = ReadBytes(cursor, &pod(), sizeof(pod()))) &&
     (cursor = DeserializeVector(cx, cursor, &imports)) &&
     (cursor = DeserializeVector(cx, cursor, &exports)) &&
     (cursor = DeserializePodVector(cx, cursor, &memoryAccesses)) &&
     (cursor = DeserializePodVector(cx, cursor, &boundsChecks)) &&
     (cursor = DeserializePodVector(cx, cursor, &codeRanges)) &&
     (cursor = DeserializePodVector(cx, cursor, &callSites)) &&
     (cursor = DeserializePodVector(cx, cursor, &callThunks)) &&
-    (cursor = DeserializeVector(cx, cursor, &funcNames)) &&
+    (cursor = DeserializePodVector(cx, cursor, &funcNames)) &&
     (cursor = filename.deserialize(cx, cursor));
     return cursor;
 }
 
 size_t
 Metadata::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
 {
     return SizeOfVectorExcludingThis(imports, mallocSizeOf) +
            SizeOfVectorExcludingThis(exports, mallocSizeOf) +
            memoryAccesses.sizeOfExcludingThis(mallocSizeOf) +
            boundsChecks.sizeOfExcludingThis(mallocSizeOf) +
            codeRanges.sizeOfExcludingThis(mallocSizeOf) +
            callSites.sizeOfExcludingThis(mallocSizeOf) +
            callThunks.sizeOfExcludingThis(mallocSizeOf) +
-           SizeOfVectorExcludingThis(funcNames, mallocSizeOf) +
+           funcNames.sizeOfExcludingThis(mallocSizeOf) +
            filename.sizeOfExcludingThis(mallocSizeOf);
 }
 
-const char*
-Metadata::getFuncName(ExclusiveContext* cx, uint32_t funcIndex, UniqueChars* owner) const
+bool
+Metadata::getFuncName(JSContext* cx, const Bytes* maybeBytecode, uint32_t funcIndex,
+                      TwoByteName* name) const
 {
-    if (funcIndex < funcNames.length() && funcNames[funcIndex])
-        return funcNames[funcIndex].get();
+    if (funcIndex < funcNames.length()) {
+        MOZ_ASSERT(maybeBytecode, "NameInBytecode requires preserved bytecode");
+
+        const NameInBytecode& n = funcNames[funcIndex];
+        MOZ_ASSERT(n.offset + n.length < maybeBytecode->length());
+
+        if (n.length == 0)
+            goto invalid;
+
+        UTF8Chars utf8((const char*)maybeBytecode->begin() + n.offset, n.length);
 
-    char* chars = JS_smprintf("wasm-function[%u]", funcIndex);
-    if (!chars) {
-        ReportOutOfMemory(cx);
-        return nullptr;
+        // This code could be optimized by having JS::UTF8CharsToNewTwoByteCharsZ
+        // return a Vector directly.
+        size_t twoByteLength;
+        UniqueTwoByteChars chars(JS::UTF8CharsToNewTwoByteCharsZ(cx, utf8, &twoByteLength).get());
+        if (!chars)
+            goto invalid;
+
+        if (!name->initLengthUninitialized(twoByteLength))
+            return false;
+
+        PodCopy(name->begin(), chars.get(), twoByteLength);
+        return true;
     }
 
-    owner->reset(chars);
-    return chars;
-}
+  invalid:
+
+    // For names that are out of range or invalid, synthesize a name.
 
-JSAtom*
-Metadata::getFuncAtom(JSContext* cx, uint32_t funcIndex) const
-{
-    UniqueChars owner;
-    const char* chars = getFuncName(cx, funcIndex, &owner);
-    if (!chars)
-        return nullptr;
+    UniqueChars chars(JS_smprintf("wasm-function[%u]", funcIndex));
+    if (!chars) {
+        ReportOutOfMemory(cx);
+        return false;
+    }
 
-    return AtomizeUTF8Chars(cx, chars, strlen(chars));
+    if (!name->initLengthUninitialized(strlen(chars.get())))
+        return false;
+
+    CopyAndInflateChars(name->begin(), chars.get(), name->length());
+    return true;
 }
--- a/js/src/asmjs/WasmCode.h
+++ b/js/src/asmjs/WasmCode.h
@@ -59,17 +59,17 @@ class CodeSegment
     template <class> friend struct js::MallocProvider;
 
     CodeSegment(const CodeSegment&) = delete;
     CodeSegment(CodeSegment&&) = delete;
     void operator=(const CodeSegment&) = delete;
     void operator=(CodeSegment&&) = delete;
 
   public:
-    static UniqueCodeSegment create(ExclusiveContext* cx,
+    static UniqueCodeSegment create(JSContext* cx,
                                     const Bytes& code,
                                     const LinkData& linkData,
                                     const Metadata& metadata,
                                     uint8_t* heapBase,
                                     uint32_t heapLength);
     ~CodeSegment();
 
     uint8_t* code() const { return bytes_; }
@@ -379,16 +379,31 @@ enum class HeapUsage
 };
 
 static inline bool
 UsesHeap(HeapUsage heapUsage)
 {
     return bool(heapUsage);
 }
 
+// NameInBytecode represents a name that is embedded in the wasm bytecode.
+// The presence of NameInBytecode implies that bytecode has been kept.
+
+struct NameInBytecode
+{
+    uint32_t offset;
+    uint32_t length;
+
+    NameInBytecode() = default;
+    NameInBytecode(uint32_t offset, uint32_t length) : offset(offset), length(length) {}
+};
+
+typedef Vector<NameInBytecode, 0, SystemAllocPolicy> NameInBytecodeVector;
+typedef Vector<char16_t, 64> TwoByteName;
+
 // Metadata holds all the data that is needed to describe compiled wasm code
 // at runtime (as opposed to data that is only used to statically link or
 // instantiate a module).
 //
 // Metadata is built incrementally by ModuleGenerator and then shared immutably
 // between modules.
 
 struct MetadataCacheablePod
@@ -409,25 +424,22 @@ struct Metadata : ShareableBase<Metadata
 
     ImportVector          imports;
     ExportVector          exports;
     MemoryAccessVector    memoryAccesses;
     BoundsCheckVector     boundsChecks;
     CodeRangeVector       codeRanges;
     CallSiteVector        callSites;
     CallThunkVector       callThunks;
-    CacheableCharsVector  funcNames;
+    NameInBytecodeVector  funcNames;
     CacheableChars        filename;
 
     bool usesHeap() const { return UsesHeap(heapUsage); }
     bool hasSharedHeap() const { return heapUsage == HeapUsage::Shared; }
 
-    const char* getFuncName(ExclusiveContext* cx, uint32_t funcIndex, UniqueChars* owner) const;
-    JSAtom* getFuncAtom(JSContext* cx, uint32_t funcIndex) const;
-
     // AsmJSMetadata derives Metadata iff isAsmJS(). Mostly this distinction is
     // encapsulated within AsmJS.cpp, but the additional virtual functions allow
     // asm.js to override wasm behavior in the handful of cases that can't be
     // easily encapsulated by AsmJS.cpp.
 
     bool isAsmJS() const {
         return kind == ModuleKind::AsmJS;
     }
@@ -439,16 +451,18 @@ struct Metadata : ShareableBase<Metadata
         return false;
     }
     virtual const char16_t* displayURL() const {
         return nullptr;
     }
     virtual ScriptSource* maybeScriptSource() const {
         return nullptr;
     }
+    virtual bool getFuncName(JSContext* cx, const Bytes* maybeBytecode, uint32_t funcIndex,
+                             TwoByteName* name) const;
 
     WASM_DECLARE_SERIALIZABLE_VIRTUAL(Metadata);
 };
 
 typedef RefPtr<Metadata> MutableMetadata;
 typedef RefPtr<const Metadata> SharedMetadata;
 
 } // namespace wasm
--- a/js/src/asmjs/WasmFrameIterator.cpp
+++ b/js/src/asmjs/WasmFrameIterator.cpp
@@ -139,30 +139,31 @@ FrameIterator::settle()
 
 JSAtom*
 FrameIterator::functionDisplayAtom() const
 {
     MOZ_ASSERT(!done());
 
     UniqueChars owner;
 
-    const char* chars;
     if (missingFrameMessage_) {
-        chars = "asm.js/wasm frames may be missing; enable the profiler before running to see all "
-                "frames";
-    } else {
-        MOZ_ASSERT(codeRange_);
-        chars = instance_->metadata().getFuncName(cx_, codeRange_->funcIndex(), &owner);
-        if (!chars) {
+        const char* msg = "asm.js/wasm frames may be missing; enable the profiler before running "
+                          "to see all frames";
+        JSAtom* atom = Atomize(cx_, msg, strlen(msg));
+        if (!atom) {
             cx_->clearPendingException();
             return cx_->names().empty;
         }
+
+        return atom;
     }
 
-    JSAtom* atom = AtomizeUTF8Chars(cx_, chars, strlen(chars));
+    MOZ_ASSERT(codeRange_);
+
+    JSAtom* atom = instance_->getFuncAtom(cx_, codeRange_->funcIndex());
     if (!atom) {
         cx_->clearPendingException();
         return cx_->names().empty;
     }
 
     return atom;
 }
 
--- a/js/src/asmjs/WasmGenerator.cpp
+++ b/js/src/asmjs/WasmGenerator.cpp
@@ -849,17 +849,17 @@ ModuleGenerator::finishFuncDefs()
         MOZ_ASSERT(funcIsDefined(funcIndex));
 
     linkData_.functionCodeLength = masm_.size();
     finishedFuncDefs_ = true;
     return true;
 }
 
 void
-ModuleGenerator::setFuncNames(CacheableCharsVector&& funcNames)
+ModuleGenerator::setFuncNames(NameInBytecodeVector&& funcNames)
 {
     MOZ_ASSERT(metadata_->funcNames.empty());
     metadata_->funcNames = Move(funcNames);
 }
 
 bool
 ModuleGenerator::initSigTableLength(uint32_t sigIndex, uint32_t numElems)
 {
--- a/js/src/asmjs/WasmGenerator.h
+++ b/js/src/asmjs/WasmGenerator.h
@@ -179,17 +179,17 @@ class MOZ_STACK_CLASS ModuleGenerator
 
     // Function definitions:
     MOZ_MUST_USE bool startFuncDefs();
     MOZ_MUST_USE bool startFuncDef(uint32_t lineOrBytecode, FunctionGenerator* fg);
     MOZ_MUST_USE bool finishFuncDef(uint32_t funcIndex, FunctionGenerator* fg);
     MOZ_MUST_USE bool finishFuncDefs();
 
     // Pretty function names:
-    void setFuncNames(CacheableCharsVector&& funcNames);
+    void setFuncNames(NameInBytecodeVector&& funcNames);
 
     // asm.js lazy initialization:
     void initSig(uint32_t sigIndex, Sig&& sig);
     void initFuncSig(uint32_t funcIndex, uint32_t sigIndex);
     MOZ_MUST_USE bool initImport(uint32_t importIndex, uint32_t sigIndex);
     MOZ_MUST_USE bool initSigTableLength(uint32_t sigIndex, uint32_t numElems);
     void initSigTableElems(uint32_t sigIndex, Uint32Vector&& elemFuncIndices);
     void bumpMinHeapLength(uint32_t newMinHeapLength);
--- a/js/src/asmjs/WasmInstance.cpp
+++ b/js/src/asmjs/WasmInstance.cpp
@@ -82,23 +82,24 @@ Instance::toggleProfiling(JSContext* cx)
     // that is the name of some Function CodeRange. This involves malloc() so
     // do it now since, once we start sampling, we'll be in a signal-handing
     // context where we cannot malloc.
     if (profilingEnabled_) {
         for (const CodeRange& codeRange : metadata_->codeRanges) {
             if (!codeRange.isFunction())
                 continue;
 
-            UniqueChars owner;
-            const char* funcName = metadata_->getFuncName(cx, codeRange.funcIndex(), &owner);
-            if (!funcName)
+            TwoByteName name(cx);
+            if (!getFuncName(cx, codeRange.funcIndex(), &name))
+                return false;
+            if (!name.append('\0'))
                 return false;
 
-            UniqueChars label(JS_smprintf("%s (%s:%u)",
-                                          funcName,
+            UniqueChars label(JS_smprintf("%hs (%s:%u)",
+                                          name.begin(),
                                           metadata_->filename.get(),
                                           codeRange.funcLineOrBytecode()));
             if (!label) {
                 ReportOutOfMemory(cx);
                 return false;
             }
 
             if (codeRange.funcIndex() >= funcLabels_.length()) {
@@ -361,17 +362,17 @@ WasmCall(JSContext* cx, unsigned argc, V
 static JSFunction*
 NewExportedFunction(JSContext* cx, Handle<WasmInstanceObject*> instanceObj, uint32_t exportIndex)
 {
     Instance& instance = instanceObj->instance();
     const Metadata& metadata = instance.metadata();
     const Export& exp = metadata.exports[exportIndex];
     unsigned numArgs = exp.sig().args().length();
 
-    RootedAtom name(cx, metadata.getFuncAtom(cx, exp.funcIndex()));
+    RootedAtom name(cx, instance.getFuncAtom(cx, exp.funcIndex()));
     if (!name)
         return nullptr;
 
     JSFunction* fun = NewNativeConstructor(cx, WasmCall, numArgs, name,
                                            gc::AllocKind::FUNCTION_EXTENDED, GenericObject,
                                            JSFunction::ASMJS_CTOR);
     if (!fun)
         return nullptr;
@@ -783,16 +784,33 @@ Instance::createText(JSContext* cx)
             return nullptr;
     } else {
         if (!buffer.append(enabledMessage))
             return nullptr;
     }
     return buffer.finishString();
 }
 
+bool
+Instance::getFuncName(JSContext* cx, uint32_t funcIndex, TwoByteName* name) const
+{
+    const Bytes* maybeBytecode = maybeBytecode_ ? &maybeBytecode_.get()->bytes : nullptr;
+    return metadata_->getFuncName(cx, maybeBytecode, funcIndex, name);
+}
+
+JSAtom*
+Instance::getFuncAtom(JSContext* cx, uint32_t funcIndex) const
+{
+    TwoByteName name(cx);
+    if (!getFuncName(cx, funcIndex, &name))
+        return nullptr;
+
+    return AtomizeChars(cx, name.begin(), name.length());
+}
+
 void
 Instance::deoptimizeImportExit(uint32_t importIndex)
 {
     const Import& import = metadata_->imports[importIndex];
     ImportExit& exit = importToExit(import);
     exit.code = codeSegment_->code() + import.interpExitCodeOffset();
     exit.baselineScript = nullptr;
 }
--- a/js/src/asmjs/WasmInstance.h
+++ b/js/src/asmjs/WasmInstance.h
@@ -125,16 +125,22 @@ class Instance
     const char* profilingLabel(uint32_t funcIndex) const { return funcLabels_[funcIndex].get(); }
 
     // If the source binary was saved (by passing the bytecode to 'create'),
     // this method will render the binary as text. Otherwise, a diagnostic
     // string will be returned.
 
     JSString* createText(JSContext* cx);
 
+    // Return the name associated with a given function index, or generate one
+    // if none was given by the module.
+
+    bool getFuncName(JSContext* cx, uint32_t funcIndex, TwoByteName* name) const;
+    JSAtom* getFuncAtom(JSContext* cx, uint32_t funcIndex) const;
+
     // Initially, calls to imports in wasm code call out through the generic
     // callImport method. If the imported callee gets JIT compiled and the types
     // match up, callImport will patch the code to instead call through a thunk
     // directly into the JIT code. If the JIT code is released, the Instance must
     // be notified so it can go back to the generic callImport.
 
     void deoptimizeImportExit(uint32_t importIndex);
 
--- a/js/src/asmjs/WasmModule.cpp
+++ b/js/src/asmjs/WasmModule.cpp
@@ -325,21 +325,25 @@ Module::instantiate(JSContext* cx,
         heapBase = heap->dataPointerEither().unwrap(/* for patching thead-safe code */);
         heapLength = heap->byteLength();
     }
 
     auto cs = CodeSegment::create(cx, code_, linkData_, *metadata_, heapBase, heapLength);
     if (!cs)
         return false;
 
-    // Only save the source if the target compartment is a debuggee (which is
-    // true when, e.g., the console is open, not just when debugging is active).
-    const ShareableBytes* maybeBytecode = cx->compartment()->isDebuggee()
-                                        ? bytecode_.get()
-                                        : nullptr;
+    // To support viewing the source of an instance (Instance::createText), the
+    // instance must hold onto a ref of the bytecode (keeping it alive). This
+    // wastes memory for most users, so we try to only save the source when a
+    // developer actually cares: when the compartment is debuggable (which is
+    // true when the web console is open) or a names section is implied (since
+    // this going to be stripped for non-developer builds).
+    const ShareableBytes* maybeBytecode = nullptr;
+    if (cx->compartment()->isDebuggee() || !metadata_->funcNames.empty())
+        maybeBytecode = bytecode_.get();
 
     // Store a summary of LinkData::FuncTableVector, only as much is needed
     // for runtime toggling of profiling mode. Currently, only asm.js has typed
     // function tables.
     TypedFuncTableVector typedFuncTables;
     if (metadata_->isAsmJS()) {
         if (!typedFuncTables.reserve(linkData_.funcTables.length()))
             return false;
--- a/js/src/asmjs/WasmTypes.h
+++ b/js/src/asmjs/WasmTypes.h
@@ -43,16 +43,17 @@ namespace jit { struct BaselineScript; }
 namespace wasm {
 
 using mozilla::DebugOnly;
 using mozilla::EnumeratedArray;
 using mozilla::Maybe;
 using mozilla::Move;
 using mozilla::MallocSizeOf;
 using mozilla::PodZero;
+using mozilla::PodCopy;
 using mozilla::RefCounted;
 
 typedef Vector<uint32_t, 0, SystemAllocPolicy> Uint32Vector;
 
 // To call Vector::podResizeToFit, a type must specialize mozilla::IsPod
 // which is pretty verbose to do within js::wasm, so factor that process out
 // into a macro.
 
--- a/js/src/jit-test/tests/wasm/binary.js
+++ b/js/src/jit-test/tests/wasm/binary.js
@@ -99,17 +99,18 @@ function string(name) {
         var code = c.charCodeAt(0);
         assertEq(code < 128, true); // TODO
         return code
     });
     return varU32(nameBytes.length).concat(nameBytes);
 }
 
 function encodedString(name, len) {
-    var nameBytes = name.split('').map(c => c.charCodeAt(0));
+    var name = unescape(encodeURIComponent(name)); // break into string of utf8 code points
+    var nameBytes = name.split('').map(c => c.charCodeAt(0)); // map to array of numbers
     return varU32(len === undefined ? nameBytes.length : len).concat(nameBytes);
 }
 
 function moduleWithSections(sectionArray) {
     var bytes = moduleHeaderThen();
     for (let section of sectionArray) {
         bytes.push(...string(section.name));
         bytes.push(...varU32(section.body.length));
@@ -273,14 +274,15 @@ function runStartTraceTest(namesContent,
     assertEq(result, expectedName);
 };
 
 runStartTraceTest(null, 'wasm-function[0]');
 runStartTraceTest([{name: 'test'}], 'test');
 runStartTraceTest([{name: 'test', locals: [{name: 'var1'}, {name: 'var2'}]}], 'test');
 runStartTraceTest([{name: 'test', locals: [{name: 'var1'}, {name: 'var2'}]}], 'test');
 runStartTraceTest([{name: 'test1'}, {name: 'test2'}], 'test1');
+runStartTraceTest([{name: 'test☃'}], 'test☃');
+runStartTraceTest([{name: 'te\xE0\xFF'}], 'te\xE0\xFF');
 runStartTraceTest([], 'wasm-function[0]');
 // Notice that invalid names section content shall not fail the parsing
 runStartTraceTest([{nameLen: 100, name: 'test'}], 'wasm-function[0]'); // invalid name size
 runStartTraceTest([{name: 'test', locals: [{nameLen: 40, name: 'var1'}]}], 'wasm-function[0]'); // invalid variable name size
 runStartTraceTest([{name: ''}], 'wasm-function[0]'); // empty name
-runStartTraceTest([{name: 'te\xE0\xFF'}], 'wasm-function[0]'); // invalid UTF8 name