Browse code

script engine: switch() fixup and optimizations

- optimize/transform switch() statements into:
- jump tables (if small enough)
- condition tables
- simple blocks (optimization when the expression in switch()
is constant)
- only a condition evaluator (when the switch body is empty,
but the switch expression has side-effect and hence cannot be
optimized away)

Andrei Pelinescu-Onciul authored on 04/02/2009 19:48:04
Showing 2 changed files
... ...
@@ -82,6 +82,7 @@
82 82
 #include "str_hash.h"
83 83
 #include "ut.h"
84 84
 #include "rvalue.h"
85
+#include "switch.h"
85 86
 
86 87
 #define RT_HASH_SIZE	8 /* route names hash */
87 88
 
... ...
@@ -697,7 +698,29 @@ int fix_actions(struct action* a)
697 697
 						return ret;
698 698
 				}
699 699
 				break;
700
-
700
+			case SWITCH_T:
701
+				if (t->val[0].type!=RVE_ST){
702
+					LOG(L_CRIT, "BUG: fix_actions: invalid subtype"
703
+								"%d for if (should be expr)\n",
704
+								t->val[0].type);
705
+					return E_BUG;
706
+				}else if (t->val[1].type!=CASE_ST){
707
+					LOG(L_CRIT, "BUG: fix_actions: invalid subtype"
708
+								"%d for switch(...){...}(should be action)\n",
709
+								t->val[1].type);
710
+					return E_BUG;
711
+				}
712
+				if (t->val[0].u.data){
713
+					if ((ret=fix_rval_expr(&t->val[0].u.data))<0)
714
+						return ret;
715
+				}else{
716
+					LOG(L_CRIT, "BUG: fix_actions: null switch()"
717
+							" expression\n");
718
+					return E_BUG;
719
+				}
720
+				if ((ret=fix_switch(t))<0)
721
+					return ret;
722
+				break;
701 723
 			case ASSIGN_T:
702 724
 			case ADD_T:
703 725
 				if (t->val[0].type !=LVAL_ST) {
704 726
new file mode 100644
... ...
@@ -0,0 +1,374 @@
0
+/* 
1
+ * $Id$
2
+ * 
3
+ * Copyright (C) 2009 iptelorg GmbH
4
+ *
5
+ * Permission to use, copy, modify, and distribute this software for any
6
+ * purpose with or without fee is hereby granted, provided that the above
7
+ * copyright notice and this permission notice appear in all copies.
8
+ *
9
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16
+ */
17
+/*
18
+ * switch.c
19
+ */
20
+/*
21
+ * History:
22
+ * --------
23
+ *  2009-02-02  initial version (andrei)
24
+*/
25
+
26
+#include "switch.h"
27
+#include "rvalue.h"
28
+#include "route.h"
29
+#include "mem/mem.h"
30
+#include "error.h"
31
+
32
+#define MAX_JT_SIZE 100  /* maximum jump table size */
33
+
34
+
35
+/** create a cond table structure (pkg_malloc'ed).
36
+ * @return 0 on error, pointer on success
37
+ */
38
+static struct switch_cond_table* mk_switch_cond_table(int n)
39
+{
40
+	struct switch_cond_table* sct;
41
+	
42
+	/* allocate everything in a single block, better for cache locality */
43
+	sct=pkg_malloc(ROUND_INT(sizeof(*sct))+
44
+					ROUND_POINTER(n*sizeof(sct->cond[0]))+
45
+					n*sizeof(sct->jump[0]));
46
+	if (sct==0) return 0;
47
+	sct->n=n;
48
+	sct->cond=(int*)((char*)sct+ROUND_INT(sizeof(*sct)));
49
+	sct->jump=(struct action**)
50
+				((char*)sct->cond+ROUND_POINTER(n*sizeof(sct->cond[0])));
51
+	sct->def=0;
52
+	return sct;
53
+}
54
+
55
+
56
+
57
+/** create a jump table structure (pkg_malloc'ed).
58
+ * @param jmp_size - size of the jump table
59
+ * @param rest - size of the fallback condition table
60
+ * @return 0 on error, pointer on success
61
+ */
62
+static struct switch_jmp_table* mk_switch_jmp_table(int jmp_size, int rest)
63
+{
64
+	struct switch_jmp_table* jt;
65
+	int size;
66
+	
67
+	/* alloc everything in a block */
68
+	size = 	ROUND_POINTER(sizeof(*jt))+
69
+			ROUND_INT(jmp_size*sizeof(jt->tbl[0]))+
70
+			ROUND_POINTER(rest*sizeof(jt->rest.cond[0]))+
71
+			rest*sizeof(jt->rest.jump[0]);
72
+	jt=pkg_malloc(size);
73
+	if (jt == 0) return 0;
74
+	memset(jt, 0, size);
75
+	jt->tbl = (struct action**)((char*) jt + ROUND_POINTER(sizeof(*jt)));
76
+	jt->rest.cond = (int*)
77
+					((char*) jt->tbl + ROUND_INT(jmp_size*sizeof(jt->tbl[0])));
78
+	jt->rest.jump = (struct action**) ((char*) jt->rest.cond + 
79
+								ROUND_POINTER(rest*sizeof(jt->rest.cond[0])));
80
+	jt->rest.n=rest;
81
+	return jt;
82
+}
83
+
84
+
85
+
86
+void destroy_case_stms(struct case_stms *lst)
87
+{
88
+	struct case_stms* l;
89
+	struct case_stms* n;
90
+	
91
+	for (l=lst; l; l=n){
92
+		n=l->next;
93
+		rve_destroy(l->ct_rve);
94
+		/* TODO: the action list is not freed (missing destroy_action() and
95
+		   there are some case when we need at least part of the action list
96
+		*/
97
+		pkg_free(l);
98
+	}
99
+}
100
+
101
+
102
+
103
+/** fixup function for SWITCH_T actions.
104
+ * can produce 4 different action types:
105
+ *  - BLOCK_T (actions) - actions grouped in a block, break ends the block
106
+ *    execution.
107
+ *  - EVAL_T (cond)  - null switch block, but the condition has to be
108
+ *                       evaluated due to possible side-effects.
109
+ *  - SWITCH_COND_T(cond, jumps) - condition table
110
+ *  - SWITCH_JT_T(cond, jumptable) - jumptable + condition table
111
+ * TODO: external optimizers that would "flatten" BLOCK_T w/no breaks or
112
+ * breaks at the end.
113
+ * 
114
+ */
115
+int fix_switch(struct action* t)
116
+{
117
+	struct case_stms* c;
118
+	int n, i, j, ret, val;
119
+	struct action* a;
120
+	struct action* block;
121
+	struct action* def_a;
122
+	struct action* action_lst;
123
+	struct action** tail;
124
+	struct switch_jmp_table* jmp;
125
+	struct switch_cond_table* sct;
126
+	int labels_no;
127
+	struct action** def_jmp_bm;
128
+	int* cond;
129
+	struct action*** jmp_bm;
130
+	int default_found;
131
+	int first, last, start, end, hits, best_hits;
132
+	struct rval_expr* sw_rve;
133
+	
134
+	ret=E_BUG;
135
+	cond=0;
136
+	jmp_bm=0;
137
+	def_jmp_bm=0;
138
+	labels_no=0;
139
+	default_found=0;
140
+	sw_rve=(struct rval_expr*)t->val[0].u.data;
141
+	/*  handle null actions: optimize away if no
142
+	   sideffects */
143
+	if (t->val[1].u.data==0){
144
+		if (!rve_has_side_effects(sw_rve)){
145
+			t->type=BLOCK_T;
146
+			rve_destroy(sw_rve);
147
+			t->val[0].type=BLOCK_ST;
148
+			t->val[0].u.data=0;
149
+			DBG("SWITCH: null switch optimized away\n");
150
+		}else{
151
+			t->type=EVAL_T;
152
+			t->val[0].type=RVE_ST;
153
+			DBG("SWITCH: null switch turned to EVAL_T\n");
154
+		}
155
+		return 0;
156
+	}
157
+	def_a=0;
158
+	n=0;
159
+	for (c=(struct case_stms*)t->val[1].u.data; c; c=c->next){
160
+		if (c->ct_rve){
161
+			if (!rve_is_constant(c->ct_rve)){
162
+				LOG(L_ERR, "ERROR: fix_switch: non constant "
163
+						"expression in case\n");
164
+				return E_BUG;
165
+			}
166
+			if (rval_expr_eval_int(0, 0,  &c->int_label, c->ct_rve)
167
+					<0){
168
+				LOG(L_ERR, "ERROR: fix_switch: case expression"
169
+						" (%d,%d) has non-interger type\n",
170
+						c->ct_rve->fpos.s_line,
171
+						c->ct_rve->fpos.s_col);
172
+				return E_BUG;
173
+			}
174
+			c->is_default=0;
175
+			n++; /* count only non-default cases */
176
+		}else{
177
+			if (default_found){
178
+				LOG(L_ERR, "ERROR: fix_switch: more then one \"default\""
179
+						" label found (%d, %d)\n",
180
+						c->ct_rve->fpos.s_line,
181
+						c->ct_rve->fpos.s_col);
182
+				return E_UNSPEC;
183
+			}
184
+			default_found=1;
185
+			c->int_label=-1;
186
+			c->is_default=1;
187
+			def_a=c->actions;
188
+		}
189
+		if ( c->actions && ((ret=fix_actions(c->actions))<0))
190
+			goto error;
191
+	}
192
+	DBG("SWITCH: %d cases, %d default\n", n, default_found);
193
+	/*: handle n==0 (no case only a default:) */
194
+	if (n==0){
195
+		if (default_found){
196
+			if (!rve_has_side_effects(sw_rve)){
197
+				t->type=BLOCK_T;
198
+				rve_destroy(sw_rve);
199
+				destroy_case_stms(t->val[1].u.data);
200
+				t->val[0].type=BLOCK_ST;
201
+				t->val[0].u.data=def_a;
202
+				t->val[1].type=0;
203
+				t->val[1].u.data=0;
204
+				DBG("SWITCH: default only switch optimized away (BLOCK_T)\n");
205
+				return 0;
206
+			}
207
+			DBG("SWITCH: default only switch with side-effect...\n");
208
+		}else{
209
+			LOG(L_CRIT, "BUG: fix_switch: empty switch not expected at this"
210
+					" point\n");
211
+			ret=E_BUG;
212
+			goto error;
213
+		}
214
+	}
215
+	labels_no=n;
216
+	cond=pkg_malloc(sizeof(cond[0])*n);
217
+	jmp_bm=pkg_malloc(sizeof(jmp_bm[0])*n);
218
+	if (cond==0 || jmp_bm==0){
219
+		LOG(L_ERR, "ERROR: fix_switch: memory allocation failure\n");
220
+		ret=E_OUT_OF_MEM;
221
+		goto error;
222
+	}
223
+	
224
+	/* fill condition table and jump point bookmarks and "flatten" the action 
225
+	   lists (transform them into a single list for the entire switch, rather
226
+	    then one block per case ) */
227
+	n=0;
228
+	action_lst=0;
229
+	tail=&action_lst;
230
+	for (c=(struct case_stms*)t->val[1].u.data; c; c=c->next){
231
+		a=c->actions;
232
+		if (a){
233
+			for (; a->next; a=a->next);
234
+			if (action_lst==0)
235
+				action_lst=c->actions;
236
+			else
237
+				*tail=c->actions;
238
+		}
239
+		if (c->is_default){
240
+			def_jmp_bm=tail;
241
+		} else {
242
+			for (j=0; j<n; j++){
243
+				if (cond[j]==c->int_label){
244
+					LOG(L_ERR, "ERROR: fix_switch: duplicate case (%d,%d)\n",
245
+							c->ct_rve->fpos.s_line, c->ct_rve->fpos.s_col);
246
+					ret=E_UNSPEC;
247
+					goto error;
248
+				}
249
+			}
250
+			cond[n]=c->int_label;
251
+			jmp_bm[n]=tail;
252
+			n++;
253
+		}
254
+		if (c->actions)
255
+			tail=&a->next;
256
+	}
257
+	/* handle constant rve w/ no side-effects: replace the whole case 
258
+	   with the case rve block */
259
+	if ( (scr_opt_lev>=2) &&
260
+			!rve_has_side_effects(sw_rve) && rve_is_constant(sw_rve)){
261
+		if (rval_expr_eval_int(0, 0,  &val, sw_rve) <0){
262
+			LOG(L_ERR, "ERROR: fix_switch: wrong type for switch(...) "
263
+					"expression (%d,%d)\n", 
264
+					sw_rve->fpos.s_line, sw_rve->fpos.s_col);
265
+			ret=E_UNSPEC;
266
+			goto error;
267
+		}
268
+		/* start with the "default:" value in case nothing is found */
269
+		block=def_jmp_bm?*def_jmp_bm:0;
270
+		for (i=0; i<n; i++){
271
+			if (cond[i]==val){
272
+				block=*jmp_bm[i];
273
+				break;
274
+			}
275
+		}
276
+		t->type=BLOCK_T;
277
+		rve_destroy(sw_rve);
278
+		t->val[0].type=BLOCK_ST;
279
+		t->val[0].u.data=block;
280
+		destroy_case_stms(t->val[1].u.data);
281
+		t->val[1].type=0;
282
+		t->val[1].u.data=0;
283
+		ret=0;
284
+		DBG("SWITCH: constant switch(%d) with %d cases optimized away to case "
285
+				" %d \n", val, n, i);
286
+		goto end;
287
+	}
288
+	/* try to create a jumptable */
289
+	/* cost: 2 cmp & table lookup
290
+	   => makes sense for more then 3 cases
291
+	   & if size< MAX_JT_SIZE
292
+	*/
293
+	best_hits=3; /* more then 3 hits needed */
294
+	start=end=0;
295
+	for (i=0; i<n; i++){
296
+		last=first=cond[i];
297
+		hits=1;
298
+		for (j=0; j<n; j++){
299
+			if ((i==j) || (cond[j]<=first)) continue;
300
+			if (cond[j]<last)
301
+				hits++;
302
+			else if ((cond[j]-first)<MAX_JT_SIZE){
303
+				last=cond[j];
304
+				hits++;
305
+			}
306
+		}
307
+		if (hits>best_hits){
308
+			best_hits=hits;
309
+			start=first;
310
+			end=last;
311
+			if (hits==n) break;
312
+		}
313
+	}
314
+	if (start!=end){
315
+		/* build jumptable: end-start entries and
316
+		 with a n-best_hits normal switch table */
317
+		jmp=mk_switch_jmp_table(end-start+1, n-best_hits);
318
+		if (jmp==0){
319
+			LOG(L_ERR, "ERROR: fix_switch: memory allocation error\n");
320
+			ret=E_OUT_OF_MEM;
321
+			goto error;
322
+		}
323
+		jmp->first=start;
324
+		jmp->last=end;
325
+		jmp->rest.n=n-best_hits;
326
+		jmp->rest.def=def_jmp_bm?*def_jmp_bm:0;
327
+		/* fill it with default values */
328
+		for (i=0; i<=(end-start); i++)
329
+			jmp->tbl[i]=jmp->rest.def;
330
+		for (i=0, j=0; i<n; i++){
331
+			if (cond[i]>=start && cond[i]<=end){
332
+				jmp->tbl[cond[i]-start]=*jmp_bm[i];
333
+			}else{
334
+				jmp->rest.cond[j]=cond[i];
335
+				jmp->rest.jump[j]=*jmp_bm[i];
336
+				j++;
337
+			}
338
+		}
339
+		t->type=SWITCH_JT_T;
340
+		t->val[1].type=JUMPTABLE_ST;
341
+		t->val[1].u.data=jmp;
342
+		ret=0;
343
+		DBG("SWITCH: optimized to jumptable [%d, %d] and %d condtable,"
344
+				"default: %s\n ",
345
+				jmp->first, jmp->last, jmp->rest.n, jmp->rest.def?"yes":"no");
346
+	}else{
347
+		sct=mk_switch_cond_table(n);
348
+		if (sct==0){
349
+			LOG(L_ERR, "ERROR: fix_switch: memory allocation error\n");
350
+			ret=E_OUT_OF_MEM;
351
+			goto error;
352
+		}
353
+		sct->n=n;
354
+		for (i=0; i<n; i++){
355
+			sct->cond[i]=cond[i];
356
+			sct->jump[i]=*jmp_bm[i];
357
+		}
358
+		sct->def=def_jmp_bm?*def_jmp_bm:0;
359
+		t->type=SWITCH_COND_T;
360
+		t->val[1].type=CONDTABLE_ST;
361
+		t->val[1].u.data=sct;
362
+		DBG("SWITCH: optimized to condtable (%d) default: %s\n ",
363
+				sct->n, sct->def?"yes":"no");
364
+		ret=0;
365
+	}
366
+end:
367
+error:
368
+	if (cond) pkg_free(cond);
369
+	if (jmp_bm) pkg_free(jmp_bm);
370
+	return ret;
371
+}
372
+
373
+/* vi: set ts=4 sw=4 tw=79:ai:cindent: */