ViewVC Help
View File | Revision Log | Show Annotations | View Changeset | Root Listing
root/svn/branches/newio/src/msgq.c
Revision: 2388
Committed: Tue Jul 9 11:22:52 2013 UTC (13 years, 1 month ago) by michael
Content type: text/x-csrc
File size: 20413 byte(s)
Log Message:
- Working towards implementing new ioengine

File Contents

# Content
1 /*
2 * ircd-hybrid: an advanced Internet Relay Chat Daemon(ircd).
3 *
4 * Copyright (C) 2000 Kevin L. Mitchell <klmitch@mit.edu>
5 * Copyright (C) 2013 by the Hybrid Development Team.
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
20 * USA
21 */
22
23 /*! \file msgq.c
24 * \brief Outbound message queue implementation.
25 * \version $Id: msgq.c 2386 2013-07-06 20:47:09Z michael $
26 */
27
28 #include "stdinc.h"
29 #include "msgq.h"
30 #include "list.h"
31 #include "log.h"
32 #include "send.h"
33 #include "memory.h"
34 #include "restart.h"
35 #include "client.h"
36 #include "ircd.h"
37 #include "numeric.h"
38 #include "xsnprintf.h"
39
40
41 #include <sys/uio.h>
42
43 #define FEAT_BUFFERPOOL 27000000 /* XXX */
44
45 #define MB_BASE_SHIFT 5 /**< Log2 of smallest message body to allocate. */
46 #define MB_MAX_SHIFT 9 /**< Log2 of largest message body to allocate. */
47
48 /** Buffer for a single message. */
49 struct MsgBuf
50 {
51 struct MsgBuf *next; /**< next msg in global queue */
52 struct MsgBuf **prev_p; /**< what points to us in linked list */
53 struct MsgBuf *real; /**< the actual MsgBuf we're attaching */
54 unsigned int ref; /**< reference count */
55 unsigned int length; /**< length of message */
56 unsigned int power; /**< size of buffer (power of 2) */
57 char msg[1]; /**< the message */
58 };
59
60 /** Return allocated length of the buffer of \a buf. */
61 #define bufsize(buf) (1 << (buf)->power)
62
63 /** Message body for a particular destination. */
64 struct Msg
65 {
66 struct Msg *next; /**< next msg */
67 unsigned int sent; /**< bytes in msg that have already been sent */
68 struct MsgBuf *msg; /**< actual message in queue */
69 };
70
71 /** Statistics tracking for message sizes. */
72 struct MsgSizes
73 {
74 unsigned int msgs; /**< total number of messages */
75 unsigned int sizes[IRCD_BUFSIZE]; /**< histogram of message sizes */
76 };
77
78 /** Global tracking data for message buffers. */
79 static struct
80 {
81 struct MsgBuf *msglist; /**< list of in-use MsgBuf's */
82
83 struct
84 {
85 unsigned int alloc; /**< number of Msg's allocated */
86 unsigned int used; /**< number of Msg's in use */
87 struct Msg *free; /**< freelist of Msg's */
88 } msgs; /**< tracking info for Msg structs */
89
90 size_t tot_bufsize; /**< total amount of memory in buffers */
91
92 /** Array of MsgBuf information, one entry for each used bucket size. */
93 struct
94 {
95 unsigned int alloc; /**< total MsgBuf's of this size */
96 unsigned int used; /**< number of MsgBuf's of this size in use */
97 struct MsgBuf *free; /**< list of free MsgBuf's */
98 } msgBufs[MB_MAX_SHIFT - MB_BASE_SHIFT + 1];
99
100 struct MsgSizes sizes; /**< histogram of message sizes */
101 } MQData;
102
103 /*
104 * This routine is used to remove a certain amount of data from a given
105 * queue and release the Msg (and MsgBuf) structure if needed
106 */
107 /** Remove some data from a list within a message queue.
108 * @param[in,out] mq Message queue to remove from.
109 * @param[in,out] qlist Particular list within queue to remove from.
110 * @param[in,out] length_p Number of bytes left to remove.
111 */
112 static void
113 msgq_delmsg(struct MsgQ *mq, struct MsgQList *qlist, unsigned int *length_p)
114 {
115 struct Msg *m;
116 unsigned int msglen;
117
118 assert(mq);
119 assert(qlist);
120 assert(qlist->head);
121 assert(length_p);
122
123 m = qlist->head; /* find the msg we're deleting from */
124
125 msglen = m->msg->length - m->sent; /* calculate how much is left */
126
127 if (*length_p >= msglen)
128 {
129 /* deleted it all? */
130 mq->length -= msglen; /* decrement length */
131 mq->count--; /* decrement the message count */
132 *length_p -= msglen;
133
134 msgq_clean(m->msg); /* free up the struct MsgBuf */
135 m->msg = 0; /* don't let it point anywhere nasty, please */
136
137 if (qlist->head == qlist->tail) /* figure out if we emptied the queue */
138 qlist->head = qlist->tail = 0;
139 else
140 qlist->head = m->next; /* just shift the list down some */
141
142 MQData.msgs.used--; /* struct Msg is not in use anymore */
143
144 m->next = MQData.msgs.free; /* throw it onto the free list */
145 MQData.msgs.free = m;
146 }
147 else
148 {
149 mq->length -= *length_p; /* decrement queue length */
150 m->sent += *length_p; /* this much of the message has been sent */
151 *length_p = 0; /* we've dealt with it all */
152 }
153 }
154
155 /** Initialize \a mq.
156 * @param[in] mq MsgQ to initialize.
157 */
158 void
159 msgq_init(struct MsgQ *mq)
160 {
161 assert(mq);
162
163 mq->length = 0;
164 mq->count = 0;
165 mq->queue.head = 0;
166 mq->queue.tail = 0;
167 mq->prio.head = 0;
168 mq->prio.tail = 0;
169 }
170
171 /** Delete bytes from the front of a message queue.
172 * @param[in] mq Queue to drop data from.
173 * @param[in] length Number of bytes to drop.
174 */
175 void
176 msgq_delete(struct MsgQ *mq, unsigned int length)
177 {
178 assert(0 != mq);
179
180 while (length > 0)
181 {
182 if (mq->queue.head && mq->queue.head->sent > 0) /* partial msg on norm q */
183 msgq_delmsg(mq, &mq->queue, &length);
184 else if (mq->prio.head) /* message (partial or complete) on prio queue */
185 msgq_delmsg(mq, &mq->prio, &length);
186 else if (mq->queue.head) /* message on normal queue */
187 msgq_delmsg(mq, &mq->queue, &length);
188 else
189 break;
190 }
191 }
192
193 /** Map data from a message queue to an I/O vector.
194 * @param[in] mq Message queue to send from.
195 * @param[out] iov Output vector.
196 * @param[in] count Number of elements in \a iov.
197 * @param[out] len Number of bytes mapped from \a mq to \a iov.
198 * @return Number of elements filled in \a iov.
199 */
200 int
201 msgq_mapiov(const struct MsgQ *mq, struct iovec *iov, int count,
202 unsigned int *len)
203 {
204 struct Msg *queue;
205 struct Msg *prio;
206 int i = 0;
207
208 assert(mq);
209 assert(iov);
210 assert(count);
211 assert(len);
212
213 if (mq->length <= 0) /* no data to map */
214 return 0;
215
216 if (mq->queue.head && mq->queue.head->sent > 0)
217 {
218 /* partial msg on norm q */
219 iov[i].iov_base = mq->queue.head->msg->msg + mq->queue.head->sent;
220 iov[i].iov_len = mq->queue.head->msg->length - mq->queue.head->sent;
221 *len += iov[i].iov_len;
222
223 queue = mq->queue.head->next; /* where we start later... */
224
225 i++; /* filled an iovec... */
226
227 if (!--count) /* check for space */
228 return i;
229 }
230 else
231 queue = mq->queue.head; /* start at head of queue */
232
233 if (mq->prio.head && mq->prio.head->sent > 0)
234 {
235 /* partial msg on prio q */
236 iov[i].iov_base = mq->prio.head->msg->msg + mq->prio.head->sent;
237 iov[i].iov_len = mq->prio.head->msg->length - mq->prio.head->sent;
238 *len += iov[i].iov_len;
239
240 prio = mq->prio.head->next; /* where we start later... */
241
242 i++; /* filled an iovec... */
243
244 if (!--count) /* check for space */
245 return i;
246 }
247 else
248 prio = mq->prio.head; /* start at head of prio */
249
250 for (; prio; prio = prio->next)
251 {
252 /* go through prio queue */
253 iov[i].iov_base = prio->msg->msg; /* store message */
254 iov[i].iov_len = prio->msg->length;
255 *len += iov[i].iov_len;
256
257 i++; /* filled an iovec... */
258
259 if (!--count) /* check for space */
260 return i;
261 }
262
263 for (; queue; queue = queue->next)
264 {
265 /* go through normal queue */
266 iov[i].iov_base = queue->msg->msg;
267 iov[i].iov_len = queue->msg->length;
268 *len += iov[i].iov_len;
269
270 i++; /* filled an iovec... */
271
272 if (!--count) /* check for space */
273 return i;
274 }
275
276 return i;
277 }
278
279 /** Allocate a message buffer large enough to hold \a length bytes.
280 * TODO: \a in_mb needs better documentation.
281 * @param[in] in_mb Some other message buffer(?).
282 * @param[in] length Number of bytes of space to reserve in output.
283 * @return Pointer to some usable message buffer.
284 */
285 static struct MsgBuf *
286 msgq_alloc(struct MsgBuf *in_mb, unsigned int length)
287 {
288 struct MsgBuf *mb;
289 unsigned int power;
290
291 /* Find the power of two size that will accommodate the message */
292 for (power = MB_BASE_SHIFT; power < MB_MAX_SHIFT + 1; power++)
293 if ((length - 1) >> power == 0)
294 break;
295
296 assert((1 << power) >= length);
297 assert((1 << power) <= 512);
298 length = 1 << power; /* reset the length */
299
300 /* If the message needs a buffer of exactly the existing size, just use it */
301 if (in_mb && in_mb->power == power)
302 {
303 in_mb->real = in_mb; /* real buffer is this buffer */
304 return in_mb;
305 }
306
307 /* Try popping one off the freelist first */
308 if ((mb = MQData.msgBufs[power - MB_BASE_SHIFT].free))
309 MQData.msgBufs[power - MB_BASE_SHIFT].free = mb->next;
310 else if (MQData.tot_bufsize < FEAT_BUFFERPOOL)
311 {
312 /* Allocate another if we won't bust the BUFFERPOOL */
313 ilog(LOG_TYPE_DEBUG, "Allocating MsgBuf of length %d (total size %zu)",
314 length, sizeof(struct MsgBuf) + length);
315
316 mb = MyMalloc(sizeof(struct MsgBuf) + length);
317 MQData.msgBufs[power - MB_BASE_SHIFT].alloc++;
318 mb->power = power; /* remember size */
319 MQData.tot_bufsize += length;
320 }
321
322 if (mb)
323 {
324 MQData.msgBufs[power - MB_BASE_SHIFT].used++; /* how many are we using? */
325
326 mb->real = 0; /* essential initializations */
327 mb->ref = 1;
328
329 if (in_mb) /* remember who's the *real* buffer */
330 in_mb->real = mb;
331 }
332 else if (in_mb) /* just use the input buffer */
333 mb = in_mb->real = in_mb;
334
335 return mb; /* return the buffer */
336 }
337
338 /** Deallocate unused message buffers.
339 */
340 static void
341 msgq_clear_freembs(void)
342 {
343 struct MsgBuf *mb;
344 int i;
345
346 /* Walk through the various size classes */
347 for (i = MB_BASE_SHIFT; i < MB_MAX_SHIFT + 1; i++)
348 {
349 /* walk down the free list */
350 while ((mb = MQData.msgBufs[i - MB_BASE_SHIFT].free))
351 {
352 MQData.msgBufs[i - MB_BASE_SHIFT].free = mb->next; /* shift free list */
353 MQData.msgBufs[i - MB_BASE_SHIFT].alloc--; /* reduce allocation count */
354 MQData.tot_bufsize -= 1 << i; /* reduce total buffer allocation count */
355 MyFree(mb); /* and free the buffer */
356 }
357 }
358 }
359
360 /** Format a message buffer for a client from a format string.
361 * @param[in] dest %Client that receives the data (may be NULL).
362 * @param[in] format Format string for message.
363 * @param[in] vl Argument list for \a format.
364 * @return Allocated MsgBuf.
365 */
366 struct MsgBuf *
367 msgq_vmake(struct Client *dest, const char *format, va_list vl)
368 {
369 struct MsgBuf *mb;
370
371 assert(format);
372
373 if (!(mb = msgq_alloc(0, IRCD_BUFSIZE)))
374 {
375 /*
376 * from "Married With Children" episode were Al bought a REAL toilet
377 * on the black market because he was tired of the wimpy water
378 * conserving toilets they make these days --Bleep
379 */
380 /*
381 * Apparently this doesn't work, the server _has_ to
382 * dump a few clients to handle the load. A fully loaded
383 * server cannot handle a net break without dumping some
384 * clients. If we flush the connections here under a full
385 * load we may end up starving the kernel for mbufs and
386 * crash the machine
387 */
388 /*
389 * attempt to recover from buffer starvation before
390 * bailing this may help servers running out of memory
391 */
392 send_queued_all();
393 mb = msgq_alloc(0, IRCD_BUFSIZE);
394
395 if (!mb)
396 { /* OK, try clearing the buffer free list */
397 msgq_clear_freembs();
398 mb = msgq_alloc(0, IRCD_BUFSIZE);
399 }
400 #if 0
401 if (!mb)
402 {
403 /* OK, try killing a client */
404 kill_highest_sendq(0); /* Don't kill any server connections */
405 msgq_clear_freembs(); /* Release whatever was just freelisted */
406 mb = msgq_alloc(0, IRCD_BUFSIZE);
407 }
408
409 if (!mb)
410 {
411 kill_highest_sendq(1); /* Try killing a server connection now */
412 msgq_clear_freembs(); /* Clear freelist again */
413 mb = msgq_alloc(0, IRCD_BUFSIZE);
414 }
415 #endif
416 if (!mb) /* AIEEEE! */
417 restart("Unable to allocate buffers!");
418 }
419
420 mb->next = MQData.msglist; /* initialize the msgbuf */
421 mb->prev_p = &MQData.msglist;
422
423 /* fill the buffer */
424 mb->length = xvsnprintf(dest, mb->msg, bufsize(mb) - 1, format, vl);
425
426 if (mb->length > bufsize(mb) - 2)
427 mb->length = bufsize(mb) - 2;
428
429 mb->msg[mb->length++] = '\r'; /* add \r\n to buffer */
430 mb->msg[mb->length++] = '\n';
431 mb->msg[mb->length] = '\0'; /* not strictly necessary */
432
433 assert(mb->length <= bufsize(mb));
434
435 if (MQData.msglist) /* link it into the list */
436 MQData.msglist->prev_p = &mb->next;
437 MQData.msglist = mb;
438
439 return mb;
440 }
441
442 /** Format a message buffer for a client from a format string.
443 * @param[in] dest %Client that receives the data (may be NULL).
444 * @param[in] format Format string for message.
445 * @return Allocated MsgBuf.
446 */
447 struct MsgBuf *
448 msgq_make(struct Client *dest, const char *format, ...)
449 {
450 va_list vl;
451 struct MsgBuf *mb;
452
453 va_start(vl, format);
454 mb = msgq_vmake(dest, format, vl);
455 va_end(vl);
456
457 return mb;
458 }
459
460 /** Append text to an existing message buffer.
461 * @param[in] dest %Client for whom to format the message.
462 * @param[in] mb Message buffer to append to.
463 * @param[in] format Format string of what to append.
464 */
465 void
466 msgq_append(struct Client *dest, struct MsgBuf *mb, const char *format, ...)
467 {
468 va_list vl;
469
470 assert(mb);
471 assert(format);
472 assert(0 == mb->real);
473
474 assert(2 < mb->length);
475 assert(bufsize(mb) >= mb->length);
476
477 mb->length -= 2; /* back up to before \r\n */
478
479 va_start(vl, format); /* append to the buffer */
480
481 mb->length += xvsnprintf(dest, mb->msg + mb->length,
482 bufsize(mb) - mb->length - 1, format, vl);
483
484 va_end(vl);
485
486 if (mb->length > bufsize(mb) - 2)
487 mb->length = bufsize(mb) - 2;
488
489 mb->msg[mb->length++] = '\r'; /* add \r\n to buffer */
490 mb->msg[mb->length++] = '\n';
491 mb->msg[mb->length] = '\0'; /* not strictly necessary */
492
493 assert(mb->length <= bufsize(mb));
494 }
495
496 /** Decrement the reference count on \a mb, freeing it if needed.
497 * @param[in] mb MsgBuf to release.
498 */
499 void
500 msgq_clean(struct MsgBuf *mb)
501 {
502 assert(mb);
503 assert(0 < mb->ref);
504
505 if (!--mb->ref)
506 {
507 /* deallocate the message */
508 if (mb->prev_p)
509 {
510 *mb->prev_p = mb->next; /* clip it out of active MsgBuf's list */
511
512 if (mb->next)
513 mb->next->prev_p = mb->prev_p;
514 }
515
516 if (mb->real && mb->real != mb) /* clean up the real buffer */
517 msgq_clean(mb->real);
518
519 mb->next = MQData.msgBufs[mb->power - MB_BASE_SHIFT].free;
520 MQData.msgBufs[mb->power - MB_BASE_SHIFT].free = mb;
521 MQData.msgBufs[mb->power - MB_BASE_SHIFT].used--;
522
523 mb->prev_p = 0;
524 }
525 }
526
527 /** Append a message to a peer's message queue.
528 * @param[in] mq Message queue to append to.
529 * @param[in] mb Message to append.
530 * @param[in] prio If non-zero, use the high-priority (lag-busting) message list; else use the normal list.
531 */
532 void
533 msgq_add(struct MsgQ *mq, struct MsgBuf *mb, int prio)
534 {
535 struct MsgQList *qlist;
536 struct Msg *msg;
537
538 assert(mq);
539 assert(mb);
540 assert(0 < mb->ref);
541 assert(0 < mb->length);
542
543 ilog(LOG_TYPE_DEBUG, "Adding buffer %p [%.*s] length %u to %s queue", mb,
544 mb->length - 2, mb->msg, mb->length, prio ? "priority" : "normal");
545
546 qlist = prio ? &mq->prio : &mq->queue;
547
548 if (!(msg = MQData.msgs.free))
549 {
550 /* do I need to allocate one? */
551 msg = MyMalloc(sizeof(struct Msg));
552 MQData.msgs.alloc++; /* we allocated another */
553 }
554 else /* shift the free list */
555 MQData.msgs.free = MQData.msgs.free->next;
556
557 MQData.msgs.used++; /* we're using another */
558
559 msg->next = 0; /* initialize the msg */
560 msg->sent = 0;
561
562 /* Get the real buffer, allocating one if necessary */
563 if (!mb->real)
564 {
565 struct MsgBuf *tmp;
566
567 MQData.sizes.msgs++; /* update histogram counts */
568 MQData.sizes.sizes[mb->length - 1]++;
569
570 tmp = msgq_alloc(mb, mb->length); /* allocate a close-fitting buffer */
571
572 if (tmp != mb)
573 {
574 /* OK, prepare the new "real" buffer */
575 ilog(LOG_TYPE_DEBUG, "Copying old buffer %p [%.*s] length %u into new "
576 "buffer %p size %u", mb, mb->length - 2, mb->msg, mb->length,
577 tmp, bufsize(tmp));
578 memcpy(tmp->msg, mb->msg, mb->length + 1); /* copy string over */
579 tmp->length = mb->length;
580
581 tmp->next = mb->next; /* replace it in the list, now */
582
583 if (tmp->next)
584 tmp->next->prev_p = &tmp->next;
585 tmp->prev_p = mb->prev_p;
586 *tmp->prev_p = tmp;
587
588 mb->next = 0; /* this one's no longer in the list */
589 mb->prev_p = 0;
590 }
591 }
592
593 mb = mb->real; /* work with the real buffer */
594 mb->ref++; /* increment the ref count on the buffer */
595
596 msg->msg = mb; /* point at the real message buffer now */
597
598 if (!qlist->head) /* queue list was empty; head and tail point to msg */
599 qlist->head = qlist->tail = msg;
600 else
601 {
602 assert(qlist->tail);
603
604 qlist->tail->next = msg; /* queue had something in it; add to end */
605 qlist->tail = msg;
606 }
607
608 mq->length += mb->length; /* update the queue length */
609 mq->count++; /* and the queue count */
610 }
611
612 /** Report memory statistics for message buffers.
613 * @param[in] cptr Client requesting information.
614 * @param[out] msg_alloc Receives number of bytes allocated in Msg structs.
615 * @param[out] msgbuf_alloc Receives number of bytes allocated in MsgBuf structs.
616 */
617 void
618 msgq_count_memory(struct Client *source_p, size_t *msg_alloc, size_t *msgbuf_alloc)
619 {
620 int i;
621 size_t total = 0, size;
622
623 assert(client_p);
624 assert(msg_alloc);
625 assert(msgbuf_alloc);
626
627 /* Data for Msg's is simple, so just send it */
628 sendto_one(source_p, ":%s %d %s :Msgs allocated %d(%zu) used %d(%zu) text %zu",
629 me.name, RPL_STATSDEBUG, source_p->name,
630 MQData.msgs.alloc, MQData.msgs.alloc * sizeof(struct Msg),
631 MQData.msgs.used, MQData.msgs.used * sizeof(struct Msg),
632 MQData.tot_bufsize);
633 /* count_memory() wants to know the total */
634 *msg_alloc = MQData.msgs.alloc * sizeof(struct Msg);
635
636 /* Ok, now walk through each size class */
637 for (i = MB_BASE_SHIFT; i < MB_MAX_SHIFT + 1; i++)
638 {
639 size = sizeof(struct MsgBuf) + (1 << i); /* total size of a buffer */
640
641 /* Send information for this buffer size class */
642 sendto_one(source_p, ":%s %d %s :MsgBufs of size %zu allocated %d(%zu) used %d(%zu)",
643 me.name, RPL_STATSDEBUG, source_p->name, 1 << i,
644 MQData.msgBufs[i - MB_BASE_SHIFT].alloc,
645 MQData.msgBufs[i - MB_BASE_SHIFT].alloc * size,
646 MQData.msgBufs[i - MB_BASE_SHIFT].used,
647 MQData.msgBufs[i - MB_BASE_SHIFT].used * size);
648
649 /* count_memory() wants to know the total */
650 total += MQData.msgBufs[i - MB_BASE_SHIFT].alloc * size;
651 }
652
653 *msgbuf_alloc = total;
654 }
655
656 /** Report remaining space in a MsgBuf.
657 * @param[in] mb Message buffer to check.
658 * @return Number of additional bytes that can be appended to the message.
659 */
660 unsigned int
661 msgq_bufleft(struct MsgBuf *mb)
662 {
663 assert(mb);
664
665 return bufsize(mb) - mb->length; /* \r\n counted in mb->length */
666 }
667
668 /** Send histogram of message lengths to a client.
669 * @param[in] cptr Client requesting statistics.
670 * @param[in] sd Stats descriptor for request (ignored).
671 * @param[in] param Extra parameter from user (ignored).
672 */
673 void
674 msgq_histogram(struct Client *source_p)
675 {
676 struct MsgSizes tmp = MQData.sizes; /* All hail structure copy! */
677 int i;
678
679 sendto_one(source_p, ":%s %d %s :Histogram of message lengths (%lu messages)",
680 me.name, RPL_STATSDEBUG, source_p->name, tmp.msgs);
681
682 for (i = 0; i + 16 <= IRCD_BUFSIZE; i += 16)
683 sendto_one(source_p, ":%s %d %s :% 4d: %u %u %u %u "
684 "%u %u %u %u %u %u %u %u %u %u %u %u",
685 me.name, RPL_STATSDEBUG, source_p->name, i + 1,
686 tmp.sizes[i + 0], tmp.sizes[i + 1], tmp.sizes[i + 2],
687 tmp.sizes[i + 3], tmp.sizes[i + 4], tmp.sizes[i + 5],
688 tmp.sizes[i + 6], tmp.sizes[i + 7], tmp.sizes[i + 8],
689 tmp.sizes[i + 9], tmp.sizes[i + 10], tmp.sizes[i + 11],
690 tmp.sizes[i + 12], tmp.sizes[i + 13], tmp.sizes[i + 14],
691 tmp.sizes[i + 15]);
692 }