|  | /* Simple expression parser */ | 
|  | %{ | 
|  | #ifndef NDEBUG | 
|  | #define YYDEBUG 1 | 
|  | #endif | 
|  | #include <assert.h> | 
|  | #include <math.h> | 
|  | #include <stdlib.h> | 
|  | #include "util/debug.h" | 
|  | #define IN_EXPR_Y 1 | 
|  | #include "expr.h" | 
|  | #include "expr-bison.h" | 
|  | int expr_lex(YYSTYPE * yylval_param , void *yyscanner); | 
|  | %} | 
|  |  | 
|  | %define api.pure full | 
|  |  | 
|  | %parse-param { double *final_val } | 
|  | %parse-param { struct expr_parse_ctx *ctx } | 
|  | %parse-param { bool compute_ids } | 
|  | %parse-param {void *scanner} | 
|  | %lex-param {void* scanner} | 
|  |  | 
|  | %union { | 
|  | double	 num; | 
|  | char	*str; | 
|  | struct ids { | 
|  | /* | 
|  | * When creating ids, holds the working set of event ids. NULL | 
|  | * implies the set is empty. | 
|  | */ | 
|  | struct hashmap *ids; | 
|  | /* | 
|  | * The metric value. When not creating ids this is the value | 
|  | * read from a counter, a constant or some computed value. When | 
|  | * creating ids the value is either a constant or BOTTOM. NAN is | 
|  | * used as the special BOTTOM value, representing a "set of all | 
|  | * values" case. | 
|  | */ | 
|  | double val; | 
|  | } ids; | 
|  | } | 
|  |  | 
|  | %token ID NUMBER MIN MAX IF ELSE LITERAL D_RATIO SOURCE_COUNT HAS_EVENT STRCMP_CPUID_STR EXPR_ERROR | 
|  | %left MIN MAX IF | 
|  | %left '|' | 
|  | %left '^' | 
|  | %left '&' | 
|  | %left '<' '>' | 
|  | %left '-' '+' | 
|  | %left '*' '/' '%' | 
|  | %left NEG NOT | 
|  | %type <num> NUMBER LITERAL | 
|  | %type <str> ID | 
|  | %destructor { free ($$); } <str> | 
|  | %type <ids> expr if_expr | 
|  | %destructor { ids__free($$.ids); } <ids> | 
|  |  | 
|  | %{ | 
|  | static void expr_error(double *final_val __maybe_unused, | 
|  | struct expr_parse_ctx *ctx __maybe_unused, | 
|  | bool compute_ids __maybe_unused, | 
|  | void *scanner __maybe_unused, | 
|  | const char *s) | 
|  | { | 
|  | pr_debug("%s\n", s); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * During compute ids, the special "bottom" value uses NAN to represent the set | 
|  | * of all values. NAN is selected as it isn't a useful constant value. | 
|  | */ | 
|  | #define BOTTOM NAN | 
|  |  | 
|  | /* During computing ids, does val represent a constant (non-BOTTOM) value? */ | 
|  | static bool is_const(double val) | 
|  | { | 
|  | return isfinite(val); | 
|  | } | 
|  |  | 
|  | static struct ids union_expr(struct ids ids1, struct ids ids2) | 
|  | { | 
|  | struct ids result = { | 
|  | .val = BOTTOM, | 
|  | .ids = ids__union(ids1.ids, ids2.ids), | 
|  | }; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | static struct ids handle_id(struct expr_parse_ctx *ctx, char *id, | 
|  | bool compute_ids, bool source_count) | 
|  | { | 
|  | struct ids result; | 
|  |  | 
|  | if (!compute_ids) { | 
|  | /* | 
|  | * Compute the event's value from ID. If the ID isn't known then | 
|  | * it isn't used to compute the formula so set to NAN. | 
|  | */ | 
|  | struct expr_id_data *data; | 
|  |  | 
|  | result.val = NAN; | 
|  | if (expr__resolve_id(ctx, id, &data) == 0) { | 
|  | result.val = source_count | 
|  | ? expr_id_data__source_count(data) | 
|  | : expr_id_data__value(data); | 
|  | } | 
|  | result.ids = NULL; | 
|  | free(id); | 
|  | } else { | 
|  | /* | 
|  | * Set the value to BOTTOM to show that any value is possible | 
|  | * when the event is computed. Create a set of just the ID. | 
|  | */ | 
|  | result.val = BOTTOM; | 
|  | result.ids = ids__new(); | 
|  | if (!result.ids || ids__insert(result.ids, id)) { | 
|  | pr_err("Error creating IDs for '%s'", id); | 
|  | free(id); | 
|  | } | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * If we're not computing ids or $1 and $3 are constants, compute the new | 
|  | * constant value using OP. Its invariant that there are no ids.  If computing | 
|  | * ids for non-constants union the set of IDs that must be computed. | 
|  | */ | 
|  | #define BINARY_OP(RESULT, OP, LHS, RHS)					\ | 
|  | if (!compute_ids || (is_const(LHS.val) && is_const(RHS.val))) { \ | 
|  | assert(LHS.ids == NULL);				\ | 
|  | assert(RHS.ids == NULL);				\ | 
|  | if (isnan(LHS.val) || isnan(RHS.val)) {			\ | 
|  | RESULT.val = NAN;				\ | 
|  | } else {						\ | 
|  | RESULT.val = LHS.val OP RHS.val;		\ | 
|  | }							\ | 
|  | RESULT.ids = NULL;					\ | 
|  | } else {							\ | 
|  | RESULT = union_expr(LHS, RHS);				\ | 
|  | } | 
|  |  | 
|  | %} | 
|  | %% | 
|  |  | 
|  | start: if_expr | 
|  | { | 
|  | if (compute_ids) | 
|  | ctx->ids = ids__union($1.ids, ctx->ids); | 
|  |  | 
|  | if (final_val) | 
|  | *final_val = $1.val; | 
|  | } | 
|  | ; | 
|  |  | 
|  | if_expr: expr IF expr ELSE if_expr | 
|  | { | 
|  | if (fpclassify($3.val) == FP_ZERO) { | 
|  | /* | 
|  | * The IF expression evaluated to 0 so treat as false, take the | 
|  | * ELSE and discard everything else. | 
|  | */ | 
|  | $$.val = $5.val; | 
|  | $$.ids = $5.ids; | 
|  | ids__free($1.ids); | 
|  | ids__free($3.ids); | 
|  | } else if (!compute_ids || is_const($3.val)) { | 
|  | /* | 
|  | * If ids aren't computed then treat the expression as true. If | 
|  | * ids are being computed and the IF expr is a non-zero | 
|  | * constant, then also evaluate the true case. | 
|  | */ | 
|  | $$.val = $1.val; | 
|  | $$.ids = $1.ids; | 
|  | ids__free($3.ids); | 
|  | ids__free($5.ids); | 
|  | } else if ($1.val == $5.val) { | 
|  | /* | 
|  | * LHS == RHS, so both are an identical constant. No need to | 
|  | * evaluate any events. | 
|  | */ | 
|  | $$.val = $1.val; | 
|  | $$.ids = NULL; | 
|  | ids__free($1.ids); | 
|  | ids__free($3.ids); | 
|  | ids__free($5.ids); | 
|  | } else { | 
|  | /* | 
|  | * Value is either the LHS or RHS and we need the IF expression | 
|  | * to compute it. | 
|  | */ | 
|  | $$ = union_expr($1, union_expr($3, $5)); | 
|  | } | 
|  | } | 
|  | | expr | 
|  | ; | 
|  |  | 
|  | expr: NUMBER | 
|  | { | 
|  | $$.val = $1; | 
|  | $$.ids = NULL; | 
|  | } | 
|  | | ID				{ $$ = handle_id(ctx, $1, compute_ids, /*source_count=*/false); } | 
|  | | SOURCE_COUNT '(' ID ')'	{ $$ = handle_id(ctx, $3, compute_ids, /*source_count=*/true); } | 
|  | | HAS_EVENT '(' ID ')' | 
|  | { | 
|  | $$.val = expr__has_event(ctx, compute_ids, $3); | 
|  | $$.ids = NULL; | 
|  | free($3); | 
|  | } | 
|  | | STRCMP_CPUID_STR '(' ID ')' | 
|  | { | 
|  | $$.val = expr__strcmp_cpuid_str(ctx, compute_ids, $3); | 
|  | $$.ids = NULL; | 
|  | free($3); | 
|  | } | 
|  | | expr '|' expr | 
|  | { | 
|  | if (is_const($1.val) && is_const($3.val)) { | 
|  | assert($1.ids == NULL); | 
|  | assert($3.ids == NULL); | 
|  | $$.ids = NULL; | 
|  | $$.val = (fpclassify($1.val) == FP_ZERO && fpclassify($3.val) == FP_ZERO) ? 0 : 1; | 
|  | } else if (is_const($1.val)) { | 
|  | assert($1.ids == NULL); | 
|  | if (fpclassify($1.val) == FP_ZERO) { | 
|  | $$ = $3; | 
|  | } else { | 
|  | $$.val = 1; | 
|  | $$.ids = NULL; | 
|  | ids__free($3.ids); | 
|  | } | 
|  | } else if (is_const($3.val)) { | 
|  | assert($3.ids == NULL); | 
|  | if (fpclassify($3.val) == FP_ZERO) { | 
|  | $$ = $1; | 
|  | } else { | 
|  | $$.val = 1; | 
|  | $$.ids = NULL; | 
|  | ids__free($1.ids); | 
|  | } | 
|  | } else { | 
|  | $$ = union_expr($1, $3); | 
|  | } | 
|  | } | 
|  | | expr '&' expr | 
|  | { | 
|  | if (is_const($1.val) && is_const($3.val)) { | 
|  | assert($1.ids == NULL); | 
|  | assert($3.ids == NULL); | 
|  | $$.val = (fpclassify($1.val) != FP_ZERO && fpclassify($3.val) != FP_ZERO) ? 1 : 0; | 
|  | $$.ids = NULL; | 
|  | } else if (is_const($1.val)) { | 
|  | assert($1.ids == NULL); | 
|  | if (fpclassify($1.val) != FP_ZERO) { | 
|  | $$ = $3; | 
|  | } else { | 
|  | $$.val = 0; | 
|  | $$.ids = NULL; | 
|  | ids__free($3.ids); | 
|  | } | 
|  | } else if (is_const($3.val)) { | 
|  | assert($3.ids == NULL); | 
|  | if (fpclassify($3.val) != FP_ZERO) { | 
|  | $$ = $1; | 
|  | } else { | 
|  | $$.val = 0; | 
|  | $$.ids = NULL; | 
|  | ids__free($1.ids); | 
|  | } | 
|  | } else { | 
|  | $$ = union_expr($1, $3); | 
|  | } | 
|  | } | 
|  | | expr '^' expr | 
|  | { | 
|  | if (is_const($1.val) && is_const($3.val)) { | 
|  | assert($1.ids == NULL); | 
|  | assert($3.ids == NULL); | 
|  | $$.val = (fpclassify($1.val) == FP_ZERO) != (fpclassify($3.val) == FP_ZERO) ? 1 : 0; | 
|  | $$.ids = NULL; | 
|  | } else { | 
|  | $$ = union_expr($1, $3); | 
|  | } | 
|  | } | 
|  | | expr '<' expr { BINARY_OP($$, <, $1, $3); } | 
|  | | expr '>' expr { BINARY_OP($$, >, $1, $3); } | 
|  | | expr '+' expr { BINARY_OP($$, +, $1, $3); } | 
|  | | expr '-' expr { BINARY_OP($$, -, $1, $3); } | 
|  | | expr '*' expr { BINARY_OP($$, *, $1, $3); } | 
|  | | expr '/' expr | 
|  | { | 
|  | if (fpclassify($3.val) == FP_ZERO) { | 
|  | pr_debug("division by zero\n"); | 
|  | assert($3.ids == NULL); | 
|  | if (compute_ids) | 
|  | ids__free($1.ids); | 
|  | $$.val = NAN; | 
|  | $$.ids = NULL; | 
|  | } else if (!compute_ids || (is_const($1.val) && is_const($3.val))) { | 
|  | assert($1.ids == NULL); | 
|  | assert($3.ids == NULL); | 
|  | $$.val = $1.val / $3.val; | 
|  | $$.ids = NULL; | 
|  | } else { | 
|  | /* LHS and/or RHS need computing from event IDs so union. */ | 
|  | $$ = union_expr($1, $3); | 
|  | } | 
|  | } | 
|  | | expr '%' expr | 
|  | { | 
|  | if (fpclassify($3.val) == FP_ZERO) { | 
|  | pr_debug("division by zero\n"); | 
|  | YYABORT; | 
|  | } else if (!compute_ids || (is_const($1.val) && is_const($3.val))) { | 
|  | assert($1.ids == NULL); | 
|  | assert($3.ids == NULL); | 
|  | $$.val = (long)$1.val % (long)$3.val; | 
|  | $$.ids = NULL; | 
|  | } else { | 
|  | /* LHS and/or RHS need computing from event IDs so union. */ | 
|  | $$ = union_expr($1, $3); | 
|  | } | 
|  | } | 
|  | | D_RATIO '(' expr ',' expr ')' | 
|  | { | 
|  | if (fpclassify($5.val) == FP_ZERO) { | 
|  | /* | 
|  | * Division by constant zero always yields zero and no events | 
|  | * are necessary. | 
|  | */ | 
|  | assert($5.ids == NULL); | 
|  | $$.val = 0.0; | 
|  | $$.ids = NULL; | 
|  | ids__free($3.ids); | 
|  | } else if (!compute_ids || (is_const($3.val) && is_const($5.val))) { | 
|  | assert($3.ids == NULL); | 
|  | assert($5.ids == NULL); | 
|  | $$.val = $3.val / $5.val; | 
|  | $$.ids = NULL; | 
|  | } else { | 
|  | /* LHS and/or RHS need computing from event IDs so union. */ | 
|  | $$ = union_expr($3, $5); | 
|  | } | 
|  | } | 
|  | | '-' expr %prec NEG | 
|  | { | 
|  | $$.val = -$2.val; | 
|  | $$.ids = $2.ids; | 
|  | } | 
|  | | '(' if_expr ')' | 
|  | { | 
|  | $$ = $2; | 
|  | } | 
|  | | MIN '(' expr ',' expr ')' | 
|  | { | 
|  | if (!compute_ids) { | 
|  | $$.val = $3.val < $5.val ? $3.val : $5.val; | 
|  | $$.ids = NULL; | 
|  | } else { | 
|  | $$ = union_expr($3, $5); | 
|  | } | 
|  | } | 
|  | | MAX '(' expr ',' expr ')' | 
|  | { | 
|  | if (!compute_ids) { | 
|  | $$.val = $3.val > $5.val ? $3.val : $5.val; | 
|  | $$.ids = NULL; | 
|  | } else { | 
|  | $$ = union_expr($3, $5); | 
|  | } | 
|  | } | 
|  | | LITERAL | 
|  | { | 
|  | $$.val = $1; | 
|  | $$.ids = NULL; | 
|  | } | 
|  | ; | 
|  |  | 
|  | %% |