js/src/jit/ParallelFunctions.cpp

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:87d74e750cd1
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=8 sts=4 et sw=4 tw=99:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "jit/ParallelFunctions.h"
8
9 #include "builtin/TypedObject.h"
10 #include "jit/arm/Simulator-arm.h"
11 #include "vm/ArrayObject.h"
12
13 #include "jsgcinlines.h"
14 #include "jsobjinlines.h"
15
16 using namespace js;
17 using namespace jit;
18
19 using parallel::Spew;
20 using parallel::SpewOps;
21 using parallel::SpewBailouts;
22 using parallel::SpewBailoutIR;
23
24 // Load the current thread context.
25 ForkJoinContext *
26 jit::ForkJoinContextPar()
27 {
28 return ForkJoinContext::current();
29 }
30
31 // NewGCThingPar() is called in place of NewGCThing() when executing
32 // parallel code. It uses the ArenaLists for the current thread and
33 // allocates from there.
34 JSObject *
35 jit::NewGCThingPar(ForkJoinContext *cx, gc::AllocKind allocKind)
36 {
37 JS_ASSERT(ForkJoinContext::current() == cx);
38 return js::NewGCObject<NoGC>(cx, allocKind, 0, gc::TenuredHeap);
39 }
40
41 bool
42 jit::ParallelWriteGuard(ForkJoinContext *cx, JSObject *object)
43 {
44 // Implements the most general form of the write guard, which is
45 // suitable for writes to any object O. There are two cases to
46 // consider and test for:
47 //
48 // 1. Writes to thread-local memory are safe. Thread-local memory
49 // is defined as memory allocated by the current thread.
50 // The definition of the PJS API guarantees that such memory
51 // cannot have escaped to other parallel threads.
52 //
53 // 2. Writes into the output buffer are safe. Some PJS operations
54 // supply an out pointer into the final target buffer. The design
55 // of the API ensures that this out pointer is always pointing
56 // at a fresh region of the buffer that is not accessible to
57 // other threads. Thus, even though this output buffer has not
58 // been created by the current thread, it is writable.
59 //
60 // There are some subtleties to consider:
61 //
62 // A. Typed objects and typed arrays are just views onto a base buffer.
63 // For the purposes of guarding parallel writes, it is not important
64 // whether the *view* is thread-local -- what matters is whether
65 // the *underlying buffer* is thread-local.
66 //
67 // B. With regard to the output buffer, we have to be careful
68 // because of the potential for sequential iterations to be
69 // intermingled with parallel ones. During a sequential
70 // iteration, the out pointer could escape into global
71 // variables and so forth, and thus be used during later
72 // parallel operations. However, those out pointers must be
73 // pointing to distinct regions of the final output buffer than
74 // the ones that are currently being written, so there is no
75 // harm done in letting them be read (but not written).
76 //
77 // In order to be able to distinguish escaped out pointers from
78 // prior iterations and the proper out pointers from the
79 // current iteration, we always track a *target memory region*
80 // (which is a span of bytes within the output buffer) and not
81 // just the output buffer itself.
82
83 JS_ASSERT(ForkJoinContext::current() == cx);
84
85 if (object->is<TypedObject>()) {
86 TypedObject &typedObj = object->as<TypedObject>();
87
88 // Note: check target region based on `typedObj`, not the owner.
89 // This is because `typedObj` may point to some subregion of the
90 // owner and we only care if that *subregion* is within the
91 // target region, not the entire owner.
92 if (IsInTargetRegion(cx, &typedObj))
93 return true;
94
95 // Also check whether owner is thread-local.
96 ArrayBufferObject &owner = typedObj.owner();
97 return cx->isThreadLocal(&owner);
98 }
99
100 // For other kinds of writable objects, must be thread-local.
101 return cx->isThreadLocal(object);
102 }
103
104 // Check that |object| (which must be a typed typedObj) maps
105 // to memory in the target region.
106 //
107 // For efficiency, we assume that all handles which the user has
108 // access to are either entirely within the target region or entirely
109 // without, but not straddling the target region nor encompassing
110 // it. This invariant is maintained by the PJS APIs, where the target
111 // region and handles are always elements of the same output array.
112 bool
113 jit::IsInTargetRegion(ForkJoinContext *cx, TypedObject *typedObj)
114 {
115 JS_ASSERT(typedObj->is<TypedObject>()); // in case JIT supplies something bogus
116 uint8_t *typedMem = typedObj->typedMem();
117 return (typedMem >= cx->targetRegionStart &&
118 typedMem < cx->targetRegionEnd);
119 }
120
121 #ifdef DEBUG
122 static void
123 printTrace(const char *prefix, struct IonLIRTraceData *cached)
124 {
125 fprintf(stderr, "%s / Block %3u / LIR %3u / Mode %u / LIR %s\n",
126 prefix,
127 cached->blockIndex, cached->lirIndex, cached->execModeInt, cached->lirOpName);
128 }
129
130 static struct IonLIRTraceData seqTraceData;
131 #endif
132
133 void
134 jit::TraceLIR(IonLIRTraceData *current)
135 {
136 #ifdef DEBUG
137 static enum { NotSet, All, Bailouts } traceMode;
138
139 // If you set IONFLAGS=trace, this function will be invoked before every LIR.
140 //
141 // You can either modify it to do whatever you like, or use gdb scripting.
142 // For example:
143 //
144 // break TraceLIR
145 // commands
146 // continue
147 // exit
148
149 if (traceMode == NotSet) {
150 // Racy, but that's ok.
151 const char *env = getenv("IONFLAGS");
152 if (strstr(env, "trace-all"))
153 traceMode = All;
154 else
155 traceMode = Bailouts;
156 }
157
158 IonLIRTraceData *cached;
159 if (current->execModeInt == 0)
160 cached = &seqTraceData;
161 else
162 cached = &ForkJoinContext::current()->traceData;
163
164 if (current->blockIndex == 0xDEADBEEF) {
165 if (current->execModeInt == 0)
166 printTrace("BAILOUT", cached);
167 else
168 SpewBailoutIR(cached);
169 }
170
171 memcpy(cached, current, sizeof(IonLIRTraceData));
172
173 if (traceMode == All)
174 printTrace("Exec", cached);
175 #endif
176 }
177
178 bool
179 jit::CheckOverRecursedPar(ForkJoinContext *cx)
180 {
181 JS_ASSERT(ForkJoinContext::current() == cx);
182 int stackDummy_;
183
184 // When an interrupt is requested, the main thread stack limit is
185 // overwritten with a sentinel value that brings us here.
186 // Therefore, we must check whether this is really a stack overrun
187 // and, if not, check whether an interrupt was requested.
188 //
189 // When not on the main thread, we don't overwrite the stack
190 // limit, but we do still call into this routine if the interrupt
191 // flag is set, so we still need to double check.
192
193 #ifdef JS_ARM_SIMULATOR
194 if (Simulator::Current()->overRecursed()) {
195 cx->bailoutRecord->setCause(ParallelBailoutOverRecursed);
196 return false;
197 }
198 #endif
199
200 uintptr_t realStackLimit;
201 if (cx->isMainThread())
202 realStackLimit = GetNativeStackLimit(cx);
203 else
204 realStackLimit = cx->perThreadData->jitStackLimit;
205
206 if (!JS_CHECK_STACK_SIZE(realStackLimit, &stackDummy_)) {
207 cx->bailoutRecord->setCause(ParallelBailoutOverRecursed);
208 return false;
209 }
210
211 return InterruptCheckPar(cx);
212 }
213
214 bool
215 jit::InterruptCheckPar(ForkJoinContext *cx)
216 {
217 JS_ASSERT(ForkJoinContext::current() == cx);
218 bool result = cx->check();
219 if (!result) {
220 // Do not set the cause here. Either it was set by this
221 // thread already by some code that then triggered an abort,
222 // or else we are just picking up an abort from some other
223 // thread. Either way we have nothing useful to contribute so
224 // we might as well leave our bailout case unset.
225 return false;
226 }
227 return true;
228 }
229
230 JSObject *
231 jit::ExtendArrayPar(ForkJoinContext *cx, JSObject *array, uint32_t length)
232 {
233 JSObject::EnsureDenseResult res =
234 array->ensureDenseElementsPreservePackedFlag(cx, 0, length);
235 if (res != JSObject::ED_OK)
236 return nullptr;
237 return array;
238 }
239
240 bool
241 jit::SetPropertyPar(ForkJoinContext *cx, HandleObject obj, HandlePropertyName name,
242 HandleValue value, bool strict, jsbytecode *pc)
243 {
244 JS_ASSERT(cx->isThreadLocal(obj));
245
246 if (*pc == JSOP_SETALIASEDVAR) {
247 // See comment in jit::SetProperty.
248 Shape *shape = obj->nativeLookupPure(name);
249 JS_ASSERT(shape && shape->hasSlot());
250 return obj->nativeSetSlotIfHasType(shape, value);
251 }
252
253 // Fail early on hooks.
254 if (obj->getOps()->setProperty)
255 return TP_RETRY_SEQUENTIALLY;
256
257 RootedValue v(cx, value);
258 RootedId id(cx, NameToId(name));
259 return baseops::SetPropertyHelper<ParallelExecution>(cx, obj, obj, id, baseops::Qualified, &v,
260 strict);
261 }
262
263 bool
264 jit::SetElementPar(ForkJoinContext *cx, HandleObject obj, HandleValue index, HandleValue value,
265 bool strict)
266 {
267 RootedId id(cx);
268 if (!ValueToIdPure(index, id.address()))
269 return false;
270
271 // SetObjectElementOperation, the sequential version, has several checks
272 // for certain deoptimizing behaviors, such as marking having written to
273 // holes and non-indexed element accesses. We don't do that here, as we
274 // can't modify any TI state anyways. If we need to add a new type, we
275 // would bail out.
276 RootedValue v(cx, value);
277 return baseops::SetPropertyHelper<ParallelExecution>(cx, obj, obj, id, baseops::Qualified, &v,
278 strict);
279 }
280
281 bool
282 jit::SetDenseElementPar(ForkJoinContext *cx, HandleObject obj, int32_t index, HandleValue value,
283 bool strict)
284 {
285 RootedValue indexVal(cx, Int32Value(index));
286 return SetElementPar(cx, obj, indexVal, value, strict);
287 }
288
289 JSString *
290 jit::ConcatStringsPar(ForkJoinContext *cx, HandleString left, HandleString right)
291 {
292 return ConcatStrings<NoGC>(cx, left, right);
293 }
294
295 JSFlatString *
296 jit::IntToStringPar(ForkJoinContext *cx, int i)
297 {
298 return Int32ToString<NoGC>(cx, i);
299 }
300
301 JSString *
302 jit::DoubleToStringPar(ForkJoinContext *cx, double d)
303 {
304 return NumberToString<NoGC>(cx, d);
305 }
306
307 JSString *
308 jit::PrimitiveToStringPar(ForkJoinContext *cx, HandleValue input)
309 {
310 // All other cases are handled in assembly.
311 JS_ASSERT(input.isDouble() || input.isInt32());
312
313 if (input.isInt32())
314 return Int32ToString<NoGC>(cx, input.toInt32());
315
316 return NumberToString<NoGC>(cx, input.toDouble());
317 }
318
319 bool
320 jit::StringToNumberPar(ForkJoinContext *cx, JSString *str, double *out)
321 {
322 return StringToNumber(cx, str, out);
323 }
324
325 #define PAR_RELATIONAL_OP(OP, EXPECTED) \
326 do { \
327 /* Optimize for two int-tagged operands (typical loop control). */ \
328 if (lhs.isInt32() && rhs.isInt32()) { \
329 *res = (lhs.toInt32() OP rhs.toInt32()) == EXPECTED; \
330 } else if (lhs.isNumber() && rhs.isNumber()) { \
331 double l = lhs.toNumber(), r = rhs.toNumber(); \
332 *res = (l OP r) == EXPECTED; \
333 } else if (lhs.isBoolean() && rhs.isBoolean()) { \
334 bool l = lhs.toBoolean(); \
335 bool r = rhs.toBoolean(); \
336 *res = (l OP r) == EXPECTED; \
337 } else if (lhs.isBoolean() && rhs.isNumber()) { \
338 bool l = lhs.toBoolean(); \
339 double r = rhs.toNumber(); \
340 *res = (l OP r) == EXPECTED; \
341 } else if (lhs.isNumber() && rhs.isBoolean()) { \
342 double l = lhs.toNumber(); \
343 bool r = rhs.toBoolean(); \
344 *res = (l OP r) == EXPECTED; \
345 } else { \
346 int32_t vsZero; \
347 if (!CompareMaybeStringsPar(cx, lhs, rhs, &vsZero)) \
348 return false; \
349 *res = (vsZero OP 0) == EXPECTED; \
350 } \
351 return true; \
352 } while(0)
353
354 static bool
355 CompareStringsPar(ForkJoinContext *cx, JSString *left, JSString *right, int32_t *res)
356 {
357 ScopedThreadSafeStringInspector leftInspector(left);
358 ScopedThreadSafeStringInspector rightInspector(right);
359 if (!leftInspector.ensureChars(cx) || !rightInspector.ensureChars(cx))
360 return false;
361
362 *res = CompareChars(leftInspector.chars(), left->length(),
363 rightInspector.chars(), right->length());
364 return true;
365 }
366
367 static bool
368 CompareMaybeStringsPar(ForkJoinContext *cx, HandleValue v1, HandleValue v2, int32_t *res)
369 {
370 if (!v1.isString())
371 return false;
372 if (!v2.isString())
373 return false;
374 return CompareStringsPar(cx, v1.toString(), v2.toString(), res);
375 }
376
377 template<bool Equal>
378 bool
379 LooselyEqualImplPar(ForkJoinContext *cx, MutableHandleValue lhs, MutableHandleValue rhs, bool *res)
380 {
381 PAR_RELATIONAL_OP(==, Equal);
382 }
383
384 bool
385 js::jit::LooselyEqualPar(ForkJoinContext *cx, MutableHandleValue lhs, MutableHandleValue rhs, bool *res)
386 {
387 return LooselyEqualImplPar<true>(cx, lhs, rhs, res);
388 }
389
390 bool
391 js::jit::LooselyUnequalPar(ForkJoinContext *cx, MutableHandleValue lhs, MutableHandleValue rhs, bool *res)
392 {
393 return LooselyEqualImplPar<false>(cx, lhs, rhs, res);
394 }
395
396 template<bool Equal>
397 bool
398 StrictlyEqualImplPar(ForkJoinContext *cx, MutableHandleValue lhs, MutableHandleValue rhs, bool *res)
399 {
400 if (lhs.isNumber()) {
401 if (rhs.isNumber()) {
402 *res = (lhs.toNumber() == rhs.toNumber()) == Equal;
403 return true;
404 }
405 } else if (lhs.isBoolean()) {
406 if (rhs.isBoolean()) {
407 *res = (lhs.toBoolean() == rhs.toBoolean()) == Equal;
408 return true;
409 }
410 } else if (lhs.isNull()) {
411 if (rhs.isNull()) {
412 *res = Equal;
413 return true;
414 }
415 } else if (lhs.isUndefined()) {
416 if (rhs.isUndefined()) {
417 *res = Equal;
418 return true;
419 }
420 } else if (lhs.isObject()) {
421 if (rhs.isObject()) {
422 *res = (lhs.toObjectOrNull() == rhs.toObjectOrNull()) == Equal;
423 return true;
424 }
425 } else if (lhs.isString()) {
426 if (rhs.isString())
427 return LooselyEqualImplPar<Equal>(cx, lhs, rhs, res);
428 }
429
430 *res = false;
431 return true;
432 }
433
434 bool
435 js::jit::StrictlyEqualPar(ForkJoinContext *cx, MutableHandleValue lhs, MutableHandleValue rhs, bool *res)
436 {
437 return StrictlyEqualImplPar<true>(cx, lhs, rhs, res);
438 }
439
440 bool
441 js::jit::StrictlyUnequalPar(ForkJoinContext *cx, MutableHandleValue lhs, MutableHandleValue rhs, bool *res)
442 {
443 return StrictlyEqualImplPar<false>(cx, lhs, rhs, res);
444 }
445
446 bool
447 js::jit::LessThanPar(ForkJoinContext *cx, MutableHandleValue lhs, MutableHandleValue rhs, bool *res)
448 {
449 PAR_RELATIONAL_OP(<, true);
450 }
451
452 bool
453 js::jit::LessThanOrEqualPar(ForkJoinContext *cx, MutableHandleValue lhs, MutableHandleValue rhs, bool *res)
454 {
455 PAR_RELATIONAL_OP(<=, true);
456 }
457
458 bool
459 js::jit::GreaterThanPar(ForkJoinContext *cx, MutableHandleValue lhs, MutableHandleValue rhs, bool *res)
460 {
461 PAR_RELATIONAL_OP(>, true);
462 }
463
464 bool
465 js::jit::GreaterThanOrEqualPar(ForkJoinContext *cx, MutableHandleValue lhs, MutableHandleValue rhs, bool *res)
466 {
467 PAR_RELATIONAL_OP(>=, true);
468 }
469
470 template<bool Equal>
471 bool
472 StringsEqualImplPar(ForkJoinContext *cx, HandleString lhs, HandleString rhs, bool *res)
473 {
474 int32_t vsZero;
475 bool ret = CompareStringsPar(cx, lhs, rhs, &vsZero);
476 if (ret != true)
477 return ret;
478 *res = (vsZero == 0) == Equal;
479 return true;
480 }
481
482 bool
483 js::jit::StringsEqualPar(ForkJoinContext *cx, HandleString v1, HandleString v2, bool *res)
484 {
485 return StringsEqualImplPar<true>(cx, v1, v2, res);
486 }
487
488 bool
489 js::jit::StringsUnequalPar(ForkJoinContext *cx, HandleString v1, HandleString v2, bool *res)
490 {
491 return StringsEqualImplPar<false>(cx, v1, v2, res);
492 }
493
494 bool
495 jit::BitNotPar(ForkJoinContext *cx, HandleValue in, int32_t *out)
496 {
497 if (in.isObject())
498 return false;
499 int i;
500 if (!NonObjectToInt32(cx, in, &i))
501 return false;
502 *out = ~i;
503 return true;
504 }
505
506 #define BIT_OP(OP) \
507 JS_BEGIN_MACRO \
508 int32_t left, right; \
509 if (lhs.isObject() || rhs.isObject()) \
510 return false; \
511 if (!NonObjectToInt32(cx, lhs, &left) || \
512 !NonObjectToInt32(cx, rhs, &right)) \
513 { \
514 return false; \
515 } \
516 *out = (OP); \
517 return true; \
518 JS_END_MACRO
519
520 bool
521 jit::BitXorPar(ForkJoinContext *cx, HandleValue lhs, HandleValue rhs, int32_t *out)
522 {
523 BIT_OP(left ^ right);
524 }
525
526 bool
527 jit::BitOrPar(ForkJoinContext *cx, HandleValue lhs, HandleValue rhs, int32_t *out)
528 {
529 BIT_OP(left | right);
530 }
531
532 bool
533 jit::BitAndPar(ForkJoinContext *cx, HandleValue lhs, HandleValue rhs, int32_t *out)
534 {
535 BIT_OP(left & right);
536 }
537
538 bool
539 jit::BitLshPar(ForkJoinContext *cx, HandleValue lhs, HandleValue rhs, int32_t *out)
540 {
541 BIT_OP(uint32_t(left) << (right & 31));
542 }
543
544 bool
545 jit::BitRshPar(ForkJoinContext *cx, HandleValue lhs, HandleValue rhs, int32_t *out)
546 {
547 BIT_OP(left >> (right & 31));
548 }
549
550 #undef BIT_OP
551
552 bool
553 jit::UrshValuesPar(ForkJoinContext *cx, HandleValue lhs, HandleValue rhs,
554 MutableHandleValue out)
555 {
556 uint32_t left;
557 int32_t right;
558 if (lhs.isObject() || rhs.isObject())
559 return false;
560 if (!NonObjectToUint32(cx, lhs, &left) || !NonObjectToInt32(cx, rhs, &right))
561 return false;
562 left >>= right & 31;
563 out.setNumber(uint32_t(left));
564 return true;
565 }
566
567 void
568 jit::AbortPar(ParallelBailoutCause cause, JSScript *outermostScript, JSScript *currentScript,
569 jsbytecode *bytecode)
570 {
571 // Spew before asserts to help with diagnosing failures.
572 Spew(SpewBailouts,
573 "Parallel abort with cause %d in %p:%s:%d "
574 "(%p:%s:%d at line %d)",
575 cause,
576 outermostScript, outermostScript->filename(), outermostScript->lineno(),
577 currentScript, currentScript->filename(), currentScript->lineno(),
578 (currentScript ? PCToLineNumber(currentScript, bytecode) : 0));
579
580 JS_ASSERT(InParallelSection());
581 JS_ASSERT(outermostScript != nullptr);
582 JS_ASSERT(currentScript != nullptr);
583 JS_ASSERT(outermostScript->hasParallelIonScript());
584
585 ForkJoinContext *cx = ForkJoinContext::current();
586
587 JS_ASSERT(cx->bailoutRecord->depth == 0);
588 cx->bailoutRecord->setCause(cause, outermostScript, currentScript, bytecode);
589 }
590
591 void
592 jit::PropagateAbortPar(JSScript *outermostScript, JSScript *currentScript)
593 {
594 Spew(SpewBailouts,
595 "Propagate parallel abort via %p:%s:%d (%p:%s:%d)",
596 outermostScript, outermostScript->filename(), outermostScript->lineno(),
597 currentScript, currentScript->filename(), currentScript->lineno());
598
599 JS_ASSERT(InParallelSection());
600 JS_ASSERT(outermostScript->hasParallelIonScript());
601
602 outermostScript->parallelIonScript()->setHasUncompiledCallTarget();
603
604 ForkJoinContext *cx = ForkJoinContext::current();
605 if (currentScript)
606 cx->bailoutRecord->addTrace(currentScript, nullptr);
607 }
608
609 void
610 jit::CallToUncompiledScriptPar(JSObject *obj)
611 {
612 JS_ASSERT(InParallelSection());
613
614 #ifdef DEBUG
615 static const int max_bound_function_unrolling = 5;
616
617 if (!obj->is<JSFunction>()) {
618 Spew(SpewBailouts, "Call to non-function");
619 return;
620 }
621
622 JSFunction *func = &obj->as<JSFunction>();
623 if (func->hasScript()) {
624 JSScript *script = func->nonLazyScript();
625 Spew(SpewBailouts, "Call to uncompiled script: %p:%s:%d",
626 script, script->filename(), script->lineno());
627 } else if (func->isInterpretedLazy()) {
628 Spew(SpewBailouts, "Call to uncompiled lazy script");
629 } else if (func->isBoundFunction()) {
630 int depth = 0;
631 JSFunction *target = &func->getBoundFunctionTarget()->as<JSFunction>();
632 while (depth < max_bound_function_unrolling) {
633 if (target->hasScript())
634 break;
635 if (target->isBoundFunction())
636 target = &target->getBoundFunctionTarget()->as<JSFunction>();
637 depth--;
638 }
639 if (target->hasScript()) {
640 JSScript *script = target->nonLazyScript();
641 Spew(SpewBailouts, "Call to bound function leading (depth: %d) to script: %p:%s:%d",
642 depth, script, script->filename(), script->lineno());
643 } else {
644 Spew(SpewBailouts, "Call to bound function (excessive depth: %d)", depth);
645 }
646 } else {
647 JS_ASSERT(func->isNative());
648 Spew(SpewBailouts, "Call to native function");
649 }
650 #endif
651 }
652
653 JSObject *
654 jit::InitRestParameterPar(ForkJoinContext *cx, uint32_t length, Value *rest,
655 HandleObject templateObj, HandleObject res)
656 {
657 // In parallel execution, we should always have succeeded in allocation
658 // before this point. We can do the allocation here like in the sequential
659 // path, but duplicating the initGCThing logic is too tedious.
660 JS_ASSERT(res);
661 JS_ASSERT(res->is<ArrayObject>());
662 JS_ASSERT(!res->getDenseInitializedLength());
663 JS_ASSERT(res->type() == templateObj->type());
664
665 if (length > 0) {
666 JSObject::EnsureDenseResult edr =
667 res->ensureDenseElementsPreservePackedFlag(cx, 0, length);
668 if (edr != JSObject::ED_OK)
669 return nullptr;
670 res->initDenseElementsUnbarriered(0, rest, length);
671 res->as<ArrayObject>().setLengthInt32(length);
672 }
673
674 return res;
675 }

mercurial