flush
[m6w6/ext-psi] / src / module.c
1
2 #ifdef HAVE_CONFIG_H
3 #include "config.h"
4 #endif
5
6 #include "php.h"
7 #include "php_ini.h"
8 #include "ext/standard/info.h"
9
10 #include "php_psi.h"
11 #include "parser.h"
12
13 #include "libjit.h"
14 #include "libffi.h"
15
16 ZEND_DECLARE_MODULE_GLOBALS(psi);
17
18 PHP_INI_BEGIN()
19 STD_PHP_INI_ENTRY("psi.engine", "ffi", PHP_INI_SYSTEM, OnUpdateString, engine, zend_psi_globals, psi_globals)
20 STD_PHP_INI_ENTRY("psi.directory", "psis", PHP_INI_SYSTEM, OnUpdateString, directory, zend_psi_globals, psi_globals)
21 PHP_INI_END();
22
23 void psi_error(int type, const char *msg, ...)
24 {
25 char buf[0x1000];
26 va_list argv;
27
28 va_start(argv, msg);
29 vslprintf(buf, 0x1000, msg, argv);
30 va_end(argv);
31
32 php_error(type, buf);
33 }
34
35 size_t psi_t_alignment(token_t t)
36 {
37 size_t align;
38 #define PSI_TAS_D(T) struct PSI_TAS_ ##T { \
39 char c; \
40 T x; \
41 }
42 #define PSI_TAS_P(T) struct PSI_TAS_ ## T ## _pointer { \
43 char c; \
44 T *x; \
45 }
46 #define PSI_TAS_C(T) align = offsetof(struct PSI_TAS_ ##T, x)
47 #define PSI_TAS_CASE(T) { \
48 PSI_TAS_D(T); \
49 PSI_TAS_C(T); \
50 }
51 switch (t) {
52 case PSI_T_INT8:
53 PSI_TAS_CASE(int8_t);
54 break;
55 case PSI_T_UINT8:
56 PSI_TAS_CASE(uint8_t);
57 break;
58 case PSI_T_INT16:
59 PSI_TAS_CASE(int16_t);
60 break;
61 case PSI_T_UINT16:
62 PSI_TAS_CASE(uint16_t);
63 break;
64 case PSI_T_INT32:
65 PSI_TAS_CASE(int32_t);
66 break;
67 case PSI_T_UINT32:
68 PSI_TAS_CASE(uint32_t);
69 break;
70 case PSI_T_INT64:
71 PSI_TAS_CASE(int64_t);
72 break;
73 case PSI_T_UINT64:
74 PSI_TAS_CASE(uint64_t);
75 break;
76 case PSI_T_FLOAT:
77 PSI_TAS_CASE(float);
78 break;
79 case PSI_T_DOUBLE:
80 PSI_TAS_CASE(double);
81 break;
82 case PSI_T_POINTER:
83 {
84 PSI_TAS_P(char);
85 PSI_TAS_C(char_pointer);
86 }
87 break;
88 EMPTY_SWITCH_DEFAULT_CASE();
89 }
90
91 return align;
92 }
93
94 size_t psi_t_size(token_t t)
95 {
96 size_t size;
97
98 switch (t) {
99 case PSI_T_INT8:
100 case PSI_T_UINT8:
101 size = 1;
102 break;
103 case PSI_T_INT16:
104 case PSI_T_UINT16:
105 size = 2;
106 break;
107 case PSI_T_INT:
108 size = sizeof(int);
109 break;
110 case PSI_T_INT32:
111 case PSI_T_UINT32:
112 size = 4;
113 break;
114 case PSI_T_INT64:
115 case PSI_T_UINT64:
116 size = 8;
117 break;
118 case PSI_T_FLOAT:
119 size = sizeof(float);
120 break;
121 case PSI_T_DOUBLE:
122 size = sizeof(double);
123 break;
124 case PSI_T_POINTER:
125 size = sizeof(char *);
126 break;
127 EMPTY_SWITCH_DEFAULT_CASE();
128 }
129 return size;
130 }
131
132 size_t psi_t_align(token_t t, size_t s)
133 {
134 size_t a = psi_t_alignment(t);
135 return ((s - 1) | (a - 1)) + 1;
136 }
137
138 int psi_internal_type(impl_type *type)
139 {
140 switch (type->type) {
141 case PSI_T_BOOL:
142 return _IS_BOOL;
143 case PSI_T_INT:
144 return IS_LONG;
145 case PSI_T_FLOAT:
146 case PSI_T_DOUBLE:
147 return IS_DOUBLE;
148 case PSI_T_STRING:
149 return IS_STRING;
150 case PSI_T_ARRAY:
151 return IS_ARRAY;
152 default:
153 return 0;
154 }
155 }
156
157 zend_internal_arg_info *psi_internal_arginfo(impl *impl)
158 {
159 size_t i;
160 zend_internal_arg_info *aip;
161 zend_internal_function_info *fi;
162
163 aip = calloc(impl->func->args->count + 1, sizeof(*aip));
164
165 fi = (zend_internal_function_info *) &aip[0];
166 fi->required_num_args = psi_num_min_args(impl);
167 fi->return_reference = impl->func->return_reference;
168 fi->type_hint = psi_internal_type(impl->func->return_type);
169
170 for (i = 0; i < impl->func->args->count; ++i) {
171 impl_arg *iarg = impl->func->args->args[i];
172 zend_internal_arg_info *ai = &aip[i+1];
173
174 ai->name = iarg->var->name;
175 ai->type_hint = psi_internal_type(iarg->type);
176 if (iarg->var->reference) {
177 ai->pass_by_reference = 1;
178 }
179 if (iarg->var->reference || (iarg->def && iarg->def->type == PSI_T_NULL)) {
180 ai->allow_null = 1;
181 }
182 }
183
184 return aip;
185 }
186
187 size_t psi_num_min_args(impl *impl)
188 {
189 size_t i, n = impl->func->args->count;
190
191 for (i = 0; i < impl->func->args->count; ++i) {
192 if (impl->func->args->args[i]->def) {
193 --n;
194 }
195 }
196 return n;
197 }
198
199 void psi_to_bool(zval *return_value, token_t t, impl_val *ret_val, set_value *set, decl_var *var)
200 {
201 impl_val *v = deref_impl_val(ret_val, var);
202
203 switch (t) {
204 case PSI_T_FLOAT:
205 RETVAL_DOUBLE((double) v->fval);
206 break;
207 case PSI_T_DOUBLE:
208 RETVAL_DOUBLE(v->dval);
209 break;
210 default:
211 RETVAL_LONG(v->lval);
212 break;
213 }
214 convert_to_boolean(return_value);
215 }
216
217 void psi_to_int(zval *return_value, token_t t, impl_val *ret_val, set_value *set, decl_var *var)
218 {
219 impl_val *v = deref_impl_val(ret_val, var);
220
221 switch (t) {
222 case PSI_T_FLOAT:
223 RETVAL_DOUBLE((double) v->fval);
224 break;
225 case PSI_T_DOUBLE:
226 RETVAL_DOUBLE(v->dval);
227 break;
228 default:
229 RETVAL_LONG(v->lval);
230 return;
231 }
232 convert_to_long(return_value);
233 }
234
235 void psi_to_double(zval *return_value, token_t t, impl_val *ret_val, set_value *set, decl_var *var)
236 {
237 impl_val *v = deref_impl_val(ret_val, var);
238
239 switch (t) {
240 case PSI_T_FLOAT:
241 RETVAL_DOUBLE((double) v->fval);
242 break;
243 case PSI_T_DOUBLE:
244 RETVAL_DOUBLE(v->dval);
245 break;
246 default:
247 RETVAL_DOUBLE((double) v->lval);
248 break;
249 }
250 }
251
252 void psi_to_string(zval *return_value, token_t t, impl_val *ret_val, set_value *set, decl_var *var)
253 {
254 switch (t) {
255 case PSI_T_INT8:
256 case PSI_T_UINT8:
257 if (!var->arg->var->pointer_level) {
258 RETVAL_STRINGL(&ret_val->cval, 1);
259 } else {
260 ret_val = deref_impl_val(ret_val, var);
261 if (ret_val && ret_val->ptr) {
262 RETVAL_STRING(ret_val->ptr);
263 } else {
264 RETVAL_EMPTY_STRING();
265 }
266 }
267 return;
268 case PSI_T_FLOAT:
269 RETVAL_DOUBLE((double) deref_impl_val(ret_val, var)->fval);
270 break;
271 case PSI_T_DOUBLE:
272 RETVAL_DOUBLE(deref_impl_val(ret_val, var)->dval);
273 break;
274 default:
275 RETVAL_LONG(deref_impl_val(ret_val, var)->lval);
276 break;
277 }
278 convert_to_string(return_value);
279 }
280
281
282 static impl_val *iterate(impl_val *val, token_t t, unsigned i, impl_val *tmp)
283 {
284 size_t size = psi_t_size(t);
285
286 memset(tmp, 0, sizeof(*tmp));
287 memcpy(tmp, val->ptr + size * i, size);
288 return tmp;
289 }
290
291 void psi_from_zval(impl_val *mem, decl_arg *spec, zval *zv, void **tmp)
292 {
293 decl_type *type = real_decl_type(spec->type);
294
295 switch (type->type) {
296 case PSI_T_FLOAT:
297 mem->fval = (float) zval_get_double(zv);
298 break;
299 case PSI_T_DOUBLE:
300 mem->dval = zval_get_double(zv);
301 break;
302 case PSI_T_INT8:
303 case PSI_T_UINT8:
304 if (spec->var->pointer_level) {
305 zend_string *zs = zval_get_string(zv);
306 *tmp = mem->ptr = estrndup(zs->val, zs->len);
307 zend_string_release(zs);
308 break;
309 }
310 /* no break */
311 default:
312 mem->zend.lval = zval_get_long(zv);
313 break;
314 }
315 }
316
317 void *psi_array_to_struct(decl_struct *s, HashTable *arr)
318 {
319 size_t i, j = 0;
320 char *mem = ecalloc(1, s->size + s->args->count * sizeof(void *));
321
322 if (arr) for (i = 0; i < s->args->count; ++i) {
323 decl_arg *darg = s->args->args[i];
324 zval *entry = zend_hash_str_find_ind(arr, darg->var->name, strlen(darg->var->name));
325
326 if (entry) {
327 impl_val val;
328 void *tmp = NULL;
329
330 memset(&tmp, 0, sizeof(tmp));
331 psi_from_zval(&val, darg, entry, &tmp);
332 memcpy(mem + darg->layout->pos, &val, darg->layout->len);
333 if (tmp) {
334 ((void **)(mem + s->size))[j++] = tmp;
335 }
336 }
337 }
338 return mem;
339 }
340
341 void psi_to_array(zval *return_value, token_t t, impl_val *ret_val, set_value *set, decl_var *var)
342 {
343 zval ele;
344 unsigned i;
345 impl_val tmp;
346
347 array_init(return_value);
348
349 if (t == PSI_T_STRUCT) {
350 decl_struct *s = real_decl_type(var->arg->type)->strct;
351 ret_val = deref_impl_val(ret_val, var);
352
353 ZEND_ASSERT(s);
354
355 if (set->count) {
356 /* explicit member casts */
357 for (i = 0; i < set->count; ++i) {
358 zval ztmp;
359 impl_val *tmp_ptr;
360 set_value *sub_set = set->inner[i];
361 decl_var *sub_var = sub_set->vars->vars[0];
362 decl_arg *sub_arg = sub_var->arg;
363 token_t t = real_decl_type(sub_arg->type)->type;
364 void *ptr = malloc(sub_arg->layout->len);
365
366 memcpy(ptr, (char *) ret_val->ptr + sub_arg->layout->pos,
367 sub_arg->layout->len);
368 tmp_ptr = enref_impl_val(ptr, sub_arg->var);
369 sub_set->func->handler(&ztmp, t, tmp_ptr, sub_set, sub_var);
370 add_assoc_zval(return_value, sub_var->name, &ztmp);
371 free(tmp_ptr);
372 if (tmp_ptr != ptr) {
373 free(ptr);
374 }
375 }
376 }
377 return;
378
379 // for (i = 0; i < s->args->count; ++i) {
380 // decl_arg *darg = s->args->args[i];
381 // impl_val tmp, tmp_ptr;
382 // zval ztmp;
383 // char *ptr = (char *) ret_val->ptr + darg->layout->pos;
384 //
385 // tmp_ptr.ptr = &tmp;
386 // memset(&tmp, 0, sizeof(tmp));
387 // memcpy(&tmp, ptr, darg->layout->len);
388 // switch (real_decl_type(darg->type)->type) {
389 // case PSI_T_FLOAT:
390 // case PSI_T_DOUBLE:
391 // psi_to_double(&ztmp, real_decl_type(darg->type)->type, &tmp, darg->var);
392 // break;
393 // case PSI_T_INT8:
394 // case PSI_T_UINT8:
395 // if (darg->var->pointer_level) {
396 // psi_to_string(&ztmp, real_decl_type(darg->type)->type, &tmp_ptr, darg->var);
397 // break;
398 // }
399 // /* no break */
400 // case PSI_T_INT16:
401 // case PSI_T_UINT16:
402 // case PSI_T_INT32:
403 // case PSI_T_UINT32:
404 // case PSI_T_INT64:
405 // case PSI_T_UINT64:
406 // psi_to_int(&ztmp, real_decl_type(darg->type)->type, &tmp, darg->var);
407 // break;
408 // case PSI_T_STRUCT:
409 // psi_to_array(&ztmp, real_decl_type(darg->type)->type, &tmp_ptr, darg->var);
410 // break;
411 // default:
412 // printf("t=%d\n", real_decl_type(darg->type)->type);
413 // abort();
414 // }
415 // add_assoc_zval(return_value, darg->var->name, &ztmp);
416 // }
417 return;
418 }
419 ret_val = deref_impl_val(ret_val, var);
420 for (i = 0; i < var->arg->var->array_size; ++i) {
421 impl_val *ptr = iterate(ret_val, t, i, &tmp);
422
423 switch (t) {
424 case PSI_T_FLOAT:
425 ZVAL_DOUBLE(&ele, (double) ptr->fval);
426 break;
427 case PSI_T_DOUBLE:
428 ZVAL_DOUBLE(&ele, ptr->dval);
429 break;
430 default:
431 ZVAL_LONG(&ele, ptr->lval);
432 break;
433 }
434
435 add_next_index_zval(return_value, &ele);
436 }
437 }
438
439 ZEND_RESULT_CODE psi_parse_args(zend_execute_data *execute_data, impl *impl)
440 {
441 impl_arg *iarg;
442
443 if (!impl->func->args->count) {
444 return zend_parse_parameters_none();
445 }
446
447 ZEND_PARSE_PARAMETERS_START(psi_num_min_args(impl), impl->func->args->count)
448 nextarg:
449 iarg = impl->func->args->args[_i];
450 if (iarg->def) {
451 Z_PARAM_OPTIONAL;
452 }
453 if (PSI_T_BOOL == iarg->type->type) {
454 if (iarg->def) {
455 iarg->val.zend.bval = iarg->def->type == PSI_T_TRUE ? 1 : 0;
456 }
457 Z_PARAM_BOOL(iarg->val.zend.bval);
458 } else if (PSI_T_INT == iarg->type->type) {
459 if (iarg->def) {
460 iarg->val.zend.lval = zend_atol(iarg->def->text, strlen(iarg->def->text));
461 }
462 Z_PARAM_LONG(iarg->val.zend.lval);
463 } else if (PSI_T_FLOAT == iarg->type->type || PSI_T_DOUBLE == iarg->type->type) {
464 if (iarg->def) {
465 iarg->val.dval = zend_strtod(iarg->def->text, NULL);
466 }
467 Z_PARAM_DOUBLE(iarg->val.dval);
468 } else if (PSI_T_STRING == iarg->type->type) {
469 struct {char *val; size_t len;} str;
470 if (iarg->def) {
471 /* FIXME */
472 str.len = strlen(iarg->def->text) - 2;
473 str.val = &iarg->def->text[1];
474 }
475 Z_PARAM_STR_EX(iarg->val.zend.str, 1, 0);
476 if (iarg->val.zend.str) {
477 zend_string_addref(iarg->val.zend.str);
478 } else if (iarg->def) {
479 iarg->val.zend.str = zend_string_init(str.val, str.len, 0);
480 }
481 } else if (PSI_T_ARRAY == iarg->type->type) {
482 /* handled as _zv in let or set */
483 Z_PARAM_PROLOGUE(0);
484 } else {
485 error_code = ZPP_ERROR_FAILURE;
486 break;
487 }
488 iarg->_zv = _arg;
489 if (_i < _max_num_args) {
490 goto nextarg;
491 }
492 ZEND_PARSE_PARAMETERS_END_EX(return FAILURE);
493
494 return SUCCESS;
495 }
496
497 void *psi_do_calloc(let_calloc *alloc)
498 {
499 decl_type *type = real_decl_type(alloc->type);
500 size_t size;
501
502 if (type->type == PSI_T_STRUCT) {
503 /* psi_do_clean expects at least one NULL pointer after the struct */
504 size = type->strct->size + sizeof(void *);
505 } else {
506 size = psi_t_size(type->type);
507 }
508
509 return ecalloc(alloc->n, size);
510 }
511
512 void *psi_do_let(decl_arg *darg)
513 {
514 impl_arg *iarg = darg->let->arg;
515 impl_val *arg_val;
516
517 darg->let->ptr = &darg->let->out;
518 arg_val = darg->let->ptr;
519
520 if (!iarg) {
521 /* let foo = calloc(1, long);
522 * let foo = NULL;
523 * let foo;
524 */
525 if (darg->let->val->func && darg->let->val->func->type == PSI_T_CALLOC) {
526 arg_val->ptr = psi_do_calloc(darg->let->val->func->alloc);
527 darg->let->mem = arg_val->ptr;
528 } else if (darg->var->array_size) {
529 arg_val->ptr = ecalloc(darg->var->array_size, sizeof(*arg_val));
530 darg->let->mem = arg_val->ptr;
531 } else {
532 memset(arg_val, 0, sizeof(*arg_val));
533 }
534 } else {
535
536 switch (darg->let->val->func->type) {
537 case PSI_T_BOOLVAL:
538 if (iarg->type->type == PSI_T_BOOL) {
539 arg_val->cval = iarg->val.zend.bval;
540 } else {
541 arg_val->cval = zend_is_true(iarg->_zv);
542 }
543 break;
544 case PSI_T_INTVAL:
545 if (iarg->type->type == PSI_T_INT) {
546 arg_val->lval = iarg->val.zend.lval;
547 } else {
548 arg_val->lval = zval_get_long(iarg->_zv);
549 }
550 break;
551 case PSI_T_STRVAL:
552 if (iarg->type->type == PSI_T_STRING) {
553 arg_val->ptr = estrdup(iarg->val.zend.str->val);
554 darg->let->mem = arg_val->ptr;
555 zend_string_release(iarg->val.zend.str);
556 } else {
557 zend_string *zs = zval_get_string(iarg->_zv);
558 arg_val->ptr = estrdup(zs->val);
559 darg->let->mem = arg_val->ptr;
560 zend_string_release(zs);
561 }
562 break;
563 case PSI_T_STRLEN:
564 if (iarg->type->type == PSI_T_STRING) {
565 arg_val->lval = iarg->val.zend.str->len;
566 zend_string_release(iarg->val.zend.str);
567 } else {
568 zend_string *zs = zval_get_string(iarg->_zv);
569 arg_val->lval = zs->len;
570 zend_string_release(zs);
571 }
572 break;
573 case PSI_T_ARRVAL:
574 if (iarg->type->type == PSI_T_ARRAY) {
575 decl_type *type = real_decl_type(darg->type);
576
577 switch (type->type) {
578 case PSI_T_STRUCT:
579 arg_val->ptr = psi_array_to_struct(type->strct, HASH_OF(iarg->_zv));
580 darg->let->mem = arg_val->ptr;
581 break;
582 }
583 }
584 break;
585 EMPTY_SWITCH_DEFAULT_CASE();
586 }
587 }
588
589 if (darg->let->val && darg->let->val->is_reference) {
590 return &darg->let->ptr;
591 } else {
592 return darg->let->ptr;
593 }
594 }
595
596 void psi_do_set(zval *return_value, set_value *set)
597 {
598 impl_val *val = (impl_val *) &set->vars->vars[0]->arg->let->ptr;
599 token_t t = real_decl_type(set->vars->vars[0]->arg->type)->type;
600
601 ZVAL_DEREF(return_value);
602 zval_dtor(return_value);
603
604 set->func->handler(return_value, t, val, set, set->vars->vars[0]);
605 }
606
607 void psi_do_return(zval *return_value, return_stmt *ret, impl_val *ret_val)
608 {
609 token_t t = real_decl_type(ret->decl->type)->type;
610
611 ret->set->func->handler(return_value, t, ret_val, ret->set, ret->decl->var);
612 }
613
614 void psi_do_free(free_stmt *fre)
615 {
616 size_t i;
617
618 for (i = 0; i < fre->vars->count; ++i) {
619 decl_var *dvar = fre->vars->vars[i];
620
621 if (dvar->arg && dvar->arg->let->out.ptr) {
622 free(dvar->arg->let->out.ptr);
623 dvar->arg->let->out.ptr = NULL;
624 }
625 }
626 }
627
628 void psi_do_clean(impl *impl)
629 {
630 size_t i;
631
632 for (i = 0; i < impl->func->args->count; ++i ) {
633 impl_arg *iarg = impl->func->args->args[i];
634
635 switch (iarg->type->type) {
636 case PSI_T_STRING:
637 if (iarg->val.zend.str) {
638 zend_string_release(iarg->val.zend.str);
639 }
640 break;
641 }
642 }
643
644 if (impl->decl->args) for (i = 0; i < impl->decl->args->count; ++i) {
645 decl_arg *darg = impl->decl->args->args[i];
646
647 if (darg->let && darg->let->mem) {
648 decl_type *type = real_decl_type(darg->type);
649
650 if (type->type == PSI_T_STRUCT) {
651 void **ptr = (void **) ((char *) darg->let->mem + type->strct->size);
652
653 while (*ptr) {
654 efree(*ptr++);
655 }
656 }
657 efree(darg->let->mem);
658 darg->let->mem = NULL;
659 }
660 }
661 }
662
663 PHP_MINIT_FUNCTION(psi)
664 {
665 PSI_ContextOps *ops;
666
667 REGISTER_INI_ENTRIES();
668
669 if (!strcasecmp(PSI_G(engine), "jit")) {
670 ops = PSI_Libjit();
671 } else {
672 ops = PSI_Libffi();
673 }
674
675 PSI_ContextInit(&PSI_G(context), ops, psi_error);
676 PSI_ContextBuild(&PSI_G(context), PSI_G(directory));
677
678 return SUCCESS;
679 }
680 PHP_MSHUTDOWN_FUNCTION(psi)
681 {
682 PSI_ContextDtor(&PSI_G(context));
683
684 UNREGISTER_INI_ENTRIES();
685
686 return SUCCESS;
687 }
688
689 /* Remove if there's nothing to do at request start */
690 /* {{{ PHP_RINIT_FUNCTION
691 */
692 PHP_RINIT_FUNCTION(psi)
693 {
694 #if defined(COMPILE_DL_PSI) && defined(ZTS)
695 ZEND_TSRMLS_CACHE_UPDATE();
696 #endif
697 return SUCCESS;
698 }
699 /* }}} */
700
701 /* Remove if there's nothing to do at request end */
702 /* {{{ PHP_RSHUTDOWN_FUNCTION
703 */
704 PHP_RSHUTDOWN_FUNCTION(psi)
705 {
706 return SUCCESS;
707 }
708 /* }}} */
709
710 PHP_MINFO_FUNCTION(psi)
711 {
712 php_info_print_table_start();
713 php_info_print_table_header(2, "psi support", "enabled");
714 php_info_print_table_end();
715
716 DISPLAY_INI_ENTRIES();
717 }
718 const zend_function_entry psi_functions[] = {
719 PHP_FE_END
720 };
721
722 zend_module_entry psi_module_entry = {
723 STANDARD_MODULE_HEADER,
724 "psi",
725 psi_functions,
726 PHP_MINIT(psi),
727 PHP_MSHUTDOWN(psi),
728 PHP_RINIT(psi), /* Replace with NULL if there's nothing to do at request start */
729 PHP_RSHUTDOWN(psi), /* Replace with NULL if there's nothing to do at request end */
730 PHP_MINFO(psi),
731 PHP_PSI_VERSION,
732 STANDARD_MODULE_PROPERTIES
733 };
734
735 #ifdef COMPILE_DL_PSI
736 #ifdef ZTS
737 ZEND_TSRMLS_CACHE_DEFINE();
738 #endif
739 ZEND_GET_MODULE(psi)
740 #endif
741
742 /*
743 * Local variables:
744 * tab-width: 4
745 * c-basic-offset: 4
746 * End:
747 * vim600: noet sw=4 ts=4 fdm=marker
748 * vim<600: noet sw=4 ts=4
749 */