e7209de94c98e3d6faa162ded07d7d650a898fae
[cacao.git] / src / vm / jit / codegen-common.c
1 /* src/vm/jit/codegen-common.c - architecture independent code generator stuff
2
3    Copyright (C) 1996-2005, 2006, 2007 R. Grafl, A. Krall, C. Kruegel,
4    C. Oates, R. Obermaisser, M. Platter, M. Probst, S. Ring,
5    E. Steiner, C. Thalinger, D. Thuernbeck, P. Tomsich, C. Ullrich,
6    J. Wenninger, Institut f. Computersprachen - TU Wien
7
8    This file is part of CACAO.
9
10    This program is free software; you can redistribute it and/or
11    modify it under the terms of the GNU General Public License as
12    published by the Free Software Foundation; either version 2, or (at
13    your option) any later version.
14
15    This program is distributed in the hope that it will be useful, but
16    WITHOUT ANY WARRANTY; without even the implied warranty of
17    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18    General Public License for more details.
19
20    You should have received a copy of the GNU General Public License
21    along with this program; if not, write to the Free Software
22    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
23    02110-1301, USA.
24
25    All functions assume the following code area / data area layout:
26
27    +-----------+
28    |           |
29    | code area | code area grows to higher addresses
30    |           |
31    +-----------+ <-- start of procedure
32    |           |
33    | data area | data area grows to lower addresses
34    |           |
35    +-----------+
36
37    The functions first write into a temporary code/data area allocated by
38    "codegen_init". "codegen_finish" copies the code and data area into permanent
39    memory. All functions writing values into the data area return the offset
40    relative the begin of the code area (start of procedure).    
41
42    $Id: codegen-common.c 8314 2007-08-15 22:18:44Z twisti $
43
44 */
45
46
47 #include "config.h"
48
49 #include <assert.h>
50 #include <string.h>
51
52 #include "vm/types.h"
53
54 #if defined(ENABLE_JIT)
55 /* this is required PATCHER_CALL_SIZE */
56 # include "codegen.h"
57 #endif
58
59 #include "mm/memory.h"
60
61 #include "toolbox/avl.h"
62 #include "toolbox/list.h"
63 #include "toolbox/logging.h"
64
65 #include "native/jni.h"
66 #include "native/localref.h"
67 #include "native/native.h"
68
69 #include "threads/threads-common.h"
70
71 #include "vm/exceptions.h"
72 #include "vm/stringlocal.h"
73
74 #include "vm/jit/abi.h"
75 #include "vm/jit/asmpart.h"
76 #include "vm/jit/codegen-common.h"
77
78 #if defined(ENABLE_DISASSEMBLER)
79 # include "vm/jit/disass.h"
80 #endif
81
82 #include "vm/jit/dseg.h"
83 #include "vm/jit/emit-common.h"
84 #include "vm/jit/jit.h"
85 #include "vm/jit/md.h"
86 #include "vm/jit/patcher-common.h"
87 #include "vm/jit/replace.h"
88 #if defined(ENABLE_SSA)
89 # include "vm/jit/optimizing/lsra.h"
90 # include "vm/jit/optimizing/ssa.h"
91 #endif
92 #include "vm/jit/stacktrace.h"
93
94 #if defined(ENABLE_INTRP)
95 #include "vm/jit/intrp/intrp.h"
96 #endif
97
98 #include "vmcore/method.h"
99 #include "vmcore/options.h"
100
101 # include "vmcore/statistics.h"
102
103 #if defined(ENABLE_VMLOG)
104 #include <vmlog_cacao.h>
105 #endif
106
107 #include "show.h"
108
109 /* in this tree we store all method addresses *********************************/
110
111 static avl_tree_t *methodtree = NULL;
112 static s4 methodtree_comparator(const void *treenode, const void *node);
113
114
115 /* codegen_init ****************************************************************
116
117    TODO
118
119 *******************************************************************************/
120
121 void codegen_init(void)
122 {
123         /* this tree is global, not method specific */
124
125         if (!methodtree) {
126 #if defined(ENABLE_JIT)
127                 methodtree_element *mte;
128 #endif
129
130                 methodtree = avl_create(&methodtree_comparator);
131
132 #if defined(ENABLE_JIT)
133                 /* insert asm_vm_call_method */
134
135                 mte = NEW(methodtree_element);
136
137                 mte->startpc = (u1 *) (ptrint) asm_vm_call_method;
138                 mte->endpc   = (u1 *) (ptrint) asm_vm_call_method_end;
139
140                 avl_insert(methodtree, mte);
141 #endif /* defined(ENABLE_JIT) */
142
143         }
144
145 }
146
147
148 /* codegen_setup ***************************************************************
149
150    Allocates and initialises code area, data area and references.
151
152 *******************************************************************************/
153
154 void codegen_setup(jitdata *jd)
155 {
156         methodinfo  *m;
157         codegendata *cd;
158
159         /* get required compiler data */
160
161         m  = jd->m;
162         cd = jd->cd;
163
164         /* initialize members */
165
166         cd->flags        = 0;
167
168         cd->mcodebase    = DMNEW(u1, MCODEINITSIZE);
169         cd->mcodeend     = cd->mcodebase + MCODEINITSIZE;
170         cd->mcodesize    = MCODEINITSIZE;
171
172         /* initialize mcode variables */
173
174         cd->mcodeptr     = cd->mcodebase;
175         cd->lastmcodeptr = cd->mcodebase;
176
177 #if defined(ENABLE_INTRP)
178         /* native dynamic superinstructions variables */
179
180         if (opt_intrp) {
181                 cd->ncodebase = DMNEW(u1, NCODEINITSIZE);
182                 cd->ncodesize = NCODEINITSIZE;
183
184                 /* initialize ncode variables */
185         
186                 cd->ncodeptr = cd->ncodebase;
187
188                 cd->lastinstwithoutdispatch = ~0; /* no inst without dispatch */
189                 cd->superstarts = NULL;
190         }
191 #endif
192
193         cd->dseg           = NULL;
194         cd->dseglen        = 0;
195
196         cd->jumpreferences = NULL;
197
198 #if defined(__I386__) || defined(__X86_64__) || defined(__XDSPCORE__) || defined(__M68K__) || defined(ENABLE_INTRP)
199         cd->datareferences = NULL;
200 #endif
201
202 /*      cd->patchrefs      = list_create_dump(OFFSET(patchref, linkage)); */
203         cd->patchrefs      = NULL;
204         cd->brancheslabel  = list_create_dump(OFFSET(branch_label_ref_t, linkage));
205         cd->listcritical   = list_create_dump(OFFSET(critical_section_ref_t, linkage));
206
207         cd->linenumberreferences = NULL;
208         cd->linenumbertablesizepos = 0;
209         cd->linenumbertablestartpos = 0;
210         cd->linenumbertab = 0;
211 }
212
213
214 /* codegen_reset ***************************************************************
215
216    Resets the codegen data structure so we can recompile the method.
217
218 *******************************************************************************/
219
220 static void codegen_reset(jitdata *jd)
221 {
222         codeinfo    *code;
223         codegendata *cd;
224         basicblock  *bptr;
225
226         /* get required compiler data */
227
228         code = jd->code;
229         cd   = jd->cd;
230
231         /* reset error flag */
232
233         cd->flags          &= ~CODEGENDATA_FLAG_ERROR;
234
235         /* reset some members, we reuse the code memory already allocated
236            as this should have almost the correct size */
237
238         cd->mcodeptr        = cd->mcodebase;
239         cd->lastmcodeptr    = cd->mcodebase;
240
241         cd->dseg            = NULL;
242         cd->dseglen         = 0;
243
244         cd->jumpreferences  = NULL;
245
246 #if defined(__I386__) || defined(__X86_64__) || defined(__XDSPCORE__) || defined(__M68K__) || defined(ENABLE_INTRP)
247         cd->datareferences  = NULL;
248 #endif
249
250 /*      cd->patchrefs       = list_create_dump(OFFSET(patchref, linkage)); */
251         cd->patchrefs       = NULL;
252         cd->brancheslabel   = list_create_dump(OFFSET(branch_label_ref_t, linkage));
253         cd->listcritical    = list_create_dump(OFFSET(critical_section_ref_t, linkage));
254
255         cd->linenumberreferences    = NULL;
256         cd->linenumbertablesizepos  = 0;
257         cd->linenumbertablestartpos = 0;
258         cd->linenumbertab           = 0;
259         
260         /* We need to clear the mpc and the branch references from all
261            basic blocks as they will definitely change. */
262
263         for (bptr = jd->basicblocks; bptr != NULL; bptr = bptr->next) {
264                 bptr->mpc        = -1;
265                 bptr->branchrefs = NULL;
266         }
267
268         /* We need to clear all the patcher references from the codeinfo
269            since they all will be regenerated */
270
271         patcher_list_reset(code);
272
273 #if defined(ENABLE_REPLACEMENT)
274         code->rplpoints     = NULL;
275         code->rplpointcount = 0;
276         code->regalloc      = NULL;
277         code->regalloccount = 0;
278         code->globalcount   = 0;
279 #endif
280 }
281
282
283 /* codegen_generate ************************************************************
284
285    Generates the code for the currently compiled method.
286
287 *******************************************************************************/
288
289 bool codegen_generate(jitdata *jd)
290 {
291         codegendata *cd;
292
293         /* get required compiler data */
294
295         cd = jd->cd;
296
297         /* call the machine-dependent code generation function */
298
299         if (!codegen_emit(jd))
300                 return false;
301
302         /* check for an error */
303
304         if (CODEGENDATA_HAS_FLAG_ERROR(cd)) {
305                 /* check for long-branches flag, if it is set we recompile the
306                    method */
307
308 #if !defined(NDEBUG)
309         if (compileverbose)
310             log_message_method("Re-generating code: ", jd->m);
311 #endif
312
313                 /* XXX maybe we should tag long-branches-methods for recompilation */
314
315                 if (CODEGENDATA_HAS_FLAG_LONGBRANCHES(cd)) {
316                         /* we have to reset the codegendata structure first */
317
318                         codegen_reset(jd);
319
320                         /* and restart the compiler run */
321
322                         if (!codegen_emit(jd))
323                                 return false;
324                 }
325                 else {
326                         vm_abort("codegen_generate: unknown error occurred during codegen_emit: flags=%x\n", cd->flags);
327                 }
328
329 #if !defined(NDEBUG)
330         if (compileverbose)
331             log_message_method("Re-generating code done: ", jd->m);
332 #endif
333         }
334
335         /* reallocate the memory and finish the code generation */
336
337         codegen_finish(jd);
338
339         /* everything's ok */
340
341         return true;
342 }
343
344
345 /* codegen_close ***************************************************************
346
347    TODO
348
349 *******************************************************************************/
350
351 void codegen_close(void)
352 {
353         /* TODO: release avl tree on i386 and x86_64 */
354 }
355
356
357 /* codegen_increase ************************************************************
358
359    Doubles code area.
360
361 *******************************************************************************/
362
363 void codegen_increase(codegendata *cd)
364 {
365         u1 *oldmcodebase;
366
367         /* save old mcodebase pointer */
368
369         oldmcodebase = cd->mcodebase;
370
371         /* reallocate to new, doubled memory */
372
373         cd->mcodebase = DMREALLOC(cd->mcodebase,
374                                                           u1,
375                                                           cd->mcodesize,
376                                                           cd->mcodesize * 2);
377         cd->mcodesize *= 2;
378         cd->mcodeend   = cd->mcodebase + cd->mcodesize;
379
380         /* set new mcodeptr */
381
382         cd->mcodeptr = cd->mcodebase + (cd->mcodeptr - oldmcodebase);
383
384 #if defined(__I386__) || defined(__MIPS__) || defined(__X86_64__) || defined(__M68K__) || defined(ENABLE_INTRP) \
385  || defined(__SPARC_64__)
386         /* adjust the pointer to the last patcher position */
387
388         if (cd->lastmcodeptr != NULL)
389                 cd->lastmcodeptr = cd->mcodebase + (cd->lastmcodeptr - oldmcodebase);
390 #endif
391 }
392
393
394 /* codegen_ncode_increase ******************************************************
395
396    Doubles code area.
397
398 *******************************************************************************/
399
400 #if defined(ENABLE_INTRP)
401 u1 *codegen_ncode_increase(codegendata *cd, u1 *ncodeptr)
402 {
403         u1 *oldncodebase;
404
405         /* save old ncodebase pointer */
406
407         oldncodebase = cd->ncodebase;
408
409         /* reallocate to new, doubled memory */
410
411         cd->ncodebase = DMREALLOC(cd->ncodebase,
412                                                           u1,
413                                                           cd->ncodesize,
414                                                           cd->ncodesize * 2);
415         cd->ncodesize *= 2;
416
417         /* return the new ncodeptr */
418
419         return (cd->ncodebase + (ncodeptr - oldncodebase));
420 }
421 #endif
422
423
424 /* codegen_add_branch_ref ******************************************************
425
426    Prepends an branch to the list.
427
428 *******************************************************************************/
429
430 void codegen_add_branch_ref(codegendata *cd, basicblock *target, s4 condition, s4 reg, u4 options)
431 {
432         branchref *br;
433         s4         branchmpc;
434
435         STATISTICS(count_branches_unresolved++);
436
437         /* calculate the mpc of the branch instruction */
438
439         branchmpc = cd->mcodeptr - cd->mcodebase;
440
441         br = DNEW(branchref);
442
443         br->branchmpc = branchmpc;
444         br->condition = condition;
445         br->reg       = reg;
446         br->options   = options;
447         br->next      = target->branchrefs;
448
449         target->branchrefs = br;
450 }
451
452
453 /* codegen_resolve_branchrefs **************************************************
454
455    Resolves and patches the branch references of a given basic block.
456
457 *******************************************************************************/
458
459 void codegen_resolve_branchrefs(codegendata *cd, basicblock *bptr)
460 {
461         branchref *br;
462         u1        *mcodeptr;
463
464         /* Save the mcodeptr because in the branch emitting functions
465            we generate code somewhere inside already generated code,
466            but we're still in the actual code generation phase. */
467
468         mcodeptr = cd->mcodeptr;
469
470         /* just to make sure */
471
472         assert(bptr->mpc >= 0);
473
474         for (br = bptr->branchrefs; br != NULL; br = br->next) {
475                 /* temporary set the mcodeptr */
476
477                 cd->mcodeptr = cd->mcodebase + br->branchmpc;
478
479                 /* emit_bccz and emit_branch emit the correct code, even if we
480                    pass condition == BRANCH_UNCONDITIONAL or reg == -1. */
481
482                 emit_bccz(cd, bptr, br->condition, br->reg, br->options);
483         }
484
485         /* restore mcodeptr */
486
487         cd->mcodeptr = mcodeptr;
488 }
489
490
491 /* codegen_branch_label_add ****************************************************
492
493    Append an branch to the label-branch list.
494
495 *******************************************************************************/
496
497 void codegen_branch_label_add(codegendata *cd, s4 label, s4 condition, s4 reg, u4 options)
498 {
499         list_t             *list;
500         branch_label_ref_t *br;
501         s4                  mpc;
502
503         /* get the label list */
504
505         list = cd->brancheslabel;
506         
507         /* calculate the current mpc */
508
509         mpc = cd->mcodeptr - cd->mcodebase;
510
511         br = DNEW(branch_label_ref_t);
512
513         br->mpc       = mpc;
514         br->label     = label;
515         br->condition = condition;
516         br->reg       = reg;
517         br->options   = options;
518
519         /* add the branch to the list */
520
521         list_add_last_unsynced(list, br);
522 }
523
524
525 /* codegen_add_patch_ref *******************************************************
526
527    Appends a new patcher reference to the list of patching positions.
528
529 *******************************************************************************/
530
531 void codegen_add_patch_ref(codegendata *cd, functionptr patcher, voidptr ref,
532                                                    s4 disp)
533 {
534         patchref *pr;
535         s4        branchmpc;
536
537         branchmpc = cd->mcodeptr - cd->mcodebase;
538
539         pr = DNEW(patchref);
540
541         pr->branchpos = branchmpc;
542         pr->disp      = disp;
543         pr->patcher   = patcher;
544         pr->ref       = ref;
545
546 /*      list_add_first(cd->patchrefs, pr); */
547         pr->next      = cd->patchrefs;
548         cd->patchrefs = pr;
549
550         /* Generate NOPs for opt_shownops. */
551
552         if (opt_shownops)
553                 PATCHER_NOPS;
554
555 #if defined(ENABLE_JIT) && (defined(__I386__) || defined(__M68K__) || defined(__MIPS__) \
556  || defined(__SPARC_64__) || defined(__X86_64__))
557
558         /* On some architectures the patcher stub call instruction might
559            be longer than the actual instruction generated.  On this
560            architectures we store the last patcher call position and after
561            the basic block code generation is completed, we check the
562            range and maybe generate some nop's. */
563         /* The nops are generated in codegen_emit in each codegen */
564
565         cd->lastmcodeptr = cd->mcodeptr + PATCHER_CALL_SIZE;
566 #endif
567 }
568
569
570 /* codegen_critical_section_new ************************************************
571
572    Allocates a new critical-section reference and adds it to the
573    critical-section list.
574
575 *******************************************************************************/
576
577 #if defined(ENABLE_THREADS)
578 void codegen_critical_section_new(codegendata *cd)
579 {
580         list_t                 *list;
581         critical_section_ref_t *csr;
582         s4                      mpc;
583
584         /* get the critical section list */
585
586         list = cd->listcritical;
587         
588         /* calculate the current mpc */
589
590         mpc = cd->mcodeptr - cd->mcodebase;
591
592         csr = DNEW(critical_section_ref_t);
593
594         /* We only can set restart right now, as start and end are set by
595            the following, corresponding functions. */
596
597         csr->start   = -1;
598         csr->end     = -1;
599         csr->restart = mpc;
600
601         /* add the branch to the list */
602
603         list_add_last_unsynced(list, csr);
604 }
605 #endif
606
607
608 /* codegen_critical_section_start **********************************************
609
610    Set the start-point of the current critical section (which is the
611    last element of the list).
612
613 *******************************************************************************/
614
615 #if defined(ENABLE_THREADS)
616 void codegen_critical_section_start(codegendata *cd)
617 {
618         list_t                 *list;
619         critical_section_ref_t *csr;
620         s4                      mpc;
621
622         /* get the critical section list */
623
624         list = cd->listcritical;
625         
626         /* calculate the current mpc */
627
628         mpc = cd->mcodeptr - cd->mcodebase;
629
630         /* get the current critical section */
631
632         csr = list_last_unsynced(list);
633
634         /* set the start point */
635
636         assert(csr->start == -1);
637
638         csr->start = mpc;
639 }
640 #endif
641
642
643 /* codegen_critical_section_end ************************************************
644
645    Set the end-point of the current critical section (which is the
646    last element of the list).
647
648 *******************************************************************************/
649
650 #if defined(ENABLE_THREADS)
651 void codegen_critical_section_end(codegendata *cd)
652 {
653         list_t                 *list;
654         critical_section_ref_t *csr;
655         s4                      mpc;
656
657         /* get the critical section list */
658
659         list = cd->listcritical;
660         
661         /* calculate the current mpc */
662
663         mpc = cd->mcodeptr - cd->mcodebase;
664
665         /* get the current critical section */
666
667         csr = list_last_unsynced(list);
668
669         /* set the end point */
670
671         assert(csr->end == -1);
672
673         csr->end = mpc;
674 }
675 #endif
676
677
678 /* codegen_critical_section_finish *********************************************
679
680    Finish the critical sections, create the critical section nodes for
681    the AVL tree and insert them into the tree.
682
683 *******************************************************************************/
684
685 #if defined(ENABLE_THREADS)
686 static void codegen_critical_section_finish(jitdata *jd)
687 {
688         codeinfo    *code;
689         codegendata *cd;
690         list_t                  *list;
691         critical_section_ref_t  *csr;
692         critical_section_node_t *csn;
693
694         /* get required compiler data */
695
696         code = jd->code;
697         cd   = jd->cd;
698
699         /* get the critical section list */
700
701         list = cd->listcritical;
702
703         /* iterate over all critical sections */
704
705         for (csr = list_first_unsynced(list); csr != NULL;
706                  csr = list_next_unsynced(list, csr)) {
707                 /* check if all points are set */
708
709                 assert(csr->start   != -1);
710                 assert(csr->end     != -1);
711                 assert(csr->restart != -1);
712
713                 /* allocate tree node */
714
715                 csn = NEW(critical_section_node_t);
716
717                 csn->start   = code->entrypoint + csr->start;
718                 csn->end     = code->entrypoint + csr->end;
719                 csn->restart = code->entrypoint + csr->restart;
720
721                 /* insert into the tree */
722
723                 critical_section_register(csn);
724         }
725 }
726 #endif
727
728
729 /* methodtree_comparator *******************************************************
730
731    Comparator function used for the AVL tree of methods.
732
733    ARGUMENTS:
734       treenode....the node from the tree
735       node........the node to compare to the tree-node
736
737 *******************************************************************************/
738
739 static s4 methodtree_comparator(const void *treenode, const void *node)
740 {
741         methodtree_element *mte;
742         methodtree_element *mtepc;
743
744         mte   = (methodtree_element *) treenode;
745         mtepc = (methodtree_element *) node;
746
747         /* compare both startpc and endpc of pc, even if they have the same value,
748            otherwise the avl_probe sometimes thinks the element is already in the
749            tree */
750
751 #ifdef __S390__
752         /* On S390 addresses are 31 bit. Compare only 31 bits of value.
753          */
754 #       define ADDR_MASK(a) ((a) & 0x7FFFFFFF)
755 #else
756 #       define ADDR_MASK(a) (a)
757 #endif
758
759         if (ADDR_MASK((long) mte->startpc) <= ADDR_MASK((long) mtepc->startpc) &&
760                 ADDR_MASK((long) mtepc->startpc) <= ADDR_MASK((long) mte->endpc) &&
761                 ADDR_MASK((long) mte->startpc) <= ADDR_MASK((long) mtepc->endpc) &&
762                 ADDR_MASK((long) mtepc->endpc) <= ADDR_MASK((long) mte->endpc)) {
763                 return 0;
764
765         } else if (ADDR_MASK((long) mtepc->startpc) < ADDR_MASK((long) mte->startpc)) {
766                 return -1;
767
768         } else {
769                 return 1;
770         }
771
772 #       undef ADDR_MASK
773 }
774
775
776 /* codegen_insertmethod ********************************************************
777
778    Insert the machine code range of a method into the AVL tree of methods.
779
780 *******************************************************************************/
781
782 void codegen_insertmethod(u1 *startpc, u1 *endpc)
783 {
784         methodtree_element *mte;
785
786         /* allocate new method entry */
787
788         mte = NEW(methodtree_element);
789
790         mte->startpc = startpc;
791         mte->endpc   = endpc;
792
793         /* this function does not return an error, but asserts for
794            duplicate entries */
795
796         avl_insert(methodtree, mte);
797 }
798
799
800 /* codegen_get_pv_from_pc ******************************************************
801
802    Find the PV for the given PC by searching in the AVL tree of
803    methods.
804
805 *******************************************************************************/
806
807 u1 *codegen_get_pv_from_pc(u1 *pc)
808 {
809         methodtree_element  mtepc;
810         methodtree_element *mte;
811
812         /* allocation of the search structure on the stack is much faster */
813
814         mtepc.startpc = pc;
815         mtepc.endpc   = pc;
816
817         mte = avl_find(methodtree, &mtepc);
818
819         if (mte == NULL) {
820                 /* No method was found.  Let's dump a stacktrace. */
821
822 #if defined(ENABLE_VMLOG)
823                 vmlog_cacao_signl("SIGSEGV");
824 #endif
825
826                 log_println("We received a SIGSEGV and tried to handle it, but we were");
827                 log_println("unable to find a Java method at:");
828                 log_println("");
829 #if SIZEOF_VOID_P == 8
830                 log_println("PC=0x%016lx", pc);
831 #else
832                 log_println("PC=0x%08x", pc);
833 #endif
834                 log_println("");
835                 assert(0);
836                 log_println("Dumping the current stacktrace:");
837
838 #if defined(ENABLE_THREADS)
839                 /* XXX michi: This should be available even without threads! */
840                 threads_print_stacktrace();
841 #endif
842
843                 vm_abort("Exiting...");
844         }
845
846         return mte->startpc;
847 }
848
849
850 /* codegen_get_pv_from_pc_nocheck **********************************************
851
852    Find the PV for the given PC by searching in the AVL tree of
853    methods.  This method does not check the return value and is used
854    by the profiler.
855
856 *******************************************************************************/
857
858 u1 *codegen_get_pv_from_pc_nocheck(u1 *pc)
859 {
860         methodtree_element  mtepc;
861         methodtree_element *mte;
862
863         /* allocation of the search structure on the stack is much faster */
864
865         mtepc.startpc = pc;
866         mtepc.endpc   = pc;
867
868         mte = avl_find(methodtree, &mtepc);
869
870         if (mte == NULL)
871                 return NULL;
872         else
873                 return mte->startpc;
874 }
875
876
877 /* codegen_set_replacement_point_notrap ****************************************
878
879    Record the position of a non-trappable replacement point.
880
881 *******************************************************************************/
882
883 #if defined(ENABLE_REPLACEMENT)
884 #if !defined(NDEBUG)
885 void codegen_set_replacement_point_notrap(codegendata *cd, s4 type)
886 #else
887 void codegen_set_replacement_point_notrap(codegendata *cd)
888 #endif
889 {
890         assert(cd->replacementpoint);
891         assert(cd->replacementpoint->type == type);
892         assert(cd->replacementpoint->flags & RPLPOINT_FLAG_NOTRAP);
893
894         cd->replacementpoint->pc = (u1*) (ptrint) (cd->mcodeptr - cd->mcodebase);
895
896         cd->replacementpoint++;
897 }
898 #endif /* defined(ENABLE_REPLACEMENT) */
899
900
901 /* codegen_set_replacement_point ***********************************************
902
903    Record the position of a trappable replacement point.
904
905 *******************************************************************************/
906
907 #if defined(ENABLE_REPLACEMENT)
908 #if !defined(NDEBUG)
909 void codegen_set_replacement_point(codegendata *cd, s4 type)
910 #else
911 void codegen_set_replacement_point(codegendata *cd)
912 #endif
913 {
914         assert(cd->replacementpoint);
915         assert(cd->replacementpoint->type == type);
916         assert(!(cd->replacementpoint->flags & RPLPOINT_FLAG_NOTRAP));
917
918         cd->replacementpoint->pc = (u1*) (ptrint) (cd->mcodeptr - cd->mcodebase);
919
920         cd->replacementpoint++;
921
922         /* XXX assert(cd->lastmcodeptr <= cd->mcodeptr); */
923
924         cd->lastmcodeptr = cd->mcodeptr + PATCHER_CALL_SIZE;
925 }
926 #endif /* defined(ENABLE_REPLACEMENT) */
927
928
929 /* codegen_finish **************************************************************
930
931    Finishes the code generation. A new memory, large enough for both
932    data and code, is allocated and data and code are copied together
933    to their final layout, unresolved jumps are resolved, ...
934
935 *******************************************************************************/
936
937 void codegen_finish(jitdata *jd)
938 {
939         codeinfo    *code;
940         codegendata *cd;
941         s4           mcodelen;
942 #if defined(ENABLE_INTRP)
943         s4           ncodelen;
944 #endif
945         s4           alignedmcodelen;
946         jumpref     *jr;
947         patchref_t  *pr;
948         u1          *epoint;
949         s4           alignedlen;
950
951         /* get required compiler data */
952
953         code = jd->code;
954         cd   = jd->cd;
955
956         /* prevent compiler warning */
957
958 #if defined(ENABLE_INTRP)
959         ncodelen = 0;
960 #endif
961
962         /* calculate the code length */
963
964         mcodelen = (s4) (cd->mcodeptr - cd->mcodebase);
965
966 #if defined(ENABLE_STATISTICS)
967         if (opt_stat) {
968                 count_code_len += mcodelen;
969                 count_data_len += cd->dseglen;
970         }
971 #endif
972
973         alignedmcodelen = MEMORY_ALIGN(mcodelen, MAX_ALIGN);
974
975 #if defined(ENABLE_INTRP)
976         if (opt_intrp)
977                 ncodelen = cd->ncodeptr - cd->ncodebase;
978         else {
979                 ncodelen = 0; /* avoid compiler warning */
980         }
981 #endif
982
983         cd->dseglen = MEMORY_ALIGN(cd->dseglen, MAX_ALIGN);
984         alignedlen = alignedmcodelen + cd->dseglen;
985
986 #if defined(ENABLE_INTRP)
987         if (opt_intrp) {
988                 alignedlen += ncodelen;
989         }
990 #endif
991
992         /* allocate new memory */
993
994         code->mcodelength = mcodelen + cd->dseglen;
995         code->mcode       = CNEW(u1, alignedlen);
996
997         /* set the entrypoint of the method */
998         
999         assert(code->entrypoint == NULL);
1000         code->entrypoint = epoint = (code->mcode + cd->dseglen);
1001
1002         /* fill the data segment (code->entrypoint must already be set!) */
1003
1004         dseg_finish(jd);
1005
1006         /* copy code to the new location */
1007
1008         MCOPY((void *) code->entrypoint, cd->mcodebase, u1, mcodelen);
1009
1010 #if defined(ENABLE_INTRP)
1011         /* relocate native dynamic superinstruction code (if any) */
1012
1013         if (opt_intrp) {
1014                 cd->mcodebase = code->entrypoint;
1015
1016                 if (ncodelen > 0) {
1017                         u1 *ncodebase = code->mcode + cd->dseglen + alignedmcodelen;
1018
1019                         MCOPY((void *) ncodebase, cd->ncodebase, u1, ncodelen);
1020
1021                         /* flush the instruction and data caches */
1022
1023                         md_cacheflush(ncodebase, ncodelen);
1024
1025                         /* set some cd variables for dynamic_super_rerwite */
1026
1027                         cd->ncodebase = ncodebase;
1028
1029                 } else {
1030                         cd->ncodebase = NULL;
1031                 }
1032
1033                 dynamic_super_rewrite(cd);
1034         }
1035 #endif
1036
1037         /* jump table resolving */
1038
1039         for (jr = cd->jumpreferences; jr != NULL; jr = jr->next)
1040                 *((functionptr *) ((ptrint) epoint + jr->tablepos)) =
1041                         (functionptr) ((ptrint) epoint + (ptrint) jr->target->mpc);
1042
1043         /* line number table resolving */
1044         {
1045                 linenumberref *lr;
1046                 ptrint lrtlen = 0;
1047                 ptrint target;
1048
1049                 for (lr = cd->linenumberreferences; lr != NULL; lr = lr->next) {
1050                         lrtlen++;
1051                         target = lr->targetmpc;
1052                         /* if the entry contains an mcode pointer (normal case), resolve it */
1053                         /* (see doc/inlining_stacktrace.txt for details)                    */
1054                         if (lr->linenumber >= -2) {
1055                             target += (ptrint) epoint;
1056                         }
1057                         *((functionptr *) ((ptrint) epoint + (ptrint) lr->tablepos)) = 
1058                                 (functionptr) target;
1059                 }
1060                 
1061                 *((functionptr *) ((ptrint) epoint + cd->linenumbertablestartpos)) =
1062                         (functionptr) ((ptrint) epoint + cd->linenumbertab);
1063
1064                 *((ptrint *) ((ptrint) epoint + cd->linenumbertablesizepos)) = lrtlen;
1065         }
1066
1067         /* patcher resolving */
1068
1069         pr = list_first_unsynced(code->patchers);
1070         while (pr) {
1071                 pr->mpc += (ptrint) epoint;
1072                 pr->datap = (ptrint) (pr->disp + epoint);
1073                 pr = list_next_unsynced(code->patchers, pr);
1074         }
1075
1076 #if defined(ENABLE_REPLACEMENT)
1077         /* replacement point resolving */
1078         {
1079                 int i;
1080                 rplpoint *rp;
1081
1082                 rp = code->rplpoints;
1083                 for (i=0; i<code->rplpointcount; ++i, ++rp) {
1084                         rp->pc = (u1*) ((ptrint) epoint + (ptrint) rp->pc);
1085                 }
1086         }
1087 #endif /* defined(ENABLE_REPLACEMENT) */
1088
1089         /* add method into methodtree to find the entrypoint */
1090
1091         codegen_insertmethod(code->entrypoint, code->entrypoint + mcodelen);
1092
1093 #if defined(__I386__) || defined(__X86_64__) || defined(__XDSPCORE__) || defined(__M68K__) || defined(ENABLE_INTRP)
1094         /* resolve data segment references */
1095
1096         dseg_resolve_datareferences(jd);
1097 #endif
1098
1099 #if defined(ENABLE_THREADS)
1100         /* create cirtical sections */
1101
1102         codegen_critical_section_finish(jd);
1103 #endif
1104
1105         /* flush the instruction and data caches */
1106
1107         md_cacheflush(code->mcode, code->mcodelength);
1108 }
1109
1110
1111 /* codegen_generate_stub_compiler **********************************************
1112
1113    Wrapper for codegen_emit_stub_compiler.
1114
1115    Returns:
1116        pointer to the compiler stub code.
1117
1118 *******************************************************************************/
1119
1120 u1 *codegen_generate_stub_compiler(methodinfo *m)
1121 {
1122         jitdata     *jd;
1123         codegendata *cd;
1124         ptrint      *d;                     /* pointer to data memory             */
1125         u1          *c;                     /* pointer to code memory             */
1126         s4           dumpsize;
1127
1128         /* mark dump memory */
1129
1130         dumpsize = dump_size();
1131
1132         /* allocate required data structures */
1133
1134         jd = DNEW(jitdata);
1135
1136         jd->m     = m;
1137         jd->cd    = DNEW(codegendata);
1138         jd->flags = 0;
1139
1140         /* get required compiler data */
1141
1142         cd = jd->cd;
1143
1144         /* allocate code memory */
1145
1146         c = CNEW(u1, 3 * SIZEOF_VOID_P + COMPILERSTUB_CODESIZE);
1147
1148         /* set pointers correctly */
1149
1150         d = (ptrint *) c;
1151
1152         cd->mcodebase = c;
1153
1154         c = c + 3 * SIZEOF_VOID_P;
1155         cd->mcodeptr = c;
1156
1157         /* NOTE: The codeinfo pointer is actually a pointer to the
1158            methodinfo (this fakes a codeinfo structure). */
1159
1160         d[0] = (ptrint) asm_call_jit_compiler;
1161         d[1] = (ptrint) m;
1162         d[2] = (ptrint) &d[1];                                    /* fake code->m */
1163
1164         /* call the emit function */
1165
1166         codegen_emit_stub_compiler(jd);
1167
1168 #if defined(ENABLE_STATISTICS)
1169         if (opt_stat)
1170                 count_cstub_len += 3 * SIZEOF_VOID_P + COMPILERSTUB_CODESIZE;
1171 #endif
1172
1173         /* flush caches */
1174
1175         md_cacheflush(cd->mcodebase, 3 * SIZEOF_VOID_P + COMPILERSTUB_CODESIZE);
1176
1177         /* release dump memory */
1178
1179         dump_release(dumpsize);
1180
1181         /* return native stub code */
1182
1183         return c;
1184 }
1185
1186
1187 /* codegen_generate_stub_native ************************************************
1188
1189    Wrapper for codegen_emit_stub_native.
1190
1191    Returns:
1192        the codeinfo representing the stub code.
1193
1194 *******************************************************************************/
1195
1196 codeinfo *codegen_generate_stub_native(methodinfo *m, functionptr f)
1197 {
1198         jitdata     *jd;
1199         codeinfo    *code;
1200         s4           dumpsize;
1201         methoddesc  *md;
1202         methoddesc  *nmd;       
1203         s4           nativeparams;
1204
1205         /* mark dump memory */
1206
1207         dumpsize = dump_size();
1208
1209         jd = DNEW(jitdata);
1210
1211         jd->m     = m;
1212         jd->cd    = DNEW(codegendata);
1213         jd->rd    = DNEW(registerdata);
1214         jd->flags = 0;
1215
1216         /* Allocate codeinfo memory from the heap as we need to keep them. */
1217
1218         jd->code  = code_codeinfo_new(m); /* XXX check allocation */
1219
1220         /* get required compiler data */
1221
1222         code = jd->code;
1223
1224         /* set the flags for the current JIT run */
1225
1226 #if defined(ENABLE_PROFILING)
1227         if (opt_prof)
1228                 jd->flags |= JITDATA_FLAG_INSTRUMENT;
1229 #endif
1230
1231         if (opt_verbosecall)
1232                 jd->flags |= JITDATA_FLAG_VERBOSECALL;
1233
1234         /* setup code generation stuff */
1235
1236 #if defined(ENABLE_JIT)
1237 # if defined(ENABLE_INTRP)
1238         if (!opt_intrp)
1239 # endif
1240                 reg_setup(jd);
1241 #endif
1242
1243         codegen_setup(jd);
1244
1245         /* create new method descriptor with additional native parameters */
1246
1247         md = m->parseddesc;
1248         nativeparams = (m->flags & ACC_STATIC) ? 2 : 1;
1249         
1250         nmd = (methoddesc *) DMNEW(u1, sizeof(methoddesc) - sizeof(typedesc) +
1251                                                            md->paramcount * sizeof(typedesc) +
1252                                                            nativeparams * sizeof(typedesc));
1253
1254         nmd->paramcount = md->paramcount + nativeparams;
1255
1256         nmd->params = DMNEW(paramdesc, nmd->paramcount);
1257
1258         nmd->paramtypes[0].type = TYPE_ADR; /* add environment pointer            */
1259
1260         if (m->flags & ACC_STATIC)
1261                 nmd->paramtypes[1].type = TYPE_ADR; /* add class pointer              */
1262
1263         MCOPY(nmd->paramtypes + nativeparams, md->paramtypes, typedesc,
1264                   md->paramcount);
1265
1266 #if defined(ENABLE_JIT)
1267 # if defined(ENABLE_INTRP)
1268         if (!opt_intrp)
1269 # endif
1270                 /* pre-allocate the arguments for the native ABI */
1271
1272                 md_param_alloc_native(nmd);
1273 #endif
1274
1275         /* generate the code */
1276
1277 #if defined(ENABLE_JIT)
1278 # if defined(ENABLE_INTRP)
1279         if (opt_intrp)
1280                 intrp_createnativestub(f, jd, nmd);
1281         else
1282 # endif
1283                 codegen_emit_stub_native(jd, nmd, f);
1284 #else
1285         intrp_createnativestub(f, jd, nmd);
1286 #endif
1287
1288         /* reallocate the memory and finish the code generation */
1289
1290         codegen_finish(jd);
1291
1292 #if defined(ENABLE_STATISTICS)
1293         /* must be done after codegen_finish() */
1294
1295         if (opt_stat)
1296                 size_stub_native += code->mcodelength;
1297 #endif
1298
1299 #if !defined(NDEBUG)
1300         /* disassemble native stub */
1301
1302         if (opt_shownativestub) {
1303 #if defined(ENABLE_DEBUG_FILTER)
1304                 if (m->filtermatches & SHOW_FILTER_FLAG_SHOW_METHOD)
1305 #endif
1306                 {
1307 #if defined(ENABLE_DISASSEMBLER)
1308                         codegen_disassemble_nativestub(m,
1309                                                                                    (u1 *) (ptrint) code->entrypoint,
1310                                                                                    (u1 *) (ptrint) code->entrypoint + (code->mcodelength - jd->cd->dseglen));
1311 #endif
1312
1313                         /* show data segment */
1314
1315                         if (opt_showddatasegment)
1316                                 dseg_display(jd);
1317                 }
1318         }
1319 #endif /* !defined(NDEBUG) */
1320
1321         /* release memory */
1322
1323         dump_release(dumpsize);
1324
1325         /* return native stub code */
1326
1327         return code;
1328 }
1329
1330
1331 /* codegen_disassemble_nativestub **********************************************
1332
1333    Disassembles the generated native stub.
1334
1335 *******************************************************************************/
1336
1337 #if defined(ENABLE_DISASSEMBLER)
1338 void codegen_disassemble_nativestub(methodinfo *m, u1 *start, u1 *end)
1339 {
1340         printf("Native stub: ");
1341         utf_fprint_printable_ascii_classname(stdout, m->class->name);
1342         printf(".");
1343         utf_fprint_printable_ascii(stdout, m->name);
1344         utf_fprint_printable_ascii(stdout, m->descriptor);
1345         printf("\n\nLength: %d\n\n", (s4) (end - start));
1346
1347         DISASSEMBLE(start, end);
1348 }
1349 #endif
1350
1351
1352 /* codegen_start_native_call ***************************************************
1353
1354    Prepares the stuff required for a native (JNI) function call:
1355
1356    - adds a stackframe info structure to the chain, for stacktraces
1357    - prepares the local references table on the stack
1358
1359    The layout of the native stub stackframe should look like this:
1360
1361    +---------------------------+ <- SP (of parent Java function)
1362    | return address            |
1363    +---------------------------+
1364    |                           |
1365    | stackframe info structure |
1366    |                           |
1367    +---------------------------+
1368    |                           |
1369    | local references table    |
1370    |                           |
1371    +---------------------------+
1372    |                           |
1373    | arguments (if any)        |
1374    |                           |
1375    +---------------------------+ <- SP (native stub)
1376
1377 *******************************************************************************/
1378
1379 void codegen_start_native_call(u1 *datasp, u1 *pv, u1 *sp, u1 *ra)
1380 {
1381         stackframeinfo *sfi;
1382         localref_table *lrt;
1383
1384         STATISTICS(count_calls_java_to_native++);
1385
1386         /* get data structures from stack */
1387
1388         sfi = (stackframeinfo *) (datasp - sizeof(stackframeinfo));
1389         lrt = (localref_table *) (datasp - sizeof(stackframeinfo) - 
1390                                                           sizeof(localref_table));
1391
1392         /* add a stackframeinfo to the chain */
1393
1394         stacktrace_create_native_stackframeinfo(sfi, pv, sp, ra);
1395
1396 #if defined(ENABLE_JNI)
1397         /* add current JNI local references table to this thread */
1398
1399         localref_table_add(lrt);
1400 #endif
1401 }
1402
1403
1404 /* codegen_finish_native_call **************************************************
1405
1406    Removes the stuff required for a native (JNI) function call.
1407    Additionally it checks for an exceptions and in case, get the
1408    exception object and clear the pointer.
1409
1410 *******************************************************************************/
1411
1412 java_object_t *codegen_finish_native_call(u1 *datasp)
1413 {
1414         stackframeinfo  *sfi;
1415         stackframeinfo **psfi;
1416         java_handle_t   *e;
1417
1418         /* get data structures from stack */
1419
1420         sfi = (stackframeinfo *) (datasp - sizeof(stackframeinfo));
1421
1422         /* remove current stackframeinfo from chain */
1423
1424         psfi = &STACKFRAMEINFO;
1425
1426         *psfi = sfi->prev;
1427
1428 #if defined(ENABLE_JNI)
1429         /* release JNI local references table for this thread */
1430
1431         localref_frame_pop_all();
1432         localref_table_remove();
1433 #endif
1434
1435         /* get the exception and return it */
1436
1437         e = exceptions_get_and_clear_exception();
1438
1439         return e;
1440 }
1441
1442
1443 /* removecompilerstub **********************************************************
1444
1445    Deletes a compilerstub from memory (simply by freeing it).
1446
1447 *******************************************************************************/
1448
1449 void removecompilerstub(u1 *stub)
1450 {
1451         /* pass size 1 to keep the intern function happy */
1452
1453         CFREE((void *) stub, 1);
1454 }
1455
1456
1457 /* removenativestub ************************************************************
1458
1459    Removes a previously created native-stub from memory.
1460     
1461 *******************************************************************************/
1462
1463 void removenativestub(u1 *stub)
1464 {
1465         /* pass size 1 to keep the intern function happy */
1466
1467         CFREE((void *) stub, 1);
1468 }
1469
1470
1471 /* codegen_reg_of_var **********************************************************
1472
1473    This function determines a register, to which the result of an
1474    operation should go, when it is ultimatively intended to store the
1475    result in pseudoregister v.  If v is assigned to an actual
1476    register, this register will be returned.  Otherwise (when v is
1477    spilled) this function returns tempregnum.  If not already done,
1478    regoff and flags are set in the stack location.
1479        
1480 *******************************************************************************/
1481
1482 s4 codegen_reg_of_var(u2 opcode, varinfo *v, s4 tempregnum)
1483 {
1484
1485 #if 0
1486         /* Do we have to generate a conditional move?  Yes, then always
1487            return the temporary register.  The real register is identified
1488            during the store. */
1489
1490         if (opcode & ICMD_CONDITION_MASK)
1491                 return tempregnum;
1492 #endif
1493
1494         if (!(v->flags & INMEMORY))
1495                 return v->vv.regoff;
1496
1497         return tempregnum;
1498 }
1499
1500
1501 /* codegen_reg_of_dst **********************************************************
1502
1503    This function determines a register, to which the result of an
1504    operation should go, when it is ultimatively intended to store the
1505    result in iptr->dst.var.  If dst.var is assigned to an actual
1506    register, this register will be returned.  Otherwise (when it is
1507    spilled) this function returns tempregnum.  If not already done,
1508    regoff and flags are set in the stack location.
1509        
1510 *******************************************************************************/
1511
1512 s4 codegen_reg_of_dst(jitdata *jd, instruction *iptr, s4 tempregnum)
1513 {
1514         return codegen_reg_of_var(iptr->opc, VAROP(iptr->dst), tempregnum);
1515 }
1516
1517
1518 /* codegen_emit_phi_moves ****************************************************
1519
1520    Emits phi moves at the end of the basicblock.
1521
1522 *******************************************************************************/
1523
1524 #if defined(ENABLE_SSA)
1525 void codegen_emit_phi_moves(jitdata *jd, basicblock *bptr)
1526 {
1527         int lt_d,lt_s,i;
1528         lsradata *ls;
1529         codegendata *cd;
1530         varinfo *s, *d;
1531         instruction tmp_i;
1532
1533         cd = jd->cd;
1534         ls = jd->ls;
1535
1536         MCODECHECK(512);
1537
1538         /* Moves from phi functions with highest indices have to be */
1539         /* inserted first, since this is the order as is used for   */
1540         /* conflict resolution */
1541
1542         for(i = ls->num_phi_moves[bptr->nr] - 1; i >= 0 ; i--) {
1543                 lt_d = ls->phi_moves[bptr->nr][i][0];
1544                 lt_s = ls->phi_moves[bptr->nr][i][1];
1545 #if defined(SSA_DEBUG_VERBOSE)
1546                 if (compileverbose)
1547                         printf("BB %3i Move %3i <- %3i ", bptr->nr, lt_d, lt_s);
1548 #endif
1549                 if (lt_s == UNUSED) {
1550 #if defined(SSA_DEBUG_VERBOSE)
1551                 if (compileverbose)
1552                         printf(" ... not processed \n");
1553 #endif
1554                         continue;
1555                 }
1556                         
1557                 d = VAR(ls->lifetime[lt_d].v_index);
1558                 s = VAR(ls->lifetime[lt_s].v_index);
1559                 
1560
1561                 if (d->type == -1) {
1562 #if defined(SSA_DEBUG_VERBOSE)
1563                         if (compileverbose)
1564                                 printf("...returning - phi lifetimes where joined\n");
1565 #endif
1566                         return;
1567                 }
1568
1569                 if (s->type == -1) {
1570 #if defined(SSA_DEBUG_VERBOSE)
1571                         if (compileverbose)
1572                                 printf("...returning - phi lifetimes where joined\n");
1573 #endif
1574                         return;
1575                 }
1576
1577                 tmp_i.opc = 0;
1578                 tmp_i.s1.varindex = ls->lifetime[lt_s].v_index;
1579                 tmp_i.dst.varindex = ls->lifetime[lt_d].v_index;
1580                 emit_copy(jd, &tmp_i);
1581
1582 #if defined(SSA_DEBUG_VERBOSE)
1583                 if (compileverbose) {
1584                         if (IS_INMEMORY(d->flags) && IS_INMEMORY(s->flags)) {
1585                                 /* mem -> mem */
1586                                 printf("M%3i <- M%3i",d->vv.regoff,s->vv.regoff);
1587                         }
1588                         else if (IS_INMEMORY(s->flags)) {
1589                                 /* mem -> reg */
1590                                 printf("R%3i <- M%3i",d->vv.regoff,s->vv.regoff);
1591                         }
1592                         else if (IS_INMEMORY(d->flags)) {
1593                                 /* reg -> mem */
1594                                 printf("M%3i <- R%3i",d->vv.regoff,s->vv.regoff);
1595                         }
1596                         else {
1597                                 /* reg -> reg */
1598                                 printf("R%3i <- R%3i",d->vv.regoff,s->vv.regoff);
1599                         }
1600                         printf("\n");
1601                 }
1602 #endif /* defined(SSA_DEBUG_VERBOSE) */
1603         }
1604 }
1605 #endif /* defined(ENABLE_SSA) */
1606
1607
1608
1609 /*
1610  * These are local overrides for various environment variables in Emacs.
1611  * Please do not remove this and leave it at the end of the file, where
1612  * Emacs will automagically detect them.
1613  * ---------------------------------------------------------------------
1614  * Local variables:
1615  * mode: c
1616  * indent-tabs-mode: t
1617  * c-basic-offset: 4
1618  * tab-width: 4
1619  * End:
1620  * vim:noexpandtab:sw=4:ts=4:
1621  */