content/canvas/src/WebGLElementArrayCache.cpp

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:4b3130eec5b3
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include "WebGLElementArrayCache.h"
7
8 #include "nsTArray.h"
9 #include "mozilla/Assertions.h"
10 #include "mozilla/MemoryReporting.h"
11
12 #include <cstdlib>
13 #include <cstring>
14 #include <limits>
15 #include <algorithm>
16
17 namespace mozilla {
18
19 static void
20 SetUpperBound(uint32_t* out_upperBound, uint32_t newBound)
21 {
22 if (!out_upperBound)
23 return;
24
25 *out_upperBound = newBound;
26 }
27
28 static void
29 UpdateUpperBound(uint32_t* out_upperBound, uint32_t newBound)
30 {
31 if (!out_upperBound)
32 return;
33
34 *out_upperBound = std::max(*out_upperBound, newBound);
35 }
36
37 /*
38 * WebGLElementArrayCacheTree contains most of the implementation of WebGLElementArrayCache,
39 * which performs WebGL element array buffer validation for drawElements.
40 *
41 * Attention: Here lie nontrivial data structures, bug-prone algorithms, and non-canonical tweaks!
42 * Whence the explanatory comments, and compiled unit test.
43 *
44 * *** What problem are we solving here? ***
45 *
46 * WebGL::DrawElements has to validate that the elements are in range wrt the current vertex attribs.
47 * This boils down to the problem, given an array of integers, of computing the maximum in an arbitrary
48 * sub-array. The naive algorithm has linear complexity; this has been a major performance problem,
49 * see bug 569431. In that bug, we took the approach of caching the max for the whole array, which
50 * does cover most cases (DrawElements typically consumes the whole element array buffer) but doesn't
51 * help in other use cases:
52 * - when doing "partial DrawElements" i.e. consuming only part of the element array buffer
53 * - when doing frequent "partial buffer updates" i.e. bufferSubData calls updating parts of the
54 * element array buffer
55 *
56 * *** The solution: a binary tree ***
57 *
58 * The solution implemented here is to use a binary tree as the cache data structure. Each tree node
59 * contains the max of its two children nodes. In this way, finding the maximum in any contiguous sub-array
60 * has log complexity instead of linear complexity.
61 *
62 * Simplistically, if the element array is
63 *
64 * 1 4 3 2
65 *
66 * then the corresponding tree is
67 *
68 * 4
69 * _/ \_
70 * 4 3
71 * / \ / \
72 * 1 4 3 2
73 *
74 * In practice, the bottom-most levels of the tree are both the largest to store (because they
75 * have more nodes), and the least useful performance-wise (because each node in the bottom
76 * levels concerns only few entries in the elements array buffer, it is cheap to compute).
77 *
78 * For this reason, we stop the tree a few levels above, so that each tree leaf actually corresponds
79 * to more than one element array entry.
80 *
81 * The number of levels that we "drop" is |sSkippedBottomTreeLevels| and the number of element array entries
82 * that each leaf corresponds to, is |sElementsPerLeaf|. This being a binary tree, we have
83 *
84 * sElementsPerLeaf = 2 ^ sSkippedBottomTreeLevels.
85 *
86 * *** Storage layout of the binary tree ***
87 *
88 * We take advantage of the specifics of the situation to avoid generalist tree storage and instead
89 * store the tree entries in a vector, mTreeData.
90 *
91 * The number of leaves is given by mNumLeaves, and mTreeData is always a vector of length
92 *
93 * 2 * mNumLeaves.
94 *
95 * Its data layout is as follows: mTreeData[0] is unused, mTreeData[1] is the root node,
96 * then at offsets 2..3 is the tree level immediately below the root node, then at offsets 4..7
97 * is the tree level below that, etc.
98 *
99 * The figure below illustrates this by writing at each tree node the offset into mTreeData at
100 * which it is stored:
101 *
102 * 1
103 * _/ \_
104 * 2 3
105 * / \ / \
106 * 4 5 6 7
107 * ...
108 *
109 * Thus, under the convention that the root level is level 0, we see that level N is stored at offsets
110 *
111 * [ 2^n .. 2^(n+1) - 1 ]
112 *
113 * in mTreeData. Likewise, all the usual tree operations have simple mathematical expressions in
114 * terms of mTreeData offsets, see all the methods such as ParentNode, LeftChildNode, etc.
115 *
116 * *** Design constraint: element types aren't known at buffer-update time ***
117 *
118 * Note that a key constraint that we're operating under, is that we don't know the types of the elements
119 * by the time WebGL bufferData/bufferSubData methods are called. The type of elements is only
120 * specified in the drawElements call. This means that we may potentially have to store caches for
121 * multiple element types, for the same element array buffer. Since we don't know yet how many
122 * element types we'll eventually support (extensions add more), the concern about memory usage is serious.
123 * This is addressed by sSkippedBottomTreeLevels as explained above. Of course, in the typical
124 * case where each element array buffer is only ever used with one type, this is also addressed
125 * by having WebGLElementArrayCache lazily create trees for each type only upon first use.
126 *
127 * Another consequence of this constraint is that when invalidating the trees, we have to invalidate
128 * all existing trees. So if trees for types uint8_t, uint16_t and uint32_t have ever been constructed for this buffer,
129 * every subsequent invalidation will have to invalidate all trees even if one of the types is never
130 * used again. This implies that it is important to minimize the cost of invalidation i.e.
131 * do lazy updates upon use as opposed to immediately updating invalidated trees. This poses a problem:
132 * it is nontrivial to keep track of the part of the tree that's invalidated. The current solution
133 * can only keep track of an invalidated interval, from |mFirstInvalidatedLeaf| to |mLastInvalidatedLeaf|.
134 * The problem is that if one does two small, far-apart partial buffer updates, the resulting invalidated
135 * area is very large even though only a small part of the array really needed to be invalidated.
136 * The real solution to this problem would be to use a smarter data structure to keep track of the
137 * invalidated area, probably an interval tree. Meanwhile, we can probably live with the current situation
138 * as the unfavorable case seems to be a small corner case: in order to run into performance issues,
139 * the number of bufferSubData in between two consecutive draws must be small but greater than 1, and
140 * the partial buffer updates must be small and far apart. Anything else than this corner case
141 * should run fast in the current setting.
142 */
143 template<typename T>
144 struct WebGLElementArrayCacheTree
145 {
146 // A too-high sSkippedBottomTreeLevels would harm the performance of small drawElements calls
147 // A too-low sSkippedBottomTreeLevels would cause undue memory usage.
148 // The current value has been validated by some benchmarking. See bug 732660.
149 static const size_t sSkippedBottomTreeLevels = 3;
150 static const size_t sElementsPerLeaf = 1 << sSkippedBottomTreeLevels;
151 static const size_t sElementsPerLeafMask = sElementsPerLeaf - 1; // sElementsPerLeaf is POT
152
153 private:
154 WebGLElementArrayCache& mParent;
155 FallibleTArray<T> mTreeData;
156 size_t mNumLeaves;
157 bool mInvalidated;
158 size_t mFirstInvalidatedLeaf;
159 size_t mLastInvalidatedLeaf;
160
161 public:
162 WebGLElementArrayCacheTree(WebGLElementArrayCache& p)
163 : mParent(p)
164 , mNumLeaves(0)
165 , mInvalidated(false)
166 , mFirstInvalidatedLeaf(0)
167 , mLastInvalidatedLeaf(0)
168 {
169 ResizeToParentSize();
170 }
171
172 T GlobalMaximum() const {
173 MOZ_ASSERT(!mInvalidated);
174 return mTreeData[1];
175 }
176
177 // returns the index of the parent node; if treeIndex=1 (the root node),
178 // the return value is 0.
179 static size_t ParentNode(size_t treeIndex) {
180 MOZ_ASSERT(treeIndex > 1);
181 return treeIndex >> 1;
182 }
183
184 static bool IsRightNode(size_t treeIndex) {
185 MOZ_ASSERT(treeIndex > 1);
186 return treeIndex & 1;
187 }
188
189 static bool IsLeftNode(size_t treeIndex) {
190 MOZ_ASSERT(treeIndex > 1);
191 return !IsRightNode(treeIndex);
192 }
193
194 static size_t SiblingNode(size_t treeIndex) {
195 MOZ_ASSERT(treeIndex > 1);
196 return treeIndex ^ 1;
197 }
198
199 static size_t LeftChildNode(size_t treeIndex) {
200 MOZ_ASSERT(treeIndex);
201 return treeIndex << 1;
202 }
203
204 static size_t RightChildNode(size_t treeIndex) {
205 MOZ_ASSERT(treeIndex);
206 return SiblingNode(LeftChildNode(treeIndex));
207 }
208
209 static size_t LeftNeighborNode(size_t treeIndex, size_t distance = 1) {
210 MOZ_ASSERT(treeIndex > 1);
211 return treeIndex - distance;
212 }
213
214 static size_t RightNeighborNode(size_t treeIndex, size_t distance = 1) {
215 MOZ_ASSERT(treeIndex > 1);
216 return treeIndex + distance;
217 }
218
219 size_t LeafForElement(size_t element) {
220 size_t leaf = element / sElementsPerLeaf;
221 MOZ_ASSERT(leaf < mNumLeaves);
222 return leaf;
223 }
224
225 size_t LeafForByte(size_t byte) {
226 return LeafForElement(byte / sizeof(T));
227 }
228
229 // Returns the index, into the tree storage, where a given leaf is stored
230 size_t TreeIndexForLeaf(size_t leaf) {
231 // See above class comment. The tree storage is an array of length 2*mNumLeaves.
232 // The leaves are stored in its second half.
233 return leaf + mNumLeaves;
234 }
235
236 static size_t LastElementUnderSameLeaf(size_t element) {
237 return element | sElementsPerLeafMask;
238 }
239
240 static size_t FirstElementUnderSameLeaf(size_t element) {
241 return element & ~sElementsPerLeafMask;
242 }
243
244 static size_t NextMultipleOfElementsPerLeaf(size_t numElements) {
245 return ((numElements - 1) | sElementsPerLeafMask) + 1;
246 }
247
248 bool Validate(T maxAllowed, size_t firstLeaf, size_t lastLeaf,
249 uint32_t* out_upperBound)
250 {
251 MOZ_ASSERT(!mInvalidated);
252
253 size_t firstTreeIndex = TreeIndexForLeaf(firstLeaf);
254 size_t lastTreeIndex = TreeIndexForLeaf(lastLeaf);
255
256 while (true) {
257 // given that we tweak these values in nontrivial ways, it doesn't hurt to do
258 // this sanity check
259 MOZ_ASSERT(firstTreeIndex <= lastTreeIndex);
260
261 // final case where there is only 1 node to validate at the current tree level
262 if (lastTreeIndex == firstTreeIndex) {
263 const T& curData = mTreeData[firstTreeIndex];
264 UpdateUpperBound(out_upperBound, curData);
265 return curData <= maxAllowed;
266 }
267
268 // if the first node at current tree level is a right node, handle it individually
269 // and replace it with its right neighbor, which is a left node
270 if (IsRightNode(firstTreeIndex)) {
271 const T& curData = mTreeData[firstTreeIndex];
272 UpdateUpperBound(out_upperBound, curData);
273 if (curData > maxAllowed)
274 return false;
275 firstTreeIndex = RightNeighborNode(firstTreeIndex);
276 }
277
278 // if the last node at current tree level is a left node, handle it individually
279 // and replace it with its left neighbor, which is a right node
280 if (IsLeftNode(lastTreeIndex)) {
281 const T& curData = mTreeData[lastTreeIndex];
282 UpdateUpperBound(out_upperBound, curData);
283 if (curData > maxAllowed)
284 return false;
285 lastTreeIndex = LeftNeighborNode(lastTreeIndex);
286 }
287
288 // at this point it can happen that firstTreeIndex and lastTreeIndex "crossed" each
289 // other. That happens if firstTreeIndex was a right node and lastTreeIndex was its
290 // right neighor: in that case, both above tweaks happened and as a result, they ended
291 // up being swapped: lastTreeIndex is now the _left_ neighbor of firstTreeIndex.
292 // When that happens, there is nothing left to validate.
293 if (lastTreeIndex == LeftNeighborNode(firstTreeIndex)) {
294 return true;
295 }
296
297 // walk up 1 level
298 firstTreeIndex = ParentNode(firstTreeIndex);
299 lastTreeIndex = ParentNode(lastTreeIndex);
300 }
301 }
302
303 template<typename U>
304 static U NextPowerOfTwo(U x) {
305 U result = 1;
306 while (result < x)
307 result <<= 1;
308 MOZ_ASSERT(result >= x);
309 MOZ_ASSERT((result & (result - 1)) == 0);
310 return result;
311 }
312
313 bool ResizeToParentSize()
314 {
315 size_t numberOfElements = mParent.ByteSize() / sizeof(T);
316 size_t requiredNumLeaves = (numberOfElements + sElementsPerLeaf - 1) / sElementsPerLeaf;
317
318 size_t oldNumLeaves = mNumLeaves;
319 mNumLeaves = NextPowerOfTwo(requiredNumLeaves);
320 Invalidate(0, mParent.ByteSize() - 1);
321
322 // see class comment for why we the tree storage size is 2 * mNumLeaves
323 if (!mTreeData.SetLength(2 * mNumLeaves)) {
324 return false;
325 }
326 if (mNumLeaves != oldNumLeaves) {
327 memset(mTreeData.Elements(), 0, mTreeData.Length() * sizeof(mTreeData[0]));
328 }
329 return true;
330 }
331
332 void Invalidate(size_t firstByte, size_t lastByte);
333
334 void Update();
335
336 size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
337 {
338 return aMallocSizeOf(this) + mTreeData.SizeOfExcludingThis(aMallocSizeOf);
339 }
340 };
341
342 // TreeForType: just a template helper to select the right tree object for a given
343 // element type.
344 template<typename T>
345 struct TreeForType {};
346
347 template<>
348 struct TreeForType<uint8_t>
349 {
350 static WebGLElementArrayCacheTree<uint8_t>*& Run(WebGLElementArrayCache *b) { return b->mUint8Tree; }
351 };
352
353 template<>
354 struct TreeForType<uint16_t>
355 {
356 static WebGLElementArrayCacheTree<uint16_t>*& Run(WebGLElementArrayCache *b) { return b->mUint16Tree; }
357 };
358
359 template<>
360 struct TreeForType<uint32_t>
361 {
362 static WebGLElementArrayCacheTree<uint32_t>*& Run(WebGLElementArrayCache *b) { return b->mUint32Tree; }
363 };
364
365 // When the buffer gets updated from firstByte to lastByte,
366 // calling this method will notify the tree accordingly
367 template<typename T>
368 void WebGLElementArrayCacheTree<T>::Invalidate(size_t firstByte, size_t lastByte)
369 {
370 lastByte = std::min(lastByte, mNumLeaves * sElementsPerLeaf * sizeof(T) - 1);
371 if (firstByte > lastByte) {
372 return;
373 }
374
375 size_t firstLeaf = LeafForByte(firstByte);
376 size_t lastLeaf = LeafForByte(lastByte);
377
378 if (mInvalidated) {
379 mFirstInvalidatedLeaf = std::min(firstLeaf, mFirstInvalidatedLeaf);
380 mLastInvalidatedLeaf = std::max(lastLeaf, mLastInvalidatedLeaf);
381 } else {
382 mInvalidated = true;
383 mFirstInvalidatedLeaf = firstLeaf;
384 mLastInvalidatedLeaf = lastLeaf;
385 }
386 }
387
388
389 // When tree has been partially invalidated, from mFirstInvalidatedLeaf to
390 // mLastInvalidatedLeaf, calling this method will 1) update the leaves in this interval
391 // from the raw buffer data, and 2) propagate this update up the tree
392 template<typename T>
393 void WebGLElementArrayCacheTree<T>::Update()
394 {
395 if (!mInvalidated) {
396 return;
397 }
398
399 MOZ_ASSERT(mLastInvalidatedLeaf < mNumLeaves);
400
401 size_t firstTreeIndex = TreeIndexForLeaf(mFirstInvalidatedLeaf);
402 size_t lastTreeIndex = TreeIndexForLeaf(mLastInvalidatedLeaf);
403
404 // Step #1: initialize the tree leaves from plain buffer data.
405 // That is, each tree leaf must be set to the max of the |sElementsPerLeaf| corresponding
406 // buffer entries.
407 // condition-less scope to prevent leaking this scope's variables into the code below
408 {
409 // treeIndex is the index of the tree leaf we're writing, i.e. the destination index
410 size_t treeIndex = firstTreeIndex;
411 // srcIndex is the index in the source buffer
412 size_t srcIndex = mFirstInvalidatedLeaf * sElementsPerLeaf;
413 size_t numberOfElements = mParent.ByteSize() / sizeof(T);
414 while (treeIndex <= lastTreeIndex) {
415 T m = 0;
416 size_t a = srcIndex;
417 size_t srcIndexNextLeaf = std::min(a + sElementsPerLeaf, numberOfElements);
418 for (; srcIndex < srcIndexNextLeaf; srcIndex++) {
419 m = std::max(m, mParent.Element<T>(srcIndex));
420 }
421 mTreeData[treeIndex] = m;
422 treeIndex++;
423 }
424 }
425
426 // Step #2: propagate the values up the tree. This is simply a matter of walking up
427 // the tree and setting each node to the max of its two children.
428 while (firstTreeIndex > 1) {
429
430 // move up 1 level
431 firstTreeIndex = ParentNode(firstTreeIndex);
432 lastTreeIndex = ParentNode(lastTreeIndex);
433
434 // fast-exit case where only one node is invalidated at the current level
435 if (firstTreeIndex == lastTreeIndex) {
436 mTreeData[firstTreeIndex] = std::max(mTreeData[LeftChildNode(firstTreeIndex)], mTreeData[RightChildNode(firstTreeIndex)]);
437 continue;
438 }
439
440 // initialize local iteration variables: child and parent.
441 size_t child = LeftChildNode(firstTreeIndex);
442 size_t parent = firstTreeIndex;
443
444 // the unrolling makes this look more complicated than it is; the plain non-unrolled
445 // version is in the second while loop below
446 const int unrollSize = 8;
447 while (RightNeighborNode(parent, unrollSize - 1) <= lastTreeIndex)
448 {
449 for (int unroll = 0; unroll < unrollSize; unroll++)
450 {
451 T a = mTreeData[child];
452 child = RightNeighborNode(child);
453 T b = mTreeData[child];
454 child = RightNeighborNode(child);
455 mTreeData[parent] = std::max(a, b);
456 parent = RightNeighborNode(parent);
457 }
458 }
459 // plain non-unrolled version, used to terminate the job after the last unrolled iteration
460 while (parent <= lastTreeIndex)
461 {
462 T a = mTreeData[child];
463 child = RightNeighborNode(child);
464 T b = mTreeData[child];
465 child = RightNeighborNode(child);
466 mTreeData[parent] = std::max(a, b);
467 parent = RightNeighborNode(parent);
468 }
469 }
470
471 mInvalidated = false;
472 }
473
474 WebGLElementArrayCache::~WebGLElementArrayCache() {
475 delete mUint8Tree;
476 delete mUint16Tree;
477 delete mUint32Tree;
478 free(mUntypedData);
479 }
480
481 bool WebGLElementArrayCache::BufferData(const void* ptr, size_t byteSize) {
482 mByteSize = byteSize;
483 if (mUint8Tree)
484 if (!mUint8Tree->ResizeToParentSize())
485 return false;
486 if (mUint16Tree)
487 if (!mUint16Tree->ResizeToParentSize())
488 return false;
489 if (mUint32Tree)
490 if (!mUint32Tree->ResizeToParentSize())
491 return false;
492 mUntypedData = realloc(mUntypedData, byteSize);
493 if (!mUntypedData)
494 return false;
495 BufferSubData(0, ptr, byteSize);
496 return true;
497 }
498
499 void WebGLElementArrayCache::BufferSubData(size_t pos, const void* ptr, size_t updateByteSize) {
500 if (!updateByteSize) return;
501 if (ptr)
502 memcpy(static_cast<uint8_t*>(mUntypedData) + pos, ptr, updateByteSize);
503 else
504 memset(static_cast<uint8_t*>(mUntypedData) + pos, 0, updateByteSize);
505 InvalidateTrees(pos, pos + updateByteSize - 1);
506 }
507
508 void WebGLElementArrayCache::InvalidateTrees(size_t firstByte, size_t lastByte)
509 {
510 if (mUint8Tree)
511 mUint8Tree->Invalidate(firstByte, lastByte);
512 if (mUint16Tree)
513 mUint16Tree->Invalidate(firstByte, lastByte);
514 if (mUint32Tree)
515 mUint32Tree->Invalidate(firstByte, lastByte);
516 }
517
518 template<typename T>
519 bool
520 WebGLElementArrayCache::Validate(uint32_t maxAllowed, size_t firstElement,
521 size_t countElements, uint32_t* out_upperBound)
522 {
523 SetUpperBound(out_upperBound, 0);
524
525 // if maxAllowed is >= the max T value, then there is no way that a T index could be invalid
526 uint32_t maxTSize = std::numeric_limits<T>::max();
527 if (maxAllowed >= maxTSize) {
528 SetUpperBound(out_upperBound, maxTSize);
529 return true;
530 }
531
532 T maxAllowedT(maxAllowed);
533
534 // integer overflow must have been handled earlier, so we assert that maxAllowedT
535 // is exactly the max allowed value.
536 MOZ_ASSERT(uint32_t(maxAllowedT) == maxAllowed);
537
538 if (!mByteSize || !countElements)
539 return true;
540
541 WebGLElementArrayCacheTree<T>*& tree = TreeForType<T>::Run(this);
542 if (!tree) {
543 tree = new WebGLElementArrayCacheTree<T>(*this);
544 }
545
546 size_t lastElement = firstElement + countElements - 1;
547
548 tree->Update();
549
550 // fast exit path when the global maximum for the whole element array buffer
551 // falls in the allowed range
552 T globalMax = tree->GlobalMaximum();
553 if (globalMax <= maxAllowedT)
554 {
555 SetUpperBound(out_upperBound, globalMax);
556 return true;
557 }
558
559 const T* elements = Elements<T>();
560
561 // before calling tree->Validate, we have to validate ourselves the boundaries of the elements span,
562 // to round them to the nearest multiple of sElementsPerLeaf.
563 size_t firstElementAdjustmentEnd = std::min(lastElement,
564 tree->LastElementUnderSameLeaf(firstElement));
565 while (firstElement <= firstElementAdjustmentEnd) {
566 const T& curData = elements[firstElement];
567 UpdateUpperBound(out_upperBound, curData);
568 if (curData > maxAllowedT)
569 return false;
570 firstElement++;
571 }
572 size_t lastElementAdjustmentEnd = std::max(firstElement,
573 tree->FirstElementUnderSameLeaf(lastElement));
574 while (lastElement >= lastElementAdjustmentEnd) {
575 const T& curData = elements[lastElement];
576 UpdateUpperBound(out_upperBound, curData);
577 if (curData > maxAllowedT)
578 return false;
579 lastElement--;
580 }
581
582 // at this point, for many tiny validations, we're already done.
583 if (firstElement > lastElement)
584 return true;
585
586 // general case
587 return tree->Validate(maxAllowedT,
588 tree->LeafForElement(firstElement),
589 tree->LeafForElement(lastElement),
590 out_upperBound);
591 }
592
593 bool
594 WebGLElementArrayCache::Validate(GLenum type, uint32_t maxAllowed,
595 size_t firstElement, size_t countElements,
596 uint32_t* out_upperBound)
597 {
598 if (type == LOCAL_GL_UNSIGNED_BYTE)
599 return Validate<uint8_t>(maxAllowed, firstElement, countElements, out_upperBound);
600 if (type == LOCAL_GL_UNSIGNED_SHORT)
601 return Validate<uint16_t>(maxAllowed, firstElement, countElements, out_upperBound);
602 if (type == LOCAL_GL_UNSIGNED_INT)
603 return Validate<uint32_t>(maxAllowed, firstElement, countElements, out_upperBound);
604
605 MOZ_ASSERT(false, "Invalid type.");
606 return false;
607 }
608
609 size_t
610 WebGLElementArrayCache::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
611 {
612 size_t uint8TreeSize = mUint8Tree ? mUint8Tree->SizeOfIncludingThis(aMallocSizeOf) : 0;
613 size_t uint16TreeSize = mUint16Tree ? mUint16Tree->SizeOfIncludingThis(aMallocSizeOf) : 0;
614 size_t uint32TreeSize = mUint32Tree ? mUint32Tree->SizeOfIncludingThis(aMallocSizeOf) : 0;
615 return aMallocSizeOf(this) +
616 mByteSize +
617 uint8TreeSize +
618 uint16TreeSize +
619 uint32TreeSize;
620 }
621
622 } // end namespace mozilla

mercurial