xref: /illumos-gate/usr/src/lib/libdtrace/common/dt_sugar.c (revision 8c69cc8fbe729fa7b091e901c4b50508ccc6bb33)
1 /*
2  * CDDL HEADER START
3  *
4  * This file and its contents are supplied under the terms of the
5  * Common Development and Distribution License ("CDDL"), version 1.0.
6  * You may only use this file in accordance with the terms of version
7  * 1.0 of the CDDL.
8  *
9  * A full copy of the text of the CDDL should have accompanied this
10  * source.  A copy of the CDDL is also available via the Internet at
11  * http://www.illumos.org/license/CDDL.
12  *
13  * CDDL HEADER END
14  */
15 
16 /*
17  * Copyright (c) 2012, 2016 by Delphix. All rights reserved.
18  */
19 
20 /*
21  * Syntactic sugar features are implemented by transforming the D parse tree
22  * such that it only uses the subset of D that is supported by the rest of the
23  * compiler / the kernel.  A clause containing these language features is
24  * referred to as a "super-clause", and its transformation typically entails
25  * creating several "sub-clauses" to implement it. For diagnosability, the
26  * sub-clauses will be printed if the "-xtree=8" flag is specified.
27  *
28  * Currently, the only syntactic sugar feature is "if/else" statements.  Each
29  * basic block (e.g. the body of the "if" and "else" statements, and the
30  * statements before and after) is turned into its own sub-clause, with a
31  * predicate that causes it to be executed only if the code flows to this point.
32  * Nested if/else statements are supported.
33  *
34  * This infrastructure is designed to accommodate other syntactic sugar features
35  * in the future.
36  */
37 
38 #include <sys/types.h>
39 #include <sys/wait.h>
40 #include <sys/sysmacros.h>
41 
42 #include <assert.h>
43 #include <strings.h>
44 #include <stdlib.h>
45 #include <stdio.h>
46 #include <ctype.h>
47 #include <dt_module.h>
48 #include <dt_program.h>
49 #include <dt_provider.h>
50 #include <dt_printf.h>
51 #include <dt_pid.h>
52 #include <dt_grammar.h>
53 #include <dt_ident.h>
54 #include <dt_string.h>
55 #include <dt_impl.h>
56 
57 typedef struct dt_sugar_parse {
58 	dtrace_hdl_t *dtsp_dtp;		/* dtrace handle */
59 	dt_node_t *dtsp_pdescs;		/* probe descriptions */
60 	int dtsp_num_conditions;	/* number of condition variables */
61 	int dtsp_num_ifs;		/* number of "if" statements */
62 	dt_node_t *dtsp_clause_list;	/* list of clauses */
63 } dt_sugar_parse_t;
64 
65 static void dt_sugar_visit_stmts(dt_sugar_parse_t *, dt_node_t *, int);
66 
67 /*
68  * Return a node for "self->%error".
69  *
70  * Note that the "%" is part of the variable name, and is included so that
71  * this variable name can not collide with any user-specified variable.
72  *
73  * This error variable is used to keep track of if there has been an error
74  * in any of the sub-clauses, and is used to prevent execution of subsequent
75  * sub-clauses following an error.
76  */
77 static dt_node_t *
78 dt_sugar_new_error_var(void)
79 {
80 	return (dt_node_op2(DT_TOK_PTR, dt_node_ident(strdup("self")),
81 	    dt_node_ident(strdup("%error"))));
82 }
83 
84 /*
85  * Append this clause to the clause list.
86  */
87 static void
88 dt_sugar_append_clause(dt_sugar_parse_t *dp, dt_node_t *clause)
89 {
90 	dp->dtsp_clause_list = dt_node_link(dp->dtsp_clause_list, clause);
91 }
92 
93 /*
94  * Prepend this clause to the clause list.
95  */
96 static void
97 dt_sugar_prepend_clause(dt_sugar_parse_t *dp, dt_node_t *clause)
98 {
99 	dp->dtsp_clause_list = dt_node_link(clause, dp->dtsp_clause_list);
100 }
101 
102 /*
103  * Return a node for "this->%condition_<condid>", or NULL if condid==0.
104  *
105  * Note that the "%" is part of the variable name, and is included so that
106  * this variable name can not collide with any user-specified variable.
107  */
108 static dt_node_t *
109 dt_sugar_new_condition_var(int condid)
110 {
111 	char *str;
112 
113 	if (condid == 0)
114 		return (NULL);
115 	assert(condid > 0);
116 
117 	(void) asprintf(&str, "%%condition_%d", ABS(condid));
118 	return (dt_node_op2(DT_TOK_PTR, dt_node_ident(strdup("this")),
119 	    dt_node_ident(str)));
120 }
121 
122 /*
123  * Return new clause to evaluate predicate and set newcond.  condid is
124  * the condition that we are already under, or 0 if none.
125  * The new clause will be of the form:
126  *
127  * dp_pdescs
128  * /!self->%error/
129  * {
130  *	this->%condition_<newcond> =
131  *	    (this->%condition_<condid> && pred);
132  * }
133  *
134  * Note: if condid==0, we will instead do "... = (1 && pred)", to effectively
135  * convert the pred to a boolean.
136  *
137  * Note: Unless an error has been encountered, we always set the condition
138  * variable (either to 0 or 1).  This lets us avoid resetting the condition
139  * variables back to 0 when the super-clause completes.
140  */
141 static dt_node_t *
142 dt_sugar_new_condition_impl(dt_sugar_parse_t *dp,
143     dt_node_t *pred, int condid, int newcond)
144 {
145 	dt_node_t *value, *body, *newpred;
146 
147 	/* predicate is !self->%error */
148 	newpred = dt_node_op1(DT_TOK_LNEG, dt_sugar_new_error_var());
149 
150 	if (condid == 0) {
151 		/*
152 		 * value is (1 && pred)
153 		 *
154 		 * Note, D doesn't allow a probe-local "this" variable to
155 		 * be reused as a different type, even from a different probe.
156 		 * Therefore, value can't simply be <pred>, because then
157 		 * its type could be different when we reuse this condid
158 		 * in a different meta-clause.
159 		 */
160 		value = dt_node_op2(DT_TOK_LAND, dt_node_int(1), pred);
161 	} else {
162 		/* value is (this->%condition_<condid> && pred) */
163 		value = dt_node_op2(DT_TOK_LAND,
164 		    dt_sugar_new_condition_var(condid), pred);
165 	}
166 
167 	/* body is "this->%condition_<retval> = <value>;" */
168 	body = dt_node_statement(dt_node_op2(DT_TOK_ASGN,
169 	    dt_sugar_new_condition_var(newcond), value));
170 
171 	return (dt_node_clause(dp->dtsp_pdescs, newpred, body));
172 }
173 
174 /*
175  * Generate a new clause to evaluate predicate and set a new condition variable,
176  * whose ID will be returned.  The new clause will be appended to
177  * dp_first_new_clause.
178  */
179 static int
180 dt_sugar_new_condition(dt_sugar_parse_t *dp, dt_node_t *pred, int condid)
181 {
182 	dp->dtsp_num_conditions++;
183 	dt_sugar_append_clause(dp, dt_sugar_new_condition_impl(dp,
184 	    pred, condid, dp->dtsp_num_conditions));
185 	return (dp->dtsp_num_conditions);
186 }
187 
188 /*
189  * Visit the specified node and all of its descendants.  Currently this is only
190  * used to count the number of "if" statements (dtsp_num_ifs).
191  */
192 static void
193 dt_sugar_visit_all(dt_sugar_parse_t *dp, dt_node_t *dnp)
194 {
195 	dt_node_t *arg;
196 
197 	switch (dnp->dn_kind) {
198 	case DT_NODE_FREE:
199 	case DT_NODE_INT:
200 	case DT_NODE_STRING:
201 	case DT_NODE_SYM:
202 	case DT_NODE_TYPE:
203 	case DT_NODE_PROBE:
204 	case DT_NODE_PDESC:
205 	case DT_NODE_IDENT:
206 		break;
207 
208 	case DT_NODE_FUNC:
209 		for (arg = dnp->dn_args; arg != NULL; arg = arg->dn_list)
210 			dt_sugar_visit_all(dp, arg);
211 		break;
212 
213 	case DT_NODE_OP1:
214 		dt_sugar_visit_all(dp, dnp->dn_child);
215 		break;
216 
217 	case DT_NODE_OP2:
218 		dt_sugar_visit_all(dp, dnp->dn_left);
219 		dt_sugar_visit_all(dp, dnp->dn_right);
220 		if (dnp->dn_op == DT_TOK_LBRAC) {
221 			dt_node_t *ln = dnp->dn_right;
222 			while (ln->dn_list != NULL) {
223 				dt_sugar_visit_all(dp, ln->dn_list);
224 				ln = ln->dn_list;
225 			}
226 		}
227 		break;
228 
229 	case DT_NODE_OP3:
230 		dt_sugar_visit_all(dp, dnp->dn_expr);
231 		dt_sugar_visit_all(dp, dnp->dn_left);
232 		dt_sugar_visit_all(dp, dnp->dn_right);
233 		break;
234 
235 	case DT_NODE_DEXPR:
236 	case DT_NODE_DFUNC:
237 		dt_sugar_visit_all(dp, dnp->dn_expr);
238 		break;
239 
240 	case DT_NODE_AGG:
241 		for (arg = dnp->dn_aggtup; arg != NULL; arg = arg->dn_list)
242 			dt_sugar_visit_all(dp, arg);
243 
244 		if (dnp->dn_aggfun)
245 			dt_sugar_visit_all(dp, dnp->dn_aggfun);
246 		break;
247 
248 	case DT_NODE_CLAUSE:
249 		for (arg = dnp->dn_pdescs; arg != NULL; arg = arg->dn_list)
250 			dt_sugar_visit_all(dp, arg);
251 
252 		if (dnp->dn_pred != NULL)
253 			dt_sugar_visit_all(dp, dnp->dn_pred);
254 
255 		for (arg = dnp->dn_acts; arg != NULL; arg = arg->dn_list)
256 			dt_sugar_visit_all(dp, arg);
257 		break;
258 
259 	case DT_NODE_INLINE: {
260 		const dt_idnode_t *inp = dnp->dn_ident->di_iarg;
261 
262 		dt_sugar_visit_all(dp, inp->din_root);
263 		break;
264 	}
265 	case DT_NODE_MEMBER:
266 		if (dnp->dn_membexpr)
267 			dt_sugar_visit_all(dp, dnp->dn_membexpr);
268 		break;
269 
270 	case DT_NODE_XLATOR:
271 		for (arg = dnp->dn_members; arg != NULL; arg = arg->dn_list)
272 			dt_sugar_visit_all(dp, arg);
273 		break;
274 
275 	case DT_NODE_PROVIDER:
276 		for (arg = dnp->dn_probes; arg != NULL; arg = arg->dn_list)
277 			dt_sugar_visit_all(dp, arg);
278 		break;
279 
280 	case DT_NODE_PROG:
281 		for (arg = dnp->dn_list; arg != NULL; arg = arg->dn_list)
282 			dt_sugar_visit_all(dp, arg);
283 		break;
284 
285 	case DT_NODE_IF:
286 		dp->dtsp_num_ifs++;
287 		dt_sugar_visit_all(dp, dnp->dn_conditional);
288 
289 		for (arg = dnp->dn_body; arg != NULL; arg = arg->dn_list)
290 			dt_sugar_visit_all(dp, arg);
291 		for (arg = dnp->dn_alternate_body; arg != NULL;
292 		    arg = arg->dn_list)
293 			dt_sugar_visit_all(dp, arg);
294 
295 		break;
296 
297 	default:
298 		(void) dnerror(dnp, D_UNKNOWN, "bad node %p, kind %d\n",
299 		    (void *)dnp, dnp->dn_kind);
300 	}
301 }
302 
303 /*
304  * Return a new clause which resets the error variable to zero:
305  *
306  *   dp_pdescs{ self->%error = 0; }
307  *
308  * This clause will be executed at the beginning of each meta-clause, to
309  * ensure the error variable is unset (in case the previous meta-clause
310  * failed).
311  */
312 static dt_node_t *
313 dt_sugar_new_clearerror_clause(dt_sugar_parse_t *dp)
314 {
315 	dt_node_t *stmt = dt_node_statement(dt_node_op2(DT_TOK_ASGN,
316 	    dt_sugar_new_error_var(), dt_node_int(0)));
317 	return (dt_node_clause(dp->dtsp_pdescs, NULL, stmt));
318 }
319 
320 /*
321  * Evaluate the conditional, and recursively visit the body of the "if"
322  * statement (and the "else", if present).
323  */
324 static void
325 dt_sugar_do_if(dt_sugar_parse_t *dp, dt_node_t *if_stmt, int precondition)
326 {
327 	int newid;
328 
329 	assert(if_stmt->dn_kind == DT_NODE_IF);
330 
331 	/* condition */
332 	newid = dt_sugar_new_condition(dp,
333 	    if_stmt->dn_conditional, precondition);
334 
335 	/* body of if */
336 	dt_sugar_visit_stmts(dp, if_stmt->dn_body, newid);
337 
338 	/*
339 	 * Visit the body of the "else" statement, if present.  Note that we
340 	 * generate a new condition which is the inverse of the previous
341 	 * condition.
342 	 */
343 	if (if_stmt->dn_alternate_body != NULL) {
344 		dt_node_t *pred =
345 		    dt_node_op1(DT_TOK_LNEG, dt_sugar_new_condition_var(newid));
346 		dt_sugar_visit_stmts(dp, if_stmt->dn_alternate_body,
347 		    dt_sugar_new_condition(dp, pred, precondition));
348 	}
349 }
350 
351 /*
352  * Generate a new clause to evaluate the statements based on the condition.
353  * The new clause will be appended to dp_first_new_clause.
354  *
355  * dp_pdescs
356  * /!self->%error && this->%condition_<condid>/
357  * {
358  *	stmts
359  * }
360  */
361 static void
362 dt_sugar_new_basic_block(dt_sugar_parse_t *dp, int condid, dt_node_t *stmts)
363 {
364 	dt_node_t *pred = NULL;
365 
366 	if (condid == 0) {
367 		/*
368 		 * Don't bother with !error on the first clause, because if
369 		 * there is only one clause, we don't add the prelude to
370 		 * zero out %error.
371 		 */
372 		if (dp->dtsp_num_conditions != 0) {
373 			pred = dt_node_op1(DT_TOK_LNEG,
374 			    dt_sugar_new_error_var());
375 		}
376 	} else {
377 		pred = dt_node_op2(DT_TOK_LAND,
378 		    dt_node_op1(DT_TOK_LNEG, dt_sugar_new_error_var()),
379 		    dt_sugar_new_condition_var(condid));
380 	}
381 	dt_sugar_append_clause(dp,
382 	    dt_node_clause(dp->dtsp_pdescs, pred, stmts));
383 }
384 
385 /*
386  * Visit all the statements in this list, and break them into basic blocks,
387  * generating new clauses for "if" and "else" statements.
388  */
389 static void
390 dt_sugar_visit_stmts(dt_sugar_parse_t *dp, dt_node_t *stmts, int precondition)
391 {
392 	dt_node_t *stmt;
393 	dt_node_t *prev_stmt = NULL;
394 	dt_node_t *next_stmt;
395 	dt_node_t *first_stmt_in_basic_block = NULL;
396 
397 	for (stmt = stmts; stmt != NULL; stmt = next_stmt) {
398 		next_stmt = stmt->dn_list;
399 
400 		if (stmt->dn_kind != DT_NODE_IF) {
401 			if (first_stmt_in_basic_block == NULL)
402 				first_stmt_in_basic_block = stmt;
403 			prev_stmt = stmt;
404 			continue;
405 		}
406 
407 		/*
408 		 * Remove this and following statements from the previous
409 		 * clause.
410 		 */
411 		if (prev_stmt != NULL)
412 			prev_stmt->dn_list = NULL;
413 
414 		/*
415 		 * Generate clause for statements preceding the "if"
416 		 */
417 		if (first_stmt_in_basic_block != NULL) {
418 			dt_sugar_new_basic_block(dp, precondition,
419 			    first_stmt_in_basic_block);
420 		}
421 
422 		dt_sugar_do_if(dp, stmt, precondition);
423 
424 		first_stmt_in_basic_block = NULL;
425 
426 		prev_stmt = stmt;
427 	}
428 
429 	/* generate clause for statements after last "if". */
430 	if (first_stmt_in_basic_block != NULL) {
431 		dt_sugar_new_basic_block(dp, precondition,
432 		    first_stmt_in_basic_block);
433 	}
434 }
435 
436 /*
437  * Generate a new clause which will set the error variable when an error occurs.
438  * Only one of these clauses is created per program (e.g. script file).
439  * The clause is:
440  *
441  * dtrace:::ERROR{ self->%error = 1; }
442  */
443 static dt_node_t *
444 dt_sugar_makeerrorclause(void)
445 {
446 	dt_node_t *acts, *pdesc;
447 
448 	pdesc = dt_node_pdesc_by_name(strdup("dtrace:::ERROR"));
449 
450 	acts = dt_node_statement(dt_node_op2(DT_TOK_ASGN,
451 	    dt_sugar_new_error_var(), dt_node_int(1)));
452 
453 	return (dt_node_clause(pdesc, NULL, acts));
454 }
455 
456 /*
457  * Transform the super-clause into straight-D, returning the new list of
458  * sub-clauses.
459  */
460 dt_node_t *
461 dt_compile_sugar(dtrace_hdl_t *dtp, dt_node_t *clause)
462 {
463 	dt_sugar_parse_t dp = { 0 };
464 	int condid = 0;
465 
466 	dp.dtsp_dtp = dtp;
467 	dp.dtsp_pdescs = clause->dn_pdescs;
468 
469 	/* make dt_node_int() generate an "int"-typed integer */
470 	yyintdecimal = B_TRUE;
471 	yyintsuffix[0] = '\0';
472 	yyintprefix = 0;
473 
474 	dt_sugar_visit_all(&dp, clause);
475 
476 	if (dp.dtsp_num_ifs == 0 && dp.dtsp_num_conditions == 0) {
477 		/*
478 		 * There is nothing that modifies the number of clauses.  Use
479 		 * the existing clause as-is, with its predicate intact.  This
480 		 * ensures that in the absence of D sugar, the body of the
481 		 * clause can create a variable that is referenced in the
482 		 * predicate.
483 		 */
484 		dt_sugar_append_clause(&dp, dt_node_clause(clause->dn_pdescs,
485 		    clause->dn_pred, clause->dn_acts));
486 	} else {
487 		if (clause->dn_pred != NULL) {
488 			condid = dt_sugar_new_condition(&dp,
489 			    clause->dn_pred, condid);
490 		}
491 
492 		if (clause->dn_acts == NULL) {
493 			/*
494 			 * dt_sugar_visit_stmts() does not emit a clause with
495 			 * an empty body (e.g. if there's an empty "if" body),
496 			 * but we need the empty body here so that we
497 			 * continue to get the default tracing action.
498 			 */
499 			dt_sugar_new_basic_block(&dp, condid, NULL);
500 		} else {
501 			dt_sugar_visit_stmts(&dp, clause->dn_acts, condid);
502 		}
503 	}
504 
505 	if (dp.dtsp_num_conditions != 0) {
506 		dt_sugar_prepend_clause(&dp,
507 		    dt_sugar_new_clearerror_clause(&dp));
508 	}
509 
510 	if (dp.dtsp_clause_list != NULL &&
511 	    dp.dtsp_clause_list->dn_list != NULL && !dtp->dt_has_sugar) {
512 		dtp->dt_has_sugar = B_TRUE;
513 		dt_sugar_prepend_clause(&dp, dt_sugar_makeerrorclause());
514 	}
515 	return (dp.dtsp_clause_list);
516 }
517