Line data Source code
1 : /* src/vm/jit/argument.cpp - argument passing from and to JIT methods
2 :
3 : Copyright (C) 1996-2013
4 : CACAOVM - Verein zur Foerderung der freien virtuellen Maschine CACAO
5 :
6 : This file is part of CACAO.
7 :
8 : This program is free software; you can redistribute it and/or
9 : modify it under the terms of the GNU General Public License as
10 : published by the Free Software Foundation; either version 2, or (at
11 : your option) any later version.
12 :
13 : This program is distributed in the hope that it will be useful, but
14 : WITHOUT ANY WARRANTY; without even the implied warranty of
15 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 : General Public License for more details.
17 :
18 : You should have received a copy of the GNU General Public License
19 : along with this program; if not, write to the Free Software
20 : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 : 02110-1301, USA.
22 :
23 : */
24 :
25 :
26 : #include "config.h"
27 :
28 : #include <cassert>
29 : #include <stdint.h>
30 :
31 : #include "arch.hpp"
32 : #include "md-abi.hpp"
33 :
34 : #include "mm/dumpmemory.hpp"
35 :
36 : #include "native/llni.hpp"
37 :
38 : #include "vm/array.hpp"
39 : #include "vm/descriptor.hpp"
40 : #include "vm/global.hpp"
41 : #include "vm/method.hpp"
42 : #include "vm/primitive.hpp"
43 : #include "vm/resolve.hpp"
44 : #include "vm/vm.hpp"
45 :
46 : #include "vm/jit/abi-asm.hpp"
47 : #include "vm/jit/codegen-common.hpp" // for GET_LOW_REG, GET_HIGH_REG
48 :
49 :
50 : /* argument_jitarray_load ******************************************************
51 :
52 : Returns the argument specified by index from one of the passed arrays
53 : and returns it.
54 :
55 : *******************************************************************************/
56 :
57 0 : imm_union argument_jitarray_load(methoddesc *md, int32_t index,
58 : uint64_t *arg_regs, uint64_t *stack)
59 : {
60 : imm_union ret;
61 : paramdesc *pd;
62 :
63 0 : pd = &md->params[index];
64 :
65 0 : switch (md->paramtypes[index].type) {
66 : case TYPE_INT:
67 : case TYPE_ADR:
68 0 : if (pd->inmemory) {
69 : #if (SIZEOF_VOID_P == 8)
70 0 : ret.l = (int64_t)stack[pd->index];
71 : #else
72 : ret.l = *(int32_t *)(stack + pd->index);
73 : #endif
74 : } else {
75 : #if (SIZEOF_VOID_P == 8)
76 0 : ret.l = arg_regs[index];
77 : #else
78 : ret.l = *(int32_t *)(arg_regs + index);
79 : #endif
80 : }
81 0 : break;
82 : case TYPE_LNG:
83 0 : if (pd->inmemory) {
84 0 : ret.l = (int64_t)stack[pd->index];
85 : } else {
86 0 : ret.l = (int64_t)arg_regs[index];
87 : }
88 0 : break;
89 : case TYPE_FLT:
90 0 : if (pd->inmemory) {
91 0 : ret.l = (int64_t)stack[pd->index];
92 : } else {
93 0 : ret.l = (int64_t)arg_regs[index];
94 : }
95 0 : break;
96 : case TYPE_DBL:
97 0 : if (pd->inmemory) {
98 0 : ret.l = (int64_t)stack[pd->index];
99 : } else {
100 0 : ret.l = (int64_t)arg_regs[index];
101 : }
102 0 : break;
103 : default:
104 0 : ret.l = 0;
105 0 : os::shouldnotreach();
106 : break;
107 : }
108 :
109 0 : return ret;
110 : }
111 :
112 :
113 : /* argument_jitarray_store *****************************************************
114 :
115 : Stores the argument into one of the passed arrays at a slot specified
116 : by index.
117 :
118 : *******************************************************************************/
119 :
120 0 : void argument_jitarray_store(methoddesc *md, int32_t index,
121 : uint64_t *arg_regs, uint64_t *stack,
122 : imm_union param)
123 : {
124 : paramdesc *pd;
125 :
126 0 : pd = &md->params[index];
127 :
128 0 : switch (md->paramtypes[index].type) {
129 : case TYPE_ADR:
130 0 : if (pd->inmemory) {
131 : #if (SIZEOF_VOID_P == 8)
132 0 : stack[pd->index] = param.l;
133 : #else
134 : assert(0);
135 : #endif
136 : } else {
137 0 : arg_regs[index] = param.l;
138 : }
139 0 : break;
140 : default:
141 0 : os::unimplemented("argument_jitarray_store: type not implemented");
142 : break;
143 : }
144 0 : }
145 :
146 :
147 : /* argument_jitreturn_load *****************************************************
148 :
149 : Loads the proper return value form the return register and returns it.
150 :
151 : *******************************************************************************/
152 :
153 0 : imm_union argument_jitreturn_load(methoddesc *md, uint64_t *return_regs)
154 : {
155 : imm_union ret;
156 :
157 0 : switch (md->returntype.type) {
158 : case TYPE_INT:
159 : case TYPE_ADR:
160 : #if (SIZEOF_VOID_P == 8)
161 0 : ret.l = return_regs[0];
162 : #else
163 : ret.l = *(int32_t *)return_regs;
164 : #endif
165 0 : break;
166 : case TYPE_LNG:
167 0 : ret.l = *(int64_t *)return_regs;
168 0 : break;
169 : case TYPE_FLT:
170 0 : ret.l = *(int64_t *)return_regs;
171 0 : break;
172 : case TYPE_DBL:
173 0 : ret.l = *(int64_t *)return_regs;
174 0 : break;
175 : default:
176 0 : ret.l = 0;
177 0 : os::shouldnotreach();
178 : break;
179 : }
180 :
181 0 : return ret;
182 : }
183 :
184 :
185 : /* argument_jitreturn_store ****************************************************
186 :
187 : Stores the proper return value into the return registers.
188 :
189 : *******************************************************************************/
190 :
191 0 : void argument_jitreturn_store(methoddesc *md, uint64_t *return_regs, imm_union ret)
192 : {
193 0 : switch (md->returntype.type) {
194 : case TYPE_ADR:
195 : #if (SIZEOF_VOID_P == 8)
196 0 : return_regs[0] = ret.l;
197 : #else
198 : assert(0);
199 : #endif
200 0 : break;
201 : default:
202 0 : os::unimplemented("argument_jitreturn_store: type not implemented");
203 : break;
204 : }
205 0 : }
206 :
207 :
208 : /* argument_vmarray_store_int **************************************************
209 :
210 : Helper function to store an integer into the argument array, taking
211 : care of architecture specific issues.
212 :
213 : *******************************************************************************/
214 :
215 96628 : static void argument_vmarray_store_int(uint64_t *array, paramdesc *pd, int32_t value)
216 : {
217 : int32_t index;
218 :
219 96628 : if (!pd->inmemory) {
220 96618 : index = pd->index;
221 96618 : array[index] = (int64_t) value;
222 : }
223 : else {
224 10 : index = ARG_CNT + pd->index;
225 : #if SIZEOF_VOID_P == 8
226 10 : array[index] = (int64_t) value;
227 : #else
228 : # if WORDS_BIGENDIAN == 1
229 : array[index] = ((int64_t) value) << 32;
230 : # else
231 : array[index] = (int64_t) value;
232 : # endif
233 : #endif
234 : }
235 96628 : }
236 :
237 :
238 : /* argument_vmarray_store_lng **************************************************
239 :
240 : Helper function to store a long into the argument array, taking
241 : care of architecture specific issues.
242 :
243 : *******************************************************************************/
244 :
245 24 : static void argument_vmarray_store_lng(uint64_t *array, paramdesc *pd, int64_t value)
246 : {
247 : int32_t index;
248 :
249 : #if SIZEOF_VOID_P == 8
250 24 : if (!pd->inmemory)
251 13 : index = pd->index;
252 : else
253 11 : index = ARG_CNT + pd->index;
254 :
255 24 : array[index] = value;
256 : #else
257 : if (!pd->inmemory) {
258 : /* move low and high 32-bits into it's own argument slot */
259 :
260 : index = GET_LOW_REG(pd->index);
261 : array[index] = value & 0x00000000ffffffff;
262 :
263 : index = GET_HIGH_REG(pd->index);
264 : array[index] = value >> 32;
265 : }
266 : else {
267 : index = ARG_CNT + pd->index;
268 : array[index] = value;
269 : }
270 : #endif
271 24 : }
272 :
273 :
274 : /* argument_vmarray_store_flt **************************************************
275 :
276 : Helper function to store a float into the argument array, taking
277 : care of architecture specific issues.
278 :
279 : *******************************************************************************/
280 :
281 28 : static void argument_vmarray_store_flt(uint64_t *array, paramdesc *pd, uint64_t value)
282 : {
283 : int32_t index;
284 :
285 28 : if (!pd->inmemory) {
286 : #if defined(SUPPORT_PASS_FLOATARGS_IN_INTREGS)
287 : index = pd->index;
288 : #else
289 16 : index = INT_ARG_CNT + pd->index;
290 : #endif
291 : #if WORDS_BIGENDIAN == 1 && !defined(__POWERPC__) && !defined(__POWERPC64__) && !defined(__S390__)
292 : array[index] = value >> 32;
293 : #else
294 16 : array[index] = value;
295 : #endif
296 : }
297 : else {
298 12 : index = ARG_CNT + pd->index;
299 : #if defined(__SPARC_64__)
300 : array[index] = value >> 32;
301 : #else
302 12 : array[index] = value;
303 : #endif
304 : }
305 28 : }
306 :
307 :
308 : /* argument_vmarray_store_dbl **************************************************
309 :
310 : Helper function to store a double into the argument array, taking
311 : care of architecture specific issues.
312 :
313 : *******************************************************************************/
314 :
315 1628 : static void argument_vmarray_store_dbl(uint64_t *array, paramdesc *pd, uint64_t value)
316 : {
317 : int32_t index;
318 :
319 1628 : if (!pd->inmemory) {
320 : #if SIZEOF_VOID_P != 8 && defined(SUPPORT_PASS_FLOATARGS_IN_INTREGS)
321 : index = GET_LOW_REG(pd->index);
322 : array[index] = value & 0x00000000ffffffff;
323 :
324 : index = GET_HIGH_REG(pd->index);
325 : array[index] = value >> 32;
326 : #else
327 1616 : index = INT_ARG_CNT + pd->index;
328 1616 : array[index] = value;
329 : #endif
330 : }
331 : else {
332 12 : index = ARG_CNT + pd->index;
333 12 : array[index] = value;
334 : }
335 1628 : }
336 :
337 :
338 : /* argument_vmarray_store_adr **************************************************
339 :
340 : Helper function to store an address into the argument array, taking
341 : care of architecture specific issues.
342 :
343 : ATTENTION: This function has to be used outside the nativeworld.
344 :
345 : *******************************************************************************/
346 :
347 755922 : static void argument_vmarray_store_adr(uint64_t *array, paramdesc *pd, java_handle_t *h)
348 : {
349 : void *value;
350 : int32_t index;
351 :
352 : /* Take the reference value out of the handle. */
353 :
354 755922 : value = LLNI_UNWRAP(h);
355 :
356 755922 : if (!pd->inmemory) {
357 755913 : index = pd->index;
358 755913 : array[index] = (uint64_t) (intptr_t) value;
359 : }
360 : else {
361 9 : index = ARG_CNT + pd->index;
362 : #if SIZEOF_VOID_P == 8
363 9 : array[index] = (uint64_t) (intptr_t) value;
364 : #else
365 : # if WORDS_BIGENDIAN == 1
366 : array[index] = ((uint64_t) (intptr_t) value) << 32;
367 : # else
368 : array[index] = (uint64_t) (intptr_t) value;
369 : # endif
370 : #endif
371 : }
372 755922 : }
373 :
374 :
375 : /* argument_vmarray_from_valist ************************************************
376 :
377 : Creates an argument array which can be passed to asm_vm_call_method.
378 : The array is created from the passed valist.
379 :
380 : ATTENTION: This function has to be used outside the native world.
381 :
382 : *******************************************************************************/
383 :
384 745735 : uint64_t *argument_vmarray_from_valist(methodinfo *m, java_handle_t *o, va_list ap)
385 : {
386 : methoddesc *md;
387 : paramdesc *pd;
388 : typedesc *td;
389 : uint64_t *array;
390 : int32_t i;
391 : imm_union value;
392 :
393 : /* get the descriptors */
394 :
395 745735 : md = m->parseddesc;
396 745735 : pd = md->params;
397 745735 : td = md->paramtypes;
398 :
399 : // Allocate argument array.
400 745735 : array = (uint64_t*) DumpMemory::allocate(sizeof(uint64_t) * (INT_ARG_CNT + FLT_ARG_CNT + md->memuse));
401 :
402 : /* if method is non-static fill first block and skip `this' pointer */
403 :
404 745757 : i = 0;
405 :
406 745757 : if (o != NULL) {
407 : /* the `this' pointer */
408 735033 : argument_vmarray_store_adr(array, pd, o);
409 :
410 735032 : pd++;
411 735032 : td++;
412 735032 : i++;
413 : }
414 :
415 863725 : for (; i < md->paramcount; i++, pd++, td++) {
416 117969 : switch (td->type) {
417 : case TYPE_INT:
418 96584 : value.i = va_arg(ap, int32_t);
419 96584 : argument_vmarray_store_int(array, pd, value.i);
420 96584 : break;
421 :
422 : case TYPE_LNG:
423 24 : value.l = va_arg(ap, int64_t);
424 24 : argument_vmarray_store_lng(array, pd, value.l);
425 24 : break;
426 :
427 : case TYPE_FLT:
428 : #if defined(__ALPHA__) || defined(__POWERPC__) || defined(__POWERPC64__)
429 : // The assembler code loads these directly and unconditionally into
430 : // the argument registers.
431 :
432 : if (!pd->inmemory)
433 : value.d = (double) va_arg(ap, double);
434 : else
435 : value.f = (float) va_arg(ap, double);
436 : #else
437 28 : value.f = (float) va_arg(ap, double);
438 : #endif
439 28 : argument_vmarray_store_flt(array, pd, value.l);
440 28 : break;
441 :
442 : case TYPE_DBL:
443 1628 : value.d = va_arg(ap, double);
444 1628 : argument_vmarray_store_dbl(array, pd, value.l);
445 1628 : break;
446 :
447 : case TYPE_ADR:
448 19705 : value.a = va_arg(ap, void*);
449 19705 : argument_vmarray_store_adr(array, pd, static_cast<java_handle_t*>(value.a));
450 19705 : break;
451 : default:
452 0 : assert(false);
453 : break;
454 : }
455 : }
456 :
457 745756 : return array;
458 : }
459 :
460 :
461 : /* argument_vmarray_from_jvalue ************************************************
462 :
463 : Creates an argument array which can be passed to asm_vm_call_method.
464 : The array is created from the passed jvalue array.
465 :
466 : ATTENTION: This function has to be used outside the native world.
467 :
468 : *******************************************************************************/
469 :
470 0 : uint64_t *argument_vmarray_from_jvalue(methodinfo *m, java_handle_t *o,
471 : const jvalue *args)
472 : {
473 : methoddesc *md;
474 : paramdesc *pd;
475 : typedesc *td;
476 : uint64_t *array;
477 : int32_t i;
478 : int32_t j;
479 :
480 : /* get the descriptors */
481 :
482 0 : md = m->parseddesc;
483 0 : pd = md->params;
484 0 : td = md->paramtypes;
485 :
486 : /* allocate argument array */
487 :
488 0 : array = (uint64_t*) DumpMemory::allocate(sizeof(uint64_t) * (INT_ARG_CNT + FLT_ARG_CNT + md->memuse));
489 :
490 : /* if method is non-static fill first block and skip `this' pointer */
491 :
492 0 : i = 0;
493 :
494 0 : if (o != NULL) {
495 : /* the `this' pointer */
496 0 : argument_vmarray_store_adr(array, pd, o);
497 :
498 0 : pd++;
499 0 : td++;
500 0 : i++;
501 : }
502 :
503 0 : for (j = 0; i < md->paramcount; i++, j++, pd++, td++) {
504 0 : switch (td->primitivetype) {
505 : case TYPE_INT:
506 0 : argument_vmarray_store_int(array, pd, args[j].i);
507 0 : break;
508 :
509 : case TYPE_LNG:
510 0 : argument_vmarray_store_lng(array, pd, args[j].j);
511 0 : break;
512 :
513 : case TYPE_FLT:
514 0 : argument_vmarray_store_flt(array, pd, args[j].j);
515 0 : break;
516 :
517 : case TYPE_DBL:
518 0 : argument_vmarray_store_dbl(array, pd, args[j].j);
519 0 : break;
520 :
521 : case TYPE_ADR:
522 0 : argument_vmarray_store_adr(array, pd, (java_handle_t *) args[j].l);
523 0 : break;
524 : default:
525 0 : assert(false);
526 : break;
527 : }
528 : }
529 :
530 0 : return array;
531 : }
532 :
533 :
534 : /* argument_vmarray_from_objectarray *******************************************
535 :
536 : Creates an argument array which can be passed to asm_vm_call_method.
537 : The array is created from the passed objectarray of boxed values.
538 :
539 : ATTENTION: This function has to be used outside the native world.
540 :
541 : RETURN VALUE:
542 : NULL.........indicates an error while creating the array
543 : (-1).........no error, but an empty array
544 : otherwise....array containing the argument values
545 :
546 : *******************************************************************************/
547 :
548 853 : uint64_t *argument_vmarray_from_objectarray(methodinfo *m, java_handle_t *o,
549 : java_handle_objectarray_t *params)
550 : {
551 : methoddesc *md;
552 : paramdesc *pd;
553 : typedesc *td;
554 : uint64_t *array;
555 : java_handle_t *param;
556 : classinfo *c;
557 : int type;
558 : int32_t i;
559 : int32_t j;
560 : imm_union value;
561 :
562 : /* get the descriptors */
563 :
564 853 : md = m->parseddesc;
565 853 : pd = md->params;
566 853 : td = md->paramtypes;
567 :
568 : /* allocate argument array */
569 :
570 853 : array = (uint64_t*) DumpMemory::allocate(sizeof(uint64_t) * (INT_ARG_CNT + FLT_ARG_CNT + md->memuse));
571 :
572 : /* The array can be NULL if we don't have any arguments to pass
573 : and the architecture does not have any argument registers
574 : (e.g. i386). In that case we return (-1) to indicate
575 : that no exception should be thrown */
576 :
577 853 : if (array == NULL)
578 0 : array = (uint64_t *)(-1);
579 :
580 : /* if method is non-static fill first block and skip `this' pointer */
581 :
582 853 : i = 0;
583 :
584 853 : if (o != NULL) {
585 : /* this pointer */
586 818 : argument_vmarray_store_adr(array, pd, o);
587 :
588 818 : pd++;
589 818 : td++;
590 818 : i++;
591 : }
592 :
593 853 : ObjectArray oa(params);
594 :
595 1264 : for (j = 0; i < md->paramcount; i++, j++, pd++, td++) {
596 : /* XXX This function can throw an exception, which should not happend
597 : here, since we are outside the nativeworld. */
598 411 : param = oa.get_element(j);
599 :
600 411 : switch (td->type) {
601 : case TYPE_INT:
602 44 : if (param == NULL)
603 0 : return NULL;
604 :
605 : /* convert the value according to its declared type */
606 :
607 44 : LLNI_class_get(param, c);
608 44 : type = Primitive::get_type_by_wrapperclass(c);
609 :
610 44 : switch (td->primitivetype) {
611 : case PRIMITIVETYPE_BOOLEAN:
612 0 : switch (type) {
613 : case PRIMITIVETYPE_BOOLEAN:
614 : /* This type is OK. */
615 : break;
616 : default:
617 0 : return NULL;
618 : }
619 0 : break;
620 :
621 : case PRIMITIVETYPE_BYTE:
622 0 : switch (type) {
623 : case PRIMITIVETYPE_BYTE:
624 : /* This type is OK. */
625 : break;
626 : default:
627 0 : return NULL;
628 : }
629 0 : break;
630 :
631 : case PRIMITIVETYPE_CHAR:
632 0 : switch (type) {
633 : case PRIMITIVETYPE_CHAR:
634 : /* This type is OK. */
635 : break;
636 : default:
637 0 : return NULL;
638 : }
639 0 : break;
640 :
641 : case PRIMITIVETYPE_SHORT:
642 0 : switch (type) {
643 : case PRIMITIVETYPE_BYTE:
644 : case PRIMITIVETYPE_SHORT:
645 : /* These types are OK. */
646 : break;
647 : default:
648 0 : return NULL;
649 : }
650 0 : break;
651 :
652 : case PRIMITIVETYPE_INT:
653 44 : switch (type) {
654 : case PRIMITIVETYPE_BYTE:
655 : case PRIMITIVETYPE_SHORT:
656 : case PRIMITIVETYPE_INT:
657 : /* These types are OK. */
658 : break;
659 : default:
660 0 : return NULL;
661 : }
662 44 : break;
663 :
664 : default:
665 : os::abort("argument_vmarray_from_objectarray: invalid type %d",
666 0 : td->primitivetype);
667 : }
668 :
669 44 : value = Primitive::unbox(param);
670 44 : argument_vmarray_store_int(array, pd, value.i);
671 44 : break;
672 :
673 : case TYPE_LNG:
674 0 : if (param == NULL)
675 0 : return NULL;
676 :
677 0 : LLNI_class_get(param, c);
678 0 : type = Primitive::get_type_by_wrapperclass(c);
679 :
680 0 : assert(td->primitivetype == PRIMITIVETYPE_LONG);
681 :
682 0 : switch (type) {
683 : case PRIMITIVETYPE_BYTE:
684 : case PRIMITIVETYPE_SHORT:
685 : case PRIMITIVETYPE_INT:
686 : case PRIMITIVETYPE_LONG:
687 : /* These types are OK. */
688 : break;
689 : default:
690 0 : return NULL;
691 : }
692 :
693 0 : value = Primitive::unbox(param);
694 0 : argument_vmarray_store_lng(array, pd, value.l);
695 0 : break;
696 :
697 : case TYPE_FLT:
698 0 : if (param == NULL)
699 0 : return NULL;
700 :
701 0 : LLNI_class_get(param, c);
702 0 : type = Primitive::get_type_by_wrapperclass(c);
703 :
704 0 : assert(td->primitivetype == PRIMITIVETYPE_FLOAT);
705 :
706 0 : switch (type) {
707 : case PRIMITIVETYPE_FLOAT:
708 : /* This type is OK. */
709 : break;
710 : default:
711 0 : return NULL;
712 : }
713 :
714 0 : value = Primitive::unbox(param);
715 0 : argument_vmarray_store_flt(array, pd, value.l);
716 0 : break;
717 :
718 : case TYPE_DBL:
719 0 : if (param == NULL)
720 0 : return NULL;
721 :
722 0 : LLNI_class_get(param, c);
723 0 : type = Primitive::get_type_by_wrapperclass(c);
724 :
725 0 : assert(td->primitivetype == PRIMITIVETYPE_DOUBLE);
726 :
727 0 : switch (type) {
728 : case PRIMITIVETYPE_FLOAT:
729 : case PRIMITIVETYPE_DOUBLE:
730 : /* These types are OK. */
731 : break;
732 : default:
733 0 : return NULL;
734 : }
735 :
736 0 : value = Primitive::unbox(param);
737 0 : argument_vmarray_store_dbl(array, pd, value.l);
738 0 : break;
739 :
740 : case TYPE_ADR:
741 367 : if (!resolve_class_from_typedesc(td, true, true, &c))
742 0 : return NULL;
743 :
744 367 : if (param != NULL) {
745 354 : if (td->arraydim > 0) {
746 22 : if (!builtin_arrayinstanceof(param, c))
747 0 : return NULL;
748 : }
749 : else {
750 332 : if (!builtin_instanceof(param, c))
751 0 : return NULL;
752 : }
753 : }
754 :
755 367 : argument_vmarray_store_adr(array, pd, param);
756 367 : break;
757 :
758 : default:
759 0 : os::abort("argument_vmarray_from_objectarray: invalid type %d", td->type);
760 : }
761 : }
762 :
763 853 : return array;
764 : }
765 :
766 :
767 : /*
768 : * These are local overrides for various environment variables in Emacs.
769 : * Please do not remove this and leave it at the end of the file, where
770 : * Emacs will automagically detect them.
771 : * ---------------------------------------------------------------------
772 : * Local variables:
773 : * mode: c++
774 : * indent-tabs-mode: t
775 : * c-basic-offset: 4
776 : * tab-width: 4
777 : * End:
778 : * vim:noexpandtab:sw=4:ts=4:
779 : */
|