/* toir.cc -- Lower D frontend statements to GCC trees.
Copyright (C) 2006-2023 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
. */
#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 &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 ();
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 labels;
d_function_chain->labels->traverse &,
&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 (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 ();
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 % into % block");
else if (ent->in_catch_scope)
error_at (make_location_t (from->loc),
"cannot % into % 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 % into % block");
else
error_at (location, "cannot % into % block");
}
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 ::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 ();
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);
}
/* Generate and set a new continue label for the current unrolled loop. */
void push_unrolled_continue_label (UnrolledLoopStatement *s)
{
this->push_continue_label (s);
}
/* Finish with the continue label for the unrolled loop. */
void pop_unrolled_continue_label (UnrolledLoopStatement *s)
{
Statement *stmt = s->getRelatedLabeled ();
d_label_entry *ent = d_function_chain->labels->get (stmt);
gcc_assert (ent != NULL && ent->bc_label == true);
this->pop_continue_label (TREE_VEC_ELT (ent->label, bc_continue));
/* Remove the continue label from the label htab, as a new one must be
inserted at the end of every unrolled loop. */
ent->label = TREE_VEC_ELT (ent->label, bc_break);
}
/* Visitor interfaces. */
/* This should be overridden by each statement class. */
void visit (Statement *) final override
{
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 *) final override
{
}
/* If statements provide simple conditional execution of statements. */
void visit (IfStatement *s) final override
{
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, don't do code generation when the condition
is `if (__ctfe)', as that is always false at run-time. */
if (s->ifbody && !s->isIfCtfeBlock ())
{
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 *) final override
{
}
/* 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 *) final override
{
gcc_unreachable ();
}
/* Do while statments implement simple loops. The body is executed, then
the condition is evaluated. */
void visit (DoStatement *s) final override
{
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) final override
{
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 *) final override
{
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 *) final override
{
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) final override
{
if (s->ident)
{
/* The break label may actually be some levels up.
eg: on a try/finally wrapping a loop. */
LabelDsymbol *sym = this->func_->searchLabel (s->ident, s->loc);
LabelStatement *label = sym->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) final override
{
if (s->ident)
{
LabelDsymbol *sym = this->func_->searchLabel (s->ident, s->loc);
LabelStatement *label = sym->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) final override
{
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) final override
{
LabelDsymbol *sym;
if (this->is_return_label (s->ident))
sym = this->func_->returnLabel;
else
sym = this->func_->searchLabel (s->ident, s->loc);
/* 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) final override
{
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.
It is not lowered during codegen. */
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) final override
{
/* 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) final override
{
/* 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) final override
{
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) final override
{
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) final override
{
/* A throw SwitchError statement gets turned into a library call.
The call is wrapped in the enclosed expression. */
gcc_assert (s->exp != NULL);
add_stmt (build_expr (s->exp));
}
/* A return statement exits the current function and supplies its return
value, if the return type is not void. */
void visit (ReturnStatement *s) final override
{
if (s->exp == NULL || s->exp->type->toBasetype ()->ty == 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 == 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_->isNRVO () && 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->isCallExp ()
? 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 ); */
add_stmt (build_expr_dtor (s->exp));
}
else
{
/* Generate: ( = expr, return ); */
tree expr = build_expr_dtor (s->exp);
tree init = stabilize_expr (&expr);
expr = convert_for_rvalue (expr, s->exp->type, type);
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 == 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) final override
{
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) final override
{
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) final override
{
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)
{
this->push_unrolled_continue_label (s);
this->build_stmt (statement);
this->pop_unrolled_continue_label (s);
}
}
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) final override
{
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) final override
{
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) final override
{
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) final override
{
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) final override
{
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 *) final override
{
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 *) final override
{
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) final override
{
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 == PINLINE::always)
ASM_INLINE_P (exp) = 1;
add_stmt (exp);
}
/* Import symbols from another module. */
void visit (ImportStatement *s) final override
{
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;
}