ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Faster/Faster.pm
(Generate patch)

Comparing Faster/Faster.pm (file contents):
Revision 1.7 by root, Fri Mar 10 00:13:15 2006 UTC vs.
Revision 1.13 by root, Fri Mar 10 18:53:49 2006 UTC

31my $LINK = "$Config{ld} $Config{ldflags} $Config{lddlflags} $Config{ccdlflags}"; 31my $LINK = "$Config{ld} $Config{ldflags} $Config{lddlflags} $Config{ccdlflags}";
32my $LIBS = "$Config{libs}"; 32my $LIBS = "$Config{libs}";
33my $_o = $Config{_o}; 33my $_o = $Config{_o};
34my $_so = ".so"; 34my $_so = ".so";
35 35
36# we don't need no steenking PIC on x86
37$COMPILE =~ s/-f(?:PIC|pic)//g
38 if $Config{archname} =~ /^(i[3456]86)-/;
39
40my $opt_assert = 1;
41
36our $source; 42our $source;
37our $label_next; 43
38our $label_last; 44my @ops;
39our $label_redo; 45my $op;
46my $op_name;
47my @loop;
40 48
41my %flag; 49my %flag;
42 50
51# complex flag steting is no longer required, rewrite this ugly code
43for (split /\n/, <<EOF) { 52for (split /\n/, <<EOF) {
44 leavesub unsafe 53 leavesub unsafe
45 leavesublv unsafe 54 leavesublv unsafe
46 return unsafe 55 return unsafe
47 flip unsafe 56 flip unsafe
50 redo unsafe 59 redo unsafe
51 next unsafe 60 next unsafe
52 eval unsafe 61 eval unsafe
53 leaveeval unsafe 62 leaveeval unsafe
54 entertry unsafe 63 entertry unsafe
55 substconst unsafe
56 formline unsafe 64 formline unsafe
57 grepstart unsafe 65 grepstart unsafe
66 mapstart unsafe
67 substcont unsafe
68 entereval unsafe noasync todo
58 require unsafe 69 require unsafe
59 match unsafe todo
60 subst unsafe todo
61 entereval unsafe todo
62 mapstart unsafe todo
63 70
71 mapstart noasync
64 pushmark noasync 72 grepstart noasync
73 match noasync
74
65 padsv noasync 75 last noasync
76 next noasync
77 redo noasync
78 seq noasync
79 pushmark noasync extend=0
80 padsv noasync extend=1
81 padav noasync extend=1
82 padhv noasync extend=1
83 padany noasync extend=1
66 entersub noasync 84 entersub noasync
67 aassign noasync 85 aassign noasync
68 sassign noasync 86 sassign noasync
69 rv2av noasync 87 rv2av noasync
88 rv2cv noasync
89 rv2gv noasync
90 rv2hv noasync
91 refgen noasync
70 nextstate noasync 92 nextstate noasync
71 gv noasync 93 gv noasync
72 gvsv noasync 94 gvsv noasync
73 add noasync 95 add noasync
74 subtract noasync 96 subtract noasync
77 complement noasync 99 complement noasync
78 cond_expr noasync 100 cond_expr noasync
79 and noasync 101 and noasync
80 or noasync 102 or noasync
81 not noasync 103 not noasync
104 defined noasync
82 method_named noasync 105 method_named noasync
83 preinc noasync 106 preinc noasync
84 postinc noasync 107 postinc noasync
85 predec noasync 108 predec noasync
86 postdec noasync 109 postdec noasync
87 stub noasync 110 stub noasync
88 unstack noasync 111 unstack noasync
89 leaveloop noasync 112 leaveloop noasync
90 shift noasync
91 aelemA noasync 113 aelem noasync
92 aelemfast noasync 114 aelemfast noasync
115 helem noasync
116 pushre noasync
117 subst noasync
118 const noasync extend=1
119 list noasync
120 join noasync
121 split noasync
122 concat noasync
123 push noasync
124 pop noasync
125 shift noasync
126 unshift noasync
127 length noasync
128 substr noasync
129 stringify noasync
130 eq noasync
131 ne noasync
132 gt noasync
133 lt noasync
134 ge noasync
135 le noasync
136 enteriter noasync
137 ord noasync
138
139 iter async
93EOF 140EOF
94 my (undef, $op, @flags) = split /\s+/; 141 my (undef, $op, @flags) = split /\s+/;
95 142
96 undef $flag{$_}{$op} 143 undef $flag{$_}{$op}
97 for ("known", @flags); 144 for ("known", @flags);
98} 145}
99 146
147my %callop = (
148 entersub => "(PL_ppaddr [OP_ENTERSUB]) (aTHX)",
149 mapstart => "Perl_pp_grepstart (aTHX)",
150);
151
152sub callop {
153 $callop{$op_name} || "Perl_pp_$op_name (aTHX)"
154}
155
156sub assert {
157 return unless $opt_assert;
158 $source .= " assert ((\"$op_name\", ($_[0])));\n";
159}
160
161sub out_callop {
162 assert "nextop == (OP *)$$op";
163 $source .= " PL_op = nextop; nextop = " . (callop $op) . ";\n";
164}
165
166sub out_jump_next {
167 assert "nextop == (OP *)${$op->next}";
168 $source .= " goto op_${$op->next};\n";
169}
170
100sub out_next { 171sub out_next {
101 my ($op) = @_;
102
103 if (${$op->next}) {
104 $source .= " nextop = (OP *)${$op->next}L;\n"; 172 $source .= " nextop = (OP *)${$op->next}L;\n";
105 $source .= " goto op_${$op->next};\n";
106 } else {
107 $source .= " return 0;\n";
108 }
109}
110 173
111sub callop { 174 out_jump_next;
112 my ($op) = @_;
113
114 my $name = $op->name;
115
116 $name eq "entersub"
117 ? "(PL_ppaddr [OP_ENTERSUB]) (aTHX)"
118 : $name eq "mapstart"
119 ? "Perl_pp_grepstart (aTHX)"
120 : "Perl_pp_$name (aTHX)"
121} 175}
176
177sub out_linear {
178 out_callop;
179 out_jump_next;
180}
181
182sub out_cond_jump {
183 $source .= " if (nextop == (OP *)${$_[0]}L) goto op_${$_[0]};\n";
184}
185
186sub op_entersub {
187 out_callop;
188 $source .= " RUNOPS_TILL ((OP *)${$op->next}L);\n";
189 out_jump_next;
190}
191
192*op_require = \&op_entersub;
122 193
123sub op_nextstate { 194sub op_nextstate {
124 my ($op) = @_;
125
126 $source .= " PL_curcop = (COP *)nextop;\n"; 195 $source .= " PL_curcop = (COP *)nextop;\n";
127 $source .= " PL_stack_sp = PL_stack_base + cxstack[cxstack_ix].blk_oldsp;\n"; 196 $source .= " PL_stack_sp = PL_stack_base + cxstack[cxstack_ix].blk_oldsp;\n";
128 $source .= " FREETMPS;\n"; 197 $source .= " FREETMPS;\n";
129 198
130 out_next $op; 199 out_next;
131} 200}
132 201
133sub op_pushmark { 202sub op_pushmark {
134 my ($op) = @_;
135
136 $source .= " PUSHMARK (PL_stack_sp);\n"; 203 $source .= " PUSHMARK (PL_stack_sp);\n";
137 204
138 out_next $op; 205 out_next;
139} 206}
140 207
141sub op_const { 208if ($Config{useithreads} ne "define") {
142 my ($op) = @_; 209 # disable optimisations on ithreads
143 210
211 *op_const = sub {
144 $source .= " { dSP; XPUSHs ((SV *)${$op->sv}L); PUTBACK; }\n"; 212 $source .= " { dSP; XPUSHs ((SV *)${$op->sv}L); PUTBACK; }\n";
145 213
146 out_next $op; 214 out_next;
147} 215 };
148 216
149*op_gv = \&op_const; 217 *op_gv = \&op_const;
150 218
219 *op_aelemfast = sub {
220 my $targ = $op->targ;
221 my $private = $op->private;
222
223 $source .= " {\n";
224
225 if ($op->flags & B::OPf_SPECIAL) {
226 $source .= " AV *av = (AV*)PAD_SV((PADOFFSET)$targ);\n";
227 } else {
228 $source .= " AV *av = GvAV ((GV *)${$op->sv}L);\n";
229 }
230
231 if ($op->flags & B::OPf_MOD) {
232 $source .= " SV *sv = *av_fetch (av, $private, 1);\n";
233 } else {
234 $source .= " SV **svp = av_fetch (av, $private, 0); SV *sv = svp ? *svp : &PL_sv_undef;\n";
235 }
236
237 if (!($op->flags & B::OPf_MOD)) {
238 $source .= " if (SvGMAGICAL (sv)) sv = sv_mortalcopy (sv);\n";
239 }
240
241 $source .= " dSP;\n";
242 $source .= " XPUSHs (sv);\n";
243 $source .= " PUTBACK;\n";
244 $source .= " }\n";
245
246 out_next;
247 };
248
249 *op_gvsv = sub {
250 $source .= " {\n";
251 $source .= " dSP;\n";
252 $source .= " EXTEND (SP, 1);\n";
253
254 if ($op->private & B::OPpLVAL_INTRO) {
255 $source .= " PUSHs (save_scalar ((GV *)${$op->sv}L));\n";
256 } else {
257 $source .= " PUSHs (GvSV ((GV *)${$op->sv}L));\n";
258 }
259
260 $source .= " PUTBACK;\n";
261 $source .= " }\n";
262
263 out_next;
264 };
265}
266
267# does kill Crossfire/res2pm
151sub op_stringify { 268sub op_stringify {
152 my ($op) = @_; 269 my $targ = $op->targ;
153 270
154 $source .= " { dSP; dTARGET; sv_copypv (TARG, TOPs); SETTARG; }\n"; 271 $source .= <<EOF;
272 {
273 dSP;
274 SV *targ = PAD_SV ((PADOFFSET)$targ);
275 sv_copypv (TARG, TOPs);
276 SETTARG;
277 PUTBACK;
278 }
279EOF
155 280
156 out_next $op; 281 out_next;
157} 282}
158 283
159sub op_and { 284sub op_and {
160 my ($op) = @_;
161
162 $source .= <<EOF; 285 $source .= <<EOF;
163 { 286 {
164 dSP; 287 dSP;
165 288
166 if (SvTRUE (TOPs)) 289 if (SvTRUE (TOPs))
171 goto op_${$op->other}; 294 goto op_${$op->other};
172 } 295 }
173 } 296 }
174EOF 297EOF
175 298
176 out_next $op; 299 out_next;
177} 300}
178 301
179sub op_or { 302sub op_or {
180 my ($op) = @_;
181
182 $source .= <<EOF; 303 $source .= <<EOF;
183 { 304 {
184 dSP; 305 dSP;
185 306
186 if (!SvTRUE (TOPs)) 307 if (!SvTRUE (TOPs))
191 goto op_${$op->other}; 312 goto op_${$op->other};
192 } 313 }
193 } 314 }
194EOF 315EOF
195 316
196 out_next $op; 317 out_next;
197} 318}
198 319
199sub op_padsv { 320sub op_padsv {
200 my ($op) = @_;
201
202 my $flags = $op->flags; 321 my $flags = $op->flags;
203 my $target = $op->targ; 322 my $target = $op->targ;
204 323
205 $source .= <<EOF; 324 $source .= <<EOF;
206 { 325 {
218 } 337 }
219 $source .= <<EOF; 338 $source .= <<EOF;
220 } 339 }
221EOF 340EOF
222 341
223 out_next $op; 342 out_next;
224}
225
226sub op_aelemfast {
227 my ($op) = @_;
228
229 my $targ = $op->targ;
230 my $private = $op->private;
231
232 $source .= " {\n";
233
234 if ($op->flags & B::OPf_SPECIAL) {
235 $source .= " AV *av = (AV*)PAD_SV((PADOFFSET)$targ);\n";
236 } else {
237 $source .= " AV *av = GvAV ((GV *)${$op->sv}L);\n";
238 }
239
240 if ($op->flags & B::OPf_MOD) {
241 $source .= " SV *sv = *av_fetch (av, $private, 1);\n";
242 } else {
243 $source .= " SV **svp = av_fetch (av, $private, 0); SV *sv = svp ? *svp : &PL_sv_undef;\n";
244 }
245
246 if (!($op->flags & B::OPf_MOD)) {
247 $source .= " if (SvGMAGICAL (sv)) sv = sv_mortalcopy (sv);\n";
248 }
249
250 $source .= " dSP;\n";
251 $source .= " XPUSHs (sv);\n";
252 $source .= " PUTBACK;\n";
253 $source .= " }\n";
254
255 out_next $op;
256} 343}
257 344
258# pattern const+ (or general push1) 345# pattern const+ (or general push1)
259# pattern pushmark return(?) 346# pattern pushmark return(?)
260# pattern pushmark gv rv2av pushmark padsv+o.ä. aassign 347# pattern pushmark gv rv2av pushmark padsv+o.ä. aassign
261 348
262# pattern const method_named 349# pattern const method_named
263sub op_method_named { 350sub op_method_named {
264 my ($op) = @_;
265
266 $source .= <<EOF; 351 $source .= <<EOF;
267 { 352 {
268 static HV *last_stash; 353 static HV *last_stash;
269 static SV *last_res; 354 static SV *last_cv;
355 static U32 last_sub_generation;
270 356
271 SV *obj = *(PL_stack_base + TOPMARK + 1); 357 SV *obj = *(PL_stack_base + TOPMARK + 1);
272 358
273 if (SvROK (obj) && SvOBJECT (SvRV (obj))) 359 if (!SvGMAGICAL (obj) && SvROK (obj) && SvOBJECT (SvRV (obj)))
274 { 360 {
275 dSP; 361 dSP;
276 HV *stash = SvSTASH (SvRV (obj)); 362 HV *stash = SvSTASH (SvRV (obj));
277 363
278 /* simple "polymorphic" inline cache */ 364 /* simple "polymorphic" inline cache */
279 if (stash == last_stash) 365 if (stash == last_stash
366 && PL_sub_generation == last_sub_generation)
280 { 367 {
281 XPUSHs (last_res); 368 XPUSHs (last_cv);
282 PUTBACK; 369 PUTBACK;
283 } 370 }
284 else 371 else
285 { 372 {
286 PL_op = nextop;
287 nextop = Perl_pp_method_named (aTHX); 373 PL_op = nextop; nextop = Perl_pp_method_named (aTHX);
288 374
289 SPAGAIN; 375 SPAGAIN;
376 last_sub_generation = PL_sub_generation;
290 last_stash = stash; 377 last_stash = stash;
291 last_res = TOPs; 378 last_cv = TOPs;
292 } 379 }
293 } 380 }
294 else 381 else
295 { 382 {
296 /* error case usually */ 383 /* error case usually */
297 PL_op = nextop;
298 nextop = Perl_pp_method_named (aTHX); 384 PL_op = nextop; nextop = Perl_pp_method_named (aTHX);
299 } 385 }
300 } 386 }
301EOF 387EOF
302 388
303 out_next $op; 389 out_next;
390}
391
392sub op_grepstart {
393 out_callop;
394 out_cond_jump $op->next->other;
395 out_jump_next;
396}
397
398*op_mapstart = \&op_grepstart;
399
400sub op_substcont {
401 out_callop;
402 out_cond_jump $op->other->pmreplstart;
403 assert "nextop == (OP *)${$op->other->next}L";
404 $source .= " goto op_${$op->other->next};\n";
405}
406
407sub out_break_op {
408 my ($idx) = @_;
409
410 out_callop;
411
412 out_cond_jump $_->[$idx]
413 for reverse @loop;
414
415 $source .= " return nextop;\n";
416}
417
418sub xop_next {
419 out_break_op 0;
420}
421
422sub op_last {
423 out_break_op 1;
424}
425
426sub xop_redo {
427 out_break_op 2;
304} 428}
305 429
306sub cv2c { 430sub cv2c {
307 my ($cv) = @_; 431 my ($cv) = @_;
308 432
433 @loop = ();
434
309 my %opsseen; 435 my %opsseen;
310 my @ops;
311 my @todo = $cv->START; 436 my @todo = $cv->START;
312 437
313 while (my $op = shift @todo) { 438 while (my $op = shift @todo) {
314 for (; $$op; $op = $op->next) { 439 for (; $$op; $op = $op->next) {
315 last if $opsseen{$$op}++; 440 last if $opsseen{$$op}++;
316 push @ops, $op; 441 push @ops, $op;
442
317 my $name = $op->name; 443 my $name = $op->name;
444 my $class = B::class $op;
445
318 if (B::class($op) eq "LOGOP") { 446 if ($class eq "LOGOP") {
319 push @todo, $op->other; 447 unshift @todo, $op->other; # unshift vs. push saves jumps
320 } elsif ($name eq "subst" and ${ $op->pmreplstart }) { 448 } elsif ($class eq "PMOP") {
321 push @todo, $op->pmreplstart; 449 unshift @todo, $op->pmreplstart if ${$op->pmreplstart};
322 } elsif ($name =~ /^enter(loop|iter)$/) { 450 } elsif ($class eq "LOOP") {
323# if ($] > 5.009) { 451 push @loop, [$op->nextop, $op->lastop->next, $op->redoop->next];
324# $labels{${$op->nextop}} = "NEXT";
325# $labels{${$op->lastop}} = "LAST";
326# $labels{${$op->redoop}} = "REDO";
327# } else {
328# $labels{$op->nextop->seq} = "NEXT";
329# $labels{$op->lastop->seq} = "LAST";
330# $labels{$op->redoop->seq} = "REDO";
331# }
332 } 452 }
333 } 453 }
334 } 454 }
335 455
336 local $source = <<EOF; 456 local $source = <<EOF;
457OP *%%%FUNC%%% (pTHX)
458{
459 register OP *nextop = (OP *)${$ops[0]}L;
460EOF
461
462 while (@ops) {
463 $op = shift @ops;
464 $op_name = $op->name;
465
466 $source .= "op_$$op: /* $op_name */\n";
467 #$source .= "fprintf (stderr, \"$$op in op $op_name\\n\");\n";#d#
468 #$source .= "{ dSP; sv_dump (TOPs); }\n";#d#
469
470 $source .= " PERL_ASYNC_CHECK ();\n"
471 unless exists $flag{noasync}{$op_name};
472
473 if (my $can = __PACKAGE__->can ("op_$op_name")) {
474 # handcrafted replacement
475 $can->($op);
476
477 } elsif (exists $flag{unsafe}{$op_name}) {
478 # unsafe, return to interpreter
479 assert "nextop == (OP *)$$op";
480 $source .= " return nextop;\n";
481
482 } elsif ("LOGOP" eq B::class $op) {
483 # logical operation with optionaö branch
484 out_callop;
485 out_cond_jump $op->other;
486 out_jump_next;
487
488 } elsif ("PMOP" eq B::class $op) {
489 # regex-thingy
490 out_callop;
491 out_cond_jump $op->pmreplroot if ${$op->pmreplroot};
492 out_jump_next;
493
494 } else {
495 # normal operator, linear execution
496 out_linear;
497 }
498 }
499
500 $op_name = "func exit"; assert (0);
501
502 $source .= <<EOF;
503op_0:
504 return 0;
505}
506EOF
507 #warn $source;
508
509 $source
510}
511
512sub source2ptr {
513 my ($source) = @_;
514
515 my $md5 = Digest::MD5::md5_hex $source;
516 $source =~ s/%%%FUNC%%%/Faster_$md5/;
517
518 my $stem = "/tmp/$md5";
519
520 unless (-e "$stem$_so") {
521 open FILE, ">:raw", "$stem.c";
522 print FILE <<EOF;
337#define PERL_NO_GET_CONTEXT 523#define PERL_NO_GET_CONTEXT
338 524
339#include <assert.h> 525#include <assert.h>
340 526
341#include "EXTERN.h" 527#include "EXTERN.h"
342#include "perl.h" 528#include "perl.h"
343#include "XSUB.h" 529#include "XSUB.h"
344 530
345/*typedef OP *(*PPFUNC)(pTHX);*/ 531#define RUNOPS_TILL(op) \\
346 532 while (nextop != (op)) \\
347OP *%%%FUNC%%% (pTHX) 533 { \\
348{
349 register OP *nextop = (OP *)${$ops[0]}L;
350EOF
351
352 for my $op (@ops) {
353 my $name = $op->name;
354 my $ppaddr = ppaddr $op->type;
355
356 $source .= "op_$$op: /* $name */\n";
357 #$source .= "fprintf (stderr, \"$$op in op $name\\n\");\n";#d#
358 #$source .= "{ dSP; sv_dump (TOPs); }\n";#d#
359
360 unless (exists $flag{noasync}{$name}) {
361 $source .= " PERL_ASYNC_CHECK ();\n";
362 }
363
364 if (my $can = __PACKAGE__->can ("op_$name")) {
365 $can->($op);
366 } elsif (exists $flag{unsafe}{$name}) {
367 $source .= " assert ((\"$name\", nextop == (OP *)$$op));\n";#d#
368 $source .= " PL_op = nextop; return " . (callop $op) . ";\n";
369 } elsif ("LOGOP" eq B::class $op or exists $flag{otherop}{$name}) {
370 $source .= " assert ((\"$name\", nextop == (OP *)$$op));\n";#d#
371 $source .= " PL_op = nextop; nextop = " . (callop $op) . ";\n";
372 $source .= " if (nextop == (OP *)${$op->other}L) goto op_${$op->other};\n";
373 $source .= " assert ((\"$name\", nextop == (OP *)${$op->next}));\n";#d#
374 $source .= ${$op->next} ? " goto op_${$op->next};\n" : " return 0;\n";
375 } else {
376 $source .= " assert ((\"$name\", nextop == (OP *)$$op));\n";#d#
377 $source .= " PL_op = nextop; nextop = " . (callop $op) . ";\n";
378 if ($name eq "entersub") {
379 $source .= <<EOF;
380while (nextop != (OP *)${$op->next})
381 {
382 PERL_ASYNC_CHECK (); 534 PERL_ASYNC_CHECK (); \\
383 PL_op = nextop; nextop = (PL_op->op_ppaddr)(aTHX); 535 PL_op = nextop; nextop = (PL_op->op_ppaddr)(aTHX); \\
384 }
385EOF
386 }
387 $source .= " assert ((\"$name\", nextop == (OP *)${$op->next}));\n";#d#
388 $source .= ${$op->next} ? " goto op_${$op->next};\n" : " return 0;\n";
389 }
390 } 536 }
391 537
392 $source .= "}\n"; 538EOF
393 #warn $source;
394
395 $source
396}
397
398sub source2ptr {
399 my ($source) = @_;
400
401 my $md5 = Digest::MD5::md5_hex $source;
402 $source =~ s/%%%FUNC%%%/Faster_$md5/;
403
404 my $stem = "/tmp/$md5";
405
406 unless (-e "$stem$_so") {
407 open FILE, ">:raw", "$stem.c";
408 print FILE $source; 539 print FILE $source;
409 close FILE; 540 close FILE;
410 system "$COMPILE -o $stem$_o $stem.c"; 541 system "$COMPILE -o $stem$_o $stem.c";
411 system "$LINK -o $stem$_so $stem$_o $LIBS"; 542 system "$LINK -o $stem$_so $stem$_o $LIBS";
412 } 543 }
420} 551}
421 552
422sub entersub { 553sub entersub {
423 my ($cv) = @_; 554 my ($cv) = @_;
424 555
556 # always compile the whole stash
557# my @stash = $cv->STASH->ARRAY;
558# warn join ":", @stash;
559# exit;
560
425 eval { 561 eval {
426 my $source = cv2c $cv; 562 my $source = cv2c $cv;
427 563
428 my $ptr = source2ptr $source; 564 my $ptr = source2ptr $source;
429 565
430 patch_cv $cv, $ptr; 566 patch_cv $cv, $ptr;
431 }; 567 };
432 568
433 warn $@ if $@; 569 warn $@ if $@;
434} 570}
435 571
437 573
4381; 5741;
439 575
440=back 576=back
441 577
442=head1 LIMITATIONS 578=head1 BUGS/LIMITATIONS
443 579
444Tainting and debugging will disable Faster. 580Perl will check much less often for asynchronous signals in
581Faster-compiled code. It tries to check on every function call, loop
582iteration and every I/O operator, though.
583
584The following things will disable Faster. If you manage to enable them at
585runtime, bad things will happen.
586
587 enabled tainting
588 enabled debugging
589
590This will dramatically reduce Faster's performance:
591
592 threads (but you don't care about speed if you use threads anyway)
593
594These constructs will force the use of the interpreter as soon as they are
595being executed, for the rest of the currently executed:
596
597 .., ... (flipflop operators)
598 goto
599 next, redo (but not well-behaved last's)
600 eval
601 require
602 any use of formats
445 603
446=head1 AUTHOR 604=head1 AUTHOR
447 605
448 Marc Lehmann <schmorp@schmorp.de> 606 Marc Lehmann <schmorp@schmorp.de>
449 http://home.schmorp.de/ 607 http://home.schmorp.de/

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines