|
@@ -0,0 +1,374 @@
|
|
|
+/*
|
|
|
+ * $Id$
|
|
|
+ *
|
|
|
+ * Copyright (C) 2009 iptelorg GmbH
|
|
|
+ *
|
|
|
+ * Permission to use, copy, modify, and distribute this software for any
|
|
|
+ * purpose with or without fee is hereby granted, provided that the above
|
|
|
+ * copyright notice and this permission notice appear in all copies.
|
|
|
+ *
|
|
|
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
|
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
|
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
|
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
+ */
|
|
|
+/*
|
|
|
+ * switch.c
|
|
|
+ */
|
|
|
+/*
|
|
|
+ * History:
|
|
|
+ * --------
|
|
|
+ * 2009-02-02 initial version (andrei)
|
|
|
+*/
|
|
|
+
|
|
|
+#include "switch.h"
|
|
|
+#include "rvalue.h"
|
|
|
+#include "route.h"
|
|
|
+#include "mem/mem.h"
|
|
|
+#include "error.h"
|
|
|
+
|
|
|
+#define MAX_JT_SIZE 100 /* maximum jump table size */
|
|
|
+
|
|
|
+
|
|
|
+/** create a cond table structure (pkg_malloc'ed).
|
|
|
+ * @return 0 on error, pointer on success
|
|
|
+ */
|
|
|
+static struct switch_cond_table* mk_switch_cond_table(int n)
|
|
|
+{
|
|
|
+ struct switch_cond_table* sct;
|
|
|
+
|
|
|
+ /* allocate everything in a single block, better for cache locality */
|
|
|
+ sct=pkg_malloc(ROUND_INT(sizeof(*sct))+
|
|
|
+ ROUND_POINTER(n*sizeof(sct->cond[0]))+
|
|
|
+ n*sizeof(sct->jump[0]));
|
|
|
+ if (sct==0) return 0;
|
|
|
+ sct->n=n;
|
|
|
+ sct->cond=(int*)((char*)sct+ROUND_INT(sizeof(*sct)));
|
|
|
+ sct->jump=(struct action**)
|
|
|
+ ((char*)sct->cond+ROUND_POINTER(n*sizeof(sct->cond[0])));
|
|
|
+ sct->def=0;
|
|
|
+ return sct;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+/** create a jump table structure (pkg_malloc'ed).
|
|
|
+ * @param jmp_size - size of the jump table
|
|
|
+ * @param rest - size of the fallback condition table
|
|
|
+ * @return 0 on error, pointer on success
|
|
|
+ */
|
|
|
+static struct switch_jmp_table* mk_switch_jmp_table(int jmp_size, int rest)
|
|
|
+{
|
|
|
+ struct switch_jmp_table* jt;
|
|
|
+ int size;
|
|
|
+
|
|
|
+ /* alloc everything in a block */
|
|
|
+ size = ROUND_POINTER(sizeof(*jt))+
|
|
|
+ ROUND_INT(jmp_size*sizeof(jt->tbl[0]))+
|
|
|
+ ROUND_POINTER(rest*sizeof(jt->rest.cond[0]))+
|
|
|
+ rest*sizeof(jt->rest.jump[0]);
|
|
|
+ jt=pkg_malloc(size);
|
|
|
+ if (jt == 0) return 0;
|
|
|
+ memset(jt, 0, size);
|
|
|
+ jt->tbl = (struct action**)((char*) jt + ROUND_POINTER(sizeof(*jt)));
|
|
|
+ jt->rest.cond = (int*)
|
|
|
+ ((char*) jt->tbl + ROUND_INT(jmp_size*sizeof(jt->tbl[0])));
|
|
|
+ jt->rest.jump = (struct action**) ((char*) jt->rest.cond +
|
|
|
+ ROUND_POINTER(rest*sizeof(jt->rest.cond[0])));
|
|
|
+ jt->rest.n=rest;
|
|
|
+ return jt;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+void destroy_case_stms(struct case_stms *lst)
|
|
|
+{
|
|
|
+ struct case_stms* l;
|
|
|
+ struct case_stms* n;
|
|
|
+
|
|
|
+ for (l=lst; l; l=n){
|
|
|
+ n=l->next;
|
|
|
+ rve_destroy(l->ct_rve);
|
|
|
+ /* TODO: the action list is not freed (missing destroy_action() and
|
|
|
+ there are some case when we need at least part of the action list
|
|
|
+ */
|
|
|
+ pkg_free(l);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+/** fixup function for SWITCH_T actions.
|
|
|
+ * can produce 4 different action types:
|
|
|
+ * - BLOCK_T (actions) - actions grouped in a block, break ends the block
|
|
|
+ * execution.
|
|
|
+ * - EVAL_T (cond) - null switch block, but the condition has to be
|
|
|
+ * evaluated due to possible side-effects.
|
|
|
+ * - SWITCH_COND_T(cond, jumps) - condition table
|
|
|
+ * - SWITCH_JT_T(cond, jumptable) - jumptable + condition table
|
|
|
+ * TODO: external optimizers that would "flatten" BLOCK_T w/no breaks or
|
|
|
+ * breaks at the end.
|
|
|
+ *
|
|
|
+ */
|
|
|
+int fix_switch(struct action* t)
|
|
|
+{
|
|
|
+ struct case_stms* c;
|
|
|
+ int n, i, j, ret, val;
|
|
|
+ struct action* a;
|
|
|
+ struct action* block;
|
|
|
+ struct action* def_a;
|
|
|
+ struct action* action_lst;
|
|
|
+ struct action** tail;
|
|
|
+ struct switch_jmp_table* jmp;
|
|
|
+ struct switch_cond_table* sct;
|
|
|
+ int labels_no;
|
|
|
+ struct action** def_jmp_bm;
|
|
|
+ int* cond;
|
|
|
+ struct action*** jmp_bm;
|
|
|
+ int default_found;
|
|
|
+ int first, last, start, end, hits, best_hits;
|
|
|
+ struct rval_expr* sw_rve;
|
|
|
+
|
|
|
+ ret=E_BUG;
|
|
|
+ cond=0;
|
|
|
+ jmp_bm=0;
|
|
|
+ def_jmp_bm=0;
|
|
|
+ labels_no=0;
|
|
|
+ default_found=0;
|
|
|
+ sw_rve=(struct rval_expr*)t->val[0].u.data;
|
|
|
+ /* handle null actions: optimize away if no
|
|
|
+ sideffects */
|
|
|
+ if (t->val[1].u.data==0){
|
|
|
+ if (!rve_has_side_effects(sw_rve)){
|
|
|
+ t->type=BLOCK_T;
|
|
|
+ rve_destroy(sw_rve);
|
|
|
+ t->val[0].type=BLOCK_ST;
|
|
|
+ t->val[0].u.data=0;
|
|
|
+ DBG("SWITCH: null switch optimized away\n");
|
|
|
+ }else{
|
|
|
+ t->type=EVAL_T;
|
|
|
+ t->val[0].type=RVE_ST;
|
|
|
+ DBG("SWITCH: null switch turned to EVAL_T\n");
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ def_a=0;
|
|
|
+ n=0;
|
|
|
+ for (c=(struct case_stms*)t->val[1].u.data; c; c=c->next){
|
|
|
+ if (c->ct_rve){
|
|
|
+ if (!rve_is_constant(c->ct_rve)){
|
|
|
+ LOG(L_ERR, "ERROR: fix_switch: non constant "
|
|
|
+ "expression in case\n");
|
|
|
+ return E_BUG;
|
|
|
+ }
|
|
|
+ if (rval_expr_eval_int(0, 0, &c->int_label, c->ct_rve)
|
|
|
+ <0){
|
|
|
+ LOG(L_ERR, "ERROR: fix_switch: case expression"
|
|
|
+ " (%d,%d) has non-interger type\n",
|
|
|
+ c->ct_rve->fpos.s_line,
|
|
|
+ c->ct_rve->fpos.s_col);
|
|
|
+ return E_BUG;
|
|
|
+ }
|
|
|
+ c->is_default=0;
|
|
|
+ n++; /* count only non-default cases */
|
|
|
+ }else{
|
|
|
+ if (default_found){
|
|
|
+ LOG(L_ERR, "ERROR: fix_switch: more then one \"default\""
|
|
|
+ " label found (%d, %d)\n",
|
|
|
+ c->ct_rve->fpos.s_line,
|
|
|
+ c->ct_rve->fpos.s_col);
|
|
|
+ return E_UNSPEC;
|
|
|
+ }
|
|
|
+ default_found=1;
|
|
|
+ c->int_label=-1;
|
|
|
+ c->is_default=1;
|
|
|
+ def_a=c->actions;
|
|
|
+ }
|
|
|
+ if ( c->actions && ((ret=fix_actions(c->actions))<0))
|
|
|
+ goto error;
|
|
|
+ }
|
|
|
+ DBG("SWITCH: %d cases, %d default\n", n, default_found);
|
|
|
+ /*: handle n==0 (no case only a default:) */
|
|
|
+ if (n==0){
|
|
|
+ if (default_found){
|
|
|
+ if (!rve_has_side_effects(sw_rve)){
|
|
|
+ t->type=BLOCK_T;
|
|
|
+ rve_destroy(sw_rve);
|
|
|
+ destroy_case_stms(t->val[1].u.data);
|
|
|
+ t->val[0].type=BLOCK_ST;
|
|
|
+ t->val[0].u.data=def_a;
|
|
|
+ t->val[1].type=0;
|
|
|
+ t->val[1].u.data=0;
|
|
|
+ DBG("SWITCH: default only switch optimized away (BLOCK_T)\n");
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ DBG("SWITCH: default only switch with side-effect...\n");
|
|
|
+ }else{
|
|
|
+ LOG(L_CRIT, "BUG: fix_switch: empty switch not expected at this"
|
|
|
+ " point\n");
|
|
|
+ ret=E_BUG;
|
|
|
+ goto error;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ labels_no=n;
|
|
|
+ cond=pkg_malloc(sizeof(cond[0])*n);
|
|
|
+ jmp_bm=pkg_malloc(sizeof(jmp_bm[0])*n);
|
|
|
+ if (cond==0 || jmp_bm==0){
|
|
|
+ LOG(L_ERR, "ERROR: fix_switch: memory allocation failure\n");
|
|
|
+ ret=E_OUT_OF_MEM;
|
|
|
+ goto error;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* fill condition table and jump point bookmarks and "flatten" the action
|
|
|
+ lists (transform them into a single list for the entire switch, rather
|
|
|
+ then one block per case ) */
|
|
|
+ n=0;
|
|
|
+ action_lst=0;
|
|
|
+ tail=&action_lst;
|
|
|
+ for (c=(struct case_stms*)t->val[1].u.data; c; c=c->next){
|
|
|
+ a=c->actions;
|
|
|
+ if (a){
|
|
|
+ for (; a->next; a=a->next);
|
|
|
+ if (action_lst==0)
|
|
|
+ action_lst=c->actions;
|
|
|
+ else
|
|
|
+ *tail=c->actions;
|
|
|
+ }
|
|
|
+ if (c->is_default){
|
|
|
+ def_jmp_bm=tail;
|
|
|
+ } else {
|
|
|
+ for (j=0; j<n; j++){
|
|
|
+ if (cond[j]==c->int_label){
|
|
|
+ LOG(L_ERR, "ERROR: fix_switch: duplicate case (%d,%d)\n",
|
|
|
+ c->ct_rve->fpos.s_line, c->ct_rve->fpos.s_col);
|
|
|
+ ret=E_UNSPEC;
|
|
|
+ goto error;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ cond[n]=c->int_label;
|
|
|
+ jmp_bm[n]=tail;
|
|
|
+ n++;
|
|
|
+ }
|
|
|
+ if (c->actions)
|
|
|
+ tail=&a->next;
|
|
|
+ }
|
|
|
+ /* handle constant rve w/ no side-effects: replace the whole case
|
|
|
+ with the case rve block */
|
|
|
+ if ( (scr_opt_lev>=2) &&
|
|
|
+ !rve_has_side_effects(sw_rve) && rve_is_constant(sw_rve)){
|
|
|
+ if (rval_expr_eval_int(0, 0, &val, sw_rve) <0){
|
|
|
+ LOG(L_ERR, "ERROR: fix_switch: wrong type for switch(...) "
|
|
|
+ "expression (%d,%d)\n",
|
|
|
+ sw_rve->fpos.s_line, sw_rve->fpos.s_col);
|
|
|
+ ret=E_UNSPEC;
|
|
|
+ goto error;
|
|
|
+ }
|
|
|
+ /* start with the "default:" value in case nothing is found */
|
|
|
+ block=def_jmp_bm?*def_jmp_bm:0;
|
|
|
+ for (i=0; i<n; i++){
|
|
|
+ if (cond[i]==val){
|
|
|
+ block=*jmp_bm[i];
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ t->type=BLOCK_T;
|
|
|
+ rve_destroy(sw_rve);
|
|
|
+ t->val[0].type=BLOCK_ST;
|
|
|
+ t->val[0].u.data=block;
|
|
|
+ destroy_case_stms(t->val[1].u.data);
|
|
|
+ t->val[1].type=0;
|
|
|
+ t->val[1].u.data=0;
|
|
|
+ ret=0;
|
|
|
+ DBG("SWITCH: constant switch(%d) with %d cases optimized away to case "
|
|
|
+ " %d \n", val, n, i);
|
|
|
+ goto end;
|
|
|
+ }
|
|
|
+ /* try to create a jumptable */
|
|
|
+ /* cost: 2 cmp & table lookup
|
|
|
+ => makes sense for more then 3 cases
|
|
|
+ & if size< MAX_JT_SIZE
|
|
|
+ */
|
|
|
+ best_hits=3; /* more then 3 hits needed */
|
|
|
+ start=end=0;
|
|
|
+ for (i=0; i<n; i++){
|
|
|
+ last=first=cond[i];
|
|
|
+ hits=1;
|
|
|
+ for (j=0; j<n; j++){
|
|
|
+ if ((i==j) || (cond[j]<=first)) continue;
|
|
|
+ if (cond[j]<last)
|
|
|
+ hits++;
|
|
|
+ else if ((cond[j]-first)<MAX_JT_SIZE){
|
|
|
+ last=cond[j];
|
|
|
+ hits++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (hits>best_hits){
|
|
|
+ best_hits=hits;
|
|
|
+ start=first;
|
|
|
+ end=last;
|
|
|
+ if (hits==n) break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (start!=end){
|
|
|
+ /* build jumptable: end-start entries and
|
|
|
+ with a n-best_hits normal switch table */
|
|
|
+ jmp=mk_switch_jmp_table(end-start+1, n-best_hits);
|
|
|
+ if (jmp==0){
|
|
|
+ LOG(L_ERR, "ERROR: fix_switch: memory allocation error\n");
|
|
|
+ ret=E_OUT_OF_MEM;
|
|
|
+ goto error;
|
|
|
+ }
|
|
|
+ jmp->first=start;
|
|
|
+ jmp->last=end;
|
|
|
+ jmp->rest.n=n-best_hits;
|
|
|
+ jmp->rest.def=def_jmp_bm?*def_jmp_bm:0;
|
|
|
+ /* fill it with default values */
|
|
|
+ for (i=0; i<=(end-start); i++)
|
|
|
+ jmp->tbl[i]=jmp->rest.def;
|
|
|
+ for (i=0, j=0; i<n; i++){
|
|
|
+ if (cond[i]>=start && cond[i]<=end){
|
|
|
+ jmp->tbl[cond[i]-start]=*jmp_bm[i];
|
|
|
+ }else{
|
|
|
+ jmp->rest.cond[j]=cond[i];
|
|
|
+ jmp->rest.jump[j]=*jmp_bm[i];
|
|
|
+ j++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ t->type=SWITCH_JT_T;
|
|
|
+ t->val[1].type=JUMPTABLE_ST;
|
|
|
+ t->val[1].u.data=jmp;
|
|
|
+ ret=0;
|
|
|
+ DBG("SWITCH: optimized to jumptable [%d, %d] and %d condtable,"
|
|
|
+ "default: %s\n ",
|
|
|
+ jmp->first, jmp->last, jmp->rest.n, jmp->rest.def?"yes":"no");
|
|
|
+ }else{
|
|
|
+ sct=mk_switch_cond_table(n);
|
|
|
+ if (sct==0){
|
|
|
+ LOG(L_ERR, "ERROR: fix_switch: memory allocation error\n");
|
|
|
+ ret=E_OUT_OF_MEM;
|
|
|
+ goto error;
|
|
|
+ }
|
|
|
+ sct->n=n;
|
|
|
+ for (i=0; i<n; i++){
|
|
|
+ sct->cond[i]=cond[i];
|
|
|
+ sct->jump[i]=*jmp_bm[i];
|
|
|
+ }
|
|
|
+ sct->def=def_jmp_bm?*def_jmp_bm:0;
|
|
|
+ t->type=SWITCH_COND_T;
|
|
|
+ t->val[1].type=CONDTABLE_ST;
|
|
|
+ t->val[1].u.data=sct;
|
|
|
+ DBG("SWITCH: optimized to condtable (%d) default: %s\n ",
|
|
|
+ sct->n, sct->def?"yes":"no");
|
|
|
+ ret=0;
|
|
|
+ }
|
|
|
+end:
|
|
|
+error:
|
|
|
+ if (cond) pkg_free(cond);
|
|
|
+ if (jmp_bm) pkg_free(jmp_bm);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+/* vi: set ts=4 sw=4 tw=79:ai:cindent: */
|