Go to most recent revision | Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
4358 | Serge | 1 | /** |
2 | * \file imports.c |
||
3 | * Standard C library function wrappers. |
||
4 | * |
||
5 | * Imports are services which the device driver or window system or |
||
6 | * operating system provides to the core renderer. The core renderer (Mesa) |
||
7 | * will call these functions in order to do memory allocation, simple I/O, |
||
8 | * etc. |
||
9 | * |
||
10 | * Some drivers will want to override/replace this file with something |
||
11 | * specialized, but that'll be rare. |
||
12 | * |
||
13 | * Eventually, I want to move roll the glheader.h file into this. |
||
14 | * |
||
15 | * \todo Functions still needed: |
||
16 | * - scanf |
||
17 | * - qsort |
||
18 | * - rand and RAND_MAX |
||
19 | */ |
||
20 | |||
21 | /* |
||
22 | * Mesa 3-D graphics library |
||
23 | * |
||
24 | * Copyright (C) 1999-2007 Brian Paul All Rights Reserved. |
||
25 | * |
||
26 | * Permission is hereby granted, free of charge, to any person obtaining a |
||
27 | * copy of this software and associated documentation files (the "Software"), |
||
28 | * to deal in the Software without restriction, including without limitation |
||
29 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
||
30 | * and/or sell copies of the Software, and to permit persons to whom the |
||
31 | * Software is furnished to do so, subject to the following conditions: |
||
32 | * |
||
33 | * The above copyright notice and this permission notice shall be included |
||
34 | * in all copies or substantial portions of the Software. |
||
35 | * |
||
36 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
||
37 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||
38 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
||
39 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR |
||
40 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, |
||
41 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
||
42 | * OTHER DEALINGS IN THE SOFTWARE. |
||
43 | */ |
||
44 | |||
45 | |||
46 | |||
47 | #include "imports.h" |
||
48 | #include "context.h" |
||
49 | #include "mtypes.h" |
||
50 | #include "version.h" |
||
51 | |||
52 | #ifdef _GNU_SOURCE |
||
53 | #include |
||
54 | #ifdef __APPLE__ |
||
55 | #include |
||
56 | #endif |
||
57 | #endif |
||
58 | |||
59 | |||
60 | #ifdef _WIN32 |
||
61 | #define vsnprintf _vsnprintf |
||
62 | #elif defined(__IBMC__) || defined(__IBMCPP__) |
||
63 | extern int vsnprintf(char *str, size_t count, const char *fmt, va_list arg); |
||
64 | #endif |
||
65 | |||
66 | /**********************************************************************/ |
||
67 | /** \name Memory */ |
||
68 | /*@{*/ |
||
69 | |||
70 | /** |
||
71 | * Allocate aligned memory. |
||
72 | * |
||
73 | * \param bytes number of bytes to allocate. |
||
74 | * \param alignment alignment (must be greater than zero). |
||
75 | * |
||
76 | * Allocates extra memory to accommodate rounding up the address for |
||
77 | * alignment and to record the real malloc address. |
||
78 | * |
||
79 | * \sa _mesa_align_free(). |
||
80 | */ |
||
81 | void * |
||
82 | _mesa_align_malloc(size_t bytes, unsigned long alignment) |
||
83 | { |
||
84 | #if defined(HAVE_POSIX_MEMALIGN) |
||
85 | void *mem; |
||
86 | int err = posix_memalign(& mem, alignment, bytes); |
||
87 | if (err) |
||
88 | return NULL; |
||
89 | return mem; |
||
90 | #elif defined(_WIN32) && defined(_MSC_VER) |
||
91 | return _aligned_malloc(bytes, alignment); |
||
92 | #else |
||
93 | uintptr_t ptr, buf; |
||
94 | |||
95 | ASSERT( alignment > 0 ); |
||
96 | |||
97 | ptr = (uintptr_t)malloc(bytes + alignment + sizeof(void *)); |
||
98 | if (!ptr) |
||
99 | return NULL; |
||
100 | |||
101 | buf = (ptr + alignment + sizeof(void *)) & ~(uintptr_t)(alignment - 1); |
||
102 | *(uintptr_t *)(buf - sizeof(void *)) = ptr; |
||
103 | |||
104 | #ifdef DEBUG |
||
105 | /* mark the non-aligned area */ |
||
106 | while ( ptr < buf - sizeof(void *) ) { |
||
107 | *(unsigned long *)ptr = 0xcdcdcdcd; |
||
108 | ptr += sizeof(unsigned long); |
||
109 | } |
||
110 | #endif |
||
111 | |||
112 | return (void *) buf; |
||
113 | #endif /* defined(HAVE_POSIX_MEMALIGN) */ |
||
114 | } |
||
115 | |||
116 | /** |
||
117 | * Same as _mesa_align_malloc(), but using calloc(1, ) instead of |
||
118 | * malloc() |
||
119 | */ |
||
120 | void * |
||
121 | _mesa_align_calloc(size_t bytes, unsigned long alignment) |
||
122 | { |
||
123 | #if defined(HAVE_POSIX_MEMALIGN) |
||
124 | void *mem; |
||
125 | |||
126 | mem = _mesa_align_malloc(bytes, alignment); |
||
127 | if (mem != NULL) { |
||
128 | (void) memset(mem, 0, bytes); |
||
129 | } |
||
130 | |||
131 | return mem; |
||
132 | #elif defined(_WIN32) && defined(_MSC_VER) |
||
133 | void *mem; |
||
134 | |||
135 | mem = _aligned_malloc(bytes, alignment); |
||
136 | if (mem != NULL) { |
||
137 | (void) memset(mem, 0, bytes); |
||
138 | } |
||
139 | |||
140 | return mem; |
||
141 | #else |
||
142 | uintptr_t ptr, buf; |
||
143 | |||
144 | ASSERT( alignment > 0 ); |
||
145 | |||
146 | ptr = (uintptr_t)calloc(1, bytes + alignment + sizeof(void *)); |
||
147 | if (!ptr) |
||
148 | return NULL; |
||
149 | |||
150 | buf = (ptr + alignment + sizeof(void *)) & ~(uintptr_t)(alignment - 1); |
||
151 | *(uintptr_t *)(buf - sizeof(void *)) = ptr; |
||
152 | |||
153 | #ifdef DEBUG |
||
154 | /* mark the non-aligned area */ |
||
155 | while ( ptr < buf - sizeof(void *) ) { |
||
156 | *(unsigned long *)ptr = 0xcdcdcdcd; |
||
157 | ptr += sizeof(unsigned long); |
||
158 | } |
||
159 | #endif |
||
160 | |||
161 | return (void *)buf; |
||
162 | #endif /* defined(HAVE_POSIX_MEMALIGN) */ |
||
163 | } |
||
164 | |||
165 | /** |
||
166 | * Free memory which was allocated with either _mesa_align_malloc() |
||
167 | * or _mesa_align_calloc(). |
||
168 | * \param ptr pointer to the memory to be freed. |
||
169 | * The actual address to free is stored in the word immediately before the |
||
170 | * address the client sees. |
||
171 | */ |
||
172 | void |
||
173 | _mesa_align_free(void *ptr) |
||
174 | { |
||
175 | #if defined(HAVE_POSIX_MEMALIGN) |
||
176 | free(ptr); |
||
177 | #elif defined(_WIN32) && defined(_MSC_VER) |
||
178 | _aligned_free(ptr); |
||
179 | #else |
||
180 | void **cubbyHole = (void **) ((char *) ptr - sizeof(void *)); |
||
181 | void *realAddr = *cubbyHole; |
||
182 | free(realAddr); |
||
183 | #endif /* defined(HAVE_POSIX_MEMALIGN) */ |
||
184 | } |
||
185 | |||
186 | /** |
||
187 | * Reallocate memory, with alignment. |
||
188 | */ |
||
189 | void * |
||
190 | _mesa_align_realloc(void *oldBuffer, size_t oldSize, size_t newSize, |
||
191 | unsigned long alignment) |
||
192 | { |
||
193 | #if defined(_WIN32) && defined(_MSC_VER) |
||
194 | (void) oldSize; |
||
195 | return _aligned_realloc(oldBuffer, newSize, alignment); |
||
196 | #else |
||
197 | const size_t copySize = (oldSize < newSize) ? oldSize : newSize; |
||
198 | void *newBuf = _mesa_align_malloc(newSize, alignment); |
||
199 | if (newBuf && oldBuffer && copySize > 0) { |
||
200 | memcpy(newBuf, oldBuffer, copySize); |
||
201 | } |
||
202 | if (oldBuffer) |
||
203 | _mesa_align_free(oldBuffer); |
||
204 | return newBuf; |
||
205 | #endif |
||
206 | } |
||
207 | |||
208 | |||
209 | |||
210 | /** Reallocate memory */ |
||
211 | void * |
||
212 | _mesa_realloc(void *oldBuffer, size_t oldSize, size_t newSize) |
||
213 | { |
||
214 | const size_t copySize = (oldSize < newSize) ? oldSize : newSize; |
||
215 | void *newBuffer = malloc(newSize); |
||
216 | if (newBuffer && oldBuffer && copySize > 0) |
||
217 | memcpy(newBuffer, oldBuffer, copySize); |
||
218 | free(oldBuffer); |
||
219 | return newBuffer; |
||
220 | } |
||
221 | |||
222 | /*@}*/ |
||
223 | |||
224 | |||
225 | /**********************************************************************/ |
||
226 | /** \name Math */ |
||
227 | /*@{*/ |
||
228 | |||
229 | |||
230 | #ifndef __GNUC__ |
||
231 | /** |
||
232 | * Find the first bit set in a word. |
||
233 | */ |
||
234 | int |
||
235 | ffs(int i) |
||
236 | { |
||
237 | register int bit = 0; |
||
238 | if (i != 0) { |
||
239 | if ((i & 0xffff) == 0) { |
||
240 | bit += 16; |
||
241 | i >>= 16; |
||
242 | } |
||
243 | if ((i & 0xff) == 0) { |
||
244 | bit += 8; |
||
245 | i >>= 8; |
||
246 | } |
||
247 | if ((i & 0xf) == 0) { |
||
248 | bit += 4; |
||
249 | i >>= 4; |
||
250 | } |
||
251 | while ((i & 1) == 0) { |
||
252 | bit++; |
||
253 | i >>= 1; |
||
254 | } |
||
255 | bit++; |
||
256 | } |
||
257 | return bit; |
||
258 | } |
||
259 | |||
260 | |||
261 | /** |
||
262 | * Find position of first bit set in given value. |
||
263 | * XXX Warning: this function can only be used on 64-bit systems! |
||
264 | * \return position of least-significant bit set, starting at 1, return zero |
||
265 | * if no bits set. |
||
266 | */ |
||
267 | int |
||
268 | ffsll(long long int val) |
||
269 | { |
||
270 | int bit; |
||
271 | |||
272 | assert(sizeof(val) == 8); |
||
273 | |||
274 | bit = ffs((int) val); |
||
275 | if (bit != 0) |
||
276 | return bit; |
||
277 | |||
278 | bit = ffs((int) (val >> 32)); |
||
279 | if (bit != 0) |
||
280 | return 32 + bit; |
||
281 | |||
282 | return 0; |
||
283 | } |
||
284 | #endif /* __GNUC__ */ |
||
285 | |||
286 | |||
287 | #if !defined(__GNUC__) ||\ |
||
288 | ((__GNUC__ * 100 + __GNUC_MINOR__) < 304) /* Not gcc 3.4 or later */ |
||
289 | /** |
||
290 | * Return number of bits set in given GLuint. |
||
291 | */ |
||
292 | unsigned int |
||
293 | _mesa_bitcount(unsigned int n) |
||
294 | { |
||
295 | unsigned int bits; |
||
296 | for (bits = 0; n > 0; n = n >> 1) { |
||
297 | bits += (n & 1); |
||
298 | } |
||
299 | return bits; |
||
300 | } |
||
301 | |||
302 | /** |
||
303 | * Return number of bits set in given 64-bit uint. |
||
304 | */ |
||
305 | unsigned int |
||
306 | _mesa_bitcount_64(uint64_t n) |
||
307 | { |
||
308 | unsigned int bits; |
||
309 | for (bits = 0; n > 0; n = n >> 1) { |
||
310 | bits += (n & 1); |
||
311 | } |
||
312 | return bits; |
||
313 | } |
||
314 | #endif |
||
315 | |||
316 | |||
317 | /* Using C99 rounding functions for roundToEven() implementation is |
||
318 | * difficult, because round(), rint, and nearbyint() are affected by |
||
319 | * fesetenv(), which the application may have done for its own |
||
320 | * purposes. Mesa's IROUND macro is close to what we want, but it |
||
321 | * rounds away from 0 on n + 0.5. |
||
322 | */ |
||
323 | int |
||
324 | _mesa_round_to_even(float val) |
||
325 | { |
||
326 | int rounded = IROUND(val); |
||
327 | |||
328 | if (val - floor(val) == 0.5) { |
||
329 | if (rounded % 2 != 0) |
||
330 | rounded += val > 0 ? -1 : 1; |
||
331 | } |
||
332 | |||
333 | return rounded; |
||
334 | } |
||
335 | |||
336 | |||
337 | /** |
||
338 | * Convert a 4-byte float to a 2-byte half float. |
||
339 | * |
||
340 | * Not all float32 values can be represented exactly as a float16 value. We |
||
341 | * round such intermediate float32 values to the nearest float16. When the |
||
342 | * float32 lies exactly between to float16 values, we round to the one with |
||
343 | * an even mantissa. |
||
344 | * |
||
345 | * This rounding behavior has several benefits: |
||
346 | * - It has no sign bias. |
||
347 | * |
||
348 | * - It reproduces the behavior of real hardware: opcode F32TO16 in Intel's |
||
349 | * GPU ISA. |
||
350 | * |
||
351 | * - By reproducing the behavior of the GPU (at least on Intel hardware), |
||
352 | * compile-time evaluation of constant packHalf2x16 GLSL expressions will |
||
353 | * result in the same value as if the expression were executed on the GPU. |
||
354 | */ |
||
355 | GLhalfARB |
||
356 | _mesa_float_to_half(float val) |
||
357 | { |
||
358 | const fi_type fi = {val}; |
||
359 | const int flt_m = fi.i & 0x7fffff; |
||
360 | const int flt_e = (fi.i >> 23) & 0xff; |
||
361 | const int flt_s = (fi.i >> 31) & 0x1; |
||
362 | int s, e, m = 0; |
||
363 | GLhalfARB result; |
||
364 | |||
365 | /* sign bit */ |
||
366 | s = flt_s; |
||
367 | |||
368 | /* handle special cases */ |
||
369 | if ((flt_e == 0) && (flt_m == 0)) { |
||
370 | /* zero */ |
||
371 | /* m = 0; - already set */ |
||
372 | e = 0; |
||
373 | } |
||
374 | else if ((flt_e == 0) && (flt_m != 0)) { |
||
375 | /* denorm -- denorm float maps to 0 half */ |
||
376 | /* m = 0; - already set */ |
||
377 | e = 0; |
||
378 | } |
||
379 | else if ((flt_e == 0xff) && (flt_m == 0)) { |
||
380 | /* infinity */ |
||
381 | /* m = 0; - already set */ |
||
382 | e = 31; |
||
383 | } |
||
384 | else if ((flt_e == 0xff) && (flt_m != 0)) { |
||
385 | /* NaN */ |
||
386 | m = 1; |
||
387 | e = 31; |
||
388 | } |
||
389 | else { |
||
390 | /* regular number */ |
||
391 | const int new_exp = flt_e - 127; |
||
392 | if (new_exp < -14) { |
||
393 | /* The float32 lies in the range (0.0, min_normal16) and is rounded |
||
394 | * to a nearby float16 value. The result will be either zero, subnormal, |
||
395 | * or normal. |
||
396 | */ |
||
397 | e = 0; |
||
398 | m = _mesa_round_to_even((1 << 24) * fabsf(fi.f)); |
||
399 | } |
||
400 | else if (new_exp > 15) { |
||
401 | /* map this value to infinity */ |
||
402 | /* m = 0; - already set */ |
||
403 | e = 31; |
||
404 | } |
||
405 | else { |
||
406 | /* The float32 lies in the range |
||
407 | * [min_normal16, max_normal16 + max_step16) |
||
408 | * and is rounded to a nearby float16 value. The result will be |
||
409 | * either normal or infinite. |
||
410 | */ |
||
411 | e = new_exp + 15; |
||
412 | m = _mesa_round_to_even(flt_m / (float) (1 << 13)); |
||
413 | } |
||
414 | } |
||
415 | |||
416 | assert(0 <= m && m <= 1024); |
||
417 | if (m == 1024) { |
||
418 | /* The float32 was rounded upwards into the range of the next exponent, |
||
419 | * so bump the exponent. This correctly handles the case where f32 |
||
420 | * should be rounded up to float16 infinity. |
||
421 | */ |
||
422 | ++e; |
||
423 | m = 0; |
||
424 | } |
||
425 | |||
426 | result = (s << 15) | (e << 10) | m; |
||
427 | return result; |
||
428 | } |
||
429 | |||
430 | |||
431 | /** |
||
432 | * Convert a 2-byte half float to a 4-byte float. |
||
433 | * Based on code from: |
||
434 | * http://www.opengl.org/discussion_boards/ubb/Forum3/HTML/008786.html |
||
435 | */ |
||
436 | float |
||
437 | _mesa_half_to_float(GLhalfARB val) |
||
438 | { |
||
439 | /* XXX could also use a 64K-entry lookup table */ |
||
440 | const int m = val & 0x3ff; |
||
441 | const int e = (val >> 10) & 0x1f; |
||
442 | const int s = (val >> 15) & 0x1; |
||
443 | int flt_m, flt_e, flt_s; |
||
444 | fi_type fi; |
||
445 | float result; |
||
446 | |||
447 | /* sign bit */ |
||
448 | flt_s = s; |
||
449 | |||
450 | /* handle special cases */ |
||
451 | if ((e == 0) && (m == 0)) { |
||
452 | /* zero */ |
||
453 | flt_m = 0; |
||
454 | flt_e = 0; |
||
455 | } |
||
456 | else if ((e == 0) && (m != 0)) { |
||
457 | /* denorm -- denorm half will fit in non-denorm single */ |
||
458 | const float half_denorm = 1.0f / 16384.0f; /* 2^-14 */ |
||
459 | float mantissa = ((float) (m)) / 1024.0f; |
||
460 | float sign = s ? -1.0f : 1.0f; |
||
461 | return sign * mantissa * half_denorm; |
||
462 | } |
||
463 | else if ((e == 31) && (m == 0)) { |
||
464 | /* infinity */ |
||
465 | flt_e = 0xff; |
||
466 | flt_m = 0; |
||
467 | } |
||
468 | else if ((e == 31) && (m != 0)) { |
||
469 | /* NaN */ |
||
470 | flt_e = 0xff; |
||
471 | flt_m = 1; |
||
472 | } |
||
473 | else { |
||
474 | /* regular */ |
||
475 | flt_e = e + 112; |
||
476 | flt_m = m << 13; |
||
477 | } |
||
478 | |||
479 | fi.i = (flt_s << 31) | (flt_e << 23) | flt_m; |
||
480 | result = fi.f; |
||
481 | return result; |
||
482 | } |
||
483 | |||
484 | /*@}*/ |
||
485 | |||
486 | |||
487 | /**********************************************************************/ |
||
488 | /** \name Sort & Search */ |
||
489 | /*@{*/ |
||
490 | |||
491 | /** |
||
492 | * Wrapper for bsearch(). |
||
493 | */ |
||
494 | void * |
||
495 | _mesa_bsearch( const void *key, const void *base, size_t nmemb, size_t size, |
||
496 | int (*compar)(const void *, const void *) ) |
||
497 | { |
||
498 | #if defined(_WIN32_WCE) |
||
499 | void *mid; |
||
500 | int cmp; |
||
501 | while (nmemb) { |
||
502 | nmemb >>= 1; |
||
503 | mid = (char *)base + nmemb * size; |
||
504 | cmp = (*compar)(key, mid); |
||
505 | if (cmp == 0) |
||
506 | return mid; |
||
507 | if (cmp > 0) { |
||
508 | base = (char *)mid + size; |
||
509 | --nmemb; |
||
510 | } |
||
511 | } |
||
512 | return NULL; |
||
513 | #else |
||
514 | return bsearch(key, base, nmemb, size, compar); |
||
515 | #endif |
||
516 | } |
||
517 | |||
518 | /*@}*/ |
||
519 | |||
520 | |||
521 | /**********************************************************************/ |
||
522 | /** \name Environment vars */ |
||
523 | /*@{*/ |
||
524 | |||
525 | /** |
||
526 | * Wrapper for getenv(). |
||
527 | */ |
||
528 | char * |
||
529 | _mesa_getenv( const char *var ) |
||
530 | { |
||
531 | #if defined(_XBOX) || defined(_WIN32_WCE) |
||
532 | return NULL; |
||
533 | #else |
||
534 | return getenv(var); |
||
535 | #endif |
||
536 | } |
||
537 | |||
538 | /*@}*/ |
||
539 | |||
540 | |||
541 | /**********************************************************************/ |
||
542 | /** \name String */ |
||
543 | /*@{*/ |
||
544 | |||
545 | /** |
||
546 | * Implemented using malloc() and strcpy. |
||
547 | * Note that NULL is handled accordingly. |
||
548 | */ |
||
549 | char * |
||
550 | _mesa_strdup( const char *s ) |
||
551 | { |
||
552 | if (s) { |
||
553 | size_t l = strlen(s); |
||
554 | char *s2 = malloc(l + 1); |
||
555 | if (s2) |
||
556 | strcpy(s2, s); |
||
557 | return s2; |
||
558 | } |
||
559 | else { |
||
560 | return NULL; |
||
561 | } |
||
562 | } |
||
563 | |||
564 | /** Wrapper around strtof() */ |
||
565 | float |
||
566 | _mesa_strtof( const char *s, char **end ) |
||
567 | { |
||
568 | #if defined(_GNU_SOURCE) && !defined(__CYGWIN__) && !defined(__FreeBSD__) && \ |
||
569 | !defined(ANDROID) && !defined(__HAIKU__) && !defined(__UCLIBC__) |
||
570 | static locale_t loc = NULL; |
||
571 | if (!loc) { |
||
572 | loc = newlocale(LC_CTYPE_MASK, "C", NULL); |
||
573 | } |
||
574 | return strtof_l(s, end, loc); |
||
575 | #elif defined(_ISOC99_SOURCE) || (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 600) |
||
576 | return strtof(s, end); |
||
577 | #else |
||
578 | return (float)strtod(s, end); |
||
579 | #endif |
||
580 | } |
||
581 | |||
582 | /** Compute simple checksum/hash for a string */ |
||
583 | unsigned int |
||
584 | _mesa_str_checksum(const char *str) |
||
585 | { |
||
586 | /* This could probably be much better */ |
||
587 | unsigned int sum, i; |
||
588 | const char *c; |
||
589 | sum = i = 1; |
||
590 | for (c = str; *c; c++, i++) |
||
591 | sum += *c * (i % 100); |
||
592 | return sum + i; |
||
593 | } |
||
594 | |||
595 | |||
596 | /*@}*/ |
||
597 | |||
598 | |||
599 | /** Needed due to #ifdef's, above. */ |
||
600 | int |
||
601 | _mesa_vsnprintf(char *str, size_t size, const char *fmt, va_list args) |
||
602 | { |
||
603 | return vsnprintf( str, size, fmt, args); |
||
604 | } |
||
605 | |||
606 | /** Wrapper around vsnprintf() */ |
||
607 | int |
||
608 | _mesa_snprintf( char *str, size_t size, const char *fmt, ... ) |
||
609 | { |
||
610 | int r; |
||
611 | va_list args; |
||
612 | va_start( args, fmt ); |
||
613 | r = vsnprintf( str, size, fmt, args ); |
||
614 | va_end( args ); |
||
615 | return r; |
||
616 | }><>><>><>><>><>=>=>><>><>>>>>>> |
||
617 |