* Removed all Id tags.
[cacao.git] / src / vm / jit / i386 / asmpart.S
1 /* src/vm/jit/i386/asmpart.S - Java-C interface functions for i386
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 */
26
27
28 #include "config.h"
29
30 #include "md-asm.h"
31
32 #include "vm/jit/i386/arch.h"
33 #include "vm/jit/i386/md-abi.h"
34
35 #include "vm/jit/abi-asm.h"
36 #include "vm/jit/methodheader.h"
37
38
39         .text
40
41
42 /* export functions ***********************************************************/
43
44         .globl asm_md_init
45
46         .globl asm_vm_call_method
47         .globl asm_vm_call_method_int
48         .globl asm_vm_call_method_long
49         .globl asm_vm_call_method_float
50         .globl asm_vm_call_method_double
51         .globl asm_vm_call_method_exception_handler
52         .globl asm_vm_call_method_end
53
54         .globl asm_call_jit_compiler
55         .globl asm_handle_nat_exception
56         .globl asm_handle_exception
57
58         .globl asm_abstractmethoderror
59
60         .globl asm_patcher_wrapper
61
62 #if defined(ENABLE_REPLACEMENT)
63         .globl asm_replacement_out
64         .globl asm_replacement_in
65 #endif
66
67         .globl asm_builtin_f2i
68         .globl asm_builtin_f2l
69         .globl asm_builtin_d2i
70         .globl asm_builtin_d2l
71
72         .globl asm_compare_and_swap
73         .globl asm_memory_barrier
74
75         .globl asm_get_cycle_count
76
77
78 /* asm_md_init *****************************************************************
79
80    Initialize machine dependent stuff.
81
82    See: http://www.srware.com/linux_numerics.txt
83
84    This puts the X86 FPU in 64-bit precision mode.  The default under
85    Linux is to use 80-bit mode, which produces subtle differences from
86    FreeBSD and other systems, eg, (int)(1000*atof("0.3")) is 300 in
87    64-bit mode, 299 in 80-bit mode.
88
89    Fixes: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=350729
90
91 *******************************************************************************/
92
93 asm_md_init:
94         sub     $4,sp                       /* allocate space for the FPU state   */
95         fnstcw  (sp)                        /* get the FPU state                  */
96         mov     (sp),%eax
97         and     $0xfcff,%ax                 /* remove the extended mode flag      */
98         or      $0x0200,%ax                 /* put the double mode flag           */
99         mov     %eax,(sp)                   /* store new FPU state                */
100         fldcw   (sp)                        /* setup new FPU state                */
101         add     $4,sp
102         ret
103
104
105 /********************* function asm_calljavafunction ***************************
106 *                                                                              *
107 *   This function calls a Java-method (which possibly needs compilation)       *
108 *   with up to 4 address parameters.                                           *
109 *                                                                              *
110 *   This functions calls the JIT-compiler which eventually translates the      *
111 *   method into machine code.                                                  *
112 *                                                                              *
113 *   C-prototype:                                                               *
114 *    javaobject_header *asm_vm_call_method(methodinfo *m,                      *
115 *         u4 count, u4 size, void *callblock);                                 *
116 *                                                                              *
117 *******************************************************************************/
118
119         .align  8
120
121         .long   0                           /* catch type all                     */
122         .long   0                           /* handler pc                         */
123         .long   0                           /* end pc                             */
124         .long   0                           /* start pc                           */
125         .long   1                           /* extable size                       */
126         .long   0                           /* line number table start            */
127         .long   0                           /* line number table size             */
128         .long   0                           /* fltsave                            */
129         .long   0                           /* intsave                            */
130         .long   0                           /* isleaf                             */
131         .long   0                           /* IsSync                             */
132         .long   0                           /* frame size                         */
133         .long   0                           /* codeinfo pointer                   */
134
135 asm_vm_call_method:
136 asm_vm_call_method_int:
137 asm_vm_call_method_long:
138 asm_vm_call_method_float:
139 asm_vm_call_method_double:
140         push    bp
141         mov     sp,bp                       /* save stackptr                      */
142         sub     $(4*4),sp                   /* create stackframe                  */
143         and     $0xfffffff0,sp              /* align stack to 16-byte             */
144
145         mov     t0,0*4(sp)                  /* save registers                     */
146         mov     s1,1*4(sp)
147         mov     s2,2*4(sp)
148
149         mov     sp,s1                       /* save stack pointer                 */
150
151         mov     3*4(bp),t0                  /* address of data structure          */
152         mov     4*4(bp),itmp1               /* number of stack arguments          */
153
154         cmp     $0,itmp1
155         je      L_asm_vm_call_method_stack_copy_done
156
157         mov     itmp1,itmp2
158         add     $1,itmp2                    /* keep stack 16-byte aligned         */
159         and     $0xfffffffe,itmp2
160         shl     $3,itmp2                    /* calculate stack size               */
161         sub     itmp2,sp                    /* create stack frame                 */
162         mov     sp,itmp2                    /* temporary stack pointer            */
163
164 L_asm_vm_call_method_stack_copy_loop:
165         mov     0(t0),itmp3                 /* load argument                      */
166         mov     itmp3,0(itmp2)              /* store argument on stack            */
167         mov     4(t0),itmp3
168         mov     itmp3,4(itmp2)
169
170         sub     $1,itmp1                    /* subtract 1 argument                */
171         add     $8,t0                       /* set address of next argument       */
172         add     $8,itmp2                    /* increase SP                        */
173
174         cmp     $0,itmp1
175         jg      L_asm_vm_call_method_stack_copy_loop
176
177 L_asm_vm_call_method_stack_copy_done:
178         lea     (2*4-256)(bp),mptr          /* We subtract 256 to force the next  */
179                                             /* move instruction to have a 32-bit  */
180                                             /* offset.                            */
181
182         mov     (0*4+256)(mptr),itmp3       /* method call as in Java             */
183         call    *itmp3                      /* call JIT compiler                  */
184
185 L_asm_vm_call_method_return:
186         mov     s1,sp                       /* restore stackpointer               */
187
188         mov     0*4(sp),t0                  /* restore registers                  */
189         mov     1*4(sp),s1
190         mov     2*4(sp),s2
191
192         leave
193         ret
194
195 asm_vm_call_method_exception_handler:
196         push    xptr                        /* pass exception pointer             */
197         call    builtin_throw_exception
198         add     $4,sp
199 asm_vm_call_method_end:
200         jmp     L_asm_vm_call_method_return
201
202
203 /* asm_call_jit_compiler *******************************************************
204
205    Invokes the compiler for untranslated JavaVM methods.
206
207    Register R0 contains a pointer to the method info structure (prepared
208    by createcompilerstub). Using the return address in R26 and the
209    offset in the LDA instruction or using the value in methodptr R28 the
210    patching address for storing the method address can be computed:
211
212    Method address was either loaded using
213
214    i386_mov_imm_reg(a, REG_ITMP2)                ; invokestatic/special
215    i386_call_reg(REG_ITMP2)
216
217    or
218
219    i386_mov_membase_reg(REG_SP, 0, REG_ITMP1)    ; invokevirtual/interface
220    i386_mov_membase_reg(REG_ITMP1, OFFSET(, vftbl), REG_ITMP2)
221    i386_mov_membase_reg(REG_ITMP2, OFFSET(vftbl, table[0]) + \
222        sizeof(methodptr) * m->vftblindex, REG_ITMP1)
223    i386_call_reg(REG_ITMP1)
224
225    In the static case the method pointer can be computed using the
226    return address and the lda function following the jmp instruction.
227
228 *******************************************************************************/
229
230 asm_call_jit_compiler:
231 L_asm_call_jit_compiler:                /* required for PIC code              */
232         sub     $(4*4),sp                   /* keep stack 16-byte aligned         */
233
234         mov     itmp1,0*4(sp)               /* pass methodinfo pointer            */
235         mov     mptr,1*4(sp)                /* pass method pointer                */
236         mov     sp,itmp2                    /* pass java sp                       */
237         add     $((1+4)*4),itmp2
238         mov     itmp2,2*4(sp)
239         mov     4*4(sp),itmp3               /* pass java ra                       */
240         mov     itmp3,3*4(sp)
241         call    jit_asm_compile
242
243         add     $(4*4),sp                   /* remove stack frame                 */
244
245         test    v0,v0                       /* check for exception                */
246         je      L_asm_call_jit_compiler_exception
247
248         jmp             *v0                         /* ...and now call the new method     */
249
250 L_asm_call_jit_compiler_exception:
251         call    exceptions_get_and_clear_exception
252                                             /* v0 == xptr                         */
253         pop     xpc                         /* get return address                 */
254         sub     $2,xpc                      /* faulting address is ra - 2         */
255         jmp     L_asm_handle_exception
256
257
258 /* asm_handle_exception ********************************************************
259 *                                                                              *
260 *   This function handles an exception. It does not use the usual calling      *
261 *   conventions. The exception pointer is passed in REG_ITMP1 and the          *
262 *   pc from the exception raising position is passed in REG_ITMP2. It searches *
263 *   the local exception table for a handler. If no one is found, it unwinds    *
264 *   stacks and continues searching the callers.                                *
265 *                                                                              *
266 *******************************************************************************/
267
268 asm_handle_nat_exception:
269         add     $4,sp                       /* clear return address of native stub*/
270                 
271 asm_handle_exception:
272 L_asm_handle_exception:                 /* required for PIC code              */
273         sub     $((ARG_CNT+TMP_CNT+3)*4),sp /* keep stack 16-byte aligned         */
274
275         SAVE_ARGUMENT_REGISTERS(0)          /* we save arg and temp registers in  */
276         SAVE_TEMPORARY_REGISTERS(ARG_CNT)   /* case this is a leaf method         */
277
278         mov     $((ARG_CNT+TMP_CNT+3)*4),itmp3 /* prepare a3 for handle_exception */
279         mov     $1,t0                       /* set maybe-leaf flag                */
280
281 L_asm_handle_exception_stack_loop:
282         sub     $(12*4),sp                  /* keep stack 16-byte aligned         */
283         mov     xptr,4*4(sp)                /* save exception pointer             */
284         mov     xpc,5*4(sp)                 /* save exception pc                  */
285         add     sp,itmp3                    /* calculate Java sp into a3...       */
286         add     $(12*4),itmp3
287         mov     itmp3,7*4(sp)               /* ...and save it                     */
288         mov     t0,8*4(sp)                  /* save maybe-leaf flag               */
289
290         mov     xpc,0*4(sp)                 /* pass exception pc                  */
291         call    codegen_get_pv_from_pc
292         mov     v0,6*4(sp)                  /* save data segment pointer          */
293
294         mov     4*4(sp),itmp3               /* pass exception pointer             */
295         mov     itmp3,0*4(sp)
296         mov     5*4(sp),itmp3               /* pass exception pc                  */
297         mov     itmp3,1*4(sp)
298         mov     v0,2*4(sp)                  /* pass data segment pointer          */
299         mov     7*4(sp),itmp3               /* pass Java stack pointer            */
300         mov     itmp3,3*4(sp)
301         call    exceptions_handle_exception
302
303         test    v0,v0
304         jz      L_asm_handle_exception_not_catched
305
306         mov     v0,xpc                      /* move handlerpc into xpc            */
307         mov     4*4(sp),xptr                /* restore exception pointer          */
308         mov     8*4(sp),t0                  /* get maybe-leaf flag                */
309         add     $(12*4),sp                  /* free stackframe                    */
310
311         test    t0,t0                       /* test for maybe-leaf flag           */
312         jz      L_asm_handle_exception_no_leaf
313
314         RESTORE_ARGUMENT_REGISTERS(0)       /* if this is a leaf method, we have  */
315         RESTORE_TEMPORARY_REGISTERS(ARG_CNT)/* to restore arg and temp registers  */
316
317         add     $((ARG_CNT+TMP_CNT+3)*4),sp /* remove maybe-leaf stackframe       */
318
319 L_asm_handle_exception_no_leaf:
320         jmp     *xpc                        /* jump to exception handler          */
321
322 L_asm_handle_exception_not_catched:
323         mov     4*4(sp),xptr                /* restore exception pointer          */
324         mov     6*4(sp),itmp3               /* restore data segment pointer       */
325         mov     8*4(sp),t0                  /* get maybe-leaf flag                */
326         add     $(12*4),sp                  /* free stackframe                    */
327
328         test    t0,t0
329         jz      L_asm_handle_exception_no_leaf_stack
330
331         add     $((ARG_CNT+TMP_CNT+3)*4),sp /* remove maybe-leaf stackframe       */
332         xor     t0,t0                       /* clear the maybe-leaf flag          */
333
334 L_asm_handle_exception_no_leaf_stack:
335         mov     FrameSize(itmp3),itmp2      /* get frame size                     */
336         add     sp,itmp2                    /* pointer to save area               */
337
338         push    xptr                        /* we are out of registers            */
339
340         mov     IntSave(itmp3),itmp1        /* itmp1 = saved int register count   */
341         test    itmp1,itmp1
342         je      noint
343
344         cmp     $1,itmp1
345         je      int1
346         cmp     $2,itmp1
347         je      int2
348
349         mov     -3*8(itmp2),s0
350 int2:   
351         mov     -2*8(itmp2),s1
352 int1:   
353         mov     -1*8(itmp2),s2
354
355         shl     $2,itmp1                    /* multiply by 4 bytes                */
356         sub     itmp1,itmp2
357                 
358 noint:
359 #if 0
360         mov     FltSave(itmp3),itmp1        /* itmp1 = saved flt register count   */
361         test    itmp1,itmp1
362         je      noflt
363
364         cmp     $1,itmp1
365         je      flt1
366         cmp     $2,itmp1
367         je      flt2
368         cmp     $3,itmp1
369         je      flt3
370                 
371         fldl    -4*8(itmp2)
372         fstp    %st(1)
373 flt3:
374         fldl    -3*8(itmp2)
375         fstp    %st(2)
376 flt2:
377         fldl    -2*8(itmp2)
378         fstp    %st(3)
379 flt1:
380         fldl    -1*8(itmp2)
381         fstp    %st(4)
382                 
383 noflt:
384 #endif
385         pop     xptr                        /* restore exception pointer          */
386         mov     FrameSize(itmp3),itmp2      /* get frame size                     */
387         add     itmp2,sp                    /* unwind stack                       */
388
389         pop     xpc                         /* the new xpc is return address      */
390         sub     $2,xpc                      /* subtract 2-bytes for call          */
391
392         xor     itmp3,itmp3                 /* prepare a3 for handle_exception    */
393
394         jmp     L_asm_handle_exception_stack_loop
395                 
396
397 /* asm_abstractmethoderror *****************************************************
398
399    Creates and throws an AbstractMethodError.
400
401 *******************************************************************************/
402
403 asm_abstractmethoderror:
404         sub     $(3*4),sp                   /* keep stack 16-byte aligned         */
405         mov     sp,itmp1                    /* pass java sp                       */
406         add     $((1+3)*4),itmp1
407         mov     itmp1,0*4(sp)
408         mov     3*4(sp),itmp2               /* pass exception address             */
409         sub     $2,itmp2
410         mov     itmp2,1*4(sp)
411         call    exceptions_asm_new_abstractmethoderror
412                                             /* exception pointer is return value  */
413         add     $(3*4),sp                   /* remove stack frame                 */
414
415         pop     xpc                         /* get exception address              */
416         sub     $2,xpc                      /* exception address is ra - 2        */
417         jmp     L_asm_handle_exception
418
419
420 /* asm_patcher_wrapper *********************************************************
421
422    XXX
423
424    Stack layout:
425      24   return address
426      20   REG_ITMP3
427      16   pointer to virtual java_objectheader
428      12   last byte of machine code (xmcode)
429       8   machine code (which is patched back later)
430       4   unresolved field reference
431       0   patcher function pointer to call
432
433 *******************************************************************************/
434
435 asm_patcher_wrapper:
436         sub     $((1+4+4)*4),sp             /* keep stack 16-byte aligned         */
437
438         mov     itmp1,(0+4)*4(sp)           /* save itmp1 and itmp2               */
439         mov     itmp2,(1+4)*4(sp)
440
441         mov     sp,itmp1                    /* pass SP of patcher stub            */
442         add     $((1+4+4)*4),itmp1
443         mov     itmp1,0*4(sp)
444         movl    $0,1*4(sp)                  /* pass PV (if NULL, use findmethod)  */
445         movl    $0,2*4(sp)                  /* pass RA (it's on the stack)        */
446         call    patcher_wrapper
447         mov     v0,itmp3                    /* save return value                  */
448
449         mov     (0+4)*4(sp),itmp1           /* restore itmp1 and itmp2            */
450         mov     (1+4)*4(sp),itmp2
451
452         test    itmp3,itmp3                 /* exception thrown?                  */
453         jne     L_asm_patcher_wrapper_exception
454
455         mov     (5+1+4+4)*4(sp),itmp3       /* restore itmp3                      */
456         add     $((6+1+4+4)*4),sp           /* remove stack frame, keep RA        */
457
458         ret                                 /* jump to new patched code           */
459
460 L_asm_patcher_wrapper_exception:
461         add     $((6+1+4+4)*4),sp           /* remove stack frame, keep RA        */
462         mov     itmp3,xptr                  /* get exception                      */
463         pop     xpc                         /* get and remove return address      */
464         jmp     L_asm_handle_exception
465
466 #if defined(ENABLE_REPLACEMENT)
467
468 /* asm_replacement_out *********************************************************
469
470    This code is jumped to from the replacement-out stubs that are executed
471    when a thread reaches an activated replacement point.
472
473    The purpose of asm_replacement_out is to read out the parts of the
474    execution state that cannot be accessed from C code, store this state,
475    and then call the C function replace_me.
476
477    Stack layout:
478       4                 start of stack inside method to replace
479       0   rplpoint *    info on the replacement point that was reached
480
481 *******************************************************************************/
482
483 /* some room to accomodate changes of the stack frame size during replacement */
484         /* XXX we should find a cleaner solution here */
485 #define REPLACEMENT_ROOM  512
486
487 asm_replacement_out:
488     /* create stack frame */
489         sub     $(sizeexecutionstate + REPLACEMENT_ROOM),sp
490
491         /* save registers in execution state */
492         mov     %eax,(EAX*4+offes_intregs)(sp)
493         mov     %ebx,(EBX*4+offes_intregs)(sp)
494         mov     %ecx,(ECX*4+offes_intregs)(sp)
495         mov     %edx,(EDX*4+offes_intregs)(sp)
496         mov     %esi,(ESI*4+offes_intregs)(sp)
497         mov     %edi,(EDI*4+offes_intregs)(sp)
498         mov     %ebp,(EBP*4+offes_intregs)(sp)
499         movl    $0  ,(ESP*4+offes_intregs)(sp) /* not used */
500
501         /* calculate sp of method */
502         mov     sp,itmp1
503         add     $(sizeexecutionstate + REPLACEMENT_ROOM + 4),itmp1
504         mov     itmp1,(offes_sp)(sp)
505
506         /* pv must be looked up via AVL tree */
507         movl    $0,(offes_pv)(sp)
508
509         /* call replace_me */
510         mov     -4(itmp1),itmp1             /* rplpoint *                         */
511     push    sp                          /* arg1: execution state              */
512     push    itmp1                       /* arg0: replacement point            */
513     call    replace_me                  /* call C function replace_me         */
514
515
516 /* asm_replacement_in **********************************************************
517
518    This code writes the given execution state and jumps to the replacement
519    code.
520
521    This function never returns!
522
523    C prototype:
524       void asm_replacement_in(executionstate *es, replace_safestack_t *st);
525
526 *******************************************************************************/
527
528 asm_replacement_in:
529         /* get arguments */
530         mov     8(sp),%esi                  /* replace_safestack_t *st            */
531         mov     4(sp),%ebp                  /* executionstate *es == safe stack   */
532
533         /* switch to the safe stack and build a stack frame */
534         mov     %ebp,sp
535         sub             $(1*4),sp
536
537         /* call replace_build_execution_state(st) */
538         mov             %esi,(0*4)(sp)
539         call    replace_build_execution_state
540
541         /* set new sp */
542         mov     (offes_sp)(%ebp),sp
543
544         /* push address of new code */
545         push    (offes_pc)(%ebp)
546
547         /* allocate an executionstate_t on the stack */
548         sub             $(sizeexecutionstate),sp
549
550         /* call replace_free_safestack(st,& of allocated executionstate_t) */
551         push    sp   /* tmpes */
552         push    %esi /* st    */
553         call    replace_free_safestack
554         add     $(2*4),sp
555
556         /* copy registers from execution state */
557         mov     (EAX*4+offes_intregs)(sp),%eax
558         mov     (EBX*4+offes_intregs)(sp),%ebx
559         mov     (ECX*4+offes_intregs)(sp),%ecx
560         mov     (EDX*4+offes_intregs)(sp),%edx
561         mov     (ESI*4+offes_intregs)(sp),%esi
562         mov     (EDI*4+offes_intregs)(sp),%edi
563         mov     (EBP*4+offes_intregs)(sp),%ebp
564
565         /* pop the execution state off the stack */
566         add             $(sizeexecutionstate),sp
567
568         /* jump to new code, hold your thumbs! ;) */
569         ret
570
571 #endif /* defined(ENABLE_REPLACEMENT) */
572
573
574 /************************ function asm_builtin_x2x *****************************
575 *                                                                              *
576 *   Wrapper functions for corner cases                                         *
577 *                                                                              *
578 *******************************************************************************/
579
580 asm_builtin_f2i:
581         sub     $(3*4),%esp
582         fsts    (%esp)
583         call    builtin_f2i
584         add     $(3*4),%esp
585         ret
586
587 asm_builtin_d2i:
588         sub     $(3*4),%esp
589         fstl    (%esp)
590         call    builtin_d2i
591         add     $(3*4),%esp
592         ret
593
594 asm_builtin_f2l:
595         sub     $(3*4),%esp
596         fsts    (%esp)
597         call    builtin_f2l
598         add     $(3*4),%esp
599         ret
600
601 asm_builtin_d2l:
602         sub     $(3*4),%esp
603         fstl    (%esp)
604         call    builtin_d2l
605         add     $(3*4),%esp
606         ret
607
608
609 /* asm_compare_and_swap ********************************************************
610
611    Does an atomic compare and swap.  Required for the lock
612    implementation.
613
614    Atomically do the following: Check if the location still contains
615    `oldval`. If so, replace it by `newval` and return `oldval`.
616
617    RETURN VALUE:
618        the old value at *p
619
620    long compare_and_swap(volatile long *p, long oldval, long newval);
621
622 *******************************************************************************/
623
624 asm_compare_and_swap:
625         mov     1*4(sp),%ecx            /* load p into a register                 */
626         mov     2*4(sp),%eax            /* load oldval into return register       */
627         mov     3*4(sp),%edx            /* load newval into a register            */
628         lock; cmpxchgl %edx,0(%ecx)
629         ret
630
631
632 /* asm_memory_barrier **********************************************************
633
634    A memory barrier for the Java Memory Model.
635
636 *******************************************************************************/
637
638 asm_memory_barrier:
639         lock; add $0,0(sp)
640         ret
641
642                 
643 /* asm_get_cycle_count *********************************************************
644
645    Get the current time-stamp counter from the CPU.
646
647 *******************************************************************************/
648
649 asm_get_cycle_count:
650         rdtsc
651         ret
652
653
654 /* disable exec-stacks ********************************************************/
655
656 #if defined(__linux__) && defined(__ELF__)
657         .section .note.GNU-stack,"",%progbits
658 #endif
659
660
661 /*
662  * These are local overrides for various environment variables in Emacs.
663  * Please do not remove this and leave it at the end of the file, where
664  * Emacs will automagically detect them.
665  * ---------------------------------------------------------------------
666  * Local variables:
667  * mode: asm
668  * indent-tabs-mode: t
669  * c-basic-offset: 4
670  * tab-width: 4
671  * End:
672  * vim:noexpandtab:sw=4:ts=4:
673  */