* src/threads/threads-common.c (threads_thread_new) [ENABLE_GC_CACAO]:
[cacao.git] / src / threads / threads-common.c
1 /* src/threads/threads-common.c - machine independent thread functions
2
3    Copyright (C) 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    $Id: threads-common.c 7923 2007-05-20 23:57:39Z michi $
26
27 */
28
29
30 #include "config.h"
31
32 #include <assert.h>
33
34 #include "vm/types.h"
35
36 #include "native/jni.h"
37
38 #include "native/include/java_lang_Object.h"
39 #include "native/include/java_lang_String.h"
40 #include "native/include/java_lang_Thread.h"
41
42 #if defined(WITH_CLASSPATH_GNU)
43 # include "native/include/java_lang_VMThread.h"
44 #endif
45
46 #include "threads/critical.h"
47 #include "threads/lock-common.h"
48 #include "threads/threads-common.h"
49
50 #include "vm/builtin.h"
51 #include "vm/stringlocal.h"
52 #include "vm/vm.h"
53
54 #include "vm/jit/stacktrace.h"
55
56 #include "vmcore/class.h"
57
58 #if defined(ENABLE_STATISTICS)
59 # include "vmcore/options.h"
60 # include "vmcore/statistics.h"
61 #endif
62
63 #include "vmcore/utf8.h"
64
65
66 /* global variables ***********************************************************/
67
68 /* global threads table */
69 static threads_table_t threads_table;
70
71
72 /* prototypes *****************************************************************/
73
74 static void threads_table_init(threadobject *mainthread);
75
76
77 /* threads_preinit *************************************************************
78
79    Do some early initialization of stuff required.
80
81    ATTENTION: Do NOT use any Java heap allocation here, as gc_init()
82    is called AFTER this function!
83
84 *******************************************************************************/
85
86 void threads_preinit(void)
87 {
88         threadobject *mainthread;
89
90         /* Initialize the threads implementation (sets the thinlock on the
91            main thread). */
92
93         threads_impl_preinit();
94
95         /* create internal thread data-structure for the main thread */
96
97         mainthread = threads_thread_new();
98
99         mainthread->object   = NULL;
100         mainthread->index    = 1;
101         mainthread->thinlock = lock_pre_compute_thinlock(mainthread->index);
102
103         /* thread is a Java thread and running */
104
105         mainthread->flags = THREAD_FLAG_JAVA;
106         mainthread->state = THREAD_STATE_RUNNABLE;
107
108         /* store the internal thread data-structure in the TSD */
109
110         threads_set_current_threadobject(mainthread);
111         
112         /* initialize the threads table with the main-thread */
113
114         threads_table_init(mainthread);
115
116         /* initialize locking subsystems */
117
118         lock_init();
119
120         /* initialize the critical section */
121
122         critical_init();
123 }
124
125
126 /* threads_table_init **********************************************************
127
128    Initialize the global threads table.  We initialize the table with
129    the main-thread, which has always the index 1.
130
131    IN:
132       mainthread....the main-thread
133
134 *******************************************************************************/
135
136 #define THREADS_INITIAL_TABLE_SIZE    8
137
138 static void threads_table_init(threadobject *mainthread)
139 {
140         threads_table_entry_t *ttemain;
141         s4                     size;
142         s4                     i;
143
144         /* initialize the threads table lock */
145
146         threads_impl_table_init();
147
148         /* initialize the table */
149
150         size = THREADS_INITIAL_TABLE_SIZE;
151
152         threads_table.table   = MNEW(threads_table_entry_t, size);
153         threads_table.size    = size;
154         threads_table.used    = 0;
155         threads_table.daemons = 0;
156
157         /* Link the entries in a freelist.  Skip 2 entries: 0 is the
158            free-list header and 1 is the main thread. */
159
160         for (i = 2; i < size; i++) {
161                 threads_table.table[i].thread = NULL;
162                 threads_table.table[i].next   = i + 1;
163         }
164
165         threads_table.table[0].next = 2;
166
167         /* terminate the freelist */
168
169         threads_table.table[size - 1].next = 0;          /* index 0 is never free */
170
171         /* insert the main-thread */
172
173         ttemain = &(threads_table.table[1]);
174
175         ttemain->thread = mainthread;
176         ttemain->next   = 0;
177
178         /* now 1 entry is used */
179
180         threads_table.used = 1;
181 }
182
183
184 /* threads_table_add ***********************************************************
185
186    Add a thread to the global threads table. The index is entered in the
187    threadobject. The thinlock value for the thread is pre-computed.
188
189    IN:
190       thread............the thread to add
191
192    RETURN VALUE:
193       The table index for the newly added thread. This value has also been
194           entered in the threadobject.
195
196 *******************************************************************************/
197
198 s4 threads_table_add(threadobject *thread)
199 {
200         threads_table_entry_t *ttefree;
201         threads_table_entry_t *ttemain;
202         threads_table_entry_t *tte;
203         s4 index;
204         s4 oldsize;
205         s4 newsize;
206         s4 i;
207
208         /* lock the threads table */
209
210         threads_table_lock();
211
212         /* get free and main entry */
213
214         ttefree = &(threads_table.table[0]);
215         ttemain = &(threads_table.table[1]);
216
217         /* get the next free index */
218
219         index = ttefree->next;
220
221         /* no entry free anymore? resize the table */
222
223         if (index == 0) {
224                 /* we must grow the table */
225
226                 oldsize = threads_table.size;
227                 newsize = oldsize * 2;
228
229                 threads_table.table = MREALLOC(threads_table.table,
230                                                                            threads_table_entry_t, oldsize, newsize);
231                 threads_table.size = newsize;
232
233                 /* the addresses have changed, get them again */
234
235                 ttefree = &(threads_table.table[0]);
236                 ttemain = &(threads_table.table[1]);
237
238                 /* link the new entries to a free list */
239
240                 for (i = oldsize; i < newsize; i++) {
241                         threads_table.table[i].thread = NULL;
242                         threads_table.table[i].next   = i + 1;
243                 }
244
245                 ttefree->next = oldsize;
246
247                 /* terminate the freelist */
248
249                 threads_table.table[newsize - 1].next = 0;   /* index 0 is never free */
250
251                 /* use the first of the new entries */
252
253                 index = ttefree->next;
254         }
255
256         /* get the entry with the assigned index */
257
258         tte = &(threads_table.table[index]);
259
260         /* store the next free index into the free-list header */
261
262         ttefree->next = tte->next;
263
264         /* store the thread in the table */
265
266         tte->thread = thread;
267
268         /* link the new entry into the used-list */
269
270         tte->next     = ttemain->next;
271         ttemain->next = index;
272
273         /* update the counters */
274
275         threads_table.used++;
276
277         if (thread->flags & THREAD_FLAG_DAEMON)
278                 threads_table.daemons++;
279
280         assert(threads_table.used < threads_table.size);
281
282         /* set the thread variables */
283
284         thread->index    = index;
285         thread->thinlock = lock_pre_compute_thinlock(index);
286
287         /* unlock the threads table */
288
289         threads_table_unlock();
290
291         return index;
292 }
293
294
295 /* threads_table_remove *******************************************************
296
297    Remove a thread from the global threads table.
298
299    IN:
300       thread............the thread to remove
301
302 ******************************************************************************/
303
304 void threads_table_remove(threadobject *thread)
305 {
306         threads_table_entry_t *ttefree;
307         threads_table_entry_t *tte;
308         s4                     index;
309         s4                     i;
310
311         /* lock the threads table */
312
313         threads_table_lock();
314
315         /* get the free entry */
316
317         ttefree = &(threads_table.table[0]);
318
319         /* get the current entry */
320
321         index = thread->index;
322         tte   = &(threads_table.table[index]);
323
324         assert(tte->thread == thread);
325
326         /* Find the entry which has the one to be removed as next entry (I
327            think it's better to do it at the removal in linear time than
328            to have a list or to do it every time we iterate over all
329            threads). */
330
331         for (i = 0; i < threads_table.size; i++) {
332                 if (threads_table.table[i].next == index) {
333                         threads_table.table[i].next = tte->next;
334                         break;
335                 }
336         }
337
338         /* clear the thread pointer in the entry */
339
340         tte->thread = NULL;
341
342         /* this entry is free now, add it to the free-list */
343
344         tte->next     = ttefree->next;
345         ttefree->next = index;
346
347         /* update the counters */
348
349         threads_table.used--;
350
351         if (thread->flags & THREAD_FLAG_DAEMON)
352                 threads_table.daemons--;
353
354         assert(threads_table.used >= 0);
355
356         /* delete the index in the threadobject to discover bugs */
357 #if !defined(NDEBUG)
358         thread->index = 0;
359 #endif
360
361         /* unlock the threads table */
362
363         threads_table_unlock();
364 }
365
366
367 /* threads_table_get ***********************************************************
368
369    Return the thread of the given table-entry index.
370
371    NOTE: It is valid to pass and index of 0, as this entry is the
372          free-list header where the thread pointer is always NULL and
373          this is thre expected behavior.
374
375    NOTE: This function does not lock the table.
376
377 *******************************************************************************/
378
379 static threadobject *threads_table_get(s4 index)
380 {
381         threadobject *thread;
382
383         /* get the requested entry */
384
385         assert((index >= 0) && (index < threads_table.size));
386
387         thread = threads_table.table[index].thread;
388
389         return thread;
390 }
391
392
393 /* threads_table_get_threads ***************************************************
394
395    Return the number of running threads.
396
397    NOTE: This function does not lock the table.
398
399 *******************************************************************************/
400
401 s4 threads_table_get_threads(void)
402 {
403         return threads_table.used;
404 }
405
406
407 /* threads_table_get_non_daemons ***********************************************
408
409    Return the number of non-daemon threads.
410
411 *******************************************************************************/
412
413 s4 threads_table_get_non_daemons(void)
414 {
415         s4 nondaemons;
416
417         /* lock the threads table */
418
419         threads_table_lock();
420
421         nondaemons = threads_table.used - threads_table.daemons;
422
423         /* unlock the threads table */
424
425         threads_table_unlock();
426
427         return nondaemons;
428 }
429
430
431 /* threads_table_first *********************************************************
432
433    Return the first thread of the threads table.
434
435    NOTE: This is always the entry with index 1 and must be the main
436          thread.
437
438    NOTE: This function does not lock the table.
439
440 *******************************************************************************/
441
442 threadobject *threads_table_first(void)
443 {
444         threadobject *thread;
445
446         /* get the requested entry */
447
448         thread = threads_table_get(1);
449
450         return thread;
451 }
452
453
454 /* threads_table_next **********************************************************
455
456    Return the next thread of the threads table relative to the passed
457    one.
458
459    NOTE: This function does not lock the table.
460
461 *******************************************************************************/
462
463 threadobject *threads_table_next(threadobject *thread)
464 {
465         threads_table_entry_t *tte;
466         threadobject          *next;
467         s4                     index;
468
469         index = thread->index;
470
471         /* get the passed entry */
472
473         assert((index > 0) && (index < threads_table.size));
474
475         tte = &(threads_table.table[index]);
476
477         /* get the requested entry */
478
479         next = threads_table_get(tte->next);
480
481         return next;
482 }
483
484
485 /* threads_table_dump *********************************************************
486
487    Dump the threads table for debugging purposes.
488
489 ******************************************************************************/
490
491 #if !defined(NDEBUG)
492 void threads_table_dump(void)
493 {
494         s4 i;
495         s4 size;
496         ptrint index;
497
498         size = threads_table.size;
499
500         log_println("threads table ==========");
501
502         log_println("size:    %d", size);
503         log_println("used:    %d", threads_table.used);
504         log_println("daemons: %d", threads_table.daemons);
505
506         for (i = 0; i < size; i++) {
507                 index = threads_table.table[i].next;
508
509                 if (threads_table.table[i].thread != NULL)
510                         log_println("%4d: thread=0x%08x, next=%d", i,
511                                                 threads_table.table[i].thread->tid, (int) index);
512                 else
513                         log_println("%4d: free, next=%d", i, (int) index);
514         }
515
516         log_println("end of threads table ==========");
517 }
518 #endif
519
520
521 /* threads_thread_new **********************************************************
522
523    Allocates and initializes an internal thread data-structure.
524
525 *******************************************************************************/
526
527 threadobject *threads_thread_new(void)
528 {
529         threadobject *t;
530
531         /* allocate internal thread data-structure */
532
533 #if defined(ENABLE_GC_BOEHM)
534         t = GCNEW_UNCOLLECTABLE(threadobject, 1);
535 #else
536         t = NEW(threadobject);
537 #endif
538
539 #if defined(ENABLE_STATISTICS)
540         if (opt_stat)
541                 size_threadobject += sizeof(threadobject);
542 #endif
543
544         /* initialize thread data structure */
545
546         t->index       = 0;
547         t->flags       = 0;
548         t->interrupted = false;
549         t->signaled    = false;
550         t->sleeping    = false;
551
552 #if defined(ENABLE_GC_CACAO)
553         t->gc_critical = false;
554         t->flags      |= THREAD_FLAG_IN_NATIVE;
555 #endif
556
557         threads_impl_thread_new(t);
558
559         return t;
560 }
561
562
563 /* threads_thread_free *********************************************************
564
565    Frees an internal thread data-structure.
566
567 *******************************************************************************/
568
569 void threads_thread_free(threadobject *t)
570 {
571         /* cleanup the implementation-specific bits */
572
573         threads_impl_thread_free(t);
574
575 #if defined(ENABLE_GC_BOEHM)
576         GCFREE(t);
577 #else
578         FREE(t, threadobject);
579 #endif
580
581 #if defined(ENABLE_STATISTICS)
582         if (opt_stat)
583                 size_threadobject -= sizeof(threadobject);
584 #endif
585 }
586
587
588 /* threads_thread_start_internal ***********************************************
589
590    Start an internal thread in the JVM.  No Java thread objects exists
591    so far.
592
593    IN:
594       name.......UTF-8 name of the thread
595       f..........function pointer to C function to start
596
597 *******************************************************************************/
598
599 bool threads_thread_start_internal(utf *name, functionptr f)
600 {
601         threadobject       *thread;
602         java_lang_Thread   *t;
603 #if defined(WITH_CLASSPATH_GNU)
604         java_lang_VMThread *vmt;
605 #endif
606
607         /* create internal thread data-structure */
608
609         thread = threads_thread_new();
610
611         /* create the java thread object */
612
613         t = (java_lang_Thread *) builtin_new(class_java_lang_Thread);
614
615         if (t == NULL)
616                 return false;
617
618 #if defined(WITH_CLASSPATH_GNU)
619         vmt = (java_lang_VMThread *) builtin_new(class_java_lang_VMThread);
620
621         if (vmt == NULL)
622                 return false;
623
624         vmt->thread = t;
625         vmt->vmdata = (java_lang_Object *) thread;
626
627         t->vmThread = vmt;
628 #elif defined(WITH_CLASSPATH_CLDC1_1)
629         t->vm_thread = (java_lang_Object *) thread;
630 #endif
631
632 #if defined(ENABLE_GC_CACAO)
633         /* register reference to java.lang.Thread with the GC */
634
635         gc_reference_register(&(thread->object));
636 #endif
637
638         thread->object = t;
639
640         thread->flags |= THREAD_FLAG_INTERNAL | THREAD_FLAG_DAEMON;
641
642         /* set java.lang.Thread fields */
643
644         t->name     = (java_lang_String *) javastring_new(name);
645 #if defined(ENABLE_JAVASE)
646         t->daemon   = true;
647 #endif
648         t->priority = NORM_PRIORITY;
649
650         /* start the thread */
651
652         threads_impl_thread_start(thread, f);
653
654         /* everything's ok */
655
656         return true;
657 }
658
659
660 /* threads_thread_start ********************************************************
661
662    Start a Java thread in the JVM.  Only the java thread object exists
663    so far.
664
665    IN:
666       object.....the java thread object java.lang.Thread
667
668 *******************************************************************************/
669
670 void threads_thread_start(java_lang_Thread *object)
671 {
672         threadobject *thread;
673
674         /* create internal thread data-structure */
675
676         thread = threads_thread_new();
677
678 #if defined(ENABLE_GC_CACAO)
679         /* register reference to java.lang.Thread with the GC */
680
681         gc_reference_register(&(thread->object));
682 #endif
683
684         /* link the two objects together */
685
686         thread->object = object;
687
688         /* this is a normal Java thread */
689
690         thread->flags |= THREAD_FLAG_JAVA;
691
692 #if defined(ENABLE_JAVASE)
693         /* is this a daemon thread? */
694
695         if (object->daemon == true)
696                 thread->flags |= THREAD_FLAG_DAEMON;
697 #endif
698
699 #if defined(WITH_CLASSPATH_GNU)
700         assert(object->vmThread);
701         assert(object->vmThread->vmdata == NULL);
702
703         object->vmThread->vmdata = (java_lang_Object *) thread;
704 #elif defined(WITH_CLASSPATH_CLDC1_1)
705         object->vm_thread = (java_lang_Object *) thread;
706 #endif
707
708         /* Start the thread.  Don't pass a function pointer (NULL) since
709            we want Thread.run()V here. */
710
711         threads_impl_thread_start(thread, NULL);
712 }
713
714
715 /* threads_thread_print_info ***************************************************
716
717    Print information of the passed thread.
718    
719 *******************************************************************************/
720
721 void threads_thread_print_info(threadobject *t)
722 {
723         java_lang_Thread *object;
724         utf              *name;
725
726         /* the thread may be currently in initalization, don't print it */
727
728         object = t->object;
729
730         if (object != NULL) {
731                 /* get thread name */
732
733 #if defined(ENABLE_JAVASE)
734                 name = javastring_toutf((java_objectheader *) object->name, false);
735 #elif defined(ENABLE_JAVAME_CLDC1_1)
736                 name = object->name;
737 #endif
738
739                 printf("\"");
740                 utf_display_printable_ascii(name);
741                 printf("\"");
742
743                 if (t->flags & THREAD_FLAG_DAEMON)
744                         printf(" daemon");
745
746                 printf(" prio=%d", object->priority);
747
748 #if SIZEOF_VOID_P == 8
749                 printf(" t=0x%016lx tid=0x%016lx (%ld)",
750                            (ptrint) t, (ptrint) t->tid, (ptrint) t->tid);
751 #else
752                 printf(" t=0x%08x tid=0x%08x (%d)",
753                            (ptrint) t, (ptrint) t->tid, (ptrint) t->tid);
754 #endif
755
756                 /* print thread state */
757
758                 switch (t->state) {
759                 case THREAD_STATE_NEW:
760                         printf(" new");
761                         break;
762                 case THREAD_STATE_RUNNABLE:
763                         printf(" runnable");
764                         break;
765                 case THREAD_STATE_BLOCKED:
766                         printf(" blocked");
767                         break;
768                 case THREAD_STATE_WAITING:
769                         printf(" waiting");
770                         break;
771                 case THREAD_STATE_TIMED_WAITING:
772                         printf(" waiting on condition");
773                         break;
774                 case THREAD_STATE_TERMINATED:
775                         printf(" terminated");
776                         break;
777                 default:
778                         vm_abort("threads_thread_print_info: unknown thread state %d",
779                                          t->state);
780                 }
781         }
782 }
783
784
785 /* threads_get_current_tid *****************************************************
786
787    Return the tid of the current thread.
788    
789    RETURN VALUE:
790        the current tid
791
792 *******************************************************************************/
793
794 ptrint threads_get_current_tid(void)
795 {
796         threadobject *thread;
797
798         thread = THREADOBJECT;
799
800         /* this may happen during bootstrap */
801
802         if (thread == NULL)
803                 return 0;
804
805         return (ptrint) thread->tid;
806 }
807
808
809 /* threads_thread_get_state ****************************************************
810
811    Returns the current state of the given thread.
812
813 *******************************************************************************/
814
815 utf *threads_thread_get_state(threadobject *thread)
816 {
817         utf *u;
818
819         switch (thread->state) {
820         case THREAD_STATE_NEW:
821                 u = utf_new_char("NEW");
822                 break;
823         case THREAD_STATE_RUNNABLE:
824                 u = utf_new_char("RUNNABLE");
825                 break;
826         case THREAD_STATE_BLOCKED:
827                 u = utf_new_char("BLOCKED");
828                 break;
829         case THREAD_STATE_WAITING:
830                 u = utf_new_char("WAITING");
831                 break;
832         case THREAD_STATE_TIMED_WAITING:
833                 u = utf_new_char("TIMED_WAITING");
834                 break;
835         case THREAD_STATE_TERMINATED:
836                 u = utf_new_char("TERMINATED");
837                 break;
838         default:
839                 vm_abort("threads_get_state: unknown thread state %d", thread->state);
840
841                 /* keep compiler happy */
842
843                 u = NULL;
844         }
845
846         return u;
847 }
848
849
850 /* threads_thread_is_alive *****************************************************
851
852    Returns if the give thread is alive.
853
854 *******************************************************************************/
855
856 bool threads_thread_is_alive(threadobject *thread)
857 {
858         bool result;
859
860         switch (thread->state) {
861         case THREAD_STATE_NEW:
862         case THREAD_STATE_TERMINATED:
863                 result = false;
864                 break;
865
866         case THREAD_STATE_RUNNABLE:
867         case THREAD_STATE_BLOCKED:
868         case THREAD_STATE_WAITING:
869         case THREAD_STATE_TIMED_WAITING:
870                 result = true;
871                 break;
872
873         default:
874                 vm_abort("threads_is_alive: unknown thread state %d", thread->state);
875
876                 /* keep compiler happy */
877
878                 result = false;
879         }
880
881         return result;
882 }
883
884
885 /* threads_dump ****************************************************************
886
887    Dumps info for all threads running in the JVM.  This function is
888    called when SIGQUIT (<ctrl>-\) is sent to CACAO.
889
890 *******************************************************************************/
891
892 void threads_dump(void)
893 {
894         threadobject *t;
895
896         /* XXX we should stop the world here */
897
898         /* lock the threads table */
899
900         threads_table_lock();
901
902         printf("Full thread dump CACAO "VERSION":\n");
903
904         /* iterate over all started threads */
905
906         for (t = threads_table_first(); t != NULL; t = threads_table_next(t)) {
907                 /* print thread info */
908
909                 printf("\n");
910                 threads_thread_print_info(t);
911                 printf("\n");
912
913                 /* print trace of thread */
914
915                 threads_thread_print_stacktrace(t);
916         }
917
918         /* unlock the threads table */
919
920         threads_table_unlock();
921 }
922
923
924 /* threads_thread_print_stacktrace *********************************************
925
926    Print the current stacktrace of the current thread.
927
928 *******************************************************************************/
929
930 void threads_thread_print_stacktrace(threadobject *thread)
931 {
932         stackframeinfo   *sfi;
933         stacktracebuffer *stb;
934         s4                dumpsize;
935
936         /* mark start of dump memory area */
937
938         dumpsize = dump_size();
939
940         /* create a stacktrace for the passed thread */
941
942         sfi = thread->_stackframeinfo;
943
944         stb = stacktrace_create(sfi);
945
946         /* print stacktrace */
947
948         if (stb != NULL)
949                 stacktrace_print_trace_from_buffer(stb);
950         else {
951                 puts("\t<<No stacktrace available>>");
952                 fflush(stdout);
953         }
954
955         dump_release(dumpsize);
956 }
957
958
959 /* threads_print_stacktrace ****************************************************
960
961    Print the current stacktrace of the current thread.
962
963 *******************************************************************************/
964
965 void threads_print_stacktrace(void)
966 {
967         threadobject *thread;
968
969         thread = THREADOBJECT;
970
971         threads_thread_print_stacktrace(thread);
972 }
973
974
975 /*
976  * These are local overrides for various environment variables in Emacs.
977  * Please do not remove this and leave it at the end of the file, where
978  * Emacs will automagically detect them.
979  * ---------------------------------------------------------------------
980  * Local variables:
981  * mode: c
982  * indent-tabs-mode: t
983  * c-basic-offset: 4
984  * tab-width: 4
985  * End:
986  * vim:noexpandtab:sw=4:ts=4:
987  */