1 /* src/vm/jit/x86_64/asmpart.S - Java-C interface functions for x86_64
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
8 This file is part of CACAO.
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.
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.
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
25 $Id: asmpart.S 7217 2007-01-16 12:52:48Z twisti $
32 #include "vm/jit/x86_64/arch.h"
33 #include "vm/jit/x86_64/md-abi.h"
34 #include "vm/jit/x86_64/md-asm.h"
35 #include "vm/jit/x86_64/offsets.h"
37 #include "vm/jit/abi-asm.h"
38 #include "vm/jit/methodheader.h"
44 /* export functions ***********************************************************/
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
53 .globl asm_call_jit_compiler
55 .globl asm_handle_exception
56 .globl asm_handle_nat_exception
58 .globl asm_abstractmethoderror
60 .globl asm_patcher_wrapper
62 #if defined(ENABLE_REPLACEMENT)
63 .globl asm_replacement_out
64 .globl asm_replacement_in
67 .globl asm_builtin_f2i
68 .globl asm_builtin_f2l
69 .globl asm_builtin_d2i
70 .globl asm_builtin_d2l
72 .globl asm_criticalsections
73 .globl asm_getclassvalues_atomic
76 /********************* function asm_calljavafunction ***************************
78 * This function calls a Java-method (which possibly needs compilation) *
79 * with up to 4 address parameters. *
81 * This functions calls the JIT-compiler which eventually translates the *
82 * method into machine code. *
85 * javaobject_header *asm_calljavamethod (methodinfo *m, *
86 * void *arg1, void *arg2, void *arg3, void *arg4); *
88 *******************************************************************************/
92 .quad 0 /* catch type all */
93 .quad 0 /* handler pc */
95 .quad 0 /* start pc */
96 .long 1 /* extable size */
97 .long 0 /* ALIGNMENT PADDING */
98 .quad 0 /* line number table start */
99 .quad 0 /* line number table size */
100 .long 0 /* ALIGNMENT PADDING */
101 .long 0 /* fltsave */
102 .long 0 /* intsave */
105 .long 0 /* frame size */
106 .quad 0 /* codeinfo pointer */
109 asm_vm_call_method_int:
110 asm_vm_call_method_long:
111 asm_vm_call_method_float:
112 asm_vm_call_method_double:
113 sub $(7*8),sp /* keep stack 16-byte aligned */
114 mov %rbx,0*8(sp) /* %rbx is not a callee saved in cacao*/
121 mov a0,itmp1 /* move method pointer for compiler */
122 xor %rbp,%rbp /* set argument stack frame to zero */
124 test a1,a1 /* maybe we have no args... */
127 mov a1,itmp3 /* arg count */
128 mov a2,itmp2 /* pointer to arg block */
130 mov itmp2,%r14 /* save argument block pointer */
131 mov itmp3,%r15 /* save argument count */
133 sub $sizevmarg,itmp2 /* initialize pointer (smaller code) */
134 add $1,itmp3 /* initialize argument count */
135 xor %r12,%r12 /* initialize integer argument counter*/
136 xor %r13,%r13 /* initialize float argument counter */
139 add $sizevmarg,itmp2 /* goto next argument block */
140 dec itmp3 /* argument count - 1 */
141 jz L_register_copy_done
142 andb $0x02,offvmargtype(itmp2) /* is this a float/double type? */
143 jnz L_register_handle_float /* yes, handle it */
145 cmp $INT_ARG_CNT,%r12 /* are we out of integer argument */
146 je L_register_copy /* register? yes, next loop */
148 lea jumptable_integer(%rip),%rbp
149 mov 0(%rbp,%r12,8),%rbx
150 inc %r12 /* integer argument counter + 1 */
153 L_register_handle_float:
154 cmp $FLT_ARG_CNT,%r13 /* are we out of float argument */
155 je L_register_copy /* register? yes, next loop */
157 lea jumptable_float(%rip),%rbp
158 mov 0(%rbp,%r13,8),%rbx
159 inc %r13 /* float argument counter + 1 */
162 L_register_copy_done:
163 mov %r15,%rbp /* calculate remaining arguments */
164 sub %r12,%rbp /* - integer arguments in registers */
165 sub %r13,%rbp /* - float arguments in registers */
166 jle L_copy_done /* are all assigned to registers? */
168 and $0xfffffffffffffffe,%rbp /* keep stack 16-byte aligned */
169 shl $3,%rbp /* calculate stack size */
170 sub %rbp,sp /* stack frame for arguments */
171 mov sp,%rbx /* use %rbx as temp sp */
173 sub $sizevmarg,%r14 /* initialize pointer (smaller code) */
174 add $1,%r15 /* initialize argument count */
177 add $sizevmarg,%r14 /* goto next argument block */
178 dec %r15 /* are there any arguments left? */
179 jz L_copy_done /* no test needed after dec */
181 andb $0x02,offvmargtype(%r14) /* is this a float/double type? */
182 jnz L_stack_handle_float
183 dec %r12 /* arguments assigned to registers */
184 jge L_stack_copy_loop
187 L_stack_handle_float:
188 dec %r13 /* arguments assigned to registers */
189 jge L_stack_copy_loop
192 mov offvmargdata(%r14),itmp3 /* copy s8 argument onto stack */
194 add $8,%rbx /* increase sp to next argument */
195 jmp L_stack_copy_loop
198 /* itmp1 still contains method pointer*/
199 lea L_asm_call_jit_compiler(%rip),mptr
200 mov sp,itmp3 /* calculate the old stack pointer */
203 lea (6*8-256)(itmp3),mptr /* We subtract 256 to force the next */
204 /* move instruction to have a 32-bit */
207 mov (0*8+256)(mptr),itmp3 /* method call as in Java */
208 call *itmp3 /* call JIT compiler */
210 add bp,sp /* remove argument stack frame if any */
212 L_asm_vm_call_method_return:
213 mov 0*8(sp),%rbx /* restore callee saved registers */
219 add $(7*8),sp /* free stack space */
222 asm_vm_call_method_exception_handler:
223 mov xptr,a0 /* pass exception pointer */
224 call builtin_throw_exception@PLT
225 jmp L_asm_vm_call_method_return
237 mov offvmargdata(itmp2),a0
240 mov offvmargdata(itmp2),a1
243 mov offvmargdata(itmp2),a2
246 mov offvmargdata(itmp2),a3
249 mov offvmargdata(itmp2),a4
252 mov offvmargdata(itmp2),a5
267 movq offvmargdata(itmp2),fa0
270 movq offvmargdata(itmp2),fa1
273 movq offvmargdata(itmp2),fa2
276 movq offvmargdata(itmp2),fa3
279 movq offvmargdata(itmp2),fa4
282 movq offvmargdata(itmp2),fa5
285 movq offvmargdata(itmp2),fa6
288 movq offvmargdata(itmp2),fa7
292 /****************** function asm_call_jit_compiler *****************************
294 * invokes the compiler for untranslated JavaVM methods. *
296 * Register R0 contains a pointer to the method info structure (prepared *
297 * by createcompilerstub). Using the return address in R26 and the *
298 * offset in the LDA instruction or using the value in methodptr R28 the *
299 * patching address for storing the method address can be computed: *
301 * method address was either loaded using *
303 * i386_mov_imm_reg(a, REG_ITMP2) ; invokestatic/special *
304 * i386_call_reg(REG_ITMP2) *
308 * i386_mov_membase_reg(REG_SP, 0, REG_ITMP2) ; invokevirtual/interface *
309 * i386_mov_membase_reg(REG_ITMP2, OFFSET(, vftbl), REG_ITMP3) *
310 * i386_mov_membase_reg(REG_ITMP3, OFFSET(vftbl, table[0]) + \ *
311 * sizeof(methodptr) * m->vftblindex, REG_ITMP1) *
312 * i386_call_reg(REG_ITMP1) *
314 * in the static case the method pointer can be computed using the *
315 * return address and the lda function following the jmp instruction *
317 *******************************************************************************/
319 asm_call_jit_compiler:
320 L_asm_call_jit_compiler: /* required for PIC code */
321 sub $(ARG_CNT+1)*8,sp /* +1: keep stack 16-byte aligned */
323 SAVE_ARGUMENT_REGISTERS(0)
325 mov itmp1,a0 /* pass methodinfo pointer */
326 mov mptr,a1 /* pass method pointer */
327 mov sp,a2 /* pass java sp */
328 add $(1+ARG_CNT+1)*8,a2
329 mov (ARG_CNT+1)*8(sp),a3 /* pass ra to java function */
330 call jit_asm_compile@PLT
332 RESTORE_ARGUMENT_REGISTERS(0)
334 add $(ARG_CNT+1)*8,sp /* remove stack frame */
336 test v0,v0 /* check for exception */
337 je L_asm_call_jit_compiler_exception
339 jmp *v0 /* ...and now call the new method */
341 L_asm_call_jit_compiler_exception:
342 call exceptions_get_and_clear_exception@PLT
343 pop xpc /* delete return address */
344 sub $3,xpc /* faulting address is ra - 3 */
345 jmp L_asm_handle_exception
348 /* asm_handle_exception ********************************************************
350 * This function handles an exception. It does not use the usual calling *
351 * conventions. The exception pointer is passed in REG_ITMP1 and the *
352 * pc from the exception raising position is passed in REG_ITMP2. It searches *
353 * the local exception table for a handler. If no one is found, it unwinds *
354 * stacks and continues searching the callers. *
356 *******************************************************************************/
358 asm_handle_nat_exception:
359 add $8,sp /* clear return address of native stub*/
361 asm_handle_exception:
362 L_asm_handle_exception: /* required for PIC code */
363 sub $((ARG_CNT+TMP_CNT)*8),sp /* create maybe-leaf stackframe */
365 SAVE_ARGUMENT_REGISTERS(0) /* we save arg and temp registers in */
366 SAVE_TEMPORARY_REGISTERS(ARG_CNT) /* case this is a leaf method */
368 mov $((ARG_CNT+TMP_CNT)*8),a3 /* prepare a3 for handle_exception */
369 mov $1,t0 /* set maybe-leaf flag */
371 L_asm_handle_exception_stack_loop:
373 mov xptr,0*8(sp) /* save exception pointer */
374 mov xpc,1*8(sp) /* save exception pc */
375 add sp,a3 /* calculate Java sp into a3... */
377 mov a3,3*8(sp) /* ...and save it */
378 mov t0,4*8(sp) /* save maybe-leaf flag */
380 mov xpc,a0 /* exception pc */
381 call codegen_get_pv_from_pc@PLT
382 mov v0,2*8(sp) /* save data segment pointer */
384 mov 0*8(sp),a0 /* pass exception pointer */
385 mov 1*8(sp),a1 /* pass exception pc */
386 mov v0,a2 /* pass data segment pointer */
387 mov 3*8(sp),a3 /* pass Java stack pointer */
388 call exceptions_handle_exception@PLT
391 jz L_asm_handle_exception_not_catched
393 mov v0,xpc /* move handlerpc into xpc */
394 mov 0*8(sp),xptr /* restore exception pointer */
395 mov 4*8(sp),t0 /* get maybe-leaf flag */
396 add $(6*8),sp /* free stack frame */
398 test t0,t0 /* test for maybe-leaf flag */
399 jz L_asm_handle_exception_no_leaf
401 RESTORE_ARGUMENT_REGISTERS(0) /* if this is a leaf method, we have */
402 RESTORE_TEMPORARY_REGISTERS(ARG_CNT)/* to restore arg and temp registers */
404 add $((ARG_CNT+TMP_CNT)*8),sp /* remove maybe-leaf stackframe */
406 L_asm_handle_exception_no_leaf:
407 jmp *xpc /* jump to the handler */
409 L_asm_handle_exception_not_catched:
410 mov 0*8(sp),xptr /* restore exception pointer */
411 mov 2*8(sp),itmp3 /* restore data segment pointer */
412 mov 4*8(sp),t0 /* get maybe-leaf flag */
416 jz L_asm_handle_exception_no_leaf_stack
418 add $((ARG_CNT+TMP_CNT)*8),sp /* remove maybe-leaf stackframe */
419 xor t0,t0 /* clear the isleaf flags */
421 L_asm_handle_exception_no_leaf_stack:
422 mov FrameSize(itmp3),itmp2l /* get frame size */
423 add sp,itmp2 /* pointer to save area */
425 mov IntSave(itmp3),a0l /* a0l = saved int register count */
448 shl $3,a0l /* multiply by 8 bytes */
453 mov FltSave(itmp3),a0l /* a0l = saved flt register count */
466 movq -5*8(itmp2),%xmm11
468 movq -4*8(itmp2),%xmm12
470 movq -3*8(itmp2),%xmm13
472 movq -2*8(itmp2),%xmm14
474 movq -1*8(itmp2),%xmm15
478 mov FrameSize(itmp3),itmp2l /* get frame size */
479 add itmp2,sp /* unwind stack */
481 /* exception pointer is still set */
482 pop xpc /* the new xpc is return address */
483 sub $3,xpc /* subtract 3 bytes for call */
485 xor a3,a3 /* prepare a3 for handle_exception */
487 jmp L_asm_handle_exception_stack_loop
490 /* asm_abstractmethoderror *****************************************************
492 Creates and throws an AbstractMethodError.
494 *******************************************************************************/
496 asm_abstractmethoderror:
497 mov sp,a0 /* pass java sp */
499 mov 0*8(sp),a1 /* pass exception address */
501 call exceptions_asm_new_abstractmethoderror@PLT
502 /* exception pointer is return value */
503 pop xpc /* get exception address */
504 sub $3,xpc /* exception address is ra - 3 */
505 jmp L_asm_handle_exception
508 /* asm_patcher_wrapper *********************************************************
514 32 pointer to virtual java_objectheader
515 24 machine code (which is patched back later)
516 16 unresolved class/method/field reference
517 8 data segment displacement from load instructions
518 0 pointer to patcher function
521 *******************************************************************************/
524 push bp /* save base pointer */
525 mov sp,bp /* move actual sp to bp */
526 sub $(3+ARG_CNT+TMP_CNT)*8,sp
527 and $0xfffffffffffffff0,sp /* align sp to 16-byte (this is for */
528 /* leaf functions) */
530 SAVE_ARGUMENT_REGISTERS(3)
531 SAVE_TEMPORARY_REGISTERS(3+ARG_CNT)
533 mov itmp1,0*8(sp) /* save itmp1 and itmp2 */
534 mov itmp2,1*8(sp) /* can be used by some instructions */
536 mov bp,a0 /* pass SP of patcher stub */
538 mov $0,a1 /* pass PV (if NULL, use findmethod) */
539 mov $0,a2 /* pass RA (it's on the stack) */
540 call patcher_wrapper@PLT
541 mov v0,2*8(sp) /* save return value */
543 RESTORE_ARGUMENT_REGISTERS(3)
544 RESTORE_TEMPORARY_REGISTERS(3+ARG_CNT)
546 mov 0*8(sp),itmp1 /* restore itmp1 and itmp2 */
547 mov 1*8(sp),itmp2 /* can be used by some instructions */
548 mov 2*8(sp),itmp3 /* restore return value */
550 mov bp,sp /* restore original sp */
551 pop bp /* restore bp */
552 add $(5*8),sp /* remove patcher stackframe, keep RA */
554 test itmp3,itmp3 /* exception thrown? */
555 jne L_asm_patcher_wrapper_exception
556 ret /* call new patched code */
558 L_asm_patcher_wrapper_exception:
559 mov itmp3,xptr /* get exception */
560 pop xpc /* get and remove return address */
561 jmp L_asm_handle_exception
563 #if defined(ENABLE_REPLACEMENT)
565 /* asm_replacement_out *********************************************************
567 This code is jumped to from the replacement-out stubs that are executed
568 when a thread reaches an activated replacement point.
570 The purpose of asm_replacement_out is to read out the parts of the
571 execution state that cannot be accessed from C code, store this state,
572 and then call the C function replace_me.
575 8 start of stack inside method to replace
576 0 rplpoint * info on the replacement point that was reached
578 *******************************************************************************/
580 /* some room to accomodate changes of the stack frame size during replacement */
581 /* XXX we should find a cleaner solution here */
582 #define REPLACEMENT_ROOM 512
585 /* create stack frame */
586 sub $(sizeexecutionstate + REPLACEMENT_ROOM),sp
588 /* save registers in execution state */
589 mov %rax,(RAX*8+offes_intregs)(sp)
590 mov %rbx,(RBX*8+offes_intregs)(sp)
591 mov %rcx,(RCX*8+offes_intregs)(sp)
592 mov %rdx,(RDX*8+offes_intregs)(sp)
593 mov %rsi,(RSI*8+offes_intregs)(sp)
594 mov %rdi,(RDI*8+offes_intregs)(sp)
595 mov %rbp,(RBP*8+offes_intregs)(sp)
596 movq $0 ,(RSP*8+offes_intregs)(sp) /* not used */
597 mov %r8 ,(R8 *8+offes_intregs)(sp)
598 mov %r9 ,(R9 *8+offes_intregs)(sp)
599 mov %r10,(R10*8+offes_intregs)(sp)
600 mov %r11,(R11*8+offes_intregs)(sp)
601 mov %r12,(R12*8+offes_intregs)(sp)
602 mov %r13,(R13*8+offes_intregs)(sp)
603 mov %r14,(R14*8+offes_intregs)(sp)
604 mov %r15,(R15*8+offes_intregs)(sp)
606 movq %xmm0 ,(XMM0 *8+offes_fltregs)(sp)
607 movq %xmm1 ,(XMM1 *8+offes_fltregs)(sp)
608 movq %xmm2 ,(XMM2 *8+offes_fltregs)(sp)
609 movq %xmm3 ,(XMM3 *8+offes_fltregs)(sp)
610 movq %xmm4 ,(XMM4 *8+offes_fltregs)(sp)
611 movq %xmm5 ,(XMM5 *8+offes_fltregs)(sp)
612 movq %xmm6 ,(XMM6 *8+offes_fltregs)(sp)
613 movq %xmm7 ,(XMM7 *8+offes_fltregs)(sp)
614 movq %xmm8 ,(XMM8 *8+offes_fltregs)(sp)
615 movq %xmm9 ,(XMM9 *8+offes_fltregs)(sp)
616 movq %xmm10,(XMM10*8+offes_fltregs)(sp)
617 movq %xmm11,(XMM11*8+offes_fltregs)(sp)
618 movq %xmm12,(XMM12*8+offes_fltregs)(sp)
619 movq %xmm13,(XMM13*8+offes_fltregs)(sp)
620 movq %xmm14,(XMM14*8+offes_fltregs)(sp)
621 movq %xmm15,(XMM15*8+offes_fltregs)(sp)
623 /* calculate sp of method */
625 add $(sizeexecutionstate + REPLACEMENT_ROOM + 8),itmp1
626 mov itmp1,(offes_sp)(sp)
628 /* pv must be looked up via AVL tree */
629 movq $0,(offes_pv)(sp)
631 /* call replace_me */
632 mov -8(itmp1),a0 /* rplpoint * */
633 mov sp,a1 /* arg1: execution state */
634 call replace_me@PLT /* call C function replace_me */
635 call abort@PLT /* NEVER REACHED */
637 /* asm_replacement_in **********************************************************
639 This code writes the given execution state and jumps to the replacement
642 This function never returns!
645 void asm_replacement_in(executionstate *es, replace_safestack_t *st);
647 *******************************************************************************/
651 mov a1,s1 /* replace_safestack_t *st */
652 mov a0,%rbp /* executionstate *es == safe stack */
654 /* switch to the safe stack */
657 /* call replace_build_execution_state(st) */
659 call replace_build_execution_state@PLT
662 mov (offes_sp)(%rbp),sp
664 /* push address of new code */
665 pushq (offes_pc)(%rbp)
667 /* allocate an executionstate_t on the stack */
668 sub $(sizeexecutionstate),sp
670 /* call replace_free_safestack(st,& of allocated executionstate_t) */
673 call replace_free_safestack@PLT
675 /* copy registers from execution state */
676 movq (XMM0 *8+offes_fltregs)(sp),%xmm0
677 movq (XMM1 *8+offes_fltregs)(sp),%xmm1
678 movq (XMM2 *8+offes_fltregs)(sp),%xmm2
679 movq (XMM3 *8+offes_fltregs)(sp),%xmm3
680 movq (XMM4 *8+offes_fltregs)(sp),%xmm4
681 movq (XMM5 *8+offes_fltregs)(sp),%xmm5
682 movq (XMM6 *8+offes_fltregs)(sp),%xmm6
683 movq (XMM7 *8+offes_fltregs)(sp),%xmm7
684 movq (XMM8 *8+offes_fltregs)(sp),%xmm8
685 movq (XMM9 *8+offes_fltregs)(sp),%xmm9
686 movq (XMM10*8+offes_fltregs)(sp),%xmm10
687 movq (XMM11*8+offes_fltregs)(sp),%xmm11
688 movq (XMM12*8+offes_fltregs)(sp),%xmm12
689 movq (XMM13*8+offes_fltregs)(sp),%xmm13
690 movq (XMM14*8+offes_fltregs)(sp),%xmm14
691 movq (XMM15*8+offes_fltregs)(sp),%xmm15
693 mov (RAX*8+offes_intregs)(sp),%rax
694 mov (RBX*8+offes_intregs)(sp),%rbx
695 mov (RCX*8+offes_intregs)(sp),%rcx
696 mov (RDX*8+offes_intregs)(sp),%rdx
697 mov (RSI*8+offes_intregs)(sp),%rsi
698 mov (RDI*8+offes_intregs)(sp),%rdi
699 mov (RBP*8+offes_intregs)(sp),%rbp
700 mov (R8 *8+offes_intregs)(sp),%r8
701 mov (R9 *8+offes_intregs)(sp),%r9
702 mov (R10*8+offes_intregs)(sp),%r10
703 mov (R11*8+offes_intregs)(sp),%r11
704 mov (R12*8+offes_intregs)(sp),%r12
705 mov (R13*8+offes_intregs)(sp),%r13
706 mov (R14*8+offes_intregs)(sp),%r14
707 mov (R15*8+offes_intregs)(sp),%r15
709 /* pop the execution state off the stack */
710 add $(sizeexecutionstate),sp
712 /* jump to new code */
715 #endif /* defined(ENABLE_REPLACEMENT) */
718 /* asm_builtin_x2x *************************************************************
720 * Wrapper functions for float to int corner cases *
722 *******************************************************************************/
727 SAVE_ARGUMENT_REGISTERS(0)
732 RESTORE_ARGUMENT_REGISTERS(0)
741 SAVE_ARGUMENT_REGISTERS(0)
746 RESTORE_ARGUMENT_REGISTERS(0)
755 SAVE_ARGUMENT_REGISTERS(0)
760 RESTORE_ARGUMENT_REGISTERS(0)
769 SAVE_ARGUMENT_REGISTERS(0)
774 RESTORE_ARGUMENT_REGISTERS(0)
780 asm_getclassvalues_atomic:
783 movl offbaseval(a0),itmp1l
784 movl offdiffval(a0),itmp2l
785 movl offbaseval(a1),itmp3l
787 movl itmp1l,offcast_super_baseval(a2)
788 movl itmp2l,offcast_super_diffval(a2)
789 movl itmp3l,offcast_sub_baseval(a2)
794 asm_criticalsections:
795 #if defined(ENABLE_THREADS)
803 /* Disable exec-stacks, required for Gentoo ***********************************/
805 #if defined(__GCC__) && defined(__ELF__)
806 .section .note.GNU-stack,"",@progbits
811 * These are local overrides for various environment variables in Emacs.
812 * Please do not remove this and leave it at the end of the file, where
813 * Emacs will automagically detect them.
814 * ---------------------------------------------------------------------
817 * indent-tabs-mode: t
821 * vim:noexpandtab:sw=4:ts=4: