| /* toir.cc -- Lower D frontend statements to GCC trees. |
| Copyright (C) 2006-2021 Free Software Foundation, Inc. |
| |
| GCC is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3, or (at your option) |
| any later version. |
| |
| GCC is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GCC; see the file COPYING3. If not see |
| <http://www.gnu.org/licenses/>. */ |
| |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| |
| #include "dmd/aggregate.h" |
| #include "dmd/declaration.h" |
| #include "dmd/expression.h" |
| #include "dmd/identifier.h" |
| #include "dmd/init.h" |
| #include "dmd/statement.h" |
| |
| #include "tree.h" |
| #include "tree-iterator.h" |
| #include "options.h" |
| #include "stmt.h" |
| #include "fold-const.h" |
| #include "diagnostic.h" |
| #include "stringpool.h" |
| #include "function.h" |
| #include "toplev.h" |
| |
| #include "d-tree.h" |
| |
| |
| /* Update data for defined and undefined labels when leaving a scope. */ |
| |
| bool |
| pop_binding_label (Statement * const &, d_label_entry *ent, binding_level *bl) |
| { |
| binding_level *obl = bl->level_chain; |
| |
| if (ent->level == bl) |
| { |
| if (bl->kind == level_try) |
| ent->in_try_scope = true; |
| else if (bl->kind == level_catch) |
| ent->in_catch_scope = true; |
| |
| ent->level = obl; |
| } |
| else if (ent->fwdrefs) |
| { |
| for (d_label_use_entry *ref = ent->fwdrefs; ref; ref = ref->next) |
| ref->level = obl; |
| } |
| |
| return true; |
| } |
| |
| /* At the end of a function, all labels declared within the function |
| go out of scope. Queue them in LABELS. */ |
| |
| bool |
| pop_label (Statement * const &, d_label_entry *ent, vec <tree> &labels) |
| { |
| if (!ent->bc_label) |
| { |
| /* Put the labels into the "variables" of the top-level block, |
| so debugger can see them. */ |
| if (DECL_NAME (ent->label)) |
| { |
| gcc_assert (DECL_INITIAL (ent->label) != NULL_TREE); |
| labels.safe_push (ent->label); |
| } |
| } |
| |
| return true; |
| } |
| |
| /* The D front-end does not use the 'binding level' system for a symbol table, |
| however it has been the goto structure for tracking code flow. |
| Primarily it is only needed to get debugging information for local variables |
| and otherwise support the back-end. */ |
| |
| void |
| push_binding_level (level_kind kind) |
| { |
| /* Add it to the front of currently active scopes stack. */ |
| binding_level *new_level = ggc_cleared_alloc <binding_level> (); |
| new_level->level_chain = current_binding_level; |
| new_level->kind = kind; |
| |
| current_binding_level = new_level; |
| } |
| |
| static int |
| cmp_labels (const void *p1, const void *p2) |
| { |
| const tree *l1 = (const tree *) p1; |
| const tree *l2 = (const tree *) p2; |
| return DECL_UID (*l1) - DECL_UID (*l2); |
| } |
| |
| tree |
| pop_binding_level (void) |
| { |
| binding_level *level = current_binding_level; |
| current_binding_level = level->level_chain; |
| |
| tree block = make_node (BLOCK); |
| BLOCK_VARS (block) = level->names; |
| BLOCK_SUBBLOCKS (block) = level->blocks; |
| |
| /* In each subblock, record that this is its superior. */ |
| for (tree t = level->blocks; t; t = BLOCK_CHAIN (t)) |
| BLOCK_SUPERCONTEXT (t) = block; |
| |
| if (level->kind == level_function) |
| { |
| /* Dispose of the block that we just made inside some higher level. */ |
| DECL_INITIAL (current_function_decl) = block; |
| BLOCK_SUPERCONTEXT (block) = current_function_decl; |
| |
| /* Pop all the labels declared in the function. */ |
| if (d_function_chain->labels) |
| { |
| auto_vec <tree> labels; |
| d_function_chain->labels->traverse <vec <tree> &, |
| &pop_label> (labels); |
| d_function_chain->labels->empty (); |
| labels.qsort (cmp_labels); |
| for (unsigned i = 0; i < labels.length (); ++i) |
| { |
| DECL_CHAIN (labels[i]) = BLOCK_VARS (block); |
| BLOCK_VARS (block) = labels[i]; |
| } |
| } |
| } |
| else |
| { |
| /* Any uses of undefined labels, and any defined labels, now operate |
| under constraints of next binding contour. */ |
| if (d_function_chain && d_function_chain->labels) |
| { |
| language_function *f = d_function_chain; |
| f->labels->traverse <binding_level *, &pop_binding_label> (level); |
| } |
| |
| current_binding_level->blocks |
| = block_chainon (current_binding_level->blocks, block); |
| } |
| |
| TREE_USED (block) = 1; |
| return block; |
| } |
| |
| /* Create an empty statement tree rooted at T. */ |
| |
| void |
| push_stmt_list (void) |
| { |
| tree t = alloc_stmt_list (); |
| vec_safe_push (d_function_chain->stmt_list, t); |
| d_keep (t); |
| } |
| |
| /* Finish the statement tree rooted at T. */ |
| |
| tree |
| pop_stmt_list (void) |
| { |
| tree t = d_function_chain->stmt_list->pop (); |
| |
| /* If the statement list is completely empty, just return it. This is just |
| as good as build_empty_stmt, with the advantage that statement lists |
| are merged when they are appended to one another. So using the |
| STATEMENT_LIST avoids pathological buildup of EMPTY_STMT_P statements. */ |
| if (TREE_SIDE_EFFECTS (t)) |
| { |
| /* If the statement list contained exactly one statement, then extract |
| it immediately. */ |
| tree_stmt_iterator i = tsi_start (t); |
| |
| if (tsi_one_before_end_p (i)) |
| { |
| tree u = tsi_stmt (i); |
| tsi_delink (&i); |
| free_stmt_list (t); |
| t = u; |
| } |
| } |
| |
| return t; |
| } |
| |
| /* T is an expression statement. Add it to the statement-tree. */ |
| |
| void |
| add_stmt (tree t) |
| { |
| /* Ignore (void) 0; expression statements received from the frontend. |
| Likewise void_node is used when contracts become nops in release code. */ |
| if (t == void_node || IS_EMPTY_STMT (t)) |
| return; |
| |
| /* At this point, we no longer care about the value of expressions, |
| so if there's no side-effects, then don't add it. */ |
| if (!TREE_SIDE_EFFECTS (t)) |
| return; |
| |
| if (TREE_CODE (t) == COMPOUND_EXPR) |
| { |
| /* Push out each comma expressions as separate statements. */ |
| add_stmt (TREE_OPERAND (t, 0)); |
| add_stmt (TREE_OPERAND (t, 1)); |
| } |
| else |
| { |
| /* Force the type to be void so we don't need to create a temporary |
| variable to hold the inner expression. */ |
| if (TREE_CODE (t) == CLEANUP_POINT_EXPR) |
| TREE_TYPE (t) = void_type_node; |
| |
| /* Append the expression to the statement list. |
| Make sure it has a proper location. */ |
| if (EXPR_P (t) && !EXPR_HAS_LOCATION (t)) |
| SET_EXPR_LOCATION (t, input_location); |
| |
| tree stmt_list = d_function_chain->stmt_list->last (); |
| append_to_statement_list_force (t, &stmt_list); |
| } |
| } |
| |
| /* Implements the visitor interface to build the GCC trees of all Statement |
| AST classes emitted from the D Front-end. |
| All visit methods accept one parameter S, which holds the frontend AST |
| of the statement to compile. They also don't return any value, instead |
| generated code are pushed to add_stmt(), which appends them to the |
| statement list in the current_binding_level. */ |
| |
| class IRVisitor : public Visitor |
| { |
| using Visitor::visit; |
| |
| FuncDeclaration *func_; |
| |
| /* Stack of labels which are targets for "break" and "continue", |
| linked through TREE_CHAIN. */ |
| tree break_label_; |
| tree continue_label_; |
| |
| public: |
| IRVisitor (FuncDeclaration *fd) |
| { |
| this->func_ = fd; |
| this->break_label_ = NULL_TREE; |
| this->continue_label_ = NULL_TREE; |
| } |
| |
| /* Helper for generating code for the statement AST class S. |
| Sets up the location of the statement before lowering. */ |
| |
| void build_stmt (Statement *s) |
| { |
| location_t saved_location = input_location; |
| input_location = make_location_t (s->loc); |
| s->accept (this); |
| input_location = saved_location; |
| } |
| |
| /* Start a new scope for a KIND statement. |
| Each user-declared variable will have a binding contour that begins |
| where the variable is declared and ends at its containing scope. */ |
| |
| void start_scope (level_kind kind) |
| { |
| push_binding_level (kind); |
| push_stmt_list (); |
| } |
| |
| /* Leave scope pushed by start_scope, returning a new bind_expr if |
| any variables where declared in the scope. */ |
| |
| tree end_scope (void) |
| { |
| tree block = pop_binding_level (); |
| tree body = pop_stmt_list (); |
| |
| if (!BLOCK_VARS (block)) |
| return body; |
| |
| tree bind = build3 (BIND_EXPR, void_type_node, |
| BLOCK_VARS (block), body, block); |
| TREE_SIDE_EFFECTS (bind) = 1; |
| return bind; |
| } |
| |
| /* Like end_scope, but also push it into the outer statement-tree. */ |
| |
| void finish_scope (void) |
| { |
| tree scope = this->end_scope (); |
| add_stmt (scope); |
| } |
| |
| /* Return TRUE if IDENT is the current function return label. */ |
| |
| bool is_return_label (Identifier *ident) |
| { |
| if (this->func_->returnLabel) |
| return this->func_->returnLabel->ident == ident; |
| |
| return false; |
| } |
| |
| /* Define a label, specifying the location in the source file. |
| Return the LABEL_DECL node for the label. */ |
| |
| tree define_label (Statement *s, Identifier *ident = NULL) |
| { |
| tree label = this->lookup_label (s, ident); |
| gcc_assert (DECL_INITIAL (label) == NULL_TREE); |
| |
| d_label_entry *ent = d_function_chain->labels->get (s); |
| gcc_assert (ent != NULL); |
| |
| /* Mark label as having been defined. */ |
| DECL_INITIAL (label) = error_mark_node; |
| |
| ent->level = current_binding_level; |
| |
| for (d_label_use_entry *ref = ent->fwdrefs; ref ; ref = ref->next) |
| this->check_previous_goto (ent->statement, ref); |
| ent->fwdrefs = NULL; |
| |
| return label; |
| } |
| |
| /* Emit a LABEL expression. */ |
| |
| void do_label (tree label) |
| { |
| /* Don't write out label unless it is marked as used by the frontend. |
| This makes auto-vectorization possible in conditional loops. |
| The only excemption to this is in the LabelStatement visitor, |
| in which all computed labels are marked regardless. */ |
| if (TREE_USED (label)) |
| add_stmt (build1 (LABEL_EXPR, void_type_node, label)); |
| } |
| |
| /* Emit a goto expression to LABEL. */ |
| |
| void do_jump (tree label) |
| { |
| add_stmt (fold_build1 (GOTO_EXPR, void_type_node, label)); |
| TREE_USED (label) = 1; |
| } |
| |
| /* Check that a new jump at statement scope FROM to a label declared in |
| statement scope TO is valid. */ |
| |
| void check_goto (Statement *from, Statement *to) |
| { |
| d_label_entry *ent = d_function_chain->labels->get (to); |
| gcc_assert (ent != NULL); |
| |
| /* If the label hasn't been defined yet, defer checking. */ |
| if (!DECL_INITIAL (ent->label)) |
| { |
| d_label_use_entry *fwdref = ggc_alloc <d_label_use_entry> (); |
| fwdref->level = current_binding_level; |
| fwdref->statement = from; |
| fwdref->next = ent->fwdrefs; |
| ent->fwdrefs = fwdref; |
| return; |
| } |
| |
| if (ent->in_try_scope) |
| error_at (make_location_t (from->loc), |
| "cannot %<goto%> into %<try%> block"); |
| else if (ent->in_catch_scope) |
| error_at (make_location_t (from->loc), |
| "cannot %<goto%> into %<catch%> block"); |
| } |
| |
| /* Check that a previously seen jump to a newly defined label is valid. |
| S is the label statement; FWDREF is the jump context. This is called |
| for both user-defined and case labels. */ |
| |
| void check_previous_goto (Statement *s, d_label_use_entry *fwdref) |
| { |
| for (binding_level *b = current_binding_level; b ; b = b->level_chain) |
| { |
| if (b == fwdref->level) |
| break; |
| |
| if (b->kind == level_try || b->kind == level_catch) |
| { |
| location_t location; |
| |
| if (s->isLabelStatement ()) |
| { |
| location = make_location_t (fwdref->statement->loc); |
| if (b->kind == level_try) |
| error_at (location, "cannot %<goto%> into %<try%> block"); |
| else |
| error_at (location, "cannot %<goto%> into %<catch%> block"); |
| } |
| else if (s->isCaseStatement ()) |
| { |
| location = make_location_t (s->loc); |
| error_at (location, "case cannot be in different " |
| "%<try%> block level from %<switch%>"); |
| } |
| else if (s->isDefaultStatement ()) |
| { |
| location = make_location_t (s->loc); |
| error_at (location, "default cannot be in different " |
| "%<try%> block level from %<switch%>"); |
| } |
| else |
| gcc_unreachable (); |
| } |
| } |
| } |
| |
| /* Get or build LABEL_DECL using the IDENT and statement block S given. */ |
| |
| tree lookup_label (Statement *s, Identifier *ident = NULL) |
| { |
| /* You can't use labels at global scope. */ |
| if (d_function_chain == NULL) |
| { |
| error ("label %s referenced outside of any function", |
| ident ? ident->toChars () : "(unnamed)"); |
| return NULL_TREE; |
| } |
| |
| /* Create the label htab for the function on demand. */ |
| if (!d_function_chain->labels) |
| { |
| d_function_chain->labels |
| = hash_map <Statement *, d_label_entry>::create_ggc (13); |
| } |
| |
| d_label_entry *ent = d_function_chain->labels->get (s); |
| if (ent != NULL) |
| return ent->label; |
| else |
| { |
| tree name = ident ? get_identifier (ident->toChars ()) : NULL_TREE; |
| tree decl = build_decl (make_location_t (s->loc), LABEL_DECL, |
| name, void_type_node); |
| DECL_CONTEXT (decl) = current_function_decl; |
| DECL_MODE (decl) = VOIDmode; |
| |
| /* Create new empty slot. */ |
| ent = ggc_cleared_alloc <d_label_entry> (); |
| ent->statement = s; |
| ent->label = decl; |
| |
| bool existed = d_function_chain->labels->put (s, *ent); |
| gcc_assert (!existed); |
| |
| return decl; |
| } |
| } |
| |
| /* Get the LABEL_DECL to represent a break or continue for the |
| statement S given. BC indicates which. */ |
| |
| tree lookup_bc_label (Statement *s, bc_kind bc) |
| { |
| tree vec = this->lookup_label (s); |
| |
| /* The break and continue labels are put into a TREE_VEC. */ |
| if (TREE_CODE (vec) == LABEL_DECL) |
| { |
| d_label_entry *ent = d_function_chain->labels->get (s); |
| gcc_assert (ent != NULL); |
| |
| vec = make_tree_vec (2); |
| TREE_VEC_ELT (vec, bc_break) = ent->label; |
| |
| /* Build the continue label. */ |
| tree label = build_decl (make_location_t (s->loc), LABEL_DECL, |
| NULL_TREE, void_type_node); |
| DECL_CONTEXT (label) = current_function_decl; |
| DECL_MODE (label) = VOIDmode; |
| TREE_VEC_ELT (vec, bc_continue) = label; |
| |
| ent->label = vec; |
| ent->bc_label = true; |
| } |
| |
| return TREE_VEC_ELT (vec, bc); |
| } |
| |
| /* Set and return the current break label for the current block. */ |
| |
| tree push_break_label (Statement *s) |
| { |
| tree label = this->lookup_bc_label (s->getRelatedLabeled (), bc_break); |
| DECL_CHAIN (label) = this->break_label_; |
| this->break_label_ = label; |
| return label; |
| } |
| |
| /* Finish with the current break label. */ |
| |
| void pop_break_label (tree label) |
| { |
| gcc_assert (this->break_label_ == label); |
| this->break_label_ = DECL_CHAIN (this->break_label_); |
| this->do_label (label); |
| } |
| |
| /* Set and return the continue label for the current block. */ |
| |
| tree push_continue_label (Statement *s) |
| { |
| tree label = this->lookup_bc_label (s->getRelatedLabeled (), bc_continue); |
| DECL_CHAIN (label) = this->continue_label_; |
| this->continue_label_ = label; |
| return label; |
| } |
| |
| /* Finish with the current continue label. */ |
| |
| void pop_continue_label (tree label) |
| { |
| gcc_assert (this->continue_label_ == label); |
| this->continue_label_ = DECL_CHAIN (this->continue_label_); |
| this->do_label (label); |
| } |
| |
| /* Visitor interfaces. */ |
| |
| |
| /* This should be overridden by each statement class. */ |
| |
| void visit (Statement *) |
| { |
| gcc_unreachable (); |
| } |
| |
| /* The frontend lowers `scope (exit/failure/success)' statements as |
| try/catch/finally. At this point, this statement is just an empty |
| placeholder. Maybe the frontend shouldn't leak these. */ |
| |
| void visit (ScopeGuardStatement *) |
| { |
| } |
| |
| /* If statements provide simple conditional execution of statements. */ |
| |
| void visit (IfStatement *s) |
| { |
| this->start_scope (level_cond); |
| |
| /* Build the outer `if' condition, which may produce temporaries |
| requiring scope destruction. */ |
| tree ifcond = convert_for_condition (build_expr_dtor (s->condition), |
| s->condition->type); |
| tree ifbody = void_node; |
| tree elsebody = void_node; |
| |
| /* Build the `then' branch. */ |
| if (s->ifbody) |
| { |
| push_stmt_list (); |
| this->build_stmt (s->ifbody); |
| ifbody = pop_stmt_list (); |
| } |
| |
| /* Now build the `else' branch, which may have nested `else if' parts. */ |
| if (s->elsebody) |
| { |
| push_stmt_list (); |
| this->build_stmt (s->elsebody); |
| elsebody = pop_stmt_list (); |
| } |
| |
| /* Wrap up our constructed if condition into a COND_EXPR. */ |
| tree cond = build_vcondition (ifcond, ifbody, elsebody); |
| add_stmt (cond); |
| |
| /* Finish the if-then scope. */ |
| this->finish_scope (); |
| } |
| |
| /* Should there be any `pragma (...)' statements requiring code generation, |
| here would be the place to do it. For now, all pragmas are handled |
| by the frontend. */ |
| |
| void visit (PragmaStatement *) |
| { |
| } |
| |
| /* The frontend lowers `while (...)' statements as `for (...)' loops. |
| This visitor is not strictly required other than to enforce that |
| these kinds of statements never reach here. */ |
| |
| void visit (WhileStatement *) |
| { |
| gcc_unreachable (); |
| } |
| |
| /* Do while statments implement simple loops. The body is executed, then |
| the condition is evaluated. */ |
| |
| void visit (DoStatement *s) |
| { |
| tree lbreak = this->push_break_label (s); |
| |
| this->start_scope (level_loop); |
| if (s->_body) |
| { |
| tree lcontinue = this->push_continue_label (s); |
| this->build_stmt (s->_body); |
| this->pop_continue_label (lcontinue); |
| } |
| |
| /* Build the outer `while' condition, which may produce temporaries |
| requiring scope destruction. */ |
| tree exitcond = convert_for_condition (build_expr_dtor (s->condition), |
| s->condition->type); |
| add_stmt (build_vcondition (exitcond, void_node, |
| build1 (GOTO_EXPR, void_type_node, lbreak))); |
| TREE_USED (lbreak) = 1; |
| |
| tree body = this->end_scope (); |
| add_stmt (build1 (LOOP_EXPR, void_type_node, body)); |
| |
| this->pop_break_label (lbreak); |
| } |
| |
| /* For statements implement loops with initialization, test, and |
| increment clauses. */ |
| |
| void visit (ForStatement *s) |
| { |
| tree lbreak = this->push_break_label (s); |
| this->start_scope (level_loop); |
| |
| if (s->_init) |
| this->build_stmt (s->_init); |
| |
| if (s->condition) |
| { |
| tree exitcond = convert_for_condition (build_expr_dtor (s->condition), |
| s->condition->type); |
| add_stmt (build_vcondition (exitcond, void_node, |
| build1 (GOTO_EXPR, void_type_node, |
| lbreak))); |
| TREE_USED (lbreak) = 1; |
| } |
| |
| if (s->_body) |
| { |
| tree lcontinue = this->push_continue_label (s); |
| this->build_stmt (s->_body); |
| this->pop_continue_label (lcontinue); |
| } |
| |
| if (s->increment) |
| { |
| /* Force side effects? */ |
| add_stmt (build_expr_dtor (s->increment)); |
| } |
| |
| tree body = this->end_scope (); |
| add_stmt (build1 (LOOP_EXPR, void_type_node, body)); |
| |
| this->pop_break_label (lbreak); |
| } |
| |
| /* The frontend lowers `foreach (...)' statements as `for (...)' loops. |
| This visitor is not strictly required other than to enforce that |
| these kinds of statements never reach here. */ |
| |
| void visit (ForeachStatement *) |
| { |
| gcc_unreachable (); |
| } |
| |
| /* The frontend lowers `foreach (...; [x..y])' statements as `for (...)' |
| loops. This visitor is not strictly required other than to enforce that |
| these kinds of statements never reach here. */ |
| |
| void visit (ForeachRangeStatement *) |
| { |
| gcc_unreachable (); |
| } |
| |
| /* Jump to the associated exit label for the current loop. If IDENT |
| for the Statement is not null, then the label is user defined. */ |
| |
| void visit (BreakStatement *s) |
| { |
| if (s->ident) |
| { |
| /* The break label may actually be some levels up. |
| eg: on a try/finally wrapping a loop. */ |
| LabelStatement *label = this->func_->searchLabel (s->ident)->statement; |
| gcc_assert (label != NULL); |
| Statement *stmt = label->statement->getRelatedLabeled (); |
| this->do_jump (this->lookup_bc_label (stmt, bc_break)); |
| } |
| else |
| this->do_jump (this->break_label_); |
| } |
| |
| /* Jump to the associated continue label for the current loop. If IDENT |
| for the Statement is not null, then the label is user defined. */ |
| |
| void visit (ContinueStatement *s) |
| { |
| if (s->ident) |
| { |
| LabelStatement *label = this->func_->searchLabel (s->ident)->statement; |
| gcc_assert (label != NULL); |
| this->do_jump (this->lookup_bc_label (label->statement, |
| bc_continue)); |
| } |
| else |
| this->do_jump (this->continue_label_); |
| } |
| |
| /* A goto statement jumps to the statement identified by the given label. */ |
| |
| void visit (GotoStatement *s) |
| { |
| gcc_assert (s->label->statement != NULL); |
| gcc_assert (s->tf == s->label->statement->tf); |
| |
| /* If no label found, there was an error. */ |
| tree label = this->lookup_label (s->label->statement, s->label->ident); |
| this->do_jump (label); |
| |
| /* Need to error if the goto is jumping into a try or catch block. */ |
| this->check_goto (s, s->label->statement); |
| } |
| |
| /* Statements can be labeled. A label is an identifier that precedes |
| a statement. */ |
| |
| void visit (LabelStatement *s) |
| { |
| LabelDsymbol *sym; |
| |
| if (this->is_return_label (s->ident)) |
| sym = this->func_->returnLabel; |
| else |
| sym = this->func_->searchLabel (s->ident); |
| |
| /* If no label found, there was an error. */ |
| tree label = this->define_label (sym->statement, sym->ident); |
| TREE_USED (label) = 1; |
| |
| this->do_label (label); |
| |
| if (this->is_return_label (s->ident) && this->func_->fensure != NULL) |
| this->build_stmt (this->func_->fensure); |
| else if (s->statement) |
| this->build_stmt (s->statement); |
| } |
| |
| /* A switch statement goes to one of a collection of case statements |
| depending on the value of the switch expression. */ |
| |
| void visit (SwitchStatement *s) |
| { |
| this->start_scope (level_switch); |
| tree lbreak = this->push_break_label (s); |
| |
| tree condition = build_expr_dtor (s->condition); |
| Type *condtype = s->condition->type->toBasetype (); |
| |
| /* A switch statement on a string gets turned into a library call, |
| which does a binary lookup on list of string cases. */ |
| if (s->condition->type->isString ()) |
| { |
| Type *etype = condtype->nextOf ()->toBasetype (); |
| libcall_fn libcall; |
| |
| switch (etype->ty) |
| { |
| case Tchar: |
| libcall = LIBCALL_SWITCH_STRING; |
| break; |
| |
| case Twchar: |
| libcall = LIBCALL_SWITCH_USTRING; |
| break; |
| |
| case Tdchar: |
| libcall = LIBCALL_SWITCH_DSTRING; |
| break; |
| |
| default: |
| ::error ("switch statement value must be an array of " |
| "some character type, not %s", etype->toChars ()); |
| gcc_unreachable (); |
| } |
| |
| /* Apparently the backend is supposed to sort and set the indexes |
| on the case array, have to change them to be usable. */ |
| Type *satype = condtype->sarrayOf (s->cases->length); |
| vec <constructor_elt, va_gc> *elms = NULL; |
| |
| s->cases->sort (); |
| |
| for (size_t i = 0; i < s->cases->length; i++) |
| { |
| CaseStatement *cs = (*s->cases)[i]; |
| cs->index = i; |
| |
| if (cs->exp->op != TOKstring) |
| s->error ("case '%s' is not a string", cs->exp->toChars ()); |
| else |
| { |
| tree exp = build_expr (cs->exp, true); |
| CONSTRUCTOR_APPEND_ELT (elms, size_int (i), exp); |
| } |
| } |
| |
| /* Build static declaration to reference constructor. */ |
| tree ctor = build_constructor (build_ctype (satype), elms); |
| tree decl = build_artificial_decl (TREE_TYPE (ctor), ctor); |
| TREE_READONLY (decl) = 1; |
| d_pushdecl (decl); |
| rest_of_decl_compilation (decl, 1, 0); |
| |
| /* Pass it as a dynamic array. */ |
| decl = d_array_value (build_ctype (condtype->arrayOf ()), |
| size_int (s->cases->length), |
| build_address (decl)); |
| |
| condition = build_libcall (libcall, Type::tint32, 2, decl, condition); |
| } |
| else if (!condtype->isscalar ()) |
| { |
| error ("cannot handle switch condition of type %s", |
| condtype->toChars ()); |
| gcc_unreachable (); |
| } |
| |
| condition = fold (condition); |
| |
| /* Build LABEL_DECLs now so they can be refered to by goto case. |
| Also checking the jump from the switch to the label is allowed. */ |
| if (s->cases) |
| { |
| for (size_t i = 0; i < s->cases->length; i++) |
| { |
| CaseStatement *cs = (*s->cases)[i]; |
| tree caselabel = this->lookup_label (cs); |
| |
| /* Write cases as a series of if-then-else blocks. |
| if (condition == case) |
| goto caselabel; */ |
| if (s->hasVars) |
| { |
| tree ifcase = build2 (EQ_EXPR, build_ctype (condtype), |
| condition, build_expr_dtor (cs->exp)); |
| tree ifbody = fold_build1 (GOTO_EXPR, void_type_node, |
| caselabel); |
| tree cond = build_vcondition (ifcase, ifbody, void_node); |
| TREE_USED (caselabel) = 1; |
| LABEL_VARIABLE_CASE (caselabel) = 1; |
| add_stmt (cond); |
| } |
| |
| this->check_goto (s, cs); |
| } |
| |
| if (s->sdefault) |
| { |
| tree defaultlabel = this->lookup_label (s->sdefault); |
| |
| /* The default label is the last `else' block. */ |
| if (s->hasVars) |
| { |
| this->do_jump (defaultlabel); |
| LABEL_VARIABLE_CASE (defaultlabel) = 1; |
| } |
| |
| this->check_goto (s, s->sdefault); |
| } |
| } |
| |
| /* Switch body goes in its own statement list. */ |
| push_stmt_list (); |
| if (s->_body) |
| this->build_stmt (s->_body); |
| |
| tree casebody = pop_stmt_list (); |
| |
| /* Wrap up constructed body into a switch_expr, unless it was |
| converted to an if-then-else expression. */ |
| if (s->hasVars) |
| add_stmt (casebody); |
| else |
| { |
| tree switchexpr = build2 (SWITCH_EXPR, TREE_TYPE (condition), |
| condition, casebody); |
| add_stmt (switchexpr); |
| SWITCH_ALL_CASES_P (switchexpr) = 1; |
| } |
| |
| SWITCH_BREAK_LABEL_P (lbreak) = 1; |
| |
| /* If the switch had any `break' statements, emit the label now. */ |
| this->pop_break_label (lbreak); |
| this->finish_scope (); |
| } |
| |
| /* Declare the case label associated with the current SwitchStatement. */ |
| |
| void visit (CaseStatement *s) |
| { |
| /* Emit the case label. */ |
| tree label = this->define_label (s); |
| |
| if (LABEL_VARIABLE_CASE (label)) |
| this->do_label (label); |
| else |
| { |
| tree casevalue; |
| if (s->exp->type->isscalar ()) |
| casevalue = build_expr (s->exp); |
| else |
| casevalue = build_integer_cst (s->index, build_ctype (Type::tint32)); |
| |
| tree caselabel = build_case_label (casevalue, NULL_TREE, label); |
| add_stmt (caselabel); |
| } |
| |
| /* Now do the body. */ |
| if (s->statement) |
| this->build_stmt (s->statement); |
| } |
| |
| /* Declare the default label associated with the current SwitchStatement. */ |
| |
| void visit (DefaultStatement *s) |
| { |
| /* Emit the default case label. */ |
| tree label = this->define_label (s); |
| |
| if (LABEL_VARIABLE_CASE (label)) |
| this->do_label (label); |
| else |
| { |
| tree caselabel = build_case_label (NULL_TREE, NULL_TREE, label); |
| add_stmt (caselabel); |
| } |
| |
| /* Now do the body. */ |
| if (s->statement) |
| this->build_stmt (s->statement); |
| } |
| |
| /* Implements `goto default' by jumping to the label associated with |
| the DefaultStatement in a switch block. */ |
| |
| void visit (GotoDefaultStatement *s) |
| { |
| tree label = this->lookup_label (s->sw->sdefault); |
| this->do_jump (label); |
| } |
| |
| /* Implements `goto case' by jumping to the label associated with the |
| CaseStatement in a switch block. */ |
| |
| void visit (GotoCaseStatement *s) |
| { |
| tree label = this->lookup_label (s->cs); |
| this->do_jump (label); |
| } |
| |
| /* Throw a SwitchError exception, called when a switch statement has |
| no DefaultStatement, yet none of the cases match. */ |
| |
| void visit (SwitchErrorStatement *s) |
| { |
| add_stmt (build_assert_call (s->loc, LIBCALL_SWITCH_ERROR)); |
| } |
| |
| /* A return statement exits the current function and supplies its return |
| value, if the return type is not void. */ |
| |
| void visit (ReturnStatement *s) |
| { |
| if (s->exp == NULL || s->exp->type->toBasetype ()->ty == Tvoid) |
| { |
| /* Return has no value. */ |
| add_stmt (return_expr (NULL_TREE)); |
| return; |
| } |
| |
| TypeFunction *tf = this->func_->type->toTypeFunction (); |
| Type *type = this->func_->tintro != NULL |
| ? this->func_->tintro->nextOf () : tf->nextOf (); |
| |
| if ((this->func_->isMain () || this->func_->isCMain ()) |
| && type->toBasetype ()->ty == Tvoid) |
| type = Type::tint32; |
| |
| if (this->func_->shidden) |
| { |
| /* Returning by hidden reference, store the result into the retval decl. |
| The result returned then becomes the retval reference itself. */ |
| tree decl = DECL_RESULT (get_symbol_decl (this->func_)); |
| gcc_assert (!tf->isref); |
| |
| /* If returning via NRVO, just refer to the DECL_RESULT; this differs |
| from using NULL_TREE in that it indicates that we care about the |
| value of the DECL_RESULT. */ |
| if (this->func_->nrvo_can && this->func_->nrvo_var) |
| { |
| add_stmt (return_expr (decl)); |
| return; |
| } |
| |
| /* Detect a call to a constructor function, or if returning a struct |
| literal, write result directly into the return value. */ |
| StructLiteralExp *sle = NULL; |
| bool using_rvo_p = false; |
| |
| if (DotVarExp *dve = (s->exp->op == TOKcall |
| && s->exp->isCallExp ()->e1->op == TOKdotvar |
| ? s->exp->isCallExp ()->e1->isDotVarExp () |
| : NULL)) |
| { |
| if (dve->var->isCtorDeclaration ()) |
| { |
| if (CommaExp *ce = dve->e1->isCommaExp ()) |
| { |
| /* Temporary initialized inside a return expression, and |
| used as the return value. Replace it with the hidden |
| reference to allow RVO return. */ |
| DeclarationExp *de = ce->e1->isDeclarationExp (); |
| VarExp *ve = ce->e2->isVarExp (); |
| if (de != NULL && ve != NULL |
| && ve->var == de->declaration |
| && ve->var->storage_class & STCtemp) |
| { |
| tree var = get_symbol_decl (ve->var); |
| TREE_ADDRESSABLE (var) = 1; |
| SET_DECL_VALUE_EXPR (var, decl); |
| DECL_HAS_VALUE_EXPR_P (var) = 1; |
| SET_DECL_LANG_NRVO (var, this->func_->shidden); |
| using_rvo_p = true; |
| } |
| } |
| else |
| sle = dve->e1->isStructLiteralExp (); |
| } |
| } |
| else |
| sle = s->exp->isStructLiteralExp (); |
| |
| if (sle != NULL) |
| { |
| StructDeclaration *sd = type->baseElemOf ()->isTypeStruct ()->sym; |
| sle->sym = build_address (this->func_->shidden); |
| using_rvo_p = true; |
| |
| /* Fill any alignment holes in the return slot using memset. */ |
| if (!identity_compare_p (sd) || sd->isUnionDeclaration ()) |
| add_stmt (build_memset_call (this->func_->shidden)); |
| } |
| |
| if (using_rvo_p == true) |
| { |
| /* Generate: (expr, return <retval>); */ |
| add_stmt (build_expr_dtor (s->exp)); |
| } |
| else |
| { |
| /* Generate: (<retval> = expr, return <retval>); */ |
| tree expr = build_expr_dtor (s->exp); |
| tree init = stabilize_expr (&expr); |
| expr = build_assign (INIT_EXPR, this->func_->shidden, expr); |
| add_stmt (compound_expr (init, expr)); |
| } |
| |
| add_stmt (return_expr (decl)); |
| } |
| else if (tf->next->ty == Tnoreturn) |
| { |
| /* Returning an expression that has no value, but has a side effect |
| that should never return. */ |
| add_stmt (build_expr_dtor (s->exp)); |
| add_stmt (return_expr (NULL_TREE)); |
| } |
| else |
| { |
| /* Convert for initializing the DECL_RESULT. */ |
| add_stmt (build_return_dtor (s->exp, type, tf)); |
| } |
| } |
| |
| /* Evaluate the enclosed expression, and add it to the statement list. */ |
| |
| void visit (ExpStatement *s) |
| { |
| if (s->exp) |
| { |
| /* Expression may produce temporaries requiring scope destruction. */ |
| tree exp = build_expr_dtor (s->exp); |
| add_stmt (exp); |
| } |
| } |
| |
| /* Evaluate all enclosed statements. */ |
| |
| void visit (CompoundStatement *s) |
| { |
| if (s->statements == NULL) |
| return; |
| |
| for (size_t i = 0; i < s->statements->length; i++) |
| { |
| Statement *statement = (*s->statements)[i]; |
| |
| if (statement != NULL) |
| this->build_stmt (statement); |
| } |
| } |
| |
| /* The frontend lowers `foreach (Tuple!(...))' statements as an unrolled loop. |
| These are compiled down as a `do ... while (0)', where each unrolled loop |
| is nested inside and given their own continue label to jump to. */ |
| |
| void visit (UnrolledLoopStatement *s) |
| { |
| if (s->statements == NULL) |
| return; |
| |
| tree lbreak = this->push_break_label (s); |
| this->start_scope (level_loop); |
| |
| for (size_t i = 0; i < s->statements->length; i++) |
| { |
| Statement *statement = (*s->statements)[i]; |
| |
| if (statement != NULL) |
| { |
| tree lcontinue = this->push_continue_label (statement); |
| this->build_stmt (statement); |
| this->pop_continue_label (lcontinue); |
| } |
| } |
| |
| this->do_jump (this->break_label_); |
| |
| tree body = this->end_scope (); |
| add_stmt (build1 (LOOP_EXPR, void_type_node, body)); |
| |
| this->pop_break_label (lbreak); |
| } |
| |
| /* Start a new scope and visit all nested statements, wrapping |
| them up into a BIND_EXPR at the end of the scope. */ |
| |
| void visit (ScopeStatement *s) |
| { |
| if (s->statement == NULL) |
| return; |
| |
| this->start_scope (level_block); |
| this->build_stmt (s->statement); |
| this->finish_scope (); |
| } |
| |
| /* A with statement is a way to simplify repeated references to the same |
| object, where the handle is either a class or struct instance. */ |
| |
| void visit (WithStatement *s) |
| { |
| this->start_scope (level_with); |
| |
| if (s->wthis) |
| { |
| /* Perform initialisation of the `with' handle. */ |
| ExpInitializer *ie = s->wthis->_init->isExpInitializer (); |
| gcc_assert (ie != NULL); |
| |
| declare_local_var (s->wthis); |
| tree init = build_expr_dtor (ie->exp); |
| add_stmt (init); |
| } |
| |
| if (s->_body) |
| this->build_stmt (s->_body); |
| |
| this->finish_scope (); |
| } |
| |
| /* Implements `throw Object'. Frontend already checks that the object |
| thrown is a class type, but does not check if it is derived from |
| Object. Foreign objects are not currently supported at run-time. */ |
| |
| void visit (ThrowStatement *s) |
| { |
| ClassDeclaration *cd = s->exp->type->toBasetype ()->isClassHandle (); |
| InterfaceDeclaration *id = cd->isInterfaceDeclaration (); |
| tree arg = build_expr_dtor (s->exp); |
| |
| if (!global.params.useExceptions) |
| { |
| static int warned = 0; |
| if (!warned) |
| { |
| error_at (make_location_t (s->loc), "exception handling disabled; " |
| "use %<-fexceptions%> to enable"); |
| warned = 1; |
| } |
| } |
| |
| if (cd->isCPPclass () || (id != NULL && id->isCPPclass ())) |
| error_at (make_location_t (s->loc), "cannot throw C++ classes"); |
| else if (cd->com || (id != NULL && id->com)) |
| error_at (make_location_t (s->loc), "cannot throw COM objects"); |
| else |
| arg = build_nop (build_ctype (get_object_type ()), arg); |
| |
| add_stmt (build_libcall (LIBCALL_THROW, Type::tvoid, 1, arg)); |
| } |
| |
| /* Build a try-catch statement, one of the building blocks for exception |
| handling generated by the frontend. This is also used to implement |
| `scope (failure)' statements. */ |
| |
| void visit (TryCatchStatement *s) |
| { |
| this->start_scope (level_try); |
| if (s->_body) |
| this->build_stmt (s->_body); |
| |
| tree trybody = this->end_scope (); |
| |
| /* Try handlers go in their own statement list. */ |
| push_stmt_list (); |
| |
| if (s->catches) |
| { |
| for (size_t i = 0; i < s->catches->length; i++) |
| { |
| Catch *vcatch = (*s->catches)[i]; |
| |
| this->start_scope (level_catch); |
| |
| tree ehptr = builtin_decl_explicit (BUILT_IN_EH_POINTER); |
| tree catchtype = build_ctype (vcatch->type); |
| tree object = NULL_TREE; |
| |
| ehptr = build_call_expr (ehptr, 1, integer_zero_node); |
| |
| /* Retrieve the internal exception object, which could be for a |
| D or C++ catch handler. This is different from the generic |
| exception pointer returned from gcc runtime. */ |
| Type *tcatch = vcatch->type->toBasetype (); |
| ClassDeclaration *cd = tcatch->isClassHandle (); |
| |
| libcall_fn libcall = (cd->isCPPclass ()) ? LIBCALL_CXA_BEGIN_CATCH |
| : LIBCALL_BEGIN_CATCH; |
| object = build_libcall (libcall, vcatch->type, 1, ehptr); |
| |
| if (vcatch->var) |
| { |
| tree var = get_symbol_decl (vcatch->var); |
| tree init = build_assign (INIT_EXPR, var, object); |
| |
| declare_local_var (vcatch->var); |
| add_stmt (init); |
| } |
| else |
| { |
| /* Still need to emit a call to __gdc_begin_catch() to |
| remove the object from the uncaught exceptions list. */ |
| add_stmt (object); |
| } |
| |
| if (vcatch->handler) |
| this->build_stmt (vcatch->handler); |
| |
| tree catchbody = this->end_scope (); |
| |
| /* Need to wrap C++ handlers in a try/finally block to signal |
| the end catch callback. */ |
| if (cd->isCPPclass ()) |
| { |
| tree endcatch = build_libcall (LIBCALL_CXA_END_CATCH, |
| Type::tvoid, 0); |
| catchbody = build2 (TRY_FINALLY_EXPR, void_type_node, |
| catchbody, endcatch); |
| } |
| |
| add_stmt (build2 (CATCH_EXPR, void_type_node, |
| catchtype, catchbody)); |
| } |
| } |
| |
| tree catches = pop_stmt_list (); |
| |
| /* Back-end expects all catches in a TRY_CATCH_EXPR to be enclosed in a |
| statement list, however pop_stmt_list may optimize away the list |
| if there is only a single catch to push. */ |
| if (TREE_CODE (catches) != STATEMENT_LIST) |
| { |
| tree stmt_list = alloc_stmt_list (); |
| append_to_statement_list_force (catches, &stmt_list); |
| catches = stmt_list; |
| } |
| |
| add_stmt (build2 (TRY_CATCH_EXPR, void_type_node, trybody, catches)); |
| } |
| |
| /* Build a try-finally statement, one of the building blocks for exception |
| handling generated by the frontend. This is also used to implement |
| `scope (exit)' statements. */ |
| |
| void visit (TryFinallyStatement *s) |
| { |
| this->start_scope (level_try); |
| if (s->_body) |
| this->build_stmt (s->_body); |
| |
| tree trybody = this->end_scope (); |
| |
| this->start_scope (level_finally); |
| if (s->finalbody) |
| this->build_stmt (s->finalbody); |
| |
| tree finally = this->end_scope (); |
| |
| add_stmt (build2 (TRY_FINALLY_EXPR, void_type_node, trybody, finally)); |
| } |
| |
| /* The frontend lowers `synchronized (...)' statements as a call to |
| monitor/critical enter and exit wrapped around try/finally. |
| This visitor is not strictly required other than to enforce that |
| these kinds of statements never reach here. */ |
| |
| void visit (SynchronizedStatement *) |
| { |
| gcc_unreachable (); |
| } |
| |
| /* D Inline Assembler is not implemented, as it would require writing |
| an assembly parser for each supported target. Instead we leverage |
| GCC extended assembler using the GccAsmStatement class. */ |
| |
| void visit (AsmStatement *) |
| { |
| sorry ("D inline assembler statements are not supported in GDC."); |
| } |
| |
| /* Build a GCC extended assembler expression, whose components are |
| an INSN string, some OUTPUTS, some INPUTS, and some CLOBBERS. */ |
| |
| void visit (GccAsmStatement *s) |
| { |
| StringExp *insn = s->insn->toStringExp (); |
| tree outputs = NULL_TREE; |
| tree inputs = NULL_TREE; |
| tree clobbers = NULL_TREE; |
| tree labels = NULL_TREE; |
| |
| /* Collect all arguments, which may be input or output operands. */ |
| if (s->args) |
| { |
| for (size_t i = 0; i < s->args->length; i++) |
| { |
| Identifier *name = (*s->names)[i]; |
| const char *sname = name ? name->toChars () : NULL; |
| tree id = name ? build_string (strlen (sname), sname) : NULL_TREE; |
| |
| StringExp *constr = (*s->constraints)[i]->toStringExp (); |
| const char *cstring = (const char *)(constr->len |
| ? constr->string : ""); |
| tree str = build_string (constr->len, cstring); |
| |
| Expression *earg = (*s->args)[i]; |
| tree val = build_expr (earg); |
| |
| if (i < s->outputargs) |
| { |
| tree arg = build_tree_list (id, str); |
| outputs = chainon (outputs, build_tree_list (arg, val)); |
| } |
| else |
| { |
| tree arg = build_tree_list (id, str); |
| inputs = chainon (inputs, build_tree_list (arg, val)); |
| } |
| } |
| } |
| |
| /* Collect all clobber arguments. */ |
| if (s->clobbers) |
| { |
| for (size_t i = 0; i < s->clobbers->length; i++) |
| { |
| StringExp *clobber = (*s->clobbers)[i]->toStringExp (); |
| const char *cstring = (const char *)(clobber->len |
| ? clobber->string : ""); |
| |
| tree val = build_string (clobber->len, cstring); |
| clobbers = chainon (clobbers, build_tree_list (0, val)); |
| } |
| } |
| |
| /* Collect all goto labels, these should have been already checked |
| by the front-end, so pass down the label symbol to the back-end. */ |
| if (s->labels) |
| { |
| for (size_t i = 0; i < s->labels->length; i++) |
| { |
| Identifier *ident = (*s->labels)[i]; |
| GotoStatement *gs = (*s->gotos)[i]; |
| |
| gcc_assert (gs->label->statement != NULL); |
| gcc_assert (gs->tf == gs->label->statement->tf); |
| |
| const char *sident = ident->toChars (); |
| tree name = build_string (strlen (sident), sident); |
| tree label = this->lookup_label (gs->label->statement, |
| gs->label->ident); |
| TREE_USED (label) = 1; |
| |
| labels = chainon (labels, build_tree_list (name, label)); |
| } |
| } |
| |
| /* Do some extra validation on all input and output operands. */ |
| const char *insnstring = (const char *)(insn->len ? insn->string : ""); |
| tree string = build_string (insn->len, insnstring); |
| string = resolve_asm_operand_names (string, outputs, inputs, labels); |
| |
| if (s->args) |
| { |
| unsigned noutputs = s->outputargs; |
| unsigned ninputs = (s->args->length - noutputs); |
| const char **oconstraints = XALLOCAVEC (const char *, noutputs); |
| bool allows_mem, allows_reg, is_inout; |
| size_t i; |
| tree t; |
| |
| for (i = 0, t = outputs; t != NULL_TREE; t = TREE_CHAIN (t), i++) |
| { |
| tree output = TREE_VALUE (t); |
| const char *constraint |
| = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (t))); |
| |
| oconstraints[i] = constraint; |
| |
| if (parse_output_constraint (&constraint, i, ninputs, noutputs, |
| &allows_mem, &allows_reg, &is_inout)) |
| { |
| /* If the output argument is going to end up in memory. */ |
| if (!allows_reg) |
| d_mark_addressable (output); |
| } |
| else |
| output = error_mark_node; |
| |
| TREE_VALUE (t) = output; |
| } |
| |
| for (i = 0, t = inputs; t != NULL_TREE; t = TREE_CHAIN (t), i++) |
| { |
| tree input = TREE_VALUE (t); |
| const char *constraint |
| = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (t))); |
| |
| if (parse_input_constraint (&constraint, i, ninputs, noutputs, 0, |
| oconstraints, &allows_mem, &allows_reg)) |
| { |
| /* If the input argument is going to end up in memory. */ |
| if (!allows_reg && allows_mem) |
| d_mark_addressable (input); |
| } |
| else |
| input = error_mark_node; |
| |
| TREE_VALUE (t) = input; |
| } |
| } |
| |
| tree exp = build5 (ASM_EXPR, void_type_node, string, |
| outputs, inputs, clobbers, labels); |
| SET_EXPR_LOCATION (exp, make_location_t (s->loc)); |
| |
| /* If the extended syntax was not used, mark the ASM_EXPR as being an |
| ASM_INPUT expression instead of an ASM_OPERAND with no operands. */ |
| if (s->args == NULL && s->clobbers == NULL) |
| ASM_INPUT_P (exp) = 1; |
| |
| /* All asm statements are assumed to have a side effect. As a future |
| optimization, this could be unset when building in release mode. */ |
| ASM_VOLATILE_P (exp) = 1; |
| |
| /* If the function has been annotated with `pragma(inline)', then mark |
| the asm expression as being inline as well. */ |
| if (this->func_->inlining == PINLINEalways) |
| ASM_INLINE_P (exp) = 1; |
| |
| add_stmt (exp); |
| } |
| |
| /* Import symbols from another module. */ |
| |
| void visit (ImportStatement *s) |
| { |
| if (s->imports == NULL) |
| return; |
| |
| for (size_t i = 0; i < s->imports->length; i++) |
| { |
| Dsymbol *dsym = (*s->imports)[i]; |
| |
| if (dsym != NULL) |
| build_decl_tree (dsym); |
| } |
| } |
| }; |
| |
| /* Main entry point for the IRVisitor interface to generate |
| code for the body of function FD. */ |
| |
| void |
| build_function_body (FuncDeclaration *fd) |
| { |
| IRVisitor v = IRVisitor (fd); |
| location_t saved_location = input_location; |
| input_location = make_location_t (fd->loc); |
| v.build_stmt (fd->fbody); |
| input_location = saved_location; |
| } |