txNodeSet.cpp (15235B)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 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 "txNodeSet.h" 7 8 #include <algorithm> 9 10 #include "txLog.h" 11 #include "txXPathTreeWalker.h" 12 13 /** 14 * Implementation of an XPath nodeset 15 */ 16 17 #ifdef NS_BUILD_REFCNT_LOGGING 18 # define LOG_CHUNK_MOVE(_start, _new_start, _count) \ 19 { \ 20 txXPathNode* start = const_cast<txXPathNode*>(_start); \ 21 while (start < _start + _count) { \ 22 NS_LogDtor(start, "txXPathNode", sizeof(*start)); \ 23 ++start; \ 24 } \ 25 start = const_cast<txXPathNode*>(_new_start); \ 26 while (start < _new_start + _count) { \ 27 NS_LogCtor(start, "txXPathNode", sizeof(*start)); \ 28 ++start; \ 29 } \ 30 } 31 #else 32 # define LOG_CHUNK_MOVE(_start, _new_start, _count) 33 #endif 34 35 static const int32_t kTxNodeSetMinSize = 4; 36 static const int32_t kTxNodeSetGrowFactor = 2; 37 38 #define kForward 1 39 #define kReversed -1 40 41 txNodeSet::txNodeSet(txResultRecycler* aRecycler) 42 : txAExprResult(aRecycler), 43 mStart(nullptr), 44 mEnd(nullptr), 45 mStartBuffer(nullptr), 46 mEndBuffer(nullptr), 47 mDirection(kForward), 48 mMarks(nullptr) {} 49 50 txNodeSet::txNodeSet(const txXPathNode& aNode, txResultRecycler* aRecycler) 51 : txAExprResult(aRecycler), 52 mStart(nullptr), 53 mEnd(nullptr), 54 mStartBuffer(nullptr), 55 mEndBuffer(nullptr), 56 mDirection(kForward), 57 mMarks(nullptr) { 58 if (!ensureGrowSize(1)) { 59 return; 60 } 61 62 new (mStart) txXPathNode(aNode); 63 ++mEnd; 64 } 65 66 txNodeSet::txNodeSet(const txNodeSet& aSource, txResultRecycler* aRecycler) 67 : txAExprResult(aRecycler), 68 mStart(nullptr), 69 mEnd(nullptr), 70 mStartBuffer(nullptr), 71 mEndBuffer(nullptr), 72 mDirection(kForward), 73 mMarks(nullptr) { 74 append(aSource); 75 } 76 77 txNodeSet::~txNodeSet() { 78 delete[] mMarks; 79 80 if (mStartBuffer) { 81 destroyElements(mStart, mEnd); 82 83 free(mStartBuffer); 84 } 85 } 86 87 nsresult txNodeSet::add(txXPathNode&& aNode) { 88 NS_ASSERTION(mDirection == kForward, 89 "only append(aNode) is supported on reversed nodesets"); 90 91 if (isEmpty()) { 92 return append(std::move(aNode)); 93 } 94 95 bool dupe; 96 txXPathNode* pos = findPosition(aNode, mStart, mEnd, dupe); 97 98 if (dupe) { 99 return NS_OK; 100 } 101 102 // save pos, ensureGrowSize messes with the pointers 103 int32_t moveSize = mEnd - pos; 104 int32_t offset = pos - mStart; 105 if (!ensureGrowSize(1)) { 106 return NS_ERROR_OUT_OF_MEMORY; 107 } 108 // set pos to where it was 109 pos = mStart + offset; 110 111 if (moveSize > 0) { 112 LOG_CHUNK_MOVE(pos, pos + 1, moveSize); 113 // This move is okay even though txXPathNode is not trivially copyable as 114 // the created hole at `pos` is used for inplace new below. 115 memmove((void*)(pos + 1), pos, moveSize * sizeof(txXPathNode)); 116 } 117 118 new (pos) txXPathNode(std::move(aNode)); 119 ++mEnd; 120 121 return NS_OK; 122 } 123 124 nsresult txNodeSet::add(const txNodeSet& aNodes) { 125 return add(aNodes, copyElements, nullptr); 126 } 127 128 nsresult txNodeSet::addAndTransfer(txNodeSet* aNodes) { 129 // failure is out-of-memory, transfer didn't happen 130 nsresult rv = add(*aNodes, transferElements, destroyElements); 131 NS_ENSURE_SUCCESS(rv, rv); 132 133 #ifdef TX_DONT_RECYCLE_BUFFER 134 if (aNodes->mStartBuffer) { 135 free(aNodes->mStartBuffer); 136 aNodes->mStartBuffer = aNodes->mEndBuffer = nullptr; 137 } 138 #endif 139 aNodes->mStart = aNodes->mEnd = aNodes->mStartBuffer; 140 141 return NS_OK; 142 } 143 144 /** 145 * add(aNodeSet, aTransferOp) 146 * 147 * The code is optimized to make a minimum number of calls to 148 * Node::compareDocumentPosition. The idea is this: 149 * We have the two nodesets (number indicate "document position") 150 * 151 * 1 3 7 <- source 1 152 * 2 3 6 8 9 <- source 2 153 * _ _ _ _ _ _ _ _ <- result 154 * 155 * 156 * When merging these nodesets into the result, the nodes are transfered 157 * in chunks to the end of the buffer so that each chunk does not contain 158 * a node from the other nodeset, in document order. 159 * 160 * We select the last non-transfered node in the first nodeset and find 161 * where in the other nodeset it would be inserted. In this case we would 162 * take the 7 from the first nodeset and find the position between the 163 * 6 and 8 in the second. We then take the nodes after the insert-position 164 * and transfer them to the end of the resulting nodeset. Which in this case 165 * means that we first transfered the 8 and 9 nodes, giving us the following: 166 * 167 * 1 3 7 <- source 1 168 * 2 3 6 <- source 2 169 * _ _ _ _ _ _ 8 9 <- result 170 * 171 * The corresponding procedure is done for the second nodeset, that is 172 * the insertion position of the 6 in the first nodeset is found, which 173 * is between the 3 and the 7. The 7 is memmoved (as it stays within 174 * the same nodeset) to the result buffer. 175 * 176 * As the result buffer is filled from the end, it is safe to share the 177 * buffer between this nodeset and the result. 178 * 179 * This is repeated until both of the nodesets are empty. 180 * 181 * If we find a duplicate node when searching for where insertposition we 182 * check for sequences of duplicate nodes, which can be optimized. 183 * 184 */ 185 nsresult txNodeSet::add(const txNodeSet& aNodes, transferOp aTransfer, 186 destroyOp aDestroy) { 187 NS_ASSERTION(mDirection == kForward, 188 "only append(aNode) is supported on reversed nodesets"); 189 190 if (aNodes.isEmpty()) { 191 return NS_OK; 192 } 193 194 if (!ensureGrowSize(aNodes.size())) { 195 return NS_ERROR_OUT_OF_MEMORY; 196 } 197 198 // This is probably a rather common case, so lets try to shortcut. 199 if (mStart == mEnd || 200 txXPathNodeUtils::comparePosition(mEnd[-1], *aNodes.mStart) < 0) { 201 aTransfer(mEnd, aNodes.mStart, aNodes.mEnd); 202 mEnd += aNodes.size(); 203 204 return NS_OK; 205 } 206 207 // Last element in this nodeset 208 txXPathNode* thisPos = mEnd; 209 210 // Last element of the other nodeset 211 txXPathNode* otherPos = aNodes.mEnd; 212 213 // Pointer to the insertion point in this nodeset 214 txXPathNode* insertPos = mEndBuffer; 215 216 bool dupe; 217 txXPathNode* pos; 218 int32_t count; 219 while (thisPos > mStart || otherPos > aNodes.mStart) { 220 // Find where the last remaining node of this nodeset would 221 // be inserted in the other nodeset. 222 if (thisPos > mStart) { 223 pos = findPosition(thisPos[-1], aNodes.mStart, otherPos, dupe); 224 225 if (dupe) { 226 const txXPathNode* deletePos = thisPos; 227 --thisPos; // this is already added 228 // check dupe sequence 229 while (thisPos > mStart && pos > aNodes.mStart && 230 thisPos[-1] == pos[-1]) { 231 --thisPos; 232 --pos; 233 } 234 235 if (aDestroy) { 236 aDestroy(thisPos, deletePos); 237 } 238 } 239 } else { 240 pos = aNodes.mStart; 241 } 242 243 // Transfer the otherNodes after the insertion point to the result 244 count = otherPos - pos; 245 if (count > 0) { 246 insertPos -= count; 247 aTransfer(insertPos, pos, otherPos); 248 otherPos -= count; 249 } 250 251 // Find where the last remaining node of the otherNodeset would 252 // be inserted in this nodeset. 253 if (otherPos > aNodes.mStart) { 254 pos = findPosition(otherPos[-1], mStart, thisPos, dupe); 255 256 if (dupe) { 257 const txXPathNode* deletePos = otherPos; 258 --otherPos; // this is already added 259 // check dupe sequence 260 while (otherPos > aNodes.mStart && pos > mStart && 261 otherPos[-1] == pos[-1]) { 262 --otherPos; 263 --pos; 264 } 265 266 if (aDestroy) { 267 aDestroy(otherPos, deletePos); 268 } 269 } 270 } else { 271 pos = mStart; 272 } 273 274 // Move the nodes from this nodeset after the insertion point 275 // to the result 276 count = thisPos - pos; 277 if (count > 0) { 278 insertPos -= count; 279 LOG_CHUNK_MOVE(pos, insertPos, count); 280 memmove((void*)insertPos, pos, count * sizeof(txXPathNode)); 281 thisPos -= count; 282 } 283 } 284 mStart = insertPos; 285 mEnd = mEndBuffer; 286 287 return NS_OK; 288 } 289 290 /** 291 * Append API 292 * These functions should be used with care. 293 * They are intended to be used when the caller assures that the resulting 294 * nodeset remains in document order. 295 * Abuse will break document order, and cause errors in the result. 296 * These functions are significantly faster than the add API, as no 297 * order info operations will be performed. 298 */ 299 300 nsresult txNodeSet::append(txXPathNode&& aNode) { 301 if (!ensureGrowSize(1)) { 302 return NS_ERROR_OUT_OF_MEMORY; 303 } 304 305 if (mDirection == kForward) { 306 new (mEnd) txXPathNode(std::move(aNode)); 307 ++mEnd; 308 309 return NS_OK; 310 } 311 312 new (--mStart) txXPathNode(std::move(aNode)); 313 314 return NS_OK; 315 } 316 317 nsresult txNodeSet::append(const txNodeSet& aNodes) { 318 NS_ASSERTION(mDirection == kForward, 319 "only append(aNode) is supported on reversed nodesets"); 320 321 if (aNodes.isEmpty()) { 322 return NS_OK; 323 } 324 325 int32_t appended = aNodes.size(); 326 if (!ensureGrowSize(appended)) { 327 return NS_ERROR_OUT_OF_MEMORY; 328 } 329 330 copyElements(mEnd, aNodes.mStart, aNodes.mEnd); 331 mEnd += appended; 332 333 return NS_OK; 334 } 335 336 nsresult txNodeSet::mark(int32_t aIndex) { 337 NS_ASSERTION(aIndex >= 0 && mStart && mEnd - mStart > aIndex, 338 "index out of bounds"); 339 if (!mMarks) { 340 int32_t length = size(); 341 mMarks = new bool[length]; 342 memset(mMarks, 0, length * sizeof(bool)); 343 } 344 if (mDirection == kForward) { 345 mMarks[aIndex] = true; 346 } else { 347 mMarks[size() - aIndex - 1] = true; 348 } 349 350 return NS_OK; 351 } 352 353 nsresult txNodeSet::sweep() { 354 if (!mMarks) { 355 // sweep everything 356 clear(); 357 } 358 359 int32_t chunk, pos = 0; 360 int32_t length = size(); 361 txXPathNode* insertion = mStartBuffer; 362 363 while (pos < length) { 364 while (pos < length && !mMarks[pos]) { 365 // delete unmarked 366 mStart[pos].~txXPathNode(); 367 ++pos; 368 } 369 // find chunk to move 370 chunk = 0; 371 while (pos < length && mMarks[pos]) { 372 ++pos; 373 ++chunk; 374 } 375 // move chunk 376 if (chunk > 0) { 377 LOG_CHUNK_MOVE(mStart + pos - chunk, insertion, chunk); 378 memmove((void*)insertion, mStart + pos - chunk, 379 chunk * sizeof(txXPathNode)); 380 insertion += chunk; 381 } 382 } 383 mStart = mStartBuffer; 384 mEnd = insertion; 385 delete[] mMarks; 386 mMarks = nullptr; 387 388 return NS_OK; 389 } 390 391 void txNodeSet::clear() { 392 destroyElements(mStart, mEnd); 393 #ifdef TX_DONT_RECYCLE_BUFFER 394 if (mStartBuffer) { 395 free(mStartBuffer); 396 mStartBuffer = mEndBuffer = nullptr; 397 } 398 #endif 399 mStart = mEnd = mStartBuffer; 400 delete[] mMarks; 401 mMarks = nullptr; 402 mDirection = kForward; 403 } 404 405 int32_t txNodeSet::indexOf(const txXPathNode& aNode, uint32_t aStart) const { 406 NS_ASSERTION(mDirection == kForward, 407 "only append(aNode) is supported on reversed nodesets"); 408 409 if (!mStart || mStart == mEnd) { 410 return -1; 411 } 412 413 txXPathNode* pos = mStart + aStart; 414 for (; pos < mEnd; ++pos) { 415 if (*pos == aNode) { 416 return pos - mStart; 417 } 418 } 419 420 return -1; 421 } 422 423 const txXPathNode& txNodeSet::get(int32_t aIndex) const { 424 if (mDirection == kForward) { 425 return mStart[aIndex]; 426 } 427 428 return mEnd[-aIndex - 1]; 429 } 430 431 short txNodeSet::getResultType() { return txAExprResult::NODESET; } 432 433 bool txNodeSet::booleanValue() { return !isEmpty(); } 434 double txNodeSet::numberValue() { 435 nsAutoString str; 436 stringValue(str); 437 438 return txDouble::toDouble(str); 439 } 440 441 void txNodeSet::stringValue(nsString& aStr) { 442 NS_ASSERTION(mDirection == kForward, 443 "only append(aNode) is supported on reversed nodesets"); 444 if (isEmpty()) { 445 return; 446 } 447 txXPathNodeUtils::appendNodeValue(get(0), aStr); 448 } 449 450 const nsString* txNodeSet::stringValuePointer() { return nullptr; } 451 452 bool txNodeSet::ensureGrowSize(int32_t aSize) { 453 // check if there is enough place in the buffer as is 454 if (mDirection == kForward && aSize <= mEndBuffer - mEnd) { 455 return true; 456 } 457 458 if (mDirection == kReversed && aSize <= mStart - mStartBuffer) { 459 return true; 460 } 461 462 // check if we just have to align mStart to have enough space 463 int32_t oldSize = mEnd - mStart; 464 int32_t oldLength = mEndBuffer - mStartBuffer; 465 int32_t ensureSize = oldSize + aSize; 466 if (ensureSize <= oldLength) { 467 // just move the buffer 468 txXPathNode* dest = mStartBuffer; 469 if (mDirection == kReversed) { 470 dest = mEndBuffer - oldSize; 471 } 472 LOG_CHUNK_MOVE(mStart, dest, oldSize); 473 memmove((void*)dest, mStart, oldSize * sizeof(txXPathNode)); 474 mStart = dest; 475 mEnd = dest + oldSize; 476 477 return true; 478 } 479 480 // This isn't 100% safe. But until someone manages to make a 1gig nodeset 481 // it should be ok. 482 int32_t newLength = std::max(oldLength, kTxNodeSetMinSize); 483 484 while (newLength < ensureSize) { 485 newLength *= kTxNodeSetGrowFactor; 486 } 487 488 txXPathNode* newArr = 489 static_cast<txXPathNode*>(moz_xmalloc(newLength * sizeof(txXPathNode))); 490 491 txXPathNode* dest = newArr; 492 if (mDirection == kReversed) { 493 dest += newLength - oldSize; 494 } 495 496 if (oldSize > 0) { 497 LOG_CHUNK_MOVE(mStart, dest, oldSize); 498 memcpy((void*)dest, mStart, oldSize * sizeof(txXPathNode)); 499 } 500 501 if (mStartBuffer) { 502 #ifdef DEBUG 503 memset((void*)mStartBuffer, 0, 504 (mEndBuffer - mStartBuffer) * sizeof(txXPathNode)); 505 #endif 506 free(mStartBuffer); 507 } 508 509 mStartBuffer = newArr; 510 mEndBuffer = mStartBuffer + newLength; 511 mStart = dest; 512 mEnd = dest + oldSize; 513 514 return true; 515 } 516 517 txXPathNode* txNodeSet::findPosition(const txXPathNode& aNode, 518 txXPathNode* aFirst, txXPathNode* aLast, 519 bool& aDupe) const { 520 aDupe = false; 521 if (aLast - aFirst <= 2) { 522 // If we search 2 nodes or less there is no point in further divides 523 txXPathNode* pos = aFirst; 524 for (; pos < aLast; ++pos) { 525 int cmp = txXPathNodeUtils::comparePosition(aNode, *pos); 526 if (cmp < 0) { 527 return pos; 528 } 529 530 if (cmp == 0) { 531 aDupe = true; 532 533 return pos; 534 } 535 } 536 return pos; 537 } 538 539 // (cannot add two pointers) 540 txXPathNode* midpos = aFirst + (aLast - aFirst) / 2; 541 int cmp = txXPathNodeUtils::comparePosition(aNode, *midpos); 542 if (cmp == 0) { 543 aDupe = true; 544 545 return midpos; 546 } 547 548 if (cmp > 0) { 549 return findPosition(aNode, midpos + 1, aLast, aDupe); 550 } 551 552 // midpos excluded as end of range 553 554 return findPosition(aNode, aFirst, midpos, aDupe); 555 } 556 557 /* static */ 558 void txNodeSet::copyElements(txXPathNode* aDest, const txXPathNode* aStart, 559 const txXPathNode* aEnd) { 560 const txXPathNode* pos = aStart; 561 while (pos < aEnd) { 562 new (aDest) txXPathNode(*pos); 563 ++aDest; 564 ++pos; 565 } 566 } 567 568 /* static */ 569 void txNodeSet::transferElements(txXPathNode* aDest, const txXPathNode* aStart, 570 const txXPathNode* aEnd) { 571 LOG_CHUNK_MOVE(aStart, aDest, (aEnd - aStart)); 572 memcpy((void*)aDest, aStart, (aEnd - aStart) * sizeof(txXPathNode)); 573 }